This commit is contained in:
Jedd Morgan 2023-08-31 12:08:35 +01:00
Родитель 5d92e12eff
Коммит aa46d49620
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: BBD1F7EA4F833F16
40 изменённых файлов: 1851 добавлений и 1396 удалений

Просмотреть файл

@ -2,11 +2,8 @@ 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]
@ -23,7 +20,7 @@ public class SendChildrenToSpeckle : MonoBehaviour
{
sender = GetComponent<Sender>();
}
[ContextMenu(nameof(Send))]
public void Send()
{
@ -31,30 +28,32 @@ public class SendChildrenToSpeckle : MonoBehaviour
.Where(t => t != this.transform)
.Select(o => o.gameObject)
.ToImmutableHashSet();
Debug.Log("starting send...");
sender.Send(streamId, selected, null, branchName, createCommit,
sender.Send(
streamId,
selected,
null,
branchName,
createCommit,
onErrorAction: OnError,
onProgressAction: OnProgress,
onDataSentAction: OnSent);
onDataSentAction: OnSent
);
}
private void OnSent(string objectId)
{
Debug.Log($"Data sent {objectId}", this);
}
private void OnError(string message, Exception e)
{
Debug.LogError($"Error while sending {message} \n {e}", this);
}
private void OnProgress(ConcurrentDictionary<string, int> dict)
{
Debug.Log($"progress was made", this);
}
}

Просмотреть файл

@ -1,220 +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
{
{
[Obsolete]
public class InteractionLogic : MonoBehaviour
{
private Receiver receiver;
public void InitReceiver(Stream stream, bool autoReceive)
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);
});
}
}
}
}

Просмотреть файл

@ -10,125 +10,123 @@ using Stream = Speckle.Core.Api.Stream;
namespace Speckle.ConnectorUnity
{
[Obsolete]
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()
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);
}
}
}
}

Просмотреть файл

@ -13,28 +13,29 @@ namespace Speckle.ConnectorUnity.Components.Editor
[CustomEditor(typeof(SpeckleReceiver))]
public class SpeckleReceiverEditor : UnityEditor.Editor
{
private static bool generateAssets = false;
private bool foldOutStatus = true;
private Texture2D? previewImage;
private static bool _generateAssets = false;
private bool _foldOutStatus = true;
private Texture2D? _previewImage;
public override async void OnInspectorGUI()
{
var speckleReceiver = (SpeckleReceiver)target;
DrawDefaultInspector();
//Preview image
{
foldOutStatus = EditorGUILayout.Foldout(foldOutStatus, "Preview Image");
if (foldOutStatus)
_foldOutStatus = EditorGUILayout.Foldout(_foldOutStatus, "Preview Image");
if (_foldOutStatus)
{
Rect rect = GUILayoutUtility.GetAspectRect(7f / 4f);
if (previewImage != null) GUI.DrawTexture(rect, previewImage);
if (_previewImage != null)
GUI.DrawTexture(rect, _previewImage);
}
}
//TODO: Draw events in a collapsed region
//Receive settings
{
bool prev = GUI.enabled;
@ -42,10 +43,10 @@ namespace Speckle.ConnectorUnity.Components.Editor
//Receive button
bool userRequestedReceive = GUILayout.Button("Receive!");
bool selection = EditorGUILayout.ToggleLeft("Generate Assets", generateAssets);
if (generateAssets != selection)
bool selection = EditorGUILayout.ToggleLeft("Generate Assets", _generateAssets);
if (_generateAssets != selection)
{
generateAssets = selection;
_generateAssets = selection;
UpdateGenerateAssets();
}
GUI.enabled = prev;
@ -54,14 +55,19 @@ namespace Speckle.ConnectorUnity.Components.Editor
{
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);
var rect = EditorGUILayout.GetControlRect(
false,
EditorGUIUtility.singleLineHeight
);
EditorGUI.ProgressBar(rect, value, $"{percent}%");
}
else if (userRequestedReceive)
{
var id = Progress.Start(
"Receiving Speckle data",
"Fetching commit data", Progress.Options.Sticky);
"Fetching commit data",
Progress.Options.Sticky
);
Progress.ShowDetails();
try
@ -86,21 +92,20 @@ namespace Speckle.ConnectorUnity.Components.Editor
}
}
}
public void OnEnable()
{
Init();
}
public void Reset()
{
Init();
}
private void Init()
{
var speckleReceiver = (SpeckleReceiver) target;
var speckleReceiver = (SpeckleReceiver)target;
UpdatePreviewImage();
speckleReceiver.OnCommitSelectionChange.AddListener(_ => UpdatePreviewImage());
UpdateGenerateAssets();
@ -108,31 +113,35 @@ namespace Speckle.ConnectorUnity.Components.Editor
private void UpdatePreviewImage()
{
previewImage = null;
((SpeckleReceiver)target).GetPreviewImage(t => previewImage = t);
_previewImage = null;
((SpeckleReceiver)target).GetPreviewImage(t => _previewImage = t);
}
private async Task ReceiveSelection(int progressId)
{
var speckleReceiver = (SpeckleReceiver)target;
bool shouldCancel = false;
Progress.RegisterCancelCallback(progressId, () =>
{
speckleReceiver.Cancel();
shouldCancel = true;
return true;
});
Progress.RegisterCancelCallback(
progressId,
() =>
{
speckleReceiver.Cancel();
shouldCancel = true;
return true;
}
);
Base commitObject;
try
{
var token = speckleReceiver.BeginOperation();
commitObject = await Task.Run(async () => await ReceiveCommit(progressId).ConfigureAwait(false),
token
)
.ConfigureAwait(true);
commitObject = await Task.Run(
async () => await ReceiveCommit(progressId).ConfigureAwait(false),
token
)
.ConfigureAwait(true);
}
finally
{
@ -142,32 +151,42 @@ namespace Speckle.ConnectorUnity.Components.Editor
int childrenConverted = 0;
int childrenFailed = 0;
int totalChildren = (int) Math.Min(commitObject.totalChildrenCount, int.MaxValue);
int totalChildren = (int)Math.Min(commitObject.totalChildrenCount, int.MaxValue);
float totalChildrenFloat = commitObject.totalChildrenCount;
var convertProgress = Progress.Start("Converting To Native", "Preparing...", Progress.Options.Indefinite | Progress.Options.Sticky, progressId);
var convertProgress = Progress.Start(
"Converting To Native",
"Preparing...",
Progress.Options.Indefinite | Progress.Options.Sticky,
progressId
);
bool BeforeConvert(TraversalContext context)
{
Base b = context.current;
//NOTE: progress wont reach 100% because not all objects are convertable
float progress = (childrenConverted + childrenFailed) / totalChildrenFloat;
if (shouldCancel) return false;
if (shouldCancel)
return false;
shouldCancel = EditorUtility.DisplayCancelableProgressBar(
"Converting To Native...",
$"{b.speckle_type} - {b.id}",
progress);
progress
);
return !shouldCancel;
}
foreach (var conversionResult in speckleReceiver.Converter.RecursivelyConvertToNative_Enumerable(
commitObject,
speckleReceiver.transform,
BeforeConvert))
foreach (
var conversionResult in speckleReceiver.Converter.RecursivelyConvertToNative_Enumerable(
commitObject,
speckleReceiver.transform,
BeforeConvert
)
)
{
Base speckleObject = conversionResult.SpeckleObject;
if (conversionResult.WasSuccessful(out _, out var ex))
@ -179,31 +198,45 @@ namespace Speckle.ConnectorUnity.Components.Editor
childrenFailed++;
Debug.LogWarning(
$"Failed to convert Speckle object of type {speckleObject.speckle_type}\n{ex}",
this);
this
);
}
Progress.Report(progressId, childrenConverted + childrenFailed, totalChildren, "Receiving objects");
Progress.Report(
progressId,
childrenConverted + childrenFailed,
totalChildren,
"Receiving objects"
);
if (shouldCancel) break;
if (shouldCancel)
break;
}
var resultString = $"{childrenConverted} {nameof(GameObject)}s created";
if (childrenFailed != 0) resultString += $", {childrenFailed} objects failed to convert!";
Debug.Log(shouldCancel
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);
speckleReceiver
);
Progress.Finish(convertProgress);
if (shouldCancel) throw new OperationCanceledException("Conversion operation canceled through editor dialogue");
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);
}
private async Task<Base> ReceiveCommit(int progressId)
@ -211,9 +244,19 @@ namespace Speckle.ConnectorUnity.Components.Editor
var speckleReceiver = (SpeckleReceiver)target;
string serverLogName = speckleReceiver.Account.Client?.ServerUrl ?? "Speckle";
int transport = Progress.Start($"Downloading data from {serverLogName}", "Waiting...", Progress.Options.Sticky, progressId);
int deserialize = Progress.Start("Deserializing data", "Waiting...", Progress.Options.Sticky, progressId);
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;
@ -222,7 +265,7 @@ namespace Speckle.ConnectorUnity.Components.Editor
totalObjectCount = count;
Progress.Report(progressId, 0, totalObjectCount, "Receiving objects");
}
void OnProgress(ConcurrentDictionary<string, int> dict)
{
bool r = dict.TryGetValue("RemoteTransport", out int rtProgress);
@ -230,14 +273,24 @@ namespace Speckle.ConnectorUnity.Components.Editor
if (r || l)
{
var fetched = (rtProgress + ltProgress);
Progress.Report(transport, fetched, totalObjectCount, $"{fetched}/{totalObjectCount}");
Progress.Report(
transport,
fetched,
totalObjectCount,
$"{fetched}/{totalObjectCount}"
);
}
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);
Progress.Report(
deserialize,
tsProgress,
totalObjectCount,
$"{tsProgress}/{totalObjectCount}"
);
Progress.Report(progressId, tsProgress, totalObjectCount);
}
}
@ -246,18 +299,19 @@ namespace Speckle.ConnectorUnity.Components.Editor
{
speckleReceiver.OnTotalChildrenCountKnown.AddListener(OnTotalChildrenKnown);
speckleReceiver.OnReceiveProgressAction.AddListener(OnProgress);
commitObject = await speckleReceiver.ReceiveAsync(speckleReceiver.CancellationToken)
commitObject = await speckleReceiver
.ReceiveAsync(speckleReceiver.CancellationToken)
.ConfigureAwait(false);
Progress.Finish(transport);
Progress.Finish(deserialize);
}
catch(OperationCanceledException)
catch (OperationCanceledException)
{
Progress.Finish(transport, Progress.Status.Canceled);
Progress.Finish(deserialize, Progress.Status.Canceled);
throw;
}
catch(Exception)
catch (Exception)
{
Progress.Finish(transport, Progress.Status.Failed);
Progress.Finish(deserialize, Progress.Status.Failed);
@ -287,12 +341,13 @@ namespace Speckle.ConnectorUnity.Components.Editor
go.AddComponent<SpeckleReceiver>();
go.AddComponent<ReceiveFromURL>();
go.AddComponent<SpeckleSender>();
#if UNITY_2021_2_OR_NEWER
var icon = AssetDatabase.LoadAssetAtPath<Texture2D>("Packages/systems.speckle.speckle-unity/Editor/Gizmos/logo128.png");
var icon = AssetDatabase.LoadAssetAtPath<Texture2D>(
"Packages/systems.speckle.speckle-unity/Editor/Gizmos/logo128.png"
);
EditorGUIUtility.SetIconForObject(go, icon);
#endif
}
}
}

Просмотреть файл

