Create user extesible funcionality

Create user extesible funcionality

Tripler123
Advocate Advocate
937 Views
9 Replies
Message 1 of 10

Create user extesible funcionality

Tripler123
Advocate
Advocate

Hello,

 

This is more of a programming question than a Revit API question. I was creating a program that allows me to quantify elements. This is the code.

 

 

public double GetFormworkArea(Element el)
        {
			string elementDefinition = el.LookupParameter("element definition").AsString();
            switch (elementDefinition)
            {
				case "exterior Column":
					return getArea(el) / 2;
					break;
				case "interior Column":
					return getArea(el) * 3 / 2;
					break;
			}

			return 0;
        }

 

I have two cases inner column and outer column. But during the modeling process the user wants to create a new definition called "central column" with a formula get area()

How could I do for the user to add this functionality

 

0 Likes
938 Views
9 Replies
Replies (9)
Message 2 of 10

ricaun
Advisor
Advisor

You probably need a database to save/edit all your elementDefinition cases. (Simple file should work, or you could save on the file using Extensible Storage)

 

And you need some kind of Expression Evaluator, similar to Revit Formula works.

 

Like this, the user can create any formula for each elementDefinition case.

 

Something like this for the file:

 

exterior Column = AREA / 2
interior Column = AREA * 3 / 2
central Column = AREA
my Column = AREA * AREA

 

Each line is one case and split by = separate the case with the formula.

 

That's an idea,

 

See yaa!

 

Luiz Henrique Cassettari

ricaun.com - Revit API Developer

AppLoader EasyConduit WireInConduit ConduitMaterial CircuitName ElectricalUtils

Message 3 of 10

Sean_Page
Collaborator
Collaborator

Not sure if it would work for you solution or not, but for these types of things I often use xml files and readers to provide scalability as well as project / owner specific needs.

 

I have attached a PDF example of something similar I had shared previously on LinkedIn.

 

Also a working example on my Git.

https://github.com/TorsionTools/R22

Sean Page, AIA, NCARB, LEED AP
Partner, Computational Designer, Architect
Message 4 of 10

Tripler123
Advocate
Advocate

Thank you very much for your answers.

I will try it and comment my results.

0 Likes
Message 5 of 10

jeremy_tammik
Alumni
Alumni

Since the Revit API is .NET based, and .NET provides powerful scripting support, why not make use of that?

 

For instance, search the Internet for '.net script':

 

https://duckduckgo.com/?q=.net+script

 

Here is an example script engine:

 

https://www.codeproject.com/articles/30999/scriptengine-user-defined-calculations-in-c-vb-jsc

  

Jeremy Tammik Developer Advocacy and Support + The Building Coder + Autodesk Developer Network + ADN Open
0 Likes
Message 6 of 10

ricaun
Advisor
Advisor

I always use JSON to save data, really simple to serialize and deserialize a c# object.

 

Revit already uses the Newtonsoft.Json package by default.

 

Here is an example: https://github.com/ricaun/RevitAddin.JsonExample.

 

@jeremy_tammik wrote:

Since the Revit API is .NET based, and .NET provides powerful scripting support, why not make use of that?

 

For instance, search the Internet for '.net script':

Interesting but I don't think is a good idea to give the user the ability to run any C# code, looks dangerous.

 

This ExpressionParser looks that gonna work.

 

public class ExpressionParser
{
    /// <summary>
    /// eval
    /// Source: https://stackoverflow.com/questions/3972854/parse-math-expression
    /// </summary>
    /// <param name="exp"></param>
    /// <param name="vars"></param>
    /// <returns></returns>
    public double eval(string exp, Dictionary<string, double> vars)
    {
        int bracketCounter = 0;
        int operatorIndex = -1;

        exp = exp.Trim();

        for (int i = 0; i < exp.Length; i++)
        {
            char c = exp[i];
            if (c == '(') bracketCounter++;
            else if (c == ')') bracketCounter--;
            else if ((c == '+' || c == '-') && bracketCounter == 0)
            {
                operatorIndex = i;
                break;
            }
            else if ((c == '*' || c == '/') && bracketCounter == 0 && operatorIndex < 0)
            {
                operatorIndex = i;
            }
        }
        if (operatorIndex < 0)
        {
            exp = exp.Trim();
            if (exp[0] == '(' && exp[exp.Length - 1] == ')')
                return eval(exp.Substring(1, exp.Length - 1), vars);
            else if (vars.ContainsKey(exp))
                return vars[exp];
            else
                return double.Parse(exp);
        }
        else
        {
            switch (exp[operatorIndex])
            {
                case '+':
                    return eval(exp.Substring(0, operatorIndex), vars) + eval(exp.Substring(operatorIndex + 1), vars);
                case '-':
                    return eval(exp.Substring(0, operatorIndex), vars) - eval(exp.Substring(operatorIndex + 1), vars);
                case '*':
                    return eval(exp.Substring(0, operatorIndex), vars) * eval(exp.Substring(operatorIndex + 1), vars);
                case '/':
                    return eval(exp.Substring(0, operatorIndex), vars) / eval(exp.Substring(operatorIndex + 1), vars);
            }
        }
        return 0;
    }
}

