Hi Jeremy, thanks for your suggestion! I was searching for two days straight, but never found this topic you linked. Thats a shame since it is exactly what I needed.
As for my solution (which possibly will be thrown away at this point) - I will share code and explanation.
My initial idea is to place electric sockets according to equipment that is already present in project.
So for example we have one piece of equipment, that will always be placed close to the wall (let's say some table).
For start I use ReferenceIntersector to find closest wall behind that table. Then I use LinePlaneIntersection code (thanks again, Jeremy!) with Element.FacingOrientation to find projection point on the wall behind that table (projection from the center of Element).
Now it's time to place socket. By now we should have input csv which contains data for Symbol name (socket), room, Element name (that table), height of socket and position of socket (distance from center of table). For each new socket there will be different input data.
Cycling through project equipment we compare each Element name to be same as in the database, as well as it's room. In our company we have strongly typed projects, with exact same equipment pieces and positions, so simple cycling and compare will work.
If match is found we use NewFamilyInstance to create socket. After that I get it's BoundingBox center (since LocationPoint of host-based family seems to always be in the center of host), and do simple comparison:
if BBox center point is farther than projected point (from initial Element locationPoint, aka table) - it means that family is placed on the wrong side, so I just mirror it, and delete the original. Using MirrorElements method returns mirrored instance, that is then used to change parameters (height and position).
The whole idea might look sloppy, but since I create all families and maintain project template - I have full control over families origin points, etc., so its safe to use this strategy.
public Result Execute(
ExternalCommandData commandData,
ref string message,
ElementSet elements)
{
UIApplication uiapp = commandData.Application;
UIDocument uidoc = uiapp.ActiveUIDocument;
Document doc = uidoc.Document;
try
{
var view = doc.ActiveView;
Selection selection = uidoc.Selection;
// Pick equipment, temoprary for tests
var pickedElementReference = selection.PickObject(
ObjectType.Element, new ElementPickFilter(),
"Please select");
if (null == pickedElementReference)
{
TaskDialog.Show("Failed", "Selection Failed");
return Result.Failed;
}
FamilyInstance ElementFamilyInstance
= doc.GetElement(pickedElementReference)
as FamilyInstance;
if (ElementFamilyInstance == null)
{
return Result.Failed;
}
// Getting location point and other data from instance
LocationPoint location1 = ElementFamilyInstance.Location as LocationPoint; //name for tests
XYZ ElementLocation = location1.Point;
var view3d = Get3dView(doc);
var orient = ElementFamilyInstance.FacingOrientation;
// Finding nearest wall
ReferenceIntersector refIntersector = new ReferenceIntersector(
new ElementCategoryFilter(BuiltInCategory.OST_Walls),
FindReferenceTarget.Element,
view3d
);
var x = refIntersector.FindNearest(location1.Point, orient);
var y = x.GetReference();
var elementWall = doc.GetElement(y);
Wall nearestWall = elementWall as Wall;
List<ElementId> lst = new List<ElementId>();
lst.Add(elementWall.Id);
//Getting wall faces and creating projection line
IList<Reference> sideFacesInterior = HostObjectUtils.GetSideFaces(nearestWall, ShellLayerType.Interior);
IList<Reference> sideFacesExterior = HostObjectUtils.GetSideFaces(nearestWall, ShellLayerType.Exterior);
double lineParameter = 0;
XYZ newPoint = ElementLocation + orient;
newPoint = new XYZ(newPoint.X,newPoint.Y,newPoint.Z);
var line1 = Line.CreateBound(ElementLocation, newPoint);
Face faceInterior = uidoc.Document.GetElement(sideFacesInterior[0]).GetGeometryObjectFromReference(sideFacesInterior[0]) as Face;
PlanarFace planeInterior = faceInterior as PlanarFace;
var pointInterior = LinePlaneIntersection(line1, planeInterior, out lineParameter);
Face faceExterior = uidoc.Document.GetElement(sideFacesExterior[0]).GetGeometryObjectFromReference(sideFacesExterior[0]) as Face;
PlanarFace planeExterior = faceExterior as PlanarFace;
var pointExterior = LinePlaneIntersection(line1, planeExterior, out lineParameter);
XYZ minimalPoint = new XYZ();
Reference placingRef = null;
// Looking for closest projected point
if (ElementLocation.DistanceTo(pointInterior) < ElementLocation.DistanceTo(pointExterior))
{
minimalPoint = pointInterior;
placingRef = sideFacesInterior[0]; // No need
}
else
{
minimalPoint = pointExterior;
placingRef = sideFacesExterior[0]; // No need
}
FamilySymbol famSym = GetSymbol(doc, "Double Socket 2.0", "Double Socket IP54");
//Getting mirror plane from wall
LocationCurve lc = nearestWall.Location as LocationCurve;
XYZ EndP1 = lc.Curve.GetEndPoint(0);
XYZ EndP2 = lc.Curve.GetEndPoint(1);
double h1 = nearestWall.LookupParameter("Height").AsDouble();
XYZ EndP3 = new XYZ(EndP1.X, EndP1.Y, EndP1.Z + h1);
Plane plane = Plane.CreateByThreePoints(EndP1, EndP2, EndP3);
FamilyInstance instance;
using (var tr = new Transaction(doc, "Place instance"))
{
tr.Start();
if (!famSym.IsActive)
{ famSym.Activate(); doc.Regenerate(); }
instance = doc.Create.NewFamilyInstance(minimalPoint, famSym, new XYZ(0, 0, 0), nearestWall, Autodesk.Revit.DB.Structure.StructuralType.NonStructural);
tr.Commit();
}
// Probably there is no need to make separate transaction since I don't use LocationPoint of socket
LocationPoint socketLocation = instance.Location as LocationPoint;
XYZ socketPoint = socketLocation.Point; //can't use this since LocationPoint is always in the center of the host wall
BoundingBoxXYZ elementBBox = instance.get_BoundingBox(view);
XYZ globalMax = elementBBox.Max;
XYZ globalMin = elementBBox.Min;
Transform viewTransform = view.CropBox.Transform;
XYZ max = viewTransform.Inverse.OfPoint(globalMax);
XYZ min = viewTransform.Inverse.OfPoint(globalMin);
XYZ UpRight = new XYZ(GetMax(min.X, max.X), GetMax(max.Y, min.Y), 0);
XYZ DownLeft = new XYZ(GetMin(min.X, max.X), GetMin(max.Y, min.Y), 0);
var Center = (UpRight + DownLeft) / 2;
List<ElementId> mirrorElements = new List<ElementId>
{
instance.Id
};
using (var tr1 = new Transaction(doc, "transaction"))
{
tr1.Start();
if (ElementLocation.DistanceTo(minimalPoint) < ElementLocation.DistanceTo(Center)) //Can use Comparer here
{
var mirroredSocketIds = ElementTransformUtils.MirrorElements(doc, mirrorElements, plane, true);
mirrorElements.Clear();
doc.Delete(instance.Id);
ElementId newSocketId = mirroredSocketIds[0];
Element newSocket = doc.GetElement(newSocketId);
}
tr1.Commit();
}
}
catch (OperationCanceledException)
{
return Result.Cancelled;
}
catch (Exception ex)
{
message = ex.Message;
TaskDialog.Show("Failed", message);
return Result.Failed;
}
return Result.Succeeded;
}