@ -12,36 +12,40 @@ using Component = UnityEngine.Component;
#nullable enable
namespace Speckle.ConnectorUnity.Components.Editor
{
public enum SelectionFilter
{
[Tooltip("Convert all children of this GameObject")]
Children,
[Tooltip("Convert GameObjects currently selected in the hierarchy (consider padlocking this inspector)")]
[Tooltip(
"Convert GameObjects currently selected in the hierarchy (consider padlocking this inspector)"
)]
Selection,
[InspectorName("All (excl. disabled)")]
[Tooltip("Convert all GameObjects (excluding disabled) in the active scene")]
Enabled,
[Tooltip("Convert all GameObjects (including disabled) in the active scene")]
[InspectorName("All (incl. disabled)")]
All,
}
[CustomEditor(typeof(SpeckleSender))]
[CanEditMultipleObjects]
public class SpeckleSendEditor : UnityEditor.Editor
{
private SelectionFilter selectedFilter = SelectionFilter.Children;
private SelectionFilter _selectedFilter = SelectionFilter.Children;
public override async void OnInspectorGUI()
{
//Draw events in a collapsed region
DrawDefaultInspector();
bool shouldSend = GUILayout.Button("Send!");
selectedFilter = (SelectionFilter)EditorGUILayout.EnumPopup("Selection", selectedFilter);
_selectedFilter = (SelectionFilter)
EditorGUILayout.EnumPopup("Selection", _selectedFilter);
if (shouldSend)
{
await ConvertAndSend();
@ -50,30 +54,35 @@ namespace Speckle.ConnectorUnity.Components.Editor
public async Task<string?> ConvertAndSend()
{
var speckleSender = (SpeckleSender) target;
var speckleSender = (SpeckleSender)target;
if (!speckleSender.GetSelection(out _, out _, out _, out string? error))
{
Debug.LogWarning($"Not ready to send: {error}", speckleSender);
return null;
}
RecursiveConverter converter = speckleSender.Converter;
Base data = selectedFilter switch
Base data = _selectedFilter switch
{
SelectionFilter.All => ConvertAll(converter),
SelectionFilter.Enabled => ConvertEnabled(converter),
SelectionFilter.Children => ConvertChildren(converter),
SelectionFilter.Selection => ConvertSelection(converter),
_ => throw new InvalidEnumArgumentException(nameof(selectedFilter), (int) selectedFilter, selectedFilter.GetType()),
_
=> throw new InvalidEnumArgumentException(
nameof(_selectedFilter),
(int)_selectedFilter,
_selectedFilter.GetType()
),
};
//TODO onError action?
if (data["@objects"] is IList l && l.Count == 0)
{
Debug.LogWarning($"Nothing to send", speckleSender);
return null;
}
}
return await speckleSender.SendDataAsync(data, true);
}
@ -81,31 +90,36 @@ namespace Speckle.ConnectorUnity.Components.Editor
private Base ConvertChildren(RecursiveConverter converter)
{
return converter.RecursivelyConvertToSpeckle(
new []{((Component)target).gameObject},
_ => true);
new[] { ((Component)target).gameObject },
_ => true
);
}
private Base ConvertSelection(RecursiveConverter converter)
{
ISet<GameObject> selection = Selection.GetFiltered<GameObject>(SelectionMode.Deep).ToImmutableHashSet();
ISet<GameObject> selection = Selection
.GetFiltered<GameObject>(SelectionMode.Deep)
.ToImmutableHashSet();
return converter.RecursivelyConvertToSpeckle(
SceneManager.GetActiveScene().GetRootGameObjects(),
go => selection.Contains(go));
SceneManager.GetActiveScene().GetRootGameObjects(),
go => selection.Contains(go)
);
}
private Base ConvertAll(RecursiveConverter converter)
{
return converter.RecursivelyConvertToSpeckle(
SceneManager.GetActiveScene().GetRootGameObjects(),
_ => true);
SceneManager.GetActiveScene().GetRootGameObjects(),
_ => true
);
}
private Base ConvertEnabled(RecursiveConverter converter)
{
return converter.RecursivelyConvertToSpeckle(
SceneManager.GetActiveScene().GetRootGameObjects(),
go => go.activeInHierarchy);
SceneManager.GetActiveScene().GetRootGameObjects(),
go => go.activeInHierarchy
);
}
}
}

Просмотреть файл

