Fabrication Parts ElementIntersection

Fabrication Parts ElementIntersection

djohansing
Contributor Contributor
2,500 Views
16 Replies
Message 1 of 17

Fabrication Parts ElementIntersection

djohansing
Contributor
Contributor

When attempting to create an ElementIntersectsElementFilter with a fabrication part, it results in an exception where the FabricationParts category is not supported by this filter.

After looking into this, it seems that FabricationPart's geometry is made up of Mesh objects.

The BooleanOperationsUtils.ExecuteBooleanOperation method seems to only support solids.

What would be the best way to detect an intersection between a FabricationPart and another element, or an intersection between a Mesh and a Mesh / Solid?

I have tried creating a MeshToSolid function that uses the TesselatedShapeBuilder to convert the Mesh to a Solid, which would then allow me to use the boolean operations, but it rarely succeeds to create a solid from the FabricationPart mesh.

0 Likes
Accepted solutions (1)
2,501 Views
16 Replies
Replies (16)
Message 2 of 17

djohansing
Contributor
Contributor

@jeremytammik replied with:

 

Yes, I would also expect the BooleanOperationsUtils to only support solids.

 

I can also well imagine that it is difficult and often impossible to convert a mesh to a solid.

 

One thing that might be easy to achieve is to collect all the mesh vertices, determine their convex hull, and create a solid from that that can be used by the BooleanOperationsUtils for the element-to-solid intersection:

 

https://thebuildingcoder.typepad.com/blog/2009/06/convex-hull-and-volume-computation.html

 

Have you tried anything like that?

Or, one step simpler, and worthwhile trying out first, just intersect with the bounding box.

The latter has the additional advantage of being a quick element filter, whereas the solid intersection is slow.

I hope this helps.

0 Likes
Message 3 of 17

djohansing
Contributor
Contributor

@jeremytammik 

 

I have thought of using the convex hull of the vertices, but my concern is that protrusions on the Fabrication part would drastically change its geometry with this method. I am looking to get a more accurate intersection volume. 

 

I am already using the bounding box filter as a first pass before running the boolean operation.

0 Likes
Message 4 of 17

jeremytammik
Autodesk
Autodesk

Dear Doug,

 

Thank you for your query and for moving the issue out here into the public discussion forum for all to share and contribute.

 

If you have already used the bounding box intersection filter, I would assume that you have narrowed down the candidate BIM elements enough to retrieve them out of the Revit memory space into your own .NET add-in memory space for post-processing.

 

Now you would like to perform an intersection test with them.

 

For what purpose? Collision detection?

 

I can well imagine that the convex hull of the mesh points might be excessive and that protrusions on the fabrication part might drastically affect their convex hull geometry.

 

Many other options a can be imagined.

 

One efficient and minimal approach that should cover your needs is to calculate intersections with lines instead of solids.

 

Retrieve all the mesh edge curves as lines. If the edges are non-linear, tessellate them to retrieve lots of little line segments.

 

Search for intersections between any of the lines with your other candidate collision elements.

 

Line intersection is easy and cheap to calculate.

 

How does that sound?

 

Best regards,

 

Jeremy

 

P.S. Tracking history:

 

 



Jeremy Tammik
Developer Technical Services
Autodesk Developer Network, ADN Open
The Building Coder

0 Likes
Message 5 of 17

djohansing
Contributor
Contributor

Jeremy,

 

Yes, the purpose is for determining the exact clashing intersection volume, not just whether or not there is an intersection.

 

I think this line method could tell me whether or not a line from a mesh intersects with another solid, but it would not give me the total intersection volume. I am also not sure if this method would work when trying to intersect two meshes (two fabrication parts).

 

I have noticed that the reason I may not be able to create a solid with the TessellatedShapeBuilder is because the FabricationPart mesh just may not be a closed mesh. I am currently looking for a third party library to simply close the mesh so I can pass that into the shape builder.

0 Likes
Message 6 of 17

jeremytammik
Autodesk
Autodesk

Dear Doug,

 

Well that is a very strong requirement: if you need the clashing volume, you need to have a solid.

 

Yes, the only way to get a solid from a mesh that does not form a closed shape is to close it.

 

Yes, finding a suitable third-party solution to that task will same you a lot of non-trivial work.

 

Looking forward to hearing what suitable third-party libraries you can find and how you end up resolving this.

 

Thank you!

 

Best regards,

 

Jeremy

 



Jeremy Tammik
Developer Technical Services
Autodesk Developer Network, ADN Open
The Building Coder

0 Likes
Message 7 of 17

djohansing
Contributor
Contributor

I was able to use the geometry3Sharp open source geometry library to perform "MeshAutoRepair", which closes the mesh. Getting closer ... 

 

Now the TessellatedShapeBuilder is still giving me the following issues: 

 

NonManifoldEdgeSame edge is used by more than two faces. This issue can be posted for topologically impossible face sets as well as for face sets with duplicate faces. Associated numbers are face and loop in which the problem became obvious indices respectively.
OverlappingAdjacentFacesAdjacent faces which either exactly overlaps or have a too acute angle between them. This issue can be reported for face sets with duplicate faces as well. Associated numbers are face and loop indices of one face and face and loop indices of another face respectively.

 