With this simple code to test!

 

var expressionParser = new ExpressionParser();
var vars = new Dictionary<string, double>();
vars.Add("AREA", 10.0);
var area = expressionParser.eval("AREA * AREA", vars);
System.Windows.MessageBox.Show($"AREA {area}");

 

See yaa!

Luiz Henrique Cassettari

ricaun.com - Revit API Developer

AppLoader EasyConduit WireInConduit ConduitMaterial CircuitName ElectricalUtils

Message 7 of 10

RPTHOMAS108
Mentor
Mentor

You may find FormulaManager.Evaluate offers a more Revit centric approach.

 

However with the above parameterId as ElementId seems to imply a parameter element is required. Likely to establish the parameter type of the result. 

 

"It evaluates formula using list of global or family parameters depends on document type. "

 

That isn't that clear: probably however means you have to be in a family document to evaluate a family parameter and a project to evaluate a global one. I guess you could make it work via adding what you need in a temporary way if it is requiring a parameter of some kind i.e. a global one in project (although you wouldn't be able to reference other parameter names in the formula string).

 

 

Message 8 of 10

RPTHOMAS108
Mentor
Mentor

A simple example that works:

 

Public Function Obj_220118a(commandData As ExternalCommandData, ByRef message As String, elements As ElementSet) As Result

        Dim app = commandData.Application
        Dim uidoc = commandData.Application.ActiveUIDocument
        Dim IntDoc = uidoc.Document

        Dim Formula As String = "(10*10)^0.5"
        Dim Formula0 As String = "Pi()"
        Dim Out As String = ""

        Using Tx As New Transaction(IntDoc, "XX")
            If Tx.Start Then

                Dim G As String = Guid.NewGuid.ToString
                Dim GP = GlobalParameter.Create(IntDoc, "RPT_" & G, SpecTypeId.Number)
                Out = FormulaManager.Evaluate(GP.Id, IntDoc, Formula0)

                Tx.RollBack()
            End If
        End Using

        TaskDialog.Show("Result", Out)

        Return Result.Succeeded

    End Function

 

 

Message 9 of 10

Tripler123
Advocate
Advocate

 

I thought that I could use as the quantification tables, but it didn't work and I had an error with the following code.

 

public Result Execute(ExternalCommandData commandData,
                              ref string message,
                              ElementSet elements)
        {

            UIApplication uiApp = commandData.Application;
            UIDocument uiDoc = uiApp.ActiveUIDocument;
            Document doc = uiDoc.Document;

            ElementId elementId = uiDoc.Selection.PickObject(ObjectType.Element, "Select element").ElementId;

            //IList<string> functions =  FormulaManager.GetFunctions();

            try
            {
                string result = FormulaManager.Evaluate(elementId, doc, "Volume * 2");
                TaskDialog.Show("Lambda Ingenieria e Innovacion", result);
            }
            catch (Exception ex)
            {
                TaskDialog.Show("Lambda Ingenieria e Innovacion", ex.Message);
                return Result.Cancelled;

            }
            return Result.Succeeded;
        }

 

0 Likes
Message 10 of 10

RPTHOMAS108
Mentor
Mentor

No you need to provide the id to a FamilyParameter or GlobalParameter (GlobalParameter in Project or FamilyParameter in Family) and not an Element or parameter you get from Element.Parameter.

 

A further alternative would be if you had a known empty family that is added to a project. The user could add formulas to that and you could then change type parameter value inputs for those formulas in the project family type to get an output that way.

So in your situation you would add a type parameter to the empty family named 'Area'. The user would add separate type parameters to same family with formulas:

Parameter name = ExtColumn, Formula = Area / 2
Parameter name =  InteriorColumn, Formula = Area * 1.5

...

If you then edit the type and populate the 'Area' parameter then you can read off the appropriate named parameter value (ExtColumn or InteriorColumn...). 'Area' could be a shared parameter that is not user modifiable and you should find it by GUID not it's name 'Area'.

 

Although the approach I outlined initially above seems to work and is likely the most straightforward option. The loaded family option has the benefit of not having to store the formulas in some separate settings file nor create a UI for editing such i.e. it is all inherently Revit.

0 Likes