@ -20,10 +20,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 +98,6 @@ namespace Speckle.ConnectorUnity.Components.Editor
private List<Branch> Branches
{
get => _streamManager.Branches;
set => _streamManager.Branches = value;
}
@ -142,7 +141,11 @@ namespace Speckle.ConnectorUnity.Components.Editor
SelectedStream = Streams[i];
EditorUtility.DisplayProgressBar("Loading stream details...", "", 0);
Branches = await Client.StreamGetBranches(SelectedStream.id, BranchesLimit, CommitsLimit);
Branches = await Client.StreamGetBranches(
SelectedStream.id,
BranchesLimit,
CommitsLimit
);
if (Branches.Any())
{
SelectedBranchIndex = 0;
@ -155,7 +158,6 @@ namespace Speckle.ConnectorUnity.Components.Editor
EditorUtility.ClearProgressBar();
}
private async Task Receive()
{
var transport = new ServerTransport(SelectedAccount, SelectedStream.id);
@ -163,53 +165,82 @@ namespace Speckle.ConnectorUnity.Components.Editor
try
{
Commit selectedCommit = Branches[SelectedBranchIndex].commits.items[SelectedCommitIndex];
Commit selectedCommit = Branches[SelectedBranchIndex].commits.items[
SelectedCommitIndex
];
// Receive Speckle Objects
var @base = await Operations.Receive(
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, 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},
});
Analytics.TrackEvent(
SelectedAccount,
Analytics.Events.Receive,
new Dictionary<string, object>()
{
{ "mode", nameof(StreamManagerEditor) },
{
"sourceHostApp",
HostApplications
.GetHostAppFromString(selectedCommit.sourceApplication)
.Slug
},
{ "sourceHostAppVersion", selectedCommit.sourceApplication ?? "" },
{ "hostPlatform", Application.platform.ToString() },
{ "isMultiplayer", selectedCommit.authorId != SelectedAccount.userInfo.id },
}
);
//Convert Speckle Objects
int childrenConverted = 0;
void BeforeConvertCallback(Base b)
{
EditorUtility.DisplayProgressBar("Converting To Native...", $"{b.speckle_type} - {b.id}",
Convert.ToSingle(childrenConverted++ / _totalChildrenCount));
EditorUtility.DisplayProgressBar(
"Converting To Native...",
$"{b.speckle_type} - {b.id}",
Convert.ToSingle(childrenConverted++ / _totalChildrenCount)
);
}
var go = _streamManager.ConvertRecursivelyToNative(@base,
Branches[SelectedBranchIndex].commits.items[SelectedCommitIndex].id, BeforeConvertCallback);
_streamManager.ConvertRecursivelyToNative(
@base,
Branches[SelectedBranchIndex].commits.items[SelectedCommitIndex].id,
BeforeConvertCallback
);
// Read Receipt
await Client.CommitReceived(new CommitReceivedInput
{
streamId = SelectedStream.id,
commitId = Branches[SelectedBranchIndex].commits.items[SelectedCommitIndex].id,
message = $"received commit from {HostApplications.Unity.Name} Editor",
sourceApplication = HostApplications.Unity.Name
});
await Client.CommitReceived(
new CommitReceivedInput
{
streamId = SelectedStream.id,
commitId = Branches[SelectedBranchIndex].commits.items[
SelectedCommitIndex
].id,
message = $"received commit from {HostApplications.Unity.Name} Editor",
sourceApplication = HostApplications.Unity.Name
}
);
}
catch (Exception e)
{
@ -223,8 +254,7 @@ namespace Speckle.ConnectorUnity.Components.Editor
public override async void OnInspectorGUI()
{
_streamManager = (StreamManager) target;
_streamManager = (StreamManager)target;
#region Account GUI
@ -234,12 +264,15 @@ namespace Speckle.ConnectorUnity.Components.Editor
return;
}
EditorGUILayout.BeginHorizontal();
SelectedAccountIndex = EditorGUILayout.Popup("Accounts", SelectedAccountIndex,
SelectedAccountIndex = EditorGUILayout.Popup(
"Accounts",
SelectedAccountIndex,
Accounts.Select(x => x.userInfo.email + " | " + x.serverInfo.name).ToArray(),
GUILayout.ExpandWidth(true), GUILayout.Height(20));
GUILayout.ExpandWidth(true),
GUILayout.Height(20)
);
if (OldSelectedAccountIndex != SelectedAccountIndex)
{
@ -255,26 +288,37 @@ namespace Speckle.ConnectorUnity.Components.Editor
EditorGUILayout.EndHorizontal();
#region Speckle Account Info
_foldOutAccount = EditorGUILayout.BeginFoldoutHeaderGroup(_foldOutAccount, "Account Info");
_foldOutAccount = EditorGUILayout.BeginFoldoutHeaderGroup(
_foldOutAccount,
"Account Info"
);
if (_foldOutAccount)
{
EditorGUI.BeginDisabledGroup(true);
EditorGUILayout.TextField("Name", SelectedAccount.userInfo.name,
EditorGUILayout.TextField(
"Name",
SelectedAccount.userInfo.name,
GUILayout.Height(20),
GUILayout.ExpandWidth(true));
GUILayout.ExpandWidth(true)
);
EditorGUILayout.TextField("Server", SelectedAccount.serverInfo.name,
EditorGUILayout.TextField(
"Server",
SelectedAccount.serverInfo.name,
GUILayout.Height(20),
GUILayout.ExpandWidth(true));
GUILayout.ExpandWidth(true)
);
EditorGUILayout.TextField("URL", SelectedAccount.serverInfo.url,
EditorGUILayout.TextField(
"URL",
SelectedAccount.serverInfo.url,
GUILayout.Height(20),
GUILayout.ExpandWidth(true));
GUILayout.ExpandWidth(true)
);
EditorGUI.EndDisabledGroup();
}
@ -292,9 +336,13 @@ namespace Speckle.ConnectorUnity.Components.Editor
EditorGUILayout.BeginHorizontal();
SelectedStreamIndex = EditorGUILayout.Popup("Streams",
SelectedStreamIndex, Streams.Select(x => x.name).ToArray(), GUILayout.Height(20),
GUILayout.ExpandWidth(true));
SelectedStreamIndex = EditorGUILayout.Popup(
"Streams",
SelectedStreamIndex,
Streams.Select(x => x.name).ToArray(),
GUILayout.Height(20),
GUILayout.ExpandWidth(true)
);
if (OldSelectedStreamIndex != SelectedStreamIndex)
{
@ -319,23 +367,29 @@ namespace Speckle.ConnectorUnity.Components.Editor
EditorGUILayout.BeginHorizontal();
SelectedBranchIndex = EditorGUILayout.Popup("Branches",
SelectedBranchIndex, Branches.Select(x => x.name).ToArray(), GUILayout.Height(20),
GUILayout.ExpandWidth(true));
SelectedBranchIndex = EditorGUILayout.Popup(
"Branches",
SelectedBranchIndex,
Branches.Select(x => x.name).ToArray(),
GUILayout.Height(20),
GUILayout.ExpandWidth(true)
);
EditorGUILayout.EndHorizontal();
if (!Branches[SelectedBranchIndex].commits.items.Any())
return;
EditorGUILayout.BeginHorizontal();
SelectedCommitIndex = EditorGUILayout.Popup("Commits",
SelectedCommitIndex = EditorGUILayout.Popup(
"Commits",
SelectedCommitIndex,
Branches[SelectedBranchIndex].commits.items.Select(x => $"{x.message} - {x.id}").ToArray(),
Branches[SelectedBranchIndex].commits.items
.Select(x => $"{x.message} - {x.id}")
.ToArray(),
GUILayout.Height(20),
GUILayout.ExpandWidth(true));
GUILayout.ExpandWidth(true)
);
EditorGUILayout.EndHorizontal();
@ -347,12 +401,12 @@ namespace Speckle.ConnectorUnity.Components.Editor
GUILayout.Label("Generate assets");
GUILayout.FlexibleSpace();
bool selection = GUILayout.Toggle(generateAssets, "");
if (generateAssets != selection)
bool selection = GUILayout.Toggle(_generateAssets, "");
if (_generateAssets != selection)
{
generateAssets = selection;
_streamManager.RC.AssetCache.nativeCaches = NativeCacheFactory.GetDefaultNativeCacheSetup(generateAssets);
_generateAssets = selection;
_streamManager.RC.AssetCache.nativeCaches =
NativeCacheFactory.GetDefaultNativeCacheSetup(_generateAssets);
}
EditorGUILayout.EndHorizontal();

Просмотреть файл

@ -1,8 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using Speckle.Core.Models;
using UnityEditor;
using UnityEngine;
@ -18,23 +16,28 @@ namespace Speckle.ConnectorUnity.NativeCache.Editor
public const string DefaultPath = "Assets/Resources";
public string path = DefaultPath;
private MemoryNativeCache readCache;
private MemoryNativeCache _readCache;
#nullable enable
void Awake()
{
readCache = CreateInstance<MemoryNativeCache>();
_readCache = CreateInstance<MemoryNativeCache>();
}
public override bool TryGetObject<T>(Base speckleObject, [NotNullWhen(true)] out T? nativeObject) where T : class
public override bool TryGetObject<T>(
Base speckleObject,
[NotNullWhen(true)] out T? nativeObject
)
where T : class
{
if(readCache.TryGetObject(speckleObject, out nativeObject))
if (_readCache.TryGetObject(speckleObject, out nativeObject))
return true;
Type nativeType = typeof(T);
if (!GetAssetPath(nativeType, speckleObject, out string? assetPath)) return false;
if (!GetAssetPath(nativeType, speckleObject, out string? assetPath))
return false;
nativeObject = AssetDatabase.LoadAssetAtPath<T>(assetPath);
return nativeObject != null;
}
@ -43,32 +46,39 @@ namespace Speckle.ConnectorUnity.NativeCache.Editor
{
return WriteObject(speckleObject, nativeObject);
}
private bool WriteObject(Base speckleObject, Object nativeObject)
{
Type nativeType = nativeObject.GetType();
if (!GetAssetPath(nativeType, speckleObject, out string? assetPath)) return false;
if (!GetAssetPath(nativeType, speckleObject, out string? assetPath))
return false;
// Special case for GameObjects, we want to use PrefabUtility
if (nativeObject is GameObject go)
{
var prefab = PrefabUtility.SaveAsPrefabAssetAndConnect(go, assetPath, InteractionMode.AutomatedAction);
return readCache.TrySaveObject(speckleObject, prefab);
var prefab = PrefabUtility.SaveAsPrefabAssetAndConnect(
go,
assetPath,
InteractionMode.AutomatedAction
);
return _readCache.TrySaveObject(speckleObject, prefab);
}
// Exit early if there's already an asset
Object? existing = AssetDatabase.LoadAssetAtPath(assetPath, nativeObject.GetType());
if (existing != null)
{
Debug.LogWarning($"Failed to write asset as one already existed at path: {assetPath}", this);
Debug.LogWarning(
$"Failed to write asset as one already existed at path: {assetPath}",
this
);
return false;
}
AssetDatabase.CreateAsset(nativeObject, $"{assetPath}");
return readCache.TrySaveObject(speckleObject, nativeObject);
return _readCache.TrySaveObject(speckleObject, nativeObject);
}
public override void BeginWrite()
{
base.BeginWrite();
@ -77,32 +87,40 @@ namespace Speckle.ConnectorUnity.NativeCache.Editor
public override void FinishWrite()
{
if (!isWriting) return;
if (!isWriting)
return;
//AssetDatabase.StopAssetEditing();
AssetDatabase.SaveAssets();
if (readCache != null) readCache.LoadedAssets.Clear();
if (_readCache != null)
_readCache.LoadedAssets.Clear();
base.FinishWrite();
}
private bool GetAssetPath(Type nativeType, Base speckleObject, [NotNullWhen(true)] out string? outPath)
private bool GetAssetPath(
Type nativeType,
Base speckleObject,
[NotNullWhen(true)] out string? outPath
)
{
string? folder = AssetHelpers.GetAssetFolder(nativeType, path);
outPath = null;
if (folder == null) return false;
if (!CreateDirectory(folder)) return false;
if (folder == null)
return false;
if (!CreateDirectory(folder))
return false;
string assetName = AssetHelpers.GetAssetName(speckleObject, nativeType);
outPath = $"{folder}/{assetName}";
return true;
}
private static bool CreateDirectory(string directoryPath)
{
if (Directory.Exists(directoryPath))
return true;
var info = Directory.CreateDirectory(directoryPath);
AssetDatabase.Refresh();
return info.Exists;
@ -111,16 +129,20 @@ namespace Speckle.ConnectorUnity.NativeCache.Editor
[ContextMenu("SetPath")]
public void SetPath_Menu()
{
var selection = EditorUtility.OpenFolderPanel("Set Assets Path", "Assets/Resources", "");
if (selection.StartsWith(Application.dataPath)) {
var selection = EditorUtility.OpenFolderPanel(
"Set Assets Path",
"Assets/Resources",
""
);
if (selection.StartsWith(Application.dataPath))
{
path = "Assets" + selection.Substring(Application.dataPath.Length);
}
else
{
Debug.LogError($"Expected selection to be within {Application.dataPath}");
}
}
}
}

Просмотреть файл

@ -13,8 +13,10 @@ namespace Speckle.ConnectorUnity.Wrappers.Selection.Editor
public sealed class AccountSelectionDrawer : OptionSelectionDrawer<Account>
{
protected override bool DisplayRefresh => true;
protected override string FormatOption(Account o) => $"{o.userInfo.email} | {o.serverInfo.name}";
protected override string FormatOption(Account o) =>
$"{o.userInfo.email} | {o.serverInfo.name}";
public AccountSelectionDrawer()
{
details = new (string, Func<Account, string>)[]
@ -29,17 +31,18 @@ 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),
@ -51,33 +54,31 @@ namespace Speckle.ConnectorUnity.Wrappers.Selection.Editor
};
}
}
[CustomPropertyDrawer(typeof(BranchSelection))]
public sealed class BranchSelectionDrawer : OptionSelectionDrawer<Branch>
{
protected override bool DisplayRefresh => true;
protected override string FormatOption(Branch o) => $"{o.name}";
public BranchSelectionDrawer()
{
properties = new []
properties = new[]
{
$"<{nameof(BranchSelection.BranchesLimit)}>k__BackingField",
$"<{nameof(BranchSelection.CommitsLimit)}>k__BackingField",
};
details = new (string, Func<Branch, string>)[]
{
("Description", s => s.description),
};
details = new (string, Func<Branch, string>)[] { ("Description", s => s.description), };
}
}
[CustomPropertyDrawer(typeof(CommitSelection))]
public sealed class CommitSelectionDrawer : OptionSelectionDrawer<Commit>
{
protected override string FormatOption(Commit o) => $"{o.message} - {o.id}";
public CommitSelectionDrawer()
{
details = new (string, Func<Commit, string>)[]
@ -90,13 +91,14 @@ namespace Speckle.ConnectorUnity.Wrappers.Selection.Editor
};
}
}
public abstract class OptionSelectionDrawer<TOption> : PropertyDrawer where TOption : class
public abstract class OptionSelectionDrawer<TOption> : PropertyDrawer
where TOption : class
{
private const float RefreshButtonWidthScale = 0.2f;
private const float PrefixIndentation = 100f;
protected readonly float DetailsTextHeight = EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
protected readonly float DetailsTextHeight =
EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
protected virtual bool DisplayRefresh => false;
protected abstract string FormatOption(TOption o);
@ -105,7 +107,7 @@ namespace Speckle.ConnectorUnity.Wrappers.Selection.Editor
protected string[] properties = { };
protected (string, Func<TOption, string>)[] details = { };
private string[] GetFormattedOptions(TOption[] options)
{
int optionsCount = options.Length;
@ -118,7 +120,12 @@ namespace Speckle.ConnectorUnity.Wrappers.Selection.Editor
return choices;
}
protected virtual void OnGUIDetails(Rect position, SerializedProperty property, GUIContent label, TOption? selection)
protected virtual void OnGUIDetails(
Rect position,
SerializedProperty property,
GUIContent label,
TOption? selection
)
{
position.height = DetailsTextHeight;
@ -142,80 +149,106 @@ namespace Speckle.ConnectorUnity.Wrappers.Selection.Editor
EditorGUI.EndDisabledGroup();
EditorGUI.indentLevel--;
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
EditorGUI.BeginProperty(position, label, property);
var t = (OptionSelection<TOption>)fieldInfo.GetValue(property.serializedObject.targetObject);
var t =
(OptionSelection<TOption>)
fieldInfo.GetValue(property.serializedObject.targetObject);
var selectionRect = position;
var selectionRect = position;
selectionRect.x += PrefixIndentation + 5;
selectionRect.width -= PrefixIndentation + 5;
TOption? selectedOption = t.Selected;
// Options selection
{
var popupSize = DisplayRefresh
? new Rect(selectionRect.x, selectionRect.y, selectionRect.width * (1-RefreshButtonWidthScale), DetailsTextHeight)
? new Rect(
selectionRect.x,
selectionRect.y,
selectionRect.width * (1 - RefreshButtonWidthScale),
DetailsTextHeight
)
: selectionRect;
string selectedChoice = selectedOption != null ? FormatOption(selectedOption) : "";
if (GUI.Button(popupSize, selectedChoice, EditorStyles.popup))
{
var windowPos = GUIUtility.GUIToScreenPoint(Event.current.mousePosition);
var provider = ScriptableObject.CreateInstance<StringListSearchProvider>();
provider.Title = typeof(TOption).Name;
provider.listItems = GetFormattedOptions(t.Options);;
provider.onSetIndexCallback = o => { t.SelectedIndex = o;};
provider.listItems = GetFormattedOptions(t.Options);
;
provider.onSetIndexCallback = o =>
{
t.SelectedIndex = o;
};
SearchWindow.Open(new SearchWindowContext(windowPos), provider);
}
// Optional refresh
if (DisplayRefresh)
{
var buttonSize = new Rect(selectionRect.x + popupSize.width , selectionRect.y, selectionRect.width * RefreshButtonWidthScale, DetailsTextHeight);
var buttonSize = new Rect(
selectionRect.x + popupSize.width,
selectionRect.y,
selectionRect.width * RefreshButtonWidthScale,
DetailsTextHeight
);
if (GUI.Button(buttonSize, "Refresh"))
{
EditorApplication.delayCall += t.RefreshOptions;
}
}
}
// Collapsable details
{
{
int visiblePropCount = property.isExpanded ? GUIDetailsPropertyCount : 0;
var detailsHeight = new Vector2(PrefixIndentation, DetailsTextHeight + visiblePropCount * DetailsTextHeight);
var foldoutRect = new Rect(position.position, detailsHeight);
property.isExpanded = EditorGUI.BeginFoldoutHeaderGroup(foldoutRect, property.isExpanded, label);
var detailsHeight = new Vector2(
PrefixIndentation,
DetailsTextHeight + visiblePropCount * DetailsTextHeight
);
var foldoutRect = new Rect(position.position, detailsHeight);
property.isExpanded = EditorGUI.BeginFoldoutHeaderGroup(
foldoutRect,
property.isExpanded,
label
);
if (property.isExpanded)
{
OnGUIDetails(position, property, label, selectedOption);
}
EditorGUI.EndFoldoutHeaderGroup();
}
EditorGUI.EndProperty();
//EditorUtility.SetDirty(property.serializedObject.targetObject);
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
var standardHeight = EditorGUIUtility.singleLineHeight;
if (!property.isExpanded) return standardHeight + EditorGUIUtility.standardVerticalSpacing;
var detailsHeight = GUIDetailsPropertyCount * (standardHeight + EditorGUIUtility.standardVerticalSpacing);
return standardHeight + detailsHeight + EditorGUIUtility.standardVerticalSpacing + EditorGUIUtility.standardVerticalSpacing;
if (!property.isExpanded)
return standardHeight + EditorGUIUtility.standardVerticalSpacing;
var detailsHeight =
GUIDetailsPropertyCount
* (standardHeight + EditorGUIUtility.standardVerticalSpacing);
return standardHeight
+ detailsHeight
+ EditorGUIUtility.standardVerticalSpacing
+ EditorGUIUtility.standardVerticalSpacing;
}
}
#nullable disable
#nullable disable
public sealed class StringListSearchProvider : ScriptableObject, ISearchWindowProvider
{
@ -223,12 +256,13 @@ namespace Speckle.ConnectorUnity.Wrappers.Selection.Editor
public string[] listItems;
public Action<int> onSetIndexCallback;
public List<SearchTreeEntry> CreateSearchTree(SearchWindowContext context)
{
List<SearchTreeEntry> searchList = new(listItems.Length + 1) {new SearchTreeGroupEntry(new GUIContent(Title), 0)};
for(int i = 0; i < listItems.Length; i++)
List<SearchTreeEntry> searchList =
new(listItems.Length + 1) { new SearchTreeGroupEntry(new GUIContent(Title), 0) };
for (int i = 0; i < listItems.Length; i++)
{
SearchTreeEntry entry = new SearchTreeEntry(new GUIContent(listItems[i]))
{
@ -237,18 +271,15 @@ namespace Speckle.ConnectorUnity.Wrappers.Selection.Editor
};
searchList.Add(entry);
}
return searchList;
}
public bool OnSelectEntry(SearchTreeEntry SearchTreeEntry, SearchWindowContext context)
{
onSetIndexCallback?.Invoke((int)SearchTreeEntry.userData);
return true;
}
}
}

Просмотреть файл

@ -8,7 +8,6 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Sentry;
using Speckle.ConnectorUnity.Components;
using Speckle.ConnectorUnity.Utils;
@ -17,184 +16,201 @@ 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))]
[Obsolete]
public class Receiver : MonoBehaviour
{
public string StreamId;
public string BranchName = "main";
public Stream Stream;
public int TotalChildrenCount = 0;
public GameObject ReceivedData;
private bool AutoReceive;
private bool DeleteOld;
private Action<ConcurrentDictionary<string, int>> OnProgressAction;
private Action<string, Exception> OnErrorAction;
private Action<int> OnTotalChildrenCountKnown;
private Action<GameObject> OnDataReceivedAction;
private Client Client { get; set; }
public Receiver()
{
}
/// <summary>
/// Initializes the Receiver manually
/// A Speckle Receiver, it's a wrapper around a basic Speckle Client
/// that handles conversions and subscriptions for you
/// </summary>
/// <param name="streamId">Id of the stream to receive</param>
/// <param name="autoReceive">If true, it will automatically receive updates sent to this stream</param>
/// <param name="deleteOld">If true, it will delete previously received objects when new one are received</param>
/// <param name="account">Account to use, if null the default account will be used</param>
/// <param name="onDataReceivedAction">Action to run after new data has been received and converted</param>
/// <param name="onProgressAction">Action to run when there is download/conversion progress</param>
/// <param name="onErrorAction">Action to run on error</param>
/// <param name="onTotalChildrenCountKnown">Action to run when the TotalChildrenCount is known</param>
public void Init(string streamId, bool autoReceive = false, bool deleteOld = true, Account account = null,
Action<GameObject> onDataReceivedAction = null, Action<ConcurrentDictionary<string, int>> onProgressAction = null,
Action<string, Exception> onErrorAction = null, Action<int> onTotalChildrenCountKnown = null)
[RequireComponent(typeof(RecursiveConverter))]
[Obsolete]
public class Receiver : MonoBehaviour
{
StreamId = streamId;
AutoReceive = autoReceive;
DeleteOld = deleteOld;
OnDataReceivedAction = onDataReceivedAction;
OnErrorAction = onErrorAction;
OnProgressAction = onProgressAction;
OnTotalChildrenCountKnown = onTotalChildrenCountKnown;
public string StreamId;
public string BranchName = "main";
public Stream Stream;
public int TotalChildrenCount = 0;
public GameObject ReceivedData;
Client = new Client(account ?? AccountManager.GetDefaultAccount());
private bool AutoReceive;
private bool DeleteOld;
private Action<ConcurrentDictionary<string, int>> OnProgressAction;
private Action<string, Exception> OnErrorAction;
private Action<int> OnTotalChildrenCountKnown;
private Action<GameObject> OnDataReceivedAction;
private Client Client { get; set; }
if (AutoReceive)
{
Client.SubscribeCommitCreated(StreamId);
Client.OnCommitCreated += Client_OnCommitCreated;
}
}
public Receiver() { }
/// <summary>
/// Gets and converts the data of the last commit on the Stream
/// </summary>
/// <returns></returns>
public void Receive()
{
if (Client == null || string.IsNullOrEmpty(StreamId))
throw new Exception("Receiver has not been initialized. Please call Init().");
Task.Run(async () =>
{
try
/// <summary>
/// Initializes the Receiver manually
/// </summary>
/// <param name="streamId">Id of the stream to receive</param>
/// <param name="autoReceive">If true, it will automatically receive updates sent to this stream</param>
/// <param name="deleteOld">If true, it will delete previously received objects when new one are received</param>
/// <param name="account">Account to use, if null the default account will be used</param>
/// <param name="onDataReceivedAction">Action to run after new data has been received and converted</param>
/// <param name="onProgressAction">Action to run when there is download/conversion progress</param>
/// <param name="onErrorAction">Action to run on error</param>
/// <param name="onTotalChildrenCountKnown">Action to run when the TotalChildrenCount is known</param>
public void Init(
string streamId,
bool autoReceive = false,
bool deleteOld = true,
Account account = null,
Action<GameObject> onDataReceivedAction = null,
Action<ConcurrentDictionary<string, int>> onProgressAction = null,
Action<string, Exception> onErrorAction = null,
Action<int> onTotalChildrenCountKnown = null
)
{
var mainBranch = await Client.BranchGet(StreamId, BranchName, 1);
if (!mainBranch.commits.items.Any())
throw new Exception("This branch has no commits");
var commit = mainBranch.commits.items[0];
GetAndConvertObject(commit.referencedObject, commit.id, commit.sourceApplication, commit.authorId);
StreamId = streamId;
AutoReceive = autoReceive;
DeleteOld = deleteOld;
OnDataReceivedAction = onDataReceivedAction;
OnErrorAction = onErrorAction;
OnProgressAction = onProgressAction;
OnTotalChildrenCountKnown = onTotalChildrenCountKnown;
Client = new Client(account ?? AccountManager.GetDefaultAccount());
if (AutoReceive)
{
Client.SubscribeCommitCreated(StreamId);
Client.OnCommitCreated += Client_OnCommitCreated;
}
}
catch (Exception e)
/// <summary>
/// Gets and converts the data of the last commit on the Stream
/// </summary>
/// <returns></returns>
public void Receive()
{
throw new SpeckleException(e.Message, e, true, SentryLevel.Error);
if (Client == null || string.IsNullOrEmpty(StreamId))
throw new Exception("Receiver has not been initialized. Please call Init().");
Task.Run(async () =>
{
try
{
var mainBranch = await Client.BranchGet(StreamId, BranchName, 1);
if (!mainBranch.commits.items.Any())
throw new Exception("This branch has no commits");
var commit = mainBranch.commits.items[0];
GetAndConvertObject(
commit.referencedObject,
commit.id,
commit.sourceApplication,
commit.authorId
);
}
catch (Exception e)
{
throw new SpeckleException(e.Message, e, true, SentryLevel.Error);
}
});
}
});
}
#region private methods
#region private methods
/// <summary>
/// Fired when a new commit is created on this stream
/// It receives and converts the objects and then executes the user defined _onCommitCreated action.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected virtual void Client_OnCommitCreated(object sender, CommitInfo e)
{
if (e.branchName == BranchName)
{
Debug.Log("New commit created");
GetAndConvertObject(e.objectId, e.id, e.sourceApplication, e.authorId);
}
}
private async void GetAndConvertObject(string objectId, string commitId, string sourceApplication, string authorId)
{
try
{
var transport = new ServerTransport(Client.Account, StreamId);
var @base = await Operations.Receive(
objectId,
remoteTransport: transport,
onErrorAction: OnErrorAction,
onProgressAction: OnProgressAction,
onTotalChildrenCountKnown: OnTotalChildrenCountKnown,
disposeTransports: true
);
Analytics.TrackEvent(Client.Account, Analytics.Events.Receive, new Dictionary<string, object>()
/// <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)
{
{"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(() =>
if (e.branchName == BranchName)
{
Debug.Log("New commit created");
GetAndConvertObject(e.objectId, e.id, e.sourceApplication, e.authorId);
}
}
private async void GetAndConvertObject(
string objectId,
string commitId,
string sourceApplication,
string authorId
)
{
var root = new GameObject()
{
name = 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
);
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
Analytics.TrackEvent(
Client.Account,
Analytics.Events.Receive,
new Dictionary<string, object>()
{
{ "mode", nameof(Receiver) },
{
"sourceHostApp",
HostApplications.GetHostAppFromString(sourceApplication).Slug
},
{ "sourceHostAppVersion", sourceApplication ?? "" },
{ "hostPlatform", Application.platform.ToString() },
{
"isMultiplayer",
authorId != null && authorId != Client.Account.userInfo.id
},
}
);
Dispatcher
.Instance()
.Enqueue(() =>
{
var root = new GameObject() { name = commitId, };
var rc = GetComponent<RecursiveConverter>();
var go = rc.RecursivelyConvertToNative(@base, root.transform);
//remove previously received object
if (DeleteOld && ReceivedData != null)
Destroy(ReceivedData);
ReceivedData = root;
OnDataReceivedAction?.Invoke(root);
});
}
catch (Exception e)
{
throw new SpeckleException(e.Message, e, true, SentryLevel.Error);
}
try
{
await Client.CommitReceived(
new CommitReceivedInput
{
streamId = StreamId,
commitId = commitId,
message = $"received commit from {Application.unityVersion}",
sourceApplication = HostApplications.Unity.GetVersion(
CoreUtils.GetHostAppVersion()
)
}
);
}
catch
{
// Do nothing!
}
}
private void OnDestroy()
{
streamId = StreamId,
commitId = commitId,
message = $"received commit from {Application.unityVersion}",
sourceApplication = HostApplications.Unity.GetVersion(CoreUtils.GetHostAppVersion())
});
}
catch
{
// Do nothing!
}
Client?.CommitCreatedSubscription?.Dispose();
}
#endregion
}
private void OnDestroy()
{
Client?.CommitCreatedSubscription?.Dispose();
}
#endregion
}
}

