Welcome to the Onshape forum! Ask questions and join in the discussions about everything Onshape.

First time visiting? Here are some places to start:

  1. Looking for a certain topic? Check out the categories filter or use Search (upper right).
  2. Need support? Ask a question to our Community Support category.
  3. Please submit support tickets for bugs but you can request improvements in the Product Feedback category.
  4. Be respectful, on topic and if you see a problem, Flag it.

If you would like to contact our Community Manager personally, feel free to send a private message or an email.

FeatureScript: Evaluate generic expression

mahirmahir Member, Developers Posts: 882 ✭✭✭✭
I'm currently working on an equation driven 3D curve, but I'm having trouble getting FeatureScript to evaluate string expressions as values. For example, if #T=45, then setVariable(context, "X", "tan(#T)") should set #X to 1. Instead, it just gets stored as a string. Any ideas?


  • ilya_baranilya_baran Onshape Employees, Developers, HDM Posts: 964
    Copying my response from the other thread:

    Unfortunately I think having the feature user input the equation is very difficult to do for now, since we don't allow much even in the isAnything input and FS isn't great for string parsing.  We will need to do a bit of work on our side to make this possible...
    Ilya Baran \ Director, Architecture and FeatureScript \ Onshape Inc
  • mahirmahir Member, Developers Posts: 882 ✭✭✭✭
    Fair enough. I guess I'll sit tight for now. On a related note, how do I make the assignVariable function work properly? I think it would let a user save an expression directly to a variable, but I'm not sure how it implement it.
  • mahirmahir Member, Developers Posts: 882 ✭✭✭✭
    @ilya_baran (or whoever), is it possible to import external js libraries in a fs? An external math parser would do the trick here.
  • ilya_baranilya_baran Onshape Employees, Developers, HDM Posts: 964
    No, sorry -- FS is a totally different language than JS.  It can't do anything potentially non-deterministic, like running external code.
    Ilya Baran \ Director, Architecture and FeatureScript \ Onshape Inc
  • mahirmahir Member, Developers Posts: 882 ✭✭✭✭
    Oh well. Doesn't hurt to ask. I might take a look at implementing a parser directly in fs code for shits 'n giggles. Cheers.
  • ilya_baranilya_baran Onshape Employees, Developers, HDM Posts: 964
    I don't think a parser is the way to go in FS -- it's not really designed for that...  I think it would be more fruitful and similar to the feature we'd eventually want if you went for an expression tree -- something along the following lines:

    Declare an Expression type and a constant t_ that represents a free variable.  Overload operators for Expression type to construct expression trees and write an evaluate function.  Overload functions like sin, cos, etc. to also take an Expression.  Probably ignore units for the time being.

    Make an initial feature that sets the context variable #t_ to t_ (or zero if the "entry" box is checked -- that way the user will be able to enter an expression in terms of #t_ in your curve feature).

    In your curve feature, get an isAnything and expect a number (if the "entry" box is checked) or an Expression (if it is not).  If it's an Expression, evaluate it for t_ between 0 and 1 to get the curve values.

    This is a fair bit of work, but less than a parser I think.  Let me know if I can clarify anything...

    Ilya Baran \ Director, Architecture and FeatureScript \ Onshape Inc
  • mahirmahir Member, Developers Posts: 882 ✭✭✭✭
    Coding-wise, I'm completely self-taught. Expression trees are pretty foreign to me. I'll dig around for some examples. I do like a good challenge.
  • ilya_baranilya_baran Onshape Employees, Developers, HDM Posts: 964
    I realized last night it was actually easier than I thought -- here's a starting point of what I meant -- hopefully enough to get you going:
    Ilya Baran \ Director, Architecture and FeatureScript \ Onshape Inc
  • mahirmahir Member, Developers Posts: 882 ✭✭✭✭
    Nice. I'll try to run with the baton from here :) Thanks for the head start!
  • mahirmahir Member, Developers Posts: 882 ✭✭✭✭
    Perhaps you could explain that little bit of js magic you did there? I didn't know js could define functions on the fly like that. The implementation is definitely simpler and more direct than creating an expression tree based parser.
  • mahirmahir Member, Developers Posts: 882 ✭✭✭✭
    @ilya_baran, I'm having some trouble with the feature behaving properly. Obvously, you were able to generate a custom curve, but I can't successfully edit the feature or create a new one. I thought it was some of the function exports I added, but I get the same behavior when testing a fresh copy of your feature.

    Assuming I can get it working, I have a couple other questions. Can this be done without a "pre-feature" designating the free parameter? Also, do I have to export constants as well (like PI)? Also, I'd like to be able to specify #X, #Y, or #Z as a function of the other variables. Aside from checking for circular references, e.g. X(Y) & Y(X), how would I check when one XYZ definition references another XYZ definition? This would be necessary to ensure calculation order - X(T) before Y(X,T) before Z(X,Y,T). Would I need to declare 3 more constants x_, y_, and z_?
  • kevin_o_toole_1kevin_o_toole_1 Onshape Employees, Developers, HDM Posts: 469
    I'll give the explanation a shot.

    Ilya's utilizing the expression parsing that already exists in FeatureScript, but instead of creating a value (as the math usually does) it creates a function that returns a value when passed free parameter t.

    In FeatureScript, you create a lambda function and pass it around like any other variable. (FS documentation) (more on lambdas). Such functions are not evaluated when they are defined – instead, they are evaluated later, when provided input parameters.

    In FeatureScript, you can also define operator overloads for any type, which behave differently than the standard operation. (FS documentation).

    Given this, Ilya's has defined a specific type called "Expression". An Expression, rather than having a single value, is a function which can return a value when passed a parameter (t). The constant t_ is a trivial Expression which returns itself (made accessible as #t via the first feature). So, when a user types #t into the "x" field in the a dialog, definition.x is set to an Expression into which you can pass any value and get that value back.

    Now, where this gets useful is that Ilya also defined several overloads for common operators on Expressions (and, through the magic of FeatureScript, these overloads are visible and can be used inside the Part Studio). So, for instance, when you add a number to an Expression, rather than getting a result, you get another Expression which can return a result once you pass in the variable t. When a user types #t + 1 into the dialog, the result is a function which takes any value, t, and gives back t + 1.

    The overall effect is that, when you type a full expression in this dialog, all of its operators are evaluated in FeatureScript, and those that involve #t will result in full Expressions. This mechanism will be compatible with whatever functions or operators you overload for the Expression type.

    Hope that helps.

    On your questions:
    This is not how we want to be doing this long-term in FeatureScript. It doesn't extend nicely to multiple variables, it requires a special feature to define the free parameter, its not clear to the user which variables drive the curve, and which are regular FS variables, etc. It's an elegant hack, but a hack nonetheless.

    If you want X, Y, or Z to be functions of each other, you can set a variable feature #X = blah * #t, then in another variable feature say #Y = blah * #X * #t, then in the curve feature set x to #X, etc. I don't think there's any way to set them all to be functions of each other in one feature, since all feature inputs are evaluated when the feature is created, before you have a chance to hook them up (a useful property, since it prevents circular references like you describe).

  • mahirmahir Member, Developers Posts: 882 ✭✭✭✭
    edited July 2016
    Thanks for the explanation, @kevin_o_toole_1. I'll keep playing with it to get it working, but I look forward to a more streamlined implementation. Ideally, we could get an expression to evaluate the same as if it were typed into a sketch dimension or feature parameter. It would be trivial to replace #T with a number for each curve point. Even implementing #X/Y/Z would be easy enough by checking which parameter referenced which. Onward and upward!
  • kevin_o_toole_1kevin_o_toole_1 Onshape Employees, Developers, HDM Posts: 469
    One last thing I didn't realize: The intended editing workflow is
    1. Unsuppress the variable #t = 0.5
    2. Edit the feature to the equations you want
    3. Suppress the variable #t = 0.5
    The reason you have to do this is that there's currently validation in our dialogs that make sure the expression a user types evaluates to a number or value with units. Our new Expression type is neither (it's a function), so you need the fake variable to get it to evaluate to something valid during the time when this is checked.

    Like I said, the UX is not good right now, since our dialogs were never designed for this purpose. The fact that a solution like this exists is a side effect of having the power of a full programming language in your CAD system, but ultimately, you are going to have to wait for us to build this kind of thing into the product to get the right workflow.

  • mahirmahir Member, Developers Posts: 882 ✭✭✭✭
    edited July 2016
    Here's a link to a somewhat cleaned up version including documentation. I added support for cylindrical and spherical coordinates as well as an origin offset option.

    FeatureScript Parametric Curve
  • MBartlett21MBartlett21 Member Posts: 1,764 EDU
    Here is a library that I did to parse an expression and evaluate it.
    To evaluate a string, call evalExpression with the string and a map of variables (the map of variables can generally be constructed with mergeMaps(getAllVariables(context), {"variable" : "value"}) to get variables from the context)
    The function evalExpression returns a map with fields "type" and "value"
    If the type == "empty" then there is no expression to evaluate.
    If the type == "depends" then there is some variables that it needs to know to evaluate the expression. The value is then an array of variable names.
    If the type == "result" then the value is the result.

    MB - I make FeatureScripts:View FeatureScripts
Sign In or Register to comment.