Why is the ElementType empty of all elements in model?

Why is the ElementType empty of all elements in model?

rita.aguiar
Advocate Advocate
3,054 Views
15 Replies
Message 1 of 16

Why is the ElementType empty of all elements in model?

rita.aguiar
Advocate
Advocate

I am trying to obtain the ElementType of each Element in my Revit model.

Model specifically, I would like to later obtain the parameters of the ElementType.

I am using this filter to obtain the elements:

            FilteredElementCollector collector = new FilteredElementCollector(doc);
            collector.WhereElementIsElementType().WhereElementIsViewIndependent();

I know that this filter retrieves the elements I want.

Now I want  to know their ElementType to later obtain its parameters.

(Attention: I don't want the Element's Parameters, I want their ElementType's Parameters).

 

foreach (Element e in collector)
            {
                ElementId id = e.GetTypeId();
                ElementType elementType = (ElementType)doc.GetElement(id);

if (null != elementType) { ParameterSet parameters = elementType.Parameters;

// does other stuff...
}
0 Likes
3,055 Views
15 Replies
Replies (15)
Message 2 of 16

aignatovich
Advisor
Advisor

Hi!

 

You have already extracted element types from the model using collector.WhereElementIsElementType(), not elements. Element type does not have parent element type (except family symbols, that have a parent Family)

Message 3 of 16

naveen.kumar.t
Autodesk Support
Autodesk Support

Hi @rita.aguiar,

Are you looking for this one??

ElementType ET = doc.GetElement(element.GetTypeId()) as ElementType;

To get parameters

foreach(Parameter p in ET.Parameters)
                    {
                       //Your code
                    }

Naveen Kumar T
Developer Technical Services
Autodesk Developer Network

Message 4 of 16

aignatovich
Advisor
Advisor

In the code above, where you create a collector.

 

This is Revit Python Shell analog of your search code:

collector.PNG

Message 5 of 16

rita.aguiar
Advocate
Advocate

thank you, I later noticed that I was using the 

 

 

FilteredElementCollector collector = new FilteredElementCollector(doc);
collector.WhereElementIsElementType().WhereElementIsViewIndependent();

 

 

instead of 

 

FilteredElementCollector collector = new FilteredElementCollector(doc);
collector.WhereElementIsNotElementType().WhereElementIsViewIndependent();

But I still obtain the same result. 

 

0 Likes
Message 6 of 16

rita.aguiar
Advocate
Advocate

Thank you @naveen.kumar.t

 

I have also tried this. But ElementType always comes back null because its id is invalid {-1}.

0 Likes
Message 7 of 16

aignatovich
Advisor
Advisor

Check your code.

This is a version with retrieving elements:

collector2.PNG

 

col = FilteredElementCollector(doc)

elems = col.WhereElementIsNotElementType().WhereElementIsViewIndependent()

elementTypes = filter(lambda x: x != None, map(lambda x: doc.GetElement(x.GetTypeId()), elems))

lookup(elementTypes)
Message 8 of 16

rita.aguiar
Advocate
Advocate

Here http://www.revitapidocs.com/2015/cc66ca8e-302e-f072-edca-d847bcf14c86.htm

Says that "Some elements cannot have type assigned, in which case this method returns invalid element id."
However, I have set a condition in the code to only continue doing what I want if the id is not null nor -1

            FilteredElementCollector collector = new FilteredElementCollector(doc);
            collector.WhereElementIsNotElementType().WhereElementIsViewIndependent();

 

foreach (Element e in collector)
{
ElementId id = e.GetTypeId();
ElementType elementType = (ElementType)doc.GetElement(id);

if (null != id && -1 != id.IntegerValue)
{
ParameterSet parameters = elementType.Parameters;
//......
}
}

But when debugging the code, this condition always turns false!

Is it possible that all my revit elements have an invalid type id???

This is very strange.

I am attaching a printscreen of the revit model i'm using. It's very simple: only has 4 walls and 8 columns. The 4 walls belong to one type, and the 8 columns belong to another type.

 

0 Likes
Message 9 of 16

FAIR59
Advisor
Advisor

Have you debugged long enough?

 

I have started a new project , template <None>, placed 4 Wall and 8 Columns. The collector has  1826 elements.

The first element with valid TypeId is number 1315 !

The first wall is number 1797.

Message 10 of 16

rita.aguiar
Advocate
Advocate

Yes @FAIR59

 

I have tried different ways of doing this. Above I tried to first get the ElementType Id with GetElement Method (ElementId) but I have also tried with GetElement Method (String) by looking up a parameter with the name "Type Name".

FilteredElementCollector collector = new FilteredElementCollector(doc);
            collector.WhereElementIsNotElementType().WhereElementIsViewIndependent();

 

foreach (Element e in collector)
{
   Parameter type = e.LookupParameter("Type Name");
   ElementType elementType = (ElementType)doc.GetElement(type.ToString());
if (null != elementType) {
ParameterSet parameters = elementType.Parameters;
// does other stuff.... }
}

But type is always null for all elements in collector and thus elementType is null as well.

Is it possible that the Elements in the model just don't have this parameter that informs about their ElementType and so

e.LookupParameter("Type Name")

is null? No, I don't think so....

 

(I have attached the project and the Revit document)

0 Likes
Message 11 of 16

FAIR59
Advisor
Advisor

I have made a test macro, and found the result I would expect. (Revit 2019)

            foreach(Element e in collector)
            {
            	string elementInfo = string.Format("<{0}> {1} [{2}]",e.Id,e.Name,e.GetType());
            	ElementId id = e.GetTypeId();
            	if (id==null || id==ElementId.InvalidElementId) continue;
            	sb.AppendLine(elementInfo);
            	Element _type = doc.GetElement(id);
            	sb.AppendLine(string.Format("      *{3}*   <{0}> {1} {2}",id,_type.Name, _type.GetType(), _type is ElementType));
            }
            TaskDialog.Show("debug",sb.ToString());

List_ElementType.PNG

Message 12 of 16

RPTHOMAS108
Mentor
Mentor

Rather than iterating the collector directly you should probably use .ToElements or .ToElementIDs to yield the items and then iterate those. Your code is causing the collector to reset. 

 

i.e.

  ICollection<Element> Els = collector.ToElements();
            foreach (Element e in Els) // para cada e do tipo Element em elementSet
            {....

not

           foreach (Element e in collector) // para cada e do tipo Element em elementSet
            {

Then you can get element type as below, I've commented out your line directly above so you can see where I've changed it in your code.

 //ElementType elementType = (ElementType)doc.GetElement(type.ToString());
                ElementType elementType = doc.GetElement(e.GetTypeId()) as ElementType;

 

For getting parameter values you've used .ToString instead of .AsString in a couple of instances.

 

I believe the below line will always be false for two reasons: variable e is from the collector excluding types. ElementType is the base class so if it were a FamilySymbol for example then I believe 'is' would return false for ElementType. Can use the static function Type.IsSubClassOf for this particular comparison (although I think there are more straightforward ways to establish this with the API).

 

 worksheet.Cells[row, 2] = (e is ElementType) ? 1 : 0; // escreve nas linhas da coluna 2 os valores de ElementType de e

 

Message 13 of 16

rita.aguiar
Advocate
Advocate

Thank you @RPTHOMAS108

 

Why do you say my code is causing the collector to reset? What should I do to avoid that from happening?

 

I tried with .ToElements()

 

FilteredElementCollector collector = new FilteredElementCollector(doc);
ICollection<Element> Els = collector.ToElements();
foreach (Element e in Els)

 

But unfortunately I get the same amount of elements as using just the collector

FilteredElementCollector collector = new FilteredElementCollector(doc);
collector.WhereElementIsNotElementType().WhereElementIsViewIndependent();
    foreach (Element e in collector)

 

0 Likes
Message 14 of 16

RPTHOMAS108
Mentor
Mentor

When I ran your code the debug environment gave the warning of the collector being reset. This may have been caused by my investigation of the elements within debug.

 

From the RevitAPI.chm

 

"One special consideration when using this class in .NET: the debugger will attempt to traverse the members of the collector because of its implementation of IEnumerable. You may see strange results if you also attempt to extract the first element or all elements from the collector while the debugger is also looking at the contents of the collector."

 

It isn't something I'm overly familiar with since I never use it that way i.e. when I use it the scope is limited to a few lines:  declaring the collector, applying the filter(s) and then extracting the Elements to iterate. In your case you keep the scope open within the iteration so there is more possibilities for inadvertently affecting it. I've never been told to use it directly and don't see the benefit? My interpretation is that it was made to implement IEnumarable to give it compatibility with LINQ not to give it certain other behaviours of the IEnumerable interface.

 

I think you need to decide a better way of establishing what elements you should be getting. I don't know what you are missing? When I ran your code with the minor changes above I got the walls and column ElementTypes I thought you were looking to find?

 

If the aim is to collect all model elements then my approach would be to split into two extractions and one iteration:

 

1) Extract instances with parameters

2) Extract ElementTypes and their parameters

3) Link the ElementType parameter information to the instance list via ElementID (.GetTypeID)

 

I used to use Cateogory.HasMaterialQuantities but I don't think that is safe e.g. staircases. Not sure what is ultimately meant by 'WhereElementIsViewIndependent'. I believe it is a bit more general than elements with solid geometry (grids/levels/scope boxes etc.). 

 

 

 
0 Likes
Message 15 of 16

rita.aguiar
Advocate
Advocate

@RPTHOMAS108

I ended up doing what you said in 1) and 2).

 

1) I used a combination of filters to obtain the instances:

//filter

List<Element> collector = new FilteredElementCollector(doc)
.WhereElementIsNotElementType()
.Where(e => e.IsPhysicalElement())
.ToList<Element>();

//method for filter

public static bool IsPhysicalElement(this Element e) { if (e.Category == null) return false; if (e.ViewSpecific) return false; return e.Category.CategoryType == CategoryType.Model && e.Category.CanAddSubcategory; }

2) and to obtain the ElementTypes:

 // filter

List<Element> collector_types = new FilteredElementCollector(doc) .WhereElementIsElementType() .ToList<Element>();

 

I haven't managed to successfully do 3).

Now I need to retrieve just the ElementTypes which are being used in the Revit model, i.e. the ElementTypes with at least an instance in the Revit model. Maybe I could create a new method to filter the ElementTypes. Will 3) help retrieve just the ElementTypes in use? 

 

Thank you very much for your help.

 

0 Likes
Message 16 of 16

RPTHOMAS108
Mentor
Mentor

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;
        }


    }
     
}

 

0 Likes