#nullable enable using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using Speckle.ConnectorUnity.Utils; using Speckle.Core.Logging; using Speckle.Core.Models; using Speckle.Core.Models.GraphTraversal; using UnityEngine; namespace Speckle.ConnectorUnity.Components { /// /// Struct that encapsulates the result of a ToNative conversion of a single Speckle Object () /// 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; /// /// 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) { if (converted is null) throw new ArgumentNullException(nameof(converted)); } /// /// 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 is null) throw new ArgumentNullException(nameof(exception)); } private ConversionResult( TraversalContext traversalContext, GameObject? converted, Exception? exception ) { this.traversalContext = traversalContext; this.converted = converted; this.exception = exception; } /// /// /// /// 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 WasSuccessful(); } public bool WasSuccessful() => this.exception == null; public Base SpeckleObject => traversalContext.Current; } public partial class RecursiveConverter { /// /// Calling this function will perform the conversion process synchronously /// The conversion result public List RecursivelyConvertToNative_Sync( Base rootObject, Transform? parent, Predicate? predicate = null ) { return RecursivelyConvertToNative_Enumerable(rootObject, parent, predicate).ToList(); } /// /// Calling this function will start a coroutine to complete later on the coroutine loop /// The started Coroutine public Coroutine RecursivelyConvertToNative_Coroutine( Base rootObject, Transform? parent, Predicate? predicate = null ) { return StartCoroutine( RecursivelyConvertToNative_Enumerable(rootObject, parent, predicate).GetEnumerator() ); } /// /// Will recursively traverse the given and convert convertable child objects /// where the given /// /// The Speckle object to traverse and convert all convertable children /// Optional parent for the created root s /// A filter function to allow for selectively excluding certain objects from being converted /// An unevaluated of all created s public IEnumerable RecursivelyConvertToNative_Enumerable( Base rootObject, Transform? parent, Predicate? predicate = null ) { var userPredicate = predicate ?? (_ => true); var traversalFunc = DefaultTraversal.CreateBIMTraverseFunc(ConverterInstance); var objectsToConvert = traversalFunc .Traverse(rootObject) .Where(x => ConverterInstance.CanConvertToNative(x.Current)) .Where(x => userPredicate(x)); Dictionary created = new(); foreach (var conversionResult in ConvertTree(objectsToConvert, parent, created)) { if (!isActiveAndEnabled) throw new InvalidOperationException( $"Cannot convert objects while {GetType()} is disabled" ); yield return conversionResult; } } /// /// 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) /// /// /// /// /// protected IEnumerable ConvertTree( IEnumerable objectTree, Transform? parent, IDictionary outCreatedObjects ) { InitializeAssetCache(); AssetCache.BeginWrite(); foreach (TraversalContext tc in objectTree) { ConversionResult result; try { Transform? currentParent = GetParent(tc, outCreatedObjects) ?? parent; var converted = ConvertToNative(tc.Current, currentParent); result = new ConversionResult(tc, converted); outCreatedObjects.TryAdd(tc.Current, result.converted); } catch (Exception ex) { result = new ConversionResult(tc, ex); } yield return result; } AssetCache.FinishWrite(); } protected static Transform? GetParent( TraversalContext? tc, IDictionary createdObjects ) { if (tc == null) return null; //We've reached the root object, and still not found a converted parent if (createdObjects.TryGetValue(tc.Current, out GameObject? p) && p != null) return p.transform; //Go one level up, and repeat! return GetParent(tc.Parent, createdObjects); } protected GameObject ConvertToNative(Base speckleObject, Transform? parentTransform) { GameObject? go = ConverterInstance.ConvertToNative(speckleObject) as GameObject; if (go == null) throw new SpeckleException("Conversion Returned Null"); go.transform.SetParent(parentTransform, true); //Set some common for all created GameObjects //TODO add support for more unity specific props if (go.name == "New Game Object" || string.IsNullOrWhiteSpace(go.name)) go.name = CoreUtils.GenerateObjectName(speckleObject); if (speckleObject["physicsLayer"] is string layerName) { int layer = LayerMask.NameToLayer(layerName); //TODO: check how this can be interoperable with Unreal and Blender if (layer > -1) go.layer = layer; } //if (baseObject["tag"] is string t) go.tag = t; //if (baseObject["isStatic"] is bool isStatic) go.isStatic = isStatic; return go; } #region deprecated conversion functions [Obsolete("Use " + nameof(RecursivelyConvertToNative_Coroutine))] public IEnumerator ConvertCoroutine( Base rootObject, Transform? parent, List outCreatedObjects ) => ConvertCoroutine( rootObject, parent, outCreatedObjects, b => ConverterInstance.CanConvertToNative(b) ); [Obsolete("Use " + nameof(RecursivelyConvertToNative_Coroutine))] public IEnumerator ConvertCoroutine( Base rootObject, Transform? parent, List outCreatedObjects, Func predicate ) { foreach (string propertyName in GetPotentialChildren(rootObject)) { ConvertChild(rootObject[propertyName], parent, predicate, outCreatedObjects); yield return null; } } /// /// 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 [Obsolete("Use " + nameof(RecursivelyConvertToNative_Sync))] public virtual List RecursivelyConvertToNative(object? o, Transform? parent) => RecursivelyConvertToNative(o, parent, b => ConverterInstance.CanConvertToNative(b)); /// /// A function to determine if an object should be converted [Obsolete("Use " + nameof(RecursivelyConvertToNative_Sync))] public virtual List RecursivelyConvertToNative( object? o, Transform? parent, Func predicate ) { InitializeAssetCache(); var createdGameObjects = new List(); try { AssetCache.BeginWrite(); ConvertChild(o, parent, predicate, createdGameObjects); } finally { AssetCache.FinishWrite(); } //TODO track event? return createdGameObjects; } private void InitializeAssetCache() { //Ensure we have A native cache if (AssetCache.nativeCaches.Any(x => x == null)) { AssetCache.nativeCaches = NativeCacheFactory.GetStandaloneCacheSetup(); } ConverterInstance.SetContextDocument(AssetCache); } [Obsolete] public virtual void RecurseTreeToNative( Base baseObject, Transform? parent, Func predicate, IList outCreatedObjects ) { object? converted = null; if (predicate(baseObject)) { try { converted = ConverterInstance.ConvertToNative(baseObject); } catch (Exception ex) when (!ex.IsFatal()) { Debug.LogWarning( $"Failed to convert {baseObject.speckle_type} - {baseObject.id}\n{ex}" ); } } // Handle new GameObjects Transform? nextParent = parent; if (converted is GameObject go) { outCreatedObjects.Add(go); nextParent = go.transform; go.transform.SetParent(parent, true); //Set some common for all created GameObjects //TODO add support for more unity specific props if (go.name == "New Game Object" || string.IsNullOrWhiteSpace(go.name)) go.name = 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 (baseObject["isStatic"] is bool isStatic) go.isStatic = isStatic; } // For geometry, only traverse `elements` prop, otherwise, try and convert everything IEnumerable potentialChildren = GetPotentialChildren(baseObject); // Convert Children foreach (string propertyName in potentialChildren) { ConvertChild(baseObject[propertyName], nextParent, predicate, outCreatedObjects); } } [Obsolete] private IEnumerable GetPotentialChildren(Base baseObject) { return ConverterInstance.CanConvertToNative(baseObject) ? new[] { "elements" } : baseObject.GetMembers().Keys; } [Obsolete] protected virtual void ConvertChild( object? value, Transform? parent, Func predicate, IList outCreatedObjects ) { foreach (Base b in GraphTraversal.TraverseMember(value)) { RecurseTreeToNative(b, parent, predicate, outCreatedObjects); } } #endregion } }