Arc along lines

Arc along lines

revitaddins
Contributor Contributor
472 Views
5 Replies
Message 1 of 6

Arc along lines

revitaddins
Contributor
Contributor

Hi All,

 

I'm struggling with creating arcs along curves (3D). The goal is to create an array of arcs programmatically based on 3D Curves. Anyone got an idea on how to achieve this? please see below code (not working as expected) but for reference of what I'm trying to do. see attached file as well. Thanks.

 

        public Result Execute(ExternalCommandData revit, ref string message, ElementSet elements)
        {
            // Revit application data
            UIApplication uiapp = revit.Application;
            UIDocument uidoc = uiapp.ActiveUIDocument;
            Application app = uiapp.Application;
            Document doc = uidoc.Document;

            uiapplication = uiapp;
            uidocument = uidoc;
            document = doc;
            revitApplication = app;
            externalCommand = revit;

            // Command name to equal execute class name
            string commandName = "CreateSurfaceFromMassingAndContours";

            // Call the command to execute within a transaction to safely dispose objects in memory
            using (TransactionGroup t = new TransactionGroup(doc, commandName))
            {
                // Start the transaction
                t.Start();

                // Pick massing element
                Reference massingRef = uidoc.Selection.PickObject(ObjectType.Element, new MassingElementSelectionFilter(), "Select a massing element");
                Element massingElement = doc.GetElement(massingRef);

                // Extract line geometry from the massing element
                Curve massLine = ExtractLineFromMassing(massingElement);

                if (massLine == null)
                {
                    TaskDialog.Show("Error", "No valid line found in the selected massing element.");
                    return Result.Failed;
                }

                // Pick contour lines
                ICollection<ElementId> contourIds = PickContourLines(uidoc);

                if (contourIds.Count == 0)
                {
                    TaskDialog.Show("Error", "No contour lines selected. Please select contour lines.");
                    return Result.Failed;
                }

                // Create the base CurveLoop from the selected contour curves
                CurveLoop baseCurveLoop = CreateCurveLoopFromContours(doc, contourIds);

                if (baseCurveLoop == null || baseCurveLoop.IsOpen())
                {
                    TaskDialog.Show("Error", "Failed to create a valid closed CurveLoop from the selected contours.");
                    return Result.Failed;
                }

                // Extrude the base CurveLoop to create a solid
                Solid baseSolid = ExtrudeBaseContour(doc, baseCurveLoop);

                if (baseSolid == null)
                {
                    TaskDialog.Show("Error", "Failed to create base solid from the base CurveLoop.");
                    return Result.Failed;
                }

                List<XYZ> intersectionPoints = IntersectPerpendicularLinesWithBaseSolidAndMassCurves(doc, massLine, baseSolid, contourIds);

                List<XYZ> newintersectionPoints = ProjectPointsToBaseCurveLoop(intersectionPoints, baseCurveLoop); // START AND ENDPOINT OF THE ARC

                List<XYZ> massCurveIntersections = PointsOnMassLine(doc, massLine); // arc MIDPOINT

                if (newintersectionPoints.Count < 3)
                {
                    TaskDialog.Show("Error", "Not enough intersection points to create a topography surface.");
                    return Result.Failed;
                }

                CreateArcsFromIntersectionPoints(doc, newintersectionPoints, massCurveIntersections);

                // End the transaction group
                t.Assimilate();
            }

            return Result.Succeeded;
        }

        private static ICollection<ElementId> PickContourLines(UIDocument uidoc)
        {
            Selection sel = uidoc.Selection;
            IList<Reference> pickedRefs = sel.PickObjects(ObjectType.Element, new ContourLineSelectionFilter(), "Select contour lines");
            List<ElementId> selectedIds = pickedRefs.Select(r => r.ElementId).ToList();
            return selectedIds;
        }

        private Curve ExtractLineFromMassing(Element massingElement)
        {
            GeometryElement geometryElement = massingElement.get_Geometry(new Options());

            foreach (GeometryObject geomObj in geometryElement)
            {
                if (geomObj is GeometryInstance instance)
                {
                    GeometryElement instanceGeometry = instance.GetInstanceGeometry();
                    foreach (GeometryObject instGeomObj in instanceGeometry)
                    {
                        if (instGeomObj is Curve curve)
                        {
                            return curve;
                        }
                    }
                }
                else if (geomObj is Curve curve)
                {
                    return curve;
                }
            }

            return null;
        }

        private CurveLoop CreateCurveLoopFromContours(Document doc, ICollection<ElementId> contourIds)
        {
            CurveLoop curveLoop = new CurveLoop();

            foreach (ElementId contourId in contourIds)
            {
                Element contourElement = doc.GetElement(contourId);
                Curve contourCurve = (contourElement.Location as LocationCurve).Curve;

                // Check if the curve is already bound, if not, create a bound curve
                if (!contourCurve.IsBound)
                {
                    XYZ startPoint = contourCurve.GetEndPoint(0);
                    XYZ endPoint = contourCurve.GetEndPoint(1);
                    Line boundLine = Line.CreateBound(startPoint, endPoint);
                    curveLoop.Append(boundLine);
                }
                else
                {
                    curveLoop.Append(contourCurve);
                }
            }

            return curveLoop;
        }

        private Solid ExtrudeBaseContour(Document doc, CurveLoop baseCurveLoop)
        {
            double extrusionHeight = 100.0; // Adjust the height of the extrusion as needed

            Solid baseSolid = GeometryCreationUtilities.CreateExtrusionGeometry(new List<CurveLoop> { baseCurveLoop }, XYZ.BasisZ, extrusionHeight);

            // Create a DirectShape to visualize the solid
            using (Transaction t = new Transaction(doc, "Create Base Solid"))
            {
                t.Start();
                DirectShape directShape = DirectShape.CreateElement(doc, new ElementId(BuiltInCategory.OST_GenericModel));
                directShape.SetShape(new GeometryObject[] { baseSolid });
                t.Commit();
            }

            return baseSolid;
        }

        private List<XYZ> IntersectPerpendicularLinesWithBaseSolidAndMassCurves(Document doc, Curve massLine, Solid baseSolid, ICollection<ElementId> contourIds)
        {
            List<XYZ> intersectionPoints = new List<XYZ>();

            // Define spacing or number of perpendicular lines
            double spacing = 1; // Adjust the spacing as needed for better coverage
            int numPoints = (int)(massLine.Length / spacing);

            for (int i = 0; i <= numPoints; i++)
            {
                double parameter = (double)i / numPoints;
                XYZ pointOnMassLine = massLine.Evaluate(parameter, true);

                // Create a line perpendicular to the mass line
                XYZ tangent = massLine.ComputeDerivatives(parameter, true).BasisX.Normalize();
                XYZ perpendicularDirection = tangent.CrossProduct(XYZ.BasisZ).Normalize();
                Line perpendicularLine = Line.CreateBound(pointOnMassLine - perpendicularDirection * 100.0, pointOnMassLine + perpendicularDirection * 100.0);

                // Intersect the perpendicular line with the base solid
                intersectionPoints.AddRange(IntersectLineWithSolid(perpendicularLine, baseSolid));
            }

            if (intersectionPoints.Count < 3)
            {
                TaskDialog.Show("Error", "No intersection points found.");
            }

            return intersectionPoints;
        }

        private List<XYZ> PointsOnMassLine(Document doc, Curve massLine)
        {
            List<XYZ> intersectionPoints = new List<XYZ>();

            // Define spacing or number of perpendicular lines
            double spacing = 1; // Adjust the spacing as needed for better coverage
            int numPoints = (int)(massLine.Length / spacing);

            for (int i = 0; i <= numPoints; i++)
            {
                double parameter = (double)i / numPoints;
                XYZ pointOnMassLine = massLine.Evaluate(parameter, true);

                // Create a line perpendicular to the mass line
                XYZ tangent = massLine.ComputeDerivatives(parameter, true).BasisX.Normalize();

                intersectionPoints.Add(pointOnMassLine);
            }

            if (intersectionPoints.Count < 3)
            {
                TaskDialog.Show("Error", "No intersection points found.");
            }

            return intersectionPoints;
        }

        private List<XYZ> ProjectPointsToBaseCurveLoop(List<XYZ> points, CurveLoop baseCurveLoop)
        {
            List<XYZ> projectedPoints = new List<XYZ>();

            foreach (XYZ point in points)
            {
                XYZ nearestPoint = FindNearestPointOnCurveLoop(point, baseCurveLoop);
                XYZ projectedPoint = new XYZ(point.X, point.Y, nearestPoint.Z);
                projectedPoints.Add(projectedPoint);
            }

            return projectedPoints;
        }

        private XYZ FindNearestPointOnCurveLoop(XYZ point, CurveLoop curveLoop)
        {
            XYZ nearestPoint = null;
            double minDistance = double.MaxValue;

            foreach (Curve curve in curveLoop)
            {
                XYZ projectedPoint = curve.Project(point).XYZPoint;
                double distance = point.DistanceTo(projectedPoint);

                if (distance < minDistance)
                {
                    minDistance = distance;
                    nearestPoint = projectedPoint;
                }
            }

            return nearestPoint;
        }

        private void CreateArcsFromIntersectionPoints(Document doc, List<XYZ> intersectionPoints, List<XYZ> massCurveIntersections)
        {
            using (Transaction t = new Transaction(doc, "Create Arcs"))
            {
                t.Start();

                // Assuming intersections and mass curve points are paired correctly
                for (int i = 0; i < massCurveIntersections.Count; i++)
                {
                    XYZ midPoint = massCurveIntersections[i];
                    if (i < intersectionPoints.Count - 1)
                    {
                        XYZ startPoint = intersectionPoints[i];
                        XYZ endPoint = intersectionPoints[i + 1];

                        if (!startPoint.IsAlmostEqualTo(endPoint))
                        {
                            Arc arc = Arc.Create(startPoint, endPoint, midPoint);
                            Plane plane = Plane.CreateByNormalAndOrigin(XYZ.BasisZ, midPoint);
                            SketchPlane sketchPlane = SketchPlane.Create(doc, plane);
                            doc.Create.NewModelCurve(arc, sketchPlane);
                        }
                    }
                }

                t.Commit();
            }
        }

        private List<XYZ> IntersectLineWithSolid(Line line, Solid solid)
        {
            List<XYZ> intersectionPoints = new List<XYZ>();

            foreach (Face face in solid.Faces)
            {
                IntersectionResultArray results;
                SetComparisonResult result = face.Intersect(line, out results);

                if (result == SetComparisonResult.Overlap)
                {
                    foreach (IntersectionResult intersect in results)
                    {
                        intersectionPoints.Add(intersect.XYZPoint);
                    }
                }
            }

            return intersectionPoints;
        }

        private class MassingElementSelectionFilter : ISelectionFilter
        {
            public bool AllowElement(Element elem)
            {
                return elem.Category.Id.IntegerValue == (int)BuiltInCategory.OST_Mass;
            }

            public bool AllowReference(Reference reference, XYZ position)
            {
                return false;
            }
        }

        private class ContourLineSelectionFilter : ISelectionFilter
        {
            public bool AllowElement(Element elem)
            {
                return elem.Category.Id.IntegerValue == (int)BuiltInCategory.OST_Lines;
            }

            public bool AllowReference(Reference reference, XYZ position)
            {
                return false;
            }
        }
    }
}

 