Просмотреть файл

@ -16,135 +16,150 @@ using UnityEngine.SceneManagement;
namespace Speckle.ConnectorUnity
{
/// <summary>
/// A Speckle Sender, it's a wrapper around a basic Speckle Client
/// that handles conversions for you
/// </summary>
[RequireComponent(typeof(RecursiveConverter)), ExecuteAlways]
[Obsolete]
public class Sender : MonoBehaviour
{
private ServerTransport transport;
private RecursiveConverter converter;
private CancellationTokenSource cancellationTokenSource;
#nullable enable
private void Awake()
{
converter = GetComponent<RecursiveConverter>();
}
/// <summary>
/// Converts and sends the data of the last commit on the Stream
/// A Speckle Sender, it's a wrapper around a basic Speckle Client
/// that handles conversions for you
/// </summary>
/// <param name="streamId">ID of the stream to send to</param>
/// <param name="gameObjects">List of gameObjects to convert and send</param>
/// <param name="account">Account to use. If not provided the default account will be used</param>
/// <param name="branchName">Name of branch to send to</param>
/// <param name="createCommit">When true, will create a commit using the root object</param>
/// <param name="onDataSentAction">Action to run after the data has been sent</param>
/// <param name="onProgressAction">Action to run when there is download/conversion progress</param>
/// <param name="onErrorAction">Action to run on error</param>
/// <exception cref="SpeckleException"></exception>
public void Send(string streamId,
ISet<GameObject> gameObjects,
Account? account = null,
string branchName = "main",
bool createCommit = true,
Action<string>? onDataSentAction = null,
Action<ConcurrentDictionary<string, int>>? onProgressAction = null,
Action<string, Exception>? onErrorAction = null)
[RequireComponent(typeof(RecursiveConverter)), ExecuteAlways]
[Obsolete]
public class Sender : MonoBehaviour
{
try
{
CancelOperations();
cancellationTokenSource = new CancellationTokenSource();
var client = new Client(account ?? AccountManager.GetDefaultAccount());
transport = new ServerTransport(client.Account, streamId);
transport.CancellationToken = cancellationTokenSource.Token;
var rootObjects = SceneManager.GetActiveScene().GetRootGameObjects();
var data = converter.RecursivelyConvertToSpeckle(rootObjects,
o => gameObjects.Contains(o));
SendData(transport, data, client, branchName, createCommit, cancellationTokenSource.Token, onDataSentAction, onProgressAction, onErrorAction);
}
catch (Exception e)
{
throw new SpeckleException(e.ToString(), e, true, SentryLevel.Error);
}
}
private ServerTransport transport;
private RecursiveConverter converter;
private CancellationTokenSource cancellationTokenSource;
#nullable enable
public static void SendData(ServerTransport remoteTransport,
Base data,
Client client,
string branchName,
bool createCommit,
CancellationToken cancellationToken,
Action<string>? onDataSentAction = null,
Action<ConcurrentDictionary<string, int>>? onProgressAction = null,
Action<string, Exception>? onErrorAction = null)
{
Task.Run(async () =>
{
var res = await Operations.Send(
data,
cancellationToken: cancellationToken,
new List<ITransport>() {remoteTransport},
useDefaultCache: true,
disposeTransports: true,
onProgressAction: onProgressAction,
onErrorAction: onErrorAction
);
Analytics.TrackEvent(client.Account, Analytics.Events.Send);
if (createCommit && !cancellationToken.IsCancellationRequested)
private void Awake()
{
long count = data.GetTotalChildrenCount();
await client.CommitCreate(cancellationToken,
new CommitCreateInput
{
streamId = remoteTransport.StreamId,
branchName = branchName,
objectId = res,
message = $"Sent {count} objects from Unity",
sourceApplication = HostApplications.Unity.Name,
totalChildrenCount = (int)count,
});
converter = GetComponent<RecursiveConverter>();
}
onDataSentAction?.Invoke(res);
}, cancellationToken);
/// <summary>
/// Converts and sends the data of the last commit on the Stream
/// </summary>
/// <param name="streamId">ID of the stream to send to</param>
/// <param name="gameObjects">List of gameObjects to convert and send</param>
/// <param name="account">Account to use. If not provided the default account will be used</param>
/// <param name="branchName">Name of branch to send to</param>
/// <param name="createCommit">When true, will create a commit using the root object</param>
/// <param name="onDataSentAction">Action to run after the data has been sent</param>
/// <param name="onProgressAction">Action to run when there is download/conversion progress</param>
/// <param name="onErrorAction">Action to run on error</param>
/// <exception cref="SpeckleException"></exception>
public void Send(
string streamId,
ISet<GameObject> gameObjects,
Account? account = null,
string branchName = "main",
bool createCommit = true,
Action<string>? onDataSentAction = null,
Action<ConcurrentDictionary<string, int>>? onProgressAction = null,
Action<string, Exception>? onErrorAction = null
)
{
try
{
CancelOperations();
cancellationTokenSource = new CancellationTokenSource();
var client = new Client(account ?? AccountManager.GetDefaultAccount());
transport = new ServerTransport(client.Account, streamId);
transport.CancellationToken = cancellationTokenSource.Token;
var rootObjects = SceneManager.GetActiveScene().GetRootGameObjects();
var data = converter.RecursivelyConvertToSpeckle(
rootObjects,
o => gameObjects.Contains(o)
);
SendData(
transport,
data,
client,
branchName,
createCommit,
cancellationTokenSource.Token,
onDataSentAction,
onProgressAction,
onErrorAction
);
}
catch (Exception e)
{
throw new SpeckleException(e.ToString(), e, true, SentryLevel.Error);
}
}
public static void SendData(
ServerTransport remoteTransport,
Base data,
Client client,
string branchName,
bool createCommit,
CancellationToken cancellationToken,
Action<string>? onDataSentAction = null,
Action<ConcurrentDictionary<string, int>>? onProgressAction = null,
Action<string, Exception>? onErrorAction = null
)
{
Task.Run(
async () =>
{
var res = await Operations.Send(
data,
cancellationToken: cancellationToken,
new List<ITransport>() { remoteTransport },
useDefaultCache: true,
disposeTransports: true,
onProgressAction: onProgressAction,
onErrorAction: onErrorAction
);
Analytics.TrackEvent(client.Account, Analytics.Events.Send);
if (createCommit && !cancellationToken.IsCancellationRequested)
{
long count = data.GetTotalChildrenCount();
await client.CommitCreate(
cancellationToken,
new CommitCreateInput
{
streamId = remoteTransport.StreamId,
branchName = branchName,
objectId = res,
message = $"Sent {count} objects from Unity",
sourceApplication = HostApplications.Unity.Name,
totalChildrenCount = (int)count,
}
);
}
onDataSentAction?.Invoke(res);
},
cancellationToken
);
}
private void OnDestroy()
{
CancelOperations();
}
public void CancelOperations()
{
cancellationTokenSource?.Cancel();
transport?.Dispose();
cancellationTokenSource?.Dispose();
}
#region private methods
#endregion
}
private void OnDestroy()
{
CancelOperations();
}
public void CancelOperations()
{
cancellationTokenSource?.Cancel();
transport?.Dispose();
cancellationTokenSource?.Dispose();
}
#region private methods
#endregion
}
}

