55 Commits

Author SHA1 Message Date
Claire Kuang c567228c4d Merge pull request #123 from specklesystems/claire/cnx-699-update-github-links-to-point-to-v3
Update README.md to align with main github page
2024-11-01 18:56:12 +00:00
Claire Kuang a3e2998841 Update README.md to align with main github page 2024-11-01 18:55:51 +00:00
Jedd Morgan 1adaca1bbe 2.20 package version (#122)
* Bump Sdk

* Fixed compiler errors

* Bump version
2024-08-08 16:32:05 +01:00
Jedd Morgan 9cc1c1f6bf Bump Core Deps to 2.20 (#120)
* Bump Sdk

* Fixed compiler errors
2024-08-08 14:31:07 +01:00
Jedd Morgan 2da9c917c9 Merge pull request #121 from specklesystems/dev
chore(domains): deprecate speckle.xyz for app.speckle.systems (#119)
2024-08-08 14:27:19 +01:00
Iain Sproat 1446e6f665 chore(domains): deprecate speckle.xyz for app.speckle.systems (#119) 2024-07-18 17:20:50 +02:00
Jedd Morgan 725a9823ea Updated deps to 2.19 (#118)
* Updated deps to 2.19

* 2.19
2024-05-27 17:25:50 +01:00
Jedd Morgan c4c363e33f Bump 2.18.3 (#117) 2024-04-16 17:01:39 +01:00
Jedd Morgan cf7e72aa7d Merge pull request #115 from specklesystems/JR-Morgan-patch-1
Update README.md
2024-03-19 18:44:21 +00:00
Jedd Morgan 695a25af51 Update README.md 2024-03-19 18:44:09 +00:00
Jedd Morgan 63d83d0044 Merge pull request #114 from specklesystems/JR-Morgan-patch-1
2.18.1.json
2024-03-19 18:43:04 +00:00
Jedd Morgan db88d0dc41 2.18.1.json 2024-03-19 18:42:52 +00:00
Jedd Morgan 5359731dce Merge pull request #111 from specklesystems/dev
* 2.18 Update

* fixed bug with selection not correctly restoring after deserializing

* Bump core to 2.18.0-rc

* FE2 terms (#112)

* FE2 terms

* SendComponent

* Removed terminology switching

* Bumped core version (#113)
2024-03-14 10:53:50 +00:00
Jedd Morgan 790e5d8294 Bumped core version (#113) 2024-03-14 10:51:39 +00:00
Jedd Morgan 00c4a43c3a FE2 terms (#112)
* FE2 terms

* SendComponent

* Removed terminology switching
2024-03-14 10:41:07 +00:00
Jedd Morgan 8e32a0214e Bump core to 2.18.0-rc 2024-02-29 11:25:45 +00:00
Jedd Morgan ab5d4c2fba fixed bug with selection not correctly restoring after deserializing 2024-02-28 14:05:18 +00:00
Jedd Morgan 1b2eeed3eb 2.18 Update 2024-02-22 18:31:09 +00:00
Jedd Morgan edeec70993 2.17.1 (#110)
* Bump core to 2.17.0-rc

* feat!(selection): selection now indexes based on ID (#107)

* bump package version (#108)

* Bumped core to 2.17.1

* Account id to include hash of server info
2024-02-08 23:01:21 +00:00
Jedd Morgan ce33e7c454 Merge pull request #105 from specklesystems/jrm/core/2.16.0
Updated core and objects to 2.16.0
2023-10-31 12:18:13 +00:00
Jedd Morgan fb1e458970 Bumped version connector package semver 2023-10-31 12:17:15 +00:00
Jedd Morgan 2755a9abd7 Fix compiler errors for 2.16 core/objects bump 2023-10-31 12:15:13 +00:00
Jedd Morgan ee9795e39f Bump Core + Objects 2023-10-31 11:52:47 +00:00
Jedd Morgan 999e6ae4ea Merge pull request #103 from specklesystems/jrm/update-docs
Updated SpeckleReceiver.cs
2023-09-26 22:46:37 +01:00
Jedd Morgan 8df96eeca4 Updated SpeckleReceiver.cs 2023-09-26 22:45:44 +01:00
Jedd Morgan 6aa92d4c57 Merge pull request #102 from specklesystems/doc-comments
Update SpeckleReceiver.cs
2023-09-26 22:18:41 +01:00
Jedd Morgan 030cb277b8 Update SpeckleReceiver.cs 2023-09-26 22:18:28 +01:00
Jedd Morgan 5ee498afce Update SpeckleReceiver.cs 2023-09-26 22:17:28 +01:00
Jedd Morgan 0bb1591624 Merge pull request #99 from specklesystems/jrm/unity/speckle-properties-example
Added manual speckle properties example
2023-09-12 14:01:18 +01:00
Jedd Morgan 5dd889c898 Update package.json 2023-09-12 13:59:30 +01:00
Jedd Morgan 9c7d1deb0a Added speckle properties example 2023-09-12 13:56:18 +01:00
Jedd Morgan 106d4c8e73 Merge pull request #98 from specklesystems/jrm/converter/convert-speckle-properties-on-instances
Attach Speckle Properties to instances
2023-08-31 15:41:22 +01:00
Jedd Morgan 7c8f70c0c0 Attach Speckle Properties to instances 2023-08-31 15:40:08 +01:00
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 a89e4cdfe7 wip implementation of traversal refactor 2023-04-21 22:34:59 +01:00
97 changed files with 7049 additions and 5259 deletions
@@ -0,0 +1,62 @@
using System.Collections;
using System.Collections.Generic;
using Objects.Converter.Unity;
using Speckle.ConnectorUnity.Utils;
using Speckle.ConnectorUnity.Wrappers;
using Speckle.Core.Api;
using Speckle.Core.Credentials;
using Speckle.Core.Models;
using Speckle.Core.Transports;
using UnityEngine;
/// <summary>
/// Example script for grabbing speckle properties for a specific object "on-the-fly"
/// </summary>
/// <remarks>
/// see discussion https://speckle.community/t/reloading-assemblies-takes-too-long/6708
/// </remarks>
[AddComponentMenu("Speckle/Extras/" + nameof(AttachSpecklePropertiesExample))]
public class AttachSpecklePropertiesExample : MonoBehaviour
{
public string streamId;
public string objectId;
public virtual void Start()
{
Client speckleClient = new(AccountManager.GetDefaultAccount()!);
StartCoroutine(AttachSpeckleProperties(speckleClient, streamId, objectId));
}
public IEnumerator AttachSpeckleProperties(
Client speckleClient,
string streamId,
string objectId
)
{
//Fetch the object from Speckle
ServerTransport remoteTransport = new(speckleClient.Account, streamId);
Utils.WaitForTask<Base> operation =
new(async () => await Operations.Receive(objectId, remoteTransport));
//yield until task completes
yield return operation;
Base speckleObject = operation.Result;
//Do something with the properties. e.g. attach SpeckleProperties component
DoSomething(speckleObject);
}
protected virtual void DoSomething(Base speckleObject)
{
//GetProperties will filter "useful" properties
Dictionary<string, object> properties = ConverterUnity.GetProperties(
speckleObject,
typeof(SpeckleObject)
);
var sd = this.gameObject.AddComponent<SpeckleProperties>();
sd.Data = properties;
sd.SpeckleType = speckleObject.GetType();
}
}
@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 2dd598fed5008c44a815ba09e81a2d19
guid: b5627857f30c8994c87469d287b2d115
MonoImporter:
externalObjects: {}
serializedVersion: 2
+68
View File
@@ -0,0 +1,68 @@
using System.Collections;
using System.Threading.Tasks;
using Speckle.ConnectorUnity;
using Speckle.ConnectorUnity.Components;
using Speckle.Core.Api;
using Speckle.Core.Api.GraphQL.Models;
using Speckle.Core.Credentials;
using Speckle.Core.Models;
using Speckle.Core.Transports;
using UnityEngine;
[AddComponentMenu("Speckle/Extras/Manual Receiver")]
[RequireComponent(typeof(RecursiveConverter))]
public class ManualReceive : MonoBehaviour
{
public string authToken;
public string serverUrl;
public string streamId,
objectId;
private RecursiveConverter receiver;
void Awake()
{
receiver = GetComponent<RecursiveConverter>();
}
IEnumerator Start()
{
Debug.developerConsoleVisible = true;
if (Time.timeSinceLevelLoad > 20)
yield return null;
Receive();
}
[ContextMenu(nameof(Receive))]
public void Receive()
{
var account = new Account()
{
token = authToken,
serverInfo = new ServerInfo() { url = serverUrl },
};
Task.Run(async () =>
{
using ServerTransport transport = new(account, streamId);
MemoryTransport localTransport = new();
Base speckleObject = await Operations.Receive(
objectId,
remoteTransport: transport,
localTransport: localTransport
);
Dispatcher
.Instance()
.Enqueue(() =>
{
var parentObject = new GameObject(name);
receiver.RecursivelyConvertToNative_Sync(speckleObject, parentObject.transform);
Debug.Log($"Receive {objectId} completed");
});
});
}
}
-65
View File
@@ -1,65 +0,0 @@
using System.Collections;
using System.Threading.Tasks;
using Speckle.ConnectorUnity;
using Speckle.ConnectorUnity.Components;
using Speckle.Core.Api;
using Speckle.Core.Credentials;
using Speckle.Core.Transports;
using UnityEngine;
[RequireComponent(typeof(RecursiveConverter))]
public class ManualReceive : MonoBehaviour
{
public string authToken;
public string serverUrl;
public string streamId, objectId;
private RecursiveConverter receiver;
void Awake()
{
receiver = GetComponent<RecursiveConverter>();
}
IEnumerator Start()
{
Debug.developerConsoleVisible = true;
if(Time.timeSinceLevelLoad > 20) yield return null;
Receive();
}
public void Receive()
{
var account = new Account()
{
token = authToken,
serverInfo = new ServerInfo() {url = serverUrl},
};
Task.Run(async () =>
{
var transport = new ServerTransport(account, streamId);
var localTransport = new MemoryTransport();
var @base = await Operations.Receive(
objectId,
remoteTransport: transport,
localTransport: localTransport,
onErrorAction: (m, e)=> Debug.LogError(m + e),
disposeTransports: true
);
Dispatcher.Instance().Enqueue(() =>
{
var parentObject = new GameObject(name);
receiver.RecursivelyConvertToNative(@base, parentObject.transform);
Debug.Log($"Receive {objectId} completed");
});
});
}
}
+22 -17
View File
@@ -2,6 +2,7 @@ using System.Threading;
using System.Threading.Tasks;
using Speckle.ConnectorUnity.Components;
using Speckle.Core.Api;
using Speckle.Core.Credentials;
using Speckle.Core.Models;
using Speckle.Core.Transports;
using UnityEngine;
@@ -18,24 +19,25 @@ namespace Extra
{
[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}");
}
@@ -44,20 +46,23 @@ namespace Extra
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,
Client client = sender.Account.Client!;
Stream stream = sender.Stream.Selected;
Account selectedAccount = sender.Account.Selected!;
using ServerTransport transport = new(selectedAccount, stream!.id);
string branchId = await client.BranchCreate(
new BranchCreateInput() { streamId = stream.id, name = branchName }
);
return await SpeckleSender.SendDataAsync(
remoteTransport: transport,
data: data,
client: client!,
branchName: branchName,
createCommit: true,
onProgressAction: null,
onErrorAction: (m, e) => throw e);
data,
client,
branchId,
true
);
}
}
@@ -68,7 +73,7 @@ namespace Extra
public override async void OnInspectorGUI()
{
DrawDefaultInspector();
if (GUILayout.Button("Create and send"))
{
await ((PerformanceTestSender)target).SendIterations();
+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);
}
}
+19 -3
View File
@@ -1,4 +1,20 @@
{
"name": "Speckle.Extra",
"references":[ "GUID:eed1b8b83e2c0074d9e5de2348e3ff72", "GUID:e6adfdc4e436206479f48eafc82f32b5", "GUID:d274441ecc3eb3f43b093eec1503d681" ]
}
"name": "Speckle.Extra",
"rootNamespace": "",
"references": [
"GUID:eed1b8b83e2c0074d9e5de2348e3ff72",
"GUID:e6adfdc4e436206479f48eafc82f32b5",
"GUID:d274441ecc3eb3f43b093eec1503d681",
"GUID:50d889142fdf9de4b8501c6eaa4b3225",
"GUID:7383cd71541a2aa48a7baf23f74b4d5f"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}
+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 -114
View File
@@ -1,132 +1,131 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using Speckle.Core.Credentials;
using System.Linq;
using UnityEngine.Events;
using UnityEngine.UI;
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:
selectedId:
- rid: 1485638386691080199
type: {class: StreamSelection, ns: Speckle.ConnectorUnity.Wrappers.Selection, asm: Speckle.ConnectorUnity.Wrappers}
data:
selectedId:
<StreamsLimit>k__BackingField: 50
<AccountSelection>k__BackingField:
rid: 1485638386691080198
- rid: 1485638386691080200
type: {class: BranchSelection, ns: Speckle.ConnectorUnity.Wrappers.Selection, asm: Speckle.ConnectorUnity.Wrappers}
data:
selectedId:
<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:
selectedId:
- rid: 1485638386691080195
type: {class: StreamSelection, ns: Speckle.ConnectorUnity.Wrappers.Selection, asm: Speckle.ConnectorUnity.Wrappers}
data:
selectedId:
<StreamsLimit>k__BackingField: 50
<AccountSelection>k__BackingField:
rid: 1485638386691080194
- rid: 1485638386691080196
type: {class: BranchSelection, ns: Speckle.ConnectorUnity.Wrappers.Selection, asm: Speckle.ConnectorUnity.Wrappers}
data:
selectedId:
<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:
selectedId:
<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
@@ -1135,6 +1162,18 @@ Transform:
m_Father: {fileID: 0}
m_RootOrder: 4
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &300223686
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 &310693430
GameObject:
m_ObjectHideFlags: 0
@@ -1616,18 +1655,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
@@ -2616,6 +2643,19 @@ Rigidbody:
m_Interpolate: 0
m_Constraints: 0
m_CollisionDetection: 0
--- !u!114 &1242741158
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 &1279250012
GameObject:
m_ObjectHideFlags: 0
@@ -3019,6 +3059,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: 1242741158}
- {fileID: 300223686}
--- !u!1 &1464556211
GameObject:
m_ObjectHideFlags: 0
@@ -3599,21 +3654,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 +3766,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
@@ -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,46 @@
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;
namespace Speckle.ConnectorUnity.Tests
{
[TestFixture, TestOf(typeof(RecursiveConverter))]
public class ConvertToNativeTests : ComponentTest<RecursiveConverter>
{
private static IEnumerable<string> TestCases()
{
yield return @"https://latest.speckle.systems/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
}
@@ -1,6 +1,6 @@
fileFormatVersion: 2
guid: 6940276f6a5ab054d93a08a3133be230
TextScriptImporter:
guid: 79301723eb79d2745ab1e1a9360f6f2d
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
@@ -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:
+6 -6
View File
@@ -1,14 +1,14 @@
{
"dependencies": {
"com.unity.2d.sprite": "1.0.0",
"com.unity.collab-proxy": "2.0.1",
"com.unity.ide.rider": "3.0.18",
"com.unity.ide.visualstudio": "2.0.17",
"com.unity.collab-proxy": "2.0.7",
"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.toolchain.win-x86_64-linux-x86_64": "2.0.2",
"com.unity.timeline": "1.6.5",
"com.unity.toolchain.win-x86_64-linux-x86_64": "2.0.4",
"com.unity.ugui": "1.0.0",
"com.unity.modules.ai": "1.0.0",
"com.unity.modules.androidjni": "1.0.0",
+11 -11
View File
@@ -7,7 +7,7 @@
"dependencies": {}
},
"com.unity.collab-proxy": {
"version": "2.0.1",
"version": "2.0.7",
"depth": 0,
"source": "registry",
"dependencies": {},
@@ -21,7 +21,7 @@
"url": "https://packages.unity.com"
},
"com.unity.ide.rider": {
"version": "3.0.18",
"version": "3.0.24",
"depth": 0,
"source": "registry",
"dependencies": {
@@ -30,7 +30,7 @@
"url": "https://packages.unity.com"
},
"com.unity.ide.visualstudio": {
"version": "2.0.17",
"version": "2.0.18",
"depth": 0,
"source": "registry",
"dependencies": {
@@ -46,23 +46,23 @@
"url": "https://packages.unity.com"
},
"com.unity.sysroot": {
"version": "2.0.3",
"version": "2.0.5",
"depth": 1,
"source": "registry",
"dependencies": {},
"url": "https://packages.unity.com"
},
"com.unity.sysroot.linux-x86_64": {
"version": "2.0.2",
"version": "2.0.4",
"depth": 1,
"source": "registry",
"dependencies": {
"com.unity.sysroot": "2.0.3"
"com.unity.sysroot": "2.0.5"
},
"url": "https://packages.unity.com"
},
"com.unity.test-framework": {
"version": "1.1.31",
"version": "1.1.33",
"depth": 0,
"source": "registry",
"dependencies": {
@@ -82,7 +82,7 @@
"url": "https://packages.unity.com"
},
"com.unity.timeline": {
"version": "1.6.4",
"version": "1.6.5",
"depth": 0,
"source": "registry",
"dependencies": {
@@ -94,12 +94,12 @@
"url": "https://packages.unity.com"
},
"com.unity.toolchain.win-x86_64-linux-x86_64": {
"version": "2.0.2",
"version": "2.0.4",
"depth": 0,
"source": "registry",
"dependencies": {
"com.unity.sysroot": "2.0.3",
"com.unity.sysroot.linux-x86_64": "2.0.2"
"com.unity.sysroot": "2.0.5",
"com.unity.sysroot.linux-x86_64": "2.0.4"
},
"url": "https://packages.unity.com"
},
@@ -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,125 @@
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 SerializedProperty _accountSelection;
private SerializedProperty _streamSelection;
private SerializedProperty _branchSelection;
private SerializedProperty _commitSelection;
#nullable enable
private static bool _generateAssets;
private bool _foldOutStatus = true;
private Texture2D? _previewImage;
public override async void OnInspectorGUI()
{
var speckleReceiver = (SpeckleReceiver)target;
//Selection
EditorGUILayout.PropertyField(_accountSelection);
EditorGUILayout.PropertyField(_streamSelection, new GUIContent("Project"));
EditorGUILayout.PropertyField(_branchSelection, new GUIContent("Model"));
EditorGUILayout.PropertyField(_commitSelection, new GUIContent("Version"));
//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 Model",
"Fetching data from Speckle",
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();
_accountSelection = serializedObject.FindProperty(
$"<{nameof(SpeckleReceiver.Account)}>k__BackingField"
);
_streamSelection = serializedObject.FindProperty(
$"<{nameof(SpeckleReceiver.Stream)}>k__BackingField"
);
_branchSelection = serializedObject.FindProperty(
$"<{nameof(SpeckleReceiver.Branch)}>k__BackingField"
);
_commitSelection = serializedObject.FindProperty(
$"<{nameof(SpeckleReceiver.Commit)}>k__BackingField"
);
}
public void Reset()
{
Init();
@@ -30,7 +127,7 @@ namespace Speckle.ConnectorUnity.Components.Editor
private void Init()
{
var speckleReceiver = (SpeckleReceiver) target;
var speckleReceiver = (SpeckleReceiver)target;
UpdatePreviewImage();
speckleReceiver.OnCommitSelectionChange.AddListener(_ => UpdatePreviewImage());
UpdateGenerateAssets();
@@ -38,161 +135,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).ConfigureAwait(false);;
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).ConfigureAwait(true);;
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().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");
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
@@ -201,10 +361,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
}
@@ -1,3 +1,4 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
@@ -9,39 +10,62 @@ using UnityEngine;
using UnityEngine.SceneManagement;
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 SerializedProperty _accountSelection;
private SerializedProperty _streamSelection;
private SerializedProperty _branchSelection;
#nullable enable
private SelectionFilter _selectedFilter = SelectionFilter.Children;
public void OnEnable()
{
_accountSelection = serializedObject.FindProperty(
$"<{nameof(SpeckleSender.Account)}>k__BackingField"
);
_streamSelection = serializedObject.FindProperty(
$"<{nameof(SpeckleSender.Stream)}>k__BackingField"
);
_branchSelection = serializedObject.FindProperty(
$"<{nameof(SpeckleSender.Branch)}>k__BackingField"
);
}
public override async void OnInspectorGUI()
{
//Draw events in a collapsed region
DrawDefaultInspector();
//Selection
EditorGUILayout.PropertyField(_accountSelection);
EditorGUILayout.PropertyField(_streamSelection, new GUIContent("Project"));
EditorGUILayout.PropertyField(_branchSelection, new GUIContent("Model"));
bool shouldSend = GUILayout.Button("Send!");
selectedFilter = (SelectionFilter)EditorGUILayout.EnumPopup("Selection", selectedFilter);
_selectedFilter = (SelectionFilter)
EditorGUILayout.EnumPopup("Selection", _selectedFilter);
if (shouldSend)
{
await ConvertAndSend();
@@ -50,30 +74,34 @@ 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);
Debug.LogWarning("Nothing to send", speckleSender);
return null;
}
}
return await speckleSender.SendDataAsync(data, true);
}
@@ -81,37 +109,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;
}
}
}
@@ -2,7 +2,6 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Sentry;
using Speckle.Core.Api;
using Speckle.Core.Credentials;
using Speckle.Core.Kits;
@@ -20,10 +19,10 @@ 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; } = 75;
@@ -98,7 +97,6 @@ namespace Speckle.ConnectorUnity.Components.Editor
private List<Branch> Branches
{
get => _streamManager.Branches;
set => _streamManager.Branches = value;
}
@@ -142,7 +140,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 +157,6 @@ namespace Speckle.ConnectorUnity.Components.Editor
EditorUtility.ClearProgressBar();
}
private async Task Receive()
{
var transport = new ServerTransport(SelectedAccount, SelectedStream.id);
@@ -163,49 +164,83 @@ 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
});
}
catch (Exception e)
{
throw new SpeckleException(e.Message, e, true, SentryLevel.Error);
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
}
);
}
finally
{
@@ -215,8 +250,7 @@ namespace Speckle.ConnectorUnity.Components.Editor
public override async void OnInspectorGUI()
{
_streamManager = (StreamManager) target;
_streamManager = (StreamManager)target;
#region Account GUI
@@ -226,12 +260,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 +284,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 +332,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 +363,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 +397,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();
@@ -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,66 +46,75 @@ 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);
}
public override void BeginWrite()
{
base.BeginWrite();
//AssetDatabase.StartAssetEditing();
return _readCache.TrySaveObject(speckleObject, nativeObject);
}
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 +123,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}");
}
}
}
}
@@ -1,6 +1,7 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Globalization;
using Speckle.Core.Api;
using Speckle.Core.Credentials;
using UnityEditor;
@@ -13,8 +14,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 +32,78 @@ 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),
("Project 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(CultureInfo.InvariantCulture)),
("Updated at", s => s.updatedAt.ToString(CultureInfo.InvariantCulture)),
};
}
}
[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>)[]
{
("Model Id", s => s.id),
("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),
("Version Id", s => s.id),
("Author Name", s => s.authorName),
("Created At", s => s.createdAt),
("Created At", s => s.createdAt.ToString(CultureInfo.InvariantCulture)),
("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,10 +112,10 @@ namespace Speckle.ConnectorUnity.Wrappers.Selection.Editor
protected string[] properties = { };
protected (string, Func<TOption, string>)[] details = { };
private string[] GetFormattedOptions(TOption[] options)
private string[] GetFormattedOptions(IReadOnlyList<TOption> options)
{
int optionsCount = options.Length;
int optionsCount = options.Count;
string[] choices = new string[optionsCount];
for (int i = 0; i < optionsCount; i++)
{
@@ -118,7 +125,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 +154,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.Selected = t.Options[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 +261,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)) };
for (int i = 0; i < listItems.Length; i++)
{
SearchTreeEntry entry = new SearchTreeEntry(new GUIContent(listItems[i]))
{
@@ -237,18 +276,15 @@ namespace Speckle.ConnectorUnity.Wrappers.Selection.Editor
};
searchList.Add(entry);
}
return searchList;
}
public bool OnSelectEntry(SearchTreeEntry SearchTreeEntry, SearchWindowContext context)
public bool OnSelectEntry(SearchTreeEntry searchTreeEntry, SearchWindowContext context)
{
onSetIndexCallback?.Invoke((int)SearchTreeEntry.userData);
onSetIndexCallback?.Invoke((int)searchTreeEntry.userData);
return true;
}
}
}
@@ -18,87 +18,110 @@ namespace Speckle.ConnectorUnity.Wrappers.Editor
{
private static readonly string[] SpeckleTypeOptionStrings;
private static readonly Type[] SpeckleTypeOptions;
private static HashSet<string> ArrayFoldoutState = new();
private static bool instancePropFoldoutState = true;
private static bool dynamicPropFoldoutState = true;
private static bool isEditMode = false;
private static bool isEditMode;
static SpecklePropertiesEditor()
{
var options = typeof(Mesh).Assembly
.GetTypes()
.Where(x => x.IsSubclassOf(typeof(Base)) && !x.IsAbstract).ToList();
.Where(x => x.IsSubclassOf(typeof(Base)) && !x.IsAbstract)
.ToList();
var strings = options
.Where(x => x.FullName != null)
.Select(x => x.FullName!.Replace('.', '/'));
SpeckleTypeOptionStrings = strings.Append(nameof(Base)).ToArray();
SpeckleTypeOptions = options.Append(typeof(Base)).ToArray();
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();
Debug.Assert(SpeckleTypeOptions.Length == SpeckleTypeOptionStrings.Length);
}
private static GUILayoutOption[] propLayoutOptions = { GUILayout.ExpandWidth(true) };
public override void OnInspectorGUI()
{
SpeckleProperties properties = (SpeckleProperties)target;
//Edit Mode
isEditMode = EditorGUILayout.ToggleLeft("Enable Inspector Edit Mode (experimental)", isEditMode);
isEditMode = EditorGUILayout.ToggleLeft(
"Enable Inspector Edit Mode (experimental)",
isEditMode
);
if (isEditMode)
{
GUILayout.Label(
"Modifying properties through the inspector is experimental and can lead to invalid objects, proceed at your own risk!",
EditorStyles.helpBox);
EditorStyles.helpBox
);
GUILayout.Space(10);
}
GUI.enabled = isEditMode;
// SpeckleType
GUILayout.Label("Speckle Type: ", EditorStyles.boldLabel );
GUILayout.Label("Speckle Type: ", EditorStyles.boldLabel);
var oldIndex = Array.IndexOf(SpeckleTypeOptions, properties.SpeckleType);
var speckleTypeSelectedIndex = EditorGUILayout.Popup(oldIndex, SpeckleTypeOptionStrings);
if(oldIndex != speckleTypeSelectedIndex && speckleTypeSelectedIndex >= 0)
var speckleTypeSelectedIndex = EditorGUILayout.Popup(
oldIndex,
SpeckleTypeOptionStrings
);
if (oldIndex != speckleTypeSelectedIndex && speckleTypeSelectedIndex >= 0)
{
properties.SpeckleType = SpeckleTypeOptions[speckleTypeSelectedIndex];
}
// Instance Properties
var InstancePropertyNames = DynamicBase.GetInstanceMembersNames(properties.SpeckleType);
instancePropFoldoutState = EditorGUILayout.Foldout(instancePropFoldoutState, "Instance Properties: ", EditorStyles.foldoutHeader);
var instancePropertyNames =
(IReadOnlyCollection<string>)
DynamicBase.GetInstanceMembersNames(properties.SpeckleType);
instancePropFoldoutState = EditorGUILayout.Foldout(
instancePropFoldoutState,
"Instance Properties: ",
EditorStyles.foldoutHeader
);
if (instancePropFoldoutState)
{
foreach (var propName in InstancePropertyNames)
foreach (var propName in instancePropertyNames)
{
if (!properties.Data.TryGetValue(propName, out object? existingValue)) continue;
if (!properties.Data.TryGetValue(propName, out object? existingValue))
continue;
var newValue = CreateField(existingValue, propName, propLayoutOptions);
if(newValue != existingValue)
if (newValue != existingValue)
properties.Data[propName] = newValue;
}
}
GUILayout.Space(10);
dynamicPropFoldoutState = EditorGUILayout.Foldout(dynamicPropFoldoutState, "Dynamic Properties:", EditorStyles.foldoutHeader);
dynamicPropFoldoutState = EditorGUILayout.Foldout(
dynamicPropFoldoutState,
"Dynamic Properties:",
EditorStyles.foldoutHeader
);
if (dynamicPropFoldoutState)
{
var ignoreSet = InstancePropertyNames.ToImmutableHashSet();
var ignoreSet = instancePropertyNames.ToImmutableHashSet();
foreach (var kvp in properties.Data)
{
if (ignoreSet.Contains(kvp.Key)) continue;
if (ignoreSet.Contains(kvp.Key))
continue;
var existingValue = kvp.Value;
var newValue = CreateField(existingValue, kvp.Key, propLayoutOptions);
if(newValue != existingValue)
if (newValue != existingValue)
properties.Data[kvp.Key] = newValue;
GUILayout.Space(10);
}
}
@@ -114,13 +137,18 @@ namespace Speckle.ConnectorUnity.Wrappers.Editor
_ => CreateFieldPrimitive(v, propName, options),
};
if (ret != null) return ret;
EditorGUILayout.TextField(propName, v == null? "NULL" : v.ToString());
if (ret != null)
return ret;
EditorGUILayout.TextField(propName, v == null ? "NULL" : v.ToString());
return v;
}
private static object? CreateFieldPrimitive(object? v, string propName, params GUILayoutOption[] options)
private static object? CreateFieldPrimitive(
object? v,
string propName,
params GUILayoutOption[] options
)
{
return v switch
{
@@ -131,11 +159,19 @@ namespace Speckle.ConnectorUnity.Wrappers.Editor
string s => EditorGUILayout.TextField(propName, s, options),
bool b => EditorGUILayout.Toggle(propName, b, options),
Enum e => EditorGUILayout.EnumPopup(propName, e, options),
Point p => PointToVector3(EditorGUILayout.Vector3Field(propName, new Vector3((float)p.x, (float)p.z, (float)p.z), options), p),
Point p
=> PointToVector3(
EditorGUILayout.Vector3Field(
propName,
new Vector3((float)p.x, (float)p.z, (float)p.z),
options
),
p
),
_ => null,
};
}
private static Point PointToVector3(Vector3 vector, Point p)
{
p.x = vector.x;
@@ -143,10 +179,13 @@ namespace Speckle.ConnectorUnity.Wrappers.Editor
p.z = vector.z;
return p;
}
private IList ArrayField(string propName, IList list, params GUILayoutOption[] options)
{
bool isExpanded = EditorGUILayout.Foldout(ArrayFoldoutState.Contains(propName), propName);
bool isExpanded = EditorGUILayout.Foldout(
ArrayFoldoutState.Contains(propName),
propName
);
if (isExpanded)
{
ArrayFoldoutState.Add(propName);
@@ -154,10 +193,13 @@ namespace Speckle.ConnectorUnity.Wrappers.Editor
{
object? item = list[i];
var r = CreateFieldPrimitive(item, i.ToString(), options);
if (r == null)
{
EditorGUILayout.TextField(i.ToString(), item == null? "NULL" : item.ToString());
EditorGUILayout.TextField(
i.ToString(),
item == null ? "NULL" : item.ToString()
);
continue;
}
//Update list item
@@ -171,6 +213,5 @@ namespace Speckle.ConnectorUnity.Wrappers.Editor
return list;
}
}
}
@@ -1,189 +1,199 @@
using Speckle.Core.Api;
using Speckle.Core.Api.SubscriptionModels;
using Speckle.Core.Credentials;
using Speckle.Core.Logging;
using Speckle.Core.Transports;
using System;
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.Api;
using Speckle.Core.Api.SubscriptionModels;
using Speckle.Core.Credentials;
using Speckle.Core.Kits;
using Speckle.Core.Logging;
using Speckle.Core.Transports;
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("See " + nameof(SpeckleReceiver))]
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 () =>
{
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
);
});
}
});
}
#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!
}
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);
});
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
}
}
}
@@ -1,149 +1,157 @@
using Speckle.Core.Api;
using Speckle.Core.Credentials;
using Speckle.Core.Logging;
using Speckle.Core.Models;
using Speckle.Core.Transports;
using System;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Sentry;
using Speckle.ConnectorUnity.Components;
using Speckle.Core.Api;
using Speckle.Core.Credentials;
using Speckle.Core.Kits;
using Speckle.Core.Logging;
using Speckle.Core.Models;
using Speckle.Core.Transports;
using UnityEngine;
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("See " + nameof(SpeckleSender))]
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>
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
)
{
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
);
}
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(
new CommitCreateInput
{
streamId = remoteTransport.StreamId,
branchName = branchName,
objectId = res,
message = $"Sent {count} objects from Unity",
sourceApplication = HostApplications.Unity.Name,
totalChildrenCount = (int)count,
},
cancellationToken
);
}
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,178 @@
using System;
using System.Collections;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Speckle.Core.Api;
using Speckle.Core.Api.GraphQL.Models;
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),
_tokenSource.Token
);
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),
_tokenSource.Token
);
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
),
_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,63 +2,264 @@
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();
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 convertableObjects = traversalFunc.Traverse(rootObject)
.Where(tc => ConverterInstance.CanConvertToNative(tc.current));
var objectsToConvert = traversalFunc
.Traverse(rootObject)
.Where(x => ConverterInstance.CanConvertToNative(x.Current))
.Where(x => userPredicate(x));
foreach (TraversalContext tc in convertableObjects)
Dictionary<Base, GameObject?> created = new();
foreach (var conversionResult in ConvertTree(objectsToConvert, parent, created))
{
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;
if (!isActiveAndEnabled)
throw new InvalidOperationException(
$"Cannot convert objects while {GetType()} is disabled"
);
yield return go;
yield return conversionResult;
}
}
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)
/// <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))
{
@@ -66,7 +267,7 @@ namespace Speckle.ConnectorUnity.Components
yield return null;
}
}
/// <summary>
/// Given <paramref name="o"/>,
/// will recursively convert any objects in the tree
@@ -74,15 +275,21 @@ 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
)
{
InitializeAssetCache();
var createdGameObjects = new List<GameObject>();
try
{
@@ -95,10 +302,9 @@ namespace Speckle.ConnectorUnity.Components
}
//TODO track event?
return createdGameObjects;
return createdGameObjects;
}
private void InitializeAssetCache()
@@ -111,11 +317,28 @@ namespace Speckle.ConnectorUnity.Components
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))
converted = ConverterInstance.ConvertToNative(baseObject);
if (predicate(baseObject))
{
try
{
converted = ConverterInstance.ConvertToNative(baseObject);
}
catch (Exception ex) when (!ex.IsFatal())
{
Debug.LogWarning(
$"Failed to convert {baseObject.speckle_type} - {baseObject.id}\n{ex}"
);
}
}
// Handle new GameObjects
Transform? nextParent = parent;
@@ -123,22 +346,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 = AssetHelpers.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);
@@ -147,32 +371,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
)
{
foreach (Base b in GraphTraversal.TraverseMember(value))
{
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
}
}
}
@@ -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();
}
}
}
}
@@ -6,7 +6,8 @@
"GUID:eed1b8b83e2c0074d9e5de2348e3ff72",
"GUID:13aec21e8e96f864bafd00df49f225fc",
"GUID:d274441ecc3eb3f43b093eec1503d681",
"GUID:50d889142fdf9de4b8501c6eaa4b3225"
"GUID:50d889142fdf9de4b8501c6eaa4b3225",
"GUID:7383cd71541a2aa48a7baf23f74b4d5f"
],
"includePlatforms": [],
"excludePlatforms": [],
@@ -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,144 +29,301 @@ 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;
}
/// <inheritdoc cref="ReceiveAndConvert_Async"/>
/// <example>
/// This function is designed to run as a coroutine i.e.
/// <c>StartCoroutine(mySpeckleReceiver.ReceiveAndConvert_Routine());</c>
/// </example>
public IEnumerator ReceiveAndConvert_Routine(
Transform? parent = null,
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();
}
/// <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 async void ReceiveAndConvert_Async(
Transform? parent = null,
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;
}
/// <summary>
/// Gets the current selection
/// </summary>
/// <param name="client">The selected Account's Client</param>
/// <param name="stream">The selected <see cref="Stream"/></param>
/// <param name="commit">The selected <see cref="Commit"/></param>
/// <exception cref="InvalidOperationException">Selection was not complete or invalid</exception>
public void ValidateSelection(out Client client, out Stream stream, out Commit commit)
{
Client? selectedClient = Account.Client;
client =
selectedClient ?? throw new InvalidOperationException("Invalid Speckle account selection");
Stream? selectedStream = Stream.Selected;
stream =
selectedStream ?? throw new InvalidOperationException("Invalid Speckle project selection");
Commit? selectedCommit = Commit.Selected;
commit =
selectedCommit ?? throw new InvalidOperationException("Invalid Speckle version 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)
).ConfigureAwait(false);
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>
/// <returns></returns>
public static async Task<Base?> ReceiveAsync(CancellationToken token,
/// <param name="cancellationToken"></param>
/// <exception cref="Exception">Throws various types of exceptions to indicate faliure</exception>
/// <returns>The requested Speckle object</returns>
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;
using var transport = new ServerTransport(client.Account, streamId);
transport.CancellationToken = cancellationToken;
cancellationToken.ThrowIfCancellationRequested();
Base requestedObject = await Operations
.Receive(
objectId,
transport,
null,
onProgressAction,
onTotalChildrenCountKnown,
cancellationToken
)
.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?.authorId != null && commit?.authorId != client.Account?.userInfo?.id
},
}
);
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(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}");
}
await client
.CommitReceived(
new CommitReceivedInput
{
streamId = streamId,
commitId = commit?.id,
message = $"received commit from {Application.unityVersion}",
sourceApplication = HostApplications.Unity.GetVersion(
CoreUtils.GetHostAppVersion()
)
},
cancellationToken
)
.ConfigureAwait(false);
}
catch (Exception e)
catch (Exception ex)
{
onErrorAction?.Invoke(e.Message, e);
// Do nothing!
Debug.LogWarning($"Failed to send read receipt\n{ex}");
}
return ret;
return requestedObject;
}
/// <summary>
/// Helper method for using <see cref="RecursiveConverter"/>.
/// Creates blank GameObjects for each property/category of the root object.
@@ -171,8 +332,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);
@@ -180,10 +351,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())
@@ -191,52 +361,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;
@@ -244,51 +418,57 @@ 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(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")]
[ContextMenu("Open Speckle Model in Browser")]
protected void OpenUrlInBrowser()
{
string url = GetSelectedUrl();
Application.OpenURL(url);
Uri url = GetSelectedUrl();
Application.OpenURL(url.ToString());
}
#endif
public string GetSelectedUrl()
{
string serverUrl = Account.Selected!.serverInfo.url;
string? streamId = Stream.Selected?.id;
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}";
return $"{serverUrl}/streams/{streamId}";
public Uri GetSelectedUrl()
{
Account selectedAccount = Account.Selected!;
StreamWrapper sw =
new()
{
ServerUrl = selectedAccount.serverInfo.url,
StreamId = Stream.Selected!.id,
BranchName = Branch.Selected?.id,
CommitId = Commit.Selected?.id
};
sw.SetAccount(selectedAccount);
return sw.ToServerUri();
}
public void Awake()
{
Converter = GetComponent<RecursiveConverter>();
@@ -305,32 +485,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 { Count: > 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(ReceiveAndConvert_Routine), 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,132 +23,183 @@ 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;
[Obsolete("No longer used")]
[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);
using ServerTransport transport = new(client.Account, stream.id);
transport.CancellationToken = CancellationTokenSource.Token;
return await SendDataAsync(CancellationTokenSource.Token,
return await SendDataAsync(
remoteTransport: transport,
data: data,
client: client,
branchName: branch.name,
createCommit: createCommit,
onProgressAction: dict => OnSendProgressAction.Invoke(dict),
onErrorAction: (m, e) => OnErrorAction.Invoke(m, e)
data,
client,
branch.id,
createCommit,
dict => OnSendProgressAction.Invoke(dict),
CancellationTokenSource.Token
);
}
public static async Task<string> SendDataAsync(CancellationToken cancellationToken,
/// <param name="remoteTransport">The transport to send to</param>
/// <param name="data">The data to send</param>
/// <param name="client">An authenticated Speckle Client</param>
/// <param name="branchId">The branch name or id</param>
/// <param name="createCommit">when <see langword="true"/> will call <see cref="Client.CommitCreate"/>, otherwise only the object data is sent</param>
/// <param name="onProgressAction">Called every progress tick of the <see cref="Operations.Send"/></param>
/// <param name="cancellationToken">Optional cancellation token</param>
/// <returns>The id (hash) of the object sent</returns>
public static async Task<string> SendDataAsync(
ServerTransport remoteTransport,
Base data,
Client client,
string branchName,
string branchId,
bool createCommit,
Action<ConcurrentDictionary<string, int>>? onProgressAction = null,
Action<string, Exception>? onErrorAction = null)
CancellationToken cancellationToken = default
)
{
string res = await Operations.Send(
data,
cancellationToken: cancellationToken,
new List<ITransport>{remoteTransport},
useDefaultCache: true,
disposeTransports: true,
onProgressAction: onProgressAction,
onErrorAction: onErrorAction
remoteTransport,
true,
onProgressAction,
cancellationToken
);
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 url = $"{client.ServerUrl}/streams/{streamId}/commits/{commitId}";
string commitId = await CreateCommit(
data,
client,
streamId,
branchId,
res,
commitMessage,
cancellationToken
);
StreamWrapper sw =
new()
{
ServerUrl = client.ServerUrl,
StreamId = streamId,
BranchName = branchId,
CommitId = commitId,
};
sw.SetAccount(client.Account);
string url = sw.ToServerUri().GetLeftPart(UriPartial.Path);
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(
Base data,
Client client,
string streamId,
string branchName,
string objectId,
string message)
string message,
CancellationToken cancellationToken
)
{
string commitId = await client.CommitCreate(cancellationToken,
string commitId = await client.CommitCreate(
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,
});
},
cancellationToken
);
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 +207,7 @@ namespace Speckle.ConnectorUnity.Components
error = null;
return true;
}
#if UNITY_EDITOR
[ContextMenu("Open Speckle Stream in Browser")]
protected void OpenUrlInBrowser()
@@ -165,24 +216,26 @@ 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()
{
Initialise(true);
Converter = GetComponent<RecursiveConverter>();
}
protected void Initialise(bool forceRefresh = false)
{
CoreUtils.SetupInit();
@@ -193,24 +246,47 @@ 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 { Count: > 0 } || forceRefresh)
Account.RefreshOptions();
}
public void OnDestroy()
{
CancellationTokenSource?.Cancel();
CancellationTokenSource?.Dispose();
}
public void OnBeforeSerialize()
{
//pass
}
public void OnAfterDeserialize()
{
Initialise();
}
[Obsolete("use other overload")]
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
)
{
return await SendDataAsync(
remoteTransport,
data,
client,
branchName,
createCommit,
onProgressAction,
cancellationToken
);
}
}
}
@@ -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,7 +4,6 @@ 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;
@@ -23,7 +22,6 @@ namespace Objects.Converter.Unity
{
public partial class ConverterUnity
{
#region helper methods
@@ -34,32 +32,42 @@ 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>
/// <param name="arr">flat list of x,y,z values</param>
/// <param name="units"></param>
/// <returns></returns>
public Vector3[] ArrayToPoints(IList<double> arr, string units)
/// <exception cref="ArgumentException">Length of <paramref name="arr"/> must be a multiple of 3</exception>
public Vector3[] ArrayToPoints(IReadOnlyList<double> arr, string units)
{
if (arr.Count % 3 != 0) throw new Exception("Array malformed: length%3 != 0.");
if (arr.Count % 3 != 0)
{
throw new ArgumentException(
"Array malformed: length not a multiple of 3",
nameof(arr)
);
}
Vector3[] points = new Vector3[arr.Count / 3];
var f = Speckle.Core.Kits.Units.GetConversionFactor(units, ModelUnits);
double 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);
}
return points;
}
@@ -68,19 +76,12 @@ namespace Objects.Converter.Unity
#region ToSpeckle
//TODO: more of these
/// <summary>
///
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
[Obsolete("", true)]
public virtual Point PointToSpeckle(Vector3 p)
{
//switch y and z
return new Point(p.x, p.z, p.y);
}
#endregion
@@ -89,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?
@@ -115,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[] { 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>
@@ -127,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;
@@ -157,37 +162,63 @@ namespace Objects.Converter.Unity
var go = NewPointBasedGameObject(points, curve.speckle_type);
return go;
}
public Dictionary<string, object?> GetProperties(Base o) => GetProperties(o, typeof(Base));
/// <summary>Gets all properties of <paramref name="o"/> except ignored ones. <br/>
/// Ignored properties include properties of <see cref="Base"/>
/// And some hardcoded ones that are likely to be converted (such as material, elements, and name)
/// </summary>
/// <param name="o">The speckle object to grab properties from</param>
/// <returns>The properties</returns>
/// <remarks>If you don't want to filter any properties, simply use <see cref="Base.GetMembers"/></remarks>
public static Dictionary<string, object?> GetProperties(Base o) =>
GetProperties(o, typeof(Base));
public Dictionary<string, object?> GetProperties(Base o, Type excludeType)
/// <summary>
/// Gets all properties of <paramref name="o"/> except ignored ones.<br/>
/// Ignored properties include properties of <paramref name="excludeType"/>
/// And some hardcoded ones that are likely to be converted (such as material, elements, and name)
/// </summary>
/// <inheritdoc cref="GetProperties(Base)"/>
/// <param name="excludeType">A <see cref="Type"/> whose properties should be ignored</param>
public static 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)
{
excludeProps.Add(alias);
}
excludeProps.Add("@Materials");
excludeProps.Add("@Views");
excludeProps.Add("renderMaterial");
excludeProps.Add("typedDefinition");
excludeProps.Add("definition");
excludeProps.Add("geometry");
excludeProps.Add("elements");
excludeProps.Add("transform");
excludeProps.Add("name");
//excludeProps.Add("tag");
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)
{
@@ -195,7 +226,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)
{
@@ -212,17 +243,14 @@ 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;
}
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))
if (LoadedAssets.TryGetObject(instance.definition, out GameObject? existingGo))
{
var go = InstantiateCopy(existingGo);
go.name = defName;
@@ -231,28 +259,37 @@ 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);
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(instance.definition, meshes, out Mesh? nativeMesh, out _))
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);
}
@@ -263,74 +300,82 @@ 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(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;
AttachSpeckleProperties(native, instance.GetType(), () => GetProperties(instance));
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)
{
var sf = Speckle.Core.Kits.Units.GetConversionFactor(speckleTransform.units, ModelUnits);
var smatrix = speckleTransform.matrix;
var sf = GetConversionFactor(speckleTransform.units);
var sMatrix = speckleTransform.matrix;
return new Matrix4x4
{
// Left (X -> X)
[0, 0] = smatrix.M11,
[2, 0] = smatrix.M21,
[1, 0] = smatrix.M31,
[3, 0] = smatrix.M41,
[0, 0] = (float)sMatrix.M11,
[2, 0] = (float)sMatrix.M21,
[1, 0] = (float)sMatrix.M31,
[3, 0] = (float)sMatrix.M41,
//Up (Z -> Y)
[0, 2] = smatrix.M12,
[2, 2] = smatrix.M22,
[1, 2] = smatrix.M32,
[3, 2] = smatrix.M42,
[0, 2] = (float)sMatrix.M12,
[2, 2] = (float)sMatrix.M22,
[1, 2] = (float)sMatrix.M32,
[3, 2] = (float)sMatrix.M42,
//Forwards (Y -> Z)
[0, 1] = smatrix.M13,
[2, 1] = smatrix.M23,
[1, 1] = smatrix.M33,
[3, 1] = smatrix.M43,
[0, 1] = (float)sMatrix.M13,
[2, 1] = (float)sMatrix.M23,
[1, 1] = (float)sMatrix.M33,
[3, 1] = (float)sMatrix.M43,
//Translation
[0, 3] = (float) (smatrix.M14 * sf),
[2, 3] = (float) (smatrix.M24 * sf),
[1, 3] = (float) (smatrix.M34 * sf),
[3, 3] = smatrix.M44,
[0, 3] = (float)(sMatrix.M14 * sf),
[2, 3] = (float)(sMatrix.M24 * sf),
[1, 3] = (float)(sMatrix.M34 * sf),
[3, 3] = (float)sMatrix.M44,
};
}
@@ -342,16 +387,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,31 +3,22 @@ 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;
using Material = UnityEngine.Material;
using Mesh = UnityEngine.Mesh;
using SMesh = Objects.Geometry.Mesh;
using SColor = System.Drawing.Color;
using Transform = UnityEngine.Transform;
using STransform = Objects.Other.Transform;
#nullable enable
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 +28,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 +42,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 +135,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 +159,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 +170,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 +181,11 @@ 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);
}
var go = new GameObject();
go.transform.position = center;
go.SafeMeshSet(nativeMesh, nativeMaterials);
@@ -235,21 +193,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 +211,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 convert the mesh data anyway just to get the center... eek
MeshDataToNative(meshes,
out List<Vector3> verts,
out _,
out _,
out _);
MeshDataToNative(meshes, out List<Vector3> verts, out _, out _, out _);
center = CalculateBounds(verts).center;
return true;
}
@@ -276,30 +231,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 +268,6 @@ namespace Objects.Converter.Unity
nativeMesh.SetUVs(0, uvs);
nativeMesh.SetColors(vertexColors);
int j = 0;
foreach (var subMeshTriangles in subMeshes)
{
@@ -326,12 +283,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 +299,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 +328,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 +337,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 +368,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 +385,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.GenerateObjectName(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,163 @@
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;
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:
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"};
public static IList<string> DisplayValuePropertyAliases { get; set; } =
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)
{
@@ -172,6 +202,18 @@ namespace Objects.Converter.Unity
return objects.Select(x => ConvertToNative(x)).ToList();
}
public object ConvertToNativeDisplayable(Base @object)
{
throw new NotImplementedException(
$"{nameof(ConvertToNativeDisplayable)} is not implemented by this converter, use {nameof(ConvertToNative)} instead"
);
}
public bool CanConvertToNativeDisplayable(Base @object)
{
throw new NotImplementedException();
}
public bool CanConvertToSpeckle(object @object)
{
switch (@object)
@@ -183,6 +225,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 +252,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 +276,4 @@ namespace Objects.Converter.Unity
}
}
}
}
}
@@ -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: 6faf057a8f503f44287177f8e2094e3e
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:
@@ -1,928 +0,0 @@
{
"runtimeTarget": {
"name": ".NETStandard,Version=v2.0/",
"signature": ""
},
"compilationOptions": {},
"targets": {
".NETStandard,Version=v2.0": {},
".NETStandard,Version=v2.0/": {
"SpeckleCore2/2.0.999-local": {
"dependencies": {
"GraphQL.Client": "5.1.1",
"Microsoft.CSharp": "4.7.0",
"Microsoft.Data.Sqlite": "7.0.3",
"NETStandard.Library": "2.0.3",
"Polly": "7.2.3",
"Polly.Contrib.WaitAndRetry": "1.1.1",
"Polly.Extensions.Http": "3.0.0",
"Sentry": "3.29.0",
"Sentry.Serilog": "3.29.0",
"Serilog": "2.12.0",
"Serilog.Enrichers.ClientInfo": "1.2.0",
"Serilog.Enrichers.GlobalLogContext": "3.0.0",
"Serilog.Exceptions": "8.4.0",
"Serilog.Sinks.Console": "4.1.0",
"Serilog.Sinks.Seq": "5.2.2",
"Speckle.Newtonsoft.Json": "13.0.2"
},
"runtime": {
"SpeckleCore2.dll": {}
}
},
"GraphQL.Client/5.1.1": {
"dependencies": {
"GraphQL.Client.Abstractions": "5.1.1",
"GraphQL.Client.Abstractions.Websocket": "5.1.1",
"System.Reactive": "5.0.0"
},
"runtime": {
"lib/netstandard2.0/GraphQL.Client.dll": {
"assemblyVersion": "5.1.1.0",
"fileVersion": "5.1.1.0"
}
}
},
"GraphQL.Client.Abstractions/5.1.1": {
"dependencies": {
"GraphQL.Primitives": "5.1.1"
},
"runtime": {
"lib/netstandard2.0/GraphQL.Client.Abstractions.dll": {
"assemblyVersion": "5.1.1.0",
"fileVersion": "5.1.1.0"
}
}
},
"GraphQL.Client.Abstractions.Websocket/5.1.1": {
"dependencies": {
"GraphQL.Client.Abstractions": "5.1.1"
},
"runtime": {
"lib/netstandard2.0/GraphQL.Client.Abstractions.Websocket.dll": {
"assemblyVersion": "5.1.1.0",
"fileVersion": "5.1.1.0"
}
}
},
"GraphQL.Primitives/5.1.1": {
"runtime": {
"lib/netstandard2.0/GraphQL.Primitives.dll": {
"assemblyVersion": "5.1.1.0",
"fileVersion": "5.1.1.0"
}
}
},
"Microsoft.AspNetCore.Http/2.1.1": {
"dependencies": {
"Microsoft.AspNetCore.Http.Abstractions": "2.1.1",
"Microsoft.AspNetCore.WebUtilities": "2.1.1",
"Microsoft.Extensions.ObjectPool": "2.1.1",
"Microsoft.Extensions.Options": "2.1.1",
"Microsoft.Net.Http.Headers": "2.1.1"
},
"runtime": {
"lib/netstandard2.0/Microsoft.AspNetCore.Http.dll": {
"assemblyVersion": "2.1.1.0",
"fileVersion": "2.1.1.18157"
}
}
},
"Microsoft.AspNetCore.Http.Abstractions/2.1.1": {
"dependencies": {
"Microsoft.AspNetCore.Http.Features": "2.1.1",
"System.Text.Encodings.Web": "5.0.1"
},
"runtime": {
"lib/netstandard2.0/Microsoft.AspNetCore.Http.Abstractions.dll": {
"assemblyVersion": "2.1.1.0",
"fileVersion": "2.1.1.18157"
}
}
},
"Microsoft.AspNetCore.Http.Features/2.1.1": {
"dependencies": {
"Microsoft.Extensions.Primitives": "2.1.1"
},
"runtime": {
"lib/netstandard2.0/Microsoft.AspNetCore.Http.Features.dll": {
"assemblyVersion": "2.1.1.0",
"fileVersion": "2.1.1.18157"
}
}
},
"Microsoft.AspNetCore.WebUtilities/2.1.1": {
"dependencies": {
"Microsoft.Net.Http.Headers": "2.1.1",
"System.Text.Encodings.Web": "5.0.1"
},
"runtime": {
"lib/netstandard2.0/Microsoft.AspNetCore.WebUtilities.dll": {
"assemblyVersion": "2.1.1.0",
"fileVersion": "2.1.1.18157"
}
}
},
"Microsoft.Bcl.AsyncInterfaces/5.0.0": {
"dependencies": {
"System.Threading.Tasks.Extensions": "4.5.4"
},
"runtime": {
"lib/netstandard2.0/Microsoft.Bcl.AsyncInterfaces.dll": {
"assemblyVersion": "5.0.0.0",
"fileVersion": "5.0.20.51904"
}
}
},
"Microsoft.CSharp/4.7.0": {
"runtime": {
"lib/netstandard2.0/Microsoft.CSharp.dll": {
"assemblyVersion": "4.0.5.0",
"fileVersion": "4.700.19.56404"
}
}
},
"Microsoft.Data.Sqlite/7.0.3": {
"dependencies": {
"Microsoft.Data.Sqlite.Core": "7.0.3",
"SQLitePCLRaw.bundle_e_sqlite3": "2.1.4"
}
},
"Microsoft.Data.Sqlite.Core/7.0.3": {
"dependencies": {
"SQLitePCLRaw.core": "2.1.4"
},
"runtime": {
"lib/netstandard2.0/Microsoft.Data.Sqlite.dll": {
"assemblyVersion": "7.0.3.0",
"fileVersion": "7.0.323.6302"
}
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions/2.1.1": {
"runtime": {
"lib/netstandard2.0/Microsoft.Extensions.DependencyInjection.Abstractions.dll": {
"assemblyVersion": "2.1.1.0",
"fileVersion": "2.1.1.18157"
}
}
},
"Microsoft.Extensions.ObjectPool/2.1.1": {
"runtime": {
"lib/netstandard2.0/Microsoft.Extensions.ObjectPool.dll": {
"assemblyVersion": "2.1.1.0",
"fileVersion": "2.1.1.18157"
}
}
},
"Microsoft.Extensions.Options/2.1.1": {
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "2.1.1",
"Microsoft.Extensions.Primitives": "2.1.1"
},
"runtime": {
"lib/netstandard2.0/Microsoft.Extensions.Options.dll": {
"assemblyVersion": "2.1.1.0",
"fileVersion": "2.1.1.18157"
}
}
},
"Microsoft.Extensions.Primitives/2.1.1": {
"dependencies": {
"System.Memory": "4.5.4",
"System.Runtime.CompilerServices.Unsafe": "5.0.0"
},
"runtime": {
"lib/netstandard2.0/Microsoft.Extensions.Primitives.dll": {
"assemblyVersion": "2.1.1.0",
"fileVersion": "2.1.1.18157"
}
}
},
"Microsoft.Net.Http.Headers/2.1.1": {
"dependencies": {
"Microsoft.Extensions.Primitives": "2.1.1",
"System.Buffers": "4.5.1"
},
"runtime": {
"lib/netstandard2.0/Microsoft.Net.Http.Headers.dll": {
"assemblyVersion": "2.1.1.0",
"fileVersion": "2.1.1.18157"
}
}
},
"Microsoft.NETCore.Platforms/1.1.0": {},
"Microsoft.NETCore.Targets/1.1.0": {},
"NETStandard.Library/2.0.3": {
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0"
}
},
"Polly/7.2.3": {
"runtime": {
"lib/netstandard2.0/Polly.dll": {
"assemblyVersion": "7.0.0.0",
"fileVersion": "7.2.3.0"
}
}
},
"Polly.Contrib.WaitAndRetry/1.1.1": {
"runtime": {
"lib/netstandard2.0/Polly.Contrib.WaitAndRetry.dll": {
"assemblyVersion": "1.0.0.0",
"fileVersion": "1.1.1.0"
}
}
},
"Polly.Extensions.Http/3.0.0": {
"dependencies": {
"Polly": "7.2.3"
},
"runtime": {
"lib/netstandard2.0/Polly.Extensions.Http.dll": {
"assemblyVersion": "3.0.0.0",
"fileVersion": "3.0.0.0"
}
}
},
"Sentry/3.29.0": {
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
"System.Buffers": "4.5.1",
"System.Reflection.Metadata": "5.0.0",
"System.Text.Json": "5.0.2",
"System.Threading.Tasks.Extensions": "4.5.4"
},
"runtime": {
"lib/netstandard2.0/Sentry.dll": {
"assemblyVersion": "3.29.0.0",
"fileVersion": "3.29.0.0"
}
}
},
"Sentry.Serilog/3.29.0": {
"dependencies": {
"Sentry": "3.29.0",
"Serilog": "2.12.0"
},
"runtime": {
"lib/netstandard2.0/Sentry.Serilog.dll": {
"assemblyVersion": "3.29.0.0",
"fileVersion": "3.29.0.0"
}
}
},
"Serilog/2.12.0": {
"runtime": {
"lib/netstandard2.0/Serilog.dll": {
"assemblyVersion": "2.0.0.0",
"fileVersion": "2.12.0.0"
}
}
},
"Serilog.Enrichers.ClientInfo/1.2.0": {
"dependencies": {
"Microsoft.AspNetCore.Http": "2.1.1",
"Serilog": "2.12.0"
},
"runtime": {
"lib/netstandard2.0/Serilog.Enrichers.ClientInfo.dll": {
"assemblyVersion": "0.0.0.0",
"fileVersion": "0.0.0.0"
}
}
},
"Serilog.Enrichers.GlobalLogContext/3.0.0": {
"dependencies": {
"Serilog": "2.12.0"
},
"runtime": {
"lib/netstandard2.0/Serilog.Enrichers.GlobalLogContext.dll": {
"assemblyVersion": "3.0.0.0",
"fileVersion": "3.0.0.0"
}
}
},
"Serilog.Exceptions/8.4.0": {
"dependencies": {
"Serilog": "2.12.0",
"System.Reflection.TypeExtensions": "4.7.0"
},
"runtime": {
"lib/netstandard2.0/Serilog.Exceptions.dll": {
"assemblyVersion": "8.0.0.0",
"fileVersion": "8.4.0.0"
}
}
},
"Serilog.Formatting.Compact/1.1.0": {
"dependencies": {
"Serilog": "2.12.0"
},
"runtime": {
"lib/netstandard2.0/Serilog.Formatting.Compact.dll": {
"assemblyVersion": "1.1.0.0",
"fileVersion": "1.1.0.0"
}
}
},
"Serilog.Sinks.Console/4.1.0": {
"dependencies": {
"Serilog": "2.12.0"
},
"runtime": {
"lib/netstandard2.0/Serilog.Sinks.Console.dll": {
"assemblyVersion": "4.1.0.0",
"fileVersion": "4.1.0.0"
}
}
},
"Serilog.Sinks.File/5.0.0": {
"dependencies": {
"Serilog": "2.12.0"
},
"runtime": {
"lib/netstandard2.0/Serilog.Sinks.File.dll": {
"assemblyVersion": "5.0.0.0",
"fileVersion": "5.0.0.0"
}
}
},
"Serilog.Sinks.PeriodicBatching/3.1.0": {
"dependencies": {
"Serilog": "2.12.0"
},
"runtime": {
"lib/netstandard2.0/Serilog.Sinks.PeriodicBatching.dll": {
"assemblyVersion": "3.0.0.0",
"fileVersion": "3.1.0.0"
}
}
},
"Serilog.Sinks.Seq/5.2.2": {
"dependencies": {
"Serilog": "2.12.0",
"Serilog.Formatting.Compact": "1.1.0",
"Serilog.Sinks.File": "5.0.0",
"Serilog.Sinks.PeriodicBatching": "3.1.0"
},
"runtime": {
"lib/netstandard2.0/Serilog.Sinks.Seq.dll": {
"assemblyVersion": "5.2.2.0",
"fileVersion": "5.2.2.0"
}
}
},
"Speckle.Newtonsoft.Json/13.0.2": {
"runtime": {
"lib/netstandard2.0/Speckle.Newtonsoft.Json.dll": {
"assemblyVersion": "11.0.0.0",
"fileVersion": "11.0.1.0"
}
}
},
"SQLitePCLRaw.bundle_e_sqlite3/2.1.4": {
"dependencies": {
"SQLitePCLRaw.lib.e_sqlite3": "2.1.4",
"SQLitePCLRaw.provider.e_sqlite3": "2.1.4"
},
"runtime": {
"lib/netstandard2.0/SQLitePCLRaw.batteries_v2.dll": {
"assemblyVersion": "2.1.4.1835",
"fileVersion": "2.1.4.1835"
}
}
},
"SQLitePCLRaw.core/2.1.4": {
"dependencies": {
"System.Memory": "4.5.4"
},
"runtime": {
"lib/netstandard2.0/SQLitePCLRaw.core.dll": {
"assemblyVersion": "2.1.4.1835",
"fileVersion": "2.1.4.1835"
}
}
},
"SQLitePCLRaw.lib.e_sqlite3/2.1.4": {},
"SQLitePCLRaw.provider.e_sqlite3/2.1.4": {
"dependencies": {
"SQLitePCLRaw.core": "2.1.4"
},
"runtime": {
"lib/netstandard2.0/SQLitePCLRaw.provider.e_sqlite3.dll": {
"assemblyVersion": "2.1.4.1835",
"fileVersion": "2.1.4.1835"
}
}
},
"System.Buffers/4.5.1": {
"runtime": {
"lib/netstandard2.0/System.Buffers.dll": {
"assemblyVersion": "4.0.3.0",
"fileVersion": "4.6.28619.1"
}
}
},
"System.Collections.Immutable/5.0.0": {
"dependencies": {
"System.Memory": "4.5.4"
},
"runtime": {
"lib/netstandard2.0/System.Collections.Immutable.dll": {
"assemblyVersion": "5.0.0.0",
"fileVersion": "5.0.20.51904"
}
}
},
"System.Memory/4.5.4": {
"dependencies": {
"System.Buffers": "4.5.1",
"System.Numerics.Vectors": "4.5.0",
"System.Runtime.CompilerServices.Unsafe": "5.0.0"
},
"runtime": {
"lib/netstandard2.0/System.Memory.dll": {
"assemblyVersion": "4.0.1.1",
"fileVersion": "4.6.28619.1"
}
}
},
"System.Numerics.Vectors/4.5.0": {
"runtime": {
"lib/netstandard2.0/System.Numerics.Vectors.dll": {
"assemblyVersion": "4.1.4.0",
"fileVersion": "4.6.26515.6"
}
}
},
"System.Reactive/5.0.0": {
"dependencies": {
"System.Runtime.InteropServices.WindowsRuntime": "4.3.0",
"System.Threading.Tasks.Extensions": "4.5.4"
},
"runtime": {
"lib/netstandard2.0/System.Reactive.dll": {
"assemblyVersion": "5.0.0.0",
"fileVersion": "5.0.0.1"
}
}
},
"System.Reflection.Metadata/5.0.0": {
"dependencies": {
"System.Collections.Immutable": "5.0.0"
},
"runtime": {
"lib/netstandard2.0/System.Reflection.Metadata.dll": {
"assemblyVersion": "5.0.0.0",
"fileVersion": "5.0.20.51904"
}
}
},
"System.Reflection.TypeExtensions/4.7.0": {
"runtime": {
"lib/netstandard2.0/System.Reflection.TypeExtensions.dll": {
"assemblyVersion": "4.1.5.0",
"fileVersion": "4.700.19.56404"
}
}
},
"System.Runtime/4.3.0": {
"dependencies": {
"Microsoft.NETCore.Platforms": "1.1.0",
"Microsoft.NETCore.Targets": "1.1.0"
}
},
"System.Runtime.CompilerServices.Unsafe/5.0.0": {
"runtime": {
"lib/netstandard2.0/System.Runtime.CompilerServices.Unsafe.dll": {
"assemblyVersion": "5.0.0.0",
"fileVersion": "5.0.20.51904"
}
}
},
"System.Runtime.InteropServices.WindowsRuntime/4.3.0": {
"dependencies": {
"System.Runtime": "4.3.0"
},
"runtime": {
"lib/netstandard1.3/System.Runtime.InteropServices.WindowsRuntime.dll": {
"assemblyVersion": "4.0.2.0",
"fileVersion": "4.6.24705.1"
}
}
},
"System.Text.Encodings.Web/5.0.1": {
"dependencies": {
"System.Buffers": "4.5.1",
"System.Memory": "4.5.4"
},
"runtime": {
"lib/netstandard2.0/System.Text.Encodings.Web.dll": {
"assemblyVersion": "5.0.0.1",
"fileVersion": "5.0.421.11614"
}
}
},
"System.Text.Json/5.0.2": {
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
"System.Buffers": "4.5.1",
"System.Memory": "4.5.4",
"System.Numerics.Vectors": "4.5.0",
"System.Runtime.CompilerServices.Unsafe": "5.0.0",
"System.Text.Encodings.Web": "5.0.1",
"System.Threading.Tasks.Extensions": "4.5.4"
},
"runtime": {
"lib/netstandard2.0/System.Text.Json.dll": {
"assemblyVersion": "5.0.0.0",
"fileVersion": "5.0.521.16609"
}
}
},
"System.Threading.Tasks.Extensions/4.5.4": {
"dependencies": {
"System.Runtime.CompilerServices.Unsafe": "5.0.0"
},
"runtime": {
"lib/netstandard2.0/System.Threading.Tasks.Extensions.dll": {
"assemblyVersion": "4.2.0.1",
"fileVersion": "4.6.28619.1"
}
}
}
}
},
"libraries": {
"SpeckleCore2/2.0.999-local": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"GraphQL.Client/5.1.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-6bfM9qU4AMcFWm4BHd2M6LE5+rLtK47/+VRtypggwb9appC8sbF58ytVBVOKpqKhKpmZERfPLGJap8O/FH3w5w==",
"path": "graphql.client/5.1.1",
"hashPath": "graphql.client.5.1.1.nupkg.sha512"
},
"GraphQL.Client.Abstractions/5.1.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-/znG7Lcz3rzG9VSCq+V2ARb63c/uIs8idGOvXyltZ32Wy570GX/I8HNUIZ1yDThmQRJ5KOGSd9Mzk37lFg49rg==",
"path": "graphql.client.abstractions/5.1.1",
"hashPath": "graphql.client.abstractions.5.1.1.nupkg.sha512"
},
"GraphQL.Client.Abstractions.Websocket/5.1.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-n1gU3GlqJ0jQceb/VEEr4c0D2vpQc5AtDwthK89+yX7VpzlhJKqE5B4RJwx//Jb33mKybfJioWwDgVfSOPAwdw==",
"path": "graphql.client.abstractions.websocket/5.1.1",
"hashPath": "graphql.client.abstractions.websocket.5.1.1.nupkg.sha512"
},
"GraphQL.Primitives/5.1.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-RlGNsv19gbz6sQwkzif9J6Jd148nuIg1kRQf2AFOLp5K00IA+pKMdJvHF5t5llDR52Rok46ynhJv/wg+ps9ZhQ==",
"path": "graphql.primitives/5.1.1",
"hashPath": "graphql.primitives.5.1.1.nupkg.sha512"
},
"Microsoft.AspNetCore.Http/2.1.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-pPDcCW8spnyibK3krpxrOpaFHf5fjV6k1Hsl6gfh77N/8gRYlLU7MOQDUnjpEwdlHmtxwJKQJNxZqVQOmJGRUw==",
"path": "microsoft.aspnetcore.http/2.1.1",
"hashPath": "microsoft.aspnetcore.http.2.1.1.nupkg.sha512"
},
"Microsoft.AspNetCore.Http.Abstractions/2.1.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-kQUEVOU4loc8CPSb2WoHFTESqwIa8Ik7ysCBfTwzHAd0moWovc9JQLmhDIHlYLjHbyexqZAlkq/FPRUZqokebw==",
"path": "microsoft.aspnetcore.http.abstractions/2.1.1",
"hashPath": "microsoft.aspnetcore.http.abstractions.2.1.1.nupkg.sha512"
},
"Microsoft.AspNetCore.Http.Features/2.1.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-VklZ7hWgSvHBcDtwYYkdMdI/adlf7ebxTZ9kdzAhX+gUs5jSHE9mZlTamdgf9miSsxc1QjNazHXTDJdVPZKKTw==",
"path": "microsoft.aspnetcore.http.features/2.1.1",
"hashPath": "microsoft.aspnetcore.http.features.2.1.1.nupkg.sha512"
},
"Microsoft.AspNetCore.WebUtilities/2.1.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-PGKIZt4+412Z/XPoSjvYu/QIbTxcAQuEFNoA1Pw8a9mgmO0ZhNBmfaNyhgXFf7Rq62kP0tT/2WXpxdcQhkFUPA==",
"path": "microsoft.aspnetcore.webutilities/2.1.1",
"hashPath": "microsoft.aspnetcore.webutilities.2.1.1.nupkg.sha512"
},
"Microsoft.Bcl.AsyncInterfaces/5.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-W8DPQjkMScOMTtJbPwmPyj9c3zYSFGawDW3jwlBOOsnY+EzZFLgNQ/UMkK35JmkNOVPdCyPr2Tw7Vv9N+KA3ZQ==",
"path": "microsoft.bcl.asyncinterfaces/5.0.0",
"hashPath": "microsoft.bcl.asyncinterfaces.5.0.0.nupkg.sha512"
},
"Microsoft.CSharp/4.7.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-pTj+D3uJWyN3My70i2Hqo+OXixq3Os2D1nJ2x92FFo6sk8fYS1m1WLNTs0Dc1uPaViH0YvEEwvzddQ7y4rhXmA==",
"path": "microsoft.csharp/4.7.0",
"hashPath": "microsoft.csharp.4.7.0.nupkg.sha512"
},
"Microsoft.Data.Sqlite/7.0.3": {
"type": "package",
"serviceable": true,
"sha512": "sha512-uumx0bb4FsN7ApP0ZoQDfSJi9c2Xen0PlXCT2BF27cM+yUMFzDEhqxR7/1/DV8ck4mYtL9yShBoOa7jeJ3736w==",
"path": "microsoft.data.sqlite/7.0.3",
"hashPath": "microsoft.data.sqlite.7.0.3.nupkg.sha512"
},
"Microsoft.Data.Sqlite.Core/7.0.3": {
"type": "package",
"serviceable": true,
"sha512": "sha512-pCmzLLWTIrIv94o7JtQ1qcPD0oc1YNY9XvlO6/tOF9YCcUfDZ3Tx9Z//CM7hFnprduHFPekif7jteBc/sXQ31Q==",
"path": "microsoft.data.sqlite.core/7.0.3",
"hashPath": "microsoft.data.sqlite.core.7.0.3.nupkg.sha512"
},
"Microsoft.Extensions.DependencyInjection.Abstractions/2.1.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-MgYpU5cwZohUMKKg3sbPhvGG+eAZ/59E9UwPwlrUkyXU+PGzqwZg9yyQNjhxuAWmoNoFReoemeCku50prYSGzA==",
"path": "microsoft.extensions.dependencyinjection.abstractions/2.1.1",
"hashPath": "microsoft.extensions.dependencyinjection.abstractions.2.1.1.nupkg.sha512"
},
"Microsoft.Extensions.ObjectPool/2.1.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-SErON45qh4ogDp6lr6UvVmFYW0FERihW+IQ+2JyFv1PUyWktcJytFaWH5zarufJvZwhci7Rf1IyGXr9pVEadTw==",
"path": "microsoft.extensions.objectpool/2.1.1",
"hashPath": "microsoft.extensions.objectpool.2.1.1.nupkg.sha512"
},
"Microsoft.Extensions.Options/2.1.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-V7lXCU78lAbzaulCGFKojcCyG8RTJicEbiBkPJjFqiqXwndEBBIehdXRMWEVU3UtzQ1yDvphiWUL9th6/4gJ7w==",
"path": "microsoft.extensions.options/2.1.1",
"hashPath": "microsoft.extensions.options.2.1.1.nupkg.sha512"
},
"Microsoft.Extensions.Primitives/2.1.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-scJ1GZNIxMmjpENh0UZ8XCQ6vzr/LzeF9WvEA51Ix2OQGAs9WPgPu8ABVUdvpKPLuor/t05gm6menJK3PwqOXg==",
"path": "microsoft.extensions.primitives/2.1.1",
"hashPath": "microsoft.extensions.primitives.2.1.1.nupkg.sha512"
},
"Microsoft.Net.Http.Headers/2.1.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-lPNIphl8b2EuhOE9dMH6EZDmu7pS882O+HMi5BJNsigxHaWlBrYxZHFZgE18cyaPp6SSZcTkKkuzfjV/RRQKlA==",
"path": "microsoft.net.http.headers/2.1.1",
"hashPath": "microsoft.net.http.headers.2.1.1.nupkg.sha512"
},
"Microsoft.NETCore.Platforms/1.1.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==",
"path": "microsoft.netcore.platforms/1.1.0",
"hashPath": "microsoft.netcore.platforms.1.1.0.nupkg.sha512"
},
"Microsoft.NETCore.Targets/1.1.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg==",
"path": "microsoft.netcore.targets/1.1.0",
"hashPath": "microsoft.netcore.targets.1.1.0.nupkg.sha512"
},
"NETStandard.Library/2.0.3": {
"type": "package",
"serviceable": true,
"sha512": "sha512-st47PosZSHrjECdjeIzZQbzivYBJFv6P2nv4cj2ypdI204DO+vZ7l5raGMiX4eXMJ53RfOIg+/s4DHVZ54Nu2A==",
"path": "netstandard.library/2.0.3",
"hashPath": "netstandard.library.2.0.3.nupkg.sha512"
},
"Polly/7.2.3": {
"type": "package",
"serviceable": true,
"sha512": "sha512-DeCY0OFbNdNxsjntr1gTXHJ5pKUwYzp04Er2LLeN3g6pWhffsGuKVfMBLe1lw7x76HrPkLxKEFxBlpRxS2nDEQ==",
"path": "polly/7.2.3",
"hashPath": "polly.7.2.3.nupkg.sha512"
},
"Polly.Contrib.WaitAndRetry/1.1.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-1MUQLiSo4KDkQe6nzQRhIU05lm9jlexX5BVsbuw0SL82ynZ+GzAHQxJVDPVBboxV37Po3SG077aX8DuSy8TkaA==",
"path": "polly.contrib.waitandretry/1.1.1",
"hashPath": "polly.contrib.waitandretry.1.1.1.nupkg.sha512"
},
"Polly.Extensions.Http/3.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-drrG+hB3pYFY7w1c3BD+lSGYvH2oIclH8GRSehgfyP5kjnFnHKQuuBhuHLv+PWyFuaTDyk/vfRpnxOzd11+J8g==",
"path": "polly.extensions.http/3.0.0",
"hashPath": "polly.extensions.http.3.0.0.nupkg.sha512"
},
"Sentry/3.29.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-0AQbZte9ETORcrLj+gmzZcjbB3UwLl6KmdRVC9mcFfWTPftnSqVrgWDQHR70t2EYK5w6Y1pM8FAFyLDSJWvyRA==",
"path": "sentry/3.29.0",
"hashPath": "sentry.3.29.0.nupkg.sha512"
},
"Sentry.Serilog/3.29.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-1g+x8fkoYe3/X6qH2F5hJ6eGkfhGqkMcib0P+dGH6lndnVseP+Lgx8HeO8zk3x3bkS2/GUEV7fn/QnFdh32R4A==",
"path": "sentry.serilog/3.29.0",
"hashPath": "sentry.serilog.3.29.0.nupkg.sha512"
},
"Serilog/2.12.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-xaiJLIdu6rYMKfQMYUZgTy8YK7SMZjB4Yk50C/u//Z4OsvxkUfSPJy4nknfvwAC34yr13q7kcyh4grbwhSxyZg==",
"path": "serilog/2.12.0",
"hashPath": "serilog.2.12.0.nupkg.sha512"
},
"Serilog.Enrichers.ClientInfo/1.2.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-ZJx2eJQKX6+GxjZM9Y++7DPunR7Nizk9Vdq+BqMs/YPfW3Sv+qDm3PVC88srywJMDKfRaecHFWktBjw5F2pmoQ==",
"path": "serilog.enrichers.clientinfo/1.2.0",
"hashPath": "serilog.enrichers.clientinfo.1.2.0.nupkg.sha512"
},
"Serilog.Enrichers.GlobalLogContext/3.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-IIZcj5mAUVhIl/NTA+YI2KC+sPDzcwvs0ZMHH42jsPfl1a4LVX7ohVpw5UK+e3GxuV3Nv239Il5oM2peUIl44g==",
"path": "serilog.enrichers.globallogcontext/3.0.0",
"hashPath": "serilog.enrichers.globallogcontext.3.0.0.nupkg.sha512"
},
"Serilog.Exceptions/8.4.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-nc/+hUw3lsdo0zCj0KMIybAu7perMx79vu72w0za9Nsi6mWyNkGXxYxakAjWB7nEmYL6zdmhEQRB4oJ2ALUeug==",
"path": "serilog.exceptions/8.4.0",
"hashPath": "serilog.exceptions.8.4.0.nupkg.sha512"
},
"Serilog.Formatting.Compact/1.1.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-pNroKVjo+rDqlxNG5PXkRLpfSCuDOBY0ri6jp9PLe505ljqwhwZz8ospy2vWhQlFu5GkIesh3FcDs4n7sWZODA==",
"path": "serilog.formatting.compact/1.1.0",
"hashPath": "serilog.formatting.compact.1.1.0.nupkg.sha512"
},
"Serilog.Sinks.Console/4.1.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-K6N5q+5fetjnJPvCmkWOpJ/V8IEIoMIB1s86OzBrbxwTyHxdx3pmz4H+8+O/Dc/ftUX12DM1aynx/dDowkwzqg==",
"path": "serilog.sinks.console/4.1.0",
"hashPath": "serilog.sinks.console.4.1.0.nupkg.sha512"
},
"Serilog.Sinks.File/5.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-uwV5hdhWPwUH1szhO8PJpFiahqXmzPzJT/sOijH/kFgUx+cyoDTMM8MHD0adw9+Iem6itoibbUXHYslzXsLEAg==",
"path": "serilog.sinks.file/5.0.0",
"hashPath": "serilog.sinks.file.5.0.0.nupkg.sha512"
},
"Serilog.Sinks.PeriodicBatching/3.1.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-NDWR7m3PalVlGEq3rzoktrXikjFMLmpwF0HI4sowo8YDdU+gqPlTHlDQiOGxHfB0sTfjPA9JjA7ctKG9zqjGkw==",
"path": "serilog.sinks.periodicbatching/3.1.0",
"hashPath": "serilog.sinks.periodicbatching.3.1.0.nupkg.sha512"
},
"Serilog.Sinks.Seq/5.2.2": {
"type": "package",
"serviceable": true,
"sha512": "sha512-1Csmo5ua7NKUe0yXUx+zsRefjAniPWcXFhUXxXG8pwo0iMiw2gjn9SOkgYnnxbgWqmlGv236w0N/dHc2v5XwMg==",
"path": "serilog.sinks.seq/5.2.2",
"hashPath": "serilog.sinks.seq.5.2.2.nupkg.sha512"
},
"Speckle.Newtonsoft.Json/13.0.2": {
"type": "package",
"serviceable": true,
"sha512": "sha512-g1BejUZwax5PRfL6xHgLEK23sqHWOgOj9hE7RvfRRlN00AGt8GnPYt8HedSK7UB3HiRW8zCA9Pn0iiYxCK24BA==",
"path": "speckle.newtonsoft.json/13.0.2",
"hashPath": "speckle.newtonsoft.json.13.0.2.nupkg.sha512"
},
"SQLitePCLRaw.bundle_e_sqlite3/2.1.4": {
"type": "package",
"serviceable": true,
"sha512": "sha512-EWI1olKDjFEBMJu0+3wuxwziIAdWDVMYLhuZ3Qs84rrz+DHwD00RzWPZCa+bLnHCf3oJwuFZIRsHT5p236QXww==",
"path": "sqlitepclraw.bundle_e_sqlite3/2.1.4",
"hashPath": "sqlitepclraw.bundle_e_sqlite3.2.1.4.nupkg.sha512"
},
"SQLitePCLRaw.core/2.1.4": {
"type": "package",
"serviceable": true,
"sha512": "sha512-inBjvSHo9UDKneGNzfUfDjK08JzlcIhn1+SP5Y3m6cgXpCxXKCJDy6Mka7LpgSV+UZmKSnC8rTwB0SQ0xKu5pA==",
"path": "sqlitepclraw.core/2.1.4",
"hashPath": "sqlitepclraw.core.2.1.4.nupkg.sha512"
},
"SQLitePCLRaw.lib.e_sqlite3/2.1.4": {
"type": "package",
"serviceable": true,
"sha512": "sha512-2C9Q9eX7CPLveJA0rIhf9RXAvu+7nWZu1A2MdG6SD/NOu26TakGgL1nsbc0JAspGijFOo3HoN79xrx8a368fBg==",
"path": "sqlitepclraw.lib.e_sqlite3/2.1.4",
"hashPath": "sqlitepclraw.lib.e_sqlite3.2.1.4.nupkg.sha512"
},
"SQLitePCLRaw.provider.e_sqlite3/2.1.4": {
"type": "package",
"serviceable": true,
"sha512": "sha512-CSlb5dUp1FMIkez9Iv5EXzpeq7rHryVNqwJMWnpq87j9zWZexaEMdisDktMsnnrzKM6ahNrsTkjqNodTBPBxtQ==",
"path": "sqlitepclraw.provider.e_sqlite3/2.1.4",
"hashPath": "sqlitepclraw.provider.e_sqlite3.2.1.4.nupkg.sha512"
},
"System.Buffers/4.5.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg==",
"path": "system.buffers/4.5.1",
"hashPath": "system.buffers.4.5.1.nupkg.sha512"
},
"System.Collections.Immutable/5.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-FXkLXiK0sVVewcso0imKQoOxjoPAj42R8HtjjbSjVPAzwDfzoyoznWxgA3c38LDbN9SJux1xXoXYAhz98j7r2g==",
"path": "system.collections.immutable/5.0.0",
"hashPath": "system.collections.immutable.5.0.0.nupkg.sha512"
},
"System.Memory/4.5.4": {
"type": "package",
"serviceable": true,
"sha512": "sha512-1MbJTHS1lZ4bS4FmsJjnuGJOu88ZzTT2rLvrhW7Ygic+pC0NWA+3hgAen0HRdsocuQXCkUTdFn9yHJJhsijDXw==",
"path": "system.memory/4.5.4",
"hashPath": "system.memory.4.5.4.nupkg.sha512"
},
"System.Numerics.Vectors/4.5.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-QQTlPTl06J/iiDbJCiepZ4H//BVraReU4O4EoRw1U02H5TLUIT7xn3GnDp9AXPSlJUDyFs4uWjWafNX6WrAojQ==",
"path": "system.numerics.vectors/4.5.0",
"hashPath": "system.numerics.vectors.4.5.0.nupkg.sha512"
},
"System.Reactive/5.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-erBZjkQHWL9jpasCE/0qKAryzVBJFxGHVBAvgRN1bzM0q2s1S4oYREEEL0Vb+1kA/6BKb5FjUZMp5VXmy+gzkQ==",
"path": "system.reactive/5.0.0",
"hashPath": "system.reactive.5.0.0.nupkg.sha512"
},
"System.Reflection.Metadata/5.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-5NecZgXktdGg34rh1OenY1rFNDCI8xSjFr+Z4OU4cU06AQHUdRnIIEeWENu3Wl4YowbzkymAIMvi3WyK9U53pQ==",
"path": "system.reflection.metadata/5.0.0",
"hashPath": "system.reflection.metadata.5.0.0.nupkg.sha512"
},
"System.Reflection.TypeExtensions/4.7.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-VybpaOQQhqE6siHppMktjfGBw1GCwvCqiufqmP8F1nj7fTUNtW35LOEt3UZTEsECfo+ELAl/9o9nJx3U91i7vA==",
"path": "system.reflection.typeextensions/4.7.0",
"hashPath": "system.reflection.typeextensions.4.7.0.nupkg.sha512"
},
"System.Runtime/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==",
"path": "system.runtime/4.3.0",
"hashPath": "system.runtime.4.3.0.nupkg.sha512"
},
"System.Runtime.CompilerServices.Unsafe/5.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-ZD9TMpsmYJLrxbbmdvhwt9YEgG5WntEnZ/d1eH8JBX9LBp+Ju8BSBhUGbZMNVHHomWo2KVImJhTDl2hIgw/6MA==",
"path": "system.runtime.compilerservices.unsafe/5.0.0",
"hashPath": "system.runtime.compilerservices.unsafe.5.0.0.nupkg.sha512"
},
"System.Runtime.InteropServices.WindowsRuntime/4.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-J4GUi3xZQLUBasNwZnjrffN8i5wpHrBtZoLG+OhRyGo/+YunMRWWtwoMDlUAIdmX0uRfpHIBDSV6zyr3yf00TA==",
"path": "system.runtime.interopservices.windowsruntime/4.3.0",
"hashPath": "system.runtime.interopservices.windowsruntime.4.3.0.nupkg.sha512"
},
"System.Text.Encodings.Web/5.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-KmJ+CJXizDofbq6mpqDoRRLcxgOd2z9X3XoFNULSbvbqVRZkFX3istvr+MUjL6Zw1RT+RNdoI4GYidIINtgvqQ==",
"path": "system.text.encodings.web/5.0.1",
"hashPath": "system.text.encodings.web.5.0.1.nupkg.sha512"
},
"System.Text.Json/5.0.2": {
"type": "package",
"serviceable": true,
"sha512": "sha512-I47dVIGiV6SfAyppphxqupertT/5oZkYLDCX6vC3HpOI4ZLjyoKAreUoem2ie6G0RbRuFrlqz/PcTQjfb2DOfQ==",
"path": "system.text.json/5.0.2",
"hashPath": "system.text.json.5.0.2.nupkg.sha512"
},
"System.Threading.Tasks.Extensions/4.5.4": {
"type": "package",
"serviceable": true,
"sha512": "sha512-zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==",
"path": "system.threading.tasks.extensions/4.5.4",
"hashPath": "system.threading.tasks.extensions.4.5.4.nupkg.sha512"
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,33 @@
fileFormatVersion: 2
guid: a85708f21043b0c458a3a77c0e09e4b8
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:
@@ -47,7 +50,7 @@ PluginImporter:
second:
enabled: 1
settings:
CPU: None
CPU: x86_64
- first:
Standalone: OSXUniversal
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: AnyScriptingBackend
- 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:
@@ -15,109 +15,124 @@ 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;
namespace Speckle.ConnectorUnity
{
/// Author: Pim de Witte (pimdewitte.com) and contributors, https://github.com/PimDeWitte/UnityMainThreadDispatcher
/// <summary>
/// A thread-safe class which holds a queue with actions to execute on the next Update() method. It can be used to make calls to the main thread for
/// things such as UI Manipulation in Unity. It was developed for use in combination with the Firebase Unity plugin, which uses separate threads for event handling
/// </summary>
public class Dispatcher : MonoBehaviour {
/// Author: Pim de Witte (pimdewitte.com) and contributors, https://github.com/PimDeWitte/UnityMainThreadDispatcher
/// <summary>
/// A thread-safe class which holds a queue with actions to execute on the next Update() method. It can be used to make calls to the main thread for
/// things such as UI Manipulation in Unity. It was developed for use in combination with the Firebase Unity plugin, which uses separate threads for event handling
/// </summary>
public class Dispatcher : MonoBehaviour
{
private static readonly Queue<Action> _executionQueue = new Queue<Action>();
private static readonly Queue<Action> _executionQueue = new Queue<Action>();
public void Update()
{
lock (_executionQueue)
{
while (_executionQueue.Count > 0)
{
_executionQueue.Dequeue().Invoke();
}
}
}
public void Update() {
lock(_executionQueue) {
while (_executionQueue.Count > 0) {
_executionQueue.Dequeue().Invoke();
}
}
}
/// <summary>
/// Locks the queue and adds the IEnumerator to the queue
/// </summary>
/// <param name="action">IEnumerator function that will be executed from the main thread.</param>
public void Enqueue(IEnumerator action)
{
lock (_executionQueue)
{
_executionQueue.Enqueue(() =>
{
StartCoroutine(action);
});
}
}
/// <summary>
/// Locks the queue and adds the IEnumerator to the queue
/// </summary>
/// <param name="action">IEnumerator function that will be executed from the main thread.</param>
public void Enqueue(IEnumerator action) {
lock (_executionQueue) {
_executionQueue.Enqueue (() => {
StartCoroutine (action);
});
}
}
/// <summary>
/// Locks the queue and adds the Action to the queue
/// </summary>
/// <param name="action">function that will be executed from the main thread.</param>
public void Enqueue(Action action)
{
Enqueue(ActionWrapper(action));
}
/// <summary>
/// Locks the queue and adds the Action to the queue
/// </summary>
/// <param name="action">function that will be executed from the main thread.</param>
public void Enqueue(Action action)
{
Enqueue(ActionWrapper(action));
}
/// <summary>
/// Locks the queue and adds the Action to the queue, returning a Task which is completed when the action completes
/// </summary>
/// <param name="action">function that will be executed from the main thread.</param>
/// <returns>A Task that can be awaited until the action completes</returns>
public Task EnqueueAsync(Action action)
{
var tcs = new TaskCompletionSource<bool>();
/// <summary>
/// Locks the queue and adds the Action to the queue, returning a Task which is completed when the action completes
/// </summary>
/// <param name="action">function that will be executed from the main thread.</param>
/// <returns>A Task that can be awaited until the action completes</returns>
public Task EnqueueAsync(Action action)
{
var tcs = new TaskCompletionSource<bool>();
void WrappedAction() {
try
{
action();
tcs.TrySetResult(true);
} catch (Exception ex)
{
tcs.TrySetException(ex);
}
}
void WrappedAction()
{
try
{
action();
tcs.TrySetResult(true);
}
catch (Exception ex)
{
tcs.TrySetException(ex);
}
}
Enqueue(ActionWrapper(WrappedAction));
return tcs.Task;
}
Enqueue(ActionWrapper(WrappedAction));
return tcs.Task;
}
IEnumerator ActionWrapper(Action a)
{
a();
yield return null;
}
IEnumerator ActionWrapper(Action a)
{
a();
yield return null;
}
private static Dispatcher _instance;
private static Dispatcher _instance = null;
public static bool Exists()
{
return _instance != null;
}
public static bool Exists() {
return _instance != null;
}
public static Dispatcher Instance()
{
if (!Exists())
{
throw new Exception(
"Could not find the Dispatcher object. Please ensure you have added a Dispatcher object with this script to your scene."
);
}
return _instance;
}
public static Dispatcher Instance() {
if (!Exists ()) {
throw new Exception ("Could not find the Dispatcher object. Please ensure you have added a Dispatcher object with this script to your scene.");
}
return _instance;
}
void Awake()
{
Setup.Init(
HostApplications.Unity.GetVersion(CoreUtils.GetHostAppVersion()),
HostApplications.Unity.Slug
);
if (_instance == null)
{
_instance = this;
DontDestroyOnLoad(this.gameObject);
}
}
void Awake() {
Setup.Init(HostApplications.Unity.GetVersion(CoreUtils.GetHostAppVersion()), HostApplications.Unity.Slug);
if (_instance == null) {
_instance = this;
DontDestroyOnLoad(this.gameObject);
}
}
void OnDestroy() {
_instance = null;
}
}
}
void OnDestroy()
{
_instance = null;
}
}
}
@@ -3,7 +3,6 @@ using Speckle.Core.Kits;
namespace Speckle.ConnectorUnity.Factories
{
public static class ConverterFactory
{
public static ISpeckleConverter GetDefaultConverter()
@@ -1,21 +1,25 @@
#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
{
protected bool isWriting = false;
public abstract bool TryGetObject<T>(Base speckleObject, [NotNullWhen(true)] out T? nativeObject) where T : Object;
protected bool isWriting;
public abstract bool TryGetObject<T>(
Base speckleObject,
[NotNullWhen(true)] out T? nativeObject
)
where T : Object;
public abstract bool TrySaveObject(Base speckleObject, Object nativeObject);
@@ -40,7 +44,6 @@ namespace Speckle.ConnectorUnity.NativeCache
{
FinishWrite();
}
}
public static class AssetHelpers
@@ -48,12 +51,12 @@ namespace Speckle.ConnectorUnity.NativeCache
public static string? GetAssetFolder(Type nativeType, string path)
{
const string format = "{0}/{1}";
if (nativeType == typeof(Mesh))
{
return string.Format(format, path, "Geometry");
}
if (nativeType == typeof(Material))
if (nativeType == typeof(Material))
{
return string.Format(format, path, "Materials");
}
@@ -63,55 +66,33 @@ namespace Speckle.ConnectorUnity.NativeCache
}
return null;
}
private static readonly HashSet<char> InvalidChars = Path.GetInvalidFileNameChars().ToHashSet();
private static readonly HashSet<char> InvalidChars = Path.GetInvalidFileNameChars()
.ToHashSet();
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";
if (nativeType == typeof(GameObject)) return ".prefab";
if (nativeType == typeof(Material))
return ".mat";
if (nativeType == typeof(GameObject))
return ".prefab";
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();
string objectName =
speckleObject["name"] as string ?? speckleObject.speckle_type.Split(':').Last();
return $"{objectName} - {speckleObject.id}";
}
}
@@ -10,12 +10,14 @@ namespace Speckle.ConnectorUnity.NativeCache
{
[SerializeField, SerializeReference]
public List<AbstractNativeCache> nativeCaches;
public override bool TryGetObject<T>(Base speckleObject, out T nativeObject) where T : class
public override bool TryGetObject<T>(Base speckleObject, out T nativeObject)
where T : class
{
foreach (var c in nativeCaches)
{
if (c.TryGetObject(speckleObject, out nativeObject)) return true;
if (c.TryGetObject(speckleObject, out nativeObject))
return true;
}
nativeObject = null;
return false;
@@ -24,12 +26,13 @@ namespace Speckle.ConnectorUnity.NativeCache
public override bool TrySaveObject(Base speckleObject, Object nativeObject)
{
bool hasSavedSomewhere = false;
foreach (var c in nativeCaches)
{
hasSavedSomewhere = hasSavedSomewhere || c.TrySaveObject(speckleObject, nativeObject);
hasSavedSomewhere =
hasSavedSomewhere || c.TrySaveObject(speckleObject, nativeObject);
}
return hasSavedSomewhere;
}
@@ -57,6 +60,5 @@ namespace Speckle.ConnectorUnity.NativeCache
}
base.FinishWrite();
}
}
}
@@ -6,40 +6,48 @@ using Object = UnityEngine.Object;
namespace Speckle.ConnectorUnity.NativeCache
{
#nullable enable
#nullable enable
/// <summary>
/// In memory native object cache
/// </summary>
public sealed class MemoryNativeCache : AbstractNativeCache
{
public IDictionary<string, List<Object>> LoadedAssets { get; set; } = new Dictionary<string, List<Object>>();
public override bool TryGetObject<T>(Base speckleObject, [NotNullWhen(true)] out T? nativeObject) where T : class
public IDictionary<string, List<Object>> LoadedAssets { get; set; } =
new Dictionary<string, List<Object>>();
public override bool TryGetObject<T>(
Base speckleObject,
[NotNullWhen(true)] out T? nativeObject
)
where T : class
{
if (TryGetObject(speckleObject, out List<Object>? e))
{
nativeObject = (T?)e.FirstOrDefault(x => x is T);
return nativeObject != null;
}
nativeObject = null;
return false;
}
public bool TryGetObject(Base speckleObject, [NotNullWhen(true)] out List<Object>? nativeObject)
public bool TryGetObject(
Base speckleObject,
[NotNullWhen(true)] out List<Object>? nativeObject
)
{
return LoadedAssets.TryGetValue(speckleObject.id, out nativeObject);
}
public override bool TrySaveObject(Base speckleObject, Object nativeObject)
{
if (LoadedAssets.ContainsKey(speckleObject.id))
if (LoadedAssets.TryGetValue(speckleObject.id, out List<Object>? assets))
{
LoadedAssets[speckleObject.id].Add(nativeObject);
assets.Add(nativeObject);
return true;
}
LoadedAssets.Add(speckleObject.id, new List<Object>{nativeObject});
LoadedAssets.Add(speckleObject.id, new List<Object> { nativeObject });
return true;
}
}
@@ -5,7 +5,7 @@ using Object = UnityEngine.Object;
namespace Speckle.ConnectorUnity.NativeCache
{
#nullable enable
#nullable enable
/// <summary>
/// Loads existing assets from <see cref="Resources"/>
/// by friendly id (see <see cref="AssetHelpers.GetAssetName"/>)
@@ -14,19 +14,25 @@ namespace Speckle.ConnectorUnity.NativeCache
public sealed class ResourcesNativeCache : AbstractNativeCache
{
public bool matchByName = true;
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 (matchByName)
{
string? speckleName = speckleObject["name"] as string ?? speckleObject["Name"] as string;
string? speckleName =
speckleObject["name"] as string ?? speckleObject["Name"] as string;
if (!string.IsNullOrWhiteSpace(speckleName))
{
nativeObject = Resources.Load<T>(speckleName);
if (nativeObject != null) return true;
if (nativeObject != null)
return true;
}
}
}
nativeObject = Resources.Load<T>(AssetHelpers.GetAssetName(speckleObject, typeof(T)));
return nativeObject != null;
}
@@ -1,7 +1,7 @@
{
"name": "Speckle.ConnectorUnity.NativeCache",
"rootNamespace": "Speckle.ConnectorUnity",
"references": [],
"references": ["GUID:50d889142fdf9de4b8501c6eaa4b3225"],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
@@ -8,11 +8,12 @@ using Speckle.ConnectorUnity.NativeCache.Editor;
namespace Speckle.ConnectorUnity
{
#nullable enable
#nullable enable
public static class NativeCacheFactory
{
public static List<AbstractNativeCache> GetDefaultNativeCacheSetup(bool generateAssets = false)
public static List<AbstractNativeCache> GetDefaultNativeCacheSetup(
bool generateAssets = false
)
{
#if UNITY_EDITOR
if (generateAssets)
@@ -21,7 +22,6 @@ namespace Speckle.ConnectorUnity
}
#endif
return GetStandaloneCacheSetup();
}
public static List<AbstractNativeCache> GetStandaloneCacheSetup()
@@ -32,7 +32,7 @@ namespace Speckle.ConnectorUnity
ScriptableObject.CreateInstance<MemoryNativeCache>(),
};
}
#if UNITY_EDITOR
public static List<AbstractNativeCache> GetEditorCacheSetup()
{
@@ -46,4 +46,3 @@ namespace Speckle.ConnectorUnity
#endif
}
}
@@ -7,55 +7,55 @@
"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": {}
}
},
"GraphQL.Client/5.1.1": {
"GraphQL.Client/6.0.0": {
"dependencies": {
"GraphQL.Client.Abstractions": "5.1.1",
"GraphQL.Client.Abstractions.Websocket": "5.1.1",
"GraphQL.Client.Abstractions": "6.0.0",
"GraphQL.Client.Abstractions.Websocket": "6.0.0",
"System.Reactive": "5.0.0"
},
"runtime": {
"lib/netstandard2.0/GraphQL.Client.dll": {
"assemblyVersion": "5.1.1.0",
"fileVersion": "5.1.1.0"
"assemblyVersion": "6.0.0.0",
"fileVersion": "6.0.0.0"
}
}
},
"GraphQL.Client.Abstractions/5.1.1": {
"GraphQL.Client.Abstractions/6.0.0": {
"dependencies": {
"GraphQL.Primitives": "5.1.1"
"GraphQL.Primitives": "6.0.0"
},
"runtime": {
"lib/netstandard2.0/GraphQL.Client.Abstractions.dll": {
"assemblyVersion": "5.1.1.0",
"fileVersion": "5.1.1.0"
"assemblyVersion": "6.0.0.0",
"fileVersion": "6.0.0.0"
}
}
},
"GraphQL.Client.Abstractions.Websocket/5.1.1": {
"GraphQL.Client.Abstractions.Websocket/6.0.0": {
"dependencies": {
"GraphQL.Client.Abstractions": "5.1.1"
"GraphQL.Client.Abstractions": "6.0.0"
},
"runtime": {
"lib/netstandard2.0/GraphQL.Client.Abstractions.Websocket.dll": {
"assemblyVersion": "5.1.1.0",
"fileVersion": "5.1.1.0"
"assemblyVersion": "6.0.0.0",
"fileVersion": "6.0.0.0"
}
}
},
"GraphQL.Primitives/5.1.1": {
"GraphQL.Primitives/6.0.0": {
"runtime": {
"lib/netstandard2.0/GraphQL.Primitives.dll": {
"assemblyVersion": "5.1.1.0",
"fileVersion": "5.1.1.0"
"assemblyVersion": "6.0.0.0",
"fileVersion": "6.0.0.0"
}
}
},
@@ -128,20 +128,20 @@
}
}
},
"Microsoft.Data.Sqlite/7.0.3": {
"Microsoft.Data.Sqlite/7.0.5": {
"dependencies": {
"Microsoft.Data.Sqlite.Core": "7.0.3",
"Microsoft.Data.Sqlite.Core": "7.0.5",
"SQLitePCLRaw.bundle_e_sqlite3": "2.1.4"
}
},
"Microsoft.Data.Sqlite.Core/7.0.3": {
"Microsoft.Data.Sqlite.Core/7.0.5": {
"dependencies": {
"SQLitePCLRaw.core": "2.1.4"
},
"runtime": {
"lib/netstandard2.0/Microsoft.Data.Sqlite.dll": {
"assemblyVersion": "7.0.3.0",
"fileVersion": "7.0.323.6302"
"assemblyVersion": "7.0.5.0",
"fileVersion": "7.0.523.16503"
}
}
},
@@ -231,30 +231,27 @@
}
}
},
"Sentry/3.29.0": {
"Sentry/3.33.0": {
"dependencies": {
"Microsoft.Bcl.AsyncInterfaces": "5.0.0",
"System.Buffers": "4.5.1",
"System.Reflection.Metadata": "5.0.0",
"System.Text.Json": "5.0.2",
"System.Threading.Tasks.Extensions": "4.5.4"
"System.Text.Json": "5.0.2"
},
"runtime": {
"lib/netstandard2.0/Sentry.dll": {
"assemblyVersion": "3.29.0.0",
"fileVersion": "3.29.0.0"
"assemblyVersion": "3.33.0.0",
"fileVersion": "3.33.0.0"
}
}
},
"Sentry.Serilog/3.29.0": {
"Sentry.Serilog/3.33.0": {
"dependencies": {
"Sentry": "3.29.0",
"Sentry": "3.33.0",
"Serilog": "2.12.0"
},
"runtime": {
"lib/netstandard2.0/Sentry.Serilog.dll": {
"assemblyVersion": "3.29.0.0",
"fileVersion": "3.29.0.0"
"assemblyVersion": "3.33.0.0",
"fileVersion": "3.33.0.0"
}
}
},
@@ -266,7 +263,7 @@
}
}
},
"Serilog.Enrichers.ClientInfo/1.2.0": {
"Serilog.Enrichers.ClientInfo/1.3.0": {
"dependencies": {
"Microsoft.AspNetCore.Http": "2.1.1",
"Serilog": "2.12.0"
@@ -359,6 +356,17 @@
}
}
},
"SerilogTimings/3.0.1": {
"dependencies": {
"Serilog": "2.12.0"
},
"runtime": {
"lib/netstandard2.0/SerilogTimings.dll": {
"assemblyVersion": "3.0.1.0",
"fileVersion": "3.0.1.0"
}
}
},
"Speckle.Newtonsoft.Json/13.0.2": {
"runtime": {
"lib/netstandard2.0/Speckle.Newtonsoft.Json.dll": {
@@ -421,6 +429,17 @@
}
}
},
"System.DoubleNumerics/3.1.3": {
"dependencies": {
"NETStandard.Library": "2.0.3"
},
"runtime": {
"lib/netstandard1.3/System.DoubleNumerics.dll": {
"assemblyVersion": "1.0.0.0",
"fileVersion": "1.0.0.0"
}
}
},
"System.Memory/4.5.4": {
"dependencies": {
"System.Buffers": "4.5.1",
@@ -541,38 +560,38 @@
}
},
"libraries": {
"Objects/2.1.1": {
"Objects/2.0.999-local": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"GraphQL.Client/5.1.1": {
"GraphQL.Client/6.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-6bfM9qU4AMcFWm4BHd2M6LE5+rLtK47/+VRtypggwb9appC8sbF58ytVBVOKpqKhKpmZERfPLGJap8O/FH3w5w==",
"path": "graphql.client/5.1.1",
"hashPath": "graphql.client.5.1.1.nupkg.sha512"
"sha512": "sha512-8yPNBbuVBpTptivyAlak4GZvbwbUcjeQTL4vN1HKHRuOykZ4r7l5fcLS6vpyPyLn0x8FsL31xbOIKyxbmR9rbA==",
"path": "graphql.client/6.0.0",
"hashPath": "graphql.client.6.0.0.nupkg.sha512"
},
"GraphQL.Client.Abstractions/5.1.1": {
"GraphQL.Client.Abstractions/6.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-/znG7Lcz3rzG9VSCq+V2ARb63c/uIs8idGOvXyltZ32Wy570GX/I8HNUIZ1yDThmQRJ5KOGSd9Mzk37lFg49rg==",
"path": "graphql.client.abstractions/5.1.1",
"hashPath": "graphql.client.abstractions.5.1.1.nupkg.sha512"
"sha512": "sha512-h7uzWFORHZ+CCjwr/ThAyXMr0DPpzEANDa4Uo54wqCQ+j7qUKwqYTgOrb1W40sqbvNaZm9v/X7It31SUw0maHA==",
"path": "graphql.client.abstractions/6.0.0",
"hashPath": "graphql.client.abstractions.6.0.0.nupkg.sha512"
},
"GraphQL.Client.Abstractions.Websocket/5.1.1": {
"GraphQL.Client.Abstractions.Websocket/6.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-n1gU3GlqJ0jQceb/VEEr4c0D2vpQc5AtDwthK89+yX7VpzlhJKqE5B4RJwx//Jb33mKybfJioWwDgVfSOPAwdw==",
"path": "graphql.client.abstractions.websocket/5.1.1",
"hashPath": "graphql.client.abstractions.websocket.5.1.1.nupkg.sha512"
"sha512": "sha512-Nr9bPf8gIOvLuXpqEpqr9z9jslYFJOvd0feHth3/kPqeR3uMbjF5pjiwh4jxyMcxHdr8Pb6QiXkV3hsSyt0v7A==",
"path": "graphql.client.abstractions.websocket/6.0.0",
"hashPath": "graphql.client.abstractions.websocket.6.0.0.nupkg.sha512"
},
"GraphQL.Primitives/5.1.1": {
"GraphQL.Primitives/6.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-RlGNsv19gbz6sQwkzif9J6Jd148nuIg1kRQf2AFOLp5K00IA+pKMdJvHF5t5llDR52Rok46ynhJv/wg+ps9ZhQ==",
"path": "graphql.primitives/5.1.1",
"hashPath": "graphql.primitives.5.1.1.nupkg.sha512"
"sha512": "sha512-yg72rrYDapfsIUrul7aF6wwNnTJBOFvuA9VdDTQpPa8AlAriHbufeXYLBcodKjfUdkCnaiggX1U/nEP08Zb5GA==",
"path": "graphql.primitives/6.0.0",
"hashPath": "graphql.primitives.6.0.0.nupkg.sha512"
},
"Microsoft.AspNetCore.Http/2.1.1": {
"type": "package",
@@ -616,19 +635,19 @@
"path": "microsoft.csharp/4.7.0",
"hashPath": "microsoft.csharp.4.7.0.nupkg.sha512"
},
"Microsoft.Data.Sqlite/7.0.3": {
"Microsoft.Data.Sqlite/7.0.5": {
"type": "package",
"serviceable": true,
"sha512": "sha512-uumx0bb4FsN7ApP0ZoQDfSJi9c2Xen0PlXCT2BF27cM+yUMFzDEhqxR7/1/DV8ck4mYtL9yShBoOa7jeJ3736w==",
"path": "microsoft.data.sqlite/7.0.3",
"hashPath": "microsoft.data.sqlite.7.0.3.nupkg.sha512"
"sha512": "sha512-KGxbPeWsQMnmQy43DSBxAFtHz3l2JX8EWBSGUCvT3CuZ8KsuzbkqMIJMDOxWtG8eZSoCDI04aiVQjWuuV8HmSw==",
"path": "microsoft.data.sqlite/7.0.5",
"hashPath": "microsoft.data.sqlite.7.0.5.nupkg.sha512"
},
"Microsoft.Data.Sqlite.Core/7.0.3": {
"Microsoft.Data.Sqlite.Core/7.0.5": {
"type": "package",
"serviceable": true,
"sha512": "sha512-pCmzLLWTIrIv94o7JtQ1qcPD0oc1YNY9XvlO6/tOF9YCcUfDZ3Tx9Z//CM7hFnprduHFPekif7jteBc/sXQ31Q==",
"path": "microsoft.data.sqlite.core/7.0.3",
"hashPath": "microsoft.data.sqlite.core.7.0.3.nupkg.sha512"
"sha512": "sha512-FTerRmQPqHrCrnoUzhBu+E+1DNGwyrAMLqHkAqOOOu5pGfyMOj8qQUBxI/gDtWtG11p49UxSfWmBzRNlwZqfUg==",
"path": "microsoft.data.sqlite.core/7.0.5",
"hashPath": "microsoft.data.sqlite.core.7.0.5.nupkg.sha512"
},
"Microsoft.Extensions.DependencyInjection.Abstractions/2.1.1": {
"type": "package",
@@ -707,19 +726,19 @@
"path": "polly.extensions.http/3.0.0",
"hashPath": "polly.extensions.http.3.0.0.nupkg.sha512"
},
"Sentry/3.29.0": {
"Sentry/3.33.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-0AQbZte9ETORcrLj+gmzZcjbB3UwLl6KmdRVC9mcFfWTPftnSqVrgWDQHR70t2EYK5w6Y1pM8FAFyLDSJWvyRA==",
"path": "sentry/3.29.0",
"hashPath": "sentry.3.29.0.nupkg.sha512"
"sha512": "sha512-8vbD2o6IR2wrRrkSiRbnodWGWUOqIlwYtzpjvPNOb5raJdOf+zxMwfS8f6nx9bmrTTfDj7KrCB8C/5OuicAc8A==",
"path": "sentry/3.33.0",
"hashPath": "sentry.3.33.0.nupkg.sha512"
},
"Sentry.Serilog/3.29.0": {
"Sentry.Serilog/3.33.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-1g+x8fkoYe3/X6qH2F5hJ6eGkfhGqkMcib0P+dGH6lndnVseP+Lgx8HeO8zk3x3bkS2/GUEV7fn/QnFdh32R4A==",
"path": "sentry.serilog/3.29.0",
"hashPath": "sentry.serilog.3.29.0.nupkg.sha512"
"sha512": "sha512-V8BU7QGWg2qLYfNPqtuTBhC1opysny5l+Ifp6J6PhOeAxU0FssR7nYfbJVetrnLIoh2rd3DlJ6hHYYQosQYcUQ==",
"path": "sentry.serilog/3.33.0",
"hashPath": "sentry.serilog.3.33.0.nupkg.sha512"
},
"Serilog/2.12.0": {
"type": "package",
@@ -728,12 +747,12 @@
"path": "serilog/2.12.0",
"hashPath": "serilog.2.12.0.nupkg.sha512"
},
"Serilog.Enrichers.ClientInfo/1.2.0": {
"Serilog.Enrichers.ClientInfo/1.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-ZJx2eJQKX6+GxjZM9Y++7DPunR7Nizk9Vdq+BqMs/YPfW3Sv+qDm3PVC88srywJMDKfRaecHFWktBjw5F2pmoQ==",
"path": "serilog.enrichers.clientinfo/1.2.0",
"hashPath": "serilog.enrichers.clientinfo.1.2.0.nupkg.sha512"
"sha512": "sha512-mTc7PM+wC9Hr7LWSwqt5mmnlAr7RJs+eTb3PGPRhwdOackk95MkhUZognuxXEdlW19HAFNmEBTSBY5DfLwM8jQ==",
"path": "serilog.enrichers.clientinfo/1.3.0",
"hashPath": "serilog.enrichers.clientinfo.1.3.0.nupkg.sha512"
},
"Serilog.Enrichers.GlobalLogContext/3.0.0": {
"type": "package",
@@ -784,6 +803,13 @@
"path": "serilog.sinks.seq/5.2.2",
"hashPath": "serilog.sinks.seq.5.2.2.nupkg.sha512"
},
"SerilogTimings/3.0.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-Zs28eTgszAMwpIrbBnWHBI50yuxL50p/dmAUWmy75+axdZYK/Sjm5/5m1N/CisR8acJUhTVcjPZrsB1P5iv0Uw==",
"path": "serilogtimings/3.0.1",
"hashPath": "serilogtimings.3.0.1.nupkg.sha512"
},
"Speckle.Newtonsoft.Json/13.0.2": {
"type": "package",
"serviceable": true,
@@ -833,6 +859,13 @@
"path": "system.collections.immutable/5.0.0",
"hashPath": "system.collections.immutable.5.0.0.nupkg.sha512"
},
"System.DoubleNumerics/3.1.3": {
"type": "package",
"serviceable": true,
"sha512": "sha512-KRKEM/L3KBodjA9VOg3EifFVWUY6EOqaMB05UvPEDm7Zeby/kZW+4kdWUEPzW6xtkwf46p661L9NrbeeQhtLzw==",
"path": "system.doublenumerics/3.1.3",
"hashPath": "system.doublenumerics.3.1.3.nupkg.sha512"
},
"System.Memory/4.5.4": {
"type": "package",
"serviceable": true,
File diff suppressed because it is too large Load Diff
@@ -196,4 +196,4 @@ namespace System.Collections.Concurrent
}
#endregion
}
}
}
@@ -1,42 +1,39 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Collections.Generic;
using System.Threading.Tasks;
using Speckle.Core.Api;
using Speckle.Core.Credentials;
using Speckle.Core.Logging;
namespace Speckle.ConnectorUnity
{
public static class Streams
{
public static async Task<List<Stream>> List(int limit = 10)
public static class Streams
{
var account = AccountManager.GetDefaultAccount();
if (account == null)
return new List<Stream>();
var client = new Client(account);
public static async Task<List<Stream>> List(int limit = 10)
{
var account = AccountManager.GetDefaultAccount();
if (account == null)
return new List<Stream>();
var client = new Client(account);
var res = await client.StreamsGet(limit);
var res = await client.StreamsGet(limit);
return res;
return res;
}
public static async Task<Stream> Get(string streamId, int limit = 10)
{
var account = AccountManager.GetDefaultAccount();
if (account == null)
return null;
var client = new Client(account);
var res = await client.StreamGet(streamId, limit);
if (res.branches.items != null)
{
res.branches.items.Reverse();
}
return res;
}
}
public static async Task<Stream> Get(string streamId, int limit = 10)
{
var account = AccountManager.GetDefaultAccount();
if (account == null)
return null;
var client = new Client(account);
var res = await client.StreamGet(streamId, limit);
if (res.branches.items != null)
{
res.branches.items.Reverse();
}
return res;
}
}
}
}
@@ -1,36 +1,64 @@
#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);
}
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
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];
}
}
}
@@ -16,9 +16,13 @@ namespace Speckle.ConnectorUnity.Utils
/// <param name="propertyName"></param>
/// <param name="value"></param>
#pragma warning disable CS0618
public static void SetDetachedPropertyChecked(this Base speckleObject, string propertyName, object? value)
public static void SetDetachedPropertyChecked(
this Base speckleObject,
string propertyName,
object? value
)
{
if(speckleObject.GetInstanceMembersNames().Any(name => name == propertyName))
if (speckleObject.GetInstanceMembersNames().Any(name => name == propertyName))
speckleObject[propertyName] = value;
else
speckleObject[$"@{propertyName}"] = value;
@@ -0,0 +1,112 @@
using UnityEngine;
namespace Speckle.ConnectorUnity.Utils
{
// see https://github.com/Unity-Technologies/UnityCsReference/blob/master/Editor/Mono/Inspector/StandardShaderGUI.cs
public static class ShaderHelpers
{
private static readonly int SrcBlend = Shader.PropertyToID("_SrcBlend");
private static readonly int DstBlend = Shader.PropertyToID("_DstBlend");
private static readonly int ZWrite = Shader.PropertyToID("_ZWrite");
private static readonly int Mode = Shader.PropertyToID("_Mode");
public enum BlendMode
{
Opaque,
Cutout,
Fade, // Old school alpha-blending mode, fresnel does not affect amount of transparency
Transparent // Physically plausible transparency mode, implemented as alpha pre-multiply
}
public static void SetupMaterialWithBlendMode_Standard(
Material material,
BlendMode blendMode,
bool overrideRenderQueue
)
{
int minRenderQueue = -1;
int maxRenderQueue = 5000;
int defaultRenderQueue = -1;
switch (blendMode)
{
case BlendMode.Opaque:
material.SetOverrideTag("RenderType", "");
material.SetFloat(Mode, 0);
material.SetFloat(SrcBlend, (float)UnityEngine.Rendering.BlendMode.One);
material.SetFloat(DstBlend, (float)UnityEngine.Rendering.BlendMode.Zero);
material.SetFloat(ZWrite, 1.0f);
material.DisableKeyword("_ALPHATEST_ON");
material.DisableKeyword("_ALPHABLEND_ON");
material.DisableKeyword("_ALPHAPREMULTIPLY_ON");
minRenderQueue = -1;
maxRenderQueue = (int)UnityEngine.Rendering.RenderQueue.AlphaTest - 1;
defaultRenderQueue = -1;
break;
case BlendMode.Cutout:
material.SetOverrideTag("RenderType", "TransparentCutout");
material.SetFloat(Mode, 1);
material.SetFloat(SrcBlend, (float)UnityEngine.Rendering.BlendMode.One);
material.SetFloat(DstBlend, (float)UnityEngine.Rendering.BlendMode.Zero);
material.SetFloat(ZWrite, 1.0f);
material.EnableKeyword("_ALPHATEST_ON");
material.DisableKeyword("_ALPHABLEND_ON");
material.DisableKeyword("_ALPHAPREMULTIPLY_ON");
minRenderQueue = (int)UnityEngine.Rendering.RenderQueue.AlphaTest;
maxRenderQueue = (int)UnityEngine.Rendering.RenderQueue.GeometryLast;
defaultRenderQueue = (int)UnityEngine.Rendering.RenderQueue.AlphaTest;
break;
case BlendMode.Fade:
material.SetOverrideTag("RenderType", "Transparent");
material.SetFloat(Mode, 2);
material.SetFloat(SrcBlend, (float)UnityEngine.Rendering.BlendMode.SrcAlpha);
material.SetFloat(
DstBlend,
(float)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha
);
material.SetFloat(ZWrite, 0.0f);
material.DisableKeyword("_ALPHATEST_ON");
material.EnableKeyword("_ALPHABLEND_ON");
material.DisableKeyword("_ALPHAPREMULTIPLY_ON");
minRenderQueue = (int)UnityEngine.Rendering.RenderQueue.GeometryLast + 1;
maxRenderQueue = (int)UnityEngine.Rendering.RenderQueue.Overlay - 1;
defaultRenderQueue = (int)UnityEngine.Rendering.RenderQueue.Transparent;
break;
case BlendMode.Transparent:
material.SetOverrideTag("RenderType", "Transparent");
material.SetFloat(Mode, 3);
material.SetFloat(SrcBlend, (float)UnityEngine.Rendering.BlendMode.One);
material.SetFloat(
DstBlend,
(float)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha
);
material.SetFloat(ZWrite, 0.0f);
material.DisableKeyword("_ALPHATEST_ON");
material.DisableKeyword("_ALPHABLEND_ON");
material.EnableKeyword("_ALPHAPREMULTIPLY_ON");
minRenderQueue = (int)UnityEngine.Rendering.RenderQueue.GeometryLast + 1;
maxRenderQueue = (int)UnityEngine.Rendering.RenderQueue.Overlay - 1;
defaultRenderQueue = (int)UnityEngine.Rendering.RenderQueue.Transparent;
break;
}
if (
overrideRenderQueue
|| material.renderQueue < minRenderQueue
|| material.renderQueue > maxRenderQueue
)
{
if (!overrideRenderQueue)
Debug.LogFormat(
LogType.Log,
LogOption.NoStacktrace,
null,
"Render queue value outside of the allowed range ({0} - {1}) for selected Blend mode, resetting render queue to default",
minRenderQueue,
maxRenderQueue
);
material.renderQueue = defaultRenderQueue;
}
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5f9ac668e8174e13be50ba5a5de9f732
timeCreated: 1689088698
@@ -1,97 +1,137 @@
using System;
#nullable enable
using System;
using System.Collections;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Networking;
#nullable enable
namespace Speckle.ConnectorUnity.Utils
{
public static class Utils
{
public static void SafeDestroy(UnityEngine.Object obj)
public static class Utils
{
if (Application.isPlaying)
UnityEngine.Object.Destroy(obj);
public static void SafeDestroy(UnityEngine.Object obj)
{
if (Application.isPlaying)
UnityEngine.Object.Destroy(obj);
else
UnityEngine.Object.DestroyImmediate(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, CancellationToken cancellationToken = default)
{
Task = Task.Run(function, cancellationToken);
}
}
/// <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,
CancellationToken cancellationToken = default
)
{
this.Task = System.Threading.Tasks.Task.Run(function, cancellationToken);
}
}
}
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);
}
}
}
@@ -1,8 +1,8 @@
using System;
using System.Collections;
using System.Linq;
using Speckle.Core.Api;
using Speckle.Core.Credentials;
using Speckle.Core.Helpers;
using UnityEngine;
#nullable enable
@@ -11,40 +11,48 @@ namespace Speckle.ConnectorUnity.Wrappers.Selection
[Serializable]
public sealed class AccountSelection : OptionSelection<Account>, IDisposable
{
private Client? client;
private Client? _client;
public override Client? Client
{
get
{
Account? account = Selected;
if (account == null) return client = null;
if (client == null || !client.Account.Equals(account)) return client = new Client(account);
return client;
if (account == null)
return _client = null;
if (_client == null || !_client.Account.Equals(account))
return _client = new Client(account);
return _client;
}
}
protected override string? KeyFunction(Account? value) => value?.id;
protected override string? KeyFunction(Account? value)
{
if (value is null)
return null;
return value.id + Crypt.Md5(value.serverInfo.url ?? "", "X2");
}
public override void RefreshOptions()
{
Account[] accounts;
try
{
accounts = AccountManager.GetAccounts().ToArray();
if(accounts.Length == 0)
if (accounts.Length == 0)
Debug.LogWarning("No Accounts found, please login in Manager");
}
catch(Exception e)
catch (Exception e)
{
accounts = Array.Empty<Account>();
Debug.LogWarning($"Unable to refresh {this}\n{e}");
}
GenerateOptions(accounts, isDefault: (a, i) => a.isDefault || i == 0);
}
public void Dispose()
{
client?.Dispose();
_client?.Dispose();
}
}
}
@@ -9,38 +9,47 @@ namespace Speckle.ConnectorUnity.Wrappers.Selection
[Serializable]
public sealed class BranchSelection : OptionSelection<Branch>
{
[field: SerializeField, Range(1,100), Tooltip("Number of branches to request")]
public int BranchesLimit { get; set; } = 30;
[field: SerializeField, Range(1,100), Tooltip("Number of commits to request")]
public int CommitsLimit { get; set; } = 15;
[field:
SerializeField,
Range(1, ServerLimits.BRANCH_GET_LIMIT),
Tooltip("Number of branches to request")
]
public int BranchesLimit { get; set; } = ServerLimits.OLD_BRANCH_GET_LIMIT;
[field: SerializeField, Range(1, 100), Tooltip("Number of commits to request")]
public int CommitsLimit { get; set; } = 25;
[field: SerializeReference]
public StreamSelection StreamSelection { get; private set; }
public override Client? Client => StreamSelection.Client;
public BranchSelection(StreamSelection streamSelection)
{
StreamSelection = streamSelection;
Initialise();
}
public void Initialise()
{
StreamSelection.OnSelectionChange = RefreshOptions;
}
protected override string? KeyFunction(Branch? value) => value?.name;
protected override string? KeyFunction(Branch? value) => value?.id;
public override void RefreshOptions()
{
Stream? stream = StreamSelection.Selected;
if (stream == null) return;
IList<Branch> branches;
if (stream == null)
return;
IReadOnlyList<Branch> branches;
try
{
branches = Client!.StreamGetBranches(stream.id, BranchesLimit, CommitsLimit).GetAwaiter().GetResult();
branches = Client!
.StreamGetBranches(stream.id, BranchesLimit, CommitsLimit)
.GetAwaiter()
.GetResult();
}
catch(Exception e)
catch (Exception e)
{
Debug.LogWarning($"Unable to refresh {this}\n{e}");
branches = Array.Empty<Branch>();
@@ -6,35 +6,32 @@ using UnityEngine;
#nullable enable
namespace Speckle.ConnectorUnity.Wrappers.Selection
{
[Serializable]
public sealed class CommitSelection : OptionSelection<Commit>
{
[field: SerializeReference]
public BranchSelection BranchSelection { get; private set; }
public override Client? Client => BranchSelection.Client;
public CommitSelection(BranchSelection branchSelection)
{
BranchSelection = branchSelection;
Initialise();
}
public void Initialise()
{
BranchSelection.OnSelectionChange = RefreshOptions;
}
protected override string? KeyFunction(Commit? value) => value?.id;
public override void RefreshOptions()
{
Branch? branch = BranchSelection!.Selected;
if (branch == null) return;
Branch? branch = BranchSelection.Selected;
if (branch == null)
return;
List<Commit> commits = branch.commits.items;
GenerateOptions(commits, (_, i) => i == 0);
}
@@ -10,7 +10,7 @@ namespace Speckle.ConnectorUnity.Wrappers.Selection
/// <summary>
/// Reusable <see langword="abstract"/> serializable type that abstracts
/// the fetching of <typeparamref name="TOption"/> objects.
/// And exposes an <see cref="Array"/> of <see cref="Options"/>
/// And exposes an list of <see cref="Options"/>
/// with serialised selection.
/// </summary>
/// <typeparam name="TOption"></typeparam>
@@ -18,29 +18,42 @@ namespace Speckle.ConnectorUnity.Wrappers.Selection
public abstract class OptionSelection<TOption>
where TOption : class
{
[SerializeField] private int selectedIndex = -1;
public IReadOnlyList<TOption> Options { get; protected set; } = Array.Empty<TOption>();
public int SelectedIndex
{
get => selectedIndex;
set
{
selectedIndex = value;
OnSelectionChange?.Invoke();
}
}
private Dictionary<string, int>? _indexMap;
[SerializeField]
private string? selectedId;
public TOption? Selected
{
get
{
if (Options == null) return null;
if (SelectedIndex < 0 || SelectedIndex >= Options.Length) return null;
return Options[SelectedIndex];
if (selectedId == null)
return null;
TryGetOption(selectedId, out var value);
return value;
}
set
{
selectedId = KeyFunction(value);
OnSelectionChange?.Invoke();
}
}
public TOption[] Options { get; protected set; } = Array.Empty<TOption>();
public bool TryGetOption(string key, [NotNullWhen(true)] out TOption? value)
{
if (_indexMap is not null && _indexMap.TryGetValue(key, out int index))
{
value = Options[index];
return true;
}
value = null;
return false;
}
public Action? OnSelectionChange { get; set; }
public abstract Client? Client { get; }
@@ -50,31 +63,39 @@ namespace Speckle.ConnectorUnity.Wrappers.Selection
public abstract void RefreshOptions();
protected void GenerateOptions(IList<TOption> source, Func<TOption, int, bool> isDefault)
protected void GenerateOptions(
IReadOnlyCollection<TOption?> source,
Func<TOption, int, bool> isDefault
)
{
List<TOption> optionsToAdd = new List<TOption>(source.Count);
int defaultOption = -1;
List<TOption> optionsToAdd = new(source.Count);
Dictionary<string, int> indexMap = new(source.Count);
string? defaultOption = null;
int index = 0;
foreach (TOption? a in source)
{
if (a == null) continue;
if (a == null)
continue;
var key = KeyFunction(a);
optionsToAdd.Add(a);
if (isDefault(a, index)) defaultOption = index;
indexMap.Add(key, index);
if (isDefault(a, index))
defaultOption = key;
index++;
}
TOption? currentSelected = Selected;
bool selectionOutOfRange = SelectedIndex < 0 || SelectedIndex >= optionsToAdd.Count;
if (selectionOutOfRange
|| (currentSelected != null
&& KeyFunction(currentSelected) != KeyFunction(optionsToAdd[SelectedIndex])))
string? currentSelected = selectedId;
if (currentSelected is null || !indexMap.ContainsKey(currentSelected))
{
selectedIndex = defaultOption;
selectedId = defaultOption;
}
Options = optionsToAdd.ToArray();
//Debug.Log($"{this.GetType()} updated");
Options = optionsToAdd;
_indexMap = indexMap;
OnSelectionChange?.Invoke();
}
}
}
}
@@ -9,17 +9,20 @@ namespace Speckle.ConnectorUnity.Wrappers.Selection
[Serializable]
public sealed class StreamSelection : OptionSelection<Stream>
{
private const int DEFAULT_REQUEST_LIMIT = 50;
[field: SerializeField, Range(1,100), Tooltip("Number of streams to request")]
public int StreamsLimit { get; set; } = DEFAULT_REQUEST_LIMIT;
private const int DefaultRequestLimit = 50;
[field: SerializeField, Range(1, 100), Tooltip("Number of streams to request")]
public int StreamsLimit { get; set; } = DefaultRequestLimit;
[field: SerializeReference]
public AccountSelection AccountSelection { get; private set; }
public StreamSelection(AccountSelection accountSelection)
{
AccountSelection = accountSelection;
Initialise();
}
public void Initialise()
{
AccountSelection.OnSelectionChange = RefreshOptions;
@@ -28,15 +31,17 @@ namespace Speckle.ConnectorUnity.Wrappers.Selection
public override Client? Client => AccountSelection.Client;
protected override string? KeyFunction(Stream? value) => value?.id;
public override void RefreshOptions()
{
if (Client == null) return;
IList<Stream> streams;
if (Client == null)
return;
IReadOnlyList<Stream> streams;
try
{
streams = Client.StreamsGet(StreamsLimit).GetAwaiter().GetResult();
}
catch(Exception e)
catch (Exception e)
{
Debug.LogWarning($"Unable to refresh {this}\n{e}");
streams = Array.Empty<Stream>();
@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Collections.Specialized;
using Speckle.Core.Api;
using Speckle.Core.Models;
using Speckle.Core.Serialisation;
using UnityEngine;
namespace Speckle.ConnectorUnity.Wrappers
@@ -15,19 +16,20 @@ namespace Speckle.ConnectorUnity.Wrappers
[Serializable, DisallowMultipleComponent]
public class SpeckleProperties : MonoBehaviour, ISerializationCallbackReceiver
{
[SerializeField, HideInInspector]
private string _serializedData = "";
[SerializeField, HideInInspector]
private bool _hasChanged;
private ObservableConcurrentDictionary<string, object> _data;
public IDictionary<string, object> Data
{
get => _data;
set
{
((ICollection<KeyValuePair<string, object>>) _data).Clear();
((ICollection<KeyValuePair<string, object>>)_data).Clear();
foreach (var kvp in value)
{
@@ -39,21 +41,17 @@ namespace Speckle.ConnectorUnity.Wrappers
[SerializeField, HideInInspector]
private string _serializedSpeckleType;
private Type _speckleType = typeof(Base);
public Type SpeckleType {
get
{
return _speckleType ??= typeof(Base);
}
public Type SpeckleType
{
get => _speckleType ??= typeof(Base);
set
{
Debug.Assert(typeof(Base).IsAssignableFrom(value));
Debug.Assert(!value.IsAbstract);
_speckleType = value;
_hasChanged = true;
}
}
public SpeckleProperties()
@@ -63,7 +61,7 @@ namespace Speckle.ConnectorUnity.Wrappers
_hasChanged = true;
SpeckleType = typeof(Base);
}
private void CollectionChangeHandler(object sender, NotifyCollectionChangedEventArgs e)
{
_hasChanged = true;
@@ -71,19 +69,22 @@ namespace Speckle.ConnectorUnity.Wrappers
public void OnBeforeSerialize()
{
if (!_hasChanged) return;
if (!_hasChanged)
return;
_serializedData = Operations.Serialize(new SpeckleData(Data));
_hasChanged = false;
_serializedSpeckleType = SpeckleType.AssemblyQualifiedName;
}
public void OnAfterDeserialize()
{
Base speckleData = Operations.Deserialize(_serializedData);
var deserializer = new BaseObjectDeserializerV2();
Base speckleData = deserializer.Deserialize(_serializedData);
Data = speckleData.GetMembers();
_hasChanged = false;
try
{
_speckleType = Type.GetType(_serializedSpeckleType);
@@ -96,7 +97,7 @@ namespace Speckle.ConnectorUnity.Wrappers
}
[Serializable]
private class SpeckleData : Base
private sealed class SpeckleData : Base
{
public SpeckleData(IDictionary<string, object> data)
{
@@ -107,4 +108,4 @@ namespace Speckle.ConnectorUnity.Wrappers
}
}
}
}
}
@@ -1,9 +1,9 @@
{
"name": "systems.speckle.speckle-unity",
"version": "2.13.0",
"version": "2.20.0",
"displayName": "Speckle Unity Connector",
"description": "AEC Interoperability for Unity through Speckle",
"unity": "2018.4",
"unity": "2021.1",
"documentationUrl": "https://speckle.guide/user/unity.html",
"changelogUrl": "https://speckle.systems/blog/",
"license": "Apache-2.0",
@@ -21,4 +21,4 @@
"email": "hello@speckle.systems",
"url": "https://speckle.systems"
}
}
}
+2 -2
View File
@@ -1,2 +1,2 @@
m_EditorVersion: 2021.3.22f1
m_EditorVersionWithRevision: 2021.3.22f1 (b6c551784ba3)
m_EditorVersion: 2021.3.30f1
m_EditorVersionWithRevision: 2021.3.30f1 (b4360d7cdac4)
+33 -17
View File
@@ -1,16 +1,30 @@
<h1 align="center">
<img src="https://user-images.githubusercontent.com/2679513/131189167-18ea5fe1-c578-47f6-9785-3748178e4312.png" width="150px"/><br/>
Speckle | Unity
</h1>
<p align="center"><a href="https://twitter.com/SpeckleSystems"><img src="https://img.shields.io/twitter/follow/SpeckleSystems?style=social" alt="Twitter Follow"></a> <a href="https://speckle.community"><img src="https://img.shields.io/discourse/users?server=https%3A%2F%2Fspeckle.community&amp;style=flat-square&amp;logo=discourse&amp;logoColor=white" alt="Community forum users"></a> <a href="https://speckle.systems"><img src="https://img.shields.io/badge/https://-speckle.systems-royalblue?style=flat-square" alt="website"></a> <a href="https://speckle.guide/dev/"><img src="https://img.shields.io/badge/docs-speckle.guide-orange?style=flat-square&amp;logo=read-the-docs&amp;logoColor=white" alt="docs"></a></p>
# Connector Unity
> Speckle is the first AEC data hub that connects with your favorite AEC tools. Speckle exists to overcome the challenges of working in a fragmented industry where communication, creative workflows, and the exchange of data are often hindered by siloed software and processes. It is here to make the industry better.
[![Twitter Follow](https://img.shields.io/twitter/follow/SpeckleSystems?style=social)](https://twitter.com/SpeckleSystems) [![Community forum users](https://img.shields.io/discourse/users?server=https%3A%2F%2Fdiscourse.speckle.works&style=flat-square&logo=discourse&logoColor=white)](https://discourse.speckle.works) [![website](https://img.shields.io/badge/https://-speckle.systems-royalblue?style=flat-square)](https://speckle.systems) [![docs](https://img.shields.io/badge/docs-speckle.guide-orange?style=flat-square&logo=read-the-docs&logoColor=white)](https://speckle.guide/user/unity.html)
<h3 align="center">
Speckle Connector for Unity
</h3>
> [!WARNING]
> This is a legacy repo! A new next generation connector will be coming soon. In the meantime, check out our active next generation repos here 👇<br/>
> [`speckle-sharp-connectors`](https://github.com/specklesystems/speckle-sharp-connectors): our .NET next generation connectors and desktop UI<br/>
> [`speckle-sharp-sdk`](https://github.com/specklesystems/speckle-sharp-sdk): our .NET SDK, Tests, and Objects
## Introduction
This repo holds Speckle's Unity Connector + a sample project (Speckle playground). This connector is currently in an Alpha stage.
This connector is meant to be used by developers, it doesn't have an elaborated UI but it offers convenience methods to send and receive data. The connector uses our [Speckle .NET SDK](https://github.com/specklesystems/speckle-sharp).
This repo holds Speckle's Unity Connector package + a sample project (Speckle playground).
The package offers several Unity Components to send and receive data from Speckle, and allows developers to easily develop their own components and features.
It has a simple UI, and is missing some of the comforts present in other connectors.
The connector uses our [Speckle .NET SDK](https://github.com/specklesystems/speckle-sharp).
![unity](https://user-images.githubusercontent.com/2679513/108543628-3a83ff00-72dd-11eb-8792-3d43ce54e6af.gif)
@@ -20,26 +34,28 @@ If you are enjoying using Speckle, don't forget to ⭐ our [GitHub repositories]
and [join our community forum](https://speckle.community/) where you can post any questions, suggestions, and discuss exciting projects!
## Notice
We support Unity 2020 and 2021 (newer versions likely work, but aren't currently part of our test pipeline).
We officially support Unity 2021.3 or newer.
Features:
- Receive Speckle Objects at Editor or Runtime
- Send Speckle Objects at Runtime (editor support in the works!)
- Send Speckle Objects at Editor or Runtime
- Material override/substitution
- Automatic receiving changes
Currently tested on Windows and MacOS. Experimental support for Android [in the works](https://github.com/specklesystems/speckle-unity/issues/68).
Currently tested on Windows, Linux, and MacOS.
## Sample project
This repo holds a simple sample project (Speckle Playground). Simply [download this repo](https://github.com/specklesystems/speckle-unity/archive/refs/heads/main.zip)
or clone with git, and open in Unity 2020.3.
Android will work [with some signficant limitations](https://github.com/specklesystems/speckle-unity/issues/68), and other platforms likly work with similar limitations.
## Sample Project
This repo holds a simple sample project (Speckle Playground), containing an example GUI (UnityUI) for fetching stream/branch data, and sending/receiving geometry to/from Speckle.
Simply [download this repo](https://github.com/specklesystems/speckle-unity/archive/refs/heads/main.zip)
or clone with git, and open in Unity 2021.3 or newer.
```
git clone https://github.com/specklesystems/speckle-unity.git
```
The sample project contains an example GUI (UnityUI) for fetching stream/branch data, and receiving/sending geometry to Speckle.
## Installation
## Installation (Package)
To install the connector into your own Unity project (rather than using the sample project), open the Package Manager (`Windows -> Package Manager`)
and select **Add Package from git URL**. (requires [git](https://git-scm.com/downloads) installed)
@@ -59,18 +75,18 @@ We encourage everyone interested to hack / contribute / debug / give feedback to
### Requirements
- Unity 2020.3+
- Have created an account on [speckle.xyz](https://speckle.xyz) (or your own server)
- Unity 2021 or greater
- Have created an account on [app.speckle.systems](https://app.speckle.systems) (or your own server)
- Installed [Speckle Manager](https://speckle.guide/user/manager.html) (recommended, otherwise you'll need to implement your own authentication system in Unity)
### Dependencies
All dependencies to Speckle Core have been included; compiled in the Asset folder until we figure out how to best reference Core.
All dependencies to Speckle Core have been included; compiled in `systems.speckle.speckle-unity` package.
## Contributing
Please make sure you read the [contribution guidelines](.github/CONTRIBUTING.md) for an overview of the best practices we try to follow.
Please make sure you read the [contribution guidelines](https://github.com/specklesystems/speckle-sharp/blob/main/.github/CONTRIBUTING.md) for an overview of the best practices we try to follow.
## License