vpenades / SharpGLTF

glTF reader and writer for .NET Standard
MIT License
470 stars 75 forks source link

Multiple objects exported as one object in wrong location #35

Closed cesarecaoduro closed 4 years ago

cesarecaoduro commented 4 years ago

I am trying to write a very simple exporter for Revit and this is the code. I can't attach a screenshot but basically the objects are exported properly in terms of shape but they are all in wrong locations. I would like to export each object as a separate object and the coordinate system should be the World Coordinate System with Origin in 0,0,0 and with Z Axis point to the top.

Any help will be much appreciated.

#region Namespaces
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Numerics;
using System.Threading;
using System.Threading.Tasks;
using Autodesk.Revit.ApplicationServices;
using Autodesk.Revit.Attributes;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using Autodesk.Revit.UI.Selection;
using SharpGLTF.Geometry;
using SharpGLTF.Geometry.VertexTypes;
using SharpGLTF.Materials;
using SharpGLTF.Schema2;
#endregion

namespace GLTFManager
{
    using VERTEX = SharpGLTF.Geometry.VertexTypes.VertexPosition;

    [Transaction(TransactionMode.Manual)]
    public class Command : IExternalCommand
    {
        public Result Execute(
          ExternalCommandData commandData,
          ref string message,
          ElementSet elements)
        {
            UIApplication uiapp = commandData.Application;
            UIDocument uidoc = uiapp.ActiveUIDocument;
            Application app = uiapp.Application;
            Document doc = uidoc.Document;

            // Access current selection

            IList<Reference> sel = uidoc.Selection.PickObjects(ObjectType.Element);
            Options opt = new Options()
            {
                ComputeReferences = true,
                DetailLevel = ViewDetailLevel.Fine,
                IncludeNonVisibleObjects = false
            };

            var scene = new SharpGLTF.Scenes.SceneBuilder("Default");
            var material = new MaterialBuilder("Default")
                .WithDoubleSide(true)
                .WithChannelParam(KnownChannel.BaseColor, new System.Numerics.Vector4((float)0.5, (float)0.5, (float)0.5, 1));

            var model = ModelRoot.CreateModel();

            int count = 0;
            int countEl = 0;
            Stopwatch sp = new Stopwatch();
            sp.Start();

            foreach (Reference reference in sel)
            {

                Element el = doc.GetElement(reference);
                try {
                    var mesh = new MeshBuilder<VERTEX>(string.Format("msh_{0}_{1}", el.Name, countEl + 1));
                    var prim = mesh.UsePrimitive(material);
                    GeometryElement ge = el.get_Geometry(opt);
                    count += GetGeometryObjects(ge, ref prim);
                    scene.AddRigidMesh(mesh, Matrix4x4.Identity);
                    countEl++;
                } 
                catch { continue;}
            }
            sp.Stop();

            model = scene.ToGltf2();
            model.SaveAsWavefront("mesh.obj");
            model.SaveGLTF("mesh.gltf");

            TaskDialog.Show("Number of triangles ", string.Format("{0} triangles for {1} elements exported in {2}ms",count.ToString(), countEl, sp.ElapsedMilliseconds));
            return Result.Succeeded;
        }

        private int GetGeometryObjects(GeometryElement geomElem, ref PrimitiveBuilder<MaterialBuilder, VERTEX, VertexEmpty, VertexEmpty> prim)
        {
            int count = 0;
            foreach (GeometryObject geomObj in geomElem)
            {
                Curve curve = geomObj as Autodesk.Revit.DB.Curve;
                if (null != curve) continue;
                Solid solid = geomObj as Solid;
                if (null != solid){
                    count += GetFacesArray(solid, ref prim);
                    continue;
                }
                //If this GeometryObject is Instance, call AddCurvesAndSolids
                GeometryInstance geomInst = geomObj as GeometryInstance;
                if (null != geomInst){
                    count += GetGeometryObjects(geomInst.GetSymbolGeometry(), ref prim);
                }
            }
            return count;
        }

        private int GetFacesArray(Solid solid, ref PrimitiveBuilder<MaterialBuilder, VERTEX, VertexEmpty, VertexEmpty> prim, double lod = 1)
        {
            int count = 0;
            FaceArray faces = solid.Faces;
            foreach (Face face in faces)
            {
                Autodesk.Revit.DB.Mesh m = face.Triangulate(lod);
                int numTriangles = m.NumTriangles;
                for (int i=0; i<numTriangles; i++)
                {
                    MeshTriangle triangle = m.get_Triangle(i);
                    XYZ v1 = triangle.get_Vertex(0); 
                    XYZ v2 = triangle.get_Vertex(1);
                    XYZ v3 = triangle.get_Vertex(2);
                    prim.AddTriangle(
                        new VERTEX((float)v1.X, (float)v1.Y, (float)v1.Z),
                        new VERTEX((float)v2.X, (float)v2.Y, (float)v2.Z),
                        new VERTEX((float)v3.X, (float)v3.Y, (float)v3.Z)
                        );
                    count++;
                }
            }

            return count;
        }
    }
}
vpenades commented 4 years ago

Hi, you have two ways of doing it... if you notice in yout code there's this line:

scene.AddRigidMesh(mesh, Matrix4x4.Identity);

When you add a mesh to the scene, you can specify a transform matrix that will set the origin of the mesh within the scene, so you can do things like this:

scene.AddRigidMesh(mesh, Matrix4x4.CreateTranslation(100,43,50) );

You can see an example here

Additionally, if you want to store a hierarchy graph you can also create an armature like this:

var armature1 = new NodeBuilder("Skeleton1");

var child0 = armature1
    .CreateNode("Joint 0")
    .WithLocalTranslation(new Vector3(0, 1, 0));

var child1 = armature1
    .CreateNode("Joint 1")
    .WithLocalTranslation(new Vector3(10,5, 0));

var scene = new SceneBuilder();
scene.AddRigidMesh(cube, child0 );
scene.AddRigidMesh(sphere, child1 );

Notice that both the Matrix4x4 and NodeBuilder objects support setting translation, rotation and scaling.

Additional notes about your code:

In the Material builder you're using .WithDoubleSide(true) , this only tells that the triangles should be rendered from both sides, and it is not really neccesary if you're creating solid meshes.

cesarecaoduro commented 4 years ago

Thanks I have managed to export it in the right location using the CreateWorld method. I need to test the graph structure but I understand now how to use it. Is it possible to nest the tree? Something like: var child11 = child1.CreateNode('Joint 1-1')

I will also remove the .withDoubleSide(true) thanks for the suggestions.

vpenades commented 4 years ago

yes, in order to build a tree, you use the NodeBuilder class.

You create the root node with new NodeBuilder("root name"); and then, you can use the .CreateNode("child name") recursively to create the nodes of the tree.

Once you hace created the tree, you can pass any node to the AddRigidMesh(mesh, node); instead of a matrix.

cesarecaoduro commented 4 years ago

Awesome...the code is working well and it is really fast as well.

Maxxxel commented 2 years ago

https://gist.github.com/Maxxxel/2908309eee583131d88d152ba8416d0f

How i solved it.