13 Commits

Author SHA1 Message Date
Jedd Morgan f406cb8229 wip update for the sample project to use new ui components 2023-06-26 22:13:12 +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 a89e4cdfe7 wip implementation of traversal refactor 2023-04-21 22:34:59 +01:00
63 changed files with 5375 additions and 1736 deletions
@@ -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");
});
+153
View File
@@ -0,0 +1,153 @@
using System;
using System.Collections;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Speckle.ConnectorUnity.Components;
using Speckle.ConnectorUnity.Utils;
using Speckle.Core.Api;
using Speckle.Core.Credentials;
using Speckle.Core.Logging;
using Speckle.Core.Models;
using UnityEngine;
[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.WaitForTask<Account>(async () => await GetAccount(sw));
yield return accountTask;
_tokenSource.Token.ThrowIfCancellationRequested();
using Client c = new(accountTask.Result);
var objectIdTask = new 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.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 e)
{
if (string.IsNullOrEmpty(sw.StreamId))
throw e;
//Fallback to a non authed account
account = new Account
{
token = "",
serverInfo = new ServerInfo { url = sw.ServerUrl },
userInfo = new UserInfo()
};
}
return account;
}
}
@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 2dd598fed5008c44a815ba09e81a2d19
guid: 942bf0cb27c5c5045bc4cbb7fc0fad71
MonoImporter:
externalObjects: {}
serializedVersion: 2
+1
View File
@@ -9,6 +9,7 @@ using UnityEngine;
using UnityEngine.Events;
[RequireComponent(typeof(Sender)), ExecuteAlways]
[Obsolete]
public class SendChildrenToSpeckle : MonoBehaviour
{
public LayerMask layerMask;
+1 -1
View File
@@ -1,4 +1,4 @@
{
"name": "Speckle.Extra",
"references":[ "GUID:eed1b8b83e2c0074d9e5de2348e3ff72", "GUID:e6adfdc4e436206479f48eafc82f32b5", "GUID:d274441ecc3eb3f43b093eec1503d681" ]
"references":[ "GUID:eed1b8b83e2c0074d9e5de2348e3ff72", "GUID:e6adfdc4e436206479f48eafc82f32b5", "GUID:d274441ecc3eb3f43b093eec1503d681", "GUID:50d889142fdf9de4b8501c6eaa4b3225" ]
}
+3 -2
View File
@@ -10,7 +10,8 @@ using UnityEngine.UI;
using Text = UnityEngine.UI.Text;
namespace Speckle.ConnectorUnity
{
{
[Obsolete]
public class InteractionLogic : MonoBehaviour
{
private Receiver receiver;
@@ -216,4 +217,4 @@ namespace Speckle.ConnectorUnity
});
}
}
}
}
+3 -1
View File
@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using Speckle.Core.Credentials;
@@ -8,6 +9,7 @@ using Stream = Speckle.Core.Api.Stream;
namespace Speckle.ConnectorUnity
{
[Obsolete]
public class SpeckleExamples : MonoBehaviour
{
public Text SelectStreamText;
@@ -129,4 +131,4 @@ namespace Speckle.ConnectorUnity
}
}
}
}
}
File diff suppressed because one or more lines are too long
@@ -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:
@@ -1,9 +1,8 @@
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;
@@ -18,6 +17,77 @@ namespace Speckle.ConnectorUnity.Components.Editor
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));
Debug.Log(value);
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();
@@ -28,6 +98,7 @@ namespace Speckle.ConnectorUnity.Components.Editor
Init();
}
private void Init()
{
var speckleReceiver = (SpeckleReceiver) target;
@@ -42,39 +113,92 @@ namespace Speckle.ConnectorUnity.Components.Editor
((SpeckleReceiver)target).GetPreviewImage(t => previewImage = t);
}
public override async void OnInspectorGUI()
private async Task ReceiveSelection(int progressId)
{
var speckleReceiver = (SpeckleReceiver) target;
var speckleReceiver = (SpeckleReceiver)target;
DrawDefaultInspector();
//Preview image
foldOutStatus = EditorGUILayout.Foldout(foldOutStatus, "Preview Image");
if (foldOutStatus)
bool shouldCancel = false;
Progress.RegisterCancelCallback(progressId, () =>
{
Rect rect = GUILayoutUtility.GetAspectRect(7f/4f);
if(previewImage != null) GUI.DrawTexture(rect, previewImage);
speckleReceiver.Cancel();
shouldCancel = true;
return true;
});
Base commitObject;
try
{
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)
int totalChildren = (int) Math.Min(commitObject.totalChildrenCount, int.MaxValue);
float totalChildrenFloat = commitObject.totalChildrenCount;
var convertProgress = Progress.Start("Converting To Native", "Preparing...", Progress.Options.Indefinite | Progress.Options.Sticky, progressId);
bool BeforeConvert(TraversalContext context)
{
generateAssets = selection;
UpdateGenerateAssets();
}
//TODO: Draw events in a collapsed region
Base b = context.current;
if (receive)
{
await ReceiveAndConvert(speckleReceiver).ConfigureAwait(false);;
//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()
@@ -83,114 +207,75 @@ namespace Speckle.ConnectorUnity.Components.Editor
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).ConfigureAwait(true);;
var speckleReceiver = (SpeckleReceiver)target;
if (commitObject == null) return null;
string serverLogName = speckleReceiver.Account.Client?.ServerUrl ?? "Speckle";
var gameObject = Convert(speckleReceiver, commitObject, commit.id);
Debug.Log($"Successfully received and converted commit: {commit.id}", target);
return gameObject;
}
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().ConfigureAwait(false);;
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");
// Ensure it gets reparented if this was a context click (otherwise does nothing)
@@ -208,5 +293,6 @@ namespace Speckle.ConnectorUnity.Components.Editor
EditorGUIUtility.SetIconForObject(go, icon);
#endif
}
}
}
@@ -106,12 +106,6 @@ namespace Speckle.ConnectorUnity.Components.Editor
SceneManager.GetActiveScene().GetRootGameObjects(),
go => go.activeInHierarchy);
}
private void CancelSend()
{
((SpeckleReceiver)target).CancellationTokenSource?.Cancel();
EditorApplication.delayCall += EditorUtility.ClearProgressBar;
}
}
}
@@ -163,9 +163,10 @@ 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 =>
{
@@ -179,8 +180,15 @@ namespace Speckle.ConnectorUnity.Components.Editor
);
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;
@@ -46,8 +46,8 @@ namespace Speckle.ConnectorUnity.Wrappers.Selection.Editor
("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()),
};
}
}
@@ -84,7 +84,7 @@ namespace Speckle.ConnectorUnity.Wrappers.Selection.Editor
{
("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),
};
@@ -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,10 +5,13 @@ 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 JetBrains.Annotations;
using Sentry;
using Speckle.ConnectorUnity.Components;
using Speckle.ConnectorUnity.Utils;
using Speckle.Core.Kits;
using UnityEngine;
@@ -18,7 +21,8 @@ namespace Speckle.ConnectorUnity
/// A Speckle Receiver, it's a wrapper around a basic Speckle Client
/// that handles conversions and subscriptions for you
/// </summary>
[RequireComponent( typeof( RecursiveConverter ) )]
[RequireComponent(typeof(RecursiveConverter))]
[Obsolete]
public class Receiver : MonoBehaviour
{
public string StreamId;
@@ -93,7 +97,7 @@ namespace Speckle.ConnectorUnity
if (!mainBranch.commits.items.Any())
throw new Exception("This branch has no commits");
var commit = mainBranch.commits.items[0];
GetAndConvertObject(commit.referencedObject, commit.id);
GetAndConvertObject(commit.referencedObject, commit.id, commit.sourceApplication, commit.authorId);
}
catch (Exception e)
{
@@ -116,12 +120,12 @@ namespace Speckle.ConnectorUnity
if (e.branchName == BranchName)
{
Debug.Log("New commit created");
GetAndConvertObject(e.objectId, e.id);
GetAndConvertObject(e.objectId, e.id, e.sourceApplication, e.authorId);
}
}
private async void GetAndConvertObject(string objectId, string commitId)
private async void GetAndConvertObject(string objectId, string commitId, string sourceApplication, string authorId)
{
try
{
@@ -136,7 +140,14 @@ namespace Speckle.ConnectorUnity
disposeTransports: true
);
Analytics.TrackEvent(Client.Account, Analytics.Events.Receive);
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(() =>
{
@@ -186,4 +197,4 @@ namespace Speckle.ConnectorUnity
#endregion
}
}
}
@@ -21,6 +21,7 @@ namespace Speckle.ConnectorUnity
/// that handles conversions for you
/// </summary>
[RequireComponent(typeof(RecursiveConverter)), ExecuteAlways]
[Obsolete]
public class Sender : MonoBehaviour
{
@@ -146,4 +147,4 @@ namespace Speckle.ConnectorUnity
#endregion
}
}
}
@@ -2,62 +2,225 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Speckle.ConnectorUnity.NativeCache;
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 ConvertToNative(Base rootObject, Transform? parent, IDictionary<Base, GameObject> outCreatedObjects)
/// <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
)
{
InitializeAssetCache();
var traversalFunc = DefaultTraversal.CreateBIMTraverseFunc(ConverterInstance);
var convertableObjects = traversalFunc.Traverse(rootObject)
.Where(tc => ConverterInstance.CanConvertToNative(tc.current));
foreach (TraversalContext tc in convertableObjects)
{
Transform? nativeParent = parent;
if (tc.parent != null && outCreatedObjects.TryGetValue(tc.parent.current, out GameObject p))
nativeParent = p.transform;
GameObject? go = ConverterInstance.ConvertToNative(tc.current) as GameObject;
if (go == null) continue;
go.transform.SetParent(parent, true);
outCreatedObjects.Add(tc.current, go);
//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 = AssetHelpers.GenerateObjectName(tc.current);
//if (baseObject["tag"] is string t) go.tag = t;
if (tc.current["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["isStatic"] is bool isStatic) go.isStatic = isStatic;
yield return go;
}
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))
@@ -74,11 +237,13 @@ 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>
[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>
[Obsolete("Use " + nameof(RecursivelyConvertToNative_Sync))]
public virtual List<GameObject> RecursivelyConvertToNative(object? o, Transform? parent, Func<Base, bool> predicate)
{
InitializeAssetCache();
@@ -111,6 +276,7 @@ namespace Speckle.ConnectorUnity.Components
ConverterInstance.SetContextDocument(AssetCache);
}
[Obsolete]
public virtual void RecurseTreeToNative(Base baseObject, Transform? parent, Func<Base, bool> predicate, IList<GameObject> outCreatedObjects)
{
object? converted = null;
@@ -129,7 +295,7 @@ namespace Speckle.ConnectorUnity.Components
//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 = AssetHelpers.GenerateObjectName(baseObject);
go.name = CoreUtils.GenerateObjectName(baseObject);
//if (baseObject["tag"] is string t) go.tag = t;
if (baseObject["physicsLayer"] is string layerName)
{
@@ -150,6 +316,7 @@ namespace Speckle.ConnectorUnity.Components
}
[Obsolete]
private IEnumerable<string> GetPotentialChildren(Base baseObject)
{
return ConverterInstance.CanConvertToNative(baseObject)
@@ -157,7 +324,7 @@ namespace Speckle.ConnectorUnity.Components
: baseObject.GetMembers().Keys;
}
[Obsolete]
protected virtual void ConvertChild(object? value, Transform? parent, Func<Base, bool> predicate, IList<GameObject> outCreatedObjects)
{
foreach (Base b in GraphTraversal.TraverseMember(value))
@@ -165,14 +332,6 @@ namespace Speckle.ConnectorUnity.Components
RecurseTreeToNative(b, parent, predicate, outCreatedObjects);
}
}
[Obsolete("Use " + nameof(RecursivelyConvertToNative), true)]
public GameObject ConvertRecursivelyToNative(Base @base, string name)
{
var parentObject = new GameObject(name);
RecursivelyConvertToNative(@base, parentObject.transform);
return parentObject;
}
#endregion
}
}
}
@@ -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;
@@ -18,7 +19,12 @@ namespace Speckle.ConnectorUnity.Components
[field: SerializeField]
public AggregateNativeCache AssetCache { get; set; }
private void Awake()
protected void Awake()
{
Init();
}
protected void Init()
{
Setup.Init(HostApplications.Unity.GetVersion(CoreUtils.GetHostAppVersion()), HostApplications.Unity.Slug);
@@ -30,4 +36,4 @@ namespace Speckle.ConnectorUnity.Components
}
}
}
}
}
@@ -1,16 +1,19 @@
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;
@@ -37,132 +40,257 @@ namespace Speckle.ConnectorUnity.Components
public RecursiveConverter Converter { get; private set; }
#nullable enable
[Header("Events")]
[HideInInspector]
public CommitSelectionEvent OnCommitSelectionChange;
public CommitSelectionEvent OnCommitSelectionChange = new();
[HideInInspector]
public OperationProgressEvent OnReceiveProgressAction;
public OperationProgressEvent OnReceiveProgressAction = new();
[HideInInspector]
public ErrorActionEvent OnErrorAction;
public ErrorActionEvent OnErrorAction = new();
[HideInInspector]
public ChildrenCountHandler OnTotalChildrenCountKnown;
public ChildrenCountHandler OnTotalChildrenCountKnown = new();
[HideInInspector]
public ReceiveCompleteHandler OnComplete;
public ReceiveCompleteHandler OnComplete = new();
#nullable enable
protected internal CancellationTokenSource? CancellationTokenSource { get; private set; }
protected CancellationTokenSource? CancellationTokenSource { get; private set; }
public CancellationToken CancellationToken => CancellationTokenSource?.Token ?? default;
public bool IsReceiving => CancellationTokenSource != null;
//TODO runtime receiving
public IEnumerator ReceiveAndConvertRoutine(SpeckleReceiver speckleReceiver, string rootObjectName, Action<Base>? beforeConvertCallback = 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);
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);
Base? b = receiveOperation.Result;
if (b == null) yield break;
if (receiveOperation.IsFaulted)
{
OnErrorAction.Invoke("Failed to receive", receiveOperation.Exception);
FinishOperation();
yield break;
}
Base b = receiveOperation.Result;
//TODO make routine break for each catergory/object
GameObject go = ConvertToNativeWithCategories(b, rootObjectName, beforeConvertCallback);
OnComplete.Invoke(go);
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();
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)
).ConfigureAwait(false);
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();
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)
{
using var transport = new ServerTransportV2(client.Account, streamId);
transport.CancellationToken = token;
Base? ret = null;
transport.CancellationToken = cancellationToken;
cancellationToken.ThrowIfCancellationRequested();
Base? requestedObject = await Operations.Receive(
objectId: objectId,
cancellationToken: cancellationToken,
remoteTransport: transport,
onProgressAction: onProgressAction,
onErrorAction: (s, ex) =>
{
//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>()
{
{"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
{
token.ThrowIfCancellationRequested();
ret = await Operations.Receive(
objectId: objectId,
cancellationToken: token,
remoteTransport: transport,
onProgressAction: onProgressAction,
onErrorAction: onErrorAction,
onTotalChildrenCountKnown: onTotalChildrenCountKnown,
disposeTransports: false
).ConfigureAwait(false);
Analytics.TrackEvent(client.Account, Analytics.Events.Receive);
token.ThrowIfCancellationRequested();
//Read receipt
try
await client.CommitReceived(cancellationToken, new CommitReceivedInput
{
await client.CommitReceived(token, new CommitReceivedInput
{
streamId = streamId,
commitId = commitId,
message = $"received commit from {Application.unityVersion}",
sourceApplication = HostApplications.Unity.GetVersion(CoreUtils.GetHostAppVersion())
}).ConfigureAwait(false);
}
catch (Exception e)
{
// Do nothing!
Debug.LogWarning($"Failed to send read receipt\n{e}");
}
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}");
}
return ret;
return requestedObject;
}
/// <summary>
/// Helper method for using <see cref="RecursiveConverter"/>.
/// Creates blank GameObjects for each property/category of the root object.
@@ -171,6 +299,7 @@ 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>
[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)
{
@@ -203,7 +332,7 @@ namespace Speckle.ConnectorUnity.Components
return rootObject;
}
/// <summary>
///
/// </summary>
@@ -212,6 +341,7 @@ namespace Speckle.ConnectorUnity.Components
/// <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,
@@ -250,22 +380,21 @@ namespace Speckle.ConnectorUnity.Components
/// </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 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
@@ -276,6 +405,8 @@ namespace Speckle.ConnectorUnity.Components
Application.OpenURL(url);
}
#endif
public string GetSelectedUrl()
{
string serverUrl = Account.Selected!.serverInfo.url;
@@ -311,12 +442,16 @@ namespace Speckle.ConnectorUnity.Components
Account.RefreshOptions();
}
public void OnDestroy()
public void OnDisable()
{
CancellationTokenSource?.Cancel();
}
public void OnDestroy()
{
CancellationTokenSource?.Dispose();
}
public void OnBeforeSerialize()
{
//pass
@@ -325,12 +460,32 @@ namespace Speckle.ConnectorUnity.Components
{
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
GameObject go = 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 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;
@@ -84,7 +85,11 @@ namespace Speckle.ConnectorUnity.Components
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)
{
@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Objects.Other;
using Speckle.ConnectorUnity;
using Speckle.ConnectorUnity.NativeCache;
using Speckle.ConnectorUnity.Utils;
using Speckle.ConnectorUnity.Wrappers;
@@ -212,15 +213,11 @@ namespace Objects.Converter.Unity
return sobject;
}
public GameObject? InstanceToNative(Instance instance)
public GameObject InstanceToNative(Instance instance)
{
if (instance.definition == null)
{
Debug.Log($"Skipping {typeof(BlockInstance)} {instance.id}, block definition was null");
return null;
}
if (instance.definition == null) throw new ArgumentException("Definition was null", nameof(instance));
var defName = instance.definition["name"] as string ?? "";
var defName = CoreUtils.GenerateObjectName(instance.definition);
// Check for existing converted object
if(LoadedAssets.TryGetObject(instance.definition, out GameObject? existingGo))
{
@@ -231,15 +228,19 @@ namespace Objects.Converter.Unity
}
// Convert the block definition
GameObject native = new GameObject(defName);
GameObject native = new(defName);
List<SMesh> meshes = new();
List<Base> others = new();
var geometry = instance.definition is BlockDefinition b
? b.geometry
: GraphTraversal.TraverseMember(instance.definition["elements"]);
: 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);
@@ -252,7 +253,7 @@ namespace Objects.Converter.Unity
if(!TryGetMeshFromCache(instance.definition, meshes, out Mesh? nativeMesh, out _))
{
MeshToNativeMesh(meshes, out nativeMesh);
string name = AssetHelpers.GenerateObjectName(instance.definition);
string name = CoreUtils.GenerateObjectName(instance.definition);
nativeMesh.name = name;
LoadedAssets.TrySaveObject(instance.definition, nativeMesh);
}
@@ -269,8 +270,14 @@ namespace Objects.Converter.Unity
LoadedAssets.TrySaveObject(instance.definition, native);
TransformToNativeTransform(native.transform, instance.transform);
if (instance["name"] is string instanceName) native.name = instanceName;
var instanceName = CoreUtils.GetFriendlyObjectName(instance) != null
? CoreUtils.GenerateObjectName(instance)
: defName;
native.name = instanceName;
return native;
}
@@ -354,4 +361,4 @@ namespace Objects.Converter.Unity
}
}
}
}
@@ -6,6 +6,7 @@ using Speckle.ConnectorUnity.Utils;
using Objects.Other;
using Objects.Utils;
using Speckle.ConnectorUnity.NativeCache;
using Speckle.Core.Logging;
using Speckle.Core.Models;
using UnityEngine;
using UnityEngine.Rendering;
@@ -209,13 +210,9 @@ 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;
}
if (!meshes.Any()) throw new ArgumentException("Expected at least one Mesh", nameof(meshes));
Material[] nativeMaterials = RenderMaterialsToNative(meshes);
@@ -223,7 +220,7 @@ namespace Objects.Converter.Unity
{
//Convert a new one
MeshToNativeMesh(meshes, out nativeMesh, out center);
string name = AssetHelpers.GenerateObjectName(element);
string name = CoreUtils.GenerateObjectName(element);
nativeMesh.name = name;
LoadedAssets.TrySaveObject(element, nativeMesh);
}
@@ -241,15 +238,12 @@ namespace Objects.Converter.Unity
/// </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;
}
GameObject? converted = MeshesToNative(speckleMesh, new[] {speckleMesh});
throw new ArgumentException("mesh data was empty", nameof(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)));
@@ -483,7 +477,7 @@ namespace Objects.Converter.Unity
var c = renderMaterial.diffuse.ToUnityColor();
mat.color = new Color(c.r, c.g, c.b, (float) renderMaterial.opacity);
mat.name = AssetHelpers.GenerateObjectName(renderMaterial);
mat.name = CoreUtils.GenerateObjectName(renderMaterial);
mat.SetFloat(Metallic, (float) renderMaterial.metalness);
mat.SetFloat(Glossiness, 1 - (float) renderMaterial.roughness);
if (renderMaterial.emissive != SColor.Black.ToArgb()) mat.EnableKeyword("_EMISSION");
@@ -5,6 +5,7 @@ using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Objects.BuiltElements;
using Objects.Organization;
using Objects.Other;
using Speckle.ConnectorUnity.Utils;
using Speckle.ConnectorUnity.NativeCache;
@@ -111,8 +112,10 @@ namespace Objects.Converter.Unity
return View3DToNative(v);
case Mesh o:
return MeshToNative(o);
case BlockInstance 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
@@ -183,6 +186,19 @@ 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 +211,17 @@ namespace Objects.Converter.Unity
// return true;
// case Curve _:
// return true;
// case View3D _:
// case View2D:
// return false;
// case View _:
// return true;
case View2D _:
return false;
case Mesh _:
case Model:
return false; //This allows us to traverse older commits pre-collections
case Collection:
return true;
case BlockInstance _:
case Mesh:
return true;
case Instance:
return true;
default:
@@ -212,9 +232,9 @@ namespace Objects.Converter.Unity
if (@object[alias] is IList)
return true;
}
return false;
}
}
}
}
}
File diff suppressed because it is too large Load Diff
@@ -15,6 +15,7 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;
using Speckle.ConnectorUnity.Utils;
using Speckle.Core.Kits;
using Speckle.Core.Logging;
using UnityEngine;
@@ -1,15 +1,16 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using Speckle.ConnectorUnity.Utils;
using Speckle.Core.Models;
using UnityEngine;
using Object = UnityEngine.Object;
namespace Speckle.ConnectorUnity.NativeCache
{
#nullable enable
[ExecuteAlways]
public abstract class AbstractNativeCache : ScriptableObject
{
@@ -69,38 +70,12 @@ namespace Speckle.ConnectorUnity.NativeCache
public static string GetAssetName(Base speckleObject, Type nativeType)
{
string suffix = GetAssetSuffix(nativeType);
string name = GenerateObjectName(speckleObject);
string name = CoreUtils.GenerateObjectName(speckleObject);
string sanitisedName = new(name.Where(x => !InvalidChars.Contains(x)).ToArray());
return $"{sanitisedName}{suffix}";
}
public const string OBJECT_NAME_SEPERATOR = " -- ";
/// <param name="speckleObject">The object to be named</param>
/// <returns>A human-readable Object name unique to the given <paramref name="speckleObject"/></returns>
public static string GenerateObjectName(Base speckleObject)
{
var prefix = GetFriendlyObjectName(speckleObject) ?? SimplifiedSpeckleType(speckleObject);
return $"{prefix}{OBJECT_NAME_SEPERATOR}{speckleObject.id}";
}
public static string? GetFriendlyObjectName(Base speckleObject)
{
return speckleObject["name"] as string
?? speckleObject["Name"] as string
?? speckleObject["family"] as string;
}
/// <param name="speckleObject"></param>
/// <returns>The most significant type in a given <see cref="Base.speckle_type"/></returns>
public static string SimplifiedSpeckleType(Base speckleObject)
{
return speckleObject.speckle_type.Split(':')[^1];
}
public static string GetAssetSuffix(Type nativeType)
{
if (nativeType == typeof(Material)) return ".mat";
@@ -108,7 +83,7 @@ namespace Speckle.ConnectorUnity.NativeCache
return ".asset";
}
[Obsolete("use " + nameof(GenerateObjectName))]
[Obsolete("use " + nameof(CoreUtils.GenerateObjectName))]
public static string GetObjectName(Base speckleObject)
{
string objectName = speckleObject["name"] as string ?? speckleObject.speckle_type.Split(':').Last();
@@ -1,7 +1,7 @@
{
"name": "Speckle.ConnectorUnity.NativeCache",
"rootNamespace": "Speckle.ConnectorUnity",
"references": [],
"references": ["GUID:50d889142fdf9de4b8501c6eaa4b3225"],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
@@ -7,10 +7,10 @@
"targets": {
".NETStandard,Version=v2.0": {},
".NETStandard,Version=v2.0/": {
"Objects/2.1.1": {
"Objects/2.0.999-local": {
"dependencies": {
"NETStandard.Library": "2.0.3",
"Speckle.Core": "2.1.0"
"Speckle.Core": "2.0.999-local"
},
"runtime": {
"Objects.dll": {}
@@ -541,7 +541,7 @@
}
},
"libraries": {
"Objects/2.1.1": {
"Objects/2.0.999-local": {
"type": "project",
"serviceable": false,
"sha512": ""
File diff suppressed because it is too large Load Diff
@@ -1,14 +1,15 @@
#nullable enable
using Speckle.Core.Kits;
using Speckle.Core.Logging;
using Speckle.Core.Models;
namespace Speckle.ConnectorUnity
namespace Speckle.ConnectorUnity.Utils
{
public static class CoreUtils
{
public static void SetupInit()
{
SpeckleLog.Initialize(HostApplications.Unity.Slug, HostApplications.Unity.GetVersion(CoreUtils.GetHostAppVersion()));
Setup.Init(HostApplications.Unity.GetVersion(CoreUtils.GetHostAppVersion()), HostApplications.Unity.Slug);
Setup.Init(HostApplications.Unity.GetVersion(GetHostAppVersion()), HostApplications.Unity.Slug);
}
public static HostAppVersion GetHostAppVersion()
@@ -31,6 +32,30 @@ namespace Speckle.ConnectorUnity
return HostAppVersion.v;
#endif
}
public const string ObjectNameSeparator = " -- ";
/// <param name="speckleObject">The object to be named</param>
/// <returns>A human-readable Object name unique to the given <paramref name="speckleObject"/></returns>
public static string GenerateObjectName(Base speckleObject)
{
var prefix = GetFriendlyObjectName(speckleObject) ?? SimplifiedSpeckleType(speckleObject);
return $"{prefix}{ObjectNameSeparator}{speckleObject.id}";
}
public static string? GetFriendlyObjectName(Base speckleObject)
{
return speckleObject["name"] as string
?? speckleObject["Name"] as string
?? speckleObject["family"] as string;
}
/// <param name="speckleObject"></param>
/// <returns>The most significant type in a given <see cref="Base.speckle_type"/></returns>
public static string SimplifiedSpeckleType(Base speckleObject)
{
return speckleObject.speckle_type.Split(':')[^1];
}
}
}
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c62e8ef712f821d418d1a8a248e75df3
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,61 @@
#nullable enable
using Speckle.Core.Kits;
using Speckle.Core.Logging;
using Speckle.Core.Models;
namespace Speckle.ConnectorUnity.Utils
{
public static class CoreUtils
{
public static void SetupInit()
{
Setup.Init(HostApplications.Unity.GetVersion(GetHostAppVersion()), HostApplications.Unity.Slug);
}
public static HostAppVersion GetHostAppVersion()
{
#if UNITY_2019
return HostAppVersion.v2019;
#elif UNITY_2020
return HostAppVersion.v2020;
#elif UNITY_2021
return HostAppVersion.v2021;
#elif UNITY_2022
return HostAppVersion.v2022;
#elif UNITY_2023
return HostAppVersion.v2023;
#elif UNITY_2024
return HostAppVersion.v2024;
#elif UNITY_2025
return HostAppVersion.v2025;
#else
return HostAppVersion.v;
#endif
}
public const string ObjectNameSeparator = " -- ";
/// <param name="speckleObject">The object to be named</param>
/// <returns>A human-readable Object name unique to the given <paramref name="speckleObject"/></returns>
public static string GenerateObjectName(Base speckleObject)
{
var prefix = GetFriendlyObjectName(speckleObject) ?? SimplifiedSpeckleType(speckleObject);
return $"{prefix}{ObjectNameSeparator}{speckleObject.id}";
}
public static string? GetFriendlyObjectName(Base speckleObject)
{
return speckleObject["name"] as string
?? speckleObject["Name"] as string
?? speckleObject["family"] as string;
}
/// <param name="speckleObject"></param>
/// <returns>The most significant type in a given <see cref="Base.speckle_type"/></returns>
public static string SimplifiedSpeckleType(Base speckleObject)
{
return speckleObject.speckle_type.Split(':')[^1];
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 280d43bfe5dc1d14181295ae5c365183
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,28 @@
#nullable enable
using System.Linq;
using Speckle.Core.Models;
namespace Speckle.ConnectorUnity.SpeckleUtils
{
/// <summary>
/// Extension methods for <see cref="Base"/> object models
/// </summary>
public static class ObjectExtensions
{
/// <summary>
/// Sets a property dynamically, checking if an instance prop with the same name exists
/// </summary>
/// <param name="speckleObject"></param>
/// <param name="propertyName"></param>
/// <param name="value"></param>
#pragma warning disable CS0618
public static void SetDetachedPropertyChecked(this Base speckleObject, string propertyName, object? value)
{
if(speckleObject.GetInstanceMembersNames().Any(name => name == propertyName))
speckleObject[propertyName] = value;
else
speckleObject[$"@{propertyName}"] = value;
}
#pragma warning restore CS0618
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f88837bf55bd20642a1aed6b2a3b6b81
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,14 @@
{
"name": "Speckle.ConnectorUnity.SpeckleUtils",
"rootNamespace": "Speckle.ConnectorUnity",
"references": [],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": true
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: e71520b51b4847d45ab0b1dacbf935c5
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d74d43435d909534d9b12f5ed3758603
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,155 @@
#nullable enable
using System;
using System.Collections;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Networking;
namespace Speckle.ConnectorUnity.UnityUtils
{
public static class EngineUtils
{
public static void SafeDestroy(UnityEngine.Object obj)
{
if (Application.isPlaying)
UnityEngine.Object.Destroy(obj);
else
UnityEngine.Object.DestroyImmediate(obj);
}
public static bool Valid(this string name) => !string.IsNullOrEmpty(name);
public static Mesh SafeMeshGet(this MeshFilter mf) => Application.isPlaying ? mf.mesh : mf.sharedMesh;
public static void SafeMeshSet(this GameObject go, Mesh m, Material[] materials, bool addMeshComponentsIfNotFound = true)
{
MeshFilter? mf = go.GetComponent<MeshFilter>();
MeshRenderer? mr = go.GetComponent<MeshRenderer>();
if (addMeshComponentsIfNotFound)
{
if (mf == null)
mf = go.AddComponent<MeshFilter>();
if (mr == null)
mr = go.AddComponent<MeshRenderer>();
}
if (Application.isPlaying)
{
mf.mesh = m;
mr.materials = materials;
}
else
{
mf.sharedMesh = m;
mr.sharedMaterials = materials;
}
}
/// <summary>
/// Converts a Unity color to an ARBG int
/// </summary>
public static int ToIntColor(this Color c)
{
return
System.Drawing.Color
.FromArgb(Convert.ToInt32(c.r * 255), Convert.ToInt32(c.g * 255), Convert.ToInt32(c.b * 255))
.ToArgb();
}
/// <summary>
/// Converts a ARGB formatted int to a Unity Color
/// </summary>
public static Color ToUnityColor(this int c)
{
var argb = System.Drawing.Color.FromArgb(c);
return new Color(argb.R / 255f, argb.G / 255f, argb.B / 255f);
}
public static IEnumerator GetImageRoutine(string url, string authToken, Action<Texture2D?> callback)
{
using UnityWebRequest www = UnityWebRequestTexture.GetTexture(url);
www.SetRequestHeader("Authorization", $"Bearer {authToken}");
UnityWebRequestAsyncOperation request = www.SendWebRequest();
yield return request;
if(www.result != UnityWebRequest.Result.Success )
{
bool isDataError = www.result == UnityWebRequest.Result.DataProcessingError;
string error = isDataError
? $"{www.result}: {www.downloadHandler.error}"
: www.error;
Debug.LogWarning( $"Error fetching image from {www.url}: {error}" );
yield break;
}
Texture2D? texture = DownloadHandlerTexture.GetContent(www);
callback.Invoke(texture);
}
/// <summary>
/// Coroutine <see cref="CustomYieldInstruction"/> that starts and waits for an async <see cref="System.Threading.Tasks.Task"/>
/// to complete.
/// </summary>
/// <remarks>Useful for running async tasks from coroutines</remarks>
public class WaitForTask : CustomYieldInstruction
{
public readonly Task Task;
public override bool keepWaiting => !Task.IsCompleted;
public WaitForTask(Func<Task> function)
{
Task = Task.Run(function);
}
}
/// <inheritdoc cref="WaitForTask"/>
public sealed class WaitForTask<TResult> : CustomYieldInstruction
{
public readonly Task<TResult> Task;
public TResult Result => Task.Result;
public override bool keepWaiting => !Task.IsCompleted;
public WaitForTask(Func<Task<TResult>> function)
{
this.Task = System.Threading.Tasks.Task.Run(function);
}
}
public sealed class PerformanceThrottle : IEnumerator
{
private readonly IEnumerator _routine;
public PerformanceThrottle(IEnumerator routine)
{
_routine = routine;
}
public bool MoveNext()
{
while (_routine.MoveNext())
{
_ = 0;
}
return false;
}
public void Reset()
{
_routine.Reset();
}
public object? Current => _routine.Current;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 98317fb9e5c090c46a7802a177a3c691
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,14 @@
{
"name": "Speckle.ConnectorUnity.Utils.Unity",
"rootNamespace": "Speckle.ConnectorUnity.Utils.Unity",
"references": [],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 13c1dc1fefb5ca44aa3d755118da5f6c
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,9 +1,10 @@
using System;
#nullable enable
using System;
using System.Collections;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Networking;
#nullable enable
namespace Speckle.ConnectorUnity.Utils
{
public static class Utils
@@ -93,5 +94,34 @@ namespace Speckle.ConnectorUnity.Utils
Texture2D? texture = DownloadHandlerTexture.GetContent(www);
callback.Invoke(texture);
}
/// <summary>
/// Coroutine <see cref="CustomYieldInstruction"/> that starts and waits for an async <see cref="System.Threading.Tasks.Task"/>
/// to complete.
/// </summary>
/// <remarks>Useful for running async tasks from coroutines</remarks>
public class WaitForTask : CustomYieldInstruction
{
public readonly Task Task;
public override bool keepWaiting => !Task.IsCompleted;
public WaitForTask(Func<Task> function)
{
Task = Task.Run(function);
}
}
/// <inheritdoc cref="WaitForTask"/>
public sealed class WaitForTask<TResult> : CustomYieldInstruction
{
public readonly Task<TResult> Task;
public TResult Result => Task.Result;
public override bool keepWaiting => !Task.IsCompleted;
public WaitForTask(Func<Task<TResult>> function)
{
this.Task = System.Threading.Tasks.Task.Run(function);
}
}
}
}
@@ -10,9 +10,9 @@ namespace Speckle.ConnectorUnity.Wrappers.Selection
public sealed class BranchSelection : OptionSelection<Branch>
{
[field: SerializeField, Range(1,100), Tooltip("Number of branches to request")]
public int BranchesLimit { get; set; } = 30;
public int BranchesLimit { get; set; } = 100;
[field: SerializeField, Range(1,100), Tooltip("Number of commits to request")]
public int CommitsLimit { get; set; } = 15;
public int CommitsLimit { get; set; } = 25;
[field: SerializeReference]
public StreamSelection StreamSelection { get; private set; }
@@ -34,7 +34,7 @@ namespace Speckle.ConnectorUnity.Wrappers.Selection
{
get
{
if (Options == null) return null;
if (Options is null) return null;
if (SelectedIndex < 0 || SelectedIndex >= Options.Length) return null;
return Options[SelectedIndex];
}
@@ -52,7 +52,7 @@ namespace Speckle.ConnectorUnity.Wrappers.Selection
protected void GenerateOptions(IList<TOption> source, Func<TOption, int, bool> isDefault)
{
List<TOption> optionsToAdd = new List<TOption>(source.Count);
List<TOption> optionsToAdd = new (source.Count);
int defaultOption = -1;
int index = 0;
foreach (TOption? a in source)
@@ -77,4 +77,4 @@ namespace Speckle.ConnectorUnity.Wrappers.Selection
OnSelectionChange?.Invoke();
}
}
}
}
@@ -4,6 +4,8 @@ using System.Collections.Generic;
using System.Collections.Specialized;
using Speckle.Core.Api;
using Speckle.Core.Models;
using Speckle.Core.Serialisation;
using Speckle.Newtonsoft.Json;
using UnityEngine;
namespace Speckle.ConnectorUnity.Wrappers
@@ -18,8 +20,10 @@ namespace Speckle.ConnectorUnity.Wrappers
[SerializeField, HideInInspector]
private string _serializedData = "";
[SerializeField, HideInInspector]
private bool _hasChanged;
private ObservableConcurrentDictionary<string, object> _data;
public IDictionary<string, object> Data
@@ -40,10 +44,7 @@ namespace Speckle.ConnectorUnity.Wrappers
private string _serializedSpeckleType;
private Type _speckleType = typeof(Base);
public Type SpeckleType {
get
{
return _speckleType ??= typeof(Base);
}
get => _speckleType ??= typeof(Base);
set
{
@@ -80,7 +81,9 @@ namespace Speckle.ConnectorUnity.Wrappers
public void OnAfterDeserialize()
{
Base speckleData = Operations.Deserialize(_serializedData);
var deserializer = new BaseObjectDeserializerV2();
Base speckleData = deserializer.Deserialize(_serializedData);
Data = speckleData.GetMembers();
_hasChanged = false;
@@ -96,7 +99,7 @@ namespace Speckle.ConnectorUnity.Wrappers
}
[Serializable]
private class SpeckleData : Base
private sealed class SpeckleData : Base
{
public SpeckleData(IDictionary<string, object> data)
{
@@ -107,4 +110,4 @@ namespace Speckle.ConnectorUnity.Wrappers
}
}
}
}
}
@@ -1,6 +1,6 @@
{
"name": "systems.speckle.speckle-unity",
"version": "2.13.0",
"version": "2.14.2",
"displayName": "Speckle Unity Connector",
"description": "AEC Interoperability for Unity through Speckle",
"unity": "2018.4",