My thought is that these issues have to do with the 0 thickness edges on the part (see below).

 

Here is the progress so far:

 

FabricationPart on Left, Closed mesh (DirectShape) on Right

 

Annotation 2020-03-25 145854.png

 

0 Likes
Message 8 of 17

djohansing
Contributor
Contributor

@jeremytammik, or others, still no success creating a solid from this geometry. Any other insight would be helpful.

0 Likes
Message 9 of 17

jeremytammik
Autodesk
Autodesk

Looking at the specific part you share above, I would suggest that a collision check with its convex hull would yield the desired result, wouldn't it?

 

Otherwise, I can only repeat from above: if you really need the exact clashing volume, you need to have a solid.

 

One way to create a solid from a mesh would be to take each mesh face (infinitely thin), thicken it by a realistic amount to create a solid (how thick is your material? 1 mm? 1.2 mm?) and unite all those solids into one single composite shape.

  

That would reflect the real part geometry very precisely, in fact.

 



Jeremy Tammik
Developer Technical Services
Autodesk Developer Network, ADN Open
The Building Coder

0 Likes
Message 10 of 17

djohansing
Contributor
Contributor

Jeremy,

 

For the specific shape I shared, a convex hull may be ok, but for other shapes it could be an issue.

 

Any suggestions on .NET libraries available that can thicken the mesh and help create a valid solid? 

0 Likes
Message 11 of 17

jeremytammik
Autodesk
Autodesk

Dear Doug,

 

Just from reading your previous messages in this thread, I was assuming that the geometry3Sharp library would enable that.

 

Otherwise, you could just create each thickening solid from scratch by grabbing the face edge vertices and duplicating them up and down in the face normal direction by half the material thickness, or, put differently, by extruding each mesh face a mm or two.

 



Jeremy Tammik
Developer Technical Services
Autodesk Developer Network, ADN Open
The Building Coder

0 Likes
Message 12 of 17

djohansing
Contributor
Contributor

Jeremy,

 

I looked at geometry3Sharp and didn't see an obvious way to do this. I may have to attempt the thickening myself as you say ... 

 

I am surprised the Revit API doesn't have a built in way to check for an intersection of a FabricationPart, those are the parts of the building model that are most useful for coordination.

 

0 Likes
Message 13 of 17

djohansing
Contributor
Contributor
Accepted solution

FYI: I was finally able to create a solution on my own using the geometry3Sharp libraries.

 

Here is some of the relevant code to obtain the mesh intersection volumes.

        public static (g3.DMesh3 resultMesh, double volume, double area) MeshIntersect(this g3.DMesh3 dMesh1, g3.DMesh3 dMesh2)
        {
            var ar1 = new gs.MeshAutoRepair(dMesh1);
            ar1.Apply();
            var ar2 = new gs.MeshAutoRepair(dMesh2);
            ar2.Apply();

            var meshMeshCut = new g3.MeshMeshCut() { Target = ar1.Mesh, CutMesh = ar2.Mesh };
            meshMeshCut.Compute();

            var h1 = meshMeshCut.Target.GetIntersectingTriangles(meshMeshCut.CutMesh);

            var meshMeshCut2 = new g3.MeshMeshCut() { Target = ar2.Mesh, CutMesh = ar1.Mesh };
            meshMeshCut2.Compute();

            var h2 = meshMeshCut2.Target.GetIntersectingTriangles(meshMeshCut2.CutMesh);

            var r1 = new gs.MeshAutoRepair(g3.MeshEditor.Combine(new g3.DMesh3[] { h1, h2 }));
            r1.Apply();

            var volArea = g3.MeshMeasurements.VolumeArea(r1.Mesh, r1.Mesh.TriangleIndices(), r1.Mesh.GetVertex);

#if DEBUG
            //r1.Mesh.GetRevitGeometry().CreateTestDirectShape();
#endif

            return (r1.Mesh, volArea.x, volArea.y);
        }

        private static g3.DMesh3 GetIntersectingTriangles(this g3.DMesh3 mesh, g3.DMesh3 cutMesh)
        {
            var hMesh = new g3.DMesh3(mesh);
            g3.DMeshAABBTree3 spatial2 = new g3.DMeshAABBTree3(cutMesh, true);
            spatial2.WindingNumber(g3.Vector3d.Zero);
            g3.SafeListBuilder<int> keepT1 = new g3.SafeListBuilder<int>();
            g3.gParallel.ForEach(hMesh.TriangleIndices(), (tid) => {
                g3.Vector3d v = hMesh.GetTriCentroid(tid);
                if (spatial2.WindingNumber(v) > 0.9)
                    keepT1.SafeAdd(tid);
            });
            var keepT2 = keepT1.Result.ToDictionary(i => i, i => true);
            g3.MeshEditor.RemoveTriangles(hMesh, hMesh.TriangleIndices().Where(tid => !keepT2.ContainsKey(tid)).ToList());
            return hMesh;
        }