Просмотреть файл

@ -29,16 +29,18 @@ namespace Speckle.ConnectorUnity.Components
public List<Branch> Branches;
public RecursiveConverter RC { get; private set; }
#nullable enable
private void Awake()
{
RC = GetComponent<RecursiveConverter>();
}
public GameObject ConvertRecursivelyToNative(Base @base, string rootObjectName,
Action<Base>? beforeConvertCallback)
public GameObject ConvertRecursivelyToNative(
Base @base,
string rootObjectName,
Action<Base>? beforeConvertCallback
)
{
var rootObject = new GameObject(rootObjectName);
@ -46,10 +48,9 @@ namespace Speckle.ConnectorUnity.Components
{
beforeConvertCallback?.Invoke(o);
return RC.ConverterInstance.CanConvertToNative(o) //Accept geometry
|| o.speckle_type == nameof(Base) && o.totalChildrenCount > 0; // Or Base objects that have children
|| o.speckle_type == nameof(Base) && o.totalChildrenCount > 0; // Or Base objects that have children
}
// For the rootObject only, we will create property GameObjects
// i.e. revit categories
foreach (var prop in @base.GetMembers())
@ -57,13 +58,15 @@ namespace Speckle.ConnectorUnity.Components
var converted = RC.RecursivelyConvertToNative(prop.Value, null, Predicate);
//Skip empties
if (converted.Count <= 0) continue;
if (converted.Count <= 0)
continue;
var propertyObject = new GameObject(prop.Key);
propertyObject.transform.SetParent(rootObject.transform);
foreach (var o in converted)
{
if (o.transform.parent == null) o.transform.SetParent(propertyObject.transform);
if (o.transform.parent == null)
o.transform.SetParent(propertyObject.transform);
}
}
@ -80,4 +83,4 @@ namespace Speckle.ConnectorUnity.Components
}
#endif
}
}
}

Просмотреть файл

