Based on the logic that a Type is used if it has instances then we should iterate the instances, link to the type and extract the type parameters. The main problem you have is deciding if a parameter is on the type or the instance. Named values could be on both so you either need some background information about where the parameters exist or you use the same list of names to search both the instance and the type.
Incidentally the Element.LookupParameter method should only be used if you know the name is unique within the element. Best practice is to use either the BuiltInParameter enum value or the SharedParameter GUID.
The below is a bit overly complicated as I was experimenting. Also remembered that probably the best way to get a list of elements with solid geometry is to create a 3D view and use the ViewID of that within the filtered element collector. Most people will set up a view for IFC extraction and probably you could then use the same view for COBie extraction.
namespace MapaQuantidades
{
delegate string AFunc(Parameter P);
public class GetNamedParameterValuesClass
{
public Result TObj33(Autodesk.Revit.UI.ExternalCommandData commandData, ref string message, Autodesk.Revit.DB.ElementSet elements)
{
UIApplication IntApp = commandData.Application;
if (IntApp.ActiveUIDocument == null)
return Result.Cancelled;
Document IntDoc = IntApp.ActiveUIDocument.Document;
TaskDialog TD = new TaskDialog("Search mode") { MainInstruction = "Type of search for parameters." };
TD.AddCommandLink(TaskDialogCommandLinkId.CommandLink1, "Search based on specific search of likely Type/Instance (Faster)");
TD.AddCommandLink(TaskDialogCommandLinkId.CommandLink2, "Single list of names used to search on both Type and Instance (Slower)");
TaskDialogResult Res = TD.Show();
FilteredElementCollector FEC = new FilteredElementCollector(IntDoc);
List<ElementId> InstanceIDs = FEC.WhereElementIsNotElementType().WhereElementIsViewIndependent().ToElementIds() as List<ElementId>;
//With transaction mode not set to readonly could instead of above:
//1) create a temporary 3D view, hide categories of link, scope boxes, grids etc.
//2) Set temporary view to fine, (crop and section box will be off for new views)
//3) Use filtered element collector with overload containing the ViewID of the newly created view
//4) Delete view
IEnumerable<Element> Instances = InstanceIDs
.Select((ElementId eid) => IntDoc.GetElement(eid))
.Where(el => el == null == false)
.Where(el => el.GetTypeId() != ElementId.InvalidElementId)
.Where(J => J.Category == null == false)
.Where(J => J.Category.CategoryType == CategoryType.Model && J.Category.CanAddSubcategory).Select(f => f);
List<string> ParamNamesType = new string[8] {
"Family Name",
"Width",
"Classification.Uniclass.Ss.Number",
"Classification.Uniclass.EF.Number",
"Classification.Uniclass.Ss.Description",
"Classification.Uniclass.Pr.Number",
"Classification.Uniclass.Pr.Description",
"Cost"
}.ToList();
List<string> ParamNamesInst = new string[6] {
"Length",
"Area",
"Quantity",
"Price",
"Unit",
"Total"
}.ToList();
//Should know where the parameters exist (Type or Instance) and search based on this.
if (Res == TaskDialogResult.CommandLink2)
{
ParamNamesType = ParamNamesType.Union(ParamNamesInst).ToList();
ParamNamesInst = ParamNamesType;
}
//"Quantity", "Price", "Total", "Unit" : Not found
//"Type" parameter same as Element.Name and ElementType.Name extracted from class properties below
AFunc GetParamVal = (Parameter P) =>
{
string Out = null;
if (P.StorageType == StorageType.Double)
{
if (UnitUtils.IsValidDisplayUnit(P.DisplayUnitType))
{
Out = Convert.ToString(UnitUtils.ConvertFromInternalUnits(P.AsDouble(), P.DisplayUnitType));
}
else
{
Out = Convert.ToString(P.AsDouble());
}
}
else if (P.StorageType == StorageType.Integer)
{
Out = Convert.ToString(P.AsInteger());
}
else if (P.StorageType == StorageType.ElementId)
{
//Name for an element referred to by parameter is probably more useful than
//ElementID for your purpose
Element El = IntDoc.GetElement(P.AsElementId());
if (El == null)
{
Out = Convert.ToString(P.AsElementId().IntegerValue);
}
else
{
Out = El.Name;
}
}
else if (P.StorageType == StorageType.String)
{
Out = P.AsString();
}
else
{
return null;
}
return Out;
};
List<object> ElInfoSet = new List<object>();
List<string> FoundTypeParamNames = new List<string>();
List<string> FoundInstParamNames = new List<string>();
//Should use BuiltInParameter integer values or Shared Parameter GUIDs rather than using Element.LookupParameter
//Element may have mulptiple parameters of same name and only first found will be returned by Element.LookupParameter
foreach (Element Inst in Instances)
{
ElementType Et = IntDoc.GetElement(Inst.GetTypeId()) as ElementType;
IEnumerable<object> PValsType = ParamNamesType.Select(s => Et.LookupParameter(s)).Where(P => P == null == false).Where(P => P.StorageType != StorageType.None).Select(P => new
{
PName = P.Definition.Name,
Value = GetParamVal(P)
});
IEnumerable<object> PValsInstance = ParamNamesInst.Select(s => Inst.LookupParameter(s)).Where(P => P == null == false).Where(P => P.StorageType != StorageType.None).Select(P => new
{
PName = P.Definition.Name,
Value = GetParamVal(P)
});
dynamic ElInf = new
{
Parameters = new List<Tuple<string, string, bool>>(),
ElementID = Inst.Id,
ElementName = Inst.Name,
TypeName = Et.Name
};
foreach (dynamic item in PValsType)
{
ElInf.Parameters.Add(new Tuple<string, string, bool>(item.PName, item.Value, true));
if (!FoundTypeParamNames.Contains(item.PName))
{
FoundTypeParamNames.Add(item.PName);
}
}
foreach (dynamic item in PValsInstance)
{
ElInf.Parameters.Add(new Tuple<string, string, bool>(item.PName, item.Value, false));
if (!FoundInstParamNames.Contains(item.PName))
{
FoundInstParamNames.Add(item.PName);
}
}
ElInfoSet.Add(ElInf);
}
Microsoft.Office.Interop.Excel.Application xlsApp = new Microsoft.Office.Interop.Excel.Application();
xlsApp.Visible = true;
xlsApp.Workbooks.Add();
Microsoft.Office.Interop.Excel.Worksheet xlsWs = xlsApp.Worksheets.get_Item(1);
xlsWs.Cells[1, 1] = "ElementID";
xlsWs.Cells[1, 2] = "ElementName";
//(Element.Name)
xlsWs.Cells[1, 3] = "TypeName";
//(ElementType.Name) Redundant in this usage as usually same as Element.Name for the instance
//i.e. the instance uses ElementType.Name for it's name.
for (int i = 0; i <= FoundInstParamNames.Count - 1; i++)
{
xlsWs.Cells[1, i + 4] = FoundInstParamNames[i];
}
for (int i = 0; i <= FoundTypeParamNames.Count - 1; i++)
{
xlsWs.Cells[1, i + FoundInstParamNames.Count + 4] = FoundTypeParamNames[i];
}
for (int Row = 0; Row <= ElInfoSet.Count - 1; Row++)
{
dynamic El = ElInfoSet[Row];
xlsWs.Cells[Row + 2, 1] = El.ElementID;
xlsWs.Cells[Row + 2, 2] = El.ElementName;
xlsWs.Cells[Row + 2, 3] = El.TypeName;
List<Tuple<string, string, bool>> Params = El.Parameters;
for (int i = 0; i <= FoundInstParamNames.Count - 1; i++)
{
Tuple<string, string, bool> PV = Params.Find(x => x.Item1 == FoundInstParamNames[i]);
if (PV == null)
continue;
xlsWs.Cells[Row + 2, i + 4] = PV.Item2;
}
for (int i = 0; i <= FoundTypeParamNames.Count - 1; i++)
{
Tuple<string, string, bool> PV = Params.Find(x => x.Item1 == FoundTypeParamNames[i]);
if (PV == null)
continue;
xlsWs.Cells[Row + 2, i + FoundInstParamNames.Count + 4] = PV.Item2;
}
}
return Result.Succeeded;
}
}
}