0 Likes
473 Views
5 Replies
Replies (5)
Message 2 of 6

Moustafa_K
Advisor
Advisor

I am a bit confused with your question. you want to create a 2d arc following a 3d curve!!!... does it mean you want to draw the projection of 3d arc on a Plan ?   what i can see from your code, that you want to project a curveloop over a mass element... may you please clarify more? 

Moustafa Khalil
Cropped-Sharp-Bim-500x125-Autodesk-1
0 Likes
Message 3 of 6

revitaddins
Contributor
Contributor

Sorry for the confusion, yes what I have is 3D curves (from Massing element) and a set of 2D curves as a base, i would like to create a series of arcs along 3D curves (section profile like) see below image, I would like to loft them eventually, but for now I'm stuck on arc creation. Thanks for your response

revitaddins_0-1716664507386.png

 

0 Likes
Message 4 of 6

Moustafa_K
Advisor
Advisor

ok now this is more clearer. just thinking loudly: Correct me if my understanding is wrong

 

Given:

1. 2d curves  " this is a curveloop, that reflects the base of the a 3d object, a mass for example"

2. you have a 3D curve passing through / over the base [Given 1]

 

Required:

Draw 3d Arcs connecting the base [Given 1] and passing through the 3d curve [Given 2]

 

Not sure the best possible solution: but lets see

