Tuesday, September 24, 2019

A quick note for Vault API developer posterity: PromoteComponentLinks

So - I haven't posted on this blog in forever, but it seems like the best place for this.

Unless you're some kind of regular reader from the mid-2000s and I'm still in your feed - I'm guessing that you found me by googling something like:

How the @#$#%#% do I get Autodesk Vault Items to update their secondary links to files with the Vault API!?!??!

After struggling with this for a while, I figured I would share my knowledge gained so that you don't have to bang your head on the table as long as I did.

Here's the trick:
You need to call a method called ItemService.PromoteComponentLinks.

Here's the code:

 wsm.ItemService.UpdatePromoteComponents(new long[] { item.RevId },  ItemAssignAll.Default, false);

 DateTime timestamp;

 GetPromoteOrderResults promoteOrder =
      wsm.ItemService.GetPromoteComponentOrder(out timestamp);

  if (promoteOrder.PrimaryArray != null && (promoteOrder.PrimaryArray.Length>0))
      wsm.ItemService.PromoteComponents(timestamp, promoteOrder.PrimaryArray);

 if (promoteOrder.NonPrimaryArray != null && (promoteOrder.NonPrimaryArray.Length>0))
      wsm.ItemService.PromoteComponentLinks( promoteOrder.NonPrimaryArray);

 ItemsAndFiles itemsAndFiles =

newItem = itemsAndFiles.ItemRevArray[0];
 wsm.ItemService.UpdateAndCommitItems(new Item[] { newItem });

How did I figure it out? I broke down and turned on Fiddler to watch the traffic between my Vault Explorer and the server. It's actually a pretty nice hack - thank you to whomever on the Autodesk side added what the currently running command was to the URL query.

Anyway - I'm hopeful that your googling finds the above results, instead of various articles and support forum posts from Doug and Wayne from the beginning of this decade :).

Wednesday, June 21, 2017

Everybody Lies....

One of the ongoing themes of the show "House" (a medical drama from a few years back, starring Hugh Laurie as a modern medical Sherlock Holmes) - is that "Everybody Lies". This credo is applied generally to patients, some of whom lie deliberately, and some of whom lie incidentally. In either case, these lies present obstacles and sometimes trainwrecks in the way of getting to the truth...

I've recently run into a couple of examples of this with the Revit API. If you've discovered this page, maybe you're searching and this will help you too.

We've got an addin that's fairly old - goes back to the 2008 or 2009 era of Revit. The app scans all of the elements in the model and attempts to categorize them and process them. We started encountering customer models that could not be processed because what looked like duplicate element IDs. Surely Revit would not allow for multiple element IDs to share the same number, right? Seems fundamental!

After digging for a while in two different support cases, we found issues where when you interrogate elements about their ID or their Category, they lie!

For reasons lost to history, there are several places in the old code where instead of accessing the Id via "Element.Id" or the category via "Element.Category", the code instead used parameters:

Parameter idParam = elem.get_Parameter( BuiltInParameter.ID_PARAM);
Parameter catParam = elem.get_Parameter( BuiltInParameter.ELEM_CATEGORY_ID );

When we started digging into looking at each grouping of a category of elements to be processed, we would see things in the array of elements where some of the elements were "concrete types" like Pipes or Ceilings - but half of the elements to process were just abstract "Element" type objects.

Whatever these "Element" type objects are in the Revit Database, they seem to be related to the "actual" element in question. When you delete the actual element, they go away as well.  But when you ask them for their ID or Category, they respond back with their friend's category. And if you ask them their Id, they respond with their friend's Id.

So - maybe you'll never run into this issue. Certainly if we had been using the more traditional way of retrieving Categories and IDs, we wouldn't have.

But it's probably worth knowing that this is out here, and that Revit is tricky, and you can't always trust an element not to lie.  ;)

Sunday, August 14, 2016

The future minefield of DirectShape Elements.

When Autodesk released the DirectShape API a few years back - I thought it was fantastic. Finally a way to be able to create arbitrary geometry that could live on within Revit - rather than only the elements that the API let us create in the ways that the API let us create them.

We used this to interesting effect in our own Scan to BIM product - providing a mechanism to capture irregular shapes as Revit geometry. What's also interesting - beyond the creation of geometry, the API allows you to assign these objects to be just about any category you want. Think about that for a moment - you can make any arbitrary piece of geometry, and declare that it is a wall, room, stairs, etc.
This felt incredibly cool but also incredibly dangerous to me, from a BIM perspective. And while assigning some characteristics of a wall come from assigning the wall category - it's not an actual wall element, and there are a lot of limitations there.

This Week
This week I experienced for the first time the down side of this approach. A customer was using one of our other tools on one of their models, and it was throwing a weird exception in our app. We were looking through the logs, and based on what we were seeing, it seemed like a model corruption issue. We had a method which retrieved all of the phases that had rooms assigned to that phase - and one of the rooms apparently had a phase which was not actually a phase in this model. How could this happen? maybe corruption? maybe some kind of issue with the cut-and-paste between models?

