We recently discussed a comment from Mike King on how to specify openings when constructing a slab, and that reminded me of some interesting sample code I received from Ning in a comment on using the NewWall method and passing in a CurveArray containing its edges to create a wall with a sloped profile. Ning's issue was to create a new wall with a CurveArray containing more than one loop.
What Ning's code does is to retrieve the elevation profile from an existing wall and then create a wall siding covering it. Here is an example wall with an opening before processing:
When you launch Ning's command on it, it first asks you which type of wall siding you would like to add:
The command then queries the existing wall for its profile including all the inner loops representing openings and converts them to a CurveArray in the proper format for calling the NewWall method to create the desired siding complete with all openings:
I tried to adapt Ning's method to create a slab with openings for Mike, but could not get it to work right away and gave up again on that. Also, Ning needed to apply a workaround to his code in some cases. However, it does demonstrate that it is possible to create a new wall complete with openings in one single fell swoop, which is why I wanted to present it here.
Ning implements a class WallProfiles which provides the following functionality:
- Construct a list of wall siding types and prompt the user to select which one to use.
- Retrieve all walls or just the preselected ones.
- Determine their wall elevation profiles using the GetProfiles method.
GetProfiles creates a List<List<XYZ>> instance to represent each wall profile, where the inner lists represent the individual loops. A wall with no openings will have just one single loop, and the first loop is always the outer one, if I remember correctly.
In order to make the calls to NewWall, we need to convert these profile definitions to Revit API CurveArray instances. This is achieved by the following algorithm:
List<List<XYZ>> profile = wps.m_Profiles[ori]; CurveArray ca = new CurveArray(); for( int i = 0; i < profile.Count; i++ ) { List<XYZ> pts = profile[i]; for( int j = 0; j < pts.Count - 1; ++j ) { ca.Append( Line.get_Bound( pts[j], pts[j + 1] ) as Curve ); } ca.Append( Line.get_Bound( pts[pts.Count - 1], pts[0] ) as Curve ); }
Once converted, the resulting CurveArray instances are stored in a dictionary
Dictionary<XYZ, CurveArray> dictCa = new Dictionary<XYZ, CurveArray>();
The dictionary of CurveArray instances is processed to generate all the wall siding elements using the appropriate type and level:
WallType wt = wps.SelectedSidingType as WallType; Level lv = wps.m_Level; foreach( XYZ ori in dictCa.Keys ) { try { Wall wa = doc.Create.NewWall( dictCa[ori], wt, lv, false ); double ang = wa.Orientation .Normalized.Angle( ori ); if( ang > PRECISION ) wa.flip(); } catch { Util.InfoMsg( "cannot create the wall siding" ); return CmdResult.Failed; } }
Here is the complete WallSiding source code and Visual Studio solution including both the sample model we used and a second similar one. The second one can be used to reproduce an issue with the NewWall call that Ning encountered. The model is almost identical to the first one in terms of wall and window location. In spite of this, it requires an additional tweak in WallProfiles.cs, which is initially commented out.
Many thanks to Ning for this very useful sample code which shows how elegantly we can query the model for existing wall geometry and reuse it to create new walls complete with openings!
as discussed w/ Jeremy just recently, i'll add some comments here:
1) the first loop is NOT always the outer one, guess that's the case for floor/roof too(?)
2) i used solid/face/edge method (from Jeremy's sample code) to get wall profile (include all openings) in my previous code, but now i switched to much easy one which will temporarily set original walls to bearing wall, then you can access all needed edges (wall and opening edges only) directly
3) in some cases, you still cannot create new wall using already sorted CurveArray, this bring out the following questions to Autodesk:
question 1) in which special cases that CurveArray CAN or CANNOT be used to create new wall?
question 2) can wall join be accessible in API? if can, then there's no need to get opening profile at all, just join new wall w/ original wall will do the trick.
question 3) in general term, can we use API to do element join at project? to my knowledge, you can join element at family only (?)
question 4) seems Revit's wall "autojoin" may cause some issues, can we enable/disable "autojoin" in API?
Posted by: ning zhou | March 15, 2010 at 14:12
Dear Ning,
Sorry I was so slow in answering...
1) True, we did actually mention the fact that one may have to calculate the polygon areas to determine which one is the outer loop:
http://thebuildingcoder.typepad.com/blog/2008/12/2d-polygon-areas-and-outer-loop.html
http://thebuildingcoder.typepad.com/blog/2008/12/3d-polygon-areas.html
2) That sounds very interesting! I saw from your email that you are setting the built-in parameters WALL_STRUCTURAL_USAGE_PARAM and STRUCTURAL_ANALYTICAL_MODEL to one before querying the geometry, and then resetting them back to zero afterwards, but I do not actually know what the effect of this is or what you mean when you say that this makes it easier to access the edges. Let us discuss this off-line and maybe write a complete new post on it, please.
3) I have passed on this issue to the development team. Let's see what they say.
I am also discussing your questions with them and will let you know what we come up with.
Thank you!
Cheers, Jeremy.
Posted by: Jeremy Tammik | March 17, 2010 at 12:25
Dear Ning,
Although I may be slow in answering, my colleagues in the development team are not, so here are the answers to your questions already:
q1) We need to look deeper into this.
q2) No, it is not, unfortunately. We have an open wish list item for this.
q3) Just like you say: in general, not in a project, although there are exceptions such as the Mullion.JoinMullion method, and we have registered this wish. Yes in the family editor: Document.CombineElements.
q4) No.
Sorry for the many negatives here.
Cheers, Jeremy.
Posted by: Jeremy Tammik | March 17, 2010 at 12:41
Dear Jeremy,
In your BuildingCoder sample, there is a function GetWallProfilePolygons() available to get the outer most face points of a wall. That is fine.
When a multiple layer wall connects to another wall each layer geometry is varies. Is it possible to get the polygon points of each layer of a wall? Any idea please.
Thanks and Regards,
Rajeswara Rao
Posted by: Rajeswara Rao | August 03, 2010 at 09:50
Dear Rajeswara,
Code that determines and displays the exact geometric location of the individual wall layers is provided in
http://thebuildingcoder.typepad.com/blog/2008/11/wall-compound-layers.html
Cheers, Jeremy.
Posted by: Jeremy Tammik | August 03, 2010 at 23:36
Thanks. Great post. Was very helpful to me.
Posted by: Martin | December 05, 2013 at 11:14
Dear Martin,
Thank you for your appreciation.
I am very glad to hear you find it useful.
Cheers, Jeremy.
Posted by: Jeremy Tammik | December 05, 2013 at 20:39