feat(converter): Refactor to enable Block conversion

This commit is contained in:
JR-Morgan
2022-10-05 21:31:44 +01:00
parent d8d46be6b3
commit 837794f6e9
8 changed files with 922 additions and 640 deletions
@@ -5,18 +5,12 @@ using System.IO;
using System.Linq;
using System.Reflection;
using Objects.Other;
using Objects.Utils;
using Speckle.ConnectorUnity;
using Speckle.Core.Logging;
using Speckle.Core.Models;
using UnityEditor;
using UnityEngine;
using UnityEngine.Rendering;
using Material = UnityEngine.Material;
using Mesh = UnityEngine.Mesh;
using Object = UnityEngine.Object;
using SMesh = Objects.Geometry.Mesh;
using SColor = System.Drawing.Color;
using Transform = UnityEngine.Transform;
using STransform = Objects.Other.Transform;
@@ -26,20 +20,19 @@ namespace Objects.Converter.Unity
public partial class ConverterUnity
{
protected static readonly int EmissionColor = Shader.PropertyToID("_EmissionColor");
protected static readonly int Metallic = Shader.PropertyToID("_Metallic");
protected static readonly int Glossiness = Shader.PropertyToID("_Glossiness");
#region helper methods
public Vector3 VectorByCoordinates(double x, double y, double z, double scaleFactor)
/// <summary>
/// Converts a 3D vector from Speckle's RH Z-up to Unity's LH Y-up coordinate system
/// </summary>
/// <returns>Scaled Vector in Unity coordinate space</returns>
public Vector3 VectorByCoordinates(double x, double y, double z, double scaleFactor = 1d)
{
// switch y and z //TODO is this correct? LH -> RH
return new Vector3((float)(x * scaleFactor), (float)(z * scaleFactor), (float)(y * scaleFactor));
return new Vector3((float) (x * scaleFactor), (float) (z * scaleFactor), (float) (y * scaleFactor));
}
public Vector3 VectorByCoordinates(double x, double y, double z, string units)
{
var f = Speckle.Core.Kits.Units.GetConversionFactor(units, ModelUnits);
@@ -60,16 +53,17 @@ namespace Objects.Converter.Unity
Vector3[] points = new Vector3[arr.Count / 3];
var f = Speckle.Core.Kits.Units.GetConversionFactor(units, ModelUnits);
for (int i = 2, k = 0; i < arr.Count; i += 3)
points[k++] = VectorByCoordinates(arr[i - 2], arr[i - 1], arr[i], f);
return points;
}
#endregion
#region ToSpeckle
//TODO: more of these
/// <summary>
@@ -82,173 +76,13 @@ namespace Objects.Converter.Unity
//switch y and z
return new Point(p.x, p.z, p.y);
}
public virtual List<SMesh>? MeshToSpeckle(MeshFilter meshFilter)
{
Material[]? materials = meshFilter.GetComponent<Renderer>()?.materials;
#if UNITY_EDITOR
var nativeMesh = meshFilter.sharedMesh;
#else
var nativeMesh = meshFilter.mesh;
#endif
if (nativeMesh == null) return null;
List<SMesh> convertedMeshes = new List<SMesh>(nativeMesh.subMeshCount);
for (int i = 0; i < nativeMesh.subMeshCount; i++)
{
var subMesh = nativeMesh.GetSubMesh(i);
SMesh converted;
switch (subMesh.topology)
{
// case MeshTopology.Points:
// //TODO convert as pointcloud
// continue;
case MeshTopology.Triangles:
converted = SubMeshToSpeckle(nativeMesh, meshFilter.transform, subMesh, i, 3);
convertedMeshes.Add(converted);
break;
case MeshTopology.Quads:
converted = SubMeshToSpeckle(nativeMesh, meshFilter.transform, subMesh, i, 4);
convertedMeshes.Add(converted);
break;
default:
Debug.LogError($"Failed to convert submesh {i} of {typeof(GameObject)} {meshFilter.gameObject.name} to Speckle, Unsupported Mesh Topography {subMesh.topology}. Submesh will be ignored.");
continue;
}
if (materials == null || materials.Length <= i) continue;
Material mat = materials[i];
if(mat != null) converted["renderMaterial"] = MaterialToSpeckle(mat);
}
return convertedMeshes;
}
protected virtual SMesh SubMeshToSpeckle(Mesh nativeMesh, Transform instanceTransform, SubMeshDescriptor subMesh, int subMeshIndex, int faceN)
{
var nFaces = nativeMesh.GetIndices( subMeshIndex, true);
int numFaces = nFaces.Length / faceN;
List<int> sFaces = new List<int>(numFaces * (faceN + 1));
int indexOffset = subMesh.firstVertex;
// int i = 0;
// int j = 0;
// while (i < nFaces.Length)
// {
// if (j == 0)
// {
// sFaces.Add(faceN);
// j = faceN;
// }
// sFaces.Add(nFaces[i] - indexOffset);
// j--;
// i++;
// }
int i = nFaces.Length - 1;
int j = 0;
while (i >= 0) //Traverse backwards to ensure CCW face orientation
{
if (j == 0)
{
//Add face cardinality indicator ever
sFaces.Add(faceN);
j = faceN;
}
sFaces.Add(nFaces[i] - indexOffset);
j--;
i--;
}
int vertexTake = subMesh.vertexCount;
var nVertices = nativeMesh.vertices.Skip(indexOffset).Take(vertexTake);
List<double> sVertices = new List<double>(subMesh.vertexCount * 3);
foreach (var vertex in nVertices)
{
var p = instanceTransform.TransformPoint(vertex);
sVertices.Add(p.x);
sVertices.Add(p.z); //z and y swapped //TODO is this correct? LH -> RH
sVertices.Add(p.y);
}
var nColors = nativeMesh.colors.Skip(indexOffset).Take(vertexTake).ToArray();;
List<int> sColors = new List<int>(nColors.Length);
sColors.AddRange(nColors.Select(c => c.ToIntColor()));
var nTexCoords = nativeMesh.uv.Skip(indexOffset).Take(vertexTake).ToArray();
List<double> sTexCoords = new List<double>(nTexCoords.Length * 2);
foreach (var uv in nTexCoords)
{
sTexCoords.Add(uv.x);
sTexCoords.Add(uv.y);
}
var convertedMesh = new SMesh
{
vertices = sVertices,
faces = sFaces,
colors = sColors,
textureCoordinates = sTexCoords,
units = ModelUnits
};
return convertedMesh;
}
protected static HashSet<string> SupportedShadersToSpeckle = new()
{
"Legacy Shaders/Transparent/Diffuse", "Standard"
};
public virtual RenderMaterial MaterialToSpeckle(Material nativeMaterial)
{
//Warning message for unknown shaders
if(!SupportedShadersToSpeckle.Contains(nativeMaterial.shader.name)) Debug.LogWarning($"Material Shader \"{nativeMaterial.shader.name}\" is not explicitly supported, the resulting material may be incorrect");
var color = nativeMaterial.color;
var opacity = 1f;
if (nativeMaterial.shader.name.ToLower().Contains("transparent"))
{
opacity = color.a;
color.a = 255;
}
var emissive = nativeMaterial.IsKeywordEnabled("_EMISSION")
? nativeMaterial.GetColor(EmissionColor).ToIntColor()
: SColor.Black.ToArgb();
var materialName = !string.IsNullOrWhiteSpace(nativeMaterial.name)
? nativeMaterial.name.Replace("(Instance)", string.Empty).TrimEnd()
: $"material-{Guid.NewGuid().ToString().Substring(0, 8)}";
var metalness = nativeMaterial.HasProperty(Metallic)
? nativeMaterial.GetFloat(Metallic)
: 0;
var roughness = nativeMaterial.HasProperty(Glossiness)
? 1 - nativeMaterial.GetFloat(Glossiness)
: 1;
return new RenderMaterial
{
name = materialName,
diffuse = color.ToIntColor(),
opacity = opacity,
metalness = metalness,
roughness = roughness,
emissive = emissive,
};
}
#endregion
#region ToNative
protected GameObject? NewPointBasedGameObject(Vector3[] points, string name)
{
if (points.Length == 0) return null;
@@ -277,7 +111,7 @@ namespace Objects.Converter.Unity
{
Vector3 newPt = VectorByCoordinates(point.x, point.y, point.z, point.units);
var go = NewPointBasedGameObject(new Vector3[] { newPt, newPt }, point.speckle_type);
var go = NewPointBasedGameObject(new Vector3[] {newPt, newPt}, point.speckle_type);
return go;
}
@@ -289,7 +123,7 @@ namespace Objects.Converter.Unity
/// <returns></returns>
public GameObject? LineToNative(Line line)
{
var points = new List<Vector3> { VectorFromPoint(line.start), VectorFromPoint(line.end) };
var points = new List<Vector3> {VectorFromPoint(line.start), VectorFromPoint(line.end)};
var go = NewPointBasedGameObject(points.ToArray(), line.speckle_type);
return go;
@@ -320,301 +154,52 @@ namespace Objects.Converter.Unity
return go;
}
/// <summary>
/// Converts multiple <paramref name="meshes"/> (e.g. with different materials) into one native mesh
/// </summary>
/// <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(IReadOnlyCollection<SMesh> meshes)
{
if (!meshes.Any()) return null;
var go = new GameObject();
MeshDataToNative(meshes, out var nativeMesh, out var nativeMaterials, out var center);
go.transform.position = center;
go.SafeMeshSet(nativeMesh, true);
var meshRenderer = go.AddComponent<MeshRenderer>();
meshRenderer.sharedMaterials = nativeMaterials;
return go;
}
public Dictionary<string, object?> GetProperties(Base o) => GetProperties(o, typeof(Base));
public Dictionary<string, object?> GetProperties(Base o, Type excludeType)
{
var excludeProps = new HashSet<string>(excludeType.GetProperties(BindingFlags.Instance | BindingFlags.Public)
var excludeProps = new HashSet<string>(excludeType
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.Select(x => x.Name));
foreach (string alias in DisplayValuePropertyAliases)
{
excludeProps.Add(alias);
}
excludeProps.Add("renderMaterial");
excludeProps.Add("elements");
excludeProps.Add("name");
//excludeProps.Add("tag");
excludeProps.Add("physicsLayer");
return o.GetMembers()
.Where(x => !(excludeProps.Contains(x.Key) || excludeProps.Contains(x.Key.TrimStart('@'))))
.ToDictionary(x => x.Key, x => (object?)x.Value);
}
/// <summary>
/// Converts <paramref name="speckleMesh"/> to a <see cref="GameObject"/> with a <see cref="MeshRenderer"/>
/// </summary>
/// <param name="speckleMesh">Mesh to convert</param>
/// <returns></returns>
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(new[] {speckleMesh});
//if(converted != null) AttachSpeckleProperties(converted,speckleMesh.GetType(), GetProperties(speckleMesh, typeof(Mesh)));
return converted;
.ToDictionary(x => x.Key, x => (object?) x.Value);
}
/// <summary>
///
/// </summary>
/// <param name="meshes">meshes to be converted as SubMeshes</param>
/// <param name="nativeMesh">The converted native mesh</param>
/// <param name="nativeMaterials">The converted materials (one per converted sub-mesh)</param>
/// <param name="center">Center position for the mesh</param>
public void MeshDataToNative(IReadOnlyCollection<SMesh> meshes, out Mesh nativeMesh, out Material[] nativeMaterials, out Vector3 center)
{
var verts = new List<Vector3>();
var uvs = new List<Vector2>();
var vertexColors = new List<Color>();
var materials = new List<Material>(meshes.Count);
var subMeshes = new List<List<int>>(meshes.Count);
foreach (SMesh m in meshes)
{
if(m.vertices.Count == 0 || m.faces.Count == 0 ) continue;
List<int> tris = new List<int>();
SubmeshToNative(m, verts, tris, uvs, vertexColors, materials);
subMeshes.Add(tris);
}
nativeMaterials = materials.ToArray();
Debug.Assert(verts.Count >= 0);
Debug.Assert(verts.Count >= 0);
nativeMesh = new Mesh();
RecenterVertices(verts, out center);
nativeMesh.subMeshCount = subMeshes.Count;
nativeMesh.SetVertices(verts);
nativeMesh.SetUVs(0, uvs);
nativeMesh.SetColors(vertexColors);
int j = 0;
foreach(var subMeshTriangles in subMeshes)
{
nativeMesh.SetTriangles(subMeshTriangles, j);
j++;
}
if (nativeMesh.vertices.Length >= UInt16.MaxValue)
nativeMesh.indexFormat = IndexFormat.UInt32;
nativeMesh.Optimize();
nativeMesh.RecalculateBounds();
nativeMesh.RecalculateNormals();
nativeMesh.RecalculateTangents();
}
protected void SubmeshToNative(SMesh speckleMesh, List<Vector3> verts, List<int> tris, List<Vector2> texCoords, List<Color> vertexColors, List<Material> materials)
{
speckleMesh.AlignVerticesWithTexCoordsByIndex();
speckleMesh.TriangulateMesh();
int indexOffset = verts.Count;
// Convert Vertices
verts.AddRange(ArrayToPoints(speckleMesh.vertices, speckleMesh.units));
// Convert texture coordinates
bool hasValidUVs = speckleMesh.TextureCoordinatesCount == speckleMesh.VerticesCount;
if(speckleMesh.textureCoordinates.Count > 0 && !hasValidUVs) Debug.LogWarning($"Expected number of UV coordinates to equal vertices. Got {speckleMesh.TextureCoordinatesCount} expected {speckleMesh.VerticesCount}. \nID = {speckleMesh.id}");
if (hasValidUVs)
{
texCoords.Capacity += speckleMesh.TextureCoordinatesCount;
for (int j = 0; j < speckleMesh.TextureCoordinatesCount; j++)
{
var (u, v) = speckleMesh.GetTextureCoordinate(j);
texCoords.Add(new Vector2((float)u,(float)v));
}
}
else if (speckleMesh.bbox != null)
{
//Attempt to generate some crude UV coordinates using bbox //TODO this will be broken for submeshes
texCoords.AddRange(GenerateUV(verts, (float)speckleMesh.bbox.xSize.Length, (float)speckleMesh.bbox.ySize.Length));
}
// Convert vertex colors
if (speckleMesh.colors != null)
{
if (speckleMesh.colors.Count == speckleMesh.VerticesCount)
{
vertexColors.AddRange(speckleMesh.colors.Select(c => c.ToUnityColor()));
}
else if (speckleMesh.colors.Count != 0)
{
//TODO what if only some submeshes have colors?
Debug.LogWarning($"{typeof(SMesh)} {speckleMesh.id} has invalid number of vertex {nameof(SMesh.colors)}. Expected 0 or {speckleMesh.VerticesCount}, got {speckleMesh.colors.Count}");
}
}
// Convert faces
tris.Capacity += (int) (speckleMesh.faces.Count / 4f) * 3;
for (int i = 0; i < speckleMesh.faces.Count; i += 4)
{
//We can safely assume all faces are triangles since we called TriangulateMesh
tris.Add(speckleMesh.faces[i + 1] + indexOffset);
tris.Add(speckleMesh.faces[i + 3] + indexOffset);
tris.Add(speckleMesh.faces[i + 2] + indexOffset);
}
// Convert RenderMaterial
materials.Add(RenderMaterialToNative(speckleMesh["renderMaterial"] as RenderMaterial));
}
private static IEnumerable<Vector2> GenerateUV(IReadOnlyList<Vector3> verts, float xSize, float ySize)
{
var uv = new Vector2[verts.Count];
for (int i = 0; i < verts.Count; i++)
{
var vert = verts[i];
uv[i] = new Vector2(vert.x / xSize, vert.y / ySize);
}
return uv;
}
private static Matrix4x4 UnflattenMatrix(IList<double> flatMatrix)
{
Matrix4x4 matrix = new Matrix4x4();
for(int row = 0; row < 4; row++)
for(int col = 0; col < 4; col++)
{
matrix[row,col] = (float)flatMatrix[row * 4 + col];
}
return matrix.transpose;
}
#endregion
public static void RecenterVertices(List<Vector3> vertices, out Vector3 center)
{
center = Vector3.zero;
if (vertices == null || !vertices.Any()) return;
Bounds meshBounds = new Bounds { center = vertices[0] };
foreach (var vert in vertices)
meshBounds.Encapsulate(vert);
center = meshBounds.center;
for (int i = 0; i < vertices.Count; i++)
vertices[i] -= meshBounds.center;
}
private Material RenderMaterialToNative(RenderMaterial? renderMaterial)
{
//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
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
if (renderMaterial.opacity < 1)
{
shader = Shader.Find("Transparent/Diffuse");
mat = new Material(shader);
}
var c = renderMaterial.diffuse.ToUnityColor();
mat.color = new Color(c.r, c.g, c.b, (float)renderMaterial.opacity);
mat.name = materialName;
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.GenerateMaterials)
{
string name = mat.name.Trim(Path.GetInvalidFileNameChars());
if (!AssetDatabase.IsValidFolder("Assets/Resources")) AssetDatabase.CreateFolder("Assets", "Resources");
if (!AssetDatabase.IsValidFolder("Assets/Resources/Materials")) AssetDatabase.CreateFolder("Assets/Resources", "Materials");
if (!AssetDatabase.IsValidFolder("Assets/Resources/Materials/Speckle Generated")) AssetDatabase.CreateFolder("Assets/Resources/Materials", "Speckle Generated");
if (AssetDatabase.LoadAllAssetsAtPath($"Assets/Resources/Materials/Speckle Generated/" + name + ".mat").Length == 0) AssetDatabase.CreateAsset(mat, "Assets/Resources/Materials/Speckle Generated/" + name + ".mat");
}
#endif
return mat;
// 3. if not renderMaterial was passed, the default shader will be used
}
private SpeckleProperties AttachSpeckleProperties(GameObject go, Type speckleType, IDictionary<string, object?> properties)
{
var sd = go.AddComponent<SpeckleProperties>();
sd.Data = properties;
sd.SpeckleType = speckleType;
return sd;
}
private Base CreateSpeckleObjectFromProperties(GameObject go)
{
var sd = go.GetComponent<SpeckleProperties>();
if (sd == null || sd.Data == null)
return new Base();
Base sobject = (Base)Activator.CreateInstance(sd.SpeckleType);
Base sobject = (Base) Activator.CreateInstance(sd.SpeckleType);
foreach (var key in sd.Data.Keys)
{
try
{
sobject[key] = sd.Data[key];
}
catch(SpeckleException)
catch (SpeckleException)
{
// Ignore SpeckleExceptions that may be caused by get only properties
}
@@ -622,5 +207,134 @@ namespace Objects.Converter.Unity
return sobject;
}
public GameObject? BlockToNative(BlockInstance block)
{
if (block.blockDefinition == null)
{
Debug.Log($"Skipping {typeof(BlockInstance)} {block.id}, block definition was null");
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
{
GameObject? existing = AssetDatabase.LoadAssetAtPath<GameObject>($"{assetPath}/{assetName}");
if (existing)
{
var go = (GameObject) PrefabUtility.InstantiatePrefab(existing);
go.name = block.blockDefinition.name ?? "";
return go;
}
}
#endif
// No existing found, so we Convert the block
GameObject native = new GameObject(block.blockDefinition.name ?? "");
TransformToNativeTransform(native.transform, block.transform);
List<SMesh> meshes = new();
List<Base> others = new();
foreach (Base geo in block.blockDefinition.geometry)
{
if (geo is SMesh m) meshes.Add(m);
else if (geo is Brep s) meshes.AddRange(s.displayValue);
else others.Add(geo);
}
if (meshes.Any())
{
MeshToNativeMesh(meshes, out var nativeMesh);
var nativeMaterials = RenderMaterialsToNative(meshes);
native.SafeMeshSet(nativeMesh, nativeMaterials);
}
foreach (Base child in others)
{
GameObject? c = ConvertToNativeGameObject(child);
if (c == null) continue;
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
return native;
}
/// <summary>
/// Converts a 4x4 transformation matrix from Speckle's <see cref="Other.Transform"/> format,
/// to a Unity <see cref="Matrix4x4"/>. Applying Z -> Y up conversion, and applying units to the translation
/// </summary>
/// <param name="speckleTransform"></param>
/// <returns>Transformation matrix in Unity's coordinate system</returns>
public Matrix4x4 TransformToNativeMatrix(STransform speckleTransform)
{
double VD(int i) => speckleTransform.value[i];
float V(int i) => (float) VD(i);
var sf = Speckle.Core.Kits.Units.GetConversionFactor(speckleTransform.units, ModelUnits);
return new Matrix4x4
{
// Left (X -> X)
[0, 0] = V(0),
[2, 0] = V(4),
[1, 0] = V(8),
[3, 0] = V(12),
//Up (Z -> Y)
[0, 2] = V(1),
[2, 2] = V(5),
[1, 2] = V(9),
[3, 2] = V(13),
//Forwards (Y -> Z)
[0, 1] = V(2),
[2, 1] = V(6),
[1, 1] = V(10),
[3, 1] = V(14),
//Translation
[0, 3] = (float) (VD(3) * sf),
[2, 3] = (float) (VD(7) * sf),
[1, 3] = (float) (VD(11) * sf),
[3, 3] = V(15),
};
}
public void TransformToNativeTransform(Transform transform, STransform speckleTransform)
{
Matrix4x4 matrix = TransformToNativeMatrix(speckleTransform);
ApplyMatrixToTransform(transform, matrix);
}
protected static void ApplyMatrixToTransform(Transform transform, Matrix4x4 m)
{
transform.localScale =
m.lossyScale; //doesn't work for non TRS, maybe we could fallback to squareSum approach (see TransformVectorized::SetFromMatrix in UE src)
//We can't use m.rotation, as it gives us incorrect results (perhaps because of RH -> LH? or maybe our MatrixToNative is broken?)
transform.localRotation = Quaternion.LookRotation(
m.GetColumn(2),
m.GetColumn(1)
);
transform.localPosition = m.GetPosition();
}
}
}
@@ -0,0 +1,534 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Objects.Other;
using Objects.Utils;
using PlasticPipe.Certificates;
using Speckle.ConnectorUnity;
using Speckle.Core.Models;
using UnityEditor;
using UnityEngine;
using UnityEngine.Rendering;
using Material = UnityEngine.Material;
using Mesh = UnityEngine.Mesh;
using Object = UnityEngine.Object;
using SMesh = Objects.Geometry.Mesh;
using SColor = System.Drawing.Color;
using Transform = UnityEngine.Transform;
using STransform = Objects.Other.Transform;
#nullable enable
namespace Objects.Converter.Unity
{
public partial class ConverterUnity
{
protected static readonly int EmissionColor = Shader.PropertyToID("_EmissionColor");
protected static readonly int Metallic = Shader.PropertyToID("_Metallic");
protected static readonly int Glossiness = Shader.PropertyToID("_Glossiness");
#region ToSpeckle
public virtual List<SMesh>? MeshToSpeckle(MeshFilter meshFilter)
{
Material[]? materials = meshFilter.GetComponent<Renderer>()?.materials;
#if UNITY_EDITOR
var nativeMesh = meshFilter.sharedMesh;
#else
var nativeMesh = meshFilter.mesh;
#endif
if (nativeMesh == null) return null;
List<SMesh> convertedMeshes = new List<SMesh>(nativeMesh.subMeshCount);
for (int i = 0; i < nativeMesh.subMeshCount; i++)
{
var subMesh = nativeMesh.GetSubMesh(i);
SMesh converted;
switch (subMesh.topology)
{
// case MeshTopology.Points:
// //TODO convert as pointcloud
// continue;
case MeshTopology.Triangles:
converted = SubMeshToSpeckle(nativeMesh, meshFilter.transform, subMesh, i, 3);
convertedMeshes.Add(converted);
break;
case MeshTopology.Quads:
converted = SubMeshToSpeckle(nativeMesh, meshFilter.transform, subMesh, i, 4);
convertedMeshes.Add(converted);
break;
default:
Debug.LogError(
$"Failed to convert submesh {i} of {typeof(GameObject)} {meshFilter.gameObject.name} to Speckle, Unsupported Mesh Topography {subMesh.topology}. Submesh will be ignored.");
continue;
}
if (materials == null || materials.Length <= i) continue;
Material mat = materials[i];
if (mat != null) converted["renderMaterial"] = MaterialToSpeckle(mat);
}
return convertedMeshes;
}
protected virtual SMesh SubMeshToSpeckle(Mesh nativeMesh, Transform instanceTransform,
SubMeshDescriptor subMesh, int subMeshIndex, int faceN)
{
var nFaces = nativeMesh.GetIndices(subMeshIndex, true);
int numFaces = nFaces.Length / faceN;
List<int> sFaces = new List<int>(numFaces * (faceN + 1));
int indexOffset = subMesh.firstVertex;
// int i = 0;
// int j = 0;
// while (i < nFaces.Length)
// {
// if (j == 0)
// {
// sFaces.Add(faceN);
// j = faceN;
// }
// sFaces.Add(nFaces[i] - indexOffset);
// j--;
// i++;
// }
int i = nFaces.Length - 1;
int j = 0;
while (i >= 0) //Traverse backwards to ensure CCW face orientation
{
if (j == 0)
{
//Add face cardinality indicator ever
sFaces.Add(faceN);
j = faceN;
}
sFaces.Add(nFaces[i] - indexOffset);
j--;
i--;
}
int vertexTake = subMesh.vertexCount;
var nVertices = nativeMesh.vertices.Skip(indexOffset).Take(vertexTake);
List<double> sVertices = new List<double>(subMesh.vertexCount * 3);
foreach (var vertex in nVertices)
{
var p = instanceTransform.TransformPoint(vertex);
sVertices.Add(p.x);
sVertices.Add(p.z); //z and y swapped //TODO is this correct? LH -> RH
sVertices.Add(p.y);
}
var nColors = nativeMesh.colors.Skip(indexOffset).Take(vertexTake).ToArray();
;
List<int> sColors = new List<int>(nColors.Length);
sColors.AddRange(nColors.Select(c => c.ToIntColor()));
var nTexCoords = nativeMesh.uv.Skip(indexOffset).Take(vertexTake).ToArray();
List<double> sTexCoords = new List<double>(nTexCoords.Length * 2);
foreach (var uv in nTexCoords)
{
sTexCoords.Add(uv.x);
sTexCoords.Add(uv.y);
}
var convertedMesh = new SMesh
{
vertices = sVertices,
faces = sFaces,
colors = sColors,
textureCoordinates = sTexCoords,
units = ModelUnits
};
return convertedMesh;
}
/// <summary>
/// List of officially supported shaders. Will attempt to convert shaders not on this list, but will throw warning.
/// </summary>
protected static HashSet<string> SupportedShadersToSpeckle = new()
{
"Legacy Shaders/Transparent/Diffuse", "Standard"
};
public virtual RenderMaterial MaterialToSpeckle(Material nativeMaterial)
{
//Warning message for unknown shaders
if (!SupportedShadersToSpeckle.Contains(nativeMaterial.shader.name))
Debug.LogWarning(
$"Material Shader \"{nativeMaterial.shader.name}\" is not explicitly supported, the resulting material may be incorrect");
var color = nativeMaterial.color;
var opacity = 1f;
if (nativeMaterial.shader.name.ToLower().Contains("transparent"))
{
opacity = color.a;
color.a = 255;
}
var emissive = nativeMaterial.IsKeywordEnabled("_EMISSION")
? nativeMaterial.GetColor(EmissionColor).ToIntColor()
: SColor.Black.ToArgb();
var materialName = !string.IsNullOrWhiteSpace(nativeMaterial.name)
? nativeMaterial.name.Replace("(Instance)", string.Empty).TrimEnd()
: $"material-{Guid.NewGuid().ToString().Substring(0, 8)}";
var metalness = nativeMaterial.HasProperty(Metallic)
? nativeMaterial.GetFloat(Metallic)
: 0;
var roughness = nativeMaterial.HasProperty(Glossiness)
? 1 - nativeMaterial.GetFloat(Glossiness)
: 1;
return new RenderMaterial
{
name = materialName,
diffuse = color.ToIntColor(),
opacity = opacity,
metalness = metalness,
roughness = roughness,
emissive = emissive,
};
}
#endregion
#region ToNative
/// <summary>
/// Converts multiple <paramref name="meshes"/> (e.g. with different materials) into one native mesh
/// </summary>
/// <param name="element">Root element who's name/id is used to identify the mesh</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)
{
if (!meshes.Any())
{
Debug.Log($"Skipping {element.GetType()} {element.id}, zero {typeof(SMesh)} provided");
return null;
}
Mesh nativeMesh;
Material[] nativeMaterials = RenderMaterialsToNative(meshes);
Vector3 center;
if (LoadedAssets.TryGetValue(element.id, out var existingObj)
&& existingObj is Mesh existing)
{
nativeMesh = existing;
MeshDataToNative(meshes,
out List<Vector3> verts,
out _,
out _,
out _);
center = CalculateBounds(verts).center;
}
else
{
MeshToNativeMesh(meshes, out nativeMesh, out center);
string name = GetAssetName(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
}
var go = new GameObject();
go.transform.position = center;
go.SafeMeshSet(nativeMesh, nativeMaterials);
return go;
}
/// <summary>
/// Converts <paramref name="speckleMesh"/> to a <see cref="GameObject"/> with a <see cref="MeshRenderer"/>
/// </summary>
/// <param name="speckleMesh">Mesh to convert</param>
/// <returns></returns>
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});
// Raw meshes shouldn't have dynamic props to attach
//if (converted != null) AttachSpeckleProperties(converted,speckleMesh.GetType(), GetProperties(speckleMesh, typeof(Mesh)));
return converted;
}
/// <summary>
/// Converts Speckle <see cref="SMesh"/>s as a native <see cref="Mesh"/> object
/// </summary>
/// <param name="meshes">meshes to be converted as SubMeshes</param>
/// <param name="nativeMesh">The converted native mesh</param>
public void MeshToNativeMesh(IReadOnlyCollection<SMesh> meshes, out Mesh nativeMesh)
=> MeshToNativeMesh(meshes, out nativeMesh, out _, false);
/// <inheritdoc cref="MeshDataToNative(System.Collections.Generic.IReadOnlyCollection{Objects.Geometry.Mesh},out UnityEngine.Mesh,out UnityEngine.Material[])"/>
/// <param name="recenterVerts">when true, will recenter vertices</param>
/// <param name="center">Center position for the mesh</param>
public void MeshToNativeMesh(IReadOnlyCollection<SMesh> meshes,
out Mesh nativeMesh,
out Vector3 center,
bool recenterVerts = true)
{
MeshDataToNative(meshes,
out List<Vector3> verts,
out List<Vector2> uvs,
out List<Color> vertexColors,
out List<List<int>> subMeshes);
Debug.Assert(verts.Count >= 0);
center = recenterVerts ? RecenterVertices(verts) : Vector3.zero;
nativeMesh = new Mesh();
nativeMesh.subMeshCount = subMeshes.Count;
nativeMesh.SetVertices(verts);
nativeMesh.SetUVs(0, uvs);
nativeMesh.SetColors(vertexColors);
int j = 0;
foreach (var subMeshTriangles in subMeshes)
{
nativeMesh.SetTriangles(subMeshTriangles, j);
j++;
}
if (nativeMesh.vertices.Length >= UInt16.MaxValue)
nativeMesh.indexFormat = IndexFormat.UInt32;
nativeMesh.Optimize();
nativeMesh.RecalculateBounds();
nativeMesh.RecalculateNormals();
nativeMesh.RecalculateTangents();
}
public void MeshDataToNative(IReadOnlyCollection<SMesh> meshes,
out List<Vector3> verts,
out List<Vector2> uvs,
out List<Color> vertexColors,
out List<List<int>> subMeshes)
{
verts = new List<Vector3>();
uvs = new List<Vector2>();
vertexColors = new List<Color>();
subMeshes = new List<List<int>>(meshes.Count);
foreach (SMesh m in meshes)
{
if (m.vertices.Count == 0 || m.faces.Count == 0) continue;
List<int> tris = new List<int>();
SubmeshToNative(m, verts, tris, uvs, vertexColors);
subMeshes.Add(tris);
}
}
protected void SubmeshToNative(SMesh speckleMesh, List<Vector3> verts, List<int> tris, List<Vector2> texCoords,
List<Color> vertexColors)
{
speckleMesh.AlignVerticesWithTexCoordsByIndex();
speckleMesh.TriangulateMesh();
int indexOffset = verts.Count;
// Convert Vertices
verts.AddRange(ArrayToPoints(speckleMesh.vertices, speckleMesh.units));
// Convert texture coordinates
bool hasValidUVs = speckleMesh.TextureCoordinatesCount == speckleMesh.VerticesCount;
if (speckleMesh.textureCoordinates.Count > 0 && !hasValidUVs)
Debug.LogWarning(
$"Expected number of UV coordinates to equal vertices. Got {speckleMesh.TextureCoordinatesCount} expected {speckleMesh.VerticesCount}. \nID = {speckleMesh.id}");
if (hasValidUVs)
{
texCoords.Capacity += speckleMesh.TextureCoordinatesCount;
for (int j = 0; j < speckleMesh.TextureCoordinatesCount; j++)
{
var (u, v) = speckleMesh.GetTextureCoordinate(j);
texCoords.Add(new Vector2((float) u, (float) v));
}
}
else if (speckleMesh.bbox != null)
{
// Attempt to generate some crude UV coordinates using bbox
texCoords.AddRange(GenerateUV(indexOffset, verts, (float) speckleMesh.bbox.xSize.Length,
(float) speckleMesh.bbox.ySize.Length));
}
else
{
texCoords.AddRange(Enumerable.Repeat(Vector2.zero, verts.Count - indexOffset));
}
// Convert vertex colors
if (speckleMesh.colors != null)
{
if (speckleMesh.colors.Count == speckleMesh.VerticesCount)
{
vertexColors.AddRange(speckleMesh.colors.Select(c => c.ToUnityColor()));
}
else if (speckleMesh.colors.Count != 0)
{
//TODO what if only some submeshes have colors?
Debug.LogWarning(
$"{typeof(SMesh)} {speckleMesh.id} has invalid number of vertex {nameof(SMesh.colors)}. Expected 0 or {speckleMesh.VerticesCount}, got {speckleMesh.colors.Count}");
}
}
// Convert faces
tris.Capacity += (int) (speckleMesh.faces.Count / 4f) * 3;
for (int i = 0; i < speckleMesh.faces.Count; i += 4)
{
// We can safely assume all faces are triangles since we called TriangulateMesh
tris.Add(speckleMesh.faces[i + 1] + indexOffset);
tris.Add(speckleMesh.faces[i + 3] + indexOffset);
tris.Add(speckleMesh.faces[i + 2] + indexOffset);
}
}
protected static IEnumerable<Vector2> GenerateUV(int indexOffset, IReadOnlyList<Vector3> verts, float xSize,
float ySize)
{
var uv = new Vector2[verts.Count - indexOffset];
for (int i = 0; i < verts.Count - indexOffset; i++)
{
var vert = verts[i];
uv[i] = new Vector2(vert.x / xSize, vert.y / ySize);
}
return uv;
}
protected static Vector3 RecenterVertices(IList<Vector3> vertices)
{
if (!vertices.Any()) return Vector3.zero;
Bounds meshBounds = CalculateBounds(vertices);
for (int i = 0; i < vertices.Count; i++)
vertices[i] -= meshBounds.center;
return meshBounds.center;
}
protected static Bounds CalculateBounds(IList<Vector3> points)
{
Bounds b = new Bounds {center = points[0]};
foreach (var p in points)
b.Encapsulate(p);
return b;
}
public Material[] RenderMaterialsToNative(IEnumerable<SMesh> meshes)
{
return meshes.Select(m => RenderMaterialToNative(m["renderMaterial"] as RenderMaterial)).ToArray();
}
public Material RenderMaterialToNative(RenderMaterial? renderMaterial)
{
//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
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
if (renderMaterial.opacity < 1)
{
shader = Shader.Find("Transparent/Diffuse");
mat = new Material(shader);
}
var c = renderMaterial.diffuse.ToUnityColor();
mat.color = new Color(c.r, c.g, c.b, (float) renderMaterial.opacity);
mat.name = materialName;
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
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
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f174f59c0ccd3c74cb6b832d1a04b637
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -3,203 +3,228 @@ using Speckle.Core.Models;
using System;
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 UnityEditor;
using UnityEngine;
using Mesh = Objects.Geometry.Mesh;
using Object = UnityEngine.Object;
namespace Objects.Converter.Unity
{
public partial class ConverterUnity : ISpeckleConverter
{
#region implemented methods
public void SetConverterSettings(object settings) => throw new NotImplementedException();
public string Description => "Default Speckle Kit for Unity";
public string Name => nameof(ConverterUnity);
public string Author => "Speckle";
public string WebsiteOrEmail => "https://speckle.systems";
public ProgressReport Report { get; }
public ReceiveMode ReceiveMode { get; set; }
public IEnumerable<string> GetServicedApplications() => new string[] {VersionedHostApplications.Unity};
public HashSet<Exception> ConversionErrors { get; private set; }
public Dictionary<string, Object> LoadedAssets { get; private set; }
public void SetContextDocument(object doc)
public partial class ConverterUnity : ISpeckleConverter
{
if(doc is not Dictionary<string, Object> loadedAssets) throw new ArgumentException($"Expected {nameof(doc)} to be of type {typeof(Dictionary<string, Object>)}", nameof(doc));
LoadedAssets = loadedAssets;
}
#region implemented methods
public void SetContextObjects(List<ApplicationObject> objects) => throw new NotImplementedException();
public void SetConverterSettings(object settings) => throw new NotImplementedException();
public string Description => "Default Speckle Kit for Unity";
public string Name => nameof(ConverterUnity);
public string Author => "Speckle";
public string WebsiteOrEmail => "https://speckle.systems";
public ProgressReport Report { get; }
public ReceiveMode ReceiveMode { get; set; }
public IEnumerable<string> GetServicedApplications() => new string[] {VersionedHostApplications.Unity};
public Dictionary<string, Object> LoadedAssets { get; private set; }
public void SetContextDocument(object doc)
{
if (doc is not Dictionary<string, Object> loadedAssets)
throw new ArgumentException(
$"Expected {nameof(doc)} to be of type {typeof(Dictionary<string, Object>)}", nameof(doc));
LoadedAssets = loadedAssets;
}
public void SetContextObjects(List<ApplicationObject> objects) => throw new NotImplementedException();
public void SetPreviousContextObjects(List<ApplicationObject> objects) => throw new NotImplementedException();
public void SetPreviousContextObjects(List<ApplicationObject> objects) => throw new NotImplementedException();
#nullable enable
public object? ConvertToNative(Base @object) => ConvertToNativeGameObject(@object);
public object? ConvertToNative(Base @object) => ConvertToNativeGameObject(@object);
public Base ConvertToSpeckle(object @object)
{
if (!(@object is GameObject go)) throw new NotSupportedException($"Cannot convert object of type {@object.GetType()} to Speckle");
return ConvertGameObjectToSpeckle(go);
}
#endregion implemented methods
public Base ConvertGameObjectToSpeckle(GameObject go)
{
Base speckleObject = CreateSpeckleObjectFromProperties(go);
speckleObject["name"] = go.name;
//speckleObject["transform"] = TransformToSpeckle(go.Transform); //TODO
speckleObject["tag"] = go.tag;
speckleObject["physicsLayer"] = LayerMask.LayerToName(go.layer);
//speckleObject["isStatic"] = go.isStatic; //todo figure out realtime-rendered static mobility interoperability (unreal)
foreach (Component component in go.GetComponents<Component>())
{
try
public Base ConvertToSpeckle(object @object)
{
switch (component)
{
case MeshFilter meshFilter:
var displayValues = MeshToSpeckle(meshFilter);
speckleObject.SetDetachedPropertyChecked("displayValue", displayValues);
break;
// case Camera camera:
// speckleObject["cameraComponent"] = CameraToSpeckle(camera);
// break;
}
if (!(@object is GameObject go))
throw new NotSupportedException($"Cannot convert object of type {@object.GetType()} to Speckle");
return ConvertGameObjectToSpeckle(go);
}
catch(Exception e)
#endregion implemented methods
public Base ConvertGameObjectToSpeckle(GameObject go)
{
Debug.LogError($"Failed to convert {component.GetType()} component\n{e}", component);
Base speckleObject = CreateSpeckleObjectFromProperties(go);
speckleObject["name"] = go.name;
//speckleObject["transform"] = TransformToSpeckle(go.Transform); //TODO
speckleObject["tag"] = go.tag;
speckleObject["physicsLayer"] = LayerMask.LayerToName(go.layer);
//speckleObject["isStatic"] = go.isStatic; //todo figure out realtime-rendered static mobility interoperability (unreal)
foreach (Component component in go.GetComponents<Component>())
{
try
{
switch (component)
{
case MeshFilter meshFilter:
var displayValues = MeshToSpeckle(meshFilter);
speckleObject.SetDetachedPropertyChecked("displayValue", displayValues);
break;
// case Camera camera:
// speckleObject["cameraComponent"] = CameraToSpeckle(camera);
// break;
}
}
catch (Exception e)
{
Debug.LogError($"Failed to convert {component.GetType()} component\n{e}", component);
}
}
return speckleObject;
}
}
return speckleObject;
}
public GameObject? ConvertToNativeGameObject(Base speckleObject)
{
switch (speckleObject)
{
// case Point o:
// return PointToNative(o);
// case Line o:
// return LineToNative(o);
// case Polyline o:
// return PolylineToNative(o);
// case Curve o:
// return CurveToNative(o);
case View3D v:
return View3DToNative(v);
case Mesh o:
return MeshToNative(o);
default:
//Object is not a raw geometry, convert it as display value element
GameObject? element = DisplayValueToNative(speckleObject);
if (element != null)
{
if(!speckleObject.speckle_type.Contains("Objects.Geometry"))
AttachSpeckleProperties(element, speckleObject.GetType(), GetProperties(speckleObject));
return element;
}
return new GameObject();
}
}
public IList<string> DisplayValuePropertyAliases { get; set; } = new[] {"displayValue","@displayValue","displayMesh", "@displayMesh" };
public GameObject? DisplayValueToNative(Base @object)
{
foreach (string alias in DisplayValuePropertyAliases)
{
switch (@object[alias])
public GameObject? ConvertToNativeGameObject(Base speckleObject)
{
//capture any other object that might have a mesh representation
case IList dvCollection:
return MeshesToNative(dvCollection.OfType<Mesh>().ToList());
case Mesh dvMesh:
return MeshesToNative(new[] {dvMesh});
case Base dvBase:
return ConvertToNativeGameObject(dvBase);
switch (speckleObject)
{
// case Point o:
// return PointToNative(o);
// case Line o:
// return LineToNative(o);
// case Polyline o:
// return PolylineToNative(o);
// case Curve o:
// return CurveToNative(o);
case View3D v:
return View3DToNative(v);
case Mesh o:
return MeshToNative(o);
case BlockInstance o:
return BlockToNative(o);
default:
//Object is not a raw geometry, convert it as display value element
GameObject? element = DisplayValueToNative(speckleObject);
if (element != null)
{
if (!speckleObject.speckle_type.Contains("Objects.Geometry"))
AttachSpeckleProperties(element, speckleObject.GetType(), GetProperties(speckleObject));
return element;
}
return new GameObject();
}
}
}
return null;
}
public List<Base> ConvertToSpeckle(List<object> objects)
{
return objects.Select(ConvertToSpeckle).ToList();
}
public List<object?> ConvertToNative(List<Base> objects)
{
return objects.Select(x => ConvertToNative(x)).ToList();
}
public IList<string> DisplayValuePropertyAliases { get; set; } =
new[] {"displayValue", "@displayValue", "displayMesh", "@displayMesh"};
public bool CanConvertToSpeckle(object @object)
{
switch (@object)
{
case GameObject o:
return o.GetComponent<MeshFilter>() != null;
default:
return false;
}
}
public GameObject? DisplayValueToNative(Base @object)
{
foreach (string alias in DisplayValuePropertyAliases)
{
switch (@object[alias])
{
//capture any other object that might have a mesh representation
case IList dvCollection:
return MeshesToNative(@object, dvCollection.OfType<Mesh>().ToList());
case Mesh dvMesh:
return MeshesToNative(@object, new[] {dvMesh});
case Base dvBase:
return ConvertToNativeGameObject(dvBase);
}
}
public bool CanConvertToNative(Base @object)
{
switch (@object)
{
// case Point _:
// return true;
// case Line _:
// return true;
// case Polyline _:
// return true;
// case Curve _:
// return true;
// case View3D _:
// return true;
// case View2D _:
// return false;
case Mesh _:
return true;
default:
foreach (string alias in DisplayValuePropertyAliases)
{
if (@object[alias] is Base)
return true;
if (@object[alias] is IList)
return true;
}
return false;
return null;
}
private SpeckleProperties AttachSpeckleProperties(GameObject go, Type speckleType,
IDictionary<string, object?> properties)
{
var sd = go.AddComponent<SpeckleProperties>();
sd.Data = properties;
sd.SpeckleType = speckleType;
return sd;
}
public List<Base> ConvertToSpeckle(List<object> objects)
{
return objects.Select(ConvertToSpeckle).ToList();
}
public List<object?> ConvertToNative(List<Base> objects)
{
return objects.Select(x => ConvertToNative(x)).ToList();
}
public bool CanConvertToSpeckle(object @object)
{
switch (@object)
{
case GameObject o:
return o.GetComponent<MeshFilter>() != null;
default:
return false;
}
}
public bool CanConvertToNative(Base @object)
{
switch (@object)
{
// case Point _:
// return true;
// case Line _:
// return true;
// case Polyline _:
// return true;
// case Curve _:
// return true;
// case View3D _:
// return true;
case View2D _:
return false;
case Mesh _:
return true;
case BlockInstance _:
return true;
default:
foreach (string alias in DisplayValuePropertyAliases)
{
if (@object[alias] is Base)
return true;
if (@object[alias] is IList)
return true;
}
return false;
}
}
protected static void CreateDirectoryFromAssetPath(string assetPath)
{
string directoryPath = Path.GetDirectoryName(assetPath);
if (Directory.Exists(directoryPath))
return;
Directory.CreateDirectory(directoryPath);
AssetDatabase.Refresh();
}
}
}
}
@@ -313,7 +313,7 @@ namespace Speckle.ConnectorUnity.Editor
SelectedCommitIndex = EditorGUILayout.Popup("Commits",
SelectedCommitIndex,
Branches[SelectedBranchIndex].commits.items.Select(x => x.message).ToArray(),
Branches[SelectedBranchIndex].commits.items.Select(x => $"{x.message} - {x.id}").ToArray(),
GUILayout.Height(20),
GUILayout.ExpandWidth(true));
@@ -325,7 +325,7 @@ namespace Speckle.ConnectorUnity.Editor
GUILayout.Label("Generate material assets");
GUILayout.FlexibleSpace();
StreamManager.GenerateMaterials = GUILayout.Toggle(StreamManager.GenerateMaterials, "");
StreamManager.GenerateAssets = GUILayout.Toggle(StreamManager.GenerateAssets, "");
EditorGUILayout.EndHorizontal();
#endregion
@@ -43,7 +43,7 @@ namespace Speckle.ConnectorUnity
foreach (var nameAlias in namePropertyAliases)
{
string? s = baseObject[nameAlias] as string;
if (!string.IsNullOrWhiteSpace(s)) return s!; //TODO any sanitization needed?
if (!string.IsNullOrWhiteSpace(s)) return s; //TODO any sanitization needed?
}
// 2. Use type + id as fallback name
@@ -63,12 +63,14 @@ namespace Speckle.ConnectorUnity
if (converted is GameObject go)
{
outCreatedObjects.Add(go);
nextParent = go.transform;
go.name = GenerateObjectName(baseObject);
go.transform.SetParent(parent);
//TODO add support for unity specific props
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 = GenerateObjectName(baseObject);
//if (baseObject["tag"] is string t) go.tag = t;
if (baseObject["physicsLayer"] is string layerName)
{
@@ -4,6 +4,7 @@ using Speckle.Core.Credentials;
using System.Collections.Generic;
using Speckle.Core.Models;
using UnityEngine;
using UnityEngine.Serialization;
namespace Speckle.ConnectorUnity
{
@@ -27,7 +28,7 @@ namespace Speckle.ConnectorUnity
public List<Branch> Branches;
#if UNITY_EDITOR
public static bool GenerateMaterials = false;
public static bool GenerateAssets = false;
#endif
#nullable enable
+15 -20
View File
@@ -1,6 +1,7 @@
using System;
using UnityEngine;
#nullable enable
namespace Speckle.ConnectorUnity
{
public static class Utils
@@ -22,35 +23,29 @@ namespace Speckle.ConnectorUnity
public static void SafeMeshSet(this GameObject go, Mesh m, bool addMeshFilterIfNotFound)
public static void SafeMeshSet(this GameObject go, Mesh m, Material[] materials, bool addMeshComponentsIfNotFound = true)
{
MeshFilter? mf = go.GetComponent<MeshFilter>();
MeshRenderer? mr = go.GetComponent<MeshRenderer>();
var mf = go.GetComponent<MeshFilter>();
if (mf == null)
if (addMeshComponentsIfNotFound)
{
if (!addMeshFilterIfNotFound) return;
mf = go.AddComponent<MeshFilter>();
if (mf == null)
mf = go.AddComponent<MeshFilter>();
if (mr == null)
mr = go.AddComponent<MeshRenderer>();
}
if (Application.isPlaying)
mf.mesh = m;
else
mf.sharedMesh = m;
}
public static void SafeMeshSet(this GameObject go, Mesh m)
{
var mf = go.GetComponent<MeshFilter>();
if (mf == null) return;
if (Application.isPlaying)
{
mf.mesh = m;
mr.materials = materials;
}
else
{
mf.sharedMesh = m;
mr.sharedMaterials = materials;
}
}