Converted STL to GLB files frequently fail to load in Google Scene-View

Hello!

I’m trying to convert STL files to GLBs with a basic PbrMaterial in order to load them into Google’s Scene-Viewer using a direct intent link for Android devices. I’m running into an issue where the majority of STL files ran through the code snippet below result in a “Couldn’t load object” error inside of Scene-Viewer – unfortunately it doesn’t give me any more error information beyond that.

I’ve found a hacky workaround in the short-term that allows models to load in Scene-Viewer by applying a normals texture and replacing VertexElementNormals and VertexElementUV on all nodes with newly calculated values, but the resulting object has glitched textures depending on the structure of the polygons in the model.

I’ve also tried manually converting the GLB to glTF and back to GLB using glTF Tools in VSCode and found that the regeneration works perfectly. It appears that somewhere in the process the normals data is being recalculated.

This issue is affecting me on both versions 21.4.0 and 21.5.0. Since neither the original (high rate of model failure) nor the workaround (glitched textures on AR models) are desirable, I was hoping to get some help with either a better code workaround for something in the normals data being off or a new version (minor or patch) that solves the underlying data issue.

To try and help, here are a handful of intent links that will only work on Android devices, but will demonstrate the issues I’m seeing. I’ll also add the source code and attach the STLs, GLBs, and screenshots of Scene-Viewer after attempting to load the models.

Thanks in advance!

Intent links to launch Scene-Viewer (pointing at a public bucket with the attached files):

Single part stl without workaround

<a href="intent://arvr.google.com/scene-viewer/1.1?file=https://storage.googleapis.com/aspose-scene-view-ar-test/aspose-scene-view-ar-test/single.glb#Intent;scheme=https;package=com.google.ar.core;action=android.intent.action.VIEW;S.browser_fallback_url=https://developers.google.com/ar;end;">Single part stl without workaround</a>

Single part with workaround

<a href="intent://arvr.google.com/scene-viewer/1.1?file=https://storage.googleapis.com/aspose-scene-view-ar-test/single-with-normal-texture.glb#Intent;scheme=https;package=com.google.ar.core;action=android.intent.action.VIEW;S.browser_fallback_url=https://developers.google.com/ar;end;">Single part with workaround</a>

Assembly composite without workaround

<a href="intent://arvr.google.com/scene-viewer/1.1?file=https://storage.googleapis.com/aspose-scene-view-ar-test/assembly.glb#Intent;scheme=https;package=com.google.ar.core;action=android.intent.action.VIEW;S.browser_fallback_url=https://developers.google.com/ar;end;">Assembly composite without workaround</a>

Assembly composite with workaround

<a href="intent://arvr.google.com/scene-viewer/1.1?file=https://storage.googleapis.com/aspose-scene-view-ar-test/assembly-with-normal-texture.glb#Intent;scheme=https;package=com.google.ar.core;action=android.intent.action.VIEW;S.browser_fallback_url=https://developers.google.com/ar;end;">Assembly composite with workaround</a>

Source Code:

static public void Convert(FileInfo input, FileInfo output, string format)
{
  try
  {
    License license = new License();
    license.SetLicense("Aspose.3D.NET.lic");
    var outputScene = new Scene(input.FullName);
    var outputFormat = Aspose.ThreeD.FileFormat.GLTF2_Binary;

    var opt = new GltfSaveOptions(outputFormat);

    var entityNodes = outputScene.RootNode.ChildNodes;

    /** TRANSFORMATION **/
    var transformBuilder = new TransformBuilder();

    var scale = 0.01;
    // Scale down model -- default unit is 1 meter, and default scale is a centimeter conversion (.01)
    transformBuilder.Scale(scale, scale, scale);

    // Rotate to domain-preferred angle
    transformBuilder.RotateDegree(-90, new Vector3(1, 0, 0));
    transformBuilder.RotateDegree(270, new Vector3(0, 0, 1));

    // Realign to center
    var modelBB = entityNodes[0].GetBoundingBox();
    transformBuilder.Translate(modelBB.Center.x * -1, modelBB.Center.y * -1, modelBB.Center.z * -1);

    foreach (var modelNode in entityNodes)
    {
      // Apply transformation
      modelNode.Transform.TransformMatrix = transformBuilder.Matrix;

      /** MATERIALS **/
      var defaultColor = System.Drawing.Color.FromArgb(214, 226, 238);
      var defaultMaterial = new PbrMaterial(defaultColor);
      defaultMaterial.MetallicFactor = 0.65;
      defaultMaterial.RoughnessFactor = 0.2;

      // Flip this to trigger workaround behavior
      var bugTicketWorkaroundFlag = false;

      // For Android Scene-View, we need to apply a normal texture before we recalculate the normals
      if (bugTicketWorkaroundFlag)
      {
        var defaultTexture = CreateTexture(defaultColor);
        defaultMaterial.SetTexture(Material.MapNormal, defaultTexture);
      }
      // Apply material
      modelNode.Material = defaultMaterial;

      /** FIX UP **/
      // If we are exporting for Android Scene-View (i.e. open via intent link), then rebuild the normals after the transform and material is applied
      // ARCore crashes when we try to load the normals generated during stl import, but this fixes it in the majority of cases at the cost of bad visual texture
      if (bugTicketWorkaroundFlag)
      {
        var modelMesh = modelNode.GetEntity<Mesh>();

        // Clear out old normals
        modelMesh.VertexElements.Clear();
        // // Recalculate normals now that the old ones are gone and all other modifications are applied
        VertexElementNormal rebuiltNormals = PolygonModifier.GenerateNormal(modelMesh);
        VertexElementUV rebuiltUV = PolygonModifier.GenerateUV(modelMesh, rebuiltNormals);
        // // Apply new normals and replace the mesh on the entity node
        modelMesh.VertexElements.Add(rebuiltNormals);
        modelMesh.VertexElements.Add(rebuiltUV);
      }
    }

    outputScene.Save(output.FullName, opt);

  }
  catch (Exception ex)
  {
    Console.WriteLine($"[ERROR] In ARExportServiceCLI.Convert: {ex}");
  }
}

Aspose Scene-Viewer Ticket Files.zip (5.1 MB)

assembly-scene-viewer.jpg (138.5 KB)
assembly-with-normal-texture-scene-viewer.jpg (145.2 KB)
single-scene-viewer.jpg (89.4 KB)
single-with-normal-texture-scene-viewer.jpg (162.5 KB)

@gleichsnerd

Can you please try using the below hotfix and let us know in case you still notice the similar issues?

Aspose.3D-21.5.2.zip (6.7 MB)