Place new Face-based instance
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report
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.