As always, I remain busy in the Revit API discussion forum.
Here, today, I highlight the following topics:
After several decades of hard resistance, I finally caved in.
Both my bank and my VPN network managers forced me to set up a smartphone.
I got an old Android S5, reset it to factory settings, installed the updates, and removed all possible Samsung ballast.
It is working fine now, and I am still resisting all further temptation to go smartphone-crazy, not setting up dozens of apps or anything.
Just switching it on for the login credential checking, and then immediately off again.
Avoiding smartphone danger; too much digital is bad for you.
Back to a more technology friendly topic... or is it?
One recurring problematic topic is on handling third party library DLL conflicts.
A new query related to that came up, asking:
Question: I recently released a new add-in to my colleagues in the company. Unfortunately, they report a failure starting it if they are already using another third-party add-in. It throws the following file load exception:
What can I do to fix this?
Answer: This looks like a conflict between two versions of a .NET assembly DLL that both of the add-ins are dependent on.
The two add-ins require different versions and a conflict ensues.
You are not the first to run into such an issue, as you can see from the list of old discussions on handling third party library DLL conflicts from 2017.
For new suggestions and solutions that came up since then, you can search the Revit API discussion forum for 'dll conflict' and study more recent articles here on the blog:
As you can see from these examples, you have two choices:
AppDomain
and use some other method to access the functionality you require, e.g., via inter-process communication IPC.In this specific case, it sounds to me as if you are the creator of one add-in, and another add-in uses the same support DLL as you do.
In that case, I suggest you modify your add-in to work with whatever version of the support DLL happens to be loaded.
Alternatively, wrap the support DLL functionality into something that you have control over, so that you can do whatever you like with it.
Frank @Fair59 Aarssen comes to the rescue once again, presenting a very comprehensive and well explained solution
to replicate graphical column schedule sort order with C# by
implementing a custom ColumnMarkComparer
class implementing IComparer<FamilyInstance>
:
Question: I am trying to write an add-in with C# to automate the process of sequentially labelling structural columns to match the sequence in the graphical column schedule.
I have written the following code to get all the concrete columns in the project and then create a list of their Column Location Marks, i.e., A(2000)-1(2150), built-in category:
ElementCategoryFilter filter = new ElementCategoryFilter( BuiltInCategory.OST_StructuralColumns ); StructuralMaterialTypeFilter filter_mat = new StructuralMaterialTypeFilter( StructuralMaterialType.Concrete ); IList<Element> columns = collector .WherePasses( filter ) .WherePasses( filter_mat ) .WhereElementIsNotElementType() .ToElements(); List<XYZ> locations = new List<XYZ>(); List<string> colmarks = new List<string>(); foreach( Element ele in columns ) { string colmark = ele.get_Parameter( BuiltInParameter.COLUMN_LOCATION_MARK ).AsString(); colmarks.Add( colmark ); } colmarks.Sort();
My trouble comes at this point.
The colmarks.Sort
method does not match the order of the columns in the graphical column schedule.
I have also tried sorting the columns by their XYZ
location which doesn't match the order in the graphical column schedule either.
Can anyone provide any insight into how the graphical column schedule orders the columns and how I can replicate this?
Here is the graphical column schedule sort order I am aiming for:
I've tried a few different ways which involve creating a list of the parameter as strings and then sorting or using OrderBy
like this:
List<Element> sortedElements = columns .OrderBy( x => x.get_Parameter( BuiltInParameter.COLUMN_LOCATION_MARK ).AsString() ) .ToList();
That does not help:
I have the same issue in Dynamo too; the order seems to match my code but not the graphical column schedule:
I also read the thread on Graphical column schedule in Revit – sort by specified parameter and still hope someone can shed some light on the logic Revit uses to sort the columns in the graphical column schedule so that I can replicate it with my own code in C#.
Answer: Implement your own comparison class.
LocationMark syntax:
A(xxxx)-1(yyyy)
Sort order:
List<FamilyInstance> GetSortedColumns( Document doc ) { List<FamilyInstance> colums = new FilteredElementCollector( doc ) .WhereElementIsNotElementType() .OfCategory( BuiltInCategory.OST_StructuralColumns ) .OfClass( typeof( FamilyInstance ) ) .Cast<FamilyInstance>() .ToList(); colums.Sort( new ColumnMarkComparer() ); return colums; }
Extension method helper class:
public static class Extensions { public static string GetColumnLocationMark( this FamilyInstance f ) { Parameter p = f.get_Parameter( BuiltInParameter.COLUMN_LOCATION_MARK ); return( p == null ) ? string.Empty : p.AsString(); } }
Comparer helper class:
public class ColumnMarkComparer : IComparer<FamilyInstance> { int IComparer<FamilyInstance>.Compare( FamilyInstance x, FamilyInstance y ) { if( x == null ) { return y == null ? 0 : -1; } if( y == null ) return 1; string[] mark1 = x.GetColumnLocationMark() .Split( '(', ')' ); string[] mark2 = y.GetColumnLocationMark() .Split( '(', ')' ); if( mark1.Length < 4 ) { return mark2.Length < 4 ? 0 : -1; } if( mark2.Length < 4 ) return 1; // gridsequence A int res = string.Compare( mark1[ 0 ], mark2[ 0 ] ); if( res != 0 ) return res; // gridsequence 1 string m12 = mark1[ 2 ].Remove( 0, 1 ); string m22 = mark2[ 2 ].Remove( 0, 1 ); res = string.Compare( m12, m22 ); if( res != 0 ) return res; // value xxxx double d1 = 0; double d2 = 0; double.TryParse( mark1[ 1 ], out d1 ); double.TryParse( mark2[ 1 ], out d2 ); if( Math.Round( d1 - d2, 4 ) != 0 ) { if( d1 < 0 ^ d2 < 0 ) { return d1 < 0 ? 1 : -1; } else { return Math.Abs( d1 ) < Math.Abs( d2 ) ? -1 : 1; } } // value yyyy double.TryParse( mark1[ 3 ], out d1 ); double.TryParse( mark2[ 3 ], out d2 ); if( Math.Round( d1 - d2, 4 ) != 0 ) { if( d1 < 0 ^ d2 < 0 ) { return d1 < 0 ? 1 : -1; } else { return Math.Abs( d1 ) < Math.Abs( d2 ) ? -1 : 1; } } return 0; } }
Many thanks to Fair59 for providing yet another effective high-level answer, which also solves a subsequent question on sorting scheduled elements based on their sort and group fields.
I added his code to The Building Coder samples, cf. the diff to the previous release.