@ -17,11 +17,12 @@ namespace Speckle.ConnectorUnity.Components
{
[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>();
@ -32,38 +33,49 @@ namespace Speckle.ConnectorUnity.Components
{
StartCoroutine(Receive_Routine());
}
public IEnumerator Receive_Routine()
{
if (IsBusy()) throw new InvalidOperationException("A receive operation has already started");
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");
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));
var accountTask = new Utils.Utils.WaitForTask<Account>(
async () => await GetAccount(sw)
);
yield return accountTask;
_tokenSource.Token.ThrowIfCancellationRequested();
using Client c = new(accountTask.Result);
var objectIdTask = new Utils.Utils.WaitForTask<(string, Commit?)>(async () => await GetObjectID(sw, c));
var objectIdTask = new Utils.Utils.WaitForTask<(string, Commit?)>(
async () => await GetObjectID(sw, c)
);
yield return objectIdTask;
(string objectId, Commit? commit) = objectIdTask.Result;
Debug.Log($"Receiving from {sw.ServerUrl}...");
var receiveTask = new Utils.Utils.WaitForTask<Base>(async () => await SpeckleReceiver.ReceiveAsync(
c,
sw.StreamId,
objectId,
commit,
cancellationToken: _tokenSource.Token));
var receiveTask = new Utils.Utils.WaitForTask<Base>(
async () =>
await SpeckleReceiver.ReceiveAsync(
c,
sw.StreamId,
objectId,
commit,
cancellationToken: _tokenSource.Token
)
);
yield return receiveTask;
Debug.Log("Converting to native...");
_converter.RecursivelyConvertToNative_Sync(receiveTask.Result, transform);
}
@ -74,8 +86,10 @@ namespace Speckle.ConnectorUnity.Components
}
}
private async Task<(string objectId, Commit? commit)> GetObjectID(StreamWrapper sw, Client client)
private async Task<(string objectId, Commit? commit)> GetObjectID(
StreamWrapper sw,
Client client
)
{
string objectId;
Commit? commit = null;
@ -95,7 +109,9 @@ namespace Speckle.ConnectorUnity.Components
{
var branchName = string.IsNullOrEmpty(sw.BranchName) ? "main" : sw.BranchName;
var branch = await client.BranchGet(sw.StreamId, branchName, 1).ConfigureAwait(false);
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.");
@ -105,19 +121,23 @@ namespace Speckle.ConnectorUnity.Components
return (objectId, commit);
}
[ContextMenu(nameof(Cancel))]
public void Cancel()
{
if (IsNotBusy()) throw new InvalidOperationException("There are no pending receive operations to 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();
@ -126,7 +146,6 @@ namespace Speckle.ConnectorUnity.Components
_tokenSource?.Cancel();
}
private async Task<Account> GetAccount(StreamWrapper sw)
{
Account account;
@ -134,10 +153,10 @@ namespace Speckle.ConnectorUnity.Components
{
account = await sw.GetAccount().ConfigureAwait(false);
}
catch (SpeckleException e)
catch (SpeckleException)
{
if (string.IsNullOrEmpty(sw.StreamId))
throw e;
throw;
//Fallback to a non authed account
account = new Account

Просмотреть файл

@ -12,7 +12,6 @@ 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>
@ -22,12 +21,12 @@ namespace Speckle.ConnectorUnity.Components
/// 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>
@ -42,7 +41,8 @@ namespace Speckle.ConnectorUnity.Components
public ConversionResult(TraversalContext traversalContext, [NotNull] GameObject? converted)
: this(traversalContext, converted, null)
{
if (converted is null) throw new ArgumentNullException(nameof(converted));
if (converted is null)
throw new ArgumentNullException(nameof(converted));
}
/// <summary>
@ -52,14 +52,22 @@ namespace Speckle.ConnectorUnity.Components
/// <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)
public ConversionResult(
TraversalContext traversalContext,
[NotNull] Exception? exception,
GameObject? converted = null
)
: this(traversalContext, converted, exception)
{
if (exception is null) throw new ArgumentNullException(nameof(exception));
if (exception is null)
throw new ArgumentNullException(nameof(exception));
}
private ConversionResult(TraversalContext traversalContext, GameObject? converted, Exception? exception)
private ConversionResult(
TraversalContext traversalContext,
GameObject? converted,
Exception? exception
)
{
this.traversalContext = traversalContext;
this.converted = converted;
@ -67,28 +75,28 @@ namespace Speckle.ConnectorUnity.Components
}
/// <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)
[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
{
/// <inheritdoc cref="RecursivelyConvertToNative_Enumerable"/>
/// <remarks>Calling this function will perform the conversion process synchronously</remarks>
/// <returns>The conversion result</returns>
@ -100,7 +108,7 @@ namespace Speckle.ConnectorUnity.Components
{
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>
@ -110,12 +118,14 @@ namespace Speckle.ConnectorUnity.Components
Predicate<TraversalContext>? predicate = null
)
{
return StartCoroutine(RecursivelyConvertToNative_Enumerable(rootObject, parent, predicate).GetEnumerator());
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"/>
/// 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>
@ -124,12 +134,13 @@ namespace Speckle.ConnectorUnity.Components
public IEnumerable<ConversionResult> RecursivelyConvertToNative_Enumerable(
Base rootObject,
Transform? parent,
Predicate<TraversalContext>? predicate = null)
Predicate<TraversalContext>? predicate = null
)
{
var userPredicate = predicate ?? (_ => true);
var traversalFunc = DefaultTraversal.CreateBIMTraverseFunc(ConverterInstance);
var objectsToConvert = traversalFunc
.Traverse(rootObject)
.Where(x => ConverterInstance.CanConvertToNative(x.current))
@ -138,7 +149,10 @@ namespace Speckle.ConnectorUnity.Components
Dictionary<Base, GameObject?> created = new();
foreach (var conversionResult in ConvertTree(objectsToConvert, parent, created))
{
if (!isActiveAndEnabled) throw new InvalidOperationException($"Cannot convert objects while {GetType()} is disabled");
if (!isActiveAndEnabled)
throw new InvalidOperationException(
$"Cannot convert objects while {GetType()} is disabled"
);
yield return conversionResult;
}
@ -150,17 +164,21 @@ namespace Speckle.ConnectorUnity.Components
/// 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)
/// 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)
protected IEnumerable<ConversionResult> ConvertTree(
IEnumerable<TraversalContext> objectTree,
Transform? parent,
IDictionary<Base, GameObject?> outCreatedObjects
)
{
InitializeAssetCache();
AssetCache.BeginWrite();
foreach (TraversalContext tc in objectTree)
{
ConversionResult result;
@ -179,49 +197,69 @@ namespace Speckle.ConnectorUnity.Components
yield return result;
}
AssetCache.FinishWrite();
}
protected static Transform? GetParent(TraversalContext? tc, IDictionary<Base, GameObject?> createdObjects)
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)
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");
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))
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 (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));
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)
public IEnumerator ConvertCoroutine(
Base rootObject,
Transform? parent,
List<GameObject> outCreatedObjects,
Func<Base, bool> predicate
)
{
foreach (string propertyName in GetPotentialChildren(rootObject))
{
@ -229,7 +267,7 @@ namespace Speckle.ConnectorUnity.Components
yield return null;
}
}
/// <summary>
/// Given <paramref name="o"/>,
/// will recursively convert any objects in the tree
@ -238,16 +276,20 @@ namespace Speckle.ConnectorUnity.Components
/// <param name="parent">Optional parent transform for the created root <see cref="GameObject"/>s</param>
/// <returns> A list of all created <see cref="GameObject"/>s</returns>
[Obsolete("Use " + nameof(RecursivelyConvertToNative_Sync))]
public virtual List<GameObject> RecursivelyConvertToNative(object? o, Transform? parent)
=> RecursivelyConvertToNative(o, parent, b => ConverterInstance.CanConvertToNative(b));
public virtual List<GameObject> RecursivelyConvertToNative(object? o, Transform? parent) =>
RecursivelyConvertToNative(o, parent, b => ConverterInstance.CanConvertToNative(b));
/// <inheritdoc cref="RecursivelyConvertToNative(object, Transform)"/>
/// <param name="predicate">A function to determine if an object should be converted</param>
[Obsolete("Use " + nameof(RecursivelyConvertToNative_Sync))]
public virtual List<GameObject> RecursivelyConvertToNative(object? o, Transform? parent, Func<Base, bool> predicate)
public virtual List<GameObject> RecursivelyConvertToNative(
object? o,
Transform? parent,
Func<Base, bool> predicate
)
{
InitializeAssetCache();
var createdGameObjects = new List<GameObject>();
try
{
@ -260,10 +302,9 @@ namespace Speckle.ConnectorUnity.Components
}
//TODO track event?
return createdGameObjects;
return createdGameObjects;
}
private void InitializeAssetCache()
@ -277,10 +318,15 @@ namespace Speckle.ConnectorUnity.Components
}
[Obsolete]
public virtual void RecurseTreeToNative(Base baseObject, Transform? parent, Func<Base, bool> predicate, IList<GameObject> outCreatedObjects)
public virtual void RecurseTreeToNative(
Base baseObject,
Transform? parent,
Func<Base, bool> predicate,
IList<GameObject> outCreatedObjects
)
{
object? converted = null;
if(predicate(baseObject))
if (predicate(baseObject))
converted = ConverterInstance.ConvertToNative(baseObject);
// Handle new GameObjects
@ -289,22 +335,23 @@ namespace Speckle.ConnectorUnity.Components
{
outCreatedObjects.Add(go);
nextParent = go.transform;
go.transform.SetParent(parent, true);
//Set some common for all created GameObjects
//TODO add support for more unity specific props
if(go.name == "New Game Object" || string.IsNullOrWhiteSpace(go.name))
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);
@ -313,19 +360,23 @@ 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;
}
[Obsolete]
protected virtual void ConvertChild(object? value, Transform? parent, Func<Base, bool> predicate, IList<GameObject> outCreatedObjects)
protected virtual void ConvertChild(
object? value,
Transform? parent,
Func<Base, bool> predicate,
IList<GameObject> outCreatedObjects
)
{
foreach (Base b in GraphTraversal.TraverseMember(value))
{

Просмотреть файл

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

Просмотреть файл

@ -19,6 +19,7 @@ using UnityEngine;
using UnityEngine.Events;
[assembly: InternalsVisibleTo("Speckle.ConnectorUnity.Components.Editor")]
namespace Speckle.ConnectorUnity.Components
{
[ExecuteAlways]
@ -28,13 +29,13 @@ 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; }
@ -44,12 +45,16 @@ namespace Speckle.ConnectorUnity.Components
[Header("Events")]
[HideInInspector]
public CommitSelectionEvent OnCommitSelectionChange = new();
[HideInInspector]
public OperationProgressEvent OnReceiveProgressAction = new();
[HideInInspector]
public ErrorActionEvent OnErrorAction = new();
[HideInInspector]
public ChildrenCountHandler OnTotalChildrenCountKnown = new();
[HideInInspector]
public ReceiveCompleteHandler OnComplete = new();
@ -66,11 +71,12 @@ namespace Speckle.ConnectorUnity.Components
/// <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()
{
if (CancellationTokenSource == null) return false;
if (CancellationTokenSource == null)
return false;
CancellationTokenSource.Cancel();
return true;
}
/// <summary>
/// Receive the selected <see cref="Commit"/> object, and converts ToNative as children of <paramref name="parent"/>
/// </summary>
@ -79,17 +85,23 @@ namespace Speckle.ConnectorUnity.Components
/// <remarks>function does not throw, instead calls <see cref="OnErrorAction"/>, and calls <see cref="OnComplete"/> upon completion</remarks>
/// <seealso cref="ReceiveAsync(System.Threading.CancellationToken)"/>
/// <seealso cref="RecursiveConverter.RecursivelyConvertToNative_Enumerable"/>
public IEnumerator ReceiveAndConvert_Routine(Transform? parent, Predicate<TraversalContext>? predicate = null)
public IEnumerator ReceiveAndConvert_Routine(
Transform? parent,
Predicate<TraversalContext>? predicate = null
)
{
if (IsReceiving)
{
OnErrorAction.Invoke("Failed to receive", new InvalidOperationException("A pending receive operation has already started"));
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 () =>
{
@ -97,7 +109,7 @@ namespace Speckle.ConnectorUnity.Components
CancellationToken.ThrowIfCancellationRequested();
return result;
});
yield return new WaitUntil(() => receiveOperation.IsCompleted);
if (receiveOperation.IsFaulted)
@ -106,7 +118,7 @@ namespace Speckle.ConnectorUnity.Components
FinishOperation();
yield break;
}
Base b = receiveOperation.Result;
foreach (var _ in Converter.RecursivelyConvertToNative_Enumerable(b, parent, predicate))
@ -118,9 +130,11 @@ namespace Speckle.ConnectorUnity.Components
FinishOperation();
}
/// <inheritdoc cref="ReceiveAndConvert_Routine"/>
public async void ReceiveAndConvert_Async(Transform? parent, Predicate<TraversalContext>? predicate = null)
public async void ReceiveAndConvert_Async(
Transform? parent,
Predicate<TraversalContext>? predicate = null
)
{
try
{
@ -154,7 +168,7 @@ namespace Speckle.ConnectorUnity.Components
public async Task<Base> ReceiveAsync(CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
ValidateSelection(out Client? client, out Stream? stream, out Commit? commit);
Base result = await ReceiveAsync(
@ -167,20 +181,23 @@ namespace Speckle.ConnectorUnity.Components
cancellationToken: cancellationToken
)
.ConfigureAwait(false);
return result;
}
public void ValidateSelection(out Client client, out Stream stream, out Commit commit)
{
Client? selectedClient = Account.Client;
client = selectedClient ?? throw new InvalidOperationException("Invalid account selection");
client =
selectedClient ?? throw new InvalidOperationException("Invalid account selection");
Stream? selectedStream = Stream.Selected;
stream = selectedStream ?? throw new InvalidOperationException("Invalid stream selection");
stream =
selectedStream ?? throw new InvalidOperationException("Invalid stream selection");
Commit? selectedCommit = Commit.Selected;
commit = selectedCommit ?? throw new InvalidOperationException("Invalid commit selection");
commit =
selectedCommit ?? throw new InvalidOperationException("Invalid commit selection");
}
/// <summary>
@ -189,22 +206,26 @@ namespace Speckle.ConnectorUnity.Components
/// <exception cref="InvalidOperationException">already receiving</exception>
protected internal CancellationToken BeginOperation()
{
if (IsReceiving) throw new InvalidOperationException("A pending receive operation has already started");
if (IsReceiving)
throw new InvalidOperationException(
"A pending receive operation has already started"
);
CancellationTokenSource?.Dispose();
CancellationTokenSource = new();
return CancellationTokenSource.Token;
}
protected internal void FinishOperation()
{
if (!IsReceiving) throw new InvalidOperationException("No pending operations to finish");
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>
@ -223,64 +244,90 @@ namespace Speckle.ConnectorUnity.Components
string objectId,
Commit? commit,
Action<ConcurrentDictionary<string, int>>? onProgressAction = null,
Action<int>? onTotalChildrenCountKnown = null,
CancellationToken cancellationToken = default)
Action<int>? onTotalChildrenCountKnown = null,
CancellationToken cancellationToken = default
)
{
using var transport = new ServerTransportV2(client.Account, streamId);
transport.CancellationToken = cancellationToken;
cancellationToken.ThrowIfCancellationRequested();
Base? requestedObject = await Operations.Receive(
objectId: objectId,
cancellationToken: cancellationToken,
remoteTransport: transport,
onProgressAction: onProgressAction,
onErrorAction: (s, ex) =>
{
//Don't wrap cancellation exceptions!
if (ex is OperationCanceledException)
throw ex;
//HACK: Sometimes, the task was cancelled, and Operations.Receive doesn't fail in a reliable way. In this case, the exception is often simply a symptom of a cancel.
if (cancellationToken.IsCancellationRequested)
Base? requestedObject = await Operations
.Receive(
objectId: objectId,
cancellationToken: cancellationToken,
remoteTransport: transport,
onProgressAction: onProgressAction,
onErrorAction: (s, ex) =>
{
SpeckleLog.Logger.Warning(ex, "A task was cancelled, ignoring potentially symptomatic exception");
cancellationToken.ThrowIfCancellationRequested();
}
//Don't wrap cancellation exceptions!
if (ex is OperationCanceledException)
throw ex;
//Treat all operation errors as fatal
throw new SpeckleException($"Failed to receive requested object {objectId} from server: {s}", ex);
},
onTotalChildrenCountKnown: onTotalChildrenCountKnown,
disposeTransports: false
).ConfigureAwait(false);
//HACK: Sometimes, the task was cancelled, and Operations.Receive doesn't fail in a reliable way. In this case, the exception is often simply a symptom of a cancel.
if (cancellationToken.IsCancellationRequested)
{
SpeckleLog.Logger.Warning(
ex,
"A task was cancelled, ignoring potentially symptomatic exception"
);
cancellationToken.ThrowIfCancellationRequested();
}
//Treat all operation errors as fatal
throw new SpeckleException(
$"Failed to receive requested object {objectId} from server: {s}",
ex
);
},
onTotalChildrenCountKnown: onTotalChildrenCountKnown,
disposeTransports: false
)
.ConfigureAwait(false);
Analytics.TrackEvent(
client.Account,
Analytics.Events.Receive,
new Dictionary<string, object>()
{
{ "mode", nameof(SpeckleReceiver) },
{
"sourceHostApp",
HostApplications.GetHostAppFromString(commit?.sourceApplication).Slug
},
{ "sourceHostAppVersion", commit?.sourceApplication ?? "" },
{ "hostPlatform", Application.platform.ToString() },
{
"isMultiplayer",
commit != null && commit.authorId != client.Account.userInfo.id
},
}
);
Analytics.TrackEvent(client.Account, Analytics.Events.Receive, new Dictionary<string, object>()
{
{"mode", nameof(SpeckleReceiver)},
{"sourceHostApp", HostApplications.GetHostAppFromString(commit?.sourceApplication).Slug},
{"sourceHostAppVersion", commit?.sourceApplication ?? ""},
{"hostPlatform", Application.platform.ToString()},
{"isMultiplayer", commit != null && commit.authorId != client.Account.userInfo.id},
});
if (requestedObject == null)
throw new SpeckleException($"Operation {nameof(Operations.Receive)} returned null");
cancellationToken.ThrowIfCancellationRequested();
//Read receipt
try
{
await client.CommitReceived(cancellationToken, new CommitReceivedInput
{
streamId = streamId,
commitId = commit?.id,
message = $"received commit from {Application.unityVersion}",
sourceApplication = HostApplications.Unity.GetVersion(CoreUtils.GetHostAppVersion())
}).ConfigureAwait(false);
await client
.CommitReceived(
cancellationToken,
new CommitReceivedInput
{
streamId = streamId,
commitId = commit?.id,
message = $"received commit from {Application.unityVersion}",
sourceApplication = HostApplications.Unity.GetVersion(
CoreUtils.GetHostAppVersion()
)
}
)
.ConfigureAwait(false);
}
catch (Exception e)
{
@ -299,9 +346,18 @@ namespace Speckle.ConnectorUnity.Components
/// <param name="rootObjectName">The name of the parent <see cref="GameObject"/> to create</param>
/// <param name="beforeConvertCallback">Callback for each object converted</param>
/// <returns>The created parent <see cref="GameObject"/></returns>
[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)
[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);
@ -309,10 +365,9 @@ namespace Speckle.ConnectorUnity.Components
{
beforeConvertCallback?.Invoke(o);
return Converter.ConverterInstance.CanConvertToNative(o) //Accept geometry
|| o.speckle_type == nameof(Base) && o.totalChildrenCount > 0; // Or Base objects that have children
|| o.speckle_type == nameof(Base) && o.totalChildrenCount > 0; // Or Base objects that have children
}
// For the rootObject only, we will create property GameObjects
// i.e. revit categories
foreach (var prop in @base.GetMembers())
@ -320,21 +375,23 @@ 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>
@ -346,27 +403,28 @@ namespace Speckle.ConnectorUnity.Components
[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;
@ -374,29 +432,35 @@ namespace Speckle.ConnectorUnity.Components
error = null;
return true;
}
/// <summary>
/// Fetches the commit preview for the currently selected commit
/// </summary>
/// <param name="allAngles">when <see langword="true"/>, will fetch 360 degree preview image</param>
/// <param name="callback">Callback function to be called when the web request completes</param>
/// <returns>The executing <see cref="Coroutine"/> or <see langword="null"/> if <see cref="Account"/>, <see cref="Stream"/>, or <see cref="Commit"/> was <see langword="null"/></returns>
public Coroutine? GetPreviewImage(/*bool allAngles,*/ Action<Texture2D?> callback)
public Coroutine? GetPreviewImage( /*bool allAngles,*/
Action<Texture2D?> callback
)
{
Account? account = Account.Selected;
if (account == null) return null;
if (account == null)
return null;
string? streamId = Stream.Selected?.id;
if (streamId == null) return null;
if (streamId == null)
return null;
string? commitId = Commit.Selected?.id;
if (commitId == null) return null;
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;
return StartCoroutine(Utils.Utils.GetImageRoutine(url, authToken, callback));
}
#if UNITY_EDITOR
[ContextMenu("Open Speckle Stream in Browser")]
protected void OpenUrlInBrowser()
@ -405,8 +469,7 @@ namespace Speckle.ConnectorUnity.Components
Application.OpenURL(url);
}
#endif
public string GetSelectedUrl()
{
string serverUrl = Account.Selected!.serverInfo.url;
@ -414,12 +477,15 @@ namespace Speckle.ConnectorUnity.Components
string? branchName = Branch.Selected?.name;
string? commitId = Commit.Selected?.id;
if (string.IsNullOrEmpty(streamId)) return serverUrl;
if (!string.IsNullOrEmpty(commitId)) return $"{serverUrl}/streams/{streamId}/commits/{commitId}";
if (!string.IsNullOrEmpty(branchName)) return $"{serverUrl}/streams/{streamId}/branches/{branchName}";
if (string.IsNullOrEmpty(streamId))
return serverUrl;
if (!string.IsNullOrEmpty(commitId))
return $"{serverUrl}/streams/{streamId}/commits/{commitId}";
if (!string.IsNullOrEmpty(branchName))
return $"{serverUrl}/streams/{streamId}/branches/{branchName}";
return $"{serverUrl}/streams/{streamId}";
}
public void Awake()
{
Converter = GetComponent<RecursiveConverter>();
@ -436,12 +502,11 @@ namespace Speckle.ConnectorUnity.Components
Stream.Initialise();
Branch.Initialise();
Commit.Initialise();
Commit.OnSelectionChange =
() => OnCommitSelectionChange?.Invoke(Commit.Selected);
if(Account.Options is not {Length: > 0} || forceRefresh)
Commit.OnSelectionChange = () => OnCommitSelectionChange?.Invoke(Commit.Selected);
if (Account.Options is not { Length: > 0 } || forceRefresh)
Account.RefreshOptions();
}
public void OnDisable()
{
CancellationTokenSource?.Cancel();
@ -456,36 +521,54 @@ namespace Speckle.ConnectorUnity.Components
{
//pass
}
public void OnAfterDeserialize()
{
Initialise();
}
#region Deprecated members
[Obsolete("use " + nameof(ReceiveAndConvertRoutine), true)]
public IEnumerator ReceiveAndConvertRoutine(SpeckleReceiver speckleReceiver, string rootObjectName, Action<Base>? beforeConvertCallback = null)
public IEnumerator ReceiveAndConvertRoutine(
SpeckleReceiver speckleReceiver,
string rootObjectName,
Action<Base>? beforeConvertCallback = null
)
{
// ReSharper disable once MethodSupportsCancellation
Task<Base> receiveOperation = Task.Run(async () => await ReceiveAsync(CancellationToken));
Task<Base> receiveOperation = Task.Run(
async () => await ReceiveAsync(CancellationToken)
);
yield return new WaitUntil(() => receiveOperation.IsCompleted);
Base? b = receiveOperation.Result;
if (b == null) yield break;
//NOTE: coroutine doesn't break for each catergory/object
GameObject go = ConvertToNativeWithCategories(b, rootObjectName, beforeConvertCallback);
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<Transform?> { }
[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> { }
}

Просмотреть файл

@ -23,39 +23,49 @@ namespace Speckle.ConnectorUnity.Components
{
[field: SerializeReference]
public AccountSelection Account { get; private set; }
[field: SerializeReference]
public StreamSelection Stream { get; private set; }
[field: SerializeReference]
public BranchSelection Branch { get; private set; }
public RecursiveConverter Converter { get; private set; }
[Header("Events")]
[HideInInspector]
public BranchSelectionEvent OnBranchSelectionChange;
[HideInInspector]
public ErrorActionEvent OnErrorAction;
[HideInInspector]
public OperationProgressEvent OnSendProgressAction;
#nullable enable
protected internal CancellationTokenSource? CancellationTokenSource { get; private set; }
//TODO runtime sending
public async Task<string> SendDataAsync(Base data, bool createCommit)
{
CancellationTokenSource?.Cancel();
CancellationTokenSource?.Dispose();
CancellationTokenSource = new CancellationTokenSource();
if(!GetSelection(out Client? client, out Stream? stream, out Branch? branch, out string? error))
if (
!GetSelection(
out Client? client,
out Stream? stream,
out Branch? branch,
out string? error
)
)
throw new SpeckleException(error);
ServerTransport transport = new ServerTransport(client.Account, stream.id);
transport.CancellationToken = CancellationTokenSource.Token;
return await SendDataAsync(CancellationTokenSource.Token,
return await SendDataAsync(
CancellationTokenSource.Token,
remoteTransport: transport,
data: data,
client: client,
@ -66,93 +76,114 @@ namespace Speckle.ConnectorUnity.Components
);
}
public static async Task<string> SendDataAsync(CancellationToken cancellationToken,
public static async Task<string> SendDataAsync(
CancellationToken cancellationToken,
ServerTransport remoteTransport,
Base data,
Client client,
string branchName,
bool createCommit,
Action<ConcurrentDictionary<string, int>>? onProgressAction = null,
Action<string, Exception>? onErrorAction = null)
Action<string, Exception>? onErrorAction = null
)
{
string res = await Operations.Send(
data,
cancellationToken: cancellationToken,
new List<ITransport>{remoteTransport},
new List<ITransport> { remoteTransport },
useDefaultCache: true,
disposeTransports: true,
onProgressAction: onProgressAction,
onErrorAction: onErrorAction
);
Analytics.TrackEvent(client.Account, Analytics.Events.Send, new Dictionary<string, object>()
{
{"mode", nameof(SpeckleSender)},
{"hostPlatform", Application.platform.ToString()},
});
Analytics.TrackEvent(
client.Account,
Analytics.Events.Send,
new Dictionary<string, object>()
{
{ "mode", nameof(SpeckleSender) },
{ "hostPlatform", Application.platform.ToString() },
}
);
if (createCommit && !cancellationToken.IsCancellationRequested)
{
string streamId = remoteTransport.StreamId;
string unityVer = $"Unity {Application.unityVersion.Substring(0,6)}";
string unityVer = $"Unity {Application.unityVersion.Substring(0, 6)}";
data.totalChildrenCount = data.GetTotalChildrenCount();
string commitMessage = $"Sent {data.totalChildrenCount} objects from {unityVer}";
string commitId = await CreateCommit(cancellationToken, data, client, streamId, branchName, res, commitMessage);
string commitId = await CreateCommit(
cancellationToken,
data,
client,
streamId,
branchName,
res,
commitMessage
);
string url = $"{client.ServerUrl}/streams/{streamId}/commits/{commitId}";
Debug.Log($"Data successfully sent to <a href=\"{url}\">{url}</a>");
}
return res;
}
public static async Task<string> CreateCommit(CancellationToken cancellationToken,
public static async Task<string> CreateCommit(
CancellationToken cancellationToken,
Base data,
Client client,
string streamId,
string branchName,
string objectId,
string message)
string message
)
{
string commitId = await client.CommitCreate(cancellationToken,
string commitId = await client.CommitCreate(
cancellationToken,
new CommitCreateInput
{
streamId = streamId,
branchName = branchName,
objectId = objectId,
message = message,
sourceApplication = HostApplications.Unity.GetVersion(CoreUtils.GetHostAppVersion()),
sourceApplication = HostApplications.Unity.GetVersion(
CoreUtils.GetHostAppVersion()
),
totalChildrenCount = (int)data.totalChildrenCount,
});
}
);
return commitId;
}
public bool GetSelection(
[NotNullWhen(true)] out Client? client,
[NotNullWhen(true)] out Stream? stream,
[NotNullWhen(true)] out Branch? branch,
[NotNullWhen(false)] out string? error)
[NotNullWhen(false)] out string? error
)
{
Account? account = Account.Selected;
stream = Stream.Selected;
branch = Branch.Selected;
if (account == null)
{
error = "Selected Account is null";
client = null;
return false;
}
client = Account.Client ?? new Client(account);
client = Account.Client ?? new Client(account);
if (stream == null)
{
error = "Selected Stream is null";
return false;
}
if (branch == null)
if (branch == null)
{
error = "Selected Branch is null";
return false;
@ -160,8 +191,7 @@ namespace Speckle.ConnectorUnity.Components
error = null;
return true;
}
#if UNITY_EDITOR
[ContextMenu("Open Speckle Stream in Browser")]
protected void OpenUrlInBrowser()
@ -170,24 +200,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();
@ -198,24 +230,24 @@ namespace Speckle.ConnectorUnity.Components
Stream.Initialise();
Branch.Initialise();
Branch.OnSelectionChange = () => OnBranchSelectionChange?.Invoke(Branch.Selected);
if(Account.Options is not {Length: > 0} || forceRefresh)
if (Account.Options is not { Length: > 0 } || forceRefresh)
Account.RefreshOptions();
}
public void OnDestroy()
{
CancellationTokenSource?.Cancel();
CancellationTokenSource?.Dispose();
}
public void OnBeforeSerialize()
{
//pass
}
public void OnAfterDeserialize()
{
Initialise();
}
}
}

Просмотреть файл

@ -1,6 +1,4 @@
using Objects.BuiltElements;
using Speckle.Core.Kits;
using UnityEditor;
using UnityEngine;
namespace Objects.Converter.Unity

Просмотреть файл

@ -137,7 +137,7 @@ namespace Objects.Converter.Unity
}
var nColors = nativeMesh.colors.Skip(indexOffset).Take(vertexTake).ToArray();
;
List<int> sColors = new List<int>(nColors.Length);
sColors.AddRange(nColors.Select(c => c.ToIntColor()));

Просмотреть файл

@ -46,7 +46,8 @@ namespace Objects.Converter.Unity
}
//Just used as cache key for the default (null) material
private static RenderMaterial defaultMaterialPlaceholder = new() { id = "defaultMaterial" };
private static readonly RenderMaterial DefaultMaterialPlaceholder =
new() { id = "defaultMaterial" };
public virtual Material RenderMaterialToNative(RenderMaterial? renderMaterial)
{
@ -57,13 +58,13 @@ namespace Objects.Converter.Unity
{
if (
!LoadedAssets.TryGetObject(
defaultMaterialPlaceholder,
DefaultMaterialPlaceholder,
out Material? defaultMaterial
)
)
{
defaultMaterial = new Material(OpaqueMaterialShader);
LoadedAssets.TrySaveObject(defaultMaterialPlaceholder, defaultMaterial);
LoadedAssets.TrySaveObject(DefaultMaterialPlaceholder, defaultMaterial);
}
return defaultMaterial;
}
@ -104,9 +105,7 @@ namespace Objects.Converter.Unity
);
}
else if (shader.name == "Lit") //URP lit
{
ShaderHelpers.SetupMaterialWithBlendMode_URP(mat, true, 1);
}
{ }
}
LoadedAssets.TrySaveObject(renderMaterial, mat);

