I am back again from a wonderful relaxing Easter holiday with beautiful weather and an almost obscene wealth of blossoming trees and flowers in the Swiss Emmental region:
Spring time really gives us an overwhelming impression of nature's capacity for rapid change! And change is the only constant... And, as we see below, change is a constant challenge in programming as well...
Meanwhile, here are quick pointers to two of the many noteworthy items that cropped up during the past week:
For my own contribution on my first official day back at work and in the blogosphere, here is a summary of an issue I was dealing with just before Easter:
I heard of several different developers struggling with iterations over collections returned by properties of the Revit API classes. The last one we looked into was related to the iteration over an unordered set returned by the CurtainGrid Cells property.
Now the question was encountered again, in a seemingly different form, yet with a similar answer. In this case, both the question and the solution to it were found by Winston Yaw of RISA Technologies:
Question: I ran the following code on a simple Revit Structure model that only has one structural wall and I got an index of -1 for the first layer in the wall's compound structure. That makes no sense. I probably overlooked something simply, but what?
FilteredElementIterator ^elementsIter; FilteredElementCollector ^filteredCollection = gcnew FilteredElementCollector( pApplication->ActiveUIDocument->Document ); filteredCollection->OfClass( Wall::typeid ); elementsIter = filteredCollection ->GetElementIterator(); Wall ^testWall = nullptr; // Get a wall while( elementsIter->MoveNext() ) { testWall = dynamic_cast<Wall^>( elementsIter->Current ); if( testWall ) { // Look at compound structure CompoundStructure^ pCompound = testWall->WallType->GetCompoundStructure(); if( pCompound ) { // Look at first layer System::Collections::IEnumerator ^layerIter = pCompound->GetLayers()->GetEnumerator(); while( layerIter->MoveNext() ) { CompoundStructureLayer^ pLayer = dynamic_cast<CompoundStructureLayer^>( layerIter->Current ); // Get index of first layer Int32 index = pCompound->GetLayers()->IndexOf( pLayer ); // Display in MessageBox MessageBox::Show( index.ToString() ); } } } }
In this case, I was expecting a zero-valued index to be displayed. Can you tell me what's wrong, please? Thanks.
Answer: I found the problem myself. The code above is calling the GetLayers method twice: once to get the Enumerator, and again to use IndexOf.
In summary, the code is doing something like this:
The problem doesn't occur when an intermediate variable to store the collection returned is introduced as follows:
Jeremy responds: Yes, exactly. Congratulations on finding the root of the problem! As said, we have seen similar issues in the past, e.g. related to the iteration over an unordered set property.
For completeness sake, I tested accessing the compound layer structure and the IndexOf method using C# on both an architectural wall in Revit Architecture 2012 and a structural wall in Revit Structure 2012. Since I store the layer collection returned by the GetLayers method in an intermediate variable, just as suggested in your solution, I obviously don't see the problem you describe. Here is the code that I used, which includes comments to highlight the differences between the Revit 2011 and 2012 APIs, excerpted from the external command CmdWallLayers in The Building Coder samples migrated to Revit 2012 API and with a call added to exercise the IndexOf method instead of using an additional variable 'i' for the index:
[Transaction( TransactionMode.Automatic )] class CmdWallLayers : IExternalCommand { public Result Execute( ExternalCommandData commandData, ref string message, ElementSet elements ) { UIApplication app = commandData.Application; UIDocument uidoc = app.ActiveUIDocument; Document doc = app.ActiveUIDocument.Document; // retrieve selected walls, or all walls, // if nothing is selected: List<Element> walls = new List<Element>(); if( !Util.GetSelectedElementsOrAll( walls, uidoc, typeof( Wall ) ) ) { Selection sel = uidoc.Selection; message = ( 0 < sel.Elements.Size ) ? "Please select some wall elements." : "No wall elements found."; return Result.Failed; } //int i; // 2011 int n; double halfThickness, layerOffset; Creator creator = new Creator( doc ); XYZ lcstart, lcend, v, w, p, q; foreach( Wall wall in walls ) { string desc = Util.ElementDescription( wall ); LocationCurve curve = wall.Location as LocationCurve; if( null == curve ) { message = desc + ": No wall curve found."; return Result.Failed; } // wall centre line and thickness: lcstart = curve.Curve.get_EndPoint( 0 ); lcend = curve.Curve.get_EndPoint( 1 ); halfThickness = 0.5 * wall.WallType.Width; v = lcend - lcstart; v = v.Normalize(); // one foot long w = XYZ.BasisZ.CrossProduct( v ).Normalize(); if( wall.Flipped ) { w = -w; } p = lcstart - 2 * v; q = lcend + 2 * v; creator.CreateModelLine( p, q ); q = p + halfThickness * w; creator.CreateModelLine( p, q ); // exterior edge p = lcstart - v + halfThickness * w; q = lcend + v + halfThickness * w; creator.CreateModelLine( p, q ); //CompoundStructure structure = wall.WallType.CompoundStructure; // 2011 CompoundStructure structure = wall.WallType.GetCompoundStructure(); // 2012 //CompoundStructureLayerArray layers = structure.Layers; // 2011 IList<CompoundStructureLayer> layers = structure.GetLayers(); // 2012 //i = 0; // 2011 //n = layers.Size; // 2011 n = layers.Count; // 2012 Debug.Print( "{0} with thickness {1}" + " has {2} layer{3}{4}", desc, Util.MmString( 2 * halfThickness ), n, Util.PluralSuffix( n ), Util.DotOrColon( n ) ); if( 0 == n ) { // interior edge p = lcstart - v - halfThickness * w; q = lcend + v - halfThickness * w; creator.CreateModelLine( p, q ); } else { layerOffset = halfThickness; foreach( CompoundStructureLayer layer in layers ) { Debug.Print( " Layer {0}: function {1}, " + "thickness {2}", //++i, // 2011 layers.IndexOf( layer ), // 2012 layer.Function, Util.MmString( layer.Width ) ); //layerOffset -= layer.Thickness; // 2011 layerOffset -= layer.Width; // 2012 p = lcstart - v + layerOffset * w; q = lcend + v + layerOffset * w; creator.CreateModelLine( p, q ); } } } return Result.Succeeded; } }
Here is the result of running this on a structural wall:
Walls <246407 Generic - 8"> with thickness 203.2 mm has 1 layer: Layer 0: function Structure, thickness 203.2 mm
As said, there is no problem with the IndexOf method itself. As you noted yourself, the issue you observed was due to the repeated call to GetLayers to retrieve the collection multiple times while iterating over it at the same time.