A new version of Revit coming soon, solutions for automatic column type creation and instance placement, handling arc angles, career opportunities and a sustainable economic model:
Are you interested in the new features and enhancements in the upcoming next release of Revit?
Take the opportunity to join the sneak peek on What's New in Revit 2022 later today, on April 6, 2021, at 21:30 CET:
Then, register to join the Autodesk product experts for the full What's New in Revit 2022 webinar next week, April 13, 2021, at 10:00am PT, 1:00pm ET. You can register here:
Here are some other forward-looking Revit resources:
Richard RPThomas108 Thomas provided a nice complete solution to create all required column types from a list of rectangular width and height dimensions in the Revit API discussion forum thread on how to create columns types:
Question: I have the family M_Concrete-Rectangular-Column and two lists of doubles that represent b
and h
of the column.
How can I extract the unique values of the two lists and then create new family types using them?
Answer: Here is a solution in VB.NET:
Private Class ColumnType Inherits EqualityComparer(Of ColumnType) Private IntD As Integer() = New Integer(1) {} Public ReadOnly Property H As Integer Get Return IntD(0) End Get End Property Public ReadOnly Property W As Integer Get Return IntD(1) End Get End Property Public ReadOnly Property Name As String Get Return CStr(H) & "x" & CStr(W) End Get End Property Public Sub New(D1 As Integer, D2 As Integer) If D1 > D2 Then IntD = New Integer() {D1, D2} Else IntD = New Integer() {D2, D1} End If End Sub Public Overrides Function Equals(x As ColumnType, y As ColumnType) As Boolean Return x.H = y.H AndAlso x.W = y.W End Function Public Overrides Function GetHashCode(obj As ColumnType) As Integer Return obj.Name.GetHashCode End Function End Class Private Function TObj168(ByVal commandData As Autodesk.Revit.UI.ExternalCommandData, ByRef message As String, ByVal elements As Autodesk.Revit.DB.ElementSet) As Result Dim UIDoc As UIDocument = commandData.Application.ActiveUIDocument If UIDoc Is Nothing Then Return Result.Cancelled Else Dim IntDoc As Document = UIDoc.Document Dim L1 As Double() = New Double() {100, 200, 150, 500, 400, 300, 250, 250} Dim L2 As Double() = New Double() {200, 200, 150, 500, 400, 300, 250, 250} Dim all As New List(Of ColumnType) For i = 0 To L1.Length - 1 all.Add(New ColumnType(L1(i), L2(i))) Next all = all.Distinct(New ColumnType(0, 0)).ToList Dim FEC As New FilteredElementCollector(IntDoc) Dim ECF As New ElementCategoryFilter(BuiltInCategory.OST_StructuralColumns) Dim Els As List(Of FamilySymbol) = FEC.WherePasses(ECF).WhereElementIsElementType.Cast(Of FamilySymbol).ToList 'Use column name here Dim Existing As List(Of FamilySymbol) = Els.FindAll(Function(x) x.FamilyName = "Concrete-Rectangular-Column") If Existing.Count = 0 Then Return Result.Cancelled End If Dim AlreadyExists As New List(Of ColumnType) Dim ToBeMade As New List(Of ColumnType) For i = 0 To all.Count - 1 Dim Ix As Integer = i Dim J As FamilySymbol = Existing.Find(Function(x) x.Name = all(Ix).Name) If J Is Nothing Then ToBeMade.Add(all(i)) Else AlreadyExists.Add(all(i)) End If Next If ToBeMade.Count = 0 Then Return Result.Cancelled End If Using Tr As New Transaction(IntDoc, "Make types") If Tr.Start = TransactionStatus.Started Then For i = 0 To ToBeMade.Count - 1 Dim Itm = ToBeMade(i) Dim Et As ElementType = Existing(0).Duplicate(Itm.Name) 'Use actual type parameter names 'Use GUIDs instead of LookupParameter where possible Et.LookupParameter("h")?.Set(304.8 * Itm.H) Et.LookupParameter("b")?.Set(304.8 * Itm.W) Next Tr.Commit() End If End Using Return Result.Succeeded End Function
I converted it to C# and added it to The Building Coder samples for future reference:
class ColumnType : EqualityComparer<ColumnType> { int[] _dim = new int[ 2 ]; public int H { get { return _dim[ 0 ]; } } public int W { get { return _dim[ 1 ]; } } public string Name { get { return H.ToString() + "x" + W.ToString(); } } public ColumnType( int d1, int d2 ) { if( d1 > d2 ) { _dim = new int[] { d1, d2 }; } else { _dim = new int[] { d2, d1 }; } } public override bool Equals( ColumnType x, ColumnType y ) { return x.H == y.H && x.W == y.W; } public override int GetHashCode( ColumnType obj ) { return obj.Name.GetHashCode(); } } Result CreateColumnTypes( Document doc ) { int[] L1 = new int[] { 100, 200, 150, 500, 400, 300, 250, 250 }; int[] L2 = new int[] { 200, 200, 150, 500, 400, 300, 250, 250 }; List<ColumnType> all = new List<ColumnType>(); for( int i = 0; i < L1.Length; ++i ) { all.Add( new ColumnType( L1[ i ], L2[ i ] ) ); } all = all.Distinct( new ColumnType( 0, 0 ) ).ToList(); FilteredElementCollector symbols = new FilteredElementCollector( doc ) .WhereElementIsElementType() .OfCategory( BuiltInCategory.OST_StructuralColumns ); // Column name to use string column_name = "Concrete-Rectangular-Column"; IEnumerable<FamilySymbol> existing = symbols.Cast<FamilySymbol>() .Where<FamilySymbol>( x => x.FamilyName.Equals( column_name ) ); if( 0 == existing.Count() ) { return Result.Cancelled; } List<ColumnType> AlreadyExists = new List<ColumnType>(); List<ColumnType> ToBeMade = new List<ColumnType>(); for( int i = 0; i < all.Count ; ++i ) { FamilySymbol fs = existing.FirstOrDefault( x => x.Name == all[ i ].Name ); if( fs == null ) { ToBeMade.Add( all[ i ] ); } else { AlreadyExists.Add( all[ i ] ); } } if( ToBeMade.Count == 0 ) { return Result.Cancelled; } using( Transaction tx = new Transaction( doc ) ) { if( tx.Start( "Make types" ) == TransactionStatus.Started ) { FamilySymbol first = existing.First(); foreach( ColumnType ct in ToBeMade ) { ElementType et = first.Duplicate( ct.Name ); // Use actual type parameter names // Use GUIDs instead of LookupParameter where possible et.LookupParameter( "h" ).Set( 304.8 * ct.H ); et.LookupParameter( "b" ).Set( 304.8 * ct.W ); } tx.Commit(); } } return Result.Succeeded; }
An interesting aspect here is the notion of units. If we were comparing internal units (fractions of feet), we could not have used the integer for comparison. However, since we assume it is mm and column sizes are rounded, we can use them easily.
So, if we take the smallest measurement of length, then we usually have less problems. In an imperial example, if we had used inches, then we can compare an integer of 6 inches rather than a double 0.5 ft. I guess if it is 6.5 inches we are stuck. However, the mm is the smallest unit we generally work with (unless we are talking paint), so can represent smaller fractions of something as whole numbers.
It is what it is.
It would be interesting to explore intelligent rounding and unit dependent real-number fuzz.
One can well consider 1 mm the minimal metric unit in Revit, since it does not (or hardly) support smaller lengths.
For imperial sizes, I guess I would go for 1/16 of an inch.
Depending on what units the model happens to prefer, one could select one or the other dynamically and implement an rounding algorithm that adapts accordingly.
Talking about the creation of columns and column types, let's add a pointer to another related thread on automatic column creation from imported CAD drawing:
Richard also shared some useful notes on normalising arc start and end angles in the Revit API discussion forum thread on how to retrieve startAngle and endAngle of arc object:
What I've noticed recently is that the actual parameters of an arc can be far greater than 2 * pi, so how does this happen?
If you draw a detail arc and drag the ends of the arc one at a time around the circumference, the parameter values will accumulate and become greater than 2 pi (after 1 cycle). They don't reset; you can effectively wind up the arc parameters this way (I discovered parameter numbers reaching 1000 degrees). We could not create arcs otherwise since the creation method enforces p0 must be less than p1 and arcs are drawn anti clockwise. When you cross the 2 pi or 0 boundary, you need p1 not to be starting again from 0, otherwise it would be less than p0 on the other side of the boundary.
So, as @JimJia noted, they are not reliable for arc start end angles (or are if the ends have not been manipulated in such a way). However, in a way, you can get back to the correct angles between 0 and 2 x pi (since each rotation is a multiple of 2 x pi) i.e.
I'm not sure why you would need to do this however.
Other thing I would note is always use Arc.XDirection
and Arc.Normal
with AngleOnPlaneTo
(rather than assuming), since arc can be flipped or rotated.
Also, above was related to winding the arc ends around the circumference in an anti-clockwise direction to increase parameter values.
If user drags ends around in a clockwise direction, then you get negative values for the parameters. Takes less movement from original by user to go into negative domain, so this is more likely to be seen perhaps.
Many thanks to Richard for these helpful observations!
Autodesk is offering a many exciting career opportunities in Europe and elsewhere right now:
Feel free to ask me for a referral.
Good luck applying for one of them and others all over the world in the Autodesk career site!
The doughnut economic model is gaining official acceptance. For instance, Amsterdam bet its post-Covid recovery on doughnut economics, and more cities are now following suit.
The Doughnut, or Doughnut economics, is a visual framework for sustainable development – shaped like a doughnut or lifebelt – combining the concept of planetary boundaries with the complementary concept of social boundaries: