Revit API Forum
Welcome to Autodesk’s Revit API Forums. Share your knowledge, ask questions, and explore popular Revit API topics.
cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Reporting on Project Parameter Definitions - Need GUIDs

29 REPLIES 29
SOLVED
Reply
Message 1 of 30
CoderBoy
6727 Views, 29 Replies

Reporting on Project Parameter Definitions - Need GUIDs

We have a need to report the project parameters that are defined in a project, as part of a Quality Assurance tool on which we are working.

 

While Revit apparently won't let you create two (non-shared) project parameters with the same, case-sensitive name, for some reason it WILL let you add two Shared Parameters with identical, case-sensitive names provided they have each have different GUIDs.

 

Each of those parameters may be of the same or different types, in the same or different groups, and associated with the same or different categories.  So yes, you could have two parameters with identical names appear in the same group of the properties pane for an element, right next to each other. 

 

So for us to accurately report the project parameters that are defined, we have to differentiate between the two definitions.  However, those definitions may be identical with the only difference (we can tell) being the shared parameter GUIDs. 

 

But there doesn't seem to be a way to ask for the GUID of a project parameter with the API, not even when we try to resort to using Reflection.  Every time we try casting the DefinitionBiningMapIterator.Key to ExternalDefinition we get null, even for parameters we know by testing are shared.  It always casts to InternalDefinition, but never to ExternalDefinition.  So not only can we not seem to get the GUID, there doesn't seem to be a way to even tell if it's a shared parameter or not in the first place.

 

What are we missing?  Is there some other API methods we simply haven't found yet to get this information?  How can we accurately report how the project parameters were defined by the user in the GUI?

 

Thanks very much for any assistance you can provide.

 

 

29 REPLIES 29
Message 2 of 30
Scott_Wilson
in reply to: CoderBoy

Parameter.GUID is the property you seek. Armed with the correct GUID from the shared parameters text file you can verify that the correct parameter is found. Be sure to test the Parameter.IsShared property before accessing the GUID property as non-shared parameters will throw an exception.

 

That works for shared parameters, if you are looking for a non-shared project parameter then I guess you could filter out all shared ones of the same name, with the remaining non-shared one being your target.

Message 3 of 30
CoderBoy
in reply to: CoderBoy

Yes, that is how we do it for regular Element parameters.

 

But have you actually tried this on project parameters?  To the best of my knowledge project parameters do not inherit from the Parmeter class. I believe they are a very different beast, I think in part because they can be associated with one or more categories, which regular Parameter objects don't do.

 

For brief example, it is my understanding that accessing project parameters starts with this:

 

DefinitionBindingMapIterator

definitionBindingMapIterator = projectDocument.ParameterBindings.ForwardIterator();

 

definitionBindingMapIterator.Reset();

 

while (definitionBindingMapIterator.MoveNext())