Просмотреть файл

@ -1,12 +1,4 @@
using Objects.Geometry;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Serialization;
using Mesh = Objects.Geometry.Mesh;
using UnityEngine;
namespace Objects.Converter.Unity
{

Просмотреть файл

@ -5,7 +5,6 @@ using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Objects.BuiltElements;
using Objects.Organization;
using Objects.Other;
using Speckle.ConnectorUnity.Utils;
using Speckle.ConnectorUnity.NativeCache;
@ -28,11 +27,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 };
new[] { HostApplications.Unity.Name };
public AbstractNativeCache LoadedAssets { get; private set; }
@ -238,8 +237,6 @@ namespace Objects.Converter.Unity
// return false;
case View3D _:
return shouldConvertViews;
case Model:
return false; //This allows us to traverse older commits pre-collections
case Collection:
return true;
case Mesh:

Просмотреть файл

@ -22,103 +22,117 @@ 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 = null;
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()

Просмотреть файл

@ -14,9 +14,12 @@ namespace Speckle.ConnectorUnity.NativeCache
[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;
public abstract bool TryGetObject<T>(
Base speckleObject,
[NotNullWhen(true)] out T? nativeObject
)
where T : Object;
public abstract bool TrySaveObject(Base speckleObject, Object nativeObject);
@ -41,7 +44,6 @@ namespace Speckle.ConnectorUnity.NativeCache
{
FinishWrite();
}
}
public static class AssetHelpers
@ -49,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");
}
@ -64,9 +66,10 @@ 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);
@ -78,15 +81,18 @@ namespace Speckle.ConnectorUnity.NativeCache
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(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;
}