We started working on a workaround from that angle - but a day later, the customer was able to share the model with us. When we actually ran it via the debugger, we saw which room the phase was associated with: -1  (nothing).  This hadn't seemed possible in our experience. Then we looked closer - the element in question was not actually a Room - it was a DirectShape element. How the heck did that get in there?!?

The real problem in the code, which we had written stuff like this a thousand times over the past 6 or 7 years (since the FilteredElementCollector was invented), looked something like this:

FilteredElementCollector coll = new FilteredElementCollector( myDoc );
IList elements = coll.OfCategory( BuiltInCategory.OST_Rooms ).ToElements();

Historically, if all you wanted was the elements that were rooms, this was all that you needed.
That said - some API developer had created an addin that made a DirectShape box and assigned it to the Room category.

So - going forward, we as API developers can no longer rely on the category (or even Category/IsElementNotElementType) as reliable indicators of the .NET type in the Revit API.

Our quick fix for this particular issue was:

IList elements = 
   coll.OfCategory( BuiltInCategory.OST_Rooms ).OfClass( typeof(SpatialElement) ).ToList();

This would ignore any DirectShape elements that were showing up as rooms.  We quickly found other places (in other methods) where we had made the bad assumption, and had casting errors like:

IList rooms = 
  coll.OfCategory( BuiltInCategory.OST_Rooms).Cast().ToList();

The DirectShape elements failed the Cast at runtime.

Even when fixed, these issues are bound to get confusing. I believe in many cases the DirectShape elements may still schedule as their assigned category, so our customers will believe they have N rooms in the model, when in fact only some of them are "real" rooms.

I'm still wrapping my head around it, but I think ultimately you'll just always have to have an "OfClass(typeof(MyDesiredClass))" on the collector. If you don't, you're liable to get things you don't expect.

All-in-all, it's not too bad to address it, if you know about it. The key is that it's a loophole that other developers can open, and all of us who make software where you don't know what other addins have been used will have to make sure that we're double-careful about our assumptions. And I'm not looking forward to digging back through all the code I've ever written and am still supporting to find all of the bad assumptions I made.

Thursday, May 19, 2016

Autodesk Vault API 2017: 32-bit/64-bit challenges....

For end-users of Autodesk Vault - it's probably a welcome development that Autodesk Vault 2017 now supports a 64-bit version (and a 32-bit version if you're still running on a 32-bit OS).

Less so - however, for Vault developers, whose lives may get more complicated. The explanations in documentation and social media have been somewhat lacking (Where are you Doug Redmond? The citizens of Vault API land need you!).

Here's what I've managed to piece together so far:

You'll see that the SDK ships with two subfolders of the "bin" folder - "x86" and "x64". The bulk of the DLLs inside of both folders are technically "ANYCPU (64-bit preferred)" - with the exception of the new Clic License Manager loader DLL, which seems to be platform-specific. (The Clic thing is something else that could use a bit more explaining than we've been given).