{

Definition definition = definitionBindingMapIterator.Key;

 

...

 

 

None of the properties on the Definition object are of type Parameter, nor can they seem to cast to a Parameter object, unless I'm missing something.

 

Further, there is absolutely no guarantee that the current shared parameters file (if any) was used to provide the shared parameters to the project parameters.  Imagine, for example, you are an outside party who got the project file from the architect and you don't have their shared parameters file, or the correct one or more of the many shared parameters files they may be using.  WORSE, what if a different shared parameters file is currently being used and it has a shared parameter in it with the same name and other properties but a different GUID?  We'd wind up reporting the wrong shared parameter definition being used in the project, which violates the requirements.

 

Unless you can guarantee an extremely well controlled environment (we can't), you should never assume the definitions in a shared parameters file were the ones actually used in a project.

 

Also, a shared parameters file can't have two parameters with the exact same case-sensitive name in it, whereas the project parameters list CAN have two or more parameters with the exact same case-sensitive name in it, provided they were shared parameters and had different GUIDs.  We've actually seen this in one of our customers projects, which is where the need to accurately report this information came from.  So we really have a need to get the actual GUID being used inside the Revit project for the project parameter definition so we can properly report what is going on, and where things came from.

 

While I greatly appreciate the effort (thanks!), I can't find a way to reliably apply anything you've suggested.

 

If you can provide a snippet of code that works reliably on project parameters, I would be hugely grateful.

 

Thanks again.

 

 

 

Message 4 of 30
Scott_Wilson
in reply to: CoderBoy

Ok, I've been meaning to do something like this for a while now so I just knocked up a quick demo of how to get a complete list of both project and shared parameters used within a document. It's a little crude but it'll do the job.

 

It basically analyses the parameters of every element within a document and generates a list of unique parameters found and categorises them into shared or project parameters. It also appends the GUID as a hex string to the end of all shared parameter names. If you run it on a document that has multiple parameters with the same name (Both shared and project) you will see that it identifies each individually.

 

Autodesk.Revit.DB.Document dbDoc = commandData.Application.ActiveUIDocument.Document;

List<Element> fullElementList = new FilteredElementCollector(dbDoc).WherePasses(new LogicalOrFilter(new ElementIsElementTypeFilter(false),
    new ElementIsElementTypeFilter(true))).ToList<Element>();
                    
List<String> paramListProject = new List<String>();
List<String> paramListShared = new List<String>();

foreach(Element elem in fullElementList)
{                       
    foreach(Parameter param in elem.Parameters)
    {
        if(param.Definition != null)
        {
            if(param.IsShared)
            {
                String paramName = param.Definition.Name + " {" + param.GUID.ToString() + "}";
                
                if(!paramListShared.Any(X => X == paramName))
                {
                    paramListShared.Add(paramName);
                }
            }
            else
            {
                String paramName = param.Definition.Name;

                if(!paramListProject.Any(X => X == paramName))
                {
                    paramListProject.Add(paramName);
                }
            }
        }
    }
}

paramListProject.Sort();
paramListShared.Sort();

String projectParametersOutput = "Project Parameters:\n";
String sharedParametersOutput = "Shared Parameters:\n";

foreach(String paramName in paramListProject)
{
    projectParametersOutput += paramName + "\n";
}

foreach(String paramName in paramListShared)
{
    sharedParametersOutput += paramName + "\n";
}

TaskDialog.Show("List of Project Parameters", projectParametersOutput + "\n\n" + sharedParametersOutput);

 

 

As you can see, there is no need to be messing with binding maps as all the data you need is found within the parameters attached to the elements. There isn't any way to get the GUID for the project parameters, but as you have already noted, each project parameter must have a unique (case-sensitive) name so it shouldn't matter.

 

 

 

 

Message 5 of 30
CoderBoy
in reply to: Scott_Wilson

Again, I hugely appreciate the effort, but there are some significant issues:

 

#1)  [Environment clarification]     > each project parameter must have a unique (case-sensitive) name so it shouldn't matter.

 

That statement is not true, and not what I tried (perhaps unsuccessfully?) to note.  More than 1 project parameter can have the exact same case-sensitive name, provided they are shared and have different GUIDs.  (If they're not shared, then yes the names must be unique - go figure!!!) 

 

So, for example, I could have a project parameter that is a shared parameter called "Voltage" with the GUID 8AB7097C-015A-4FEB-99A9-585882951695, and I could add another project parameter that is a shared parameter (from a different shared parameters file) called "Voltage" with the different GUID 3BDF50E5-AC43-43CA-897E-46118EDBD066 and both would show up on the list of project parameters as "Voltage"

 

Worse, if they're in the same category and in the same group, in the Properties panel for an element of that category, the Revit user would actually see this:

 

Voltage

Voltage

 

If they're not in the same group, one could (for example) be at the top of the list and one could be at the bottom of the list, and the Revit user would probably just fill in whichever one they happened to see first. YES, we have at least 1 (very big) customer with this messed-up situation in their projects.

 

(And which one do you pick if you're trying to build a schedule???)

 

If those hadn't been shared parameters, and were just regular, non-shared project parameters, then Revit would not allow me to create a second project parameter called "Voltage"  (again, go figure!).  But if they're shared, I can have 2 of them.  Or any number of them, if they all have different GUIDs.

 

However, what I also noted was that in a single shared parameters file each parameter definition must have a unique (case-sensitive) name.  So it's easy to see how that could get confusing.  (This whole Revit "feature" is messed up and confusing)

 

#2) Perhaps the biggest issue with this example code is that the code for this approach presumes (and requires) that family instances have been created for at least 1 category attached to every project parameter, and by extension that families actually already exist in the project for at least 1 category attached to every project parameter.  That simply may not be the case.  Instance project parameters can be defined and associated to categories for which no instances have yet to be created.  There may not even be any families yet loaded into the project of those categories as well.  A use case for this tool may be, for example, to QA check a project file that is used as a template, or one that is early in the project lifecycle.

 

#3) The code in the example appears to assume that if it's a shared parameter, it's not a project parameter.  It further appears to assume that if it's not a shared parameter, it must be a project parameter.  Neither of these assumptions is likely to ever be correct, or would only be correct under extremely tightly controlled circumstances, which unfortunately we don't have.  Our code needs to work generically, on any project.

 

#4) And not at all insignificantly, wouldn't iterating over every parameter on every element in a project be OMG unbelievably miserably sloooow, especially for large projects?

 

 

The idea is very creative to be sure, and again the effort is very much appreciated, but unfortunately it doesn't seem to be robust enough to handle any project accurately, which is what we need.

 

Surely there's a better, more reliable/accurate way to get project parameter definition information, including whether or not a project parameter definition is shared and what its GUID is...?

 

Again, many thanks for your efforts!

 

 

Message 6 of 30
CoderBoy
in reply to: CoderBoy

I thought some sample data may help illustrate the problem, so attached are two files:

 

Awful Mess.rvt is a Revit 2014 project file with 4 project parameters defined, all called "Sample Parameter" defined as such:

 

1 is a regular non-shared project parameter of type Text
2 are project parameters that are shared of type Text
1 is a project parameter that is shared of type Length
All are applied to the rooms category
All are in the Text group

All are instance parameters

 

Attached is an image Awful Mess.png that shows the Properties pane for a room instance, check out the "Text" group.

 

For this example project file, without any room instances existing, for each project parameter we need to be able to determine whether or not the project parameter is shared, and if it is shared what it's GUID is.

 

How do we do that?

 

 

Message 7 of 30
Scott_Wilson
in reply to: CoderBoy

Did you run the code?

 

You seem confused regarding the differences between project parameters and a shared parameters. Maybe we are working from different terminolgy. "Project Parameters" are by definition not shared if you take Revit's 'Parameter Properties' dialog as a reference. If you are simply refering to paramaters that are present within a project document then I can see where the confusion might be, but why not just call them Parameters?

 

In the context of the definition above, my statement regarding project parameters (Non-Shared) requiring unique names is accurate. It is the shared parameters that can have common naming, but it is easy to differentiate them using thier GUID. At present the only way I know to get the GUID of a shared parameter without access to the correct external parameters file is to find an element that uses the parameter. This is what my example code demonstrates.

 

You say that you appreciate the help, but you then proceed to complain about my contribution in point form? Good luck solving your problem, I'll leave you to it then.

Message 8 of 30
CoderBoy
in reply to: Scott_Wilson

Hi Scott.

 

I really do very much appreciate your time and efforts, but I agree that we probably have different understandings of the situation.

 

> "Project Parameters" are by definition not shared if you take Revit's 'Parameter Properties' dialog as a reference.

 

Attached is an image of the Revit Project Parameters 'Parameter Properties' dialog that highlights what I mean by having a project parameter that comes from a shared parameters file.  The "Shared parameter" radio button area is circled.

 

Maybe this will help clear up the confusion?

 

Your code does correctly demonstrate getting the GUID for a shared parameter from the project when it is in the Parameters collection of an instance element.  I did know how to get that information. 

 

But as my earlier message with sample data mentions, I need to be able to get that information for shared instance project parameter definitions from the project when there are no instances existing in the project, or possibly even any families loaded into the project for any categories associated with the project parameter.  (That would prevent finding Type project parameter GUIDs, or the ability to create throwaway instances that can be created, queried and discarded...in other words, the data needed to "cheat" may not exist in the project to begin with). 

 

That is the problem I need to solve.  I hope between the sample data and the attached images that what I need to do is easier to understand.

 

Thanks again.

 

 

Message 9 of 30
Scott_Wilson
in reply to: CoderBoy

I just had the thought that maybe you could temporarily bind a parameter
definition to the "Project Information" category which would then make it
accessible from the Document.ProjectInformation element. You can then
analyse it using the methods discussed earlier to determine whether it is
shared or not and get the GUID if available. I haven't tried this yet so
I'm not sure if it would work, worth a try I guess.
Message 10 of 30
Scott_Wilson
in reply to: Scott_Wilson

Well it turns out that temporarily binding a shared parameter definition to BuiltInCategory.OST_ProjectInformation and then reading the ProjectInformation element's parameters to get at the GUID works just fine. I was unable to bind any non-shared parameters using the API which is as I was expecting due to the limited functionality available for dealing with non-shared parameters.

 

I'm not 100% certain, but I think it is fair to say that if a parameter definition rejects binding to a category, then it must be a non-shared project parameter, while any that succeed are shared parameters (which can be confirmed by checking for IsShared just to be sure).

 

Assuming the above rule holds true, all parameter definitions found within Document.ParameterBindings can then be categorised as shared or non-shared. In the event that several parameters are found with the same name (case-sensitive), any that are shared can be uiquely identified using the GUID and there will be at most 1 non-shared parameter which can be uniquely identified within the group based on its non-shared status.

 

That should get you motoring along.

 

By the way I'd like to thank Jeremy for his blog post and code found at http://thebuildingcoder.typepad.com/blog/2012/04/adding-a-category-to-a-shared-parameter-binding.htm... which I used to cobble together a quick test rig for the binding.

Message 11 of 30
CoderBoy
in reply to: Scott_Wilson

Hi Scott.

 

While it's unfortunate that we have to play games like this, the approach sounds utterly brilliant.  I had forgotten that the ProjectInformation object is effectively like a singleton - always exactly 1 element of it exists. 

 

It's the perfect place to try to do something like this.

 

I'll work on integrating this into my code today, but it sounds extremely promising.  I'll let you know how it goes.

 

Thank you so very much! 

 

 

Message 12 of 30
CoderBoy
in reply to: CoderBoy

Well, it looks like this approach should work, but only for some conditions:

 

1) I think it will only work on instance parameters.  This is based on the the fact that Project Information is not a category choice in the Project Parameters dialog for type parameters.  Presuming that carries through in the API, now I don't know what to do about type shared parameters.

 

2) If two or more instance parameters with identical names are already associated with the Project Information category before we start, there won't be a way to tell the existing parameters in the Project Information apart if they have the same parameter type, parameter group, etc (which they very well may).  This *might* be resolvable by trying to temporarily remove all but one of the duplicate project parameters from the Project Information category, and querying the only one left.  I'm not sure how to do that yet because I haven't played around with dissociating a category from a project parameter.

 

So it seems by attempting this approach (using the Project Information "singleton" to store Parameter instances) that accurately reporting the GUIDs for instance parameters may be possible, but it will be a LOT of work checking for and handling duplicates already assigned to that category to ensure accuracy.  At this time I still don't know what to do about getting the GUIDS for type shared project parameters.

 

 

 

Message 13 of 30
Scott_Wilson
in reply to: CoderBoy

I was just looking into the Type Parameter issue and stumbled upon some useful but also very dangerous API behavior.

 

The good news is that the API will happily let you rebind a shared 'type' parameter as an instance parameter using BindingMap.ReInsert. This is great as it will then show up on the ProjectInformation element and reveal its GUID.

 

The problem is that any type information already stored within the project is now deleted and lost forever due to the change from instance to type binding...

 

This is easily cured by doing a rollback on the transaction, but be careful using this technique as it could ruin the project file if the transaction is accidentally comitted by some other part of your code. I'd suggest placing the binding and GUID investigation code within its own method encapsulated by a sub transaction and ensure that it is rolled back properly before returning. This is probably obvious advice, but better safe than sorry.

Message 14 of 30
CoderBoy
in reply to: Scott_Wilson

Well, after wasting about 2 days trying to hack around these deficiencies in the Revit API for a solution to this problem, we have decided to cut our losses and abandon it.  If we see duplicate project parameter names we will simply report they exist, and what they are, and walk away.

 

The problems really come in when you try to write code that will work under all circumstances for any project.  For just one example, (instance) project parameters (shared or not) with duplicate names may already be bound to the Project Information category before you even start.  So an approach we tried is in a temporary transaction to be rolled back, to remove all bindings for the ProjectInformation category for all project parameters, and then when it's time to try to query a Parameter guid from the ProjectInformation object, rebind the project parameters one at a time to the ProjectInformation category so there will never be more than 1 with that exact name in the ProjectInformation object's parameters collection.  This would guarantee the right Parameter object for each project parameter being tested.

 

But unbinding them with the API seems to only actually work for shared project parameters.  Non-shared ones simply refuse to unbind.  When you remove the ProjectInformation category from what turns out to be a non-shared project parameter and try to unbind it, it refuses to unbind but the categories collection for that parameter still shows the ProjectInformation category as NOT being in the list, even though it really is bound to the ProjectInformation object if you look at it in the GUI after committing the test transaction.  UGGGH!!!!

 

Plus, when trying to do some of these things the DefinitionBindingMapIterator.MoveNext() method throws exceptions (exactly when is a blur right now).  So apparently there are some things you can do to a project parameter during the iteration, and some things you can't.

 

We've already spent far more time fighting the Revit API than management feels it is worth for whatever benefit we might gain by trying to report the GUIDs for shared project parameters.

 

Scott:  thanks again for the great ideas.  Valiant effort.  It may be possible to reliably report the GUIDs for all shared project parameters for any project under any configuration with the Revit 2014 API, but I'll only believe it when I see it.

 

I hate to leave a problem like this unsolved, but we also have to do what we're told.

 

 

Message 15 of 30
Scott_Wilson
in reply to: CoderBoy

It's unfortunate that you have to quit when you are so close. Oh well, it's been a fun little project for me, I've been wanting to learn more about parameters from the API side of things for a while now and this topic gave me the incentive to dive in.

 

I have some ideas on how to solve the duplicate names already bound problem also, but I'll keep them to myself for now until I investigate them fully.

Message 16 of 30
Ning_Zhou
in reply to: Scott_Wilson

BindingMap.ReInsert works well, for while (it.MoveNext()) exception -> don't know why rollback doesn't work, i used the following workaround:

 

public struct MyData
{
   public Definition def;
   public ElementBinding binding;
}

 

while (it.MoveNext())
{
   MyData myData;
   myData.def = it.Key;
   myData.binding = it.Current as ElementBinding;
   myDatas.Add(myData);
}

 

foreach (MyData md in myDatas)
{

   // do normal processing

}

 

see JPG for final result

Message 17 of 30
CoderBoy
in reply to: Ning_Zhou

Hi NingZhou.

 

I had thought of an approach like that for solving the iteration exception problem, but was having other issues getting Revit to behave correctly.  Would it be possible for you to provide all the code you used in your test (a zip file would be fine)?

 

If it looks close to what we need it to do (including working under all conditions) we may be able to convince management to give us another couple of hours to try to perfect this for our situation.  Your code may have also resolved other issues we have run into, providing we can get things to rollback cleanly as well.

 

Regardless, it could be a very good learning exercise to see how your code works.

 

At the very least, thank you very much for your idea!

 

 

Message 18 of 30
Ning_Zhou
in reply to: CoderBoy

no problem, below code for your info., let me know if it works or there's any issue / better way, thanks you guys especially Scott for the discussion of this interesting topic!

 

            List<string> psProject = new List<string>();
            List<string> psShared = new List<string>();
            Element pi = new FilteredElementCollector(doc).OfCategory(BuiltInCategory.OST_ProjectInformation).FirstElement();
            List<MyData> myDatas = new List<MyData>();
            BindingMap map = doc.ParameterBindings;
            DefinitionBindingMapIterator it = map.ForwardIterator();
            it.Reset();
            while (it.MoveNext())
            {
                MyData myData;
                myData.def = it.Key;
                myData.binding = it.Current as ElementBinding;
                myDatas.Add(myData);
            }
            foreach (MyData md in myDatas)
            {
                Definition def = md.def;
                ElementBinding binding = md.binding;
                if (def != null)
                {
                    CategorySet cats = binding.Categories;
                    if (!cats.Contains(pi.Category))
                        using (Transaction t = new Transaction(doc, "t"))
                        {
                            t.Start();
                            cats.Insert(pi.Category);
                            InstanceBinding x = app.Create.NewInstanceBinding(cats);
                            if (doc.ParameterBindings.ReInsert(def, x))
                            {
                                Parameter p = pi.get_Parameter(def);
                                if (p.IsShared)
                                {
                                    string pNameGUID = p.Definition.Name + " {" + p.GUID.ToString() + "}";
                                    if (!psShared.Any(X => X == pNameGUID))
                                        psShared.Add(pNameGUID);
                                }
                                else
                                {
                                    string pName = p.Definition.Name;
                                    if (!psProject.Any(X => X == pName))
                                        psProject.Add(pName);
                                }
                            }
                            else
                            {
                                string pName = def.Name;
                                if (!psProject.Any(X => X == pName))
                                    psProject.Add(pName);
                            }
                            t.RollBack();
                        }
                }
            }
            psProject.Sort();
            psShared.Sort();
            string projectParametersOutput = "Project Parameters:\n";
            foreach (string paramName in psProject)
                projectParametersOutput += paramName + "\n";
            string sharedParametersOutput = "Shared Parameters:\n";
            foreach (string paramName in psShared)
                sharedParametersOutput += paramName + "\n";
            TaskDialog.Show("List of Project Parameters", projectParametersOutput + "\n" + sharedParametersOutput);

Message 19 of 30
Scott_Wilson
in reply to: Ning_Zhou

I think the reason that DefinitionBindingMapIterator.MoveNext() was throwing exceptions is that you were messing with binding maps while still looping through them. It's similar to attempting to add/remove a list item while within a foreach loop operatiing on the same list.

 

I suspect that rolling back an enclosed transaction doesn't work either as the BindingMap is probably re-built in memory by the API at each change regardless of roll-backs, invalidating the iterator's pointers.

 

NingZhou's code solves the issue nicely by caching the definitions before looping through them. Rolling back at each loop is also a good safety measure to ensure safety of existing data. Getting the parameter from the ProjectInformation element by Definition instead of name string also solves your duplicate naming problem.

 

I think that about covers it, nice work.

 

Hmm, just had a thought to add GUID and IsShared as extensions to the Definiton class.

Message 20 of 30
CoderBoy
in reply to: Ning_Zhou

Wow!  Thank you very much for providing this code.

 

I haven't had a chance to look at this today, but am hoping to look at it again in the next couple of days.

 

It's greatly appreciated!

 

 

Can't find what you're looking for? Ask the community or share your knowledge.

Post to forums  

Autodesk DevCon in Munich May 28-29th


Rail Community