1. create a plan from any 3 points from Given 1

2. Tesselate the 3d curve to points

3. project those points on the plan you just created

4. draw  line(s) connecting these projected points

 

** Now you have the outline [given 1] and the project of the 3D curve on the same plane level.

4. divide the base curve to number of points that you which your arcs start and end,

 

** now you have the start and end of each arc.

5. Draw a line connecting each start and its relative end point

6. get the intersection point between this line [step 5] and one of the lines [step 4]

7 project this intersection point on the 3D curve. [ this is your arc middle point]

 

Then you can model a Model line Arc from these 3 Points

 

thoughts?

 

Moustafa Khalil
Cropped-Sharp-Bim-500x125-Autodesk-1
0 Likes
Message 5 of 6

revitaddins
Contributor
Contributor

Yes, you're right. I'll give your suggestion a try. Thank you.

0 Likes
Message 6 of 6

jeremy_tammik
Alumni
Alumni

This is an interesting problem that has next to nothing to do with Revit or BIM in general.

  

Therefore, this forum is not the best place to research it, since we are focused on the Revit API. 

  

Furthermore, this is not a trivial problem, even when you limit yourself to the 2D case. 

  

Maybe it would help to first determine a suitable 2D plane in 3D space on which to define your arc, and then reduce the 3D case to 2D by projecting points onto that surface.

   

Here are two papers I found that deals with similar tasks in 2D:

   

  

Here is a 3D case:

  

    

Good luck finding more suitable algorithms yourself.

  

Please let us know how you end up solving this.

  

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open
0 Likes