diff --git a/Assets/SpecklePlayground.unity b/Assets/SpecklePlayground.unity index 7c8dbdf..d50c7a2 100644 --- a/Assets/SpecklePlayground.unity +++ b/Assets/SpecklePlayground.unity @@ -2064,6 +2064,19 @@ Transform: m_Father: {fileID: 0} m_RootOrder: 6 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &916416845 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 141ce93d2d159c0448b5b8b33b1c0679, type: 3} + m_Name: + m_EditorClassIdentifier: + path: Assets/Resources --- !u!1 &1031574851 GameObject: m_ObjectHideFlags: 0 @@ -2848,6 +2861,21 @@ MonoBehaviour: m_PersistentCalls: m_Calls: [] m_IsOn: 1 +--- !u!114 &1409739885 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 88d6b4f2f80eaa14f9f07505f7e44ec2, type: 3} + m_Name: + m_EditorClassIdentifier: + nativeCaches: + - {fileID: 916416845} + - {fileID: 1951948666} --- !u!1 &1464556211 GameObject: m_ObjectHideFlags: 0 @@ -3844,6 +3872,18 @@ CanvasRenderer: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1903798475} m_CullTransparentMesh: 1 +--- !u!114 &1951948666 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: b3354e8208862c341940152f5340d41a, type: 3} + m_Name: + m_EditorClassIdentifier: --- !u!1 &2014586909 GameObject: m_ObjectHideFlags: 0 @@ -4034,7 +4074,7 @@ GameObject: serializedVersion: 6 m_Component: - component: {fileID: 2060322467} - - component: {fileID: 2060322466} + - component: {fileID: 2060322469} - component: {fileID: 2060322468} m_Layer: 0 m_Name: EditorReceiver @@ -4043,24 +4083,6 @@ GameObject: m_NavMeshLayer: 0 m_StaticEditorFlags: 0 m_IsActive: 1 ---- !u!114 &2060322466 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 2060322465} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 730f6d2eecf16994c918395debc877e7, type: 3} - m_Name: - m_EditorClassIdentifier: - SelectedAccountIndex: 0 - SelectedStreamIndex: 0 - SelectedBranchIndex: 0 - SelectedCommitIndex: 0 - OldSelectedAccountIndex: 0 - OldSelectedStreamIndex: 0 --- !u!4 &2060322467 Transform: m_ObjectHideFlags: 0 @@ -4077,6 +4099,24 @@ Transform: m_RootOrder: 9 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &2060322468 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2060322465} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 730f6d2eecf16994c918395debc877e7, type: 3} + m_Name: + m_EditorClassIdentifier: + SelectedAccountIndex: 0 + SelectedStreamIndex: 15 + SelectedBranchIndex: 0 + SelectedCommitIndex: 0 + OldSelectedAccountIndex: 0 + OldSelectedStreamIndex: 15 +--- !u!114 &2060322469 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} @@ -4088,6 +4128,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: ed6cbf9ce4dca0349997d163ec9bce7e, type: 3} m_Name: m_EditorClassIdentifier: + k__BackingField: {fileID: 1409739885} --- !u!1 &2066864134 GameObject: m_ObjectHideFlags: 0 diff --git a/Packages/manifest.json b/Packages/manifest.json index b4de94e..a7c4885 100644 --- a/Packages/manifest.json +++ b/Packages/manifest.json @@ -1,7 +1,7 @@ { "dependencies": { "com.unity.2d.sprite": "1.0.0", - "com.unity.collab-proxy": "1.17.0", + "com.unity.collab-proxy": "1.17.2", "com.unity.ide.rider": "3.0.15", "com.unity.ide.visualstudio": "2.0.16", "com.unity.ide.vscode": "1.2.5", diff --git a/Packages/packages-lock.json b/Packages/packages-lock.json index 1337dfb..fcfecb2 100644 --- a/Packages/packages-lock.json +++ b/Packages/packages-lock.json @@ -7,7 +7,7 @@ "dependencies": {} }, "com.unity.collab-proxy": { - "version": "1.17.0", + "version": "1.17.2", "depth": 0, "source": "registry", "dependencies": { @@ -55,7 +55,7 @@ "url": "https://packages.unity.com" }, "com.unity.services.core": { - "version": "1.4.0", + "version": "1.4.2", "depth": 1, "source": "registry", "dependencies": { diff --git a/Packages/systems.speckle.speckle-unity/ConverterUnity.Geometry.cs b/Packages/systems.speckle.speckle-unity/ConverterUnity.Geometry.cs index 970d21f..a322a38 100644 --- a/Packages/systems.speckle.speckle-unity/ConverterUnity.Geometry.cs +++ b/Packages/systems.speckle.speckle-unity/ConverterUnity.Geometry.cs @@ -10,6 +10,7 @@ using Speckle.Core.Logging; using Speckle.Core.Models; using UnityEditor; using UnityEngine; +using Object = UnityEngine.Object; using SMesh = Objects.Geometry.Mesh; using Transform = UnityEngine.Transform; using STransform = Objects.Other.Transform; @@ -216,29 +217,21 @@ namespace Objects.Converter.Unity return null; } -#if UNITY_EDITOR - // Check `Resources` for existing cached prefab asset - // TODO: this isn't how we check for existing materials, maybe there's a reason not to call LoadAssetPath constantly - string assetName = $"{GetAssetName(block.blockDefinition)}.prefab" - .Trim(Path.GetInvalidFileNameChars()); - const string assetPath = "Assets/Resources/Prefabs/"; - if (StreamManager.GenerateAssets) //TODO: I don't like how the converter is aware of StreamManager + // Check for existing conversions + if(LoadedAssets.TryGetObject(block.blockDefinition, out GameObject? existingPrefab)) { - GameObject? existing = AssetDatabase.LoadAssetAtPath($"{assetPath}/{assetName}"); - if (existing) - { - var go = (GameObject) PrefabUtility.InstantiatePrefab(existing); - go.name = block.blockDefinition.name ?? ""; - return go; - } - } +#if UNITY_EDITOR + var go = (GameObject) PrefabUtility.InstantiatePrefab(existingPrefab); +#else + var go = Object.Instantiate(existingPrefab); #endif - - // No existing found, so we Convert the block - - GameObject native = new GameObject(block.blockDefinition.name ?? ""); - TransformToNativeTransform(native.transform, block.transform); + go.name = block.blockDefinition.name ?? ""; + return go; + } + // Convert the block definition + GameObject native = new GameObject(block.blockDefinition.name ?? ""); + List meshes = new(); List others = new(); foreach (Base geo in block.blockDefinition.geometry) @@ -262,14 +255,9 @@ namespace Objects.Converter.Unity c.transform.SetParent(native.transform, false); } -#if UNITY_EDITOR - if (StreamManager.GenerateAssets) //TODO: I don't like how the converter is aware of StreamManager - { - CreateDirectoryFromAssetPath(assetPath); - PrefabUtility.SaveAsPrefabAssetAndConnect(native, $"Assets/Resources/Prefabs/{assetName}", - InteractionMode.AutomatedAction); - } -#endif + LoadedAssets.TrySaveObject(block.blockDefinition, native); + + TransformToNativeTransform(native.transform, block.transform); return native; } diff --git a/Packages/systems.speckle.speckle-unity/ConverterUnity.Mesh.cs b/Packages/systems.speckle.speckle-unity/ConverterUnity.Mesh.cs index 1fb664c..c88249a 100644 --- a/Packages/systems.speckle.speckle-unity/ConverterUnity.Mesh.cs +++ b/Packages/systems.speckle.speckle-unity/ConverterUnity.Mesh.cs @@ -6,6 +6,7 @@ using Objects.Other; using Objects.Utils; using PlasticPipe.Certificates; using Speckle.ConnectorUnity; +using Speckle.ConnectorUnity.NativeCache; using Speckle.Core.Models; using UnityEditor; using UnityEngine; @@ -207,7 +208,7 @@ namespace Objects.Converter.Unity /// /// Converts multiple (e.g. with different materials) into one native mesh /// - /// Root element who's name/id is used to identify the mesh + /// The object being converted /// Collection of es that shall be converted /// A with the converted , , and public GameObject? MeshesToNative(Base element, IReadOnlyCollection meshes) @@ -222,10 +223,10 @@ namespace Objects.Converter.Unity Material[] nativeMaterials = RenderMaterialsToNative(meshes); Vector3 center; - if (LoadedAssets.TryGetValue(element.id, out var existingObj) - && existingObj is Mesh existing) + if (LoadedAssets.TryGetObject(element, out Mesh? existing)) { nativeMesh = existing; + //todo This is pretty inefficient, having to the mesh data anyway just to get the center... eek MeshDataToNative(meshes, out List verts, out _, @@ -236,22 +237,15 @@ namespace Objects.Converter.Unity else { MeshToNativeMesh(meshes, out nativeMesh, out center); - string name = GetAssetName(element); + string name = AssetHelpers.GetObjectName(element); nativeMesh.name = name; -#if UNITY_EDITOR - if (StreamManager.GenerateAssets) //TODO: I don't like how the converter is aware of StreamManager - { - const string assetPath = "Assets/Resources/Meshes/Speckle Generated/"; - CreateDirectoryFromAssetPath(assetPath); - AssetDatabase.CreateAsset(nativeMesh, $"{assetPath}/{name}"); - } -#endif + LoadedAssets.TrySaveObject(element, nativeMesh); } var go = new GameObject(); go.transform.position = center; go.SafeMeshSet(nativeMesh, nativeMaterials); - + return go; } @@ -461,19 +455,14 @@ namespace Objects.Converter.Unity //todo support more complex materials var shader = Shader.Find("Standard"); Material mat = new Material(shader); - - //if a renderMaterial is passed use that, otherwise try get it from the mesh itself + + // 1. If no renderMaterial was passed, use default material if (renderMaterial == null) return mat; - // 1. match material by name, if any - string materialName = string.IsNullOrWhiteSpace(renderMaterial.name) - ? $"material-{renderMaterial.id}" - : renderMaterial.name.Replace('/', '-'); - - if (LoadedAssets.TryGetValue(materialName, out Object asset) - && asset is Material loadedMaterial) return loadedMaterial; - - // 2. re-create material by setting diffuse color and transparency on standard shaders + // 2. Try get existing/override material from asset cache + if (LoadedAssets.TryGetObject(renderMaterial, out Material? loadedMaterial)) return loadedMaterial; + + // 3. Otherwise, convert fresh! if (renderMaterial.opacity < 1) { shader = Shader.Find("Transparent/Diffuse"); @@ -482,51 +471,19 @@ namespace Objects.Converter.Unity var c = renderMaterial.diffuse.ToUnityColor(); mat.color = new Color(c.r, c.g, c.b, (float) renderMaterial.opacity); - mat.name = materialName; + mat.name = AssetHelpers.GetObjectName(renderMaterial);; mat.SetFloat(Metallic, (float) renderMaterial.metalness); mat.SetFloat(Glossiness, 1 - (float) renderMaterial.roughness); if (renderMaterial.emissive != SColor.Black.ToArgb()) mat.EnableKeyword("_EMISSION"); mat.SetColor(EmissionColor, renderMaterial.emissive.ToUnityColor()); - -#if UNITY_EDITOR - if (StreamManager.GenerateAssets) //TODO: I don't like how the converter is aware of StreamManager - { - var invalidChars = Path.GetInvalidFileNameChars(); - string name = new(mat.name.Where(x => !invalidChars.Contains(x)).ToArray()); - - const string assetPath = "Assets/Resources/Materials/Speckle Generated/"; - CreateDirectoryFromAssetPath(assetPath); - - if (AssetDatabase.LoadAllAssetsAtPath($"{assetPath}/{name}.mat") - .Length == 0) - AssetDatabase.CreateAsset(mat, $"{assetPath}/{name}.mat"); - - } -#endif - + LoadedAssets.TrySaveObject(renderMaterial, mat); + return mat; - // 3. if not renderMaterial was passed, the default shader will be used - } - - protected static string GetAssetName(Base b, bool alwaysIncludeId = true) - { - var invalidChars = Path.GetInvalidFileNameChars(); - string id = b.id; - foreach (var nameAlias in new[] {"name", "Name"}) - { - string? rawName = b[nameAlias] as string; - if (string.IsNullOrWhiteSpace(rawName)) continue; - - string name = new(rawName.Where(x => !invalidChars.Contains(x)).ToArray()); - - return alwaysIncludeId ? $"{name} - {id}" : name; - } - - return id; } + #endregion } diff --git a/Packages/systems.speckle.speckle-unity/ConverterUnity.cs b/Packages/systems.speckle.speckle-unity/ConverterUnity.cs index b78cd98..1ac5d09 100644 --- a/Packages/systems.speckle.speckle-unity/ConverterUnity.cs +++ b/Packages/systems.speckle.speckle-unity/ConverterUnity.cs @@ -5,10 +5,10 @@ using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text; using Objects.BuiltElements; using Objects.Other; using Speckle.ConnectorUnity; +using Speckle.ConnectorUnity.NativeCache; using UnityEditor; using UnityEngine; using Mesh = Objects.Geometry.Mesh; @@ -32,14 +32,14 @@ namespace Objects.Converter.Unity public IEnumerable GetServicedApplications() => new string[] {VersionedHostApplications.Unity}; - public Dictionary LoadedAssets { get; private set; } + public AbstractNativeCache LoadedAssets { get; private set; } public void SetContextDocument(object doc) { - if (doc is not Dictionary loadedAssets) + if (doc is not AbstractNativeCache context) throw new ArgumentException( $"Expected {nameof(doc)} to be of type {typeof(Dictionary)}", nameof(doc)); - LoadedAssets = loadedAssets; + LoadedAssets = context; } public void SetContextObjects(List objects) => throw new NotImplementedException(); diff --git a/Packages/systems.speckle.speckle-unity/Editor/Speckle.Connector.Editor.asmdef b/Packages/systems.speckle.speckle-unity/Editor/Speckle.Connector.Editor.asmdef index 8cb237a..0f58715 100644 --- a/Packages/systems.speckle.speckle-unity/Editor/Speckle.Connector.Editor.asmdef +++ b/Packages/systems.speckle.speckle-unity/Editor/Speckle.Connector.Editor.asmdef @@ -2,7 +2,8 @@ "name": "Speckle.Connector.Editor", "rootNamespace": "Speckle.ConnectorUnity", "references": [ - "GUID:eed1b8b83e2c0074d9e5de2348e3ff72" + "GUID:eed1b8b83e2c0074d9e5de2348e3ff72", + "GUID:05078f9b6da40444fbd72ec600449925" ], "includePlatforms": [ "Editor" diff --git a/Packages/systems.speckle.speckle-unity/Editor/StreamManagerEditor.cs b/Packages/systems.speckle.speckle-unity/Editor/StreamManagerEditor.cs index 41b8824..40359b8 100644 --- a/Packages/systems.speckle.speckle-unity/Editor/StreamManagerEditor.cs +++ b/Packages/systems.speckle.speckle-unity/Editor/StreamManagerEditor.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Sentry; +using Speckle.ConnectorUnity.NativeCache; using Speckle.Core.Api; using Speckle.Core.Credentials; using Speckle.Core.Kits; @@ -14,336 +15,353 @@ using UnityEngine; namespace Speckle.ConnectorUnity.Editor { - [CustomEditor(typeof(StreamManager))] - [CanEditMultipleObjects] - public class StreamManagerEditor : UnityEditor.Editor - { - private bool _foldOutAccount; - private int _totalChildrenCount = 0; - private StreamManager _streamManager; - - public int StreamsLimit { get; set; } = 30; - public int BranchesLimit { get; set; } = 30; - public int CommitsLimit { get; set; } = 25; - - private int SelectedAccountIndex + [CustomEditor(typeof(StreamManager))] + [CanEditMultipleObjects] + public class StreamManagerEditor : UnityEditor.Editor { - get => _streamManager.SelectedAccountIndex; - set => _streamManager.SelectedAccountIndex = value; - } + private bool _foldOutAccount; + private int _totalChildrenCount = 0; + private StreamManager _streamManager; - private int SelectedStreamIndex - { - get => _streamManager.SelectedStreamIndex; - set => _streamManager.SelectedStreamIndex = value; - } + private static bool generateAssets; - private int SelectedBranchIndex - { - get => _streamManager.SelectedBranchIndex; - set => _streamManager.SelectedBranchIndex = value; - } + public int StreamsLimit { get; set; } = 30; + public int BranchesLimit { get; set; } = 30; + public int CommitsLimit { get; set; } = 25; - private int SelectedCommitIndex - { - get => _streamManager.SelectedCommitIndex; - set => _streamManager.SelectedCommitIndex = value; - } - - private int OldSelectedAccountIndex - { - get => _streamManager.OldSelectedAccountIndex; - set => _streamManager.OldSelectedAccountIndex = value; - } - - private int OldSelectedStreamIndex - { - get => _streamManager.OldSelectedStreamIndex; - set => _streamManager.OldSelectedStreamIndex = value; - } - - private Client Client - { - get => _streamManager.Client; - set => _streamManager.Client = value; - } - - private Account SelectedAccount - { - get => _streamManager.SelectedAccount; - set => _streamManager.SelectedAccount = value; - } - - private Stream SelectedStream - { - get => _streamManager.SelectedStream; - set => _streamManager.SelectedStream = value; - } - - public List Accounts - { - get => _streamManager.Accounts; - set => _streamManager.Accounts = value; - } - - private List Streams - { - get => _streamManager.Streams; - set => _streamManager.Streams = value; - } - - private List Branches - { - get => _streamManager.Branches; - - set => _streamManager.Branches = value; - } - - private async Task LoadAccounts() - { - //refresh accounts just in case - Accounts = AccountManager.GetAccounts().ToList(); - if (!Accounts.Any()) - { - Debug.Log("No Accounts found, please login in Manager"); - } - else - { - await SelectAccount(0); - } - } - - private async Task SelectAccount(int i) - { - SelectedAccountIndex = i; - OldSelectedAccountIndex = i; - SelectedAccount = Accounts[i]; - - Client = new Client(SelectedAccount); - await LoadStreams(); - } - - private async Task LoadStreams() - { - EditorUtility.DisplayProgressBar("Loading streams...", "", 0); - Streams = await Client.StreamsGet(StreamsLimit); - EditorUtility.ClearProgressBar(); - if (Streams.Any()) - await SelectStream(0); - } - - private async Task SelectStream(int i) - { - SelectedStreamIndex = i; - OldSelectedStreamIndex = i; - SelectedStream = Streams[i]; - - EditorUtility.DisplayProgressBar("Loading stream details...", "", 0); - Branches = await Client.StreamGetBranches(SelectedStream.id, BranchesLimit, CommitsLimit); - if (Branches.Any()) - { - SelectedBranchIndex = 0; - if (Branches[SelectedBranchIndex].commits.items.Any()) + private int SelectedAccountIndex { - SelectedCommitIndex = 0; + get => _streamManager.SelectedAccountIndex; + set => _streamManager.SelectedAccountIndex = value; } - } - EditorUtility.ClearProgressBar(); - } + private int SelectedStreamIndex + { + get => _streamManager.SelectedStreamIndex; + set => _streamManager.SelectedStreamIndex = value; + } + private int SelectedBranchIndex + { + get => _streamManager.SelectedBranchIndex; + set => _streamManager.SelectedBranchIndex = value; + } - private async Task Receive() - { - var transport = new ServerTransport(SelectedAccount, SelectedStream.id); - EditorUtility.DisplayProgressBar($"Receiving data from {transport.BaseUri}...", "", 0); + private int SelectedCommitIndex + { + get => _streamManager.SelectedCommitIndex; + set => _streamManager.SelectedCommitIndex = value; + } - try - { - // Receive Speckle Objects - var @base = await Operations.Receive( - Branches[SelectedBranchIndex].commits.items[SelectedCommitIndex].referencedObject, - remoteTransport: transport, - onProgressAction: dict => - { - UnityEditor.EditorApplication.delayCall += () => + private int OldSelectedAccountIndex + { + get => _streamManager.OldSelectedAccountIndex; + set => _streamManager.OldSelectedAccountIndex = value; + } + + private int OldSelectedStreamIndex + { + get => _streamManager.OldSelectedStreamIndex; + set => _streamManager.OldSelectedStreamIndex = value; + } + + private Client Client + { + get => _streamManager.Client; + set => _streamManager.Client = value; + } + + private Account SelectedAccount + { + get => _streamManager.SelectedAccount; + set => _streamManager.SelectedAccount = value; + } + + private Stream SelectedStream + { + get => _streamManager.SelectedStream; + set => _streamManager.SelectedStream = value; + } + + public List Accounts + { + get => _streamManager.Accounts; + set => _streamManager.Accounts = value; + } + + private List Streams + { + get => _streamManager.Streams; + set => _streamManager.Streams = value; + } + + private List Branches + { + get => _streamManager.Branches; + + set => _streamManager.Branches = value; + } + + private async Task LoadAccounts() + { + //refresh accounts just in case + Accounts = AccountManager.GetAccounts().ToList(); + if (!Accounts.Any()) { - EditorUtility.DisplayProgressBar($"Receiving data from {transport.BaseUri}...", "", - Convert.ToSingle(dict.Values.Average() / _totalChildrenCount)); - }; - }, - onTotalChildrenCountKnown: count => { _totalChildrenCount = count; } - ); - - EditorUtility.ClearProgressBar(); - - //Convert Speckle Objects - int childrenConverted = 0; - void BeforeConvertCallback(Base b) - { - EditorUtility.DisplayProgressBar("Converting To Native...", $"{b.speckle_type} - {b.id}", - Convert.ToSingle(childrenConverted++ / _totalChildrenCount)); + Debug.Log("No Accounts found, please login in Manager"); + } + else + { + await SelectAccount(0); + } } - var go = _streamManager.ConvertRecursivelyToNative(@base, - Branches[SelectedBranchIndex].commits.items[SelectedCommitIndex].id, BeforeConvertCallback); - - // Read Receipt - await Client.CommitReceived(new CommitReceivedInput + private async Task SelectAccount(int i) { - streamId = SelectedStream.id, - commitId = Branches[SelectedBranchIndex].commits.items[SelectedCommitIndex].id, - message = $"received commit from {VersionedHostApplications.Unity} Editor", - sourceApplication = VersionedHostApplications.Unity - }); + SelectedAccountIndex = i; + OldSelectedAccountIndex = i; + SelectedAccount = Accounts[i]; - } - catch (Exception e) - { - throw new SpeckleException(e.Message, e, true, SentryLevel.Error); - } - finally - { - EditorApplication.delayCall += EditorUtility.ClearProgressBar; - } + Client = new Client(SelectedAccount); + await LoadStreams(); + } + + private async Task LoadStreams() + { + EditorUtility.DisplayProgressBar("Loading streams...", "", 0); + Streams = await Client.StreamsGet(StreamsLimit); + EditorUtility.ClearProgressBar(); + if (Streams.Any()) + await SelectStream(0); + } + + private async Task SelectStream(int i) + { + SelectedStreamIndex = i; + OldSelectedStreamIndex = i; + SelectedStream = Streams[i]; + + EditorUtility.DisplayProgressBar("Loading stream details...", "", 0); + Branches = await Client.StreamGetBranches(SelectedStream.id, BranchesLimit, CommitsLimit); + if (Branches.Any()) + { + SelectedBranchIndex = 0; + if (Branches[SelectedBranchIndex].commits.items.Any()) + { + SelectedCommitIndex = 0; + } + } + + EditorUtility.ClearProgressBar(); + } + + + private async Task Receive() + { + var transport = new ServerTransport(SelectedAccount, SelectedStream.id); + EditorUtility.DisplayProgressBar($"Receiving data from {transport.BaseUri}...", "", 0); + + try + { + // Receive Speckle Objects + var @base = await Operations.Receive( + Branches[SelectedBranchIndex].commits.items[SelectedCommitIndex].referencedObject, + remoteTransport: transport, + onProgressAction: dict => + { + UnityEditor.EditorApplication.delayCall += () => + { + EditorUtility.DisplayProgressBar($"Receiving data from {transport.BaseUri}...", "", + Convert.ToSingle(dict.Values.Average() / _totalChildrenCount)); + }; + }, + onTotalChildrenCountKnown: count => { _totalChildrenCount = count; } + ); + + EditorUtility.ClearProgressBar(); + + Analytics.TrackEvent(SelectedAccount, Analytics.Events.Receive); + + //Convert Speckle Objects + int childrenConverted = 0; + + void BeforeConvertCallback(Base b) + { + 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); + + // Read Receipt + await Client.CommitReceived(new CommitReceivedInput + { + streamId = SelectedStream.id, + commitId = Branches[SelectedBranchIndex].commits.items[SelectedCommitIndex].id, + message = $"received commit from {VersionedHostApplications.Unity} Editor", + sourceApplication = VersionedHostApplications.Unity + }); + } + catch (Exception e) + { + throw new SpeckleException(e.Message, e, true, SentryLevel.Error); + } + finally + { + EditorApplication.delayCall += EditorUtility.ClearProgressBar; + } + } + + public override async void OnInspectorGUI() + { + _streamManager = (StreamManager) target; + + + #region Account GUI + + if (Accounts == null) + { + await LoadAccounts(); + return; + } + + + EditorGUILayout.BeginHorizontal(); + + SelectedAccountIndex = EditorGUILayout.Popup("Accounts", SelectedAccountIndex, + Accounts.Select(x => x.userInfo.email + " | " + x.serverInfo.name).ToArray(), + GUILayout.ExpandWidth(true), GUILayout.Height(20)); + + if (OldSelectedAccountIndex != SelectedAccountIndex) + { + await SelectAccount(SelectedAccountIndex); + return; + } + + if (GUILayout.Button("Refresh", GUILayout.Width(60), GUILayout.Height(20))) + { + await LoadAccounts(); + return; + } + + EditorGUILayout.EndHorizontal(); + + + #region Speckle Account Info + + _foldOutAccount = EditorGUILayout.BeginFoldoutHeaderGroup(_foldOutAccount, "Account Info"); + + if (_foldOutAccount) + { + EditorGUI.BeginDisabledGroup(true); + + EditorGUILayout.TextField("Name", SelectedAccount.userInfo.name, + GUILayout.Height(20), + GUILayout.ExpandWidth(true)); + + EditorGUILayout.TextField("Server", SelectedAccount.serverInfo.name, + GUILayout.Height(20), + GUILayout.ExpandWidth(true)); + + EditorGUILayout.TextField("URL", SelectedAccount.serverInfo.url, + GUILayout.Height(20), + GUILayout.ExpandWidth(true)); + + EditorGUI.EndDisabledGroup(); + } + + EditorGUILayout.EndFoldoutHeaderGroup(); + + #endregion + + #endregion + + #region Stream List + + if (Streams == null) + return; + + EditorGUILayout.BeginHorizontal(); + + SelectedStreamIndex = EditorGUILayout.Popup("Streams", + SelectedStreamIndex, Streams.Select(x => x.name).ToArray(), GUILayout.Height(20), + GUILayout.ExpandWidth(true)); + + if (OldSelectedStreamIndex != SelectedStreamIndex) + { + await SelectStream(SelectedStreamIndex); + return; + } + + if (GUILayout.Button("Refresh", GUILayout.Width(60), GUILayout.Height(20))) + { + await LoadStreams(); + return; + } + + EditorGUILayout.EndHorizontal(); + + #endregion + + #region Branch List + + if (Branches == null) + return; + + EditorGUILayout.BeginHorizontal(); + + 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, + Branches[SelectedBranchIndex].commits.items.Select(x => $"{x.message} - {x.id}").ToArray(), + GUILayout.Height(20), + GUILayout.ExpandWidth(true)); + + EditorGUILayout.EndHorizontal(); + + #endregion + + #region Generate Materials + + EditorGUILayout.BeginHorizontal(); + + GUILayout.Label("Generate assets"); + GUILayout.FlexibleSpace(); + bool selection = GUILayout.Toggle(generateAssets, ""); + if (generateAssets != selection) + { + + generateAssets = selection; + _streamManager.RC.AssetCache.nativeCaches = NativeCacheFactory.GetDefaultNativeCacheSetup(generateAssets); + } + + EditorGUILayout.EndHorizontal(); + + #endregion + + + EditorGUILayout.BeginHorizontal(); + + bool receive = GUILayout.Button("Receive!"); + + EditorGUILayout.EndHorizontal(); + + if (receive) + { + await Receive(); + } + } } - - public override async void OnInspectorGUI() - { - _streamManager = (StreamManager)target; - - - #region Account GUI - if (Accounts == null) - { - await LoadAccounts(); - return; - } - - - EditorGUILayout.BeginHorizontal(); - - SelectedAccountIndex = EditorGUILayout.Popup("Accounts", SelectedAccountIndex, - Accounts.Select(x => x.userInfo.email + " | " + x.serverInfo.name).ToArray(), - GUILayout.ExpandWidth(true), GUILayout.Height(20)); - - if (OldSelectedAccountIndex != SelectedAccountIndex) - { - await SelectAccount(SelectedAccountIndex); - return; - } - - if (GUILayout.Button("Refresh", GUILayout.Width(60), GUILayout.Height(20))) - { - await LoadAccounts(); - return; - } - - EditorGUILayout.EndHorizontal(); - - - #region Speckle Account Info - _foldOutAccount = EditorGUILayout.BeginFoldoutHeaderGroup(_foldOutAccount, "Account Info"); - - if (_foldOutAccount) - { - EditorGUI.BeginDisabledGroup(true); - - EditorGUILayout.TextField("Name", SelectedAccount.userInfo.name, - GUILayout.Height(20), - GUILayout.ExpandWidth(true)); - - EditorGUILayout.TextField("Server", SelectedAccount.serverInfo.name, - GUILayout.Height(20), - GUILayout.ExpandWidth(true)); - - EditorGUILayout.TextField("URL", SelectedAccount.serverInfo.url, - GUILayout.Height(20), - GUILayout.ExpandWidth(true)); - - EditorGUI.EndDisabledGroup(); - } - - EditorGUILayout.EndFoldoutHeaderGroup(); - #endregion - #endregion - - #region Stream List - if (Streams == null) - return; - - EditorGUILayout.BeginHorizontal(); - - SelectedStreamIndex = EditorGUILayout.Popup("Streams", - SelectedStreamIndex, Streams.Select(x => x.name).ToArray(), GUILayout.Height(20), - GUILayout.ExpandWidth(true)); - - if (OldSelectedStreamIndex != SelectedStreamIndex) - { - await SelectStream(SelectedStreamIndex); - return; - } - - if (GUILayout.Button("Refresh", GUILayout.Width(60), GUILayout.Height(20))) - { - await LoadStreams(); - return; - } - - EditorGUILayout.EndHorizontal(); - #endregion - - #region Branch List - if (Branches == null) - return; - - EditorGUILayout.BeginHorizontal(); - - 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, - Branches[SelectedBranchIndex].commits.items.Select(x => $"{x.message} - {x.id}").ToArray(), - GUILayout.Height(20), - GUILayout.ExpandWidth(true)); - - EditorGUILayout.EndHorizontal(); - #endregion - - #region Generate Materials - EditorGUILayout.BeginHorizontal(); - - GUILayout.Label("Generate material assets"); - GUILayout.FlexibleSpace(); - StreamManager.GenerateAssets = GUILayout.Toggle(StreamManager.GenerateAssets, ""); - - EditorGUILayout.EndHorizontal(); - #endregion - - - EditorGUILayout.BeginHorizontal(); - - bool receive = GUILayout.Button("Receive!"); - - EditorGUILayout.EndHorizontal(); - - if (receive) - { - await Receive(); - } - - - } - - } } \ No newline at end of file diff --git a/Assets/Resources/Materials.meta b/Packages/systems.speckle.speckle-unity/NativeCache.meta similarity index 77% rename from Assets/Resources/Materials.meta rename to Packages/systems.speckle.speckle-unity/NativeCache.meta index de8b6c0..c1ff437 100644 --- a/Assets/Resources/Materials.meta +++ b/Packages/systems.speckle.speckle-unity/NativeCache.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 0ef3ae3dd987865439e8142619e4dbf6 +guid: 60d87a53f669b0a48b83ad5f49380332 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Packages/systems.speckle.speckle-unity/NativeCache/AbstractNativeCache.cs b/Packages/systems.speckle.speckle-unity/NativeCache/AbstractNativeCache.cs new file mode 100644 index 0000000..8990309 --- /dev/null +++ b/Packages/systems.speckle.speckle-unity/NativeCache/AbstractNativeCache.cs @@ -0,0 +1,90 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using Speckle.Core.Models; +using UnityEngine; +using Object = UnityEngine.Object; + +namespace Speckle.ConnectorUnity.NativeCache +{ + #nullable enable + [ExecuteAlways] + public abstract class AbstractNativeCache : ScriptableObject + { + + protected bool isWriting = false; + public abstract bool TryGetObject(Base speckleObject, [NotNullWhen(true)] out T? nativeObject) where T : Object; + + public abstract bool TrySaveObject(Base speckleObject, Object nativeObject); + + /// + /// Prepares this for save operations + /// + public virtual void BeginWrite() + { + isWriting = true; + } + + /// + /// Call when finished performing save operations. + /// Instructs the to finish writing anything to disk + /// + public virtual void FinishWrite() + { + isWriting = false; + } + + protected virtual void OnDisable() + { + FinishWrite(); + } + + } + + public static class AssetHelpers + { + 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)) + { + return string.Format(format, path, "Materials"); + } + if (nativeType == typeof(GameObject)) + { + return string.Format(format, path, "Prefabs"); + } + return null; + } + + public static string GetAssetName(Base speckleObject, Type nativeType) + { + string suffix = GetAssetSuffix(nativeType); + var invalidChars = Path.GetInvalidFileNameChars(); + string name = GetObjectName(speckleObject); + + string sanitisedName = new(name.Where(x => !invalidChars.Contains(x)).ToArray()); + return $"{sanitisedName}{suffix}"; + } + + public static string GetObjectName(Base speckleObject) + { + string objectName = speckleObject["name"] as string ?? speckleObject.GetType().ToString(); + return $"{objectName} - {speckleObject.id}"; + } + + + public static string GetAssetSuffix(Type nativeType) + { + if (nativeType == typeof(Material)) return ".mat"; + if (nativeType == typeof(GameObject)) return ".prefab"; + return ".asset"; + } + } +} diff --git a/Packages/systems.speckle.speckle-unity/NativeCache/AbstractNativeCache.cs.meta b/Packages/systems.speckle.speckle-unity/NativeCache/AbstractNativeCache.cs.meta new file mode 100644 index 0000000..59f21eb --- /dev/null +++ b/Packages/systems.speckle.speckle-unity/NativeCache/AbstractNativeCache.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f6cc5b1591bf7e64d9ac5f0ac97dfcf6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/systems.speckle.speckle-unity/NativeCache/AggregateNativeCache.cs b/Packages/systems.speckle.speckle-unity/NativeCache/AggregateNativeCache.cs new file mode 100644 index 0000000..54f0fbb --- /dev/null +++ b/Packages/systems.speckle.speckle-unity/NativeCache/AggregateNativeCache.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using Speckle.ConnectorUnity.NativeCache; +using Speckle.Core.Models; +using UnityEngine; +using Object = UnityEngine.Object; + +namespace Speckle.ConnectorUnity.NativeCache +{ + public class AggregateNativeCache : AbstractNativeCache + { + [SerializeField, SerializeReference] + public List nativeCaches; + + public override bool TryGetObject(Base speckleObject, out T nativeObject) where T : class + { + foreach (var c in nativeCaches) + { + if (c.TryGetObject(speckleObject, out nativeObject)) return true; + } + nativeObject = null; + return false; + } + + public override bool TrySaveObject(Base speckleObject, Object nativeObject) + { + bool hasSavedSomewhere = false; + + foreach (var c in nativeCaches) + { + hasSavedSomewhere = hasSavedSomewhere || c.TrySaveObject(speckleObject, nativeObject); + } + + return hasSavedSomewhere; + } + + public override void BeginWrite() + { + base.BeginWrite(); + foreach (var c in nativeCaches) + { + c.BeginWrite(); + } + } + + public override void FinishWrite() + { + foreach (var c in nativeCaches) + { + try + { + c.FinishWrite(); + } + catch (Exception e) + { + Debug.LogError(e); + } + } + base.FinishWrite(); + } + + } +} diff --git a/Packages/systems.speckle.speckle-unity/NativeCache/AggregateNativeCache.cs.meta b/Packages/systems.speckle.speckle-unity/NativeCache/AggregateNativeCache.cs.meta new file mode 100644 index 0000000..1ef0753 --- /dev/null +++ b/Packages/systems.speckle.speckle-unity/NativeCache/AggregateNativeCache.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 88d6b4f2f80eaa14f9f07505f7e44ec2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/systems.speckle.speckle-unity/NativeCache/Editor.meta b/Packages/systems.speckle.speckle-unity/NativeCache/Editor.meta new file mode 100644 index 0000000..c76453b --- /dev/null +++ b/Packages/systems.speckle.speckle-unity/NativeCache/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8fa50e4bdeaa3e94d942d3a9f137d91c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/systems.speckle.speckle-unity/NativeCache/Editor/AssetDBNativeCache.cs b/Packages/systems.speckle.speckle-unity/NativeCache/Editor/AssetDBNativeCache.cs new file mode 100644 index 0000000..9803aa6 --- /dev/null +++ b/Packages/systems.speckle.speckle-unity/NativeCache/Editor/AssetDBNativeCache.cs @@ -0,0 +1,153 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using Speckle.Core.Models; +using UnityEditor; +using UnityEngine; +using Object = UnityEngine.Object; + +namespace Speckle.ConnectorUnity.NativeCache.Editor +{ + /// + /// Uses Unity's AssetDatabase to load existing assets from a given path + /// + public sealed class AssetDBNativeCache : AbstractNativeCache + { + public string path = "Assets/Resources"; + + private MemoryNativeCache readCache; + + private Dictionary writeBuffer = new(); + +#nullable enable + private void Awake() + { + readCache = CreateInstance(); + } + + public override bool TryGetObject(Base speckleObject, [NotNullWhen(true)] out T? nativeObject) where T : class + { + if(readCache.TryGetObject(speckleObject, out nativeObject)) + return true; + + Type nativeType = typeof(T); + string? folder = AssetHelpers.GetAssetFolder(nativeType, path); + if (folder == null) return false; + + string assetName = AssetHelpers.GetAssetName(speckleObject, nativeType); + string assetPath = $"{folder}/{assetName}"; + + nativeObject = AssetDatabase.LoadAssetAtPath(assetPath); + return nativeObject != null; + } + + public override bool TrySaveObject(Base speckleObject, Object nativeObject) + { + writeBuffer.TryAdd(speckleObject, nativeObject); + return readCache.TrySaveObject(speckleObject, nativeObject); + } + + public bool WriteObject(Base speckleObject, Object nativeObject) + { + Type nativeType = nativeObject.GetType(); + string? folder = AssetHelpers.GetAssetFolder(nativeType, path); + + if (folder == null) return false; + if (!CreateDirectory(folder)) return false; + + string assetName = AssetHelpers.GetAssetName(speckleObject, nativeType); + string assetPath = $"{folder}/{assetName}"; + + // Special case for GameObjects, we want to use PrefabUtility + if (nativeObject is GameObject go) + { + PrefabUtility.SaveAsPrefabAssetAndConnect(go, assetPath, InteractionMode.AutomatedAction); + return true; + } + + // 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: {folder}/{assetName}", this); + return false; + } + + AssetDatabase.CreateAsset(nativeObject, $"{folder}/{assetName}"); + return true; + } + + + public void WriteAssets(IEnumerable> assets) + { + //Write Asset Data + try + { + AssetDatabase.StartAssetEditing(); + int i = 0; + int count = writeBuffer.Count; + foreach(var kvp in assets) + { + if (kvp.Value is GameObject p) + { + continue; + } + + EditorUtility.DisplayProgressBar("Writing assets", $"Writing asset for {kvp.Value.name}", (float)i / count); + WriteObject(kvp.Key, kvp.Value); + } + } + finally + { + AssetDatabase.StopAssetEditing(); + EditorUtility.DisplayProgressBar("Writing assets", $"Finishing writing assets", 1f); + AssetDatabase.SaveAssets(); + EditorUtility.ClearProgressBar(); + } + } + + public override void FinishWrite() + { + if (!isWriting) return; + + var prefabs = writeBuffer.Where(x => x.Value is GameObject); + var notPrefabs = writeBuffer.Where(x => x.Value is not GameObject); + + WriteAssets(notPrefabs); + WriteAssets(prefabs); + + writeBuffer.Clear(); + if (readCache != null) readCache.LoadedAssets.Clear(); + + base.FinishWrite(); + } + + private static bool CreateDirectory(string directoryPath) + { + if (Directory.Exists(directoryPath)) + return true; + + var info = Directory.CreateDirectory(directoryPath); + AssetDatabase.Refresh(); + return info.Exists; + } + + + [ContextMenu("SetPath")] + internal void SetPath_Menu() + { + 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}"); + } + + } + } +} diff --git a/Packages/systems.speckle.speckle-unity/NativeCache/Editor/AssetDBNativeCache.cs.meta b/Packages/systems.speckle.speckle-unity/NativeCache/Editor/AssetDBNativeCache.cs.meta new file mode 100644 index 0000000..bcf7f8c --- /dev/null +++ b/Packages/systems.speckle.speckle-unity/NativeCache/Editor/AssetDBNativeCache.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 141ce93d2d159c0448b5b8b33b1c0679 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/systems.speckle.speckle-unity/NativeCache/Editor/EditorCaches.asmdef b/Packages/systems.speckle.speckle-unity/NativeCache/Editor/EditorCaches.asmdef new file mode 100644 index 0000000..2127985 --- /dev/null +++ b/Packages/systems.speckle.speckle-unity/NativeCache/Editor/EditorCaches.asmdef @@ -0,0 +1,16 @@ +{ + "name": "EditorCaches", + "rootNamespace": "Speckle.ConnectorUnity", + "references": ["GUID:05078f9b6da40444fbd72ec600449925"], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Packages/systems.speckle.speckle-unity/NativeCache/Editor/EditorCaches.asmdef.meta b/Packages/systems.speckle.speckle-unity/NativeCache/Editor/EditorCaches.asmdef.meta new file mode 100644 index 0000000..34c4d69 --- /dev/null +++ b/Packages/systems.speckle.speckle-unity/NativeCache/Editor/EditorCaches.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: a97f36292d600f8459146719f68d6bb2 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/systems.speckle.speckle-unity/NativeCache/MemoryNativeCache.cs b/Packages/systems.speckle.speckle-unity/NativeCache/MemoryNativeCache.cs new file mode 100644 index 0000000..84dd4f1 --- /dev/null +++ b/Packages/systems.speckle.speckle-unity/NativeCache/MemoryNativeCache.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using Speckle.Core.Models; +using Object = UnityEngine.Object; + +namespace Speckle.ConnectorUnity.NativeCache +{ + #nullable enable + /// + /// In memory native object cache + /// + public sealed class MemoryNativeCache : AbstractNativeCache + { + public IDictionary LoadedAssets { get; set; } = new Dictionary(); + + public override bool TryGetObject(Base speckleObject, [NotNullWhen(true)] out T? nativeObject) where T : class + { + if (TryGetObject(speckleObject, out Object? e) && e is T t) + { + nativeObject = t; + return true; + } + + nativeObject = null; + return false; + } + + public bool TryGetObject(Base speckleObject, [NotNullWhen(true)] out Object? nativeObject) + { + return LoadedAssets.TryGetValue(speckleObject.id, out nativeObject); + } + + public override bool TrySaveObject(Base speckleObject, Object nativeObject) + { + return LoadedAssets.TryAdd(speckleObject.id, nativeObject); + } + } +} diff --git a/Packages/systems.speckle.speckle-unity/NativeCache/MemoryNativeCache.cs.meta b/Packages/systems.speckle.speckle-unity/NativeCache/MemoryNativeCache.cs.meta new file mode 100644 index 0000000..8890806 --- /dev/null +++ b/Packages/systems.speckle.speckle-unity/NativeCache/MemoryNativeCache.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b3354e8208862c341940152f5340d41a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/systems.speckle.speckle-unity/NativeCache/NativeCaches.asmdef b/Packages/systems.speckle.speckle-unity/NativeCache/NativeCaches.asmdef new file mode 100644 index 0000000..353a5ae --- /dev/null +++ b/Packages/systems.speckle.speckle-unity/NativeCache/NativeCaches.asmdef @@ -0,0 +1,14 @@ +{ + "name": "NativeCaches", + "rootNamespace": "Speckle.ConnectorUnity", + "references": [], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Packages/systems.speckle.speckle-unity/NativeCache/NativeCaches.asmdef.meta b/Packages/systems.speckle.speckle-unity/NativeCache/NativeCaches.asmdef.meta new file mode 100644 index 0000000..a62790a --- /dev/null +++ b/Packages/systems.speckle.speckle-unity/NativeCache/NativeCaches.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 05078f9b6da40444fbd72ec600449925 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/systems.speckle.speckle-unity/NativeCache/ResourcesNativeCache.cs b/Packages/systems.speckle.speckle-unity/NativeCache/ResourcesNativeCache.cs new file mode 100644 index 0000000..cb29f8c --- /dev/null +++ b/Packages/systems.speckle.speckle-unity/NativeCache/ResourcesNativeCache.cs @@ -0,0 +1,39 @@ +using System.Diagnostics.CodeAnalysis; +using Speckle.Core.Models; +using UnityEngine; +using Object = UnityEngine.Object; + +namespace Speckle.ConnectorUnity.NativeCache +{ + #nullable enable + /// + /// Loads existing assets from + /// optionally accepting byName overrides + /// + public sealed class ResourcesNativeCache : AbstractNativeCache + { + public bool matchByName = true; + + public override bool TryGetObject(Base speckleObject, [NotNullWhen(true)] out T? nativeObject) where T : class + { + if (matchByName) + { + string? speckleName = speckleObject["name"] as string ?? speckleObject["Name"] as string; + if (!string.IsNullOrWhiteSpace(speckleName)) + { + nativeObject = Resources.Load(speckleName); + if (nativeObject != null) return true; + } + } + + nativeObject = Resources.Load(AssetHelpers.GetAssetName(speckleObject, typeof(T))); + return nativeObject != null; + } + + public override bool TrySaveObject(Base speckleObject, Object nativeObject) + { + // Pass + return false; + } + } +} diff --git a/Packages/systems.speckle.speckle-unity/NativeCache/ResourcesNativeCache.cs.meta b/Packages/systems.speckle.speckle-unity/NativeCache/ResourcesNativeCache.cs.meta new file mode 100644 index 0000000..11cbe8c --- /dev/null +++ b/Packages/systems.speckle.speckle-unity/NativeCache/ResourcesNativeCache.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2a4a29c776298714c88f406ad39c6095 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/systems.speckle.speckle-unity/NativeCacheFactory.cs b/Packages/systems.speckle.speckle-unity/NativeCacheFactory.cs new file mode 100644 index 0000000..99fa29d --- /dev/null +++ b/Packages/systems.speckle.speckle-unity/NativeCacheFactory.cs @@ -0,0 +1,50 @@ +using System.Collections.Generic; +using Speckle.ConnectorUnity.NativeCache; +using UnityEditor.Build; +using UnityEngine; + +#if UNITY_EDITOR +using Speckle.ConnectorUnity.NativeCache.Editor; +#endif + +namespace Speckle.ConnectorUnity +{ + #nullable enable + public static class NativeCacheFactory + { + + public static List GetDefaultNativeCacheSetup(bool generateAssets = false) + { +#if UNITY_EDITOR + if (generateAssets) + { + return GetEditorCacheSetup(); + } +#endif + return GetStandaloneCacheSetup(); + + } + + public static List GetStandaloneCacheSetup() + { + return new List() + { + ScriptableObject.CreateInstance(), + ScriptableObject.CreateInstance(), + }; + } + +#if UNITY_EDITOR + public static List GetEditorCacheSetup() + { + return new List() + { + ScriptableObject.CreateInstance(), + ScriptableObject.CreateInstance(), + ScriptableObject.CreateInstance(), + }; + } +#endif + } +} + diff --git a/Packages/systems.speckle.speckle-unity/NativeCacheFactory.cs.meta b/Packages/systems.speckle.speckle-unity/NativeCacheFactory.cs.meta new file mode 100644 index 0000000..6d00b06 --- /dev/null +++ b/Packages/systems.speckle.speckle-unity/NativeCacheFactory.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8fc80190dd595f14ca90df87fe890610 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/systems.speckle.speckle-unity/Receiver.cs b/Packages/systems.speckle.speckle-unity/Receiver.cs index 1037456..1ea721e 100644 --- a/Packages/systems.speckle.speckle-unity/Receiver.cs +++ b/Packages/systems.speckle.speckle-unity/Receiver.cs @@ -139,6 +139,9 @@ namespace Speckle.ConnectorUnity onTotalChildrenCountKnown: OnTotalChildrenCountKnown, disposeTransports: true ); + + Analytics.TrackEvent(Client.Account, Analytics.Events.Receive); + Dispatcher.Instance().Enqueue(() => { var root = new GameObject() diff --git a/Packages/systems.speckle.speckle-unity/RecursiveConverter.ToNative.cs b/Packages/systems.speckle.speckle-unity/RecursiveConverter.ToNative.cs index 98b021a..5a25e89 100644 --- a/Packages/systems.speckle.speckle-unity/RecursiveConverter.ToNative.cs +++ b/Packages/systems.speckle.speckle-unity/RecursiveConverter.ToNative.cs @@ -3,6 +3,7 @@ using System; using System.Collections; using System.Collections.Generic; using System.Linq; +using Speckle.ConnectorUnity.NativeCache; using Speckle.Core.Models; using UnityEngine; @@ -12,25 +13,34 @@ namespace Speckle.ConnectorUnity { /// - /// Given , + /// Given , /// will recursively convert any objects in the tree /// /// The object to convert ( or of) /// Optional parent transform for the created root s /// A list of all created s public virtual List RecursivelyConvertToNative(object? o, Transform? parent) - => RecursivelyConvertToNative(o, parent, o => ConverterInstance.CanConvertToNative(o)); + => RecursivelyConvertToNative(o, parent, b => ConverterInstance.CanConvertToNative(b)); /// /// A function to determine if an object should be converted public virtual List RecursivelyConvertToNative(object? o, Transform? parent, Func predicate) { - LoadMaterialOverrides(); + //Ensure we have A native cache + if (AssetCache.nativeCaches.Any(x => x == null)) + { + AssetCache.nativeCaches = NativeCacheFactory.GetStandaloneCacheSetup(); + } + + ConverterInstance.SetContextDocument(AssetCache); + AssetCache.BeginWrite(); var createdGameObjects = new List(); ConvertChild(o, parent, predicate, createdGameObjects); //TODO track event + AssetCache.FinishWrite(); + return createdGameObjects; } @@ -81,8 +91,7 @@ namespace Speckle.ConnectorUnity } // For geometry, only traverse `elements` prop, otherwise, try and convert everything - IEnumerable potentialChildren; - potentialChildren = ConverterInstance.CanConvertToNative(baseObject) + IEnumerable potentialChildren = ConverterInstance.CanConvertToNative(baseObject) ? new []{"elements"} : baseObject.GetMemberNames(); @@ -124,19 +133,6 @@ namespace Speckle.ConnectorUnity Debug.Log($"Unknown type {value.GetType()} found when traversing tree, will be safely ignored"); } } - - protected virtual void LoadMaterialOverrides() - { - //using the ContextDocument to pass materials - //available in Assets/Materials to the converters - Dictionary loadedAssets = Resources.LoadAll("", typeof(Material)) - .GroupBy(o => o.name) - .Select(o => o.First()) - .ToDictionary(o => o.name); - - ConverterInstance.SetContextDocument(loadedAssets); - } - [Obsolete("Use RecursivelyConvertToNative instead")] public GameObject ConvertRecursivelyToNative(Base @base, string name) diff --git a/Packages/systems.speckle.speckle-unity/RecursiveConverter.cs b/Packages/systems.speckle.speckle-unity/RecursiveConverter.cs index d7e3579..c3e0ea7 100644 --- a/Packages/systems.speckle.speckle-unity/RecursiveConverter.cs +++ b/Packages/systems.speckle.speckle-unity/RecursiveConverter.cs @@ -1,5 +1,7 @@ -#nullable enable +using System; +using System.Collections.Generic; using Objects.Converter.Unity; +using Speckle.ConnectorUnity.NativeCache; using Speckle.Core.Kits; using UnityEngine; @@ -12,7 +14,19 @@ namespace Speckle.ConnectorUnity [ExecuteAlways, DisallowMultipleComponent] public partial class RecursiveConverter : MonoBehaviour { - public virtual ISpeckleConverter ConverterInstance { get; set; } = new ConverterUnity(); - + public ISpeckleConverter ConverterInstance { get; set; } = new ConverterUnity(); + + [field: SerializeField] + public AggregateNativeCache AssetCache { get; set; } + + private void Awake() + { + if (AssetCache == null) + { + var assetCache = ScriptableObject.CreateInstance(); + assetCache.nativeCaches = NativeCacheFactory.GetDefaultNativeCacheSetup(); + this.AssetCache = assetCache; + } + } } } \ No newline at end of file diff --git a/Packages/systems.speckle.speckle-unity/Speckle.Connector.asmdef b/Packages/systems.speckle.speckle-unity/Speckle.Connector.asmdef index c122cd6..5dcce05 100644 --- a/Packages/systems.speckle.speckle-unity/Speckle.Connector.asmdef +++ b/Packages/systems.speckle.speckle-unity/Speckle.Connector.asmdef @@ -2,7 +2,9 @@ "name": "Speckle.Connector", "rootNamespace": "Speckle.ConnectorUnity", "references": [ - "GUID:24f666972ea7e9149abddaae766b9c1d" + "GUID:24f666972ea7e9149abddaae766b9c1d", + "GUID:05078f9b6da40444fbd72ec600449925", + "GUID:a97f36292d600f8459146719f68d6bb2" ], "includePlatforms": [], "excludePlatforms": [], diff --git a/Packages/systems.speckle.speckle-unity/StreamManager.cs b/Packages/systems.speckle.speckle-unity/StreamManager.cs index 622f64d..a14546f 100644 --- a/Packages/systems.speckle.speckle-unity/StreamManager.cs +++ b/Packages/systems.speckle.speckle-unity/StreamManager.cs @@ -2,81 +2,83 @@ using System; using Speckle.Core.Api; using Speckle.Core.Credentials; using System.Collections.Generic; +using Speckle.ConnectorUnity.NativeCache; using Speckle.Core.Models; using UnityEngine; using UnityEngine.Serialization; namespace Speckle.ConnectorUnity { - [ExecuteAlways] - [AddComponentMenu("Speckle/Stream Manager")] - public class StreamManager : MonoBehaviour - { - public int SelectedAccountIndex = -1; - public int SelectedStreamIndex = -1; - public int SelectedBranchIndex = -1; - public int SelectedCommitIndex = -1; - public int OldSelectedAccountIndex = -1; - public int OldSelectedStreamIndex = -1; + [ExecuteAlways] + [AddComponentMenu("Speckle/Stream Manager")] + [RequireComponent(typeof(RecursiveConverter))] + public class StreamManager : MonoBehaviour + { + public int SelectedAccountIndex = -1; + public int SelectedStreamIndex = -1; + public int SelectedBranchIndex = -1; + public int SelectedCommitIndex = -1; + public int OldSelectedAccountIndex = -1; + public int OldSelectedStreamIndex = -1; - public Client Client; - public Account SelectedAccount; - public Stream SelectedStream; + public Client Client; + public Account SelectedAccount; + public Stream SelectedStream; - public List Accounts; - public List Streams; - public List Branches; - -#if UNITY_EDITOR - public static bool GenerateAssets = false; -#endif - + public List Accounts; + public List Streams; + public List Branches; + + public RecursiveConverter RC { get; private set; } + #nullable enable - public GameObject ConvertRecursivelyToNative(Base @base, string rootObjectName, Action? beforeConvertCallback) - { + private void Awake() + { + RC = GetComponent(); + } + + + public GameObject ConvertRecursivelyToNative(Base @base, string rootObjectName, + Action? beforeConvertCallback) + { + var rootObject = new GameObject(rootObjectName); - var rc = GetComponent(); - if (rc == null) - rc = gameObject.AddComponent(); - - var rootObject = new GameObject(rootObjectName); - - bool Predicate(Base o) - { - beforeConvertCallback?.Invoke(o); - return rc.ConverterInstance.CanConvertToNative(o) //Accept geometry - || o.speckle_type == "Base" && o.totalChildrenCount > 0; // Or Base objects that have children - } + bool Predicate(Base o) + { + beforeConvertCallback?.Invoke(o); + return RC.ConverterInstance.CanConvertToNative(o) //Accept geometry + || 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()) - { - var converted = rc.RecursivelyConvertToNative(prop.Value, null, Predicate); - - //Skip empties - if(converted.Count <= 0) continue; + // For the rootObject only, we will create property GameObjects + // i.e. revit categories + foreach (var prop in @base.GetMembers()) + { + var converted = RC.RecursivelyConvertToNative(prop.Value, null, Predicate); - 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); - } - - } + //Skip empties + 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); + } + } + + return rootObject; + } - return rootObject; - } - #if UNITY_EDITOR - [ContextMenu("Open Speckle Stream in Browser")] - protected void OpenUrlInBrowser() - { - string url = $"{SelectedAccount.serverInfo.url}/streams/{SelectedStream.id}/commits/{Branches[SelectedBranchIndex].commits.items[SelectedCommitIndex].id}"; - Application.OpenURL(url); - } + [ContextMenu("Open Speckle Stream in Browser")] + protected void OpenUrlInBrowser() + { + string url = + $"{SelectedAccount.serverInfo.url}/streams/{SelectedStream.id}/commits/{Branches[SelectedBranchIndex].commits.items[SelectedCommitIndex].id}"; + Application.OpenURL(url); + } #endif - } + } } \ No newline at end of file diff --git a/ProjectSettings/ProjectVersion.txt b/ProjectSettings/ProjectVersion.txt index 4cb0460..8ea1b85 100644 --- a/ProjectSettings/ProjectVersion.txt +++ b/ProjectSettings/ProjectVersion.txt @@ -1,2 +1,2 @@ -m_EditorVersion: 2021.3.6f1 -m_EditorVersionWithRevision: 2021.3.6f1 (7da38d85baf6) +m_EditorVersion: 2021.3.11f1 +m_EditorVersionWithRevision: 2021.3.11f1 (0a5ca18544bf)