Why is SW API so inconsistent and weird?
Posted: Sat Nov 23, 2024 9:48 am
This is not a complaint, nor any specific issue, but just something I've been wondering over the years of working with SW API. Although it is very robust and well-documented, there are just so many inconsistent weird things about it that make it really difficult for newcomers to get into it. This applies both to macros and add-ins, and all supported languages. For example:
1. Almost all API calls that return a reference to some API object, return it as a generic Object, rather than a specific class. For example ISldWorks.ActiveDoc returns Object that has to be cast to ModelDoc2 manually. If it's about protecting some proprietary class data, why not return the reference as IModelDoc2, which has nothing exposed?
2. Many API calls that return an array of doubles, return them as a Object/Variant rather than a regular double() array, and then has to be explicitly cast, which is often a nuisance. For example, IModelDocExtension.GetMassProperties2. Even functions that return an array of object references, such as IPartDoc.GetBodies2, return a generic Object that then has to be treated as an array by the user in order to access it's members. Why not return it as a simple Body2 array?
3. Lots of API calls require supplying an ungodly amount of parameters, such as IFeatureManager.FeatureExtrusion2, which requires 23 (!). In almost every scenario, user only needs to control only 1 or 2 of these parameters, while others could be marked as optional and have documented default values. In many cases, there are parameters that are only important in very obscure scenarios. For example, IEntity.Select4 requires supplying an instance of ISelectData, which rarely matters, yet requires lots of additional code to create every time. Meanwhile other functions require passing them an array or object of some type that encapsulates all these parameters, such as IFace2.MaterialPropertyValues (technically a property, not a function, but still), which is a far better coding practice... So why isn't this universal?
4. Continuing on parameters, lots of API calls accept enumerators, especially when supplying types. For example, again the IPartDoc.GetBodies2 requires supplying body type as enumerator from swBodyType_e. Okay... But why the heck do some other functions require supplying types as a String? For example, if you want to select a plane from the FeatureManagerTree, the second parameter of IModelDocExtension.SelectByID2 requires supplying the type (plane) as "PLANE"! Why?? Enumerator exists for it already, it's swSelDATUMPLANES, and it's listed right next to it in the documentation. So why not use that instead?
5. Don't get me started on return value consistency and error handling. Some functions return reference to an object it created/interacted with, for example IFeatureManager.FeatureExtrusion2 returns reference to Feature. But there is no error handling. If it fails, the only way to know it if it's return value is Null. By this logic IModelDocExtension.SelectByID2 should return reference to the object it selected, right? No. It returns Boolean to indicate whether it was successful or not, you need another call (GetSelectedObject6) to get the actual reference. Other calls return Long, which represents an enumerator with error codes, for example ISldWorks.CopyDocument, where 0 = no error, 1 = failure, 2 = another kind of failure, etc. Not to mention that 0 = False in integer/boolean conversion, whereas API calls that return boolean follow convention true = no error, false = error, which is the opposite.
Meanwhile, other functions return their errors through Out / ByRef parameters, such as ISldWorks.OpenDoc6, which requires supplying an empty variable of type Long/swFileLoadError_e for it to write. AND that function also returns True/False to indicate if it opened the doc or not, instead of reference to that doc! Arrrrrrgh!!! Why not just throw a named exception if a function failed, like every other API does??
6. Traversing through features, faces or any other kind of collection is as weird as it gets. For example, you want to get all features in FeatureManagerTree? You'd expect there'd be something like IModelDoc2.GetFeatures, similar logic to IPartDoc.GetBodies2..? Nope. You call IModelDoc2.FirstFeature to get IFeature, and then call IFeature.GetNextFeature to get it's neighbor in a long loop until you get Null to indicate that you got them all. And if you want to get references to all configurations? Yet another logic: first you call IModelDoc2.GetConfigurationNames to get a string array of config names, and then IModelDoc2.GetConfigurationByName, one by one, to get all the references. Just... Why...
I could go on an on, the list of pet peeves with API is endless. Yes, I know that pretty much every issue I mention has a workaround, and you learn them with time, often writing wrappers around SW API calls to hide the ugliness (or use frameworks such as xCAD.NET to achieve the same end), but for people new to SW API, wanting to write macros or simple add-ins, this ungodly mess is often just too much to untangle.
I wonder, how did it come to this? It often feels like the API was made as complex and inconsistent as humanly possible, going against every single best coding practice, every API call working in it's own special way and rules, like every API call was written by a different developer after a 1 week VBA coding crash course.
Again, I'm not looking for any advice or solutions with this post, I'm just genuinely curious what is the historic reason behind this mess
1. Almost all API calls that return a reference to some API object, return it as a generic Object, rather than a specific class. For example ISldWorks.ActiveDoc returns Object that has to be cast to ModelDoc2 manually. If it's about protecting some proprietary class data, why not return the reference as IModelDoc2, which has nothing exposed?
2. Many API calls that return an array of doubles, return them as a Object/Variant rather than a regular double() array, and then has to be explicitly cast, which is often a nuisance. For example, IModelDocExtension.GetMassProperties2. Even functions that return an array of object references, such as IPartDoc.GetBodies2, return a generic Object that then has to be treated as an array by the user in order to access it's members. Why not return it as a simple Body2 array?
3. Lots of API calls require supplying an ungodly amount of parameters, such as IFeatureManager.FeatureExtrusion2, which requires 23 (!). In almost every scenario, user only needs to control only 1 or 2 of these parameters, while others could be marked as optional and have documented default values. In many cases, there are parameters that are only important in very obscure scenarios. For example, IEntity.Select4 requires supplying an instance of ISelectData, which rarely matters, yet requires lots of additional code to create every time. Meanwhile other functions require passing them an array or object of some type that encapsulates all these parameters, such as IFace2.MaterialPropertyValues (technically a property, not a function, but still), which is a far better coding practice... So why isn't this universal?
4. Continuing on parameters, lots of API calls accept enumerators, especially when supplying types. For example, again the IPartDoc.GetBodies2 requires supplying body type as enumerator from swBodyType_e. Okay... But why the heck do some other functions require supplying types as a String? For example, if you want to select a plane from the FeatureManagerTree, the second parameter of IModelDocExtension.SelectByID2 requires supplying the type (plane) as "PLANE"! Why?? Enumerator exists for it already, it's swSelDATUMPLANES, and it's listed right next to it in the documentation. So why not use that instead?
5. Don't get me started on return value consistency and error handling. Some functions return reference to an object it created/interacted with, for example IFeatureManager.FeatureExtrusion2 returns reference to Feature. But there is no error handling. If it fails, the only way to know it if it's return value is Null. By this logic IModelDocExtension.SelectByID2 should return reference to the object it selected, right? No. It returns Boolean to indicate whether it was successful or not, you need another call (GetSelectedObject6) to get the actual reference. Other calls return Long, which represents an enumerator with error codes, for example ISldWorks.CopyDocument, where 0 = no error, 1 = failure, 2 = another kind of failure, etc. Not to mention that 0 = False in integer/boolean conversion, whereas API calls that return boolean follow convention true = no error, false = error, which is the opposite.
Meanwhile, other functions return their errors through Out / ByRef parameters, such as ISldWorks.OpenDoc6, which requires supplying an empty variable of type Long/swFileLoadError_e for it to write. AND that function also returns True/False to indicate if it opened the doc or not, instead of reference to that doc! Arrrrrrgh!!! Why not just throw a named exception if a function failed, like every other API does??
6. Traversing through features, faces or any other kind of collection is as weird as it gets. For example, you want to get all features in FeatureManagerTree? You'd expect there'd be something like IModelDoc2.GetFeatures, similar logic to IPartDoc.GetBodies2..? Nope. You call IModelDoc2.FirstFeature to get IFeature, and then call IFeature.GetNextFeature to get it's neighbor in a long loop until you get Null to indicate that you got them all. And if you want to get references to all configurations? Yet another logic: first you call IModelDoc2.GetConfigurationNames to get a string array of config names, and then IModelDoc2.GetConfigurationByName, one by one, to get all the references. Just... Why...
I could go on an on, the list of pet peeves with API is endless. Yes, I know that pretty much every issue I mention has a workaround, and you learn them with time, often writing wrappers around SW API calls to hide the ugliness (or use frameworks such as xCAD.NET to achieve the same end), but for people new to SW API, wanting to write macros or simple add-ins, this ungodly mess is often just too much to untangle.
I wonder, how did it come to this? It often feels like the API was made as complex and inconsistent as humanly possible, going against every single best coding practice, every API call working in it's own special way and rules, like every API call was written by a different developer after a 1 week VBA coding crash course.
Again, I'm not looking for any advice or solutions with this post, I'm just genuinely curious what is the historic reason behind this mess