By Augusto Goncalves
We’re always trying to automate tasks, it’s actually a common request when the adoption of tools increase and the amount of work follow it. As we know, this is a great area for API: the machine is amazing doing simple math and repetitive tasks.
This time the discussion was based on daily challenges at MHA, a Brazilian engineering firm localized in São Paulo. They are/were involved in several important projects, especially in MEP discipline, including the business center where Autodesk is located here in São Paulo.
Image source: Wikipedia
One task currently consuming a long time is around placing light families on a Revit project. Of course the whole process cannot be automated as is based on the engineer’s analysis and experience, but the initial step of loading and placing the required number can be speed up.
Here are the steps we identifies:
- Select a family (.rfa) file that has a Light fixture. (in this code, the category of the family is not checked)
- Choose one symbol on the family just loaded
- Ask user to select reference plane, space and a pick a box on the project. These are used to calculate the number and location of the families.
- Do some basic math (but yet not so simple logic) to find where to place the instances
- Finally, in a loop, place all family instances.
This is not intended to replace the engineer, of course not. But by placing all the instance needed, the engineer can now move and adjust it, knowing the lumens requirement is meet (number of lights/lumens per room/space).
Let’s see the code, please follow the comments.
UIApplication uiapp = commandData.Application;
UIDocument uidoc = uiapp.ActiveUIDocument;
Application app = uiapp.Application;
Document doc = uidoc.Document;
// ask user to select a family file
System.Windows.Forms.OpenFileDialog fileDlg =
new System.Windows.Forms.OpenFileDialog();
fileDlg.Filter = "Revit Family (*.rfa)|*.rfa";
fileDlg.Multiselect = false;
if (fileDlg.ShowDialog()
!= System.Windows.Forms.DialogResult.OK)
return Result.Cancelled;
// load the selected family
Family fam;
if (!doc.LoadFamily(fileDlg.FileName, out fam))
return Result.Failed;
// select the symbol on the loaded family
// this is a simple WindowsForm with a
// ComboBox and a Button (action OK)
FormSelSymbol frm = new FormSelSymbol();
foreach (ElementId symbolId in fam.GetFamilySymbolIds())
{
FamilySymbol s = doc.GetElement(symbolId) as FamilySymbol;
frm.cboSymbols.Items.Add(s);
}
// as the FamilySymbol is stored on the combo box
// let's make it show the Name property
frm.cboSymbols.DisplayMember = "Name";
if (frm.ShowDialog()
!= System.Windows.Forms.DialogResult.OK)
return Result.Cancelled;
// and get the selected item
FamilySymbol symbol =
frm.cboSymbols.SelectedItem as FamilySymbol;
// for this case, we'll first select the reference plane
ElementId refPlaneId;
ReferencePlane refPlane;
try
{
refPlaneId = uidoc.Selection.PickObject(ObjectType.Element,
new GenericFilter<ReferencePlane>(),
"Select reference plance").ElementId;
refPlane = doc.GetElement(refPlaneId) as ReferencePlane;
}
catch (Autodesk.Revit.Exceptions.OperationCanceledException)
{
return Result.Cancelled; // clean command exit
}
// space to get lumens data
Space space;
try
{
ElementId id = uidoc.Selection.PickObject(ObjectType.Element,
new GenericFilter<Space>(),
"Select space").ElementId;
space = doc.GetElement(id) as Space;
}
catch (Autodesk.Revit.Exceptions.OperationCanceledException)
{
return Result.Cancelled; // clean command exit
}
// ask user to select a pick box where the lights
// will be created (along the reference plane
PickedBox box;
try
{
box = uidoc.Selection.PickBox(PickBoxStyle.Crossing,
"Select pick box");
}
catch (Autodesk.Revit.Exceptions.OperationCanceledException)
{
return Result.Cancelled; // clean command exit
}
// now it's time for some math
// let's calculate the axis where
// the lights will be created
double h = box.Max.X - box.Min.X;
double w = box.Max.Y - box.Min.Y;
Line axis;
if (h > w)
axis = Line.CreateBound(
new XYZ(
box.Min.X,
(box.Max.Y - box.Min.Y) / 2 + box.Min.Y,
refPlane.BubbleEnd.Z),
new XYZ(
box.Max.X,
(box.Max.Y - box.Min.Y) / 2 + box.Min.Y,
refPlane.BubbleEnd.Z));
else
axis = Line.CreateBound(
new XYZ(
(box.Max.X - box.Min.X) / 2 + box.Min.X,
box.Min.Y,
refPlane.BubbleEnd.Z),
new XYZ(
(box.Max.X - box.Min.X) / 2 + box.Min.X,
box.Max.Y,
refPlane.BubbleEnd.Z));
// now calculate the number of required lights
// first the Luminous of each light (from the family symbol)
double lightLuminous = symbol.get_Parameter(
BuiltInParameter.FBX_LIGHT_LIMUNOUS_FLUX).AsDouble();
// and get the information from the Space
// assuming it was assigned as a Space Style param
double requiredLuminous = doc.GetElement(
space.get_Parameter("Space Style").AsElementId())
.get_Parameter("Luminous").AsDouble();
// now the number of lights
int numerOfLights = (int)Math.Round((requiredLimunous / lightLimunous));
// and the distance between them
double distanceBetweenLights = axis.ApproximateLength / (numerOfLights + 2);
// all set, time to add the lights!
// place family instances at the locations
for (int p = 1; p <= numerOfLights; p++)
{
// evaluate a point along the axis
// remember the Evaluate can be parametric, therefore
// normalized between 0 and 1
XYZ pointOnAxis = axis.Evaluate(
p * distanceBetweenLights / axis.ApproximateLength,
true);
doc.Create.NewFamilyInstance(
refPlane.Reference,
pointOnAxis,
new XYZ(0, 0, 0), symbol);
}
return Result.Succeeded;
And here is the GenericFilter class used on this code, a simple class that implements the filter interface
public class GenericFilter<T> : ISelectionFilter
{
public bool AllowElement(Element elem)
{
return (elem is T);
}
public bool AllowReference(Reference reference, XYZ position)
{
return true;
}
}