Просмотреть файл

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

Просмотреть файл

@ -196,4 +196,4 @@ namespace System.Collections.Concurrent
}
#endregion
}
}
}

Просмотреть файл

@ -8,35 +8,35 @@ 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;
}
}
}
}

Просмотреть файл

@ -9,37 +9,41 @@ namespace Speckle.ConnectorUnity.Utils
{
public static void SetupInit()
{
Setup.Init(HostApplications.Unity.GetVersion(GetHostAppVersion()), HostApplications.Unity.Slug);
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
#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
#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);
var prefix =
GetFriendlyObjectName(speckleObject) ?? SimplifiedSpeckleType(speckleObject);
return $"{prefix}{ObjectNameSeparator}{speckleObject.id}";
}
@ -49,13 +53,12 @@ namespace Speckle.ConnectorUnity.Utils
?? 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;

Просмотреть файл

@ -107,15 +107,6 @@ namespace Speckle.ConnectorUnity.Utils
material.renderQueue = defaultRenderQueue;
}
}
public static void SetupMaterialWithBlendMode_URP(
Material material,
bool transparent,
int blendMode
)
{
material.SetFloat("__surface", 1);
material.SetFloat("__blend", blendMode);
}
}
}

Просмотреть файл

@ -4,7 +4,6 @@ using System.Collections;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.Rendering;
namespace Speckle.ConnectorUnity.Utils
{

Просмотреть файл

@ -17,31 +17,33 @@ namespace Speckle.ConnectorUnity.Wrappers.Selection
get
{
Account? account = Selected;
if (account == null) return client = null;
if (client == null || !client.Account.Equals(account)) return client = new Client(account);
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;
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();

Просмотреть файл

@ -9,38 +9,43 @@ namespace Speckle.ConnectorUnity.Wrappers.Selection
[Serializable]
public sealed class BranchSelection : OptionSelection<Branch>
{
[field: SerializeField, Range(1,100), Tooltip("Number of branches to request")]
[field: SerializeField, Range(1, 100), Tooltip("Number of branches to request")]
public int BranchesLimit { get; set; } = 100;
[field: SerializeField, Range(1,100), Tooltip("Number of commits to request")]
[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;
public override void RefreshOptions()
{
Stream? stream = StreamSelection.Selected;
if (stream == null) return;
if (stream == null)
return;
IList<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;
if (branch == null)
return;
List<Commit> commits = branch.commits.items;
GenerateOptions(commits, (_, i) => i == 0);
}

Просмотреть файл

@ -18,7 +18,8 @@ namespace Speckle.ConnectorUnity.Wrappers.Selection
public abstract class OptionSelection<TOption>
where TOption : class
{
[SerializeField] private int selectedIndex = -1;
[SerializeField]
private int selectedIndex = -1;
public int SelectedIndex
{
@ -34,8 +35,10 @@ namespace Speckle.ConnectorUnity.Wrappers.Selection
{
get
{
if (Options is null) return null;
if (SelectedIndex < 0 || SelectedIndex >= Options.Length) return null;
if (Options is null)
return null;
if (SelectedIndex < 0 || SelectedIndex >= Options.Length)
return null;
return Options[SelectedIndex];
}
}
@ -52,22 +55,28 @@ namespace Speckle.ConnectorUnity.Wrappers.Selection
protected void GenerateOptions(IList<TOption> source, Func<TOption, int, bool> isDefault)
{
List<TOption> optionsToAdd = new (source.Count);
List<TOption> optionsToAdd = new(source.Count);
int defaultOption = -1;
int index = 0;
foreach (TOption? a in source)
{
if (a == null) continue;
if (a == null)
continue;
optionsToAdd.Add(a);
if (isDefault(a, index)) defaultOption = index;
if (isDefault(a, index))
defaultOption = index;
index++;
}
TOption? currentSelected = Selected;
bool selectionOutOfRange = SelectedIndex < 0 || SelectedIndex >= optionsToAdd.Count;
if (selectionOutOfRange
|| (currentSelected != null
&& KeyFunction(currentSelected) != KeyFunction(optionsToAdd[SelectedIndex])))
if (
selectionOutOfRange
|| (
currentSelected != null
&& KeyFunction(currentSelected) != KeyFunction(optionsToAdd[SelectedIndex])
)
)
{
selectedIndex = defaultOption;
}

Просмотреть файл

@ -10,16 +10,19 @@ namespace Speckle.ConnectorUnity.Wrappers.Selection
public sealed class StreamSelection : OptionSelection<Stream>
{
private const int DEFAULT_REQUEST_LIMIT = 50;
[field: SerializeField, Range(1,100), Tooltip("Number of streams to request")]
[field: SerializeField, Range(1, 100), Tooltip("Number of streams to request")]
public int StreamsLimit { get; set; } = DEFAULT_REQUEST_LIMIT;
[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;
if (Client == null)
return;
IList<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>();

Просмотреть файл

@ -17,21 +17,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)
{
@ -43,18 +42,17 @@ namespace Speckle.ConnectorUnity.Wrappers
[SerializeField, HideInInspector]
private string _serializedSpeckleType;
private Type _speckleType = typeof(Base);
public Type SpeckleType {
public Type SpeckleType
{
get => _speckleType ??= typeof(Base);
set
{
Debug.Assert(typeof(Base).IsAssignableFrom(value));
Debug.Assert(!value.IsAbstract);
_speckleType = value;
_hasChanged = true;
}
}
public SpeckleProperties()
@ -64,7 +62,7 @@ namespace Speckle.ConnectorUnity.Wrappers
_hasChanged = true;
SpeckleType = typeof(Base);
}
private void CollectionChangeHandler(object sender, NotifyCollectionChangedEventArgs e)
{
_hasChanged = true;
@ -72,21 +70,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()
{
var deserializer = new BaseObjectDeserializerV2();
Base speckleData = deserializer.Deserialize(_serializedData);
Data = speckleData.GetMembers();
_hasChanged = false;
try
{
_speckleType = Type.GetType(_serializedSpeckleType);