0 Likes
Message 14 of 17

jeremytammik
Autodesk
Autodesk

Dear Doug,

 

Brilliant! Congratulations!

 

As far as I can tell from the two methods you share, you are intersecting mesh triangles and returning a mesh as a result.

 

That makes perfect sense.

 

Or is are you in fact determining a real intersection volume, as your text suggests?

 

Oh, I see, by automatically repairing the mesh, you may be able to create a complete closed shell with a well-defined volume. Great!

 

In any case, it sounds like a very useful result that enables you to solve the clash detection task.

 

Would it be possible to share a complete minimal reproducible case with the community in case anybody else runs into the need?

 

Thank you!

 

Cheers,

 

Jeremy

 



Jeremy Tammik
Developer Technical Services
Autodesk Developer Network, ADN Open
The Building Coder

0 Likes
Message 15 of 17

djohansing
Contributor
Contributor

Jeremy,

 

It is determining the volume and area using the geometry3Sharp library functions after repairing/closing, cutting, determining intersecting triangles, combining, then repairing/closing again.

 

This is part of a much larger solution, but I can share some additional interop functions used to transition between g3.DMesh3 and Revit API objects. With these, you can start with a Revit API solid or mesh and then call the MeshIntersect method.

 

        public static g3.DMesh3 GetDMesh3(this Mesh mesh)
        {
            g3.DMesh3Builder meshBuilder = new g3.DMesh3Builder();
            meshBuilder.Meshes.Add(new g3.DMesh3());
            meshBuilder.SetActiveMesh(0);
            meshBuilder.AppendMesh(mesh);
            return meshBuilder.Meshes.First();
        }

        public static g3.DMesh3 GetDMesh3(this Solid solid)
        {
            g3.DMesh3Builder meshBuilder = new g3.DMesh3Builder();
            meshBuilder.Meshes.Add(new g3.DMesh3());
            meshBuilder.SetActiveMesh(0);
            foreach(Face face in solid.Faces)
            {
                meshBuilder.AppendMesh(face.Triangulate());
            }
            return meshBuilder.Meshes.First();
        }

        public static void AppendMesh(this g3.DMesh3Builder meshBuilder, Mesh mesh)
        {
            for (int i = 0; i < mesh.NumTriangles; i++)
            {
                MeshTriangle mt = mesh.get_Triangle(i);
                int v0 = meshBuilder.AppendVertex(mt.get_Vertex(0).X, mt.get_Vertex(0).Y, mt.get_Vertex(0).Z);
                int v1 = meshBuilder.AppendVertex(mt.get_Vertex(1).X, mt.get_Vertex(1).Y, mt.get_Vertex(1).Z);
                int v2 = meshBuilder.AppendVertex(mt.get_Vertex(2).X, mt.get_Vertex(2).Y, mt.get_Vertex(2).Z);
                meshBuilder.AppendTriangle(v0, v1, v2);
            }
        }

        public static IList<GeometryObject> GetRevitGeometry(this g3.DMesh3 dMesh)
        {
            TessellatedShapeBuilder builder = new TessellatedShapeBuilder();
            GetFaces(dMesh, builder);
            builder.Build();
            var result = builder.GetBuildResult();
            return result.GetGeometricalObjects();
        }

        private static void GetFaces(g3.DMesh3 mesh, TessellatedShapeBuilder builder)
        {
            builder.OpenConnectedFaceSet(true);
            foreach (var triangle in mesh.Triangles())
            {
                var va = mesh.GetVertex(triangle.a);
                var vb = mesh.GetVertex(triangle.b);
                var vc = mesh.GetVertex(triangle.c);
                XYZ v0 = new XYZ(va.x, va.y, va.z);
                XYZ v1 = new XYZ(vb.x, vb.y, vb.z);
                XYZ v2 = new XYZ(vc.x, vc.y, vc.z);
                TessellatedFace tesseFace = new TessellatedFace(new List<XYZ> { v0, v1, v2 }, ElementId.InvalidElementId);
                if (builder.DoesFaceHaveEnoughLoopsAndVertices(tesseFace)) builder.AddFace(tesseFace);
            }
            builder.CloseConnectedFaceSet();
        }
Message 16 of 17

greg7KTAR
Contributor
Contributor

I'm not sure how to post a question and where, but I have a semi related question, possibly for Jeremy Tammik. I am trying to understand how to access the Autodesk Fabrication Parts Options Tab fields through the Revit API. Any help is appreciated.

0 Likes
Message 17 of 17

NonicaTeam
Enthusiast
Enthusiast

We came out with a simplified version if somebody is looking for a solution with no external library. We basically:

-Use the Mesh to get all points (Mesh.Vertices).

-Project all points into a plane normal to the direction of Prefabrication part with element.Location.Curve. 

-Once projected, we extract the 4 outer extremes XYZ.

-Create a line in each extreme with the vector of the prefabrication part (element.Location.Curve) and use PlanarFace.Intersect to find the intersection points between Prefabrication parts and Walls.

I hope it helps somebody:)