Place new Face-based instance

Place new Face-based instance

Anonymous
Not applicable
4,240 Views
4 Replies
Message 1 of 5

Place new Face-based instance

Anonymous
Not applicable

I'm having some strange problems creating Face-based instances. I want to chain up a line of face-based objects in such a way that one of them (object A) gets hosted on a beam, the next (B) gets hosted on a face of object A, object C gets hosted on a face of B, etc.

 

I am able to place the first object A that is hosted on the beam using

Document.Create.NewFamilyInstance(Face face, XYZ location, XYZ referenceDirection, FamilySymbol symbol)

 and that works fine. However, when I try to use this same method to place object B on A, the same NewFamilyInstance overload returns a "Reference direction is parallel to face normal at insertion point" exception. That is a pretty self-explanatory error, but what is odd is that if I use the debugger to check the values, they are nothing alike.

In a simplified example using a PlanarFace, I get face.Normal = (0,1,0) and referenceDirection = (1,0,0).

 

According to TheBuildingCoder, the referenceDirection should be tangent to the plane (and thus orthogonal to the normal at the location point) which in the above example is precisely what has happened. So I don't really understand what is going on.

 

And now I'm going to do a code-dump for whomever might be interested.

 

 Main Command

namespace Prestress
{
    #region Model class Attributes
    [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
    [Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
    [Autodesk.Revit.Attributes.Journaling(Autodesk.Revit.Attributes.JournalingMode.NoCommandData)]
    #endregion
    public class Model : IExternalCommand
    {
        public Result Execute(ExternalCommandData revit, ref string message, ElementSet elements)
        {
            Global.uiApp = revit.Application;

            //Load cable families
            Transaction t = new Transaction(Global.Doc, "CableLoad");
            t.Start();

            //Cable that's hosted on the beam
            Family cablefamily1 = Global.LoadFamily("CableSegment1", @"E:\Dropbox\PUC\Dissertacao\CableSegment1.rfa");
            //Cable that's hosted on the other cable segments
            Family cablefamily2 = Global.LoadFamily("CableSegment2", @"E:\Dropbox\PUC\Dissertacao\CableSegment2.rfa");
            t.Commit();

            //Catch a cable symbol
            FamilySymbol cable1, cable2;
            cable1 = Global.GetFamilySymbol(cablefamily1, 0);
            cable2 = Global.GetFamilySymbol(cablefamily2, 0);

            //Select the element to be prestressed
            FamilyInstance beam = Global.GetElement();

            //if null, user cancelled operation
            if (beam == null)
                return Result.Cancelled;

            //get the location curve of beam and its startface (intersected by the curve's startpoint)
            Curve loc;
            loc = (beam.Location as LocationCurve).Curve;
            Face face = Global.GetFaceContainingCurveEndpoint(beam, loc, 0);

            //find point on face to place cable
            XYZ pt = new XYZ();
            foreach (EdgeArray array in face.EdgeLoops)
            {
                foreach (Edge edge in array)
                {
                    pt = edge.AsCurve().get_EndPoint(0);
                    break;
                }
            }

            //place cable
            t.SetName("CablePlace");
            t.Start();
            FamilyInstance c1 = Global.Doc.Create.NewFamilyInstance(face, pt, XYZ.BasisZ, cable1);
            t.Commit();

            //Get cable-segment's properties.
            ParameterSet ps = c1.Parameters;
            double h = 0;
            double L = 0;
            Element host = c1.Host;
            foreach (Parameter p in ps)
            {
                if (p.Definition.Name == "h")
                    h = p.AsDouble();
                else if (p.Definition.Name == "L")
                    L = p.AsDouble();
            }

            //find beam's normalized direction vector
            loc = (beam.Location as LocationCurve).Curve;
            XYZ v = loc.ComputeDerivatives(0.0d, true).BasisX.Normalize();
            //get previous cable-segment's startpoint
            LocationPoint locP = c1.Location as LocationPoint;
            //calculate previous cable-segment's endpoint ( = startpoint + beam's direction vector*L - (0,0,h) )
            pt = locP.Point + v * L - new XYZ(0, 0, h);
            //create line representing previous cable-segment's LocationCurve
            loc = Global.Application.Create.NewLineBound(locP.Point, pt);
            //find previous cable-segment's end-face (intersected by end-point of location curve)
            face = Global.GetFaceContainingCurveEndpoint(c1, loc, 1);
            Line line = null;
            foreach(EdgeArray array in face.EdgeLoops)
            {
                foreach(Edge edge in array)
                {
                    Curve c = edge.AsCurve();
                    line = Global.Application.Create.NewLineBound(c.get_EndPoint(0),c.get_EndPoint(1));
                    break;
                }
                break;
            }
            t.Start();
            FamilyInstance c2 = Global.Doc.Create.NewFamilyInstance(face, pt, XYZ.BasisZ, cable2);
            t.Commit();
            return Result.Succeeded;
        }
    }
}

 

Global, my utility class:

namespace Prestress
{
    public static class Global
    {
        private static UIApplication _uiApp;
        public static double FeetToMeters = 0.3048;
        public static UIApplication uiApp
        {
            get { return _uiApp; }
            set
            {
                _uiApp = value;
                uiDoc = value.ActiveUIDocument;
                Doc = uiDoc.Document;
                Selection = uiDoc.Selection;
                Application = value.Application;
            }
        }
        public static Document Doc;
        public static UIDocument uiDoc;
        public static RvtApplication Application;
        public static Selection Selection;
        /// <summary>Loads a family. Must be contained within an active transaction.</summary>
        public static Family LoadFamily(string familyname, string dirpath)
        {
            Family family = null;

            FilteredElementCollector fc = new FilteredElementCollector(Global.Doc).OfClass(typeof(Family));
            foreach (Family f in fc)
            {
                if (f.Name == familyname)
                {
                    family = f;
                    break;
                }
            }

            if (null == family)
                Global.Doc.LoadFamily(dirpath, out family);
            return family;
        }
        /// <summary>Returns a single element selected by the user</summary>
        public static FamilyInstance GetElement(string prompt = "Select an element")
        {
            Reference r;
            try
            {
                r = Selection.PickObject(Autodesk.Revit.UI.Selection.ObjectType.Element, prompt);
            }
            catch
            {
                return null;
            }
            return Doc.GetElement(r) as FamilyInstance;
        }
        /// <summary>Finds face containing curve endpoint</summary>
        public static Face GetFaceContainingCurveEndpoint(FaceArray fa, Curve c, int index, double tol = 1e-9)
        {
            foreach (Face f in fa)
            {
                IntersectionResultArray ira = new IntersectionResultArray();
                if (f.Intersect(c, out ira) == SetComparisonResult.Overlap)
                {
                    foreach (IntersectionResult r in ira)
                    {
                        XYZ end = c.get_EndPoint(index);
                        if (r.XYZPoint.IsAlmostEqualTo(end, tol))
                            return f;
                    }
                }
            }
            return null;
        }
        /// <summary>Finds face containing curve endpoint</summary>
        public static Face GetFaceContainingCurveEndpoint(FamilyInstance g, Curve c, int index, double tol = 0.001d)
        {
            return GetFaceContainingCurveEndpoint(GetFaces(g), c, index, tol);
        }
        /// <summary>Returns all the faces in the element</summary>
        public static FaceArray GetFaces(GeometryElement g)
        {

            FaceArray fa = new FaceArray();
            foreach (GeometryObject o in g)
            {
                GeometryObject obj = o;
                if (obj is GeometryInstance)
                {
                    GeometryInstance i = obj as GeometryInstance;
                    obj = i.GetInstanceGeometry();
                    fa = GetFaces(obj as GeometryElement);
                    continue;
                }
                if (obj is Solid)
                {
                    Solid s = obj as Solid;
                    if (0 != s.Volume)
                    {
                        if (fa.IsEmpty)
                            fa = s.Faces;
                        else
                            foreach (Face f in s.Faces)
                                fa.Append(f);
                    }
                }
            }
            return fa;
        }
        /// <summary>Returns all the faces in the element</summary>
        public static FaceArray GetFaces(FamilyInstance g)
        {
            return GetFaces(GetGeometry(g));
        }
        /// <summary>Returns the element's geometrical representation</summary>
        public static GeometryElement GetGeometry(FamilyInstance e)
        {
            Options opt = uiApp.Application.Create.NewGeometryOptions();
            opt.ComputeReferences = true;
            opt.DetailLevel = ViewDetailLevel.Fine;
            return e.get_Geometry(opt);
        }
        /// <summary>Gets the index-th FamilySymbol in the Family object</summary>
        public static FamilySymbol GetFamilySymbol(Family family, int index)
        {
            FamilySymbol cable1;
            FamilySymbolSetIterator fssi = family.Symbols.ForwardIterator();
            for (int i = 0; i <= index; i++)
                fssi.MoveNext();
            cable1 = fssi.Current as FamilySymbol;
            return cable1;
        }
    }
}

 

In the case of a horizontal cable (with a direction vector equal to (0,1,0)), XYZ.BasisZ should be orthogonal and, well, certainly not parallel.

 

And should anyone want to see the face-based classes, they're attached. I need to have two classes:

- One class that goes "into" the face (cable1). This is used to bind the chain of elements to the beam.

- One class that goes "out" of the face (Cable2). This is used to create the rest of the chain popping out of the cable1 class.

0 Likes
4,241 Views
4 Replies
Replies (4)
Message 2 of 5

GeomGym
Advocate
Advocate

I get a similar error message when trying to insert a family hosted on a reference plane from the API, http://forums.autodesk.com/t5/Autodesk-Revit-API/NewFamilyInstance-on-a-reference-plane/td-p/3256394

 

I'm interested in the explanation for this for your scenario too.

0 Likes
Message 3 of 5

Anonymous
Not applicable

Try calling Document.Regenerate() after each call to NewLineBound.  I would call it before

 

face = Global.GetFaceContainingCurveEndpoint(c1, loc, 1);

 

I actually can't see any use for your foreach loop before the final call to NewFamilyInstance, since you're not making any reference to the line that you create in the loop.  If there's some use for the line that I'm missing, then you'll want to call Document.Regenerate() again before making any reference to it.

0 Likes
Message 4 of 5

Anonymous
Not applicable

Thanks for the reply, unfortunately I've already tried using Regenerate() immediately before the second call to NewFamilyInstance (and therefore right after NewLineBound()).

 

And the line created is certified to be orthogonal to the normal of the PlanarFace since it is composed of the two endpoints of one of the face's edges. This is for the generic case, where it would replace the XYZ.BasisZ I used for the trivial case of a Face.Normal = (0,1,0).

 

So, the more complete end of the function (in a more generic mode):

            //...
            face = Global.GetFaceContainingCurveEndpoint(c1, loc, 1);
            Line line = null;
            foreach(EdgeArray array in face.EdgeLoops)
            {
                foreach(Edge edge in array)
                {
                    Curve c = edge.AsCurve();
                    line = Global.Application.Create.NewLineBound(c.get_EndPoint(0),c.get_EndPoint(1));
                    break;
                }
                break;
            }
            t.SetName("Regenerate");
            t.Start();
            Global.Doc.Regenerate();
            t.Commit();
            t.SetName("Cable2Place");
            t.Start();
            FamilyInstance c2 = Global.Doc.Create.NewFamilyInstance(face, pt, line.Direction, cable2);
            t.Commit();
            return Result.Succeeded;

 

In this case the error is different. The NewFamilyInstance() call does not throw an exception but instead creates an element that is programatically linked but is physically separate and... well, look at the image.

4-29-2013 11-05-40 PM.jpg

The reason for this change is simply because line.Direction = (1,0,0) in this case. If I used XYZ.BasisX it gives the same result.

 

As well, double-checking the point of insertion by getting the midpoint of the Line created above, we get:

pt0 = {(-46.406682905, -25.392089600, 9.842519685)}

pt1 = {(-46.177024112, -25.392089600, 9.842519685)}

midpoint = {(-46.2918535085, -25.392089600, 9.842519685)}

 

The point calculated previously (pt) is equal to pt = {(-46.291853509, -25.392089600, 9.842519685)}, exact to the 9th digit (considering rounding).

 

So the point is not to blame for the blatant discrepancy in placement location of the segment (for comparison, the beam is 1,00m tall).

 

So, the insertion point is accurate, the direction is not parallel to the normal of the face (and different orthogonal directions give different results, with BasisX giving this oddly placed segment and BasisZ being given as paralel to (0,1,0)), and the element symbol is apparently valid since it CAN be created.

 

Any ideas?

0 Likes
Message 5 of 5

jeremytammik
Autodesk
Autodesk

Dear Pnacht,

 

Could the problem be due to the use of GetInstanceGeometry() instead of GetSymbolGeometry()?
 
GetSymbolGeometry() is needed to get references usable by other operations like Face-based family creation or dimension creation.
 
http://wikihelp.autodesk.com/Revit/enu/2014/Help/3665-Developers/0074-Revit_Ge74/0108-Geometry108/01...
 
From reference docs:
 
The geometry will be in the coordinate system of the model that owns this instance. The context of the instance object (such as effective material) will be applied to the symbol. Note that this method involves extensive parsing or Revit's data structures, so try to minimize calls if performance is critical. Geometry will be parsed with the same options as those used when this object was retrieved. This method returns a copy of the Revit geometry. It is suitable for use in a tool which extracts geometry to another format or carries out a geometric analysis; however, because it returns a copy the references found in the geometry objects contained in this element are not suitable for creating new Revit elements referencing the original element (for example, dimensioning). Only the geometry returned by GetSymbolGeometry() with no transform can be used for that purpose.

Best regards,

 

Jeremy



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

0 Likes