wip implementation of traversal refactor

This commit is contained in:
Jedd Morgan
2023-04-21 22:34:59 +01:00
parent 9a89255ae8
commit a89e4cdfe7
5 changed files with 195 additions and 53 deletions
@@ -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
/// <summary>
/// Struct that encapsulates the result of a ToNative conversion of a single SpeckleObject <see cref="Base"/>
/// </summary>
public readonly struct ConversionResult
{
/// <summary>
/// The context that was converted ToNative
/// </summary>
public readonly TraversalContext traversalContext;
/// <summary>
/// The result of conversion a successful conversion
/// </summary>
public readonly GameObject? converted;
/// <summary>
/// The result of conversion a failed conversion
/// </summary>
public readonly Exception? exception;
public IEnumerator ConvertToNative(Base rootObject, Transform? parent, IDictionary<Base, GameObject> outCreatedObjects)
/// <summary>
/// Constructor used for Successful conversions
/// </summary>
/// <param name="traversalContext">The current traversal context</param>
/// <param name="converted">The resultant ToNative conversion of <see cref="TraversalContext.current"/> context object</param>
/// <exception cref="ArgumentNullException"/>
public ConversionResult(TraversalContext traversalContext, [NotNull] GameObject? converted)
: this(traversalContext, converted, null)
{
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));
/// <summary>
/// Constructor used for Failed conversions
/// </summary>
/// <param name="traversalContext">The current conversion</param>
/// <param name="exception">The operation halting exception that occured</param>
/// <param name="converted">Optional converted GameObject</param>
/// <exception cref="ArgumentNullException"/>
public ConversionResult(TraversalContext traversalContext, [NotNull] Exception? exception,
GameObject? converted = null)
: this(traversalContext, converted, exception)
{
if (exception == 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;
}
/// <summary>
///
/// </summary>
/// <param name="converted">The converted <see cref="GameObject"/></param>
/// <param name="exception">The <see cref="exception"/> that occured during conversion</param>
/// <returns>True if the conversion was successful</returns>
public bool WasSuccessful(
[NotNullWhen(true)] out GameObject? converted,
[NotNullWhen(false)] out Exception? exception)
{
converted = this.converted;
exception = this.exception;
return this.exception == null;
}
public Base SpeckleObject => traversalContext.current;
}
public partial class RecursiveConverter
{
/// <summary>
/// Coroutine for
/// </summary>
/// <param name="rootObject"></param>
/// <param name="parent"></param>
/// <param name="predicate">Optional filter function </param>
/// <returns></returns>
public IEnumerator<ConversionResult> RecursivelyConvertToNative(Base rootObject, Transform? parent,
Func<TraversalContext, bool>? predicate = null)
{
var traversalFunc = DefaultTraversal.CreateBIMTraverseFunc(ConverterInstance);
var objectsToConvert = traversalFunc.Traverse(rootObject);
if(predicate != null) objectsToConvert = objectsToConvert.Where(predicate);
Dictionary<Base, GameObject> 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);
}
/// <summary>
/// Converts a objectTree (see <see cref="GraphTraversal"/>) to unevaluated enumerable.
/// As this enumerable is iterated through, each context <see cref="Base"/> object will be converted to <see cref="GameObject"/> (if successful)
/// or <see langword="null"/> if not.
/// </summary>
/// <remarks>
/// You may enumerate over multiple frames (e.g. coroutine) but you must ensure the output eventually gets fully enumerated (exactly once)
/// </remarks>
/// <param name="objectTree"></param>
/// <param name="parent"></param>
/// <param name="outCreatedObjects"></param>
/// <returns></returns>
public IEnumerable<ConversionResult> ConvertTree(IEnumerable<TraversalContext> objectTree, Transform? parent, IDictionary<Base, GameObject?> 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<Base, GameObject?> createdObjects)
{
if (tc == null) return null; //We've reached the root object, and still not found a converted parent
if(createdObjects.TryGetValue(tc.current, out GameObject? p) && p != null)
return p.transform;
//Go one level up, and repeat!
return GetParent(tc.parent, createdObjects);
}
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;
}
@@ -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<Base>? beforeConvertCallback = null)
{
@@ -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;
}
@@ -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
/// <param name="element">The <see cref="Base"/> object being converted</param>
/// <param name="meshes">Collection of <see cref="Objects.Geometry.Mesh"/>es that shall be converted</param>
/// <returns>A <see cref="GameObject"/> with the converted <see cref="UnityEngine.Mesh"/>, <see cref="MeshFilter"/>, and <see cref="MeshRenderer"/></returns>
public GameObject? MeshesToNative(Base element, IReadOnlyCollection<SMesh> meshes)
public GameObject MeshesToNative(Base element, IReadOnlyCollection<SMesh> meshes)
{
if (!meshes.Any())
{
Debug.Log($"Skipping {element.GetType()} {element.id}, zero {typeof(SMesh)} provided");
return null;
}
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
/// </summary>
/// <param name="speckleMesh">Mesh to convert</param>
/// <returns></returns>
public GameObject? MeshToNative(SMesh speckleMesh)
public GameObject MeshToNative(SMesh speckleMesh)
{
if (speckleMesh.vertices.Count == 0 || speckleMesh.faces.Count == 0)
{
Debug.Log($"Skipping mesh {speckleMesh.id}, mesh data was empty");
return null;
}
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)));
@@ -31,6 +31,9 @@ namespace Speckle.ConnectorUnity
return HostAppVersion.v;
#endif
}
public static bool Tautology<T>(T _) => true;
}
}