diff --git a/Packages/systems.speckle.speckle-unity/Runtime/Components/RecursiveConverter.ToNative.cs b/Packages/systems.speckle.speckle-unity/Runtime/Components/RecursiveConverter.ToNative.cs index d5f676c..abe0faf 100644 --- a/Packages/systems.speckle.speckle-unity/Runtime/Components/RecursiveConverter.ToNative.cs +++ b/Packages/systems.speckle.speckle-unity/Runtime/Components/RecursiveConverter.ToNative.cs @@ -2,55 +2,197 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using Speckle.ConnectorUnity.NativeCache; +using Speckle.Core.Logging; using Speckle.Core.Models; using Speckle.Core.Models.GraphTraversal; using UnityEngine; +using UnityEngine.Networking; namespace Speckle.ConnectorUnity.Components { - public partial class RecursiveConverter + + /// + /// Struct that encapsulates the result of a ToNative conversion of a single SpeckleObject + /// + public readonly struct ConversionResult { + /// + /// The context that was converted ToNative + /// + public readonly TraversalContext traversalContext; + + /// + /// The result of conversion a successful conversion + /// + public readonly GameObject? converted; + + /// + /// The result of conversion a failed conversion + /// + public readonly Exception? exception; - public IEnumerator ConvertToNative(Base rootObject, Transform? parent, IDictionary outCreatedObjects) + /// + /// Constructor used for Successful conversions + /// + /// The current traversal context + /// The resultant ToNative conversion of context object + /// + public ConversionResult(TraversalContext traversalContext, [NotNull] GameObject? converted) + : this(traversalContext, converted, null) { - - InitializeAssetCache(); - var traversalFunc = DefaultTraversal.CreateBIMTraverseFunc(ConverterInstance); + if (converted == null) throw new ArgumentNullException(nameof(converted)); + } - var convertableObjects = traversalFunc.Traverse(rootObject) - .Where(tc => ConverterInstance.CanConvertToNative(tc.current)); + /// + /// Constructor used for Failed conversions + /// + /// The current conversion + /// The operation halting exception that occured + /// Optional converted GameObject + /// + public ConversionResult(TraversalContext traversalContext, [NotNull] Exception? exception, + GameObject? converted = null) + : this(traversalContext, converted, exception) + { + if (exception == null) throw new ArgumentNullException(nameof(exception)); + } - foreach (TraversalContext tc in convertableObjects) - { - Transform? nativeParent = parent; - if (tc.parent != null && outCreatedObjects.TryGetValue(tc.parent.current, out GameObject p)) - nativeParent = p.transform; - - GameObject? go = ConverterInstance.ConvertToNative(tc.current) as GameObject; - if (go == null) continue; - - go.transform.SetParent(parent, true); - outCreatedObjects.Add(tc.current, go); - - //Set some common for all created GameObjects - //TODO add support for more unity specific props - if(go.name == "New Game Object" || string.IsNullOrWhiteSpace(go.name)) - go.name = AssetHelpers.GenerateObjectName(tc.current); - //if (baseObject["tag"] is string t) go.tag = t; - if (tc.current["physicsLayer"] is string layerName) - { - int layer = LayerMask.NameToLayer(layerName); //TODO: check how this can be interoperable with Unreal and Blender - if (layer > -1) go.layer = layer; - } - //if (baseObject["isStatic"] is bool isStatic) go.isStatic = isStatic; + private ConversionResult(TraversalContext traversalContext, GameObject? converted, Exception? exception) + { + this.traversalContext = traversalContext; + this.converted = converted; + this.exception = exception; + } - yield return go; - } + /// + /// + /// + /// The converted + /// The that occured during conversion + /// True if the conversion was successful + public bool WasSuccessful( + [NotNullWhen(true)] out GameObject? converted, + [NotNullWhen(false)] out Exception? exception) + { + converted = this.converted; + exception = this.exception; + return this.exception == null; } + public Base SpeckleObject => traversalContext.current; + } + + public partial class RecursiveConverter + { + /// + /// Coroutine for + /// + /// + /// + /// Optional filter function + /// + public IEnumerator RecursivelyConvertToNative(Base rootObject, Transform? parent, + Func? predicate = null) + { + var traversalFunc = DefaultTraversal.CreateBIMTraverseFunc(ConverterInstance); + + var objectsToConvert = traversalFunc.Traverse(rootObject); + + if(predicate != null) objectsToConvert = objectsToConvert.Where(predicate); + + Dictionary created = new(); + foreach (var conversionResult in ConvertTree(objectsToConvert, parent, created)) + { + Base speckleObject = conversionResult.SpeckleObject; + if (conversionResult.WasSuccessful(out var converted, out var ex)) + { + created.Add(speckleObject, converted); + } + else + { + Debug.LogWarning($"Failed to convert Speckle object of type {speckleObject.speckle_type}\n{ex}",this); + } + + yield return conversionResult; + } + + Debug.Log($"Finished converting {rootObject.id} to native. Created {created.Count} {nameof(GameObject)}s ",this); + + } + /// + /// Converts a objectTree (see ) to unevaluated enumerable. + /// As this enumerable is iterated through, each context object will be converted to (if successful) + /// or if not. + /// + /// + /// You may enumerate over multiple frames (e.g. coroutine) but you must ensure the output eventually gets fully enumerated (exactly once) + /// + /// + /// + /// + /// + public IEnumerable ConvertTree(IEnumerable objectTree, Transform? parent, IDictionary outCreatedObjects) + { + InitializeAssetCache(); + AssetCache.BeginWrite(); + + foreach (TraversalContext tc in objectTree) + { + Transform? currentParent = GetParent(tc, outCreatedObjects) ?? parent; + + ConversionResult result; + try + { + var converted = ConvertToNative(tc.current, currentParent); + result = new ConversionResult(tc, converted); + outCreatedObjects.TryAdd(tc.current, result.converted); + } + catch (Exception ex) + { + result = new ConversionResult(tc, ex); + } + + yield return result; + } + + AssetCache.FinishWrite(); + } + + private Transform? GetParent(TraversalContext? tc, IDictionary createdObjects) + { + if (tc == null) return null; //We've reached the root object, and still not found a converted parent + + if(createdObjects.TryGetValue(tc.current, out GameObject? p) && p != null) + return p.transform; + + //Go one level up, and repeat! + return GetParent(tc.parent, createdObjects); + } + + private GameObject ConvertToNative(Base speckleObject, Transform? parentTransform) + { + GameObject? go = ConverterInstance.ConvertToNative(speckleObject) as GameObject; + if (go == null) throw new SpeckleException("Conversion Returned Null"); + + go.transform.SetParent(parentTransform, true); + + //Set some common for all created GameObjects + //TODO add support for more unity specific props + if(go.name == "New Game Object" || string.IsNullOrWhiteSpace(go.name)) + go.name = AssetHelpers.GenerateObjectName(speckleObject); + //if (baseObject["tag"] is string t) go.tag = t; + if (speckleObject["physicsLayer"] is string layerName) + { + int layer = LayerMask.NameToLayer(layerName); //TODO: check how this can be interoperable with Unreal and Blender + if (layer > -1) go.layer = layer; + } + //if (baseObject["isStatic"] is bool isStatic) go.isStatic = isStatic; + return go; + } diff --git a/Packages/systems.speckle.speckle-unity/Runtime/Components/SpeckleReceiver.cs b/Packages/systems.speckle.speckle-unity/Runtime/Components/SpeckleReceiver.cs index 4058f96..6bce1b3 100644 --- a/Packages/systems.speckle.speckle-unity/Runtime/Components/SpeckleReceiver.cs +++ b/Packages/systems.speckle.speckle-unity/Runtime/Components/SpeckleReceiver.cs @@ -51,7 +51,8 @@ namespace Speckle.ConnectorUnity.Components #nullable enable protected internal CancellationTokenSource? CancellationTokenSource { get; private set; } - + + //TODO runtime receiving public IEnumerator ReceiveAndConvertRoutine(SpeckleReceiver speckleReceiver, string rootObjectName, Action? beforeConvertCallback = null) { diff --git a/Packages/systems.speckle.speckle-unity/Runtime/Converter/Unity/ConverterUnity.Geometry.cs b/Packages/systems.speckle.speckle-unity/Runtime/Converter/Unity/ConverterUnity.Geometry.cs index 155097a..b3b1fef 100644 --- a/Packages/systems.speckle.speckle-unity/Runtime/Converter/Unity/ConverterUnity.Geometry.cs +++ b/Packages/systems.speckle.speckle-unity/Runtime/Converter/Unity/ConverterUnity.Geometry.cs @@ -212,13 +212,9 @@ namespace Objects.Converter.Unity return sobject; } - public GameObject? InstanceToNative(Instance instance) + public GameObject InstanceToNative(Instance instance) { - if (instance.definition == null) - { - Debug.Log($"Skipping {typeof(BlockInstance)} {instance.id}, block definition was null"); - return null; - } + if (instance.definition == null) throw new ArgumentException("Definition was null", nameof(instance)); var defName = instance.definition["name"] as string ?? ""; // Check for existing converted object @@ -269,8 +265,14 @@ namespace Objects.Converter.Unity LoadedAssets.TrySaveObject(instance.definition, native); + TransformToNativeTransform(native.transform, instance.transform); - if (instance["name"] is string instanceName) native.name = instanceName; + + var instanceName = AssetHelpers.GetFriendlyObjectName(instance) != null + ? AssetHelpers.GenerateObjectName(instance) + : defName; + + native.name = instanceName; return native; } diff --git a/Packages/systems.speckle.speckle-unity/Runtime/Converter/Unity/ConverterUnity.Mesh.cs b/Packages/systems.speckle.speckle-unity/Runtime/Converter/Unity/ConverterUnity.Mesh.cs index 4c15d03..bf70db1 100644 --- a/Packages/systems.speckle.speckle-unity/Runtime/Converter/Unity/ConverterUnity.Mesh.cs +++ b/Packages/systems.speckle.speckle-unity/Runtime/Converter/Unity/ConverterUnity.Mesh.cs @@ -6,6 +6,7 @@ using Speckle.ConnectorUnity.Utils; using Objects.Other; using Objects.Utils; using Speckle.ConnectorUnity.NativeCache; +using Speckle.Core.Logging; using Speckle.Core.Models; using UnityEngine; using UnityEngine.Rendering; @@ -209,13 +210,9 @@ namespace Objects.Converter.Unity /// The object being converted /// Collection of es that shall be converted /// A with the converted , , and - public GameObject? MeshesToNative(Base element, IReadOnlyCollection meshes) + public GameObject MeshesToNative(Base element, IReadOnlyCollection meshes) { - if (!meshes.Any()) - { - Debug.Log($"Skipping {element.GetType()} {element.id}, zero {typeof(SMesh)} provided"); - return null; - } + if (!meshes.Any()) throw new ArgumentException("Expected at least one Mesh", nameof(meshes)); Material[] nativeMaterials = RenderMaterialsToNative(meshes); @@ -241,15 +238,12 @@ namespace Objects.Converter.Unity /// /// Mesh to convert /// - public GameObject? MeshToNative(SMesh speckleMesh) + public GameObject MeshToNative(SMesh speckleMesh) { if (speckleMesh.vertices.Count == 0 || speckleMesh.faces.Count == 0) - { - Debug.Log($"Skipping mesh {speckleMesh.id}, mesh data was empty"); - return null; - } - - GameObject? converted = MeshesToNative(speckleMesh, new[] {speckleMesh}); + throw new ArgumentException("mesh data was empty", nameof(speckleMesh)); + + GameObject converted = MeshesToNative(speckleMesh, new[] {speckleMesh}); // Raw meshes shouldn't have dynamic props to attach //if (converted != null) AttachSpeckleProperties(converted,speckleMesh.GetType(), GetProperties(speckleMesh, typeof(Mesh))); diff --git a/Packages/systems.speckle.speckle-unity/Runtime/Utils/CoreUtils.cs b/Packages/systems.speckle.speckle-unity/Runtime/Utils/CoreUtils.cs index 8786c5b..94b94cd 100644 --- a/Packages/systems.speckle.speckle-unity/Runtime/Utils/CoreUtils.cs +++ b/Packages/systems.speckle.speckle-unity/Runtime/Utils/CoreUtils.cs @@ -31,6 +31,9 @@ namespace Speckle.ConnectorUnity return HostAppVersion.v; #endif } + + + public static bool Tautology(T _) => true; } }