29 Commits

Author SHA1 Message Date
Jedd Morgan f0be78888f Merge pull request #97 from specklesystems/2.15-deps
2.15 deps
2023-08-31 13:13:12 +01:00
Jedd Morgan 892e77e0ff Bumped version number 2023-08-31 12:50:20 +01:00
Jedd Morgan 8a9c21f979 Exposed toggle for attaching speckle properties 2023-08-31 12:30:50 +01:00
Jedd Morgan aa46d49620 reformat code 2023-08-31 12:08:35 +01:00
Jedd Morgan 5d92e12eff Bumped core to 2.15.1 stable 2023-08-31 12:08:14 +01:00
Jedd Morgan e1a3ae6b9c urp test 2023-07-11 23:32:01 +01:00
Jedd Morgan d44ead55db Added support for translucency with the standard shader 2023-07-11 22:55:18 +01:00
Jedd Morgan a3fb10570e deps: 2.15.0-alpha2 2023-06-29 00:26:21 +01:00
Jedd Morgan 57154a6fb8 deps: bumped core to 2.15.0-alpha 2023-06-26 22:20:33 +01:00
Jedd Morgan 5db1f450af Merge pull request #93 from specklesystems/jrm/traversal-refactor
implementation of traversal refactor
2023-06-26 22:14:52 +01:00
Jedd Morgan 62875c0a27 Editor progress bar now works 2023-06-22 17:23:57 +01:00
Jedd Morgan b3c6b59721 Cancellation fixes and editor progress 2023-06-14 17:08:51 +01:00
Jedd Morgan d9f7895b3f Improved Editor Cancellation 2023-06-11 14:39:14 +01:00
Jedd Morgan dc58f6b0b0 wip collections 2023-06-08 23:17:50 +01:00
Jedd Morgan 556a7eaddf Merge remote-tracking branch 'origin/main' into jrm/traversal-refactor 2023-06-07 17:09:27 +01:00
Jedd Morgan 39a79be700 Merge branch 'main' into jrm/traversal-refactor 2023-06-07 17:08:58 +01:00
Jedd Morgan 73ef71f4fd Merge pull request #95 from specklesystems/core/2.14.2
Bumped core to 2.14.2
2023-06-07 15:21:13 +01:00
Jedd Morgan 6a07ecfd5b bumped version number 2023-06-07 15:20:57 +01:00
Jedd Morgan e512df9c82 Bumped core to 2.14.2 2023-06-07 13:54:10 +01:00
Jedd Morgan 59221e89ba Merge pull request #94 from specklesystems/jrm/update-core-2.13.0
Added IsMultiplayer
2023-04-22 01:21:09 +01:00
Jedd Morgan 32533ddb21 Added IsMultiplayer 2023-04-22 01:20:08 +01:00
Jedd Morgan fc537e137f Merge pull request #92 from specklesystems/jrm/update-core-2.13.0
2.13 update
2023-04-22 00:15:01 +01:00
Jedd Morgan 2aaaf6572c Final polish 2023-04-22 00:14:40 +01:00
Jedd Morgan a89e4cdfe7 wip implementation of traversal refactor 2023-04-21 22:34:59 +01:00
Jedd Morgan 9a89255ae8 Fixed build warnings 2023-04-11 21:14:35 +01:00
Jedd Morgan 9f3145eb19 2.13 testing 2023-04-11 17:55:59 +01:00
Jedd Morgan 5dde0dae60 Configure await false fixes deadlocks 2023-04-06 22:50:47 +01:00
Jedd Morgan 0794c53b24 Converter now builds 2023-04-04 20:20:07 +01:00
Jedd Morgan 2973087beb Update objects & core dlls to 2.13 2023-04-04 19:59:48 +01:00
189 changed files with 9273 additions and 3190 deletions
+210
View File
@@ -0,0 +1,210 @@
root = true
# Don't use tabs for indentation.
[*]
indent_style = space
# Microsoft .NET properties
csharp_using_directive_placement = outside_namespace:silent
dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:none
dotnet_style_parentheses_in_other_binary_operators = never_if_unnecessary:none
dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:none
# ReSharper properties
resharper_align_linq_query = false
resharper_align_multiline_calls_chain = true
resharper_align_multiline_extends_list = true
resharper_align_multiline_for_stmt = true
resharper_align_multiline_parameter = true
resharper_align_multiple_declaration = true
resharper_align_multline_type_parameter_constrains = true
resharper_align_multline_type_parameter_list = true
resharper_braces_for_dowhile = not_required
resharper_braces_for_fixed = not_required
resharper_braces_for_lock = not_required
resharper_braces_for_using = not_required
resharper_csharp_align_multiline_calls_chain = false
resharper_csharp_align_multiline_extends_list = false
resharper_csharp_align_multiline_parameter = false
resharper_csharp_int_align_comments = false
resharper_csharp_outdent_commas = true
resharper_csharp_outdent_dots = false
resharper_csharp_wrap_after_declaration_lpar = true
resharper_csharp_wrap_after_invocation_lpar = true
resharper_csharp_wrap_arguments_style = chop_if_long
resharper_csharp_wrap_before_declaration_rpar = true
resharper_csharp_wrap_before_invocation_rpar = false
resharper_csharp_wrap_extends_list_style = chop_if_long
resharper_csharp_wrap_parameters_style = chop_if_long
resharper_force_attribute_style = join
resharper_indent_nested_fixed_stmt = false
resharper_indent_nested_foreach_stmt = true
resharper_indent_nested_for_stmt = true
resharper_indent_nested_lock_stmt = false
resharper_indent_nested_usings_stmt = false
resharper_indent_nested_while_stmt = true
resharper_int_align = false
resharper_int_align_nested_ternary = false
resharper_int_align_switch_expressions = false
resharper_keep_existing_declaration_block_arrangement = true
resharper_keep_existing_declaration_parens_arrangement = false
resharper_keep_existing_embedded_block_arrangement = true
resharper_keep_existing_enum_arrangement = true
resharper_keep_existing_expr_member_arrangement = true
resharper_keep_existing_initializer_arrangement = false
resharper_local_function_body = expression_body
resharper_max_attribute_length_for_same_line = 20
resharper_max_formal_parameters_on_line = 5
resharper_max_initializer_elements_on_line = 0
resharper_max_invocation_arguments_on_line = 5
resharper_outdent_binary_ops = true
resharper_outdent_dots = false
resharper_place_constructor_initializer_on_same_line = false
resharper_place_simple_initializer_on_single_line = true
resharper_prefer_explicit_discard_declaration = false
resharper_wrap_after_declaration_lpar = false
resharper_wrap_before_invocation_rpar = false
resharper_wrap_chained_binary_expressions = chop_if_long
resharper_wrap_chained_binary_patterns = chop_if_long
resharper_wrap_chained_method_calls = chop_if_long
resharper_xmldoc_indent_text = ZeroIndent
# Standard properties
insert_final_newline = true
# (Please don't specify an indent_size here; that has too many unintended consequences.)
# Code files
[*.{cs,csx,vb,vbx}]
indent_size = 4
charset = utf-8
# Xml project files
[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}]
indent_size = 2
space_after_last_pi_attribute = false
# Xml config files
[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}]
indent_size = 2
space_after_last_pi_attribute = false
# JSON files
[*.json]
indent_size = 2
# Dotnet code style settings:
[*.{cs,vb}]
# Sort using and Import directives with System.* appearing first
dotnet_sort_system_directives_first = true
dotnet_separate_import_directive_groups = false
# Avoid "this." and "Me." if not necessary
dotnet_style_qualification_for_field = false:suggestion
dotnet_style_qualification_for_property = false:suggestion
dotnet_style_qualification_for_method = false:suggestion
dotnet_style_qualification_for_event = false:suggestion
# Use language keywords instead of framework type names for type references
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
dotnet_style_predefined_type_for_member_access = true:suggestion
# Parentheses preferences
dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:silent
dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:silent
dotnet_style_parentheses_in_other_binary_operators = never_if_unnecessary:silent
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
# Modifier preferences
dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
dotnet_style_readonly_field = true:suggestion
# Expression-level preferences
dotnet_style_object_initializer = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_explicit_tuple_names = true:suggestion
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
dotnet_style_prefer_inferred_tuple_names = true:suggestion
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
dotnet_style_prefer_auto_properties = true:warning
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
dotnet_style_prefer_conditional_expression_over_return = true:silent
# CSharp code style settings:
[*.cs]
# Prefer "var" everywhere
csharp_style_var_elsewhere = false:none
csharp_style_var_for_built_in_types = false:none
csharp_style_var_when_type_is_apparent = false:none
# Prefer method-like constructs to have a block body
csharp_style_expression_bodied_methods = false:suggestion
csharp_style_expression_bodied_constructors = false:suggestion
csharp_style_expression_bodied_operators = true:suggestion
# Prefer property-like constructs to have an expression-body
csharp_style_expression_bodied_properties = true:suggestion
csharp_style_expression_bodied_indexers = true:suggestion
csharp_style_expression_bodied_accessors = true:suggestion
# Suggest more modern language features when available
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
csharp_style_inlined_variable_declaration = true:suggestion
csharp_style_throw_expression = true:suggestion
csharp_style_conditional_delegate_call = true:suggestion
# Newline settings
csharp_new_line_before_open_brace = all
csharp_new_line_before_else = true
csharp_new_line_before_catch = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_members_in_anonymous_types = true
# Space preferences
csharp_space_after_cast = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_around_binary_operators = before_and_after
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
# Wrapping preferences
csharp_preserve_single_line_statements = true
csharp_preserve_single_line_blocks = true
# Analyzer settings
dotnet_diagnostic.ide0055.severity = none
# Maintainability rules
dotnet_diagnostic.ca1501.severity = warning
dotnet_diagnostic.ca1502.severity = warning
dotnet_diagnostic.ca1505.severity = warning
dotnet_diagnostic.ca1506.severity = warning
dotnet_diagnostic.ca1507.severity = warning
dotnet_diagnostic.ca1508.severity = warning
dotnet_diagnostic.ca1509.severity = warning
# Misc
dotnet_diagnostic.ca1051.severity = none # Do not declare visible instance fields
dotnet_diagnostic.ca1062.severity = none # Public method must check all parameters for null
dotnet_diagnostic.ca1707.severity = none # Remove underscores in names
dotnet_analyzer_diagnostic.category-globalization.severity = none
dotnet_analyzer_diagnostic.category-security.severity = none
dotnet_analyzer_diagnostic.category-interoperability.severity = none
dotnet_analyzer_diagnostic.category-singlefile.severity = none
[*.{appxmanifest,asax,ascx,aspx,axaml,build,c,c++,cc,cginc,compute,cp,cpp,cs,cshtml,cu,cuh,cxx,dtd,fs,fsi,fsscript,fsx,fx,fxh,h,hh,hlsl,hlsli,hlslinc,hpp,hxx,inc,inl,ino,ipp,ixx,master,ml,mli,mpp,mq4,mq5,mqh,nuspec,paml,razor,resw,resx,shader,skin,tpp,usf,ush,vb,xaml,xamlx,xoml,xsd}]
indent_style = space
indent_size = 4
tab_width = 2
+2 -2
View File
@@ -45,14 +45,14 @@ ExportedObj/
*.pidb
*.booproj
*.svd
*.pdb
#*.pdb
*.mdb
*.opendb
*.VC.db
# Unity3D generated meta files
*.pidb.meta
*.pdb.meta
#*.pdb.meta
*.mdb.meta
# Unity3D generated file on crash reports
@@ -1,3 +1,4 @@
using System;
using System.Collections;
using System.Threading.Tasks;
using Speckle.ConnectorUnity;
@@ -7,6 +8,7 @@ using Speckle.Core.Credentials;
using Speckle.Core.Transports;
using UnityEngine;
[AddComponentMenu("Speckle/Extras/Manual Receiver")]
[RequireComponent(typeof(RecursiveConverter))]
public class ManualReceive : MonoBehaviour
{
@@ -30,7 +32,8 @@ public class ManualReceive : MonoBehaviour
if(Time.timeSinceLevelLoad > 20) yield return null;
Receive();
}
[ContextMenu(nameof(Receive))]
public void Receive()
{
var account = new Account()
@@ -48,14 +51,17 @@ public class ManualReceive : MonoBehaviour
objectId,
remoteTransport: transport,
localTransport: localTransport,
onErrorAction: (m, e)=> Debug.LogError(m + e),
onErrorAction: (m, e) => Debug.LogError(m + e),
disposeTransports: true
);
if (@base == null) throw new Exception("received data was null!");
Dispatcher.Instance().Enqueue(() =>
{
var parentObject = new GameObject(name);
receiver.RecursivelyConvertToNative(@base, parentObject.transform);
receiver.RecursivelyConvertToNative_Sync(@base, parentObject.transform);
Debug.Log($"Receive {objectId} completed");
});
+79
View File
@@ -0,0 +1,79 @@
using System.Threading;
using System.Threading.Tasks;
using Speckle.ConnectorUnity.Components;
using Speckle.Core.Api;
using Speckle.Core.Models;
using Speckle.Core.Transports;
using UnityEngine;
namespace Extra
{
/// <summary>
/// Script used to generate streams for performance benchmarking in other hostApps.
/// Will send several several commits with a varying number of copies on a GameObject (with children)
/// </summary>
[ExecuteAlways]
[RequireComponent(typeof(SpeckleSender))]
public sealed class PerformanceTestSender : MonoBehaviour
{
[Range(0, 100)]
public int numberOfIterations = 10;
public Vector3 translation = Vector3.forward * 100;
public GameObject objectToSend;
private SpeckleSender sender;
private void Awake()
{
sender = GetComponent<SpeckleSender>();
}
public async Task SendIterations()
{
GameObject go = new GameObject();
for (int i = 0; i < numberOfIterations; i++)
{
Instantiate(objectToSend, translation * i, Quaternion.identity, go.transform);
Base b = sender.Converter.RecursivelyConvertToSpeckle(go, _ => true);
await Send(b, $"{i}");
}
Debug.Log("Done!");
}
private async Task<string> Send(Base data, string branchName)
{
var client = sender.Account.Client;
var stream = sender.Stream.Selected;
ServerTransport transport = new ServerTransport(sender.Account.Selected, stream!.id);
await client.BranchCreate(new BranchCreateInput(){streamId = stream.id, name = branchName});
return await SpeckleSender.SendDataAsync(CancellationToken.None,
remoteTransport: transport,
data: data,
client: client!,
branchName: branchName,
createCommit: true,
onProgressAction: null,
onErrorAction: (m, e) => throw e);
}
}
#if UNITY_EDITOR
[UnityEditor.CustomEditor(typeof(PerformanceTestSender))]
public sealed class PerformanceTestSenderEditor : UnityEditor.Editor
{
public override async void OnInspectorGUI()
{
DrawDefaultInspector();
if (GUILayout.Button("Create and send"))
{
await ((PerformanceTestSender)target).SendIterations();
}
}
}
#endif
}
@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 2dd598fed5008c44a815ba09e81a2d19
guid: c98b93e32c844fb488992e2e376447a1
MonoImporter:
externalObjects: {}
serializedVersion: 2
+13 -13
View File
@@ -2,13 +2,11 @@ using System;
using System.Collections.Concurrent;
using System.Collections.Immutable;
using System.Linq;
using System.Runtime.CompilerServices;
using Speckle.ConnectorUnity;
using UnityEditor.Experimental;
using UnityEngine;
using UnityEngine.Events;
[RequireComponent(typeof(Sender)), ExecuteAlways]
[Obsolete]
public class SendChildrenToSpeckle : MonoBehaviour
{
public LayerMask layerMask;
@@ -22,7 +20,7 @@ public class SendChildrenToSpeckle : MonoBehaviour
{
sender = GetComponent<Sender>();
}
[ContextMenu(nameof(Send))]
public void Send()
{
@@ -30,30 +28,32 @@ public class SendChildrenToSpeckle : MonoBehaviour
.Where(t => t != this.transform)
.Select(o => o.gameObject)
.ToImmutableHashSet();
Debug.Log("starting send...");
sender.Send(streamId, selected, null, branchName, createCommit,
sender.Send(
streamId,
selected,
null,
branchName,
createCommit,
onErrorAction: OnError,
onProgressAction: OnProgress,
onDataSentAction: OnSent);
onDataSentAction: OnSent
);
}
private void OnSent(string objectId)
{
Debug.Log($"Data sent {objectId}", this);
}
private void OnError(string message, Exception e)
{
Debug.LogError($"Error while sending {message} \n {e}", this);
}
private void OnProgress(ConcurrentDictionary<string, int> dict)
{
Debug.Log($"progress was made", this);
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
{
"name": "Speckle.Extra",
"references":[ "GUID:eed1b8b83e2c0074d9e5de2348e3ff72", "GUID:e6adfdc4e436206479f48eafc82f32b5" ]
"references":[ "GUID:eed1b8b83e2c0074d9e5de2348e3ff72", "GUID:e6adfdc4e436206479f48eafc82f32b5", "GUID:d274441ecc3eb3f43b093eec1503d681", "GUID:50d889142fdf9de4b8501c6eaa4b3225" ]
}
+218 -193
View File
@@ -1,219 +1,244 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Speckle.Core.Api;
using Speckle.Core.Logging;
using UnityEngine;
using UnityEngine.UI;
using Text = UnityEngine.UI.Text;
namespace Speckle.ConnectorUnity
{
public class InteractionLogic : MonoBehaviour
{
private Receiver receiver;
public void InitReceiver(Stream stream, bool autoReceive)
[Obsolete]
public class InteractionLogic : MonoBehaviour
{
gameObject.name = $"receiver-{stream.id}-{Guid.NewGuid().ToString()}";
InitRemove();
private Receiver _receiver;
receiver = gameObject.AddComponent<Receiver>();
receiver.Stream = stream;
var btn = gameObject.transform.Find("Btn").GetComponentInChildren<Button>();
var streamText = gameObject.transform.Find("StreamText").GetComponentInChildren<Text>();
var statusText = gameObject.transform.Find("StatusText").GetComponentInChildren<Text>();
var branchesDropdown = gameObject.transform.Find("Dropdown").GetComponentInChildren<Dropdown>();
var receiveProgress = btn.GetComponentInChildren<Slider>();
receiveProgress.gameObject.SetActive(false); //hide
//populate branches
branchesDropdown.options.Clear();
List<Branch> branches = receiver.Stream.branches.items;
branches.Reverse();
foreach (Branch branch in branches)
{
branchesDropdown.options.Add(new Dropdown.OptionData(branch.name.Replace(' ', '\u00A0')));
}
//trigger ui refresh, maybe there's a better method
branchesDropdown.value = -1;
branchesDropdown.value = 0;
branchesDropdown.onValueChanged.AddListener(index =>
{
if (index == -1)
return;
receiver.BranchName = branches[index].name;
});
receiver.Init(stream.id, autoReceive, true,
onDataReceivedAction: (go) =>
public void InitReceiver(Stream stream, bool autoReceive)
{
statusText.text = $"Received {go.name}";
MakeButtonsInteractable(true);
receiveProgress.value = 0;
receiveProgress.gameObject.SetActive(false);
gameObject.name = $"receiver-{stream.id}-{Guid.NewGuid().ToString()}";
InitRemove();
AddComponents(go);
},
onTotalChildrenCountKnown: (count) => { receiver.TotalChildrenCount = count; },
onProgressAction: (dict) =>
{
//Run on a dispatcher as GOs can only be retrieved on the main thread
Dispatcher.Instance().Enqueue(() =>
{
var val = dict.Values.Average() / receiver.TotalChildrenCount;
receiveProgress.gameObject.SetActive(true);
receiveProgress.value = (float) val;
});
});
_receiver = gameObject.AddComponent<Receiver>();
_receiver.Stream = stream;
var btn = gameObject.transform.Find("Btn").GetComponentInChildren<Button>();
var streamText = gameObject.transform.Find("StreamText").GetComponentInChildren<Text>();
var statusText = gameObject.transform.Find("StatusText").GetComponentInChildren<Text>();
var branchesDropdown = gameObject.transform
.Find("Dropdown")
.GetComponentInChildren<Dropdown>();
var receiveProgress = btn.GetComponentInChildren<Slider>();
receiveProgress.gameObject.SetActive(false); //hide
streamText.text = $"Stream: {stream.name}\nId: {stream.id} - Auto: {autoReceive}";
btn.onClick.AddListener(() =>
{
statusText.text = "Receiving...";
MakeButtonsInteractable(false);
receiver.Receive();
});
}
/// <summary>
/// Recursively adds custom components to all children of a GameObject
/// </summary>
/// <param name="go"></param>
private void AddComponents(GameObject go)
{
for (var i = 0; i < go.transform.childCount; i++)
{
var child = go.transform.GetChild(i);
if (child.childCount > 0)
{
AddComponents(child.gameObject);
}
child.gameObject.AddComponent<Selectable>();
//Add extra Components
//var rigidbody = child.gameObject.AddComponent<Rigidbody>();
//rigidbody.mass = 10;
}
}
public void InitSender(Stream stream)
{
gameObject.name = $"sender-{stream.id}-{Guid.NewGuid().ToString()}";
InitRemove();
var sender = gameObject.AddComponent<Sender>();
var btn = gameObject.transform.Find("Btn").GetComponentInChildren<Button>();
var streamText = gameObject.transform.Find("StreamText").GetComponentInChildren<Text>();
var statusText = gameObject.transform.Find("StatusText").GetComponentInChildren<Text>();
btn.GetComponentInChildren<Text>().text = "Send";
statusText.text = "Ready to send";
var sendProgress = btn.GetComponentInChildren<Slider>();
sendProgress.gameObject.SetActive(false); //hide
streamText.text = $"Stream: {stream.name}\nId: {stream.id}";
btn.onClick.AddListener(() =>
{
var objs = SelectionManager.selectedObjects.Select(s => s.gameObject).ToImmutableHashSet();
if (!objs.Any())
{
statusText.text = $"No objects selected";
return;
}
MakeButtonsInteractable(false);
statusText.text = "Sending...";
try
{
sender.Send(stream.id, objs,
onProgressAction: (dict) =>
{
//Run on a dispatcher as GOs can only be retrieved on the main thread
Dispatcher.Instance().Enqueue(() =>
{
var val = dict.Values.Average() / objs.Count;
sendProgress.gameObject.SetActive(true);
sendProgress.value = (float) val;
});
},
onDataSentAction: (objectId) =>
{
Debug.Log($"Send operation completed, object id: {objectId}", this);
Dispatcher.Instance().Enqueue(() =>
{
MakeButtonsInteractable(true);
statusText.text = $"Sent {objectId}";
sendProgress.gameObject.SetActive(false); //hide
});
},
onErrorAction: (message, e) =>
{
Debug.LogError("Send operation Failed!", this);
Dispatcher.Instance().Enqueue(() =>
{
MakeButtonsInteractable(true);
statusText.text = $"Error {message}";
sendProgress.gameObject.SetActive(false); //hide
Debug.LogError(e, this);
});
});
}
catch(Exception e)
{
Dispatcher.Instance().Enqueue(() =>
//populate branches
branchesDropdown.options.Clear();
List<Branch> branches = _receiver.Stream.branches.items;
branches.Reverse();
foreach (Branch branch in branches)
{
MakeButtonsInteractable(true);
statusText.text = $"Error {e.Message}";
sendProgress.gameObject.SetActive(false); //hide
Debug.LogError(e, this);
branchesDropdown.options.Add(
new Dropdown.OptionData(branch.name.Replace(' ', '\u00A0'))
);
}
//trigger ui refresh, maybe there's a better method
branchesDropdown.value = -1;
branchesDropdown.value = 0;
branchesDropdown.onValueChanged.AddListener(index =>
{
if (index == -1)
return;
_receiver.BranchName = branches[index].name;
});
_receiver.Init(
stream.id,
autoReceive,
onDataReceivedAction: (go) =>
{
statusText.text = $"Received {go.name}";
MakeButtonsInteractable(true);
receiveProgress.value = 0;
receiveProgress.gameObject.SetActive(false);
AddComponents(go);
},
onTotalChildrenCountKnown: (count) =>
{
_receiver.TotalChildrenCount = count;
},
onProgressAction: (dict) =>
{
//Run on a dispatcher as GOs can only be retrieved on the main thread
Dispatcher
.Instance()
.Enqueue(() =>
{
var val = dict.Values.Average() / _receiver.TotalChildrenCount;
receiveProgress.gameObject.SetActive(true);
receiveProgress.value = (float)val;
});
}
);
streamText.text = $"Stream: {stream.name}\nId: {stream.id} - Auto: {autoReceive}";
btn.onClick.AddListener(() =>
{
statusText.text = "Receiving...";
MakeButtonsInteractable(false);
_receiver.Receive();
});
}
}
);
}
private void MakeButtonsInteractable(bool interactable)
{
var selectables = gameObject.transform.GetComponentsInChildren<UnityEngine.UI.Selectable>();
foreach (var selectable in selectables)
{
selectable.interactable = interactable;
}
}
private void InitRemove()
{
var close = gameObject.transform.Find("Close").GetComponentInChildren<Button>();
close.onClick.AddListener(() =>
{
//remove received geometry
if (receiver != null)
/// <summary>
/// Recursively adds custom components to all children of a GameObject
/// </summary>
/// <param name="go"></param>
private void AddComponents(GameObject go)
{
Destroy(receiver.ReceivedData);
for (var i = 0; i < go.transform.childCount; i++)
{
var child = go.transform.GetChild(i);
if (child.childCount > 0)
{
AddComponents(child.gameObject);
}
child.gameObject.AddComponent<Selectable>();
//Add extra Components
//var rigidbody = child.gameObject.AddComponent<Rigidbody>();
//rigidbody.mass = 10;
}
}
//update ui
GameObject.Find("_SpeckleExamples").GetComponent<SpeckleExamples>().RemoveStreamPrefab(gameObject);
public void InitSender(Stream stream)
{
gameObject.name = $"sender-{stream.id}-{Guid.NewGuid().ToString()}";
InitRemove();
//kill it
Destroy(gameObject);
});
var sender = gameObject.AddComponent<Sender>();
var btn = gameObject.transform.Find("Btn").GetComponentInChildren<Button>();
var streamText = gameObject.transform.Find("StreamText").GetComponentInChildren<Text>();
var statusText = gameObject.transform.Find("StatusText").GetComponentInChildren<Text>();
btn.GetComponentInChildren<Text>().text = "Send";
statusText.text = "Ready to send";
var sendProgress = btn.GetComponentInChildren<Slider>();
sendProgress.gameObject.SetActive(false); //hide
streamText.text = $"Stream: {stream.name}\nId: {stream.id}";
btn.onClick.AddListener(() =>
{
var objs = SelectionManager.selectedObjects
.Select(s => s.gameObject)
.ToImmutableHashSet();
if (!objs.Any())
{
statusText.text = $"No objects selected";
return;
}
MakeButtonsInteractable(false);
statusText.text = "Sending...";
try
{
sender.Send(
stream.id,
objs,
onProgressAction: (dict) =>
{
//Run on a dispatcher as GOs can only be retrieved on the main thread
Dispatcher
.Instance()
.Enqueue(() =>
{
var val = dict.Values.Average() / objs.Count;
sendProgress.gameObject.SetActive(true);
sendProgress.value = (float)val;
});
},
onDataSentAction: (objectId) =>
{
Debug.Log($"Send operation completed, object id: {objectId}", this);
Dispatcher
.Instance()
.Enqueue(() =>
{
MakeButtonsInteractable(true);
statusText.text = $"Sent {objectId}";
sendProgress.gameObject.SetActive(false); //hide
});
},
onErrorAction: (message, e) =>
{
Debug.LogError("Send operation Failed!", this);
Dispatcher
.Instance()
.Enqueue(() =>
{
MakeButtonsInteractable(true);
statusText.text = $"Error {message}";
sendProgress.gameObject.SetActive(false); //hide
Debug.LogError(e, this);
});
}
);
}
catch (Exception e)
{
Dispatcher
.Instance()
.Enqueue(() =>
{
MakeButtonsInteractable(true);
statusText.text = $"Error {e.Message}";
sendProgress.gameObject.SetActive(false); //hide
Debug.LogError(e, this);
});
}
});
}
private void MakeButtonsInteractable(bool interactable)
{
var selectables =
gameObject.transform.GetComponentsInChildren<UnityEngine.UI.Selectable>();
foreach (var selectable in selectables)
{
selectable.interactable = interactable;
}
}
private void InitRemove()
{
var close = gameObject.transform.Find("Close").GetComponentInChildren<Button>();
close.onClick.AddListener(() =>
{
//remove received geometry
if (_receiver != null)
{
Destroy(_receiver.ReceivedData);
}
//update ui
GameObject
.Find("_SpeckleExamples")
.GetComponent<SpeckleExamples>()
.RemoveStreamPrefab(gameObject);
//kill it
Destroy(gameObject);
});
}
}
}
}
}
+113 -113
View File
@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using Speckle.Core.Credentials;
@@ -8,125 +9,124 @@ using Stream = Speckle.Core.Api.Stream;
namespace Speckle.ConnectorUnity
{
public class SpeckleExamples : MonoBehaviour
{
public Text SelectStreamText;
public Text DetailsStreamText;
public Dropdown StreamSelectionDropdown;
public Button AddReceiverBtn;
public Toggle AutoReceiveToggle;
public Button AddSenderBtn;
public GameObject StreamPanel;
public Canvas StreamsCanvas;
private List<Stream> StreamList = null;
private Stream SelectedStream = null;
private List<GameObject> StreamPanels = new List<GameObject>();
async void Start()
[Obsolete]
public class SpeckleExamples : MonoBehaviour
{
if (SelectStreamText == null || StreamSelectionDropdown == null)
{
Debug.Log("Please set all input fields on _SpeckleExamples");
return;
}
public Text SelectStreamText;
public Text DetailsStreamText;
public Dropdown StreamSelectionDropdown;
public Button AddReceiverBtn;
public Toggle AutoReceiveToggle;
public Button AddSenderBtn;
public GameObject StreamPanel;
public Canvas StreamsCanvas;
var defaultAccount = AccountManager.GetDefaultAccount();
if (defaultAccount == null)
{
Debug.Log("Please set a default account in SpeckleManager");
return;
}
private List<Stream> StreamList = null;
private Stream SelectedStream = null;
private List<GameObject> StreamPanels = new List<GameObject>();
SelectStreamText.text = $"Select a stream on {defaultAccount.serverInfo.name}:";
async void Start()
{
if (SelectStreamText == null || StreamSelectionDropdown == null)
{
Debug.Log("Please set all input fields on _SpeckleExamples");
return;
}
StreamList = await Streams.List(30);
if (!StreamList.Any())
{
Debug.Log("There are no streams in your account, please create one online.");
return;
}
var defaultAccount = AccountManager.GetDefaultAccount();
if (defaultAccount == null)
{
Debug.Log("Please set a default account in SpeckleManager");
return;
}
StreamSelectionDropdown.options.Clear();
foreach (var stream in StreamList)
{
StreamSelectionDropdown.options.Add(new Dropdown.OptionData(stream.name + " - " + stream.id));
}
SelectStreamText.text = $"Select a stream on {defaultAccount.serverInfo.name}:";
StreamSelectionDropdown.onValueChanged.AddListener(StreamSelectionChanged);
//trigger ui refresh, maybe there's a better method
StreamSelectionDropdown.value = -1;
StreamSelectionDropdown.value = 0;
StreamList = await Streams.List(30);
if (!StreamList.Any())
{
Debug.Log("There are no streams in your account, please create one online.");
return;
}
StreamSelectionDropdown.options.Clear();
foreach (var stream in StreamList)
{
StreamSelectionDropdown.options.Add(
new Dropdown.OptionData(stream.name + " - " + stream.id)
);
}
AddReceiverBtn.onClick.AddListener(AddReceiver);
AddSenderBtn.onClick.AddListener(AddSender);
StreamSelectionDropdown.onValueChanged.AddListener(StreamSelectionChanged);
//trigger ui refresh, maybe there's a better method
StreamSelectionDropdown.value = -1;
StreamSelectionDropdown.value = 0;
AddReceiverBtn.onClick.AddListener(AddReceiver);
AddSenderBtn.onClick.AddListener(AddSender);
}
public void StreamSelectionChanged(int index)
{
if (index == -1)
return;
SelectedStream = StreamList[index];
DetailsStreamText.text =
$"Description: {SelectedStream.description}\n"
+ $"Link sharing on: {SelectedStream.isPublic}\n"
+ $"Role: {SelectedStream.role}\n"
+ $"Collaborators: {SelectedStream.collaborators.Count}\n"
+ $"Id: {SelectedStream.id}";
}
// Shows how to create a new Receiver from code and then pull data manually
// Created receivers are added to a List of Receivers for future use
private async void AddReceiver()
{
var autoReceive = AutoReceiveToggle.isOn;
var stream = await Streams.Get(SelectedStream.id, 30);
var streamPrefab = Instantiate(StreamPanel, new Vector3(0, 0, 0), Quaternion.identity);
//set position
streamPrefab.transform.SetParent(StreamsCanvas.transform);
var rt = streamPrefab.GetComponent<RectTransform>();
rt.anchoredPosition = new Vector3(-10, -110 - StreamPanels.Count * 110, 0);
streamPrefab.AddComponent<InteractionLogic>().InitReceiver(stream, autoReceive);
StreamPanels.Add(streamPrefab);
}
private async void AddSender()
{
var stream = await Streams.Get(SelectedStream.id, 10);
var streamPrefab = Instantiate(StreamPanel, new Vector3(0, 0, 0), Quaternion.identity);
streamPrefab.transform.SetParent(StreamsCanvas.transform);
var rt = streamPrefab.GetComponent<RectTransform>();
rt.anchoredPosition = new Vector3(-10, -110 - StreamPanels.Count * 110, 0);
streamPrefab.AddComponent<InteractionLogic>().InitSender(stream);
StreamPanels.Add(streamPrefab);
}
public void RemoveStreamPrefab(GameObject streamPrefab)
{
StreamPanels.RemoveAt(StreamPanels.FindIndex(x => x.name == streamPrefab.name));
ReorderStreamPrefabs();
}
private void ReorderStreamPrefabs()
{
for (var i = 0; i < StreamPanels.Count; i++)
{
var rt = StreamPanels[i].GetComponent<RectTransform>();
rt.anchoredPosition = new Vector3(-10, -110 - i * 110, 0);
}
}
}
public void StreamSelectionChanged(int index)
{
if (index == -1)
return;
SelectedStream = StreamList[index];
DetailsStreamText.text =
$"Description: {SelectedStream.description}\n" +
$"Link sharing on: {SelectedStream.isPublic}\n" +
$"Role: {SelectedStream.role}\n" +
$"Collaborators: {SelectedStream.collaborators.Count}\n" +
$"Id: {SelectedStream.id}";
}
// Shows how to create a new Receiver from code and then pull data manually
// Created receivers are added to a List of Receivers for future use
private async void AddReceiver()
{
var autoReceive = AutoReceiveToggle.isOn;
var stream = await Streams.Get(SelectedStream.id, 30);
var streamPrefab = Instantiate(StreamPanel, new Vector3(0, 0, 0),
Quaternion.identity);
//set position
streamPrefab.transform.SetParent(StreamsCanvas.transform);
var rt = streamPrefab.GetComponent<RectTransform>();
rt.anchoredPosition = new Vector3(-10, -110 - StreamPanels.Count * 110, 0);
streamPrefab.AddComponent<InteractionLogic>().InitReceiver(stream, autoReceive);
StreamPanels.Add(streamPrefab);
}
private async void AddSender()
{
var stream = await Streams.Get(SelectedStream.id, 10);
var streamPrefab = Instantiate(StreamPanel, new Vector3(0, 0, 0),
Quaternion.identity);
streamPrefab.transform.SetParent(StreamsCanvas.transform);
var rt = streamPrefab.GetComponent<RectTransform>();
rt.anchoredPosition = new Vector3(-10, -110 - StreamPanels.Count * 110, 0);
streamPrefab.AddComponent<InteractionLogic>().InitSender(stream);
StreamPanels.Add(streamPrefab);
}
public void RemoveStreamPrefab(GameObject streamPrefab)
{
StreamPanels.RemoveAt(StreamPanels.FindIndex(x => x.name == streamPrefab.name));
ReorderStreamPrefabs();
}
private void ReorderStreamPrefabs()
{
for (var i = 0; i < StreamPanels.Count; i++)
{
var rt = StreamPanels[i].GetComponent<RectTransform>();
rt.anchoredPosition = new Vector3(-10, -110 - i * 110, 0);
}
}
}
}
}
+226 -199
View File
@@ -849,6 +849,192 @@ CanvasRenderer:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 160171836}
m_CullTransparentMesh: 1
--- !u!1 &161249242
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 161249247}
- component: {fileID: 161249246}
- component: {fileID: 161249245}
- component: {fileID: 161249244}
- component: {fileID: 161249243}
m_Layer: 0
m_Name: Speckle Connector
m_TagString: Untagged
m_Icon: {fileID: 2800000, guid: ee2ed9d8fff3a1d4db5590491978062e, type: 3}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!114 &161249243
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 161249242}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: b95e704835cc48444b81e33c978f6f7f, type: 3}
m_Name:
m_EditorClassIdentifier:
<Account>k__BackingField:
rid: 1485638386691080198
<Stream>k__BackingField:
rid: 1485638386691080199
<Branch>k__BackingField:
rid: 1485638386691080200
OnBranchSelectionChange:
m_PersistentCalls:
m_Calls: []
OnErrorAction:
m_PersistentCalls:
m_Calls: []
OnSendProgressAction:
m_PersistentCalls:
m_Calls: []
references:
version: 2
RefIds:
- rid: 1485638386691080198
type: {class: AccountSelection, ns: Speckle.ConnectorUnity.Wrappers.Selection, asm: Speckle.ConnectorUnity.Wrappers}
data:
selectedIndex: 1
- rid: 1485638386691080199
type: {class: StreamSelection, ns: Speckle.ConnectorUnity.Wrappers.Selection, asm: Speckle.ConnectorUnity.Wrappers}
data:
selectedIndex: 0
<StreamsLimit>k__BackingField: 50
<AccountSelection>k__BackingField:
rid: 1485638386691080198
- rid: 1485638386691080200
type: {class: BranchSelection, ns: Speckle.ConnectorUnity.Wrappers.Selection, asm: Speckle.ConnectorUnity.Wrappers}
data:
selectedIndex: 0
<BranchesLimit>k__BackingField: 100
<CommitsLimit>k__BackingField: 0
<StreamSelection>k__BackingField:
rid: 1485638386691080199
--- !u!114 &161249244
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 161249242}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 942bf0cb27c5c5045bc4cbb7fc0fad71, type: 3}
m_Name:
m_EditorClassIdentifier:
url:
--- !u!114 &161249245
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 161249242}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 0bc895f6cb37b674995dc13b79783c55, type: 3}
m_Name:
m_EditorClassIdentifier:
<Account>k__BackingField:
rid: 1485638386691080194
<Stream>k__BackingField:
rid: 1485638386691080195
<Branch>k__BackingField:
rid: 1485638386691080196
<Commit>k__BackingField:
rid: 1485638386691080197
OnCommitSelectionChange:
m_PersistentCalls:
m_Calls: []
OnReceiveProgressAction:
m_PersistentCalls:
m_Calls: []
OnErrorAction:
m_PersistentCalls:
m_Calls: []
OnTotalChildrenCountKnown:
m_PersistentCalls:
m_Calls: []
OnComplete:
m_PersistentCalls:
m_Calls: []
references:
version: 2
RefIds:
- rid: 1485638386691080194
type: {class: AccountSelection, ns: Speckle.ConnectorUnity.Wrappers.Selection, asm: Speckle.ConnectorUnity.Wrappers}
data:
selectedIndex: 0
- rid: 1485638386691080195
type: {class: StreamSelection, ns: Speckle.ConnectorUnity.Wrappers.Selection, asm: Speckle.ConnectorUnity.Wrappers}
data:
selectedIndex: 0
<StreamsLimit>k__BackingField: 50
<AccountSelection>k__BackingField:
rid: 1485638386691080194
- rid: 1485638386691080196
type: {class: BranchSelection, ns: Speckle.ConnectorUnity.Wrappers.Selection, asm: Speckle.ConnectorUnity.Wrappers}
data:
selectedIndex: 0
<BranchesLimit>k__BackingField: 100
<CommitsLimit>k__BackingField: 25
<StreamSelection>k__BackingField:
rid: 1485638386691080195
- rid: 1485638386691080197
type: {class: CommitSelection, ns: Speckle.ConnectorUnity.Wrappers.Selection, asm: Speckle.ConnectorUnity.Wrappers}
data:
selectedIndex: 0
<BranchSelection>k__BackingField:
rid: 1485638386691080196
--- !u!114 &161249246
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 161249242}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: ed6cbf9ce4dca0349997d163ec9bce7e, type: 3}
m_Name:
m_EditorClassIdentifier:
<ConverterInstance>k__BackingField:
rid: 1485638386691080193
<AssetCache>k__BackingField: {fileID: 1438814813}
references:
version: 2
RefIds:
- rid: 1485638386691080193
type: {class: ConverterUnity, ns: Objects.Converter.Unity, asm: Objects.Converter}
data:
shouldConvertViews: 0
shouldAttachProperties: 1
<OpaqueMaterialShader>k__BackingField: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0}
<TranslucentMaterialShader>k__BackingField: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0}
<ModelUnits>k__BackingField: m
--- !u!4 &161249247
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 161249242}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_RootOrder: 9
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &194696812
GameObject:
m_ObjectHideFlags: 0
@@ -928,165 +1114,6 @@ CanvasRenderer:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 194696812}
m_CullTransparentMesh: 1
--- !u!1 &218987857
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 218987861}
- component: {fileID: 218987860}
- component: {fileID: 218987859}
- component: {fileID: 218987858}
m_Layer: 0
m_Name: Speckle Connector
m_TagString: Untagged
m_Icon: {fileID: 2800000, guid: ee2ed9d8fff3a1d4db5590491978062e, type: 3}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!114 &218987858
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 218987857}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: b95e704835cc48444b81e33c978f6f7f, type: 3}
m_Name:
m_EditorClassIdentifier:
<Account>k__BackingField:
rid: 5855987529328361546
<Stream>k__BackingField:
rid: 5855987529328361547
<Branch>k__BackingField:
rid: 5855987529328361548
OnBranchSelectionChange:
m_PersistentCalls:
m_Calls: []
OnErrorAction:
m_PersistentCalls:
m_Calls: []
OnSendProgressAction:
m_PersistentCalls:
m_Calls: []
references:
version: 2
RefIds:
- rid: 5855987529328361546
type: {class: AccountSelection, ns: Speckle.ConnectorUnity.Wrappers.Selection, asm: Speckle.ConnectorUnity.Wrappers}
data:
selectedIndex: 0
- rid: 5855987529328361547
type: {class: StreamSelection, ns: Speckle.ConnectorUnity.Wrappers.Selection, asm: Speckle.ConnectorUnity.Wrappers}
data:
selectedIndex: 0
<StreamsLimit>k__BackingField: 50
<AccountSelection>k__BackingField:
rid: 5855987529328361546
- rid: 5855987529328361548
type: {class: BranchSelection, ns: Speckle.ConnectorUnity.Wrappers.Selection, asm: Speckle.ConnectorUnity.Wrappers}
data:
selectedIndex: 0
<BranchesLimit>k__BackingField: 30
<CommitsLimit>k__BackingField: 0
<StreamSelection>k__BackingField:
rid: 5855987529328361547
--- !u!114 &218987859
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 218987857}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 0bc895f6cb37b674995dc13b79783c55, type: 3}
m_Name:
m_EditorClassIdentifier:
<Account>k__BackingField:
rid: 5855987529328361542
<Stream>k__BackingField:
rid: 5855987529328361543
<Branch>k__BackingField:
rid: 5855987529328361544
<Commit>k__BackingField:
rid: 5855987529328361545
OnCommitSelectionChange:
m_PersistentCalls:
m_Calls: []
OnReceiveProgressAction:
m_PersistentCalls:
m_Calls: []
OnErrorAction:
m_PersistentCalls:
m_Calls: []
OnTotalChildrenCountKnown:
m_PersistentCalls:
m_Calls: []
OnComplete:
m_PersistentCalls:
m_Calls: []
references:
version: 2
RefIds:
- rid: 5855987529328361542
type: {class: AccountSelection, ns: Speckle.ConnectorUnity.Wrappers.Selection, asm: Speckle.ConnectorUnity.Wrappers}
data:
selectedIndex: 0
- rid: 5855987529328361543
type: {class: StreamSelection, ns: Speckle.ConnectorUnity.Wrappers.Selection, asm: Speckle.ConnectorUnity.Wrappers}
data:
selectedIndex: 0
<StreamsLimit>k__BackingField: 50
<AccountSelection>k__BackingField:
rid: 5855987529328361542
- rid: 5855987529328361544
type: {class: BranchSelection, ns: Speckle.ConnectorUnity.Wrappers.Selection, asm: Speckle.ConnectorUnity.Wrappers}
data:
selectedIndex: 0
<BranchesLimit>k__BackingField: 30
<CommitsLimit>k__BackingField: 15
<StreamSelection>k__BackingField:
rid: 5855987529328361543
- rid: 5855987529328361545
type: {class: CommitSelection, ns: Speckle.ConnectorUnity.Wrappers.Selection, asm: Speckle.ConnectorUnity.Wrappers}
data:
selectedIndex: 0
<BranchSelection>k__BackingField:
rid: 5855987529328361544
--- !u!114 &218987860
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 218987857}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: ed6cbf9ce4dca0349997d163ec9bce7e, type: 3}
m_Name:
m_EditorClassIdentifier:
<AssetCache>k__BackingField: {fileID: 1710028308}
--- !u!4 &218987861
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 218987857}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_RootOrder: 9
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &234733581
GameObject:
m_ObjectHideFlags: 0
@@ -1616,18 +1643,6 @@ Rigidbody:
m_Interpolate: 0
m_Constraints: 0
m_CollisionDetection: 0
--- !u!114 &540478226
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: b3354e8208862c341940152f5340d41a, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!1 &641375517
GameObject:
m_ObjectHideFlags: 0
@@ -1755,6 +1770,18 @@ CanvasRenderer:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 641375517}
m_CullTransparentMesh: 1
--- !u!114 &701880765
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: b3354e8208862c341940152f5340d41a, type: 3}
m_Name:
m_EditorClassIdentifier:
--- !u!1 &712628247
GameObject:
m_ObjectHideFlags: 0
@@ -3019,6 +3046,21 @@ MonoBehaviour:
m_PersistentCalls:
m_Calls: []
m_IsOn: 1
--- !u!114 &1438814813
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 88d6b4f2f80eaa14f9f07505f7e44ec2, type: 3}
m_Name:
m_EditorClassIdentifier:
nativeCaches:
- {fileID: 1923150226}
- {fileID: 701880765}
--- !u!1 &1464556211
GameObject:
m_ObjectHideFlags: 0
@@ -3599,21 +3641,6 @@ CanvasRenderer:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1707872729}
m_CullTransparentMesh: 1
--- !u!114 &1710028308
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 88d6b4f2f80eaa14f9f07505f7e44ec2, type: 3}
m_Name:
m_EditorClassIdentifier:
nativeCaches:
- {fileID: 1771830985}
- {fileID: 540478226}
--- !u!1 &1729237655
GameObject:
m_ObjectHideFlags: 0
@@ -3726,19 +3753,6 @@ CanvasRenderer:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1762991479}
m_CullTransparentMesh: 1
--- !u!114 &1771830985
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 2a4a29c776298714c88f406ad39c6095, type: 3}
m_Name:
m_EditorClassIdentifier:
matchByName: 1
--- !u!1 &1885647142
GameObject:
m_ObjectHideFlags: 0
@@ -4043,6 +4057,19 @@ CanvasRenderer:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1903798475}
m_CullTransparentMesh: 1
--- !u!114 &1923150226
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 2a4a29c776298714c88f406ad39c6095, type: 3}
m_Name:
m_EditorClassIdentifier:
matchByName: 1
--- !u!1 &2014586909
GameObject:
m_ObjectHideFlags: 0
@@ -1,6 +1,6 @@
{
"name": "Editor",
"rootNamespace": "",
"name": "EditorTests",
"rootNamespace": "Speckle.ConnectorUnity.Tests",
"references": [
"UnityEngine.TestRunner",
"UnityEditor.TestRunner",
-96
View File
@@ -1,96 +0,0 @@
using System;
using System.Collections;
using System.Diagnostics;
using System.Threading.Tasks;
using NUnit.Framework;
using Objects.Utils;
using Speckle.Core.Api;
using Speckle.Core.Models;
using Speckle.Core.Models.Extensions;
using UnityEngine;
using UnityEngine.TestTools;
public class PerformanceTest
{
private static readonly string[] dataSource = new[]
{
"https://latest.speckle.dev/streams/24c3741255/commits/0925840e09"
};
//This method is much faster
[Test, TestCaseSource(nameof(dataSource))]
public void Receive_GetAwaiterResult(string stream)
{
var stopwatch = Stopwatch.StartNew();
Helpers.Receive(stream).GetAwaiter().GetResult();
stopwatch.Stop();
Console.WriteLine(stopwatch.ElapsedMilliseconds);
Assert.That(stopwatch.ElapsedMilliseconds, Is.Zero);
}
//This method takes around 46 seconds to complete
[Test, TestCaseSource(nameof(dataSource))]
public void Receive_TaskRunAsync(string stream)
{
var stopwatch = Stopwatch.StartNew();
Task.Run(async () =>
{
await Helpers.Receive(stream);
}).GetAwaiter().GetResult();
stopwatch.Stop();
Console.WriteLine(stopwatch.ElapsedMilliseconds);
Assert.That(stopwatch.ElapsedMilliseconds, Is.Zero);
}
// [UnityTest, TestCaseSource(nameof(dataSource))]
// public IEnumerable Receive_Coroutine(string stream)
// {
// var stopwatch = Stopwatch.StartNew();
//
// Task t = Helpers.Receive(stream);
// t.Start();
//
// yield return new WaitUntil(() => !t.IsCompleted || stopwatch.ElapsedMilliseconds >= 100000);
//
// stopwatch.Stop();
// Console.WriteLine(stopwatch.ElapsedMilliseconds);
// Assert.That(stopwatch.ElapsedMilliseconds, Is.Zero);
// Assert.True(t.IsCompletedSuccessfully);
// }
//This method takes around 46 seconds to complete
[Test]
public void TestTriangulate()
{
Base b = Task.Run(async () =>
{
return await Helpers.Receive("https://speckle.xyz/streams/4a8fd0c6b6/commits/067bf723b1");
}).GetAwaiter().GetResult();
foreach (Base child in b.Traverse(b => b is Objects.Geometry.Mesh))
{
if(child is not Objects.Geometry.Mesh m) continue;
var stopwatch = Stopwatch.StartNew();
m.TriangulateMesh();
Console.WriteLine($"took {stopwatch.ElapsedMilliseconds:ms} to triangulate {child.id}");
}
}
}
+8
View File
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 5a4f4baa829261d438b740c7d3028756
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
+18
View File
@@ -0,0 +1,18 @@
using NUnit.Framework;
using UnityEngine;
namespace Speckle.ConnectorUnity.Tests
{
public abstract class ComponentTest<T> where T : Component
{
protected T sut;
[SetUp]
public void Setup()
{
GameObject go = new();
sut = go.AddComponent<T>();
Assert.That(sut, Is.Not.Null);
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 4e2fe277dd9c47ad998138dcdbb024ae
timeCreated: 1686757093
@@ -0,0 +1,50 @@
using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;
using NUnit.Framework;
using NUnit.Framework.Constraints;
using Speckle.ConnectorUnity.Components;
using Speckle.ConnectorUnity.Wrappers;
using Speckle.Core.Api;
using Speckle.Core.Models;
using UnityEngine;
using UnityEngine.TestTools;
namespace Speckle.ConnectorUnity.Tests
{
[TestFixture, TestOf(typeof(RecursiveConverter))]
public class ConvertToNativeTests : ComponentTest<RecursiveConverter>
{
private static IEnumerable<string> TestCases()
{
yield return @"https://latest.speckle.dev/streams/c1faab5c62/commits/704984e22d";
}
private static Base Receive(string stream)
{
return Task.Run(async () => await Helpers.Receive(stream)).Result;
}
[Test, TestCaseSource(nameof(TestCases))]
public void ToNative_Passes(string stream)
{
Base testCase = Receive(stream);
var results = sut.RecursivelyConvertToNative_Sync(testCase, null);
Assert.That(results, Has.Count.GreaterThan(0));
Assert.That(results, HasSomeComponent<Transform>());
Assert.That(results, HasSomeComponent<MeshRenderer>());
Assert.That(results, HasSomeComponent<SpeckleProperties>());
}
private static Constraint HasSomeComponent<T>() where T : Component
{
return Has.Some.Matches<ConversionResult>(
x =>
{
return x.WasSuccessful(out var success, out _)
&& success.GetComponent<T>();
});
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3d9b0fc7baaf51a4a8e2bcefad8bd7b3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,28 @@
{
"name": "PlayModeTests",
"rootNamespace": "Speckle.ConnectorUnity.Tests",
"references": [
"UnityEngine.TestRunner",
"UnityEditor.TestRunner",
"Speckle.ConnectorUnity.Components",
"Utils",
"Speckle.ConnectorUnity.Wrappers"
],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": true,
"precompiledReferences": [
"nunit.framework.dll",
"SpeckleCore2.dll",
"Objects.dll"
],
"autoReferenced": false,
"defineConstraints": [
"UNITY_INCLUDE_TESTS"
],
"versionDefines": [],
"noEngineReferences": false
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 79301723eb79d2745ab1e1a9360f6f2d
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,73 @@
#nullable enable
using System;
using System.Collections;
using NUnit.Framework;
using Speckle.ConnectorUnity.Components;
using Speckle.Core.Models;
using UnityEngine;
using UnityEngine.TestTools;
namespace Speckle.ConnectorUnity.Tests
{
[TestFixture, TestOf(typeof(SpeckleReceiver))]
public sealed class SpeckleReceiverTests : ComponentTest<SpeckleReceiver>
{
[UnityTest]
public IEnumerator ReceiveAsync_Succeeds()
{
yield return null;
var task = new Utils.Utils.WaitForTask<Base>(async () => await sut.ReceiveAsync(default));
yield return task;
Base myBase = task.Result;
Assert.That(myBase, Is.Not.Null);
}
[UnityTest]
public IEnumerator ReceiveAndConvert_Async_Succeeds()
{
Transform expectedParent = new GameObject("parent").transform;
yield return null;
bool wasSuccessful = false;
Transform? actualParent = null;
sut.OnComplete.AddListener(t =>
{
wasSuccessful = true;
actualParent = t;
});
sut.OnErrorAction.AddListener((_, ex) => throw new Exception("Failed", ex));
sut.ReceiveAndConvert_Async(expectedParent);
yield return new WaitUntil(() => wasSuccessful);
Assert.That(actualParent, Is.EqualTo(expectedParent));
}
[UnityTest]
public IEnumerator ReceiveAndConvert_Routine_Succeeds()
{
Transform expectedParent = new GameObject("parent").transform;
yield return null;
bool wasSuccessful = false;
Transform? actualParent = null;
sut.OnComplete.AddListener(t =>
{
wasSuccessful = true;
actualParent = t;
});
sut.OnErrorAction.AddListener((_, ex) => throw new Exception("Failed", ex));
yield return sut.ReceiveAndConvert_Routine(expectedParent);
yield return new WaitUntil(() => wasSuccessful);
Assert.That(actualParent, Is.EqualTo(expectedParent));
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1756c50dd28a4e341a70866daa68a8d5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+5 -5
View File
@@ -1,13 +1,13 @@
{
"dependencies": {
"com.unity.2d.sprite": "1.0.0",
"com.unity.collab-proxy": "1.17.6",
"com.unity.ide.rider": "3.0.16",
"com.unity.ide.visualstudio": "2.0.16",
"com.unity.collab-proxy": "2.0.5",
"com.unity.ide.rider": "3.0.24",
"com.unity.ide.visualstudio": "2.0.18",
"com.unity.ide.vscode": "1.2.5",
"com.unity.test-framework": "1.1.31",
"com.unity.test-framework": "1.1.33",
"com.unity.textmeshpro": "3.0.6",
"com.unity.timeline": "1.6.4",
"com.unity.timeline": "1.6.5",
"com.unity.toolchain.win-x86_64-linux-x86_64": "2.0.2",
"com.unity.ugui": "1.0.0",
"com.unity.modules.ai": "1.0.0",
+6 -26
View File
@@ -7,12 +7,10 @@
"dependencies": {}
},
"com.unity.collab-proxy": {
"version": "1.17.6",
"version": "2.0.5",
"depth": 0,
"source": "registry",
"dependencies": {
"com.unity.services.core": "1.0.1"
},
"dependencies": {},
"url": "https://packages.unity.com"
},
"com.unity.ext.nunit": {
@@ -23,7 +21,7 @@
"url": "https://packages.unity.com"
},
"com.unity.ide.rider": {
"version": "3.0.16",
"version": "3.0.24",
"depth": 0,
"source": "registry",
"dependencies": {
@@ -32,7 +30,7 @@
"url": "https://packages.unity.com"
},
"com.unity.ide.visualstudio": {
"version": "2.0.16",
"version": "2.0.18",
"depth": 0,
"source": "registry",
"dependencies": {
@@ -47,24 +45,6 @@
"dependencies": {},
"url": "https://packages.unity.com"
},
"com.unity.nuget.newtonsoft-json": {
"version": "3.0.2",
"depth": 2,
"source": "registry",
"dependencies": {},
"url": "https://packages.unity.com"
},
"com.unity.services.core": {
"version": "1.6.0",
"depth": 1,
"source": "registry",
"dependencies": {
"com.unity.modules.unitywebrequest": "1.0.0",
"com.unity.nuget.newtonsoft-json": "3.0.2",
"com.unity.modules.androidjni": "1.0.0"
},
"url": "https://packages.unity.com"
},
"com.unity.sysroot": {
"version": "2.0.3",
"depth": 1,
@@ -82,7 +62,7 @@
"url": "https://packages.unity.com"
},
"com.unity.test-framework": {
"version": "1.1.31",
"version": "1.1.33",
"depth": 0,
"source": "registry",
"dependencies": {
@@ -102,7 +82,7 @@
"url": "https://packages.unity.com"
},
"com.unity.timeline": {
"version": "1.6.4",
"version": "1.6.5",
"depth": 0,
"source": "registry",
"dependencies": {
@@ -0,0 +1,34 @@
#nullable enable
using UnityEditor;
using UnityEngine;
namespace Speckle.ConnectorUnity.Components.Editor
{
[CanEditMultipleObjects]
[CustomEditor(typeof(ReceiveFromURL))]
public class ReceiveFromURLEditor: UnityEditor.Editor
{
public override void OnInspectorGUI()
{
var speckleReceiver = (ReceiveFromURL)target;
DrawDefaultInspector();
bool isBusy = speckleReceiver.IsBusy();
GUI.enabled = !isBusy;
if (GUILayout.Button("Receive!"))
{
speckleReceiver.Receive();
}
GUI.enabled = isBusy;
if (GUILayout.Button("Cancel!"))
{
speckleReceiver.Cancel();
}
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 1f1a8151dcdc40ac8662b102c2f25277
timeCreated: 1687985346
@@ -1,28 +1,103 @@
#nullable enable
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading.Tasks;
using Speckle.Core.Api;
using Speckle.Core.Models;
using Speckle.Core.Models.GraphTraversal;
using UnityEditor;
using UnityEngine;
#nullable enable
namespace Speckle.ConnectorUnity.Components.Editor
{
[CanEditMultipleObjects]
[CustomEditor(typeof(SpeckleReceiver))]
public class SpeckleReceiverEditor : UnityEditor.Editor
{
private static bool generateAssets = false;
private bool foldOutStatus = true;
private Texture2D? previewImage;
private static bool _generateAssets = false;
private bool _foldOutStatus = true;
private Texture2D? _previewImage;
public override async void OnInspectorGUI()
{
var speckleReceiver = (SpeckleReceiver)target;
DrawDefaultInspector();
//Preview image
{
_foldOutStatus = EditorGUILayout.Foldout(_foldOutStatus, "Preview Image");
if (_foldOutStatus)
{
Rect rect = GUILayoutUtility.GetAspectRect(7f / 4f);
if (_previewImage != null)
GUI.DrawTexture(rect, _previewImage);
}
}
//TODO: Draw events in a collapsed region
//Receive settings
{
bool prev = GUI.enabled;
GUI.enabled = !speckleReceiver.IsReceiving;
//Receive button
bool userRequestedReceive = GUILayout.Button("Receive!");
bool selection = EditorGUILayout.ToggleLeft("Generate Assets", _generateAssets);
if (_generateAssets != selection)
{
_generateAssets = selection;
UpdateGenerateAssets();
}
GUI.enabled = prev;
if (speckleReceiver.IsReceiving)
{
var value = Progress.globalProgress; //NOTE: this may include non-speckle items...
var percent = Math.Max(0, Mathf.Ceil(value * 100));
var rect = EditorGUILayout.GetControlRect(
false,
EditorGUIUtility.singleLineHeight
);
EditorGUI.ProgressBar(rect, value, $"{percent}%");
}
else if (userRequestedReceive)
{
var id = Progress.Start(
"Receiving Speckle data",
"Fetching commit data",
Progress.Options.Sticky
);
Progress.ShowDetails();
try
{
await ReceiveSelection(id).ConfigureAwait(true);
Progress.Finish(id);
}
catch (OperationCanceledException ex)
{
Progress.Finish(id, Progress.Status.Canceled);
Debug.Log($"Receive operation cancelled\n{ex}", this);
}
catch (Exception ex)
{
Progress.Finish(id, Progress.Status.Failed);
Debug.LogError($"Receive operation failed {ex}", this);
}
finally
{
EditorUtility.ClearProgressBar();
}
}
}
}
public void OnEnable()
{
Init();
}
public void Reset()
{
Init();
@@ -30,7 +105,7 @@ namespace Speckle.ConnectorUnity.Components.Editor
private void Init()
{
var speckleReceiver = (SpeckleReceiver) target;
var speckleReceiver = (SpeckleReceiver)target;
UpdatePreviewImage();
speckleReceiver.OnCommitSelectionChange.AddListener(_ => UpdatePreviewImage());
UpdateGenerateAssets();
@@ -38,162 +113,224 @@ namespace Speckle.ConnectorUnity.Components.Editor
private void UpdatePreviewImage()
{
previewImage = null;
((SpeckleReceiver)target).GetPreviewImage(t => previewImage = t);
_previewImage = null;
((SpeckleReceiver)target).GetPreviewImage(t => _previewImage = t);
}
public override async void OnInspectorGUI()
private async Task ReceiveSelection(int progressId)
{
var speckleReceiver = (SpeckleReceiver) target;
DrawDefaultInspector();
//Preview image
foldOutStatus = EditorGUILayout.Foldout(foldOutStatus, "Preview Image");
if (foldOutStatus)
var speckleReceiver = (SpeckleReceiver)target;
bool shouldCancel = false;
Progress.RegisterCancelCallback(
progressId,
() =>
{
speckleReceiver.Cancel();
shouldCancel = true;
return true;
}
);
Base commitObject;
try
{
Rect rect = GUILayoutUtility.GetAspectRect(7f/4f);
if(previewImage != null) GUI.DrawTexture(rect, previewImage);
var token = speckleReceiver.BeginOperation();
commitObject = await Task.Run(
async () => await ReceiveCommit(progressId).ConfigureAwait(false),
token
)
.ConfigureAwait(true);
}
finally
{
speckleReceiver.FinishOperation();
}
//Receive button
bool receive = GUILayout.Button("Receive!");
int childrenConverted = 0;
int childrenFailed = 0;
bool selection = EditorGUILayout.ToggleLeft("Generate Assets", generateAssets);
if (generateAssets != selection)
{
generateAssets = selection;
UpdateGenerateAssets();
}
//TODO: Draw events in a collapsed region
int totalChildren = (int)Math.Min(commitObject.totalChildrenCount, int.MaxValue);
float totalChildrenFloat = commitObject.totalChildrenCount;
if (receive)
var convertProgress = Progress.Start(
"Converting To Native",
"Preparing...",
Progress.Options.Indefinite | Progress.Options.Sticky,
progressId
);
bool BeforeConvert(TraversalContext context)
{
await ReceiveAndConvert(speckleReceiver);
Base b = context.current;
//NOTE: progress wont reach 100% because not all objects are convertable
float progress = (childrenConverted + childrenFailed) / totalChildrenFloat;
if (shouldCancel)
return false;
shouldCancel = EditorUtility.DisplayCancelableProgressBar(
"Converting To Native...",
$"{b.speckle_type} - {b.id}",
progress
);
return !shouldCancel;
}
foreach (
var conversionResult in speckleReceiver.Converter.RecursivelyConvertToNative_Enumerable(
commitObject,
speckleReceiver.transform,
BeforeConvert
)
)
{
Base speckleObject = conversionResult.SpeckleObject;
if (conversionResult.WasSuccessful(out _, out var ex))
{
childrenConverted++;
}
else
{
childrenFailed++;
Debug.LogWarning(
$"Failed to convert Speckle object of type {speckleObject.speckle_type}\n{ex}",
this
);
}
Progress.Report(
progressId,
childrenConverted + childrenFailed,
totalChildren,
"Receiving objects"
);
if (shouldCancel)
break;
}
var resultString = $"{childrenConverted} {nameof(GameObject)}s created";
if (childrenFailed != 0)
resultString += $", {childrenFailed} objects failed to convert!";
Debug.Log(
shouldCancel
? $"Stopped converting to native: The operation has been cancelled - {resultString}\n "
: $"Finished converting to native.\n{resultString}",
speckleReceiver
);
Progress.Finish(convertProgress);
if (shouldCancel)
throw new OperationCanceledException(
"Conversion operation canceled through editor dialogue"
);
}
private void UpdateGenerateAssets()
{
var speckleReceiver = (SpeckleReceiver) target;
speckleReceiver.Converter.AssetCache.nativeCaches = NativeCacheFactory.GetDefaultNativeCacheSetup(generateAssets);
var speckleReceiver = (SpeckleReceiver)target;
speckleReceiver.Converter.AssetCache.nativeCaches =
NativeCacheFactory.GetDefaultNativeCacheSetup(_generateAssets);
}
public async Task<GameObject?> ReceiveAndConvert(SpeckleReceiver speckleReceiver)
private async Task<Base> ReceiveCommit(int progressId)
{
speckleReceiver.CancellationTokenSource?.Cancel();
if (!speckleReceiver.GetSelection(out Client? client, out _, out Commit? commit, out string? error))
{
Debug.LogWarning($"Not ready to receive: {error}", speckleReceiver);
return null;
}
Base? commitObject = await ReceiveCommit(speckleReceiver, client.ServerUrl);
var speckleReceiver = (SpeckleReceiver)target;
if (commitObject == null) return null;
var gameObject = Convert(speckleReceiver, commitObject, commit.id);
Debug.Log($"Successfully received and converted commit: {commit.id}", target);
return gameObject;
}
string serverLogName = speckleReceiver.Account.Client?.ServerUrl ?? "Speckle";
private GameObject Convert(SpeckleReceiver receiver, Base commitObject, string name)
{
//Convert Speckle Objects
int childrenConverted = 0;
float totalChildren = commitObject.totalChildrenCount;
void BeforeConvertCallback(Base b)
{
//TODO: this is an incorrect way of measuring progress, as totalChildren != total convertable children
float progress = childrenConverted++ / totalChildren;
EditorUtility.DisplayProgressBar("Converting To Native...",
$"{b.speckle_type} - {b.id}",
progress);
}
var go = receiver.ConvertToNativeWithCategories(commitObject,
name, BeforeConvertCallback);
go.transform.SetParent(receiver.transform);
return go;
}
private async Task<Base?> ReceiveCommit(SpeckleReceiver speckleReceiver, string serverLogName)
{
string message = $"Receiving data from {serverLogName}...";
EditorUtility.DisplayProgressBar(message, "", 0);
int transport = Progress.Start(
$"Downloading data from {serverLogName}",
"Waiting...",
Progress.Options.Sticky,
progressId
);
int deserialize = Progress.Start(
"Deserializing data",
"Waiting...",
Progress.Options.Sticky,
progressId
);
Progress.SetPriority(transport, Progress.Priority.High);
var totalObjectCount = 1;
void OnTotalChildrenKnown(int count)
{
totalObjectCount = count;
};
Progress.Report(progressId, 0, totalObjectCount, "Receiving objects");
}
void OnProgress(ConcurrentDictionary<string, int> dict)
{
var currentProgress = dict.Values.Average();
var progress = (float) currentProgress / totalObjectCount;
EditorApplication.delayCall += () =>
bool r = dict.TryGetValue("RemoteTransport", out int rtProgress);
bool l = dict.TryGetValue("SQLite", out int ltProgress);
if (r || l)
{
bool shouldCancel = EditorUtility.DisplayCancelableProgressBar(message,
$"{currentProgress}/{totalObjectCount}",
progress);
if (shouldCancel)
{
CancelReceive();
}
};
};
void OnError(string message, Exception e)
{
if (e is not OperationCanceledException)
{
Debug.LogError($"Receive failed: {message}\n{e}", speckleReceiver);
var fetched = (rtProgress + ltProgress);
Progress.Report(
transport,
fetched,
totalObjectCount,
$"{fetched}/{totalObjectCount}"
);
}
CancelReceive();
};
Base? commitObject = null;
if (dict.TryGetValue("DS", out int tsProgress))
{
tsProgress--; //The root object isn't included, so we add an extra 1
Progress.Report(
deserialize,
tsProgress,
totalObjectCount,
$"{tsProgress}/{totalObjectCount}"
);
Progress.Report(progressId, tsProgress, totalObjectCount);
}
}
Base commitObject;
try
{
speckleReceiver.OnTotalChildrenCountKnown.AddListener(OnTotalChildrenKnown);
speckleReceiver.OnReceiveProgressAction.AddListener(OnProgress);
speckleReceiver.OnErrorAction.AddListener(OnError);
commitObject = await speckleReceiver.ReceiveAsync();
if (commitObject == null)
{
Debug.LogWarning($"Receive warning: Receive operation returned null", speckleReceiver);
}
commitObject = await speckleReceiver
.ReceiveAsync(speckleReceiver.CancellationToken)
.ConfigureAwait(false);
Progress.Finish(transport);
Progress.Finish(deserialize);
}
catch (OperationCanceledException)
{
Progress.Finish(transport, Progress.Status.Canceled);
Progress.Finish(deserialize, Progress.Status.Canceled);
throw;
}
catch (Exception)
{
Progress.Finish(transport, Progress.Status.Failed);
Progress.Finish(deserialize, Progress.Status.Failed);
throw;
}
finally
{
speckleReceiver.OnTotalChildrenCountKnown.RemoveListener(OnTotalChildrenKnown);
speckleReceiver.OnReceiveProgressAction.RemoveListener(OnProgress);
speckleReceiver.OnErrorAction.RemoveListener(OnError);
EditorApplication.delayCall += EditorUtility.ClearProgressBar;
}
return commitObject;
}
private void CancelReceive()
{
((SpeckleReceiver)target).CancellationTokenSource?.Cancel();
EditorApplication.delayCall += EditorUtility.ClearProgressBar;
}
[MenuItem("GameObject/Speckle/Speckle Connector", false, 10)]
static void CreateCustomGameObject(MenuCommand menuCommand) {
static void CreateCustomGameObject(MenuCommand menuCommand)
{
// Create a custom game object
GameObject go = new GameObject("Speckle Connector");
GameObject go = new("Speckle Connector");
// Ensure it gets reparented if this was a context click (otherwise does nothing)
GameObjectUtility.SetParentAndAlign(go, menuCommand.context as GameObject);
// Register the creation in the undo system
@@ -202,10 +339,13 @@ namespace Speckle.ConnectorUnity.Components.Editor
go.AddComponent<RecursiveConverter>();
go.AddComponent<SpeckleReceiver>();
go.AddComponent<ReceiveFromURL>();
go.AddComponent<SpeckleSender>();
#if UNITY_2021_2_OR_NEWER
var icon = AssetDatabase.LoadAssetAtPath<Texture2D>("Packages/systems.speckle.speckle-unity/Editor/Gizmos/logo128.png");
var icon = AssetDatabase.LoadAssetAtPath<Texture2D>(
"Packages/systems.speckle.speckle-unity/Editor/Gizmos/logo128.png"
);
EditorGUIUtility.SetIconForObject(go, icon);
#endif
}
@@ -12,36 +12,40 @@ using Component = UnityEngine.Component;
#nullable enable
namespace Speckle.ConnectorUnity.Components.Editor
{
public enum SelectionFilter
{
[Tooltip("Convert all children of this GameObject")]
Children,
[Tooltip("Convert GameObjects currently selected in the hierarchy (consider padlocking this inspector)")]
[Tooltip(
"Convert GameObjects currently selected in the hierarchy (consider padlocking this inspector)"
)]
Selection,
[InspectorName("All (excl. disabled)")]
[Tooltip("Convert all GameObjects (excluding disabled) in the active scene")]
Enabled,
[Tooltip("Convert all GameObjects (including disabled) in the active scene")]
[InspectorName("All (incl. disabled)")]
All,
}
[CustomEditor(typeof(SpeckleSender))]
[CanEditMultipleObjects]
public class SpeckleSendEditor : UnityEditor.Editor
{
private SelectionFilter selectedFilter = SelectionFilter.Children;
private SelectionFilter _selectedFilter = SelectionFilter.Children;
public override async void OnInspectorGUI()
{
//Draw events in a collapsed region
DrawDefaultInspector();
bool shouldSend = GUILayout.Button("Send!");
selectedFilter = (SelectionFilter)EditorGUILayout.EnumPopup("Selection", selectedFilter);
_selectedFilter = (SelectionFilter)
EditorGUILayout.EnumPopup("Selection", _selectedFilter);
if (shouldSend)
{
await ConvertAndSend();
@@ -50,30 +54,35 @@ namespace Speckle.ConnectorUnity.Components.Editor
public async Task<string?> ConvertAndSend()
{
var speckleSender = (SpeckleSender) target;
var speckleSender = (SpeckleSender)target;
if (!speckleSender.GetSelection(out _, out _, out _, out string? error))
{
Debug.LogWarning($"Not ready to send: {error}", speckleSender);
return null;
}
RecursiveConverter converter = speckleSender.Converter;
Base data = selectedFilter switch
Base data = _selectedFilter switch
{
SelectionFilter.All => ConvertAll(converter),
SelectionFilter.Enabled => ConvertEnabled(converter),
SelectionFilter.Children => ConvertChildren(converter),
SelectionFilter.Selection => ConvertSelection(converter),
_ => throw new InvalidEnumArgumentException(nameof(selectedFilter), (int) selectedFilter, selectedFilter.GetType()),
_
=> throw new InvalidEnumArgumentException(
nameof(_selectedFilter),
(int)_selectedFilter,
_selectedFilter.GetType()
),
};
//TODO onError action?
if (data["@objects"] is IList l && l.Count == 0)
{
Debug.LogWarning($"Nothing to send", speckleSender);
return null;
}
}
return await speckleSender.SendDataAsync(data, true);
}
@@ -81,37 +90,36 @@ namespace Speckle.ConnectorUnity.Components.Editor
private Base ConvertChildren(RecursiveConverter converter)
{
return converter.RecursivelyConvertToSpeckle(
new []{((Component)target).gameObject},
_ => true);
new[] { ((Component)target).gameObject },
_ => true
);
}
private Base ConvertSelection(RecursiveConverter converter)
{
ISet<GameObject> selection = Selection.GetFiltered<GameObject>(SelectionMode.Deep).ToImmutableHashSet();
ISet<GameObject> selection = Selection
.GetFiltered<GameObject>(SelectionMode.Deep)
.ToImmutableHashSet();
return converter.RecursivelyConvertToSpeckle(
SceneManager.GetActiveScene().GetRootGameObjects(),
go => selection.Contains(go));
SceneManager.GetActiveScene().GetRootGameObjects(),
go => selection.Contains(go)
);
}
private Base ConvertAll(RecursiveConverter converter)
{
return converter.RecursivelyConvertToSpeckle(
SceneManager.GetActiveScene().GetRootGameObjects(),
_ => true);
SceneManager.GetActiveScene().GetRootGameObjects(),
_ => true
);
}
private Base ConvertEnabled(RecursiveConverter converter)
{
return converter.RecursivelyConvertToSpeckle(
SceneManager.GetActiveScene().GetRootGameObjects(),
go => go.activeInHierarchy);
SceneManager.GetActiveScene().GetRootGameObjects(),
go => go.activeInHierarchy
);
}
private void CancelSend()
{
((SpeckleReceiver)target).CancellationTokenSource?.Cancel();
EditorApplication.delayCall += EditorUtility.ClearProgressBar;
}
}
}
@@ -20,13 +20,13 @@ namespace Speckle.ConnectorUnity.Components.Editor
public class StreamManagerEditor : UnityEditor.Editor
{
private bool _foldOutAccount;
private int _totalChildrenCount = 0;
private int _totalChildrenCount;
private StreamManager _streamManager;
private static bool generateAssets;
private static bool _generateAssets;
public int StreamsLimit { get; set; } = 30;
public int BranchesLimit { get; set; } = 30;
public int BranchesLimit { get; set; } = 75;
public int CommitsLimit { get; set; } = 25;
private int SelectedAccountIndex
@@ -98,7 +98,6 @@ namespace Speckle.ConnectorUnity.Components.Editor
private List<Branch> Branches
{
get => _streamManager.Branches;
set => _streamManager.Branches = value;
}
@@ -142,7 +141,11 @@ namespace Speckle.ConnectorUnity.Components.Editor
SelectedStream = Streams[i];
EditorUtility.DisplayProgressBar("Loading stream details...", "", 0);
Branches = await Client.StreamGetBranches(SelectedStream.id, BranchesLimit, CommitsLimit);
Branches = await Client.StreamGetBranches(
SelectedStream.id,
BranchesLimit,
CommitsLimit
);
if (Branches.Any())
{
SelectedBranchIndex = 0;
@@ -155,7 +158,6 @@ namespace Speckle.ConnectorUnity.Components.Editor
EditorUtility.ClearProgressBar();
}
private async Task Receive()
{
var transport = new ServerTransport(SelectedAccount, SelectedStream.id);
@@ -163,45 +165,82 @@ namespace Speckle.ConnectorUnity.Components.Editor
try
{
Commit selectedCommit = Branches[SelectedBranchIndex].commits.items[
SelectedCommitIndex
];
// Receive Speckle Objects
var @base = await Operations.Receive(
Branches[SelectedBranchIndex].commits.items[SelectedCommitIndex].referencedObject,
selectedCommit.referencedObject,
remoteTransport: transport,
onProgressAction: dict =>
{
UnityEditor.EditorApplication.delayCall += () =>
EditorApplication.delayCall += () =>
{
EditorUtility.DisplayProgressBar($"Receiving data from {transport.BaseUri}...", "",
Convert.ToSingle(dict.Values.Average() / _totalChildrenCount));
EditorUtility.DisplayProgressBar(
$"Receiving data from {transport.BaseUri}...",
"",
Convert.ToSingle(dict.Values.Average() / _totalChildrenCount)
);
};
},
onTotalChildrenCountKnown: count => { _totalChildrenCount = count; }
onTotalChildrenCountKnown: count =>
{
_totalChildrenCount = count;
}
);
if (@base is null)
throw new InvalidOperationException("Received object was null");
EditorUtility.ClearProgressBar();
Analytics.TrackEvent(SelectedAccount, Analytics.Events.Receive);
Analytics.TrackEvent(
SelectedAccount,
Analytics.Events.Receive,
new Dictionary<string, object>()
{
{ "mode", nameof(StreamManagerEditor) },
{
"sourceHostApp",
HostApplications
.GetHostAppFromString(selectedCommit.sourceApplication)
.Slug
},
{ "sourceHostAppVersion", selectedCommit.sourceApplication ?? "" },
{ "hostPlatform", Application.platform.ToString() },
{ "isMultiplayer", selectedCommit.authorId != SelectedAccount.userInfo.id },
}
);
//Convert Speckle Objects
int childrenConverted = 0;
void BeforeConvertCallback(Base b)
{
EditorUtility.DisplayProgressBar("Converting To Native...", $"{b.speckle_type} - {b.id}",
Convert.ToSingle(childrenConverted++ / _totalChildrenCount));
EditorUtility.DisplayProgressBar(
"Converting To Native...",
$"{b.speckle_type} - {b.id}",
Convert.ToSingle(childrenConverted++ / _totalChildrenCount)
);
}
var go = _streamManager.ConvertRecursivelyToNative(@base,
Branches[SelectedBranchIndex].commits.items[SelectedCommitIndex].id, BeforeConvertCallback);
_streamManager.ConvertRecursivelyToNative(
@base,
Branches[SelectedBranchIndex].commits.items[SelectedCommitIndex].id,
BeforeConvertCallback
);
// Read Receipt
await Client.CommitReceived(new CommitReceivedInput
{
streamId = SelectedStream.id,
commitId = Branches[SelectedBranchIndex].commits.items[SelectedCommitIndex].id,
message = $"received commit from {HostApplications.Unity.Name} Editor",
sourceApplication = HostApplications.Unity.Name
});
await Client.CommitReceived(
new CommitReceivedInput
{
streamId = SelectedStream.id,
commitId = Branches[SelectedBranchIndex].commits.items[
SelectedCommitIndex
].id,
message = $"received commit from {HostApplications.Unity.Name} Editor",
sourceApplication = HostApplications.Unity.Name
}
);
}
catch (Exception e)
{
@@ -215,8 +254,7 @@ namespace Speckle.ConnectorUnity.Components.Editor
public override async void OnInspectorGUI()
{
_streamManager = (StreamManager) target;
_streamManager = (StreamManager)target;
#region Account GUI
@@ -226,12 +264,15 @@ namespace Speckle.ConnectorUnity.Components.Editor
return;
}
EditorGUILayout.BeginHorizontal();
SelectedAccountIndex = EditorGUILayout.Popup("Accounts", SelectedAccountIndex,
SelectedAccountIndex = EditorGUILayout.Popup(
"Accounts",
SelectedAccountIndex,
Accounts.Select(x => x.userInfo.email + " | " + x.serverInfo.name).ToArray(),
GUILayout.ExpandWidth(true), GUILayout.Height(20));
GUILayout.ExpandWidth(true),
GUILayout.Height(20)
);
if (OldSelectedAccountIndex != SelectedAccountIndex)
{
@@ -247,26 +288,37 @@ namespace Speckle.ConnectorUnity.Components.Editor
EditorGUILayout.EndHorizontal();
#region Speckle Account Info
_foldOutAccount = EditorGUILayout.BeginFoldoutHeaderGroup(_foldOutAccount, "Account Info");
_foldOutAccount = EditorGUILayout.BeginFoldoutHeaderGroup(
_foldOutAccount,
"Account Info"
);
if (_foldOutAccount)
{
EditorGUI.BeginDisabledGroup(true);
EditorGUILayout.TextField("Name", SelectedAccount.userInfo.name,
EditorGUILayout.TextField(
"Name",
SelectedAccount.userInfo.name,
GUILayout.Height(20),
GUILayout.ExpandWidth(true));
GUILayout.ExpandWidth(true)
);
EditorGUILayout.TextField("Server", SelectedAccount.serverInfo.name,
EditorGUILayout.TextField(
"Server",
SelectedAccount.serverInfo.name,
GUILayout.Height(20),
GUILayout.ExpandWidth(true));
GUILayout.ExpandWidth(true)
);
EditorGUILayout.TextField("URL", SelectedAccount.serverInfo.url,
EditorGUILayout.TextField(
"URL",
SelectedAccount.serverInfo.url,
GUILayout.Height(20),
GUILayout.ExpandWidth(true));
GUILayout.ExpandWidth(true)
);
EditorGUI.EndDisabledGroup();
}
@@ -284,9 +336,13 @@ namespace Speckle.ConnectorUnity.Components.Editor
EditorGUILayout.BeginHorizontal();
SelectedStreamIndex = EditorGUILayout.Popup("Streams",
SelectedStreamIndex, Streams.Select(x => x.name).ToArray(), GUILayout.Height(20),
GUILayout.ExpandWidth(true));
SelectedStreamIndex = EditorGUILayout.Popup(
"Streams",
SelectedStreamIndex,
Streams.Select(x => x.name).ToArray(),
GUILayout.Height(20),
GUILayout.ExpandWidth(true)
);
if (OldSelectedStreamIndex != SelectedStreamIndex)
{
@@ -311,23 +367,29 @@ namespace Speckle.ConnectorUnity.Components.Editor
EditorGUILayout.BeginHorizontal();
SelectedBranchIndex = EditorGUILayout.Popup("Branches",
SelectedBranchIndex, Branches.Select(x => x.name).ToArray(), GUILayout.Height(20),
GUILayout.ExpandWidth(true));
SelectedBranchIndex = EditorGUILayout.Popup(
"Branches",
SelectedBranchIndex,
Branches.Select(x => x.name).ToArray(),
GUILayout.Height(20),
GUILayout.ExpandWidth(true)
);
EditorGUILayout.EndHorizontal();
if (!Branches[SelectedBranchIndex].commits.items.Any())
return;
EditorGUILayout.BeginHorizontal();
SelectedCommitIndex = EditorGUILayout.Popup("Commits",
SelectedCommitIndex = EditorGUILayout.Popup(
"Commits",
SelectedCommitIndex,
Branches[SelectedBranchIndex].commits.items.Select(x => $"{x.message} - {x.id}").ToArray(),
Branches[SelectedBranchIndex].commits.items
.Select(x => $"{x.message} - {x.id}")
.ToArray(),
GUILayout.Height(20),
GUILayout.ExpandWidth(true));
GUILayout.ExpandWidth(true)
);
EditorGUILayout.EndHorizontal();
@@ -339,12 +401,12 @@ namespace Speckle.ConnectorUnity.Components.Editor
GUILayout.Label("Generate assets");
GUILayout.FlexibleSpace();
bool selection = GUILayout.Toggle(generateAssets, "");
if (generateAssets != selection)
bool selection = GUILayout.Toggle(_generateAssets, "");
if (_generateAssets != selection)
{
generateAssets = selection;
_streamManager.RC.AssetCache.nativeCaches = NativeCacheFactory.GetDefaultNativeCacheSetup(generateAssets);
_generateAssets = selection;
_streamManager.RC.AssetCache.nativeCaches =
NativeCacheFactory.GetDefaultNativeCacheSetup(_generateAssets);
}
EditorGUILayout.EndHorizontal();
@@ -364,4 +426,4 @@ namespace Speckle.ConnectorUnity.Components.Editor
}
}
}
}
}
@@ -1,8 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using Speckle.Core.Models;
using UnityEditor;
using UnityEngine;
@@ -18,23 +16,28 @@ namespace Speckle.ConnectorUnity.NativeCache.Editor
public const string DefaultPath = "Assets/Resources";
public string path = DefaultPath;
private MemoryNativeCache readCache;
private MemoryNativeCache _readCache;
#nullable enable
void Awake()
{
readCache = CreateInstance<MemoryNativeCache>();
_readCache = CreateInstance<MemoryNativeCache>();
}
public override bool TryGetObject<T>(Base speckleObject, [NotNullWhen(true)] out T? nativeObject) where T : class
public override bool TryGetObject<T>(
Base speckleObject,
[NotNullWhen(true)] out T? nativeObject
)
where T : class
{
if(readCache.TryGetObject(speckleObject, out nativeObject))
if (_readCache.TryGetObject(speckleObject, out nativeObject))
return true;
Type nativeType = typeof(T);
if (!GetAssetPath(nativeType, speckleObject, out string? assetPath)) return false;
if (!GetAssetPath(nativeType, speckleObject, out string? assetPath))
return false;
nativeObject = AssetDatabase.LoadAssetAtPath<T>(assetPath);
return nativeObject != null;
}
@@ -43,32 +46,39 @@ namespace Speckle.ConnectorUnity.NativeCache.Editor
{
return WriteObject(speckleObject, nativeObject);
}
private bool WriteObject(Base speckleObject, Object nativeObject)
{
Type nativeType = nativeObject.GetType();
if (!GetAssetPath(nativeType, speckleObject, out string? assetPath)) return false;
if (!GetAssetPath(nativeType, speckleObject, out string? assetPath))
return false;
// Special case for GameObjects, we want to use PrefabUtility
if (nativeObject is GameObject go)
{
var prefab = PrefabUtility.SaveAsPrefabAssetAndConnect(go, assetPath, InteractionMode.AutomatedAction);
return readCache.TrySaveObject(speckleObject, prefab);
var prefab = PrefabUtility.SaveAsPrefabAssetAndConnect(
go,
assetPath,
InteractionMode.AutomatedAction
);
return _readCache.TrySaveObject(speckleObject, prefab);
}
// Exit early if there's already an asset
Object? existing = AssetDatabase.LoadAssetAtPath(assetPath, nativeObject.GetType());
if (existing != null)
{
Debug.LogWarning($"Failed to write asset as one already existed at path: {assetPath}", this);
Debug.LogWarning(
$"Failed to write asset as one already existed at path: {assetPath}",
this
);
return false;
}
AssetDatabase.CreateAsset(nativeObject, $"{assetPath}");
return readCache.TrySaveObject(speckleObject, nativeObject);
return _readCache.TrySaveObject(speckleObject, nativeObject);
}
public override void BeginWrite()
{
base.BeginWrite();
@@ -77,32 +87,40 @@ namespace Speckle.ConnectorUnity.NativeCache.Editor
public override void FinishWrite()
{
if (!isWriting) return;
if (!isWriting)
return;
//AssetDatabase.StopAssetEditing();
AssetDatabase.SaveAssets();
if (readCache != null) readCache.LoadedAssets.Clear();
if (_readCache != null)
_readCache.LoadedAssets.Clear();
base.FinishWrite();
}
private bool GetAssetPath(Type nativeType, Base speckleObject, [NotNullWhen(true)] out string? outPath)
private bool GetAssetPath(
Type nativeType,
Base speckleObject,
[NotNullWhen(true)] out string? outPath
)
{
string? folder = AssetHelpers.GetAssetFolder(nativeType, path);
outPath = null;
if (folder == null) return false;
if (!CreateDirectory(folder)) return false;
if (folder == null)
return false;
if (!CreateDirectory(folder))
return false;
string assetName = AssetHelpers.GetAssetName(speckleObject, nativeType);
outPath = $"{folder}/{assetName}";
return true;
}
private static bool CreateDirectory(string directoryPath)
{
if (Directory.Exists(directoryPath))
return true;
var info = Directory.CreateDirectory(directoryPath);
AssetDatabase.Refresh();
return info.Exists;
@@ -111,16 +129,20 @@ namespace Speckle.ConnectorUnity.NativeCache.Editor
[ContextMenu("SetPath")]
public void SetPath_Menu()
{
var selection = EditorUtility.OpenFolderPanel("Set Assets Path", "Assets/Resources", "");
if (selection.StartsWith(Application.dataPath)) {
var selection = EditorUtility.OpenFolderPanel(
"Set Assets Path",
"Assets/Resources",
""
);
if (selection.StartsWith(Application.dataPath))
{
path = "Assets" + selection.Substring(Application.dataPath.Length);
}
else
{
Debug.LogError($"Expected selection to be within {Application.dataPath}");
}
}
}
}
@@ -13,8 +13,10 @@ namespace Speckle.ConnectorUnity.Wrappers.Selection.Editor
public sealed class AccountSelectionDrawer : OptionSelectionDrawer<Account>
{
protected override bool DisplayRefresh => true;
protected override string FormatOption(Account o) => $"{o.userInfo.email} | {o.serverInfo.name}";
protected override string FormatOption(Account o) =>
$"{o.userInfo.email} | {o.serverInfo.name}";
public AccountSelectionDrawer()
{
details = new (string, Func<Account, string>)[]
@@ -29,74 +31,74 @@ namespace Speckle.ConnectorUnity.Wrappers.Selection.Editor
};
}
}
[CustomPropertyDrawer(typeof(StreamSelection))]
public sealed class StreamSelectionDrawer : OptionSelectionDrawer<Stream>
{
protected override bool DisplayRefresh => true;
protected override string FormatOption(Stream o) => $"{o.name}";
public StreamSelectionDrawer()
{
properties = new []{$"<{nameof(StreamSelection.StreamsLimit)}>k__BackingField"};
properties = new[] { $"<{nameof(StreamSelection.StreamsLimit)}>k__BackingField" };
details = new (string, Func<Stream, string>)[]
{
("Stream id", s => s.id),
("Description", s => s.description),
("Is Public", s => s.isPublic.ToString()),
("Role", s => s.role),
("Created at", s => s.createdAt),
("Updated at", s => s.updatedAt),
("Created at", s => s.createdAt.ToString()),
("Updated at", s => s.updatedAt.ToString()),
};
}
}
[CustomPropertyDrawer(typeof(BranchSelection))]
public sealed class BranchSelectionDrawer : OptionSelectionDrawer<Branch>
{
protected override bool DisplayRefresh => true;
protected override string FormatOption(Branch o) => $"{o.name}";
public BranchSelectionDrawer()
{
properties = new []
properties = new[]
{
$"<{nameof(BranchSelection.BranchesLimit)}>k__BackingField",
$"<{nameof(BranchSelection.CommitsLimit)}>k__BackingField",
};
details = new (string, Func<Branch, string>)[]
{
("Description", s => s.description),
};
details = new (string, Func<Branch, string>)[] { ("Description", s => s.description), };
}
}
[CustomPropertyDrawer(typeof(CommitSelection))]
public sealed class CommitSelectionDrawer : OptionSelectionDrawer<Commit>
{
protected override string FormatOption(Commit o) => $"{o.message} - {o.id}";
public CommitSelectionDrawer()
{
details = new (string, Func<Commit, string>)[]
{
("Commit Id", s => s.id),
("Author Name", s => s.authorName),
("Created At", s => s.createdAt),
("Created At", s => s.createdAt.ToString()),
("Source Application", s => s.sourceApplication),
("Reference Object Id", s => s.referencedObject),
};
}
}
public abstract class OptionSelectionDrawer<TOption> : PropertyDrawer where TOption : class
public abstract class OptionSelectionDrawer<TOption> : PropertyDrawer
where TOption : class
{
private const float RefreshButtonWidthScale = 0.2f;
private const float PrefixIndentation = 100f;
protected readonly float DetailsTextHeight = EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
protected readonly float DetailsTextHeight =
EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
protected virtual bool DisplayRefresh => false;
protected abstract string FormatOption(TOption o);
@@ -105,7 +107,7 @@ namespace Speckle.ConnectorUnity.Wrappers.Selection.Editor
protected string[] properties = { };
protected (string, Func<TOption, string>)[] details = { };
private string[] GetFormattedOptions(TOption[] options)
{
int optionsCount = options.Length;
@@ -118,7 +120,12 @@ namespace Speckle.ConnectorUnity.Wrappers.Selection.Editor
return choices;
}
protected virtual void OnGUIDetails(Rect position, SerializedProperty property, GUIContent label, TOption? selection)
protected virtual void OnGUIDetails(
Rect position,
SerializedProperty property,
GUIContent label,
TOption? selection
)
{
position.height = DetailsTextHeight;
@@ -142,80 +149,106 @@ namespace Speckle.ConnectorUnity.Wrappers.Selection.Editor
EditorGUI.EndDisabledGroup();
EditorGUI.indentLevel--;
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
EditorGUI.BeginProperty(position, label, property);
var t = (OptionSelection<TOption>)fieldInfo.GetValue(property.serializedObject.targetObject);
var t =
(OptionSelection<TOption>)
fieldInfo.GetValue(property.serializedObject.targetObject);
var selectionRect = position;
var selectionRect = position;
selectionRect.x += PrefixIndentation + 5;
selectionRect.width -= PrefixIndentation + 5;
TOption? selectedOption = t.Selected;
// Options selection
{
var popupSize = DisplayRefresh
? new Rect(selectionRect.x, selectionRect.y, selectionRect.width * (1-RefreshButtonWidthScale), DetailsTextHeight)
? new Rect(
selectionRect.x,
selectionRect.y,
selectionRect.width * (1 - RefreshButtonWidthScale),
DetailsTextHeight
)
: selectionRect;
string selectedChoice = selectedOption != null ? FormatOption(selectedOption) : "";
if (GUI.Button(popupSize, selectedChoice, EditorStyles.popup))
{
var windowPos = GUIUtility.GUIToScreenPoint(Event.current.mousePosition);
var provider = ScriptableObject.CreateInstance<StringListSearchProvider>();
provider.Title = typeof(TOption).Name;
provider.listItems = GetFormattedOptions(t.Options);;
provider.onSetIndexCallback = o => { t.SelectedIndex = o;};
provider.listItems = GetFormattedOptions(t.Options);
;
provider.onSetIndexCallback = o =>
{
t.SelectedIndex = o;
};
SearchWindow.Open(new SearchWindowContext(windowPos), provider);
}
// Optional refresh
if (DisplayRefresh)
{
var buttonSize = new Rect(selectionRect.x + popupSize.width , selectionRect.y, selectionRect.width * RefreshButtonWidthScale, DetailsTextHeight);
var buttonSize = new Rect(
selectionRect.x + popupSize.width,
selectionRect.y,
selectionRect.width * RefreshButtonWidthScale,
DetailsTextHeight
);
if (GUI.Button(buttonSize, "Refresh"))
{
EditorApplication.delayCall += t.RefreshOptions;
}
}
}
// Collapsable details
{
{
int visiblePropCount = property.isExpanded ? GUIDetailsPropertyCount : 0;
var detailsHeight = new Vector2(PrefixIndentation, DetailsTextHeight + visiblePropCount * DetailsTextHeight);
var foldoutRect = new Rect(position.position, detailsHeight);
property.isExpanded = EditorGUI.BeginFoldoutHeaderGroup(foldoutRect, property.isExpanded, label);
var detailsHeight = new Vector2(
PrefixIndentation,
DetailsTextHeight + visiblePropCount * DetailsTextHeight
);
var foldoutRect = new Rect(position.position, detailsHeight);
property.isExpanded = EditorGUI.BeginFoldoutHeaderGroup(
foldoutRect,
property.isExpanded,
label
);
if (property.isExpanded)
{
OnGUIDetails(position, property, label, selectedOption);
}
EditorGUI.EndFoldoutHeaderGroup();
}
EditorGUI.EndProperty();
//EditorUtility.SetDirty(property.serializedObject.targetObject);
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
var standardHeight = EditorGUIUtility.singleLineHeight;
if (!property.isExpanded) return standardHeight + EditorGUIUtility.standardVerticalSpacing;
var detailsHeight = GUIDetailsPropertyCount * (standardHeight + EditorGUIUtility.standardVerticalSpacing);
return standardHeight + detailsHeight + EditorGUIUtility.standardVerticalSpacing + EditorGUIUtility.standardVerticalSpacing;
if (!property.isExpanded)
return standardHeight + EditorGUIUtility.standardVerticalSpacing;
var detailsHeight =
GUIDetailsPropertyCount
* (standardHeight + EditorGUIUtility.standardVerticalSpacing);
return standardHeight
+ detailsHeight
+ EditorGUIUtility.standardVerticalSpacing
+ EditorGUIUtility.standardVerticalSpacing;
}
}
#nullable disable
#nullable disable
public sealed class StringListSearchProvider : ScriptableObject, ISearchWindowProvider
{
@@ -223,12 +256,13 @@ namespace Speckle.ConnectorUnity.Wrappers.Selection.Editor
public string[] listItems;
public Action<int> onSetIndexCallback;
public List<SearchTreeEntry> CreateSearchTree(SearchWindowContext context)
{
List<SearchTreeEntry> searchList = new(listItems.Length + 1) {new SearchTreeGroupEntry(new GUIContent(Title), 0)};
for(int i = 0; i < listItems.Length; i++)
List<SearchTreeEntry> searchList =
new(listItems.Length + 1) { new SearchTreeGroupEntry(new GUIContent(Title), 0) };
for (int i = 0; i < listItems.Length; i++)
{
SearchTreeEntry entry = new SearchTreeEntry(new GUIContent(listItems[i]))
{
@@ -237,18 +271,15 @@ namespace Speckle.ConnectorUnity.Wrappers.Selection.Editor
};
searchList.Add(entry);
}
return searchList;
}
public bool OnSelectEntry(SearchTreeEntry SearchTreeEntry, SearchWindowContext context)
{
onSetIndexCallback?.Invoke((int)SearchTreeEntry.userData);
return true;
}
}
}
@@ -34,12 +34,16 @@ namespace Speckle.ConnectorUnity.Wrappers.Editor
.Where(x => x.FullName != null)
.Select(x => x.FullName!.Replace('.', '/'));
var manualTypes = new [] { typeof(Base), typeof(Collection)};
var manualStrings = new []{ nameof(Base), nameof(Collection)};
//Manually Add `Base`
SpeckleTypeOptions = options.Concat(manualTypes).ToArray();
SpeckleTypeOptionStrings = strings.Concat(manualStrings).ToArray();
SpeckleTypeOptionStrings = strings.Append(nameof(Base)).ToArray();
SpeckleTypeOptions = options.Append(typeof(Base)).ToArray();
Debug.Assert(SpeckleTypeOptions.Length == SpeckleTypeOptionStrings.Length);
}
private static GUILayoutOption[] propLayoutOptions = { GUILayout.ExpandWidth(true) };
public override void OnInspectorGUI()
@@ -5,185 +5,212 @@ using Speckle.Core.Logging;
using Speckle.Core.Transports;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Sentry;
using Speckle.ConnectorUnity.Components;
using Speckle.ConnectorUnity.Utils;
using Speckle.Core.Kits;
using UnityEngine;
namespace Speckle.ConnectorUnity
{
/// <summary>
/// A Speckle Receiver, it's a wrapper around a basic Speckle Client
/// that handles conversions and subscriptions for you
/// </summary>
[RequireComponent( typeof( RecursiveConverter ) )]
public class Receiver : MonoBehaviour
{
public string StreamId;
public string BranchName = "main";
public Stream Stream;
public int TotalChildrenCount = 0;
public GameObject ReceivedData;
private bool AutoReceive;
private bool DeleteOld;
private Action<ConcurrentDictionary<string, int>> OnProgressAction;
private Action<string, Exception> OnErrorAction;
private Action<int> OnTotalChildrenCountKnown;
private Action<GameObject> OnDataReceivedAction;
private Client Client { get; set; }
public Receiver()
{
}
/// <summary>
/// Initializes the Receiver manually
/// A Speckle Receiver, it's a wrapper around a basic Speckle Client
/// that handles conversions and subscriptions for you
/// </summary>
/// <param name="streamId">Id of the stream to receive</param>
/// <param name="autoReceive">If true, it will automatically receive updates sent to this stream</param>
/// <param name="deleteOld">If true, it will delete previously received objects when new one are received</param>
/// <param name="account">Account to use, if null the default account will be used</param>
/// <param name="onDataReceivedAction">Action to run after new data has been received and converted</param>
/// <param name="onProgressAction">Action to run when there is download/conversion progress</param>
/// <param name="onErrorAction">Action to run on error</param>
/// <param name="onTotalChildrenCountKnown">Action to run when the TotalChildrenCount is known</param>
public void Init(string streamId, bool autoReceive = false, bool deleteOld = true, Account account = null,
Action<GameObject> onDataReceivedAction = null, Action<ConcurrentDictionary<string, int>> onProgressAction = null,
Action<string, Exception> onErrorAction = null, Action<int> onTotalChildrenCountKnown = null)
[RequireComponent(typeof(RecursiveConverter))]
[Obsolete]
public class Receiver : MonoBehaviour
{
StreamId = streamId;
AutoReceive = autoReceive;
DeleteOld = deleteOld;
OnDataReceivedAction = onDataReceivedAction;
OnErrorAction = onErrorAction;
OnProgressAction = onProgressAction;
OnTotalChildrenCountKnown = onTotalChildrenCountKnown;
public string StreamId;
public string BranchName = "main";
public Stream Stream;
public int TotalChildrenCount = 0;
public GameObject ReceivedData;
Client = new Client(account ?? AccountManager.GetDefaultAccount());
private bool AutoReceive;
private bool DeleteOld;
private Action<ConcurrentDictionary<string, int>> OnProgressAction;
private Action<string, Exception> OnErrorAction;
private Action<int> OnTotalChildrenCountKnown;
private Action<GameObject> OnDataReceivedAction;
private Client Client { get; set; }
if (AutoReceive)
{
Client.SubscribeCommitCreated(StreamId);
Client.OnCommitCreated += Client_OnCommitCreated;
}
}
public Receiver() { }
/// <summary>
/// Gets and converts the data of the last commit on the Stream
/// </summary>
/// <returns></returns>
public void Receive()
{
if (Client == null || string.IsNullOrEmpty(StreamId))
throw new Exception("Receiver has not been initialized. Please call Init().");
Task.Run(async () =>
{
try
/// <summary>
/// Initializes the Receiver manually
/// </summary>
/// <param name="streamId">Id of the stream to receive</param>
/// <param name="autoReceive">If true, it will automatically receive updates sent to this stream</param>
/// <param name="deleteOld">If true, it will delete previously received objects when new one are received</param>
/// <param name="account">Account to use, if null the default account will be used</param>
/// <param name="onDataReceivedAction">Action to run after new data has been received and converted</param>
/// <param name="onProgressAction">Action to run when there is download/conversion progress</param>
/// <param name="onErrorAction">Action to run on error</param>
/// <param name="onTotalChildrenCountKnown">Action to run when the TotalChildrenCount is known</param>
public void Init(
string streamId,
bool autoReceive = false,
bool deleteOld = true,
Account account = null,
Action<GameObject> onDataReceivedAction = null,
Action<ConcurrentDictionary<string, int>> onProgressAction = null,
Action<string, Exception> onErrorAction = null,
Action<int> onTotalChildrenCountKnown = null
)
{
var mainBranch = await Client.BranchGet(StreamId, BranchName, 1);
if (!mainBranch.commits.items.Any())
throw new Exception("This branch has no commits");
var commit = mainBranch.commits.items[0];
GetAndConvertObject(commit.referencedObject, commit.id);
StreamId = streamId;
AutoReceive = autoReceive;
DeleteOld = deleteOld;
OnDataReceivedAction = onDataReceivedAction;
OnErrorAction = onErrorAction;
OnProgressAction = onProgressAction;
OnTotalChildrenCountKnown = onTotalChildrenCountKnown;
Client = new Client(account ?? AccountManager.GetDefaultAccount());
if (AutoReceive)
{
Client.SubscribeCommitCreated(StreamId);
Client.OnCommitCreated += Client_OnCommitCreated;
}
}
catch (Exception e)
/// <summary>
/// Gets and converts the data of the last commit on the Stream
/// </summary>
/// <returns></returns>
public void Receive()
{
throw new SpeckleException(e.Message, e, true, SentryLevel.Error);
if (Client == null || string.IsNullOrEmpty(StreamId))
throw new Exception("Receiver has not been initialized. Please call Init().");
Task.Run(async () =>
{
try
{
var mainBranch = await Client.BranchGet(StreamId, BranchName, 1);
if (!mainBranch.commits.items.Any())
throw new Exception("This branch has no commits");
var commit = mainBranch.commits.items[0];
GetAndConvertObject(
commit.referencedObject,
commit.id,
commit.sourceApplication,
commit.authorId
);
}
catch (Exception e)
{
throw new SpeckleException(e.Message, e, true, SentryLevel.Error);
}
});
}
});
}
#region private methods
#region private methods
/// <summary>
/// Fired when a new commit is created on this stream
/// It receives and converts the objects and then executes the user defined _onCommitCreated action.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected virtual void Client_OnCommitCreated(object sender, CommitInfo e)
{
if (e.branchName == BranchName)
{
Debug.Log("New commit created");
GetAndConvertObject(e.objectId, e.id);
}
}
private async void GetAndConvertObject(string objectId, string commitId)
{
try
{
var transport = new ServerTransport(Client.Account, StreamId);
var @base = await Operations.Receive(
objectId,
remoteTransport: transport,
onErrorAction: OnErrorAction,
onProgressAction: OnProgressAction,
onTotalChildrenCountKnown: OnTotalChildrenCountKnown,
disposeTransports: true
);
Analytics.TrackEvent(Client.Account, Analytics.Events.Receive);
Dispatcher.Instance().Enqueue(() =>
/// <summary>
/// Fired when a new commit is created on this stream
/// It receives and converts the objects and then executes the user defined _onCommitCreated action.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected virtual void Client_OnCommitCreated(object sender, CommitInfo e)
{
var root = new GameObject()
{
name = commitId,
};
if (e.branchName == BranchName)
{
Debug.Log("New commit created");
GetAndConvertObject(e.objectId, e.id, e.sourceApplication, e.authorId);
}
}
var rc = GetComponent<RecursiveConverter>();
var go = rc.RecursivelyConvertToNative(@base, root.transform);
//remove previously received object
if (DeleteOld && ReceivedData != null)
Destroy(ReceivedData);
ReceivedData = root;
OnDataReceivedAction?.Invoke(root);
});
}
catch (Exception e)
{
throw new SpeckleException(e.Message, e, true, SentryLevel.Error);
}
try
{
await Client.CommitReceived(new CommitReceivedInput
private async void GetAndConvertObject(
string objectId,
string commitId,
string sourceApplication,
string authorId
)
{
streamId = StreamId,
commitId = commitId,
message = $"received commit from {Application.unityVersion}",
sourceApplication = HostApplications.Unity.GetVersion(CoreUtils.GetHostAppVersion())
});
}
catch
{
// Do nothing!
}
try
{
var transport = new ServerTransport(Client.Account, StreamId);
var @base = await Operations.Receive(
objectId,
remoteTransport: transport,
onErrorAction: OnErrorAction,
onProgressAction: OnProgressAction,
onTotalChildrenCountKnown: OnTotalChildrenCountKnown,
disposeTransports: true
);
Analytics.TrackEvent(
Client.Account,
Analytics.Events.Receive,
new Dictionary<string, object>()
{
{ "mode", nameof(Receiver) },
{
"sourceHostApp",
HostApplications.GetHostAppFromString(sourceApplication).Slug
},
{ "sourceHostAppVersion", sourceApplication ?? "" },
{ "hostPlatform", Application.platform.ToString() },
{
"isMultiplayer",
authorId != null && authorId != Client.Account.userInfo.id
},
}
);
Dispatcher
.Instance()
.Enqueue(() =>
{
var root = new GameObject() { name = commitId, };
var rc = GetComponent<RecursiveConverter>();
var go = rc.RecursivelyConvertToNative(@base, root.transform);
//remove previously received object
if (DeleteOld && ReceivedData != null)
Destroy(ReceivedData);
ReceivedData = root;
OnDataReceivedAction?.Invoke(root);
});
}
catch (Exception e)
{
throw new SpeckleException(e.Message, e, true, SentryLevel.Error);
}
try
{
await Client.CommitReceived(
new CommitReceivedInput
{
streamId = StreamId,
commitId = commitId,
message = $"received commit from {Application.unityVersion}",
sourceApplication = HostApplications.Unity.GetVersion(
CoreUtils.GetHostAppVersion()
)
}
);
}
catch
{
// Do nothing!
}
}
private void OnDestroy()
{
Client?.CommitCreatedSubscription?.Dispose();
}
#endregion
}
private void OnDestroy()
{
Client?.CommitCreatedSubscription?.Dispose();
}
#endregion
}
}
}
@@ -16,134 +16,150 @@ using UnityEngine.SceneManagement;
namespace Speckle.ConnectorUnity
{
/// <summary>
/// A Speckle Sender, it's a wrapper around a basic Speckle Client
/// that handles conversions for you
/// </summary>
[RequireComponent(typeof(RecursiveConverter)), ExecuteAlways]
public class Sender : MonoBehaviour
{
private ServerTransport transport;
private RecursiveConverter converter;
private CancellationTokenSource cancellationTokenSource;
#nullable enable
private void Awake()
{
converter = GetComponent<RecursiveConverter>();
}
/// <summary>
/// Converts and sends the data of the last commit on the Stream
/// A Speckle Sender, it's a wrapper around a basic Speckle Client
/// that handles conversions for you
/// </summary>
/// <param name="streamId">ID of the stream to send to</param>
/// <param name="gameObjects">List of gameObjects to convert and send</param>
/// <param name="account">Account to use. If not provided the default account will be used</param>
/// <param name="branchName">Name of branch to send to</param>
/// <param name="createCommit">When true, will create a commit using the root object</param>
/// <param name="onDataSentAction">Action to run after the data has been sent</param>
/// <param name="onProgressAction">Action to run when there is download/conversion progress</param>
/// <param name="onErrorAction">Action to run on error</param>
/// <exception cref="SpeckleException"></exception>
public void Send(string streamId,
ISet<GameObject> gameObjects,
Account? account = null,
string branchName = "main",
bool createCommit = true,
Action<string>? onDataSentAction = null,
Action<ConcurrentDictionary<string, int>>? onProgressAction = null,
Action<string, Exception>? onErrorAction = null)
[RequireComponent(typeof(RecursiveConverter)), ExecuteAlways]
[Obsolete]
public class Sender : MonoBehaviour
{
try
{
CancelOperations();
cancellationTokenSource = new CancellationTokenSource();
var client = new Client(account ?? AccountManager.GetDefaultAccount());
transport = new ServerTransport(client.Account, streamId);
transport.CancellationToken = cancellationTokenSource.Token;
var rootObjects = SceneManager.GetActiveScene().GetRootGameObjects();
var data = converter.RecursivelyConvertToSpeckle(rootObjects,
o => gameObjects.Contains(o));
SendData(transport, data, client, branchName, createCommit, cancellationTokenSource.Token, onDataSentAction, onProgressAction, onErrorAction);
}
catch (Exception e)
{
throw new SpeckleException(e.ToString(), e, true, SentryLevel.Error);
}
}
private ServerTransport transport;
private RecursiveConverter converter;
private CancellationTokenSource cancellationTokenSource;
#nullable enable
public static void SendData(ServerTransport remoteTransport,
Base data,
Client client,
string branchName,
bool createCommit,
CancellationToken cancellationToken,
Action<string>? onDataSentAction = null,
Action<ConcurrentDictionary<string, int>>? onProgressAction = null,
Action<string, Exception>? onErrorAction = null)
{
Task.Run(async () =>
{
var res = await Operations.Send(
data,
cancellationToken: cancellationToken,
new List<ITransport>() {remoteTransport},
useDefaultCache: true,
disposeTransports: true,
onProgressAction: onProgressAction,
onErrorAction: onErrorAction
);
Analytics.TrackEvent(client.Account, Analytics.Events.Send);
if (createCommit && !cancellationToken.IsCancellationRequested)
private void Awake()
{
long count = data.GetTotalChildrenCount();
await client.CommitCreate(cancellationToken,
new CommitCreateInput
{
streamId = remoteTransport.StreamId,
branchName = branchName,
objectId = res,
message = $"Sent {count} objects from Unity",
sourceApplication = HostApplications.Unity.Name,
totalChildrenCount = (int)count,
});
converter = GetComponent<RecursiveConverter>();
}
onDataSentAction?.Invoke(res);
}, cancellationToken);
/// <summary>
/// Converts and sends the data of the last commit on the Stream
/// </summary>
/// <param name="streamId">ID of the stream to send to</param>
/// <param name="gameObjects">List of gameObjects to convert and send</param>
/// <param name="account">Account to use. If not provided the default account will be used</param>
/// <param name="branchName">Name of branch to send to</param>
/// <param name="createCommit">When true, will create a commit using the root object</param>
/// <param name="onDataSentAction">Action to run after the data has been sent</param>
/// <param name="onProgressAction">Action to run when there is download/conversion progress</param>
/// <param name="onErrorAction">Action to run on error</param>
/// <exception cref="SpeckleException"></exception>
public void Send(
string streamId,
ISet<GameObject> gameObjects,
Account? account = null,
string branchName = "main",
bool createCommit = true,
Action<string>? onDataSentAction = null,
Action<ConcurrentDictionary<string, int>>? onProgressAction = null,
Action<string, Exception>? onErrorAction = null
)
{
try
{
CancelOperations();
cancellationTokenSource = new CancellationTokenSource();
var client = new Client(account ?? AccountManager.GetDefaultAccount());
transport = new ServerTransport(client.Account, streamId);
transport.CancellationToken = cancellationTokenSource.Token;
var rootObjects = SceneManager.GetActiveScene().GetRootGameObjects();
var data = converter.RecursivelyConvertToSpeckle(
rootObjects,
o => gameObjects.Contains(o)
);
SendData(
transport,
data,
client,
branchName,
createCommit,
cancellationTokenSource.Token,
onDataSentAction,
onProgressAction,
onErrorAction
);
}
catch (Exception e)
{
throw new SpeckleException(e.ToString(), e, true, SentryLevel.Error);
}
}
public static void SendData(
ServerTransport remoteTransport,
Base data,
Client client,
string branchName,
bool createCommit,
CancellationToken cancellationToken,
Action<string>? onDataSentAction = null,
Action<ConcurrentDictionary<string, int>>? onProgressAction = null,
Action<string, Exception>? onErrorAction = null
)
{
Task.Run(
async () =>
{
var res = await Operations.Send(
data,
cancellationToken: cancellationToken,
new List<ITransport>() { remoteTransport },
useDefaultCache: true,
disposeTransports: true,
onProgressAction: onProgressAction,
onErrorAction: onErrorAction
);
Analytics.TrackEvent(client.Account, Analytics.Events.Send);
if (createCommit && !cancellationToken.IsCancellationRequested)
{
long count = data.GetTotalChildrenCount();
await client.CommitCreate(
cancellationToken,
new CommitCreateInput
{
streamId = remoteTransport.StreamId,
branchName = branchName,
objectId = res,
message = $"Sent {count} objects from Unity",
sourceApplication = HostApplications.Unity.Name,
totalChildrenCount = (int)count,
}
);
}
onDataSentAction?.Invoke(res);
},
cancellationToken
);
}
private void OnDestroy()
{
CancelOperations();
}
public void CancelOperations()
{
cancellationTokenSource?.Cancel();
transport?.Dispose();
cancellationTokenSource?.Dispose();
}
#region private methods
#endregion
}
private void OnDestroy()
{
CancelOperations();
}
public void CancelOperations()
{
cancellationTokenSource?.Cancel();
transport?.Dispose();
cancellationTokenSource?.Dispose();
}
#region private methods
#endregion
}
}
}
@@ -29,16 +29,18 @@ namespace Speckle.ConnectorUnity.Components
public List<Branch> Branches;
public RecursiveConverter RC { get; private set; }
#nullable enable
private void Awake()
{
RC = GetComponent<RecursiveConverter>();
}
public GameObject ConvertRecursivelyToNative(Base @base, string rootObjectName,
Action<Base>? beforeConvertCallback)
public GameObject ConvertRecursivelyToNative(
Base @base,
string rootObjectName,
Action<Base>? beforeConvertCallback
)
{
var rootObject = new GameObject(rootObjectName);
@@ -46,10 +48,9 @@ namespace Speckle.ConnectorUnity.Components
{
beforeConvertCallback?.Invoke(o);
return RC.ConverterInstance.CanConvertToNative(o) //Accept geometry
|| o.speckle_type == nameof(Base) && o.totalChildrenCount > 0; // Or Base objects that have children
|| o.speckle_type == nameof(Base) && o.totalChildrenCount > 0; // Or Base objects that have children
}
// For the rootObject only, we will create property GameObjects
// i.e. revit categories
foreach (var prop in @base.GetMembers())
@@ -57,13 +58,15 @@ namespace Speckle.ConnectorUnity.Components
var converted = RC.RecursivelyConvertToNative(prop.Value, null, Predicate);
//Skip empties
if (converted.Count <= 0) continue;
if (converted.Count <= 0)
continue;
var propertyObject = new GameObject(prop.Key);
propertyObject.transform.SetParent(rootObject.transform);
foreach (var o in converted)
{
if (o.transform.parent == null) o.transform.SetParent(propertyObject.transform);
if (o.transform.parent == null)
o.transform.SetParent(propertyObject.transform);
}
}
@@ -80,4 +83,4 @@ namespace Speckle.ConnectorUnity.Components
}
#endif
}
}
}
@@ -0,0 +1,173 @@
using System;
using System.Collections;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Speckle.Core.Api;
using Speckle.Core.Credentials;
using Speckle.Core.Logging;
using Speckle.Core.Models;
using UnityEngine;
namespace Speckle.ConnectorUnity.Components
{
[AddComponentMenu("Speckle/Extras/Receive from URL")]
[RequireComponent(typeof(RecursiveConverter)), ExecuteAlways]
public class ReceiveFromURL : MonoBehaviour
{
[Tooltip("Url of your speckle object/commit/branch/stream")]
public string url;
private RecursiveConverter _converter;
#nullable enable
private CancellationTokenSource? _tokenSource;
void Awake()
{
_converter = GetComponent<RecursiveConverter>();
}
[ContextMenu(nameof(Receive))]
public void Receive()
{
StartCoroutine(Receive_Routine());
}
public IEnumerator Receive_Routine()
{
if (IsBusy())
throw new InvalidOperationException("A receive operation has already started");
_tokenSource = new CancellationTokenSource();
try
{
StreamWrapper sw = new(url);
if (!sw.IsValid)
throw new InvalidOperationException(
"Speckle url input is not a valid speckle stream/branch/commit"
);
var accountTask = new Utils.Utils.WaitForTask<Account>(
async () => await GetAccount(sw)
);
yield return accountTask;
_tokenSource.Token.ThrowIfCancellationRequested();
using Client c = new(accountTask.Result);
var objectIdTask = new Utils.Utils.WaitForTask<(string, Commit?)>(
async () => await GetObjectID(sw, c)
);
yield return objectIdTask;
(string objectId, Commit? commit) = objectIdTask.Result;
Debug.Log($"Receiving from {sw.ServerUrl}...");
var receiveTask = new Utils.Utils.WaitForTask<Base>(
async () =>
await SpeckleReceiver.ReceiveAsync(
c,
sw.StreamId,
objectId,
commit,
cancellationToken: _tokenSource.Token
)
);
yield return receiveTask;
Debug.Log("Converting to native...");
_converter.RecursivelyConvertToNative_Sync(receiveTask.Result, transform);
}
finally
{
_tokenSource.Dispose();
_tokenSource = null;
}
}
private async Task<(string objectId, Commit? commit)> GetObjectID(
StreamWrapper sw,
Client client
)
{
string objectId;
Commit? commit = null;
//OBJECT URL
if (!string.IsNullOrEmpty(sw.ObjectId))
{
objectId = sw.ObjectId;
}
//COMMIT URL
else if (!string.IsNullOrEmpty(sw.CommitId))
{
commit = await client.CommitGet(sw.StreamId, sw.CommitId).ConfigureAwait(false);
objectId = commit.referencedObject;
}
//BRANCH URL OR STREAM URL
else
{
var branchName = string.IsNullOrEmpty(sw.BranchName) ? "main" : sw.BranchName;
var branch = await client
.BranchGet(sw.StreamId, branchName, 1)
.ConfigureAwait(false);
if (!branch.commits.items.Any())
throw new SpeckleException("The selected branch has no commits.");
commit = branch.commits.items[0];
objectId = branch.commits.items[0].referencedObject;
}
return (objectId, commit);
}
[ContextMenu(nameof(Cancel))]
public void Cancel()
{
if (IsNotBusy())
throw new InvalidOperationException(
"There are no pending receive operations to cancel"
);
_tokenSource!.Cancel();
}
[ContextMenu(nameof(Cancel), true)]
public bool IsBusy()
{
return _tokenSource is not null;
}
[ContextMenu(nameof(Receive), true)]
internal bool IsNotBusy() => !IsBusy();
private void OnDisable()
{
_tokenSource?.Cancel();
}
private async Task<Account> GetAccount(StreamWrapper sw)
{
Account account;
try
{
account = await sw.GetAccount().ConfigureAwait(false);
}
catch (SpeckleException)
{
if (string.IsNullOrEmpty(sw.StreamId))
throw;
//Fallback to a non authed account
account = new Account
{
token = "",
serverInfo = new ServerInfo { url = sw.ServerUrl },
userInfo = new UserInfo()
};
}
return account;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 942bf0cb27c5c5045bc4cbb7fc0fad71
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -2,18 +2,264 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Speckle.ConnectorUnity.Utils;
using Speckle.Core.Logging;
using Speckle.Core.Models;
using Speckle.Core.Models.GraphTraversal;
using UnityEngine;
namespace Speckle.ConnectorUnity.Components
{
/// <summary>
/// Struct that encapsulates the result of a <see cref="RecursiveConverter"/> ToNative conversion of a single Speckle Object (<see cref="Base"/>)
/// </summary>
public readonly struct ConversionResult
{
/// <summary>
/// The context that was converted ToNative
/// </summary>
public readonly TraversalContext traversalContext;
/// <summary>
/// The result of conversion a successful conversion
/// </summary>
public readonly GameObject? converted;
/// <summary>
/// The result of conversion a failed conversion
/// </summary>
public readonly Exception? exception;
/// <summary>
/// Constructor used for Successful conversions
/// </summary>
/// <param name="traversalContext">The current traversal context</param>
/// <param name="converted">The resultant ToNative conversion of <see cref="TraversalContext.current"/> context object</param>
/// <exception cref="ArgumentNullException"/>
public ConversionResult(TraversalContext traversalContext, [NotNull] GameObject? converted)
: this(traversalContext, converted, null)
{
if (converted is null)
throw new ArgumentNullException(nameof(converted));
}
/// <summary>
/// Constructor used for Failed conversions
/// </summary>
/// <param name="traversalContext">The current conversion</param>
/// <param name="exception">The operation halting exception that occured</param>
/// <param name="converted">Optional converted GameObject</param>
/// <exception cref="ArgumentNullException"/>
public ConversionResult(
TraversalContext traversalContext,
[NotNull] Exception? exception,
GameObject? converted = null
)
: this(traversalContext, converted, exception)
{
if (exception is null)
throw new ArgumentNullException(nameof(exception));
}
private ConversionResult(
TraversalContext traversalContext,
GameObject? converted,
Exception? exception
)
{
this.traversalContext = traversalContext;
this.converted = converted;
this.exception = exception;
}
/// <summary>
///
/// </summary>
/// <param name="converted">The converted <see cref="GameObject"/></param>
/// <param name="exception">The <see cref="exception"/> that occured during conversion</param>
/// <returns>True if the conversion was successful</returns>
public bool WasSuccessful(
[NotNullWhen(true)] out GameObject? converted,
[NotNullWhen(false)] out Exception? exception
)
{
converted = this.converted;
exception = this.exception;
return WasSuccessful();
}
public bool WasSuccessful() => this.exception == null;
public Base SpeckleObject => traversalContext.current;
}
public partial class RecursiveConverter
{
public IEnumerator ConvertCoroutine(Base rootObject, Transform? parent, List<GameObject> outCreatedObjects)
=> ConvertCoroutine(rootObject, parent, outCreatedObjects,b => ConverterInstance.CanConvertToNative(b));
public IEnumerator ConvertCoroutine(Base rootObject, Transform? parent, List<GameObject> outCreatedObjects, Func<Base, bool> predicate)
/// <inheritdoc cref="RecursivelyConvertToNative_Enumerable"/>
/// <remarks>Calling this function will perform the conversion process synchronously</remarks>
/// <returns>The conversion result</returns>
public List<ConversionResult> RecursivelyConvertToNative_Sync(
Base rootObject,
Transform? parent,
Predicate<TraversalContext>? predicate = null
)
{
return RecursivelyConvertToNative_Enumerable(rootObject, parent, predicate).ToList();
}
/// <inheritdoc cref="RecursivelyConvertToNative_Enumerable"/>
/// <remarks>Calling this function will start a coroutine to complete later on the coroutine loop</remarks>
/// <returns>The started Coroutine</returns>
public Coroutine RecursivelyConvertToNative_Coroutine(
Base rootObject,
Transform? parent,
Predicate<TraversalContext>? predicate = null
)
{
return StartCoroutine(
RecursivelyConvertToNative_Enumerable(rootObject, parent, predicate).GetEnumerator()
);
}
/// <summary>
/// Will recursively traverse the given <paramref name="rootObject"/> and convert convertable child objects
/// where the given <see cref="predicate"/>
/// </summary>
/// <param name="rootObject">The Speckle object to traverse and convert all convertable children</param>
/// <param name="parent">Optional parent <see cref="Transform"/> for the created root <see cref="GameObject"/>s</param>
/// <param name="predicate">A filter function to allow for selectively excluding certain objects from being converted</param>
/// <returns>An unevaluated <see cref="IEnumerable"/> of all created <see cref="GameObject"/>s</returns>
public IEnumerable<ConversionResult> RecursivelyConvertToNative_Enumerable(
Base rootObject,
Transform? parent,
Predicate<TraversalContext>? predicate = null
)
{
var userPredicate = predicate ?? (_ => true);
var traversalFunc = DefaultTraversal.CreateBIMTraverseFunc(ConverterInstance);
var objectsToConvert = traversalFunc
.Traverse(rootObject)
.Where(x => ConverterInstance.CanConvertToNative(x.current))
.Where(x => userPredicate(x));
Dictionary<Base, GameObject?> created = new();
foreach (var conversionResult in ConvertTree(objectsToConvert, parent, created))
{
if (!isActiveAndEnabled)
throw new InvalidOperationException(
$"Cannot convert objects while {GetType()} is disabled"
);
yield return conversionResult;
}
}
/// <summary>
/// Converts a objectTree (see <see cref="GraphTraversal"/>) to unevaluated enumerable.
/// As this enumerable is iterated through, each context <see cref="Base"/> object will be converted to <see cref="GameObject"/> (if successful)
/// or <see langword="null"/> if not.
/// </summary>
/// <remarks>
/// You may enumerate over multiple frames (e.g. coroutine) but you must ensure the output eventually gets fully enumerated (exactly once)
/// </remarks>
/// <param name="objectTree"></param>
/// <param name="parent"></param>
/// <param name="outCreatedObjects"></param>
/// <returns></returns>
protected IEnumerable<ConversionResult> ConvertTree(
IEnumerable<TraversalContext> objectTree,
Transform? parent,
IDictionary<Base, GameObject?> outCreatedObjects
)
{
InitializeAssetCache();
AssetCache.BeginWrite();
foreach (TraversalContext tc in objectTree)
{
ConversionResult result;
try
{
Transform? currentParent = GetParent(tc, outCreatedObjects) ?? parent;
var converted = ConvertToNative(tc.current, currentParent);
result = new ConversionResult(tc, converted);
outCreatedObjects.TryAdd(tc.current, result.converted);
}
catch (Exception ex)
{
result = new ConversionResult(tc, ex);
}
yield return result;
}
AssetCache.FinishWrite();
}
protected static Transform? GetParent(
TraversalContext? tc,
IDictionary<Base, GameObject?> createdObjects
)
{
if (tc == null)
return null; //We've reached the root object, and still not found a converted parent
if (createdObjects.TryGetValue(tc.current, out GameObject? p) && p != null)
return p.transform;
//Go one level up, and repeat!
return GetParent(tc.parent, createdObjects);
}
protected GameObject ConvertToNative(Base speckleObject, Transform? parentTransform)
{
GameObject? go = ConverterInstance.ConvertToNative(speckleObject) as GameObject;
if (go == null)
throw new SpeckleException("Conversion Returned Null");
go.transform.SetParent(parentTransform, true);
//Set some common for all created GameObjects
//TODO add support for more unity specific props
if (go.name == "New Game Object" || string.IsNullOrWhiteSpace(go.name))
go.name = CoreUtils.GenerateObjectName(speckleObject);
if (speckleObject["physicsLayer"] is string layerName)
{
int layer = LayerMask.NameToLayer(layerName); //TODO: check how this can be interoperable with Unreal and Blender
if (layer > -1)
go.layer = layer;
}
//if (baseObject["tag"] is string t) go.tag = t;
//if (baseObject["isStatic"] is bool isStatic) go.isStatic = isStatic;
return go;
}
#region deprecated conversion functions
[Obsolete("Use " + nameof(RecursivelyConvertToNative_Coroutine))]
public IEnumerator ConvertCoroutine(
Base rootObject,
Transform? parent,
List<GameObject> outCreatedObjects
) =>
ConvertCoroutine(
rootObject,
parent,
outCreatedObjects,
b => ConverterInstance.CanConvertToNative(b)
);
[Obsolete("Use " + nameof(RecursivelyConvertToNative_Coroutine))]
public IEnumerator ConvertCoroutine(
Base rootObject,
Transform? parent,
List<GameObject> outCreatedObjects,
Func<Base, bool> predicate
)
{
foreach (string propertyName in GetPotentialChildren(rootObject))
{
@@ -21,7 +267,7 @@ namespace Speckle.ConnectorUnity.Components
yield return null;
}
}
/// <summary>
/// Given <paramref name="o"/>,
/// will recursively convert any objects in the tree
@@ -29,21 +275,22 @@ namespace Speckle.ConnectorUnity.Components
/// <param name="o">The object to convert (<see cref="Base"/> or <see cref="List{T}"/> of)</param>
/// <param name="parent">Optional parent transform for the created root <see cref="GameObject"/>s</param>
/// <returns> A list of all created <see cref="GameObject"/>s</returns>
public virtual List<GameObject> RecursivelyConvertToNative(object? o, Transform? parent)
=> RecursivelyConvertToNative(o, parent, b => ConverterInstance.CanConvertToNative(b));
[Obsolete("Use " + nameof(RecursivelyConvertToNative_Sync))]
public virtual List<GameObject> RecursivelyConvertToNative(object? o, Transform? parent) =>
RecursivelyConvertToNative(o, parent, b => ConverterInstance.CanConvertToNative(b));
/// <inheritdoc cref="RecursivelyConvertToNative(object, Transform)"/>
/// <param name="predicate">A function to determine if an object should be converted</param>
public virtual List<GameObject> RecursivelyConvertToNative(object? o, Transform? parent, Func<Base, bool> predicate)
[Obsolete("Use " + nameof(RecursivelyConvertToNative_Sync))]
public virtual List<GameObject> RecursivelyConvertToNative(
object? o,
Transform? parent,
Func<Base, bool> predicate
)
{
//Ensure we have A native cache
if (AssetCache.nativeCaches.Any(x => x == null))
{
AssetCache.nativeCaches = NativeCacheFactory.GetStandaloneCacheSetup();
}
InitializeAssetCache();
var createdGameObjects = new List<GameObject>();
ConverterInstance.SetContextDocument(AssetCache);
try
{
AssetCache.BeginWrite();
@@ -55,33 +302,31 @@ namespace Speckle.ConnectorUnity.Components
}
//TODO track event?
return createdGameObjects;
}
protected string[] namePropertyAliases = {"name", "Name"};
protected virtual string GenerateObjectName(Base baseObject)
private void InitializeAssetCache()
{
// 1. Use explicit name
foreach (var nameAlias in namePropertyAliases)
//Ensure we have A native cache
if (AssetCache.nativeCaches.Any(x => x == null))
{
string? s = baseObject[nameAlias] as string;
if (!string.IsNullOrWhiteSpace(s)) return s; //TODO any sanitization needed?
AssetCache.nativeCaches = NativeCacheFactory.GetStandaloneCacheSetup();
}
// 2. Use type + id as fallback name
// Only take the most derived type from the speckle type
string speckleType = baseObject.speckle_type.Split(':').Last();
return $"{speckleType} - {baseObject.id}";
ConverterInstance.SetContextDocument(AssetCache);
}
public virtual void RecurseTreeToNative(Base baseObject, Transform? parent, Func<Base, bool> predicate, IList<GameObject> outCreatedObjects)
[Obsolete]
public virtual void RecurseTreeToNative(
Base baseObject,
Transform? parent,
Func<Base, bool> predicate,
IList<GameObject> outCreatedObjects
)
{
object? converted = null;
if(predicate(baseObject))
if (predicate(baseObject))
converted = ConverterInstance.ConvertToNative(baseObject);
// Handle new GameObjects
@@ -90,22 +335,23 @@ namespace Speckle.ConnectorUnity.Components
{
outCreatedObjects.Add(go);
nextParent = go.transform;
go.transform.SetParent(parent, true);
//Set some common for all created GameObjects
//TODO add support for more unity specific props
if(go.name == "New Game Object" || string.IsNullOrWhiteSpace(go.name))
go.name = GenerateObjectName(baseObject);
if (go.name == "New Game Object" || string.IsNullOrWhiteSpace(go.name))
go.name = CoreUtils.GenerateObjectName(baseObject);
//if (baseObject["tag"] is string t) go.tag = t;
if (baseObject["physicsLayer"] is string layerName)
{
int layer = LayerMask.NameToLayer(layerName); //TODO: check how this can be interoperable with Unreal and Blender
if (layer > -1) go.layer = layer;
if (layer > -1)
go.layer = layer;
}
//if (baseObject["isStatic"] is bool isStatic) go.isStatic = isStatic;
}
// For geometry, only traverse `elements` prop, otherwise, try and convert everything
IEnumerable<string> potentialChildren = GetPotentialChildren(baseObject);
@@ -114,54 +360,29 @@ namespace Speckle.ConnectorUnity.Components
{
ConvertChild(baseObject[propertyName], nextParent, predicate, outCreatedObjects);
}
}
[Obsolete]
private IEnumerable<string> GetPotentialChildren(Base baseObject)
{
return ConverterInstance.CanConvertToNative(baseObject)
? new []{"elements"}
? new[] { "elements" }
: baseObject.GetMembers().Keys;
}
protected virtual void ConvertChild(object? value, Transform? parent, Func<Base, bool> predicate, IList<GameObject> outCreatedObjects)
[Obsolete]
protected virtual void ConvertChild(
object? value,
Transform? parent,
Func<Base, bool> predicate,
IList<GameObject> outCreatedObjects
)
{
if(value == null) return;
if(value.GetType().IsPrimitive) return;
if (value is string) return;
if(value is Base o)
foreach (Base b in GraphTraversal.TraverseMember(value))
{
RecurseTreeToNative(o, parent, predicate, outCreatedObjects);
}
else if (value is IDictionary dictionary)
{
foreach (object v in dictionary.Keys)
{
ConvertChild(v, parent, predicate, outCreatedObjects);
}
}
else if (value is IList collection)
{
foreach (object v in collection)
{
ConvertChild(v, parent, predicate, outCreatedObjects);
}
}
else if(!value.GetType().IsValueType) //don't want to output errors for structs
{
Debug.Log($"Unknown type {value.GetType()} found when traversing tree, will be safely ignored");
RecurseTreeToNative(b, parent, predicate, outCreatedObjects);
}
}
[Obsolete("Use RecursivelyConvertToNative instead")]
public GameObject ConvertRecursivelyToNative(Base @base, string name)
{
var parentObject = new GameObject(name);
RecursivelyConvertToNative(@base, parentObject.transform);
return parentObject;
}
#endregion
}
}
}
@@ -8,7 +8,6 @@ using UnityEngine;
namespace Speckle.ConnectorUnity.Components
{
public partial class RecursiveConverter
{
/// <summary>
@@ -31,37 +30,44 @@ namespace Speckle.ConnectorUnity.Components
/// <param name="rootObjects">Root objects of a tree</param>
/// <param name="predicate">A function to determine if an object should be converted</param>
/// <returns>A simple <see cref="Base"/> wrapping converted objects</returns>
public virtual Base RecursivelyConvertToSpeckle(IEnumerable<GameObject> rootObjects, Func<GameObject, bool> predicate)
public virtual Base RecursivelyConvertToSpeckle(
IEnumerable<GameObject> rootObjects,
Func<GameObject, bool> predicate
)
{
List<Base> convertedRootObjects = new List<Base>();
foreach (GameObject rootObject in rootObjects)
{
RecurseTreeToSpeckle(rootObject, predicate, convertedRootObjects);
}
return new Base()
{
["@objects"] = convertedRootObjects,
};
return new Base() { ["@objects"] = convertedRootObjects, };
}
public virtual Base RecursivelyConvertToSpeckle(GameObject rootObject, Func<GameObject, bool> predicate)
public virtual Base RecursivelyConvertToSpeckle(
GameObject rootObject,
Func<GameObject, bool> predicate
)
{
return RecursivelyConvertToSpeckle(new[] {rootObject}, predicate);
return RecursivelyConvertToSpeckle(new[] { rootObject }, predicate);
}
public virtual void RecurseTreeToSpeckle(GameObject currentObject, Func<GameObject, bool> predicate, List<Base> outConverted)
public virtual void RecurseTreeToSpeckle(
GameObject currentObject,
Func<GameObject, bool> predicate,
List<Base> outConverted
)
{
// Convert children first
var convertedChildren = new List<Base>(currentObject.transform.childCount);
foreach(Transform child in currentObject.transform)
foreach (Transform child in currentObject.transform)
{
RecurseTreeToSpeckle(child.gameObject, predicate, convertedChildren);
}
if (ConverterInstance.CanConvertToSpeckle(currentObject) && predicate(currentObject))
{
// Convert and output
// Convert and output
Base converted = ConverterInstance.ConvertToSpeckle(currentObject);
converted.SetDetachedPropertyChecked("elements", convertedChildren);
outConverted.Add(converted);
@@ -71,7 +77,6 @@ namespace Speckle.ConnectorUnity.Components
// Skip this object, and output any children
outConverted.AddRange(convertedChildren);
}
}
}
}
}
@@ -1,5 +1,6 @@
using Speckle.ConnectorUnity.Factories;
using Speckle.ConnectorUnity.NativeCache;
using Speckle.ConnectorUnity.Utils;
using Speckle.Core.Kits;
using Speckle.Core.Logging;
using UnityEngine;
@@ -13,14 +14,24 @@ namespace Speckle.ConnectorUnity.Components
[ExecuteAlways, DisallowMultipleComponent]
public partial class RecursiveConverter : MonoBehaviour
{
public ISpeckleConverter ConverterInstance { get; set; } = ConverterFactory.GetDefaultConverter();
[field: SerializeReference]
public ISpeckleConverter ConverterInstance { get; set; } =
ConverterFactory.GetDefaultConverter();
[field: SerializeField]
public AggregateNativeCache AssetCache { get; set; }
private void Awake()
protected void Awake()
{
Setup.Init(HostApplications.Unity.GetVersion(CoreUtils.GetHostAppVersion()), HostApplications.Unity.Slug);
Init();
}
protected void Init()
{
Setup.Init(
HostApplications.Unity.GetVersion(CoreUtils.GetHostAppVersion()),
HostApplications.Unity.Slug
);
if (AssetCache == null)
{
@@ -28,6 +39,8 @@ namespace Speckle.ConnectorUnity.Components
assetCache.nativeCaches = NativeCacheFactory.GetDefaultNativeCacheSetup();
this.AssetCache = assetCache;
}
InitializeAssetCache();
}
}
}
}
@@ -1,21 +1,25 @@
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Speckle.ConnectorUnity.Utils;
using Speckle.ConnectorUnity.Wrappers.Selection;
using Speckle.Core.Api;
using Speckle.Core.Credentials;
using Speckle.Core.Kits;
using Speckle.Core.Logging;
using Speckle.Core.Models;
using Speckle.Core.Models.GraphTraversal;
using Speckle.Core.Transports;
using UnityEngine;
using UnityEngine.Events;
[assembly: InternalsVisibleTo("Speckle.ConnectorUnity.Components.Editor")]
namespace Speckle.ConnectorUnity.Components
{
[ExecuteAlways]
@@ -25,146 +29,315 @@ namespace Speckle.ConnectorUnity.Components
{
[field: SerializeReference]
public AccountSelection Account { get; private set; }
[field: SerializeReference]
public StreamSelection Stream { get; private set; }
[field: SerializeReference]
public BranchSelection Branch { get; private set; }
[field: SerializeReference]
public CommitSelection Commit { get; private set; }
public RecursiveConverter Converter { get; private set; }
#nullable enable
[Header("Events")]
[HideInInspector]
public CommitSelectionEvent OnCommitSelectionChange;
[HideInInspector]
public OperationProgressEvent OnReceiveProgressAction;
[HideInInspector]
public ErrorActionEvent OnErrorAction;
[HideInInspector]
public ChildrenCountHandler OnTotalChildrenCountKnown;
[HideInInspector]
public ReceiveCompleteHandler OnComplete;
public CommitSelectionEvent OnCommitSelectionChange = new();
#nullable enable
protected internal CancellationTokenSource? CancellationTokenSource { get; private set; }
[HideInInspector]
public OperationProgressEvent OnReceiveProgressAction = new();
//TODO runtime receiving
public IEnumerator ReceiveAndConvertRoutine(SpeckleReceiver speckleReceiver, string rootObjectName, Action<Base>? beforeConvertCallback = null)
[HideInInspector]
public ErrorActionEvent OnErrorAction = new();
[HideInInspector]
public ChildrenCountHandler OnTotalChildrenCountKnown = new();
[HideInInspector]
public ReceiveCompleteHandler OnComplete = new();
protected CancellationTokenSource? CancellationTokenSource { get; private set; }
public CancellationToken CancellationToken => CancellationTokenSource?.Token ?? default;
public bool IsReceiving => CancellationTokenSource != null;
/// <summary>
/// Cancels any current receive operations
/// </summary>
/// <remarks>
/// Note, this does not cancel any currently executing ConvertToNative, just the <see cref="Operations.Receive"/>.
/// </remarks>
/// <returns><see langword="true"/> if the cancellation request was made. <see langword="false"/> if there was no pending operation to cancel (see <see cref="IsReceiving"/>)</returns>
public bool Cancel()
{
Task<Base?> receiveOperation = Task.Run(ReceiveAsync);
yield return new WaitUntil(() => receiveOperation.IsCompleted);
Base? b = receiveOperation.Result;
if (b == null) yield break;
//TODO make routine break for each catergory/object
GameObject go = ConvertToNativeWithCategories(b, rootObjectName, beforeConvertCallback);
OnComplete.Invoke(go);
if (CancellationTokenSource == null)
return false;
CancellationTokenSource.Cancel();
return true;
}
/// <summary>
/// Receive the selected <see cref="Commit"/> object, and converts ToNative as children of <paramref name="parent"/>
/// </summary>
/// <param name="parent">Optional parent <see cref="Transform"/> for the created root <see cref="GameObject"/>s</param>
/// <param name="predicate">A filter function to allow for selectively excluding certain objects from being converted</param>
/// <remarks>function does not throw, instead calls <see cref="OnErrorAction"/>, and calls <see cref="OnComplete"/> upon completion</remarks>
/// <seealso cref="ReceiveAsync(System.Threading.CancellationToken)"/>
/// <seealso cref="RecursiveConverter.RecursivelyConvertToNative_Enumerable"/>
public IEnumerator ReceiveAndConvert_Routine(
Transform? parent,
Predicate<TraversalContext>? predicate = null
)
{
if (IsReceiving)
{
OnErrorAction.Invoke(
"Failed to receive",
new InvalidOperationException("A pending receive operation has already started")
);
yield break;
}
CancellationTokenSource?.Dispose();
CancellationTokenSource = new();
// ReSharper disable once MethodSupportsCancellation
Task<Base> receiveOperation = Task.Run(async () =>
{
Base result = await ReceiveAsync(CancellationToken);
CancellationToken.ThrowIfCancellationRequested();
return result;
});
yield return new WaitUntil(() => receiveOperation.IsCompleted);
if (receiveOperation.IsFaulted)
{
OnErrorAction.Invoke("Failed to receive", receiveOperation.Exception);
FinishOperation();
yield break;
}
Base b = receiveOperation.Result;
foreach (var _ in Converter.RecursivelyConvertToNative_Enumerable(b, parent, predicate))
{
yield return null;
}
OnComplete.Invoke(parent);
FinishOperation();
}
/// <inheritdoc cref="ReceiveAndConvert_Routine"/>
public async void ReceiveAndConvert_Async(
Transform? parent,
Predicate<TraversalContext>? predicate = null
)
{
try
{
BeginOperation();
Base commitObject = await ReceiveAsync(CancellationToken).ConfigureAwait(true);
Converter.RecursivelyConvertToNative_Sync(commitObject, parent, predicate);
OnComplete.Invoke(parent);
}
catch (Exception ex)
{
OnErrorAction.Invoke("Failed to receive", ex);
}
finally
{
FinishOperation();
}
}
/// <summary>
/// Receives the selected commit object using async Task
/// </summary>
/// <returns>Awaitable commit object</returns>
/// <param name="cancellationToken"></param>
/// <exception cref="SpeckleException">thrown when selection is incomplete</exception>
public async Task<Base?> ReceiveAsync()
/// <remarks>
/// This function is safe to call concurrently from any threads.
/// For this reason we use <paramref name="cancellationToken"/> parameter, rather than use the <see cref="CancellationToken"/> property
/// <br/>
/// Additionally, <see cref="OnComplete"/> and <see cref="OnErrorAction"/> won't be called.
/// </remarks>
public async Task<Base> ReceiveAsync(CancellationToken cancellationToken)
{
CancellationTokenSource?.Cancel();
cancellationToken.ThrowIfCancellationRequested();
ValidateSelection(out Client? client, out Stream? stream, out Commit? commit);
Base result = await ReceiveAsync(
client: client,
streamId: stream.id,
objectId: commit.referencedObject,
commit: commit,
onProgressAction: dict => OnReceiveProgressAction.Invoke(dict),
onTotalChildrenCountKnown: c => OnTotalChildrenCountKnown.Invoke(c),
cancellationToken: cancellationToken
)
.ConfigureAwait(false);
return result;
}
public void ValidateSelection(out Client client, out Stream stream, out Commit commit)
{
Client? selectedClient = Account.Client;
client =
selectedClient ?? throw new InvalidOperationException("Invalid account selection");
Stream? selectedStream = Stream.Selected;
stream =
selectedStream ?? throw new InvalidOperationException("Invalid stream selection");
Commit? selectedCommit = Commit.Selected;
commit =
selectedCommit ?? throw new InvalidOperationException("Invalid commit selection");
}
/// <summary>
/// Starts a new receive operation with a <see cref="CancellationToken"/>
/// </summary>
/// <exception cref="InvalidOperationException">already receiving</exception>
protected internal CancellationToken BeginOperation()
{
if (IsReceiving)
throw new InvalidOperationException(
"A pending receive operation has already started"
);
CancellationTokenSource?.Dispose();
CancellationTokenSource = new CancellationTokenSource();
if(!GetSelection(out Client? client, out Stream? stream, out Commit? commit, out string? error))
throw new SpeckleException(error);
return await ReceiveAsync(
token: CancellationTokenSource.Token,
client: client,
streamId: stream.id,
objectId: commit.referencedObject,
commitId: commit.id,
onProgressAction: dict => OnReceiveProgressAction.Invoke(dict),
onErrorAction: (m, e) => OnErrorAction.Invoke(m, e),
onTotalChildrenCountKnown: c => OnTotalChildrenCountKnown.Invoke(c)
);
CancellationTokenSource = new();
return CancellationTokenSource.Token;
}
protected internal void FinishOperation()
{
if (!IsReceiving)
throw new InvalidOperationException("No pending operations to finish");
CancellationTokenSource!.Dispose();
CancellationTokenSource = null;
}
/// <summary>
/// Receives the requested <see cref="objectId"/> using async Task
/// </summary>
/// <param name="token"></param>
/// <param name="client"></param>
/// <param name="streamId"></param>
/// <param name="objectId"></param>
/// <param name="commitId"></param>
/// <param name="commit"></param>
/// <param name="onProgressAction"></param>
/// <param name="onErrorAction"></param>
/// <param name="onTotalChildrenCountKnown"></param>
/// <param name="cancellationToken"></param>
/// <exception cref="Exception">Throws various types of exceptions to indicate faliure</exception>
/// <returns></returns>
public static async Task<Base?> ReceiveAsync(CancellationToken token,
public static async Task<Base> ReceiveAsync(
Client client,
string streamId,
string objectId,
string? commitId,
Commit? commit,
Action<ConcurrentDictionary<string, int>>? onProgressAction = null,
Action<string, Exception>? onErrorAction = null,
Action<int>? onTotalChildrenCountKnown = null)
Action<int>? onTotalChildrenCountKnown = null,
CancellationToken cancellationToken = default
)
{
ServerTransport transport = new ServerTransport(client.Account, streamId);
transport.CancellationToken = token;
Base? ret = null;
try
{
Analytics.TrackEvent(client.Account, Analytics.Events.Receive);
using var transport = new ServerTransportV2(client.Account, streamId);
token.ThrowIfCancellationRequested();
transport.CancellationToken = cancellationToken;
ret = await Operations.Receive(
cancellationToken.ThrowIfCancellationRequested();
Base? requestedObject = await Operations
.Receive(
objectId: objectId,
cancellationToken: token,
cancellationToken: cancellationToken,
remoteTransport: transport,
onProgressAction: onProgressAction,
onErrorAction: onErrorAction,
onTotalChildrenCountKnown: onTotalChildrenCountKnown,
disposeTransports: true
);
token.ThrowIfCancellationRequested();
//Read receipt
try
{
await client.CommitReceived(token, new CommitReceivedInput
onErrorAction: (s, ex) =>
{
streamId = streamId,
commitId = commitId,
message = $"received commit from {Application.unityVersion}",
sourceApplication = HostApplications.Unity.GetVersion(CoreUtils.GetHostAppVersion())
});
}
catch (Exception e)
//Don't wrap cancellation exceptions!
if (ex is OperationCanceledException)
throw ex;
//HACK: Sometimes, the task was cancelled, and Operations.Receive doesn't fail in a reliable way. In this case, the exception is often simply a symptom of a cancel.
if (cancellationToken.IsCancellationRequested)
{
SpeckleLog.Logger.Warning(
ex,
"A task was cancelled, ignoring potentially symptomatic exception"
);
cancellationToken.ThrowIfCancellationRequested();
}
//Treat all operation errors as fatal
throw new SpeckleException(
$"Failed to receive requested object {objectId} from server: {s}",
ex
);
},
onTotalChildrenCountKnown: onTotalChildrenCountKnown,
disposeTransports: false
)
.ConfigureAwait(false);
Analytics.TrackEvent(
client.Account,
Analytics.Events.Receive,
new Dictionary<string, object>()
{
// Do nothing!
Debug.LogWarning($"Failed to send read receipt\n{e}");
{ "mode", nameof(SpeckleReceiver) },
{
"sourceHostApp",
HostApplications.GetHostAppFromString(commit?.sourceApplication).Slug
},
{ "sourceHostAppVersion", commit?.sourceApplication ?? "" },
{ "hostPlatform", Application.platform.ToString() },
{
"isMultiplayer",
commit != null && commit.authorId != client.Account.userInfo.id
},
}
);
if (requestedObject == null)
throw new SpeckleException($"Operation {nameof(Operations.Receive)} returned null");
cancellationToken.ThrowIfCancellationRequested();
//Read receipt
try
{
await client
.CommitReceived(
cancellationToken,
new CommitReceivedInput
{
streamId = streamId,
commitId = commit?.id,
message = $"received commit from {Application.unityVersion}",
sourceApplication = HostApplications.Unity.GetVersion(
CoreUtils.GetHostAppVersion()
)
}
)
.ConfigureAwait(false);
}
catch (Exception e)
{
onErrorAction?.Invoke(e.Message, e);
// Do nothing!
Debug.LogWarning($"Failed to send read receipt\n{e}");
}
finally
{
transport?.Dispose();
}
return ret;
return requestedObject;
}
/// <summary>
/// Helper method for using <see cref="RecursiveConverter"/>.
/// Creates blank GameObjects for each property/category of the root object.
@@ -173,8 +346,18 @@ namespace Speckle.ConnectorUnity.Components
/// <param name="rootObjectName">The name of the parent <see cref="GameObject"/> to create</param>
/// <param name="beforeConvertCallback">Callback for each object converted</param>
/// <returns>The created parent <see cref="GameObject"/></returns>
public GameObject ConvertToNativeWithCategories(Base @base, string rootObjectName,
Action<Base>? beforeConvertCallback)
[Obsolete(
"Use "
+ nameof(RecursiveConverter)
+ " Now we have implemented support for "
+ nameof(Collection)
+ "s, receiving any collection is now the default behaviour"
)]
public GameObject ConvertToNativeWithCategories(
Base @base,
string rootObjectName,
Action<Base>? beforeConvertCallback
)
{
var rootObject = new GameObject(rootObjectName);
@@ -182,10 +365,9 @@ namespace Speckle.ConnectorUnity.Components
{
beforeConvertCallback?.Invoke(o);
return Converter.ConverterInstance.CanConvertToNative(o) //Accept geometry
|| o.speckle_type == nameof(Base) && o.totalChildrenCount > 0; // Or Base objects that have children
|| o.speckle_type == nameof(Base) && o.totalChildrenCount > 0; // Or Base objects that have children
}
// For the rootObject only, we will create property GameObjects
// i.e. revit categories
foreach (var prop in @base.GetMembers())
@@ -193,52 +375,56 @@ namespace Speckle.ConnectorUnity.Components
var converted = Converter.RecursivelyConvertToNative(prop.Value, null, Predicate);
//Skip empties
if (converted.Count <= 0) continue;
if (converted.Count <= 0)
continue;
var propertyObject = new GameObject(prop.Key);
propertyObject.transform.SetParent(rootObject.transform);
foreach (var o in converted)
{
if (o.transform.parent == null) o.transform.SetParent(propertyObject.transform);
if (o.transform.parent == null)
o.transform.SetParent(propertyObject.transform);
}
}
return rootObject;
}
/// <summary>
///
///
/// </summary>
/// <param name="client"></param>
/// <param name="stream"></param>
/// <param name="commit"></param>
/// <param name="error">error messages for </param>
/// <returns>true if selection is complete, as we are ready to receive</returns>
[Obsolete("Use " + nameof(ValidateSelection))]
public bool GetSelection(
[NotNullWhen(true)] out Client? client,
[NotNullWhen(true)] out Stream? stream,
[NotNullWhen(true)] out Commit? commit,
[NotNullWhen(false)] out string? error)
[NotNullWhen(false)] out string? error
)
{
Account? account = Account.Selected;
stream = Stream.Selected;
commit = Commit.Selected;
if (account == null)
{
error = "Selected Account is null";
client = null;
return false;
}
client = Account.Client ?? new Client(account);
client = Account.Client ?? new Client(account);
if (stream == null)
{
error = "Selected Stream is null";
return false;
}
if (commit == null)
if (commit == null)
{
error = "Selected Commit is null";
return false;
@@ -246,30 +432,35 @@ namespace Speckle.ConnectorUnity.Components
error = null;
return true;
}
/// <summary>
/// Fetches the commit preview for the currently selected commit
/// </summary>
/// <param name="allAngles">when <see langword="true"/>, will fetch 360 degree preview image</param>
/// <param name="callback">Callback function to be called when the web request completes</param>
/// <returns><see langword="false"/> if <see cref="Account"/>, <see cref="Stream"/>, or <see cref="Commit"/> was <see langword="null"/></returns>
public bool GetPreviewImage(/*bool allAngles,*/ Action<Texture2D?> callback)
/// <returns>The executing <see cref="Coroutine"/> or <see langword="null"/> if <see cref="Account"/>, <see cref="Stream"/>, or <see cref="Commit"/> was <see langword="null"/></returns>
public Coroutine? GetPreviewImage( /*bool allAngles,*/
Action<Texture2D?> callback
)
{
Account? account = Account.Selected;
if (account == null) return false;
if (account == null)
return null;
string? streamId = Stream.Selected?.id;
if (streamId == null) return false;
if (streamId == null)
return null;
string? commitId = Commit.Selected?.id;
if (commitId == null) return false;
if (commitId == null)
return null;
string angles = /*allAngles ? "all" :*/ "";
string angles = /*allAngles ? "all" :*/
"";
string url = $"{account.serverInfo.url}/preview/{streamId}/commits/{commitId}/{angles}";
string authToken = account.token;
StartCoroutine(Utils.Utils.GetImageRoutine(url, authToken, callback));
return true;
return StartCoroutine(Utils.Utils.GetImageRoutine(url, authToken, callback));
}
#if UNITY_EDITOR
[ContextMenu("Open Speckle Stream in Browser")]
protected void OpenUrlInBrowser()
@@ -278,6 +469,7 @@ namespace Speckle.ConnectorUnity.Components
Application.OpenURL(url);
}
#endif
public string GetSelectedUrl()
{
string serverUrl = Account.Selected!.serverInfo.url;
@@ -285,21 +477,24 @@ namespace Speckle.ConnectorUnity.Components
string? branchName = Branch.Selected?.name;
string? commitId = Commit.Selected?.id;
if (string.IsNullOrEmpty(streamId)) return serverUrl;
if (!string.IsNullOrEmpty(commitId)) return $"{serverUrl}/streams/{streamId}/commits/{commitId}";
if (!string.IsNullOrEmpty(branchName)) return $"{serverUrl}/streams/{streamId}/branches/{branchName}";
if (string.IsNullOrEmpty(streamId))
return serverUrl;
if (!string.IsNullOrEmpty(commitId))
return $"{serverUrl}/streams/{streamId}/commits/{commitId}";
if (!string.IsNullOrEmpty(branchName))
return $"{serverUrl}/streams/{streamId}/branches/{branchName}";
return $"{serverUrl}/streams/{streamId}";
}
public void Awake()
{
CoreUtils.SetupInit();
Converter = GetComponent<RecursiveConverter>();
Initialise(true);
}
protected void Initialise(bool forceRefresh = false)
{
CoreUtils.SetupInit();
Account ??= new AccountSelection();
Stream ??= new StreamSelection(Account);
Branch ??= new BranchSelection(Stream);
@@ -307,32 +502,73 @@ namespace Speckle.ConnectorUnity.Components
Stream.Initialise();
Branch.Initialise();
Commit.Initialise();
Commit.OnSelectionChange =
() => OnCommitSelectionChange?.Invoke(Commit.Selected);
if(Account.Options is not {Length: > 0} || forceRefresh)
Commit.OnSelectionChange = () => OnCommitSelectionChange?.Invoke(Commit.Selected);
if (Account.Options is not { Length: > 0 } || forceRefresh)
Account.RefreshOptions();
}
public void OnDestroy()
public void OnDisable()
{
CancellationTokenSource?.Cancel();
}
public void OnDestroy()
{
CancellationTokenSource?.Dispose();
}
public void OnBeforeSerialize()
{
//pass
}
public void OnAfterDeserialize()
{
Initialise();
}
#region Deprecated members
[Obsolete("use " + nameof(ReceiveAndConvertRoutine), true)]
public IEnumerator ReceiveAndConvertRoutine(
SpeckleReceiver speckleReceiver,
string rootObjectName,
Action<Base>? beforeConvertCallback = null
)
{
// ReSharper disable once MethodSupportsCancellation
Task<Base> receiveOperation = Task.Run(
async () => await ReceiveAsync(CancellationToken)
);
yield return new WaitUntil(() => receiveOperation.IsCompleted);
Base? b = receiveOperation.Result;
if (b == null)
yield break;
//NOTE: coroutine doesn't break for each catergory/object
ConvertToNativeWithCategories(b, rootObjectName, beforeConvertCallback);
}
#endregion
}
[Serializable] public sealed class CommitSelectionEvent : UnityEvent<Commit?> { }
[Serializable] public sealed class BranchSelectionEvent : UnityEvent<Branch?> { }
[Serializable] public sealed class ErrorActionEvent : UnityEvent<string, Exception> { }
[Serializable] public sealed class OperationProgressEvent : UnityEvent<ConcurrentDictionary<string, int>> { }
[Serializable] public sealed class ReceiveCompleteHandler : UnityEvent<GameObject> { }
[Serializable] public sealed class ChildrenCountHandler : UnityEvent<int> { }
[Serializable]
public sealed class CommitSelectionEvent : UnityEvent<Commit?> { }
[Serializable]
public sealed class BranchSelectionEvent : UnityEvent<Branch?> { }
[Serializable]
public sealed class ErrorActionEvent : UnityEvent<string, Exception> { }
[Serializable]
public sealed class OperationProgressEvent : UnityEvent<ConcurrentDictionary<string, int>> { }
[Serializable]
public sealed class ReceiveCompleteHandler : UnityEvent<Transform?> { }
[Serializable]
public sealed class ChildrenCountHandler : UnityEvent<int> { }
}
@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Tasks;
using Speckle.ConnectorUnity.Utils;
using Speckle.ConnectorUnity.Wrappers.Selection;
using Speckle.Core.Api;
using Speckle.Core.Credentials;
@@ -22,39 +23,49 @@ namespace Speckle.ConnectorUnity.Components
{
[field: SerializeReference]
public AccountSelection Account { get; private set; }
[field: SerializeReference]
public StreamSelection Stream { get; private set; }
[field: SerializeReference]
public BranchSelection Branch { get; private set; }
public RecursiveConverter Converter { get; private set; }
[Header("Events")]
[HideInInspector]
public BranchSelectionEvent OnBranchSelectionChange;
[HideInInspector]
public ErrorActionEvent OnErrorAction;
[HideInInspector]
public OperationProgressEvent OnSendProgressAction;
#nullable enable
protected internal CancellationTokenSource? CancellationTokenSource { get; private set; }
//TODO runtime sending
public async Task<string> SendDataAsync(Base data, bool createCommit)
{
CancellationTokenSource?.Cancel();
CancellationTokenSource?.Dispose();
CancellationTokenSource = new CancellationTokenSource();
if(!GetSelection(out Client? client, out Stream? stream, out Branch? branch, out string? error))
if (
!GetSelection(
out Client? client,
out Stream? stream,
out Branch? branch,
out string? error
)
)
throw new SpeckleException(error);
ServerTransport transport = new ServerTransport(client.Account, stream.id);
transport.CancellationToken = CancellationTokenSource.Token;
return await SendDataAsync(CancellationTokenSource.Token,
return await SendDataAsync(
CancellationTokenSource.Token,
remoteTransport: transport,
data: data,
client: client,
@@ -65,89 +76,114 @@ namespace Speckle.ConnectorUnity.Components
);
}
public static async Task<string> SendDataAsync(CancellationToken cancellationToken,
public static async Task<string> SendDataAsync(
CancellationToken cancellationToken,
ServerTransport remoteTransport,
Base data,
Client client,
string branchName,
bool createCommit,
Action<ConcurrentDictionary<string, int>>? onProgressAction = null,
Action<string, Exception>? onErrorAction = null)
Action<string, Exception>? onErrorAction = null
)
{
string res = await Operations.Send(
data,
cancellationToken: cancellationToken,
new List<ITransport>{remoteTransport},
new List<ITransport> { remoteTransport },
useDefaultCache: true,
disposeTransports: true,
onProgressAction: onProgressAction,
onErrorAction: onErrorAction
);
Analytics.TrackEvent(client.Account, Analytics.Events.Send);
Analytics.TrackEvent(
client.Account,
Analytics.Events.Send,
new Dictionary<string, object>()
{
{ "mode", nameof(SpeckleSender) },
{ "hostPlatform", Application.platform.ToString() },
}
);
if (createCommit && !cancellationToken.IsCancellationRequested)
{
string streamId = remoteTransport.StreamId;
string unityVer = $"Unity {Application.unityVersion.Substring(0,6)}";
string unityVer = $"Unity {Application.unityVersion.Substring(0, 6)}";
data.totalChildrenCount = data.GetTotalChildrenCount();
string commitMessage = $"Sent {data.totalChildrenCount} objects from {unityVer}";
string commitId = await CreateCommit(cancellationToken, data, client, streamId, branchName, res, commitMessage);
string commitId = await CreateCommit(
cancellationToken,
data,
client,
streamId,
branchName,
res,
commitMessage
);
string url = $"{client.ServerUrl}/streams/{streamId}/commits/{commitId}";
Debug.Log($"Data successfully sent to <a href=\"{url}\">{url}</a>");
}
return res;
}
public static async Task<string> CreateCommit(CancellationToken cancellationToken,
public static async Task<string> CreateCommit(
CancellationToken cancellationToken,
Base data,
Client client,
string streamId,
string branchName,
string objectId,
string message)
string message
)
{
string commitId = await client.CommitCreate(cancellationToken,
string commitId = await client.CommitCreate(
cancellationToken,
new CommitCreateInput
{
streamId = streamId,
branchName = branchName,
objectId = objectId,
message = message,
sourceApplication = HostApplications.Unity.GetVersion(CoreUtils.GetHostAppVersion()),
sourceApplication = HostApplications.Unity.GetVersion(
CoreUtils.GetHostAppVersion()
),
totalChildrenCount = (int)data.totalChildrenCount,
});
}
);
return commitId;
}
public bool GetSelection(
[NotNullWhen(true)] out Client? client,
[NotNullWhen(true)] out Stream? stream,
[NotNullWhen(true)] out Branch? branch,
[NotNullWhen(false)] out string? error)
[NotNullWhen(false)] out string? error
)
{
Account? account = Account.Selected;
stream = Stream.Selected;
branch = Branch.Selected;
if (account == null)
{
error = "Selected Account is null";
client = null;
return false;
}
client = Account.Client ?? new Client(account);
client = Account.Client ?? new Client(account);
if (stream == null)
{
error = "Selected Stream is null";
return false;
}
if (branch == null)
if (branch == null)
{
error = "Selected Branch is null";
return false;
@@ -155,8 +191,7 @@ namespace Speckle.ConnectorUnity.Components
error = null;
return true;
}
#if UNITY_EDITOR
[ContextMenu("Open Speckle Stream in Browser")]
protected void OpenUrlInBrowser()
@@ -165,27 +200,29 @@ namespace Speckle.ConnectorUnity.Components
Application.OpenURL(url);
}
#endif
public string GetSelectedUrl()
{
string serverUrl = Account.Selected!.serverInfo.url;
string? streamId = Stream.Selected?.id;
string? branchName = Branch.Selected?.name;
if (string.IsNullOrEmpty(streamId)) return serverUrl;
if (!string.IsNullOrEmpty(branchName)) return $"{serverUrl}/streams/{streamId}/branches/{branchName}";
if (string.IsNullOrEmpty(streamId))
return serverUrl;
if (!string.IsNullOrEmpty(branchName))
return $"{serverUrl}/streams/{streamId}/branches/{branchName}";
return $"{serverUrl}/streams/{streamId}";
}
public void Awake()
{
CoreUtils.SetupInit();
Initialise(true);
Converter = GetComponent<RecursiveConverter>();
}
protected void Initialise(bool forceRefresh = false)
{
CoreUtils.SetupInit();
Account ??= new AccountSelection();
Stream ??= new StreamSelection(Account);
Branch ??= new BranchSelection(Stream);
@@ -193,24 +230,24 @@ namespace Speckle.ConnectorUnity.Components
Stream.Initialise();
Branch.Initialise();
Branch.OnSelectionChange = () => OnBranchSelectionChange?.Invoke(Branch.Selected);
if(Account.Options is not {Length: > 0} || forceRefresh)
if (Account.Options is not { Length: > 0 } || forceRefresh)
Account.RefreshOptions();
}
public void OnDestroy()
{
CancellationTokenSource?.Cancel();
CancellationTokenSource?.Dispose();
}
public void OnBeforeSerialize()
{
//pass
}
public void OnAfterDeserialize()
{
Initialise();
}
}
}
@@ -1,29 +1,50 @@
using Objects.BuiltElements;
using UnityEngine;
namespace Objects.Converter.Unity
{
public partial class ConverterUnity
{
/// <summary>
/// Converts a Speckle View3D to a GameObject
/// </summary>
/// <param name="point"></param>
/// <returns></returns>
public GameObject View3DToNative(View3D speckleView)
public partial class ConverterUnity
{
var go = new GameObject(speckleView.name);
var camera = go.AddComponent<Camera>();
camera.transform.position = VectorByCoordinates(speckleView.origin.x, speckleView.origin.y, speckleView.origin.z,
speckleView.origin.units);
camera.transform.forward = VectorByCoordinates(speckleView.forwardDirection.x, speckleView.forwardDirection.y,
speckleView.forwardDirection.z, speckleView.forwardDirection.units);
camera.transform.up = VectorByCoordinates(speckleView.upDirection.x, speckleView.upDirection.y,
speckleView.upDirection.z, speckleView.upDirection.units);
[Tooltip("Enable/Disable the converting of Speckle View objects to Unity Cameras")]
public bool shouldConvertViews;
AttachSpeckleProperties(go, speckleView.GetType(),speckleView.GetMembers());
return go;
public GameObject View3DToNative(View3D speckleView)
{
var go = new GameObject(speckleView.name);
go.AddComponent<Camera>();
var matrix = View3DToMatrix(speckleView).transpose;
ApplyMatrixToTransform(go.transform, matrix);
AttachSpeckleProperties(go, speckleView.GetType(), () => speckleView.GetMembers());
return go;
}
protected Matrix4x4 View3DToMatrix(View3D view)
{
var sf = GetConversionFactor(view.units);
var tx = (float)(view.origin.x * sf);
var ty = (float)(view.origin.z * sf); //Y up -> Z up coordinate transformation
var tz = (float)(view.origin.y * sf);
var forward = new Vector3(
(float)view.forwardDirection.x,
(float)view.forwardDirection.z,
(float)view.forwardDirection.y
);
var up = new Vector3(
(float)view.upDirection.x,
(float)view.upDirection.z,
(float)view.upDirection.y
);
var right = Vector3.Cross(forward, up).normalized;
return new Matrix4x4(
new Vector4(right.x, up.x, forward.x, tx),
new Vector4(right.y, up.y, forward.y, ty),
new Vector4(right.z, up.z, forward.z, tz),
new Vector4(0, 0, 0, 1)
);
}
}
}
}
}
@@ -4,11 +4,11 @@ using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Objects.Other;
using Speckle.ConnectorUnity.NativeCache;
using Speckle.ConnectorUnity.Utils;
using Speckle.ConnectorUnity.Wrappers;
using Speckle.Core.Logging;
using Speckle.Core.Models;
using Speckle.Core.Models.GraphTraversal;
using UnityEditor;
using UnityEngine;
using Mesh = UnityEngine.Mesh;
@@ -22,7 +22,6 @@ namespace Objects.Converter.Unity
{
public partial class ConverterUnity
{
#region helper methods
@@ -33,29 +32,33 @@ namespace Objects.Converter.Unity
public Vector3 VectorByCoordinates(double x, double y, double z, double scaleFactor = 1d)
{
// switch y and z //TODO is this correct? LH -> RH
return new Vector3((float) (x * scaleFactor), (float) (z * scaleFactor), (float) (y * scaleFactor));
return new Vector3(
(float)(x * scaleFactor),
(float)(z * scaleFactor),
(float)(y * scaleFactor)
);
}
public Vector3 VectorByCoordinates(double x, double y, double z, string units)
{
var f = Speckle.Core.Kits.Units.GetConversionFactor(units, ModelUnits);
var f = GetConversionFactor(units);
return VectorByCoordinates(x, y, z, f);
}
public Vector3 VectorFromPoint(Point p) => VectorByCoordinates(p.x, p.y, p.z, p.units);
/// <summary>
///
///
/// </summary>
/// <param name="arr"></param>
/// <returns></returns>
public Vector3[] ArrayToPoints(IList<double> arr, string units)
{
if (arr.Count % 3 != 0) throw new Exception("Array malformed: length%3 != 0.");
if (arr.Count % 3 != 0)
throw new Exception("Array malformed: length not a multiple of 3");
Vector3[] points = new Vector3[arr.Count / 3];
var f = Speckle.Core.Kits.Units.GetConversionFactor(units, ModelUnits);
var f = GetConversionFactor(units);
for (int i = 2, k = 0; i < arr.Count; i += 3)
points[k++] = VectorByCoordinates(arr[i - 2], arr[i - 1], arr[i], f);
@@ -70,7 +73,7 @@ namespace Objects.Converter.Unity
//TODO: more of these
/// <summary>
///
///
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
@@ -79,7 +82,6 @@ namespace Objects.Converter.Unity
//switch y and z
return new Point(p.x, p.z, p.y);
}
#endregion
@@ -88,7 +90,8 @@ namespace Objects.Converter.Unity
protected GameObject? NewPointBasedGameObject(Vector3[] points, string name)
{
if (points.Length == 0) return null;
if (points.Length == 0)
return null;
float pointDiameter = 1; //TODO: figure out how best to change this?
@@ -114,11 +117,10 @@ namespace Objects.Converter.Unity
{
Vector3 newPt = VectorByCoordinates(point.x, point.y, point.z, point.units);
var go = NewPointBasedGameObject(new Vector3[] {newPt, newPt}, point.speckle_type);
var go = NewPointBasedGameObject(new Vector3[] { newPt, newPt }, point.speckle_type);
return go;
}
/// <summary>
/// Converts a Speckle <paramref name="line"/> to a <see cref="GameObject"/> with a <see cref="LineRenderer"/>
/// </summary>
@@ -126,7 +128,11 @@ namespace Objects.Converter.Unity
/// <returns></returns>
public GameObject? LineToNative(Line line)
{
var points = new List<Vector3> {VectorFromPoint(line.start), VectorFromPoint(line.end)};
var points = new List<Vector3>
{
VectorFromPoint(line.start),
VectorFromPoint(line.end)
};
var go = NewPointBasedGameObject(points.ToArray(), line.speckle_type);
return go;
@@ -156,15 +162,16 @@ namespace Objects.Converter.Unity
var go = NewPointBasedGameObject(points, curve.speckle_type);
return go;
}
public Dictionary<string, object?> GetProperties(Base o) => GetProperties(o, typeof(Base));
public Dictionary<string, object?> GetProperties(Base o, Type excludeType)
{
var excludeProps = new HashSet<string>(excludeType
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Select(x => x.Name));
var excludeProps = new HashSet<string>(
excludeType
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Select(x => x.Name)
);
foreach (string alias in DisplayValuePropertyAliases)
{
@@ -178,15 +185,19 @@ namespace Objects.Converter.Unity
excludeProps.Add("physicsLayer");
return o.GetMembers()
.Where(x => !(excludeProps.Contains(x.Key) || excludeProps.Contains(x.Key.TrimStart('@'))))
.ToDictionary(x => x.Key, x => (object?) x.Value);
.Where(
x =>
!(
excludeProps.Contains(x.Key)
|| excludeProps.Contains(x.Key.TrimStart('@'))
)
)
.ToDictionary(x => x.Key, x => (object?)x.Value);
}
#endregion
private Base CreateSpeckleObjectFromProperties(GameObject go)
{
@@ -194,7 +205,7 @@ namespace Objects.Converter.Unity
if (sd == null || sd.Data == null)
return new Base();
Base sobject = (Base) Activator.CreateInstance(sd.SpeckleType);
Base sobject = (Base)Activator.CreateInstance(sd.SpeckleType);
foreach (var key in sd.Data.Keys)
{
@@ -211,43 +222,55 @@ namespace Objects.Converter.Unity
return sobject;
}
public GameObject? BlockToNative(BlockInstance block)
public GameObject InstanceToNative(Instance instance)
{
if (block.blockDefinition == null)
{
Debug.Log($"Skipping {typeof(BlockInstance)} {block.id}, block definition was null");
return null;
}
if (instance.definition == null)
throw new ArgumentException("Definition was null", nameof(instance));
var defName = CoreUtils.GenerateObjectName(instance.definition);
// Check for existing converted object
if(LoadedAssets.TryGetObject(block.blockDefinition, out GameObject? existingGo))
if (LoadedAssets.TryGetObject(instance.definition, out GameObject? existingGo))
{
var go = InstantiateCopy(existingGo);
go.name = block.blockDefinition.name ?? "";
TransformToNativeTransform(go.transform, block.transform);
go.name = defName;
TransformToNativeTransform(go.transform, instance.transform);
return go;
}
// Convert the block definition
GameObject native = new GameObject(block.blockDefinition.name ?? "");
GameObject native = new(defName);
List<SMesh> meshes = new();
List<Base> others = new();
foreach (Base geo in block.blockDefinition.geometry)
var geometry = instance.definition is BlockDefinition b
? b.geometry
: GraphTraversal.TraverseMember(
new[]
{
instance.definition["elements"] ?? instance.definition["@elements"],
instance.definition["displayValue"] ?? instance.definition["@displayValue"],
}
);
foreach (Base geo in geometry)
{
if (geo is SMesh m) meshes.Add(m);
else if (geo is IDisplayValue<List<SMesh>> s) meshes.AddRange(s.displayValue);
else others.Add(geo);
if (geo is SMesh m)
meshes.Add(m);
else if (geo is IDisplayValue<List<SMesh>> s)
meshes.AddRange(s.displayValue);
else
others.Add(geo);
}
if (meshes.Any())
{
if(!TryGetMeshFromCache(block.blockDefinition, meshes, out Mesh? nativeMesh, out _))
if (!TryGetMeshFromCache(instance.definition, meshes, out Mesh? nativeMesh, out _))
{
MeshToNativeMesh(meshes, out nativeMesh);
string name = AssetHelpers.GetObjectName(block.blockDefinition);
string name = CoreUtils.GenerateObjectName(instance.definition);
nativeMesh.name = name;
LoadedAssets.TrySaveObject(block.blockDefinition, nativeMesh);
LoadedAssets.TrySaveObject(instance.definition, nativeMesh);
}
var nativeMaterials = RenderMaterialsToNative(meshes);
native.SafeMeshSet(nativeMesh, nativeMaterials);
@@ -256,76 +279,79 @@ namespace Objects.Converter.Unity
foreach (Base child in others)
{
GameObject? c = ConvertToNativeGameObject(child);
if (c == null) continue;
if (c == null)
continue;
c.transform.SetParent(native.transform, false);
}
LoadedAssets.TrySaveObject(block.blockDefinition, native);
TransformToNativeTransform(native.transform, block.transform);
if (block["name"] is string instanceName) native.name = instanceName;
LoadedAssets.TrySaveObject(instance.definition, native);
TransformToNativeTransform(native.transform, instance.transform);
var instanceName =
CoreUtils.GetFriendlyObjectName(instance) != null
? CoreUtils.GenerateObjectName(instance)
: defName;
native.name = instanceName;
return native;
}
private static GameObject InstantiateCopy(GameObject existingGo)
{
#if UNITY_EDITOR
GameObject? prefabInstance = null;
bool isPrefab = PrefabUtility.GetPrefabAssetType(existingGo) != PrefabAssetType.NotAPrefab;
bool isPrefab =
PrefabUtility.GetPrefabAssetType(existingGo) != PrefabAssetType.NotAPrefab;
if (isPrefab)
{
GameObject? prefabAsset = PrefabUtility.GetCorrespondingObjectFromSource(existingGo);
if (prefabAsset == null) prefabAsset = existingGo;
prefabInstance = (GameObject) PrefabUtility.InstantiatePrefab(prefabAsset);
GameObject? prefabAsset = PrefabUtility.GetCorrespondingObjectFromSource(
existingGo
);
if (prefabAsset == null)
prefabAsset = existingGo;
prefabInstance = (GameObject)PrefabUtility.InstantiatePrefab(prefabAsset);
}
if (prefabInstance != null)
return prefabInstance;
#endif
return Object.Instantiate(existingGo);
}
/// <summary>
/// Converts a 4x4 transformation matrix from Speckle's <see cref="Other.Transform"/> format,
/// to a Unity <see cref="Matrix4x4"/>. Applying Z -> Y up conversion, and applying units to the translation
/// to a Unity <see cref="Matrix4x4"/>. Applying Z -> Y up conversion, and applying units to the translation
/// </summary>
/// <param name="speckleTransform"></param>
/// <returns>Transformation matrix in Unity's coordinate system</returns>
public Matrix4x4 TransformToNativeMatrix(STransform speckleTransform)
{
double VD(int i) => speckleTransform.value[i];
float V(int i) => (float) VD(i);
var sf = Speckle.Core.Kits.Units.GetConversionFactor(speckleTransform.units, ModelUnits);
var sf = GetConversionFactor(speckleTransform.units);
var smatrix = speckleTransform.matrix;
return new Matrix4x4
{
// Left (X -> X)
[0, 0] = V(0),
[2, 0] = V(4),
[1, 0] = V(8),
[3, 0] = V(12),
[0, 0] = smatrix.M11,
[2, 0] = smatrix.M21,
[1, 0] = smatrix.M31,
[3, 0] = smatrix.M41,
//Up (Z -> Y)
[0, 2] = V(1),
[2, 2] = V(5),
[1, 2] = V(9),
[3, 2] = V(13),
[0, 2] = smatrix.M12,
[2, 2] = smatrix.M22,
[1, 2] = smatrix.M32,
[3, 2] = smatrix.M42,
//Forwards (Y -> Z)
[0, 1] = V(2),
[2, 1] = V(6),
[1, 1] = V(10),
[3, 1] = V(14),
[0, 1] = smatrix.M13,
[2, 1] = smatrix.M23,
[1, 1] = smatrix.M33,
[3, 1] = smatrix.M43,
//Translation
[0, 3] = (float) (VD(3) * sf),
[2, 3] = (float) (VD(7) * sf),
[1, 3] = (float) (VD(11) * sf),
[3, 3] = V(15),
[0, 3] = (float)(smatrix.M14 * sf),
[2, 3] = (float)(smatrix.M24 * sf),
[1, 3] = (float)(smatrix.M34 * sf),
[3, 3] = smatrix.M44,
};
}
@@ -337,16 +363,11 @@ namespace Objects.Converter.Unity
protected static void ApplyMatrixToTransform(Transform transform, Matrix4x4 m)
{
transform.localScale =
m.lossyScale; //doesn't work for non TRS, maybe we could fallback to squareSum approach (see TransformVectorized::SetFromMatrix in UE src)
transform.localScale = m.lossyScale; //doesn't work for non TRS, maybe we could fallback to squareSum approach (see TransformVectorized::SetFromMatrix in UE src)
//We can't use m.rotation, as it gives us incorrect results (perhaps because of RH -> LH? or maybe our MatrixToNative is broken?)
transform.localRotation = Quaternion.LookRotation(
m.GetColumn(2),
m.GetColumn(1)
);
transform.localRotation = Quaternion.LookRotation(m.GetColumn(2), m.GetColumn(1));
transform.localPosition = m.GetPosition();
}
}
}
}
@@ -3,9 +3,7 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Speckle.ConnectorUnity.Utils;
using Objects.Other;
using Objects.Utils;
using Speckle.ConnectorUnity.NativeCache;
using Speckle.Core.Models;
using UnityEngine;
using UnityEngine.Rendering;
@@ -21,13 +19,8 @@ namespace Objects.Converter.Unity
{
public partial class ConverterUnity
{
protected static readonly int EmissionColor = Shader.PropertyToID("_EmissionColor");
protected static readonly int Metallic = Shader.PropertyToID("_Metallic");
protected static readonly int Glossiness = Shader.PropertyToID("_Glossiness");
#region ToSpeckle
public virtual List<SMesh>? MeshToSpeckle(MeshFilter meshFilter)
{
#if UNITY_EDITOR
@@ -37,7 +30,8 @@ namespace Objects.Converter.Unity
Material[]? materials = meshFilter.GetComponent<Renderer>()?.materials;
var nativeMesh = meshFilter.mesh;
#endif
if (nativeMesh == null) return null;
if (nativeMesh == null)
return null;
List<SMesh> convertedMeshes = new List<SMesh>(nativeMesh.subMeshCount);
for (int i = 0; i < nativeMesh.subMeshCount; i++)
@@ -50,31 +44,50 @@ namespace Objects.Converter.Unity
// //TODO convert as pointcloud
// continue;
case MeshTopology.Triangles:
converted = SubMeshToSpeckle(nativeMesh, meshFilter.transform, subMesh, i, 3);
converted = SubMeshToSpeckle(
nativeMesh,
meshFilter.transform,
subMesh,
i,
3
);
convertedMeshes.Add(converted);
break;
case MeshTopology.Quads:
converted = SubMeshToSpeckle(nativeMesh, meshFilter.transform, subMesh, i, 4);
converted = SubMeshToSpeckle(
nativeMesh,
meshFilter.transform,
subMesh,
i,
4
);
convertedMeshes.Add(converted);
break;
default:
Debug.LogError(
$"Failed to convert submesh {i} of {typeof(GameObject)} {meshFilter.gameObject.name} to Speckle, Unsupported Mesh Topography {subMesh.topology}. Submesh will be ignored.");
$"Failed to convert submesh {i} of {typeof(GameObject)} {meshFilter.gameObject.name} to Speckle, Unsupported Mesh Topography {subMesh.topology}. Submesh will be ignored."
);
continue;
}
if (materials == null || materials.Length <= i) continue;
if (materials == null || materials.Length <= i)
continue;
Material mat = materials[i];
if (mat != null) converted["renderMaterial"] = MaterialToSpeckle(mat);
if (mat != null)
converted["renderMaterial"] = MaterialToSpeckle(mat);
}
return convertedMeshes;
}
protected virtual SMesh SubMeshToSpeckle(Mesh nativeMesh, Transform instanceTransform,
SubMeshDescriptor subMesh, int subMeshIndex, int faceN)
protected virtual SMesh SubMeshToSpeckle(
Mesh nativeMesh,
Transform instanceTransform,
SubMeshDescriptor subMesh,
int subMeshIndex,
int faceN
)
{
var nFaces = nativeMesh.GetIndices(subMeshIndex, true);
int numFaces = nFaces.Length / faceN;
@@ -124,7 +137,7 @@ namespace Objects.Converter.Unity
}
var nColors = nativeMesh.colors.Skip(indexOffset).Take(vertexTake).ToArray();
;
List<int> sColors = new List<int>(nColors.Length);
sColors.AddRange(nColors.Select(c => c.ToIntColor()));
@@ -148,56 +161,6 @@ namespace Objects.Converter.Unity
return convertedMesh;
}
/// <summary>
/// List of officially supported shaders. Will attempt to convert shaders not on this list, but will throw warning.
/// </summary>
protected static HashSet<string> SupportedShadersToSpeckle = new()
{
"Legacy Shaders/Transparent/Diffuse", "Standard"
};
public virtual RenderMaterial MaterialToSpeckle(Material nativeMaterial)
{
//Warning message for unknown shaders
if (!SupportedShadersToSpeckle.Contains(nativeMaterial.shader.name))
Debug.LogWarning(
$"Material Shader \"{nativeMaterial.shader.name}\" is not explicitly supported, the resulting material may be incorrect");
var color = nativeMaterial.color;
var opacity = 1f;
if (nativeMaterial.shader.name.ToLower().Contains("transparent"))
{
opacity = color.a;
color.a = 255;
}
var emissive = nativeMaterial.IsKeywordEnabled("_EMISSION")
? nativeMaterial.GetColor(EmissionColor).ToIntColor()
: SColor.Black.ToArgb();
var materialName = !string.IsNullOrWhiteSpace(nativeMaterial.name)
? nativeMaterial.name.Replace("(Instance)", string.Empty).TrimEnd()
: $"material-{Guid.NewGuid().ToString().Substring(0, 8)}";
var metalness = nativeMaterial.HasProperty(Metallic)
? nativeMaterial.GetFloat(Metallic)
: 0;
var roughness = nativeMaterial.HasProperty(Glossiness)
? 1 - nativeMaterial.GetFloat(Glossiness)
: 1;
return new RenderMaterial
{
name = materialName,
diffuse = color.ToIntColor(),
opacity = opacity,
metalness = metalness,
roughness = roughness,
emissive = emissive,
};
}
#endregion
@@ -209,13 +172,10 @@ namespace Objects.Converter.Unity
/// <param name="element">The <see cref="Base"/> object being converted</param>
/// <param name="meshes">Collection of <see cref="Objects.Geometry.Mesh"/>es that shall be converted</param>
/// <returns>A <see cref="GameObject"/> with the converted <see cref="UnityEngine.Mesh"/>, <see cref="MeshFilter"/>, and <see cref="MeshRenderer"/></returns>
public GameObject? MeshesToNative(Base element, IReadOnlyCollection<SMesh> meshes)
public GameObject MeshesToNative(Base element, IReadOnlyCollection<SMesh> meshes)
{
if (!meshes.Any())
{
Debug.Log($"Skipping {element.GetType()} {element.id}, zero {typeof(SMesh)} provided");
return null;
}
throw new ArgumentException("Expected at least one Mesh", nameof(meshes));
Material[] nativeMaterials = RenderMaterialsToNative(meshes);
@@ -223,11 +183,11 @@ namespace Objects.Converter.Unity
{
//Convert a new one
MeshToNativeMesh(meshes, out nativeMesh, out center);
string name = AssetHelpers.GetObjectName(element);
string name = CoreUtils.GenerateObjectName(element);
nativeMesh.name = name;
LoadedAssets.TrySaveObject(element, nativeMesh);
}
var go = new GameObject();
go.transform.position = center;
go.SafeMeshSet(nativeMesh, nativeMaterials);
@@ -235,21 +195,17 @@ namespace Objects.Converter.Unity
return go;
}
/// <summary>
/// Converts <paramref name="speckleMesh"/> to a <see cref="GameObject"/> with a <see cref="MeshRenderer"/>
/// </summary>
/// <param name="speckleMesh">Mesh to convert</param>
/// <returns></returns>
public GameObject? MeshToNative(SMesh speckleMesh)
public GameObject MeshToNative(SMesh speckleMesh)
{
if (speckleMesh.vertices.Count == 0 || speckleMesh.faces.Count == 0)
{
Debug.Log($"Skipping mesh {speckleMesh.id}, mesh data was empty");
return null;
}
throw new ArgumentException("mesh data was empty", nameof(speckleMesh));
GameObject? converted = MeshesToNative(speckleMesh, new[] {speckleMesh});
GameObject converted = MeshesToNative(speckleMesh, new[] { speckleMesh });
// Raw meshes shouldn't have dynamic props to attach
//if (converted != null) AttachSpeckleProperties(converted,speckleMesh.GetType(), GetProperties(speckleMesh, typeof(Mesh)));
@@ -257,17 +213,18 @@ namespace Objects.Converter.Unity
return converted;
}
protected bool TryGetMeshFromCache(Base element, IReadOnlyCollection<SMesh> meshes, [NotNullWhen(true)] out Mesh? nativeMesh, out Vector3 center)
protected bool TryGetMeshFromCache(
Base element,
IReadOnlyCollection<SMesh> meshes,
[NotNullWhen(true)] out Mesh? nativeMesh,
out Vector3 center
)
{
if (LoadedAssets.TryGetObject(element, out Mesh? existing))
{
nativeMesh = existing;
//todo This is pretty inefficient, having to the mesh data anyway just to get the center... eek
MeshDataToNative(meshes,
out List<Vector3> verts,
out _,
out _,
out _);
//todo This is pretty inefficient, having to convert the mesh data anyway just to get the center... eek
MeshDataToNative(meshes, out List<Vector3> verts, out _, out _, out _);
center = CalculateBounds(verts).center;
return true;
}
@@ -276,30 +233,33 @@ namespace Objects.Converter.Unity
center = default;
return false;
}
/// <summary>
/// Converts Speckle <see cref="SMesh"/>s as a native <see cref="Mesh"/> object
/// Converts Speckle <see cref="SMesh"/>s as a native <see cref="Mesh"/> object
/// </summary>
/// <param name="meshes">meshes to be converted as SubMeshes</param>
/// <param name="nativeMesh">The converted native mesh</param>
public void MeshToNativeMesh(IReadOnlyCollection<SMesh> meshes, out Mesh nativeMesh)
=> MeshToNativeMesh(meshes, out nativeMesh, out _, false);
public void MeshToNativeMesh(IReadOnlyCollection<SMesh> meshes, out Mesh nativeMesh) =>
MeshToNativeMesh(meshes, out nativeMesh, out _, false);
/// <inheritdoc cref="MeshToNativeMesh(IReadOnlyCollection{Objects.Geometry.Mesh},out Mesh)"/>
/// <param name="recenterVerts">when true, will recenter vertices</param>
/// <param name="center">Center position for the mesh</param>
public void MeshToNativeMesh(IReadOnlyCollection<SMesh> meshes,
public void MeshToNativeMesh(
IReadOnlyCollection<SMesh> meshes,
out Mesh nativeMesh,
out Vector3 center,
bool recenterVerts = true)
bool recenterVerts = true
)
{
MeshDataToNative(meshes,
MeshDataToNative(
meshes,
out List<Vector3> verts,
out List<Vector2> uvs,
out List<Color> vertexColors,
out List<List<int>> subMeshes);
out List<List<int>> subMeshes
);
Debug.Assert(verts.Count >= 0);
center = recenterVerts ? RecenterVertices(verts) : Vector3.zero;
@@ -310,7 +270,6 @@ namespace Objects.Converter.Unity
nativeMesh.SetUVs(0, uvs);
nativeMesh.SetColors(vertexColors);
int j = 0;
foreach (var subMeshTriangles in subMeshes)
{
@@ -326,12 +285,14 @@ namespace Objects.Converter.Unity
nativeMesh.RecalculateNormals();
nativeMesh.RecalculateTangents();
}
public void MeshDataToNative(IReadOnlyCollection<SMesh> meshes,
public void MeshDataToNative(
IReadOnlyCollection<SMesh> meshes,
out List<Vector3> verts,
out List<Vector2> uvs,
out List<Color> vertexColors,
out List<List<int>> subMeshes)
out List<List<int>> subMeshes
)
{
verts = new List<Vector3>();
uvs = new List<Vector2>();
@@ -340,17 +301,22 @@ namespace Objects.Converter.Unity
foreach (SMesh m in meshes)
{
if (m.vertices.Count == 0 || m.faces.Count == 0) continue;
if (m.vertices.Count == 0 || m.faces.Count == 0)
continue;
List<int> tris = new List<int>();
SubmeshToNative(m, verts, tris, uvs, vertexColors);
subMeshes.Add(tris);
}
}
protected void SubmeshToNative(SMesh speckleMesh, List<Vector3> verts, List<int> tris, List<Vector2> texCoords,
List<Color> vertexColors)
protected void SubmeshToNative(
SMesh speckleMesh,
List<Vector3> verts,
List<int> tris,
List<Vector2> texCoords,
List<Color> vertexColors
)
{
speckleMesh.AlignVerticesWithTexCoordsByIndex();
speckleMesh.TriangulateMesh();
@@ -364,7 +330,8 @@ namespace Objects.Converter.Unity
bool hasValidUVs = speckleMesh.TextureCoordinatesCount == speckleMesh.VerticesCount;
if (speckleMesh.textureCoordinates.Count > 0 && !hasValidUVs)
Debug.LogWarning(
$"Expected number of UV coordinates to equal vertices. Got {speckleMesh.TextureCoordinatesCount} expected {speckleMesh.VerticesCount}. \nID = {speckleMesh.id}");
$"Expected number of UV coordinates to equal vertices. Got {speckleMesh.TextureCoordinatesCount} expected {speckleMesh.VerticesCount}. \nID = {speckleMesh.id}"
);
if (hasValidUVs)
{
@@ -372,14 +339,20 @@ namespace Objects.Converter.Unity
for (int j = 0; j < speckleMesh.TextureCoordinatesCount; j++)
{
var (u, v) = speckleMesh.GetTextureCoordinate(j);
texCoords.Add(new Vector2((float) u, (float) v));
texCoords.Add(new Vector2((float)u, (float)v));
}
}
else if (speckleMesh.bbox != null)
{
// Attempt to generate some crude UV coordinates using bbox
texCoords.AddRange(GenerateUV(indexOffset, verts, (float) speckleMesh.bbox.xSize.Length,
(float) speckleMesh.bbox.ySize.Length));
texCoords.AddRange(
GenerateUV(
indexOffset,
verts,
(float)speckleMesh.bbox.xSize.Length,
(float)speckleMesh.bbox.ySize.Length
)
);
}
else
{
@@ -397,12 +370,13 @@ namespace Objects.Converter.Unity
{
//TODO what if only some submeshes have colors?
Debug.LogWarning(
$"{typeof(SMesh)} {speckleMesh.id} has invalid number of vertex {nameof(SMesh.colors)}. Expected 0 or {speckleMesh.VerticesCount}, got {speckleMesh.colors.Count}");
$"{typeof(SMesh)} {speckleMesh.id} has invalid number of vertex {nameof(SMesh.colors)}. Expected 0 or {speckleMesh.VerticesCount}, got {speckleMesh.colors.Count}"
);
}
}
// Convert faces
tris.Capacity += (int) (speckleMesh.faces.Count / 4f) * 3;
tris.Capacity += (int)(speckleMesh.faces.Count / 4f) * 3;
for (int i = 0; i < speckleMesh.faces.Count; i += 4)
{
@@ -413,89 +387,46 @@ namespace Objects.Converter.Unity
}
}
protected static IEnumerable<Vector2> GenerateUV(int indexOffset, IReadOnlyList<Vector3> verts, float xSize,
float ySize)
protected static IEnumerable<Vector2> GenerateUV(
int indexOffset,
IReadOnlyList<Vector3> verts,
float xSize,
float ySize
)
{
var uv = new Vector2[verts.Count - indexOffset];
for (int i = 0; i < verts.Count - indexOffset; i++)
{
var vert = verts[i];
uv[i] = new Vector2(vert.x / xSize, vert.y / ySize);
}
return uv;
}
protected static Vector3 RecenterVertices(IList<Vector3> vertices)
{
if (!vertices.Any()) return Vector3.zero;
if (!vertices.Any())
return Vector3.zero;
Bounds meshBounds = CalculateBounds(vertices);
for (int i = 0; i < vertices.Count; i++)
vertices[i] -= meshBounds.center;
return meshBounds.center;
}
protected static Bounds CalculateBounds(IList<Vector3> points)
{
Bounds b = new Bounds {center = points[0]};
Bounds b = new Bounds { center = points[0] };
foreach (var p in points)
b.Encapsulate(p);
return b;
}
public Material[] RenderMaterialsToNative(IEnumerable<SMesh> meshes)
{
return meshes.Select(m => RenderMaterialToNative(m["renderMaterial"] as RenderMaterial)).ToArray();
}
//Just used as cache key for the default (null) material
private static RenderMaterial defaultMaterialPlaceholder = new(){id="defaultMaterial"};
public Material RenderMaterialToNative(RenderMaterial? renderMaterial)
{
//todo support more complex materials
// 1. If no renderMaterial was passed, use default material
if (renderMaterial == null)
{
if (!LoadedAssets.TryGetObject(defaultMaterialPlaceholder, out Material? defaultMaterial))
{
defaultMaterial = new Material(Shader.Find("Standard"));
LoadedAssets.TrySaveObject(defaultMaterialPlaceholder, defaultMaterial);
}
return defaultMaterial;
}
// 2. Try get existing/override material from asset cache
if (LoadedAssets.TryGetObject(renderMaterial, out Material? loadedMaterial)) return loadedMaterial;
// 3. Otherwise, convert fresh!
string shaderName = renderMaterial.opacity < 1 ? "Transparent/Diffuse" : "Standard";
Shader shader = Shader.Find(shaderName);
var mat = new Material(shader);
var c = renderMaterial.diffuse.ToUnityColor();
mat.color = new Color(c.r, c.g, c.b, (float) renderMaterial.opacity);
mat.name = AssetHelpers.GetObjectName(renderMaterial);
mat.SetFloat(Metallic, (float) renderMaterial.metalness);
mat.SetFloat(Glossiness, 1 - (float) renderMaterial.roughness);
if (renderMaterial.emissive != SColor.Black.ToArgb()) mat.EnableKeyword("_EMISSION");
mat.SetColor(EmissionColor, renderMaterial.emissive.ToUnityColor());
LoadedAssets.TrySaveObject(renderMaterial, mat);
return mat;
}
#endregion
}
}
@@ -0,0 +1,164 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Speckle.ConnectorUnity.Utils;
using Speckle.Core.Helpers;
using Objects.Other;
using UnityEngine;
using Material = UnityEngine.Material;
using SMesh = Objects.Geometry.Mesh;
using SColor = System.Drawing.Color;
using STransform = Objects.Other.Transform;
namespace Objects.Converter.Unity
{
public partial class ConverterUnity
{
protected const string DefaultShader = "Standard";
protected static readonly int EmissionColor = Shader.PropertyToID("_EmissionColor");
protected static readonly int Metallic = Shader.PropertyToID("_Metallic");
protected static readonly int Glossiness = Shader.PropertyToID("_Glossiness");
[field: SerializeField]
[field: Tooltip("The shader to use when converting opaque RenderMaterials to native")]
public Shader OpaqueMaterialShader { get; set; }
[field: SerializeField]
[field: Tooltip("The shader to use when converting non-opaque RenderMaterials to native")]
public Shader TranslucentMaterialShader { get; set; }
#nullable enable
/// <summary>
/// List of officially supported shaders. We will attempt to convert shaders not on this list, but will throw warning.
/// </summary>
protected static HashSet<string> SupportedShadersToSpeckle =
new() { "Legacy Shaders/Transparent/Diffuse", "Standard" };
#region ToNative
public Material[] RenderMaterialsToNative(IEnumerable<SMesh> meshes)
{
return meshes
.Select(m => RenderMaterialToNative(m["renderMaterial"] as RenderMaterial))
.ToArray();
}
//Just used as cache key for the default (null) material
private static readonly RenderMaterial DefaultMaterialPlaceholder =
new() { id = "defaultMaterial" };
public virtual Material RenderMaterialToNative(RenderMaterial? renderMaterial)
{
//todo support more complex materials
// 1. If no renderMaterial was passed, use default material
if (renderMaterial == null)
{
if (
!LoadedAssets.TryGetObject(
DefaultMaterialPlaceholder,
out Material? defaultMaterial
)
)
{
defaultMaterial = new Material(OpaqueMaterialShader);
LoadedAssets.TrySaveObject(DefaultMaterialPlaceholder, defaultMaterial);
}
return defaultMaterial;
}
// 2. Try get existing/override material from asset cache
if (LoadedAssets.TryGetObject(renderMaterial, out Material? loadedMaterial))
return loadedMaterial;
// 3. Otherwise, convert fresh!
string name = CoreUtils.GenerateObjectName(renderMaterial);
Color diffuse = renderMaterial.diffuse.ToUnityColor();
bool isOpaque = Math.Abs(renderMaterial.opacity - 1d) < Constants.Eps;
Color color = new(diffuse.r, diffuse.g, diffuse.b, (float)renderMaterial.opacity);
float metalic = (float)renderMaterial.metalness;
float gloss = 1f - (float)renderMaterial.roughness;
bool hasEmission = renderMaterial.emissive != SColor.Black.ToArgb();
Color emission = renderMaterial.emissive.ToUnityColor();
Shader shader =
renderMaterial.opacity < 1 ? TranslucentMaterialShader : OpaqueMaterialShader;
var mat = new Material(shader);
mat.color = color;
mat.name = name;
mat.SetFloat(Metallic, metalic);
mat.SetFloat(Glossiness, gloss);
mat.SetColor(EmissionColor, emission);
if (hasEmission)
mat.EnableKeyword("_EMISSION");
if (!isOpaque)
{
if (shader.name == "Standard")
{
ShaderHelpers.SetupMaterialWithBlendMode_Standard(
mat,
ShaderHelpers.BlendMode.Transparent,
true
);
}
else if (shader.name == "Lit") //URP lit
{ }
}
LoadedAssets.TrySaveObject(renderMaterial, mat);
return mat;
}
#endregion
#region ToSpeckle
public virtual RenderMaterial MaterialToSpeckle(Material nativeMaterial)
{
//Warning message for unknown shaders
if (!SupportedShadersToSpeckle.Contains(nativeMaterial.shader.name))
Debug.LogWarning(
$"Material Shader \"{nativeMaterial.shader.name}\" is not explicitly supported, the resulting material may be incorrect"
);
var color = nativeMaterial.color;
var opacity = 1f;
if (nativeMaterial.shader.name.ToLower().Contains("transparent"))
{
opacity = color.a;
color.a = 255;
}
var emissive = nativeMaterial.IsKeywordEnabled("_EMISSION")
? nativeMaterial.GetColor(EmissionColor).ToIntColor()
: SColor.Black.ToArgb();
var materialName = !string.IsNullOrWhiteSpace(nativeMaterial.name)
? nativeMaterial.name.Replace("(Instance)", string.Empty).TrimEnd()
: $"material-{Guid.NewGuid().ToString().Substring(0, 8)}";
var metalness = nativeMaterial.HasProperty(Metallic)
? nativeMaterial.GetFloat(Metallic)
: 0;
var roughness = nativeMaterial.HasProperty(Glossiness)
? 1 - nativeMaterial.GetFloat(Glossiness)
: 1;
return new RenderMaterial
{
name = materialName,
diffuse = color.ToIntColor(),
opacity = opacity,
metalness = metalness,
roughness = roughness,
emissive = emissive,
};
}
#endregion
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8f91633e63e64685a6b2a4fd609312a0
timeCreated: 1689085502
@@ -1,23 +1,22 @@
using Objects.Geometry;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using Mesh = Objects.Geometry.Mesh;
using UnityEngine;
namespace Objects.Converter.Unity
{
public partial class ConverterUnity
{
public string ModelUnits = Speckle.Core.Kits.Units.Meters; //the default Unity units are meters
private double ScaleToNative(double value, string units)
public partial class ConverterUnity
{
var f = Speckle.Core.Kits.Units.GetConversionFactor(units, ModelUnits);
return value * f;
[field: SerializeField]
[field: Tooltip("The units used by conversion functions")]
public string ModelUnits { get; set; } = Speckle.Core.Kits.Units.Meters; //the default Unity units are meters
private double ScaleToNative(double value, string units)
{
var f = GetConversionFactor(units);
return value * f;
}
private double GetConversionFactor(string units)
{
return Speckle.Core.Kits.Units.GetConversionFactor(units, ModelUnits);
}
}
}
}
@@ -15,8 +15,14 @@ using Object = UnityEngine.Object;
namespace Objects.Converter.Unity
{
[Serializable]
public partial class ConverterUnity : ISpeckleConverter
{
[Tooltip(
"Enable/Disable attaching non-converted properties to received objects. Disabling this will lead to lighter weight objects that serialize much faster."
)]
public bool shouldAttachProperties = true;
#region implemented methods
public void SetConverterSettings(object settings) => throw new NotImplementedException();
@@ -26,10 +32,11 @@ namespace Objects.Converter.Unity
public string Author => "Speckle";
public string WebsiteOrEmail => "https://speckle.systems";
public ProgressReport Report { get; }
public ProgressReport Report => throw new NotImplementedException();
public ReceiveMode ReceiveMode { get; set; }
public IEnumerable<string> GetServicedApplications() => new string[] {HostApplications.Unity.Name};
public IEnumerable<string> GetServicedApplications() =>
new[] { HostApplications.Unity.Name };
public AbstractNativeCache LoadedAssets { get; private set; }
@@ -37,13 +44,23 @@ namespace Objects.Converter.Unity
{
if (doc is not AbstractNativeCache context)
throw new ArgumentException(
$"Expected {nameof(doc)} to be of type {typeof(Dictionary<string, Object>)}", nameof(doc));
$"Expected {nameof(doc)} to be of type {typeof(Dictionary<string, Object>)}",
nameof(doc)
);
LoadedAssets = context;
if (OpaqueMaterialShader == null)
OpaqueMaterialShader = Shader.Find(DefaultShader);
if (TranslucentMaterialShader == null)
TranslucentMaterialShader = Shader.Find(DefaultShader);
}
public void SetContextObjects(List<ApplicationObject> objects) => throw new NotImplementedException();
public void SetContextObjects(List<ApplicationObject> objects) =>
throw new NotImplementedException();
public void SetPreviousContextObjects(List<ApplicationObject> objects) => throw new NotImplementedException();
public void SetPreviousContextObjects(List<ApplicationObject> objects) =>
throw new NotImplementedException();
#nullable enable
public object? ConvertToNative(Base @object) => ConvertToNativeGameObject(@object);
@@ -51,7 +68,9 @@ namespace Objects.Converter.Unity
public Base ConvertToSpeckle(object @object)
{
if (!(@object is GameObject go))
throw new NotSupportedException($"Cannot convert object of type {@object.GetType()} to Speckle");
throw new NotSupportedException(
$"Cannot convert object of type {@object.GetType()} to Speckle"
);
return ConvertGameObjectToSpeckle(go);
}
@@ -87,14 +106,16 @@ namespace Objects.Converter.Unity
}
catch (Exception e)
{
Debug.LogError($"Failed to convert {component.GetType()} component\n{e}", component);
Debug.LogError(
$"Failed to convert {component.GetType()} component\n{e}",
component
);
}
}
return speckleObject;
}
public GameObject? ConvertToNativeGameObject(Base speckleObject)
{
switch (speckleObject)
@@ -111,8 +132,10 @@ namespace Objects.Converter.Unity
return View3DToNative(v);
case Mesh o:
return MeshToNative(o);
case BlockInstance o:
return BlockToNative(o);
case Instance o:
return InstanceToNative(o);
case Collection c:
return CollectionToNative(c);
default:
//Object is not a raw geometry, convert it as display value element
@@ -120,7 +143,11 @@ namespace Objects.Converter.Unity
if (element != null)
{
if (!speckleObject.speckle_type.Contains("Objects.Geometry"))
AttachSpeckleProperties(element, speckleObject.GetType(), GetProperties(speckleObject));
AttachSpeckleProperties(
element,
speckleObject.GetType(),
() => GetProperties(speckleObject)
);
return element;
}
@@ -129,9 +156,8 @@ namespace Objects.Converter.Unity
}
}
public IList<string> DisplayValuePropertyAliases { get; set; } =
new[] {"displayValue", "@displayValue", "displayMesh", "@displayMesh"};
new[] { "displayValue", "@displayValue", "displayMesh", "@displayMesh" };
public GameObject? DisplayValueToNative(Base @object)
{
@@ -143,7 +169,7 @@ namespace Objects.Converter.Unity
case IList dvCollection:
return MeshesToNative(@object, dvCollection.OfType<Mesh>().ToList());
case Mesh dvMesh:
return MeshesToNative(@object, new[] {dvMesh});
return MeshesToNative(@object, new[] { dvMesh });
case Base dvBase:
return ConvertToNativeGameObject(dvBase);
}
@@ -151,16 +177,20 @@ namespace Objects.Converter.Unity
return null;
}
private SpeckleProperties AttachSpeckleProperties(GameObject go, Type speckleType,
IDictionary<string, object?> properties)
private SpeckleProperties? AttachSpeckleProperties(
GameObject go,
Type speckleType,
Func<IDictionary<string, object?>> properties
)
{
if (!shouldAttachProperties)
return null;
var sd = go.AddComponent<SpeckleProperties>();
sd.Data = properties;
sd.Data = properties.Invoke();
sd.SpeckleType = speckleType;
return sd;
}
public List<Base> ConvertToSpeckle(List<object> objects)
{
@@ -183,6 +213,21 @@ namespace Objects.Converter.Unity
}
}
public GameObject CollectionToNative(Collection collection)
{
var name =
collection.name
?? $"{collection.collectionType} -- {collection.applicationId ?? collection.id}";
var go = new GameObject(name);
AttachSpeckleProperties(go, collection.GetType(), () => GetProperties(collection));
if (name == "Rooms")
{
go.SetActive(false);
}
return go;
}
public bool CanConvertToNative(Base @object)
{
switch (@object)
@@ -195,13 +240,15 @@ namespace Objects.Converter.Unity
// return true;
// case Curve _:
// return true;
// case View3D _:
// return true;
case View2D _:
return false;
case Mesh _:
// case View2D:
// return false;
case View3D _:
return shouldConvertViews;
case Collection:
return true;
case BlockInstance _:
case Mesh:
return true;
case Instance:
return true;
default:
@@ -217,4 +264,4 @@ namespace Objects.Converter.Unity
}
}
}
}
}
@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: 3e49710593731cc49b9d22a0ba82b5d1
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 1
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 1
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
Windows Store Apps: WindowsStoreApps
second:
enabled: 0
settings:
CPU: AnyCPU
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: b875f05888babdd4db73c70d39a33feb
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 1
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 1
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
Windows Store Apps: WindowsStoreApps
second:
enabled: 0
settings:
CPU: AnyCPU
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: c822f631fa258514f8b76cdcaacc1b36
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 1
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 1
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
Windows Store Apps: WindowsStoreApps
second:
enabled: 0
settings:
CPU: AnyCPU
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: 5e6c835b7544a374095e58eddb5d2f1c
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 1
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 1
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
Windows Store Apps: WindowsStoreApps
second:
enabled: 0
settings:
CPU: AnyCPU
userData:
assetBundleName:
assetBundleVariant:
@@ -23,6 +23,9 @@ PluginImporter:
Exclude WebGL: 0
Exclude Win: 0
Exclude Win64: 0
Exclude WindowsStoreApps: 0
Exclude iOS: 0
Exclude tvOS: 0
- first:
Android: Android
second:
@@ -74,9 +77,30 @@ PluginImporter:
- first:
Windows Store Apps: WindowsStoreApps
second:
enabled: 0
enabled: 1
settings:
CPU: AnyCPU
DontProcess: false
PlaceholderPath:
SDK: AnySDK
ScriptingBackend: DotNet
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
CPU: AnyCPU
CompileFlags:
FrameworkDependencies:
- first:
tvOS: tvOS
second:
enabled: 1
settings:
CPU: AnyCPU
CompileFlags:
FrameworkDependencies:
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: f4e83b55b28720a47a937b987aba6b02
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 1
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 1
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
Windows Store Apps: WindowsStoreApps
second:
enabled: 0
settings:
CPU: AnyCPU
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: 49715c68e5dbf254ca4ca7bc11292537
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 1
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 1
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
Windows Store Apps: WindowsStoreApps
second:
enabled: 0
settings:
CPU: AnyCPU
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: c84f8458a24050a48b32654c570e7acc
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 1
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 1
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
Windows Store Apps: WindowsStoreApps
second:
enabled: 0
settings:
CPU: AnyCPU
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: f741667477576f444a8a566e2a1f4ce9
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 1
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 1
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
Windows Store Apps: WindowsStoreApps
second:
enabled: 0
settings:
CPU: AnyCPU
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: 5f1430ebe15696242a6b437faa8b8666
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 1
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 1
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
Windows Store Apps: WindowsStoreApps
second:
enabled: 0
settings:
CPU: AnyCPU
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: 6f16b872bea64534a87c7229fa49ac30
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 1
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 1
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
Windows Store Apps: WindowsStoreApps
second:
enabled: 0
settings:
CPU: AnyCPU
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: 817747ee0314c9441bac48ea74d34d42
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 1
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 1
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
Windows Store Apps: WindowsStoreApps
second:
enabled: 0
settings:
CPU: AnyCPU
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: 38b2c99acc4856c4186972b73c6fc332
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 1
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 1
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
Windows Store Apps: WindowsStoreApps
second:
enabled: 0
settings:
CPU: AnyCPU
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: a2d9231f2f78f0d4ea8786452ac71598
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 1
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 1
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
Windows Store Apps: WindowsStoreApps
second:
enabled: 0
settings:
CPU: AnyCPU
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: b8ebfa0955817084a887b7a704d43545
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 1
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 1
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
Windows Store Apps: WindowsStoreApps
second:
enabled: 0
settings:
CPU: AnyCPU
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: 2c0b2a3a645b5844faa74f29d90cff65
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 1
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 1
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
Windows Store Apps: WindowsStoreApps
second:
enabled: 0
settings:
CPU: AnyCPU
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: 3d1e392b0c6fd8f4a9e494675b787a9a
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 1
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 1
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
Windows Store Apps: WindowsStoreApps
second:
enabled: 0
settings:
CPU: AnyCPU
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: b0e1bc6a04c09e54188d8d8244b876e4
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 1
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 1
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
Windows Store Apps: WindowsStoreApps
second:
enabled: 0
settings:
CPU: AnyCPU
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: d3a7df0fa5bb87b43ac372cfefb11aca
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 1
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 1
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
Windows Store Apps: WindowsStoreApps
second:
enabled: 0
settings:
CPU: AnyCPU
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: 3855fc1a60cef174fb322913390b6fd8
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 1
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 1
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
Windows Store Apps: WindowsStoreApps
second:
enabled: 0
settings:
CPU: AnyCPU
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: abe52d099c937594c8788655f4c2d181
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 1
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 1
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
Windows Store Apps: WindowsStoreApps
second:
enabled: 0
settings:
CPU: AnyCPU
userData:
assetBundleName:
assetBundleVariant:

Some files were not shown because too many files have changed in this diff Show More