The Vault Explorer Environment
If you're doing Extensions within Vault Explorer, it's pretty straightforward: If you install on a 64-bit machine, you get the 64-bit versions. The documentation says that you'll have to make sure your DLLs match the bit-ness of Vault Explorer... But I believe that it IS possible to use ANYCPU for your explorer customization DLLs (they should match the Vault Explorer automatically, and all of the referenced DLLs are ANYCPU).  So that's not really bad at all (unless you've got 32-bit-specific dependencies in your project - and then you're in for a longer day, even after you've got things worked out... Separate DLLs, separate installers, etc).

Vault Job Processor
Seems to be the same as the Explorer issues above.

Standalone Vault Applications
This is the one that really had me puzzled for a little while. I was naively thinking that I could leave my standalone Vault apps as 32-bit (even on a 64-bit machine). That might work in some cases, but if you've used any of the higher level pieces of the framework (VDF, etc) - then you're out of luck. Those pieces of the framework will attempt to load any Vault Extensions that you have loaded on the current machine - and seem to cause problems if ANY of the Vault Extensions don't match the bit-ness of your standalone app.

So - if you're doing standalone apps for sale, or other cases where you can't guarantee the bit-ness of the operating system, then you may need to do two versions of the apps... One 32-bit and one 64-bit.

All in all, this stuff just kind of snuck up on me - I don't recall hearing about it back at Developer Days - so it's been a more complicated upgrade process than I anticipated.

Good luck out there...

Thursday, May 31, 2012

Which way am I looking?

One of the somewhat obscure but interesting features that I had been looking forward to in the Revit 2013 API, I finally got a chance to use recently when updating our Scan To BIM for Revit application.

That is - you can finally look at the ViewRange settings for a particular plan view. Now, there are a thousand posts out there about how View Range works, and I think you can also ask a hundred Revit users and get at least 30 different answers. The truth is, there are a LOT of complications to it.

From the API persective, there are 5 kinds of PlaneViewPlanes:

  • Top Clip Plane
  • Cut Plane
  • Bottom Clip Plane
  • View Depth Plane
  • Underlay Bottom Plane
Finding out the ViewRange is pretty easy:

PlanViewRange range = myPlanView.GetViewRange();
// for a given plane, find the associated level and offset
ElementId cutLevelId = range.GetLevelId( PlanViewPlane.CutPlane );
double cutLevelOffset = range.GetOffset( PlanViewPlane.CutPlane );
Of course, you can set the view range data as well.

The Real Reason For My Post: View Direction

This is all well and good, but now we come to the REAL reason for my post. As happens so often within the Revit API, you think you can see the way to get from point A to point B and accomplish your mission. And then you are blindsided by some strange Revit complexity that you might have heard about, but had not dealt with before.

The fundamental problem I was trying to solve was to identify what parts of a point cloud were visible in the Plan View. Much of this you can determine from the the PlanViewRange, shown above. But it soon became clear that there was a bit more to it than that. As you may know, Plan Views may face upwards or downwards within Revit. That is to say - a FloorPlan is always "looking down" at the floor - but a Reflected Ceiling Plan or Mechanical Plan is always "looking up" at the ceiling. Apparently structural views could not be decided, so you can actually edit the setting on the view type to be up or down for a model.

I had originally guessed that the ViewDirection property would be a different vector to represent the direction that you're looking ... but that's not the case. So - if you have to know the direction, how do you find it?

It hinges upon the ViewFamilyType (this concept existed as an Element in 2012, but has a fully exposed class in 2013 - so you wouldn't have been stuck in 2012). Views all derive from a ViewFamilyType. Inside the ViewFamilyType, there is a hidden BuiltInParameter (it's only a visible parameter on the aforementioned Structural ViewType). 

 // determine the view direction
ElementId vftId = vp.GetTypeId();
Element vft = vp.Document.GetElement(vftId);
if (vft == null) throw new ArgumentException("Unable to find view family type for view: " + vp.Name);
Parameter viewDirParam = vft.get_Parameter(BuiltInParameter.PLAN_VIEW_VIEW_DIR);

So once you have that, you're all set - the parameter tells you, for your view, whether you're looking up or down.

It's obscure, but as always, I hope you stumble upon this when it's useful to you!

Tuesday, June 28, 2011

Point Cloud Fix in Revit 2012 Web Update 1

One of the biggest problems that people have had with the new Point Cloud capabilities of Revit 2012 is how it dealt with multiple point clouds inserted into your Revit project.
If you had multiple scans that all were from the same origin, and you inserted them all origin-to-origin, then they would all line up, right? wrong...Some of the problem related to how Revit handled large coordinate values (as you may know, Revit doesn't like it when elements start showing up miles from the model origin). If you inserted a PCG file which was indexed from something where the coordinates were in state-plane (which is not all that uncommon) - Revit would basically reset the coordinates and insert it center-to-center. And then how are you going to insert the next one so that it is consistent?
The answer was supposed to be the third insert option: Auto - Origin to Last Placed (meaning that it would make sure that the origin matched the origin of the previous model). But it didn't work :).

So what's new in 2012 Web Update 1? It works!


While no one likes to see bugs like this, it's nice to see decent turnaround on it. I had been worried it was going to involve having to re-index all of the PCG files - that would have been bad.

[Editor's Note: Jason Seck just pointed out to me... you might still be annoyed that they haven't addressed the "first cloud" issue... Your first large coordinate cloud is still going to come in Center-to-Center. All they have fixed is the "relative placement" of clouds issues. I think it will be a while before the "first cloud" issue is fixed, because to do that they would need to fix the "two-miles-from-the-origin issue", and that seems like a big one.]

Saturday, May 28, 2011

An oddity with Drafting View mirroring

So I was asked if it was possible to mirror a drafting view using the API (apparently the built-in project mirroring doesn't cover drafting views).

It seemed straightforward enough... so I wrote a quick test... and it didn't work...
- it worked fine in a floor plan view
- it didn't work in a drafting view
- the code completed just fine - it just didn't really seem to show any difference.

My initial code is below...
- Get all the elements in the current view
- Use the new ElementTransformUtil class to figure out which could be mirrored, and mirror them.

FilteredElementCollector coll =
new FilteredElementCollector(uiDoc.Document, uiDoc.ActiveView.Id);

IList elems = coll.ToElements();

// make sure that stuff can be mirrored
List toMirror = new List();

foreach (Element elem in elems)
if (ElementTransformUtils.CanMirrorElement(uiDoc.Document, elem.Id) == false) continue;

So why did this complete successfully, but not show any difference?
The answer is one of those great undocumented elements in the wilds of the Revit database. When I looked in the debugger at what I was actually attempting to mirror, I saw this:

What I see here is a mystery element called "ExtentElem" which is being copied. I have a sneaking suspicion that it might be telling the view the actual extent of what is in it... So how can we exclude it? well - it has no actual category (which is probably a sign that in general we don't want to mirror an element like that!).

So - we add a line like:

if (elem.Category == null) continue;

and voila! we have our drafting view being mirrored.

I wish that I could say that all Revit database mysteries are "easily" solvable like this - there's plenty of times where you just run into a wall, and can't get to what you want... but today's story has a happy ending!