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.

Feature Request: make default feature parameters for numbers work

dave_cowdendave_cowden Member, Developers Posts: 475 ✭✭✭
Default Feature parameters for numbers seem to be ignored.

For example, the below feature will still have the count parameter set to '2'. Where '2' comes from, I have no idea.

FeatureScript 275;
import(path : "onshape/std/geometry.fs", version : "275.0");
annotation { "Feature Type Name" : "NumericCountDefaultsDoNotWork" }
export const myFeature = defineFeature(function(context is Context, id is Id, definition is map)
    precondition
    {
       annotation { "Name" : "My Count" }
       isInteger(definition.count, POSITIVE_COUNT_BOUNDS);
       
    }
    {
        // Define the function's action
    }, { count : 3 });


Comments

  • kevin_o_toole_1kevin_o_toole_1 Onshape Employees, Developers, HDM Posts: 565
    edited February 2016
    Short answer: The default of 2 comes from POSITIVE_COUNT_BOUNDS, defined in the module valueBounds.

    Long answer: FeatureScript has two kinds of parameter defaults: FeatureScript defaults and UI defaults.

    FeatureScript defaults are defined at the end of DefineFeature, as you did above. This default (3) is used if you call your feature within another FeatureScript feature or function without providing a "count", like:
    function myOtherFunction(context, id)
    {
        myFeature(context, id, {});
    }


    UI defaults (i.e. the first thing a user sees in a feature dialog) get handled separately. Defaults for values with units and integers are handled as part of the bounds passed in. The bounds define how the dialog will behave: the lower bound, the upper bound, and the default value. Importantly, this default value depends on the document's default units, so we can have a default of 1 inch for some users, and 2.5cm for others without either group needing to see a long, ugly floating-point value.

    If you want to change default, minimum, or maximum values of a field, the thing to do is make your own bound spec, likely starting by copying one of those defined in valueBounds. In your case, above your feature, you can define:
    export const MY_COUNT_BOUNDS = 
    {
        "min"      : 1,
        "max"      : 1e9,
        (unitless) : [1, 3, 1e5]
    } as IntegerBoundSpec;
    And then refer to those bounds when you define the parameter:
    annotation { "Name" : "My Count" }
    isInteger(definition.count, MY_COUNT_BOUNDS);


  • dave_cowdendave_cowden Member, Developers Posts: 475 ✭✭✭
    Thanks that clears up a lot. But can you explain more what's going on in that line starting with unitless? I guess the middle value is the default. But what are the other two? I would guess they are min, default, max.. Except min and max are already defined just above. Are min and max duplicated?
  • kevin_o_toole_1kevin_o_toole_1 Onshape Employees, Developers, HDM Posts: 565
    edited February 2016
    Yup, good question. You're right that the first and last values are a min and max.

    The three-value array for each unit type is essentially a clamp function: if a user types in 1234567 into a count field, the UI will replace that value with 100000, the max for that unit type. Similarly, typing a 0 will be replaced with a 1.

    The global min and max should be at least as lenient as the min/max for any specific unit. These values are for a FeatureScript sanity check, so you can be certain your feature will never deal will values outside that range. If the user manages to enter a value outside the global min/max range (say, by putting variables or other expressions in the dialog box that evaluate to something outside that range), your feature's precondition will fail.

  • dave_cowdendave_cowden Member, Developers Posts: 475 ✭✭✭
    ok got it. so min and max are the _real_ min and max, and the array is what's used in the UI. got it. This answers the question, thanks
  • kevin_o_toole_1kevin_o_toole_1 Onshape Employees, Developers, HDM Posts: 565
    Correct.

    There is definitely a bit more to think about than one would expect for something as simple as changing the default value, but we want to make sure that FeatureScript features always have the same behaviors that users have come to expect in all our other features (e.g. clamping and having reasonable defaults in different units). This provides a consistent experience for the end user of your features, something we think has been missing from programmatic CAD extensions that we've seen elsewhere.

  • dave_cowdendave_cowden Member, Developers Posts: 475 ✭✭✭
    yes, understood.  I have another question but i'll post it as a new topic so the board is clean
  • billy2billy2 Member, OS Professional, Mentor, Developers, User Group Leader Posts: 2,068 PRO
    edited February 2016
    So you can 'box' each unit's inputs, and based on the document's units the input is filtered?
    export const MY_RADIUS_BOUNDS =
    {
        "min"        : -TOLERANCE.zeroLength * meter,
        "max"        : 500 * meter,
        (meter)      : [1e-5, 0.010, 500],
        (centimeter) : .05,
        (millimeter) : [.01, 0.5, 10],
        (inch)       : .1,
        (foot)       : 0.01,
        (yard)       : 0.0025
    } as LengthBoundSpec;
    export const MY_COUNT_BOUNDS =
    {
            "min" : 1,
            "max" : 1e9,
            (unitless) : [1, 3, 1e5]
        } as IntegerBoundSpec;
    annotation { "Feature Type Name" : "Star" }
    export const star = defineFeature(function(context is Context, id is Id, definition is map)
        precondition
        {
            // Define the parameters of the feature type
            annotation { "Name" : "star's size" }
            isLength(definition.length, MY_LENGTH_BOUNDS);
            annotation { "Name" : "fat star" }
            isLength(definition.radius, MY_RADIUS_BOUNDS);


    So I switch the document's units to cm and type in 1e-6, where does this lower boundary come from?


    So I switch back to mm and it works as expected:




    The $10,000 question:
           println('def ' ~ definition.radius);

    So definition.radius isn't a number like 1mm, it's an object literal with more meaning than just a value.
    length=definition.radius*ang+5;
    This isn't real. I suspect this expression would call an objects property, get's it's value and eval. FS parses each mathematical expression, substituting in the appropriate values and then evaluate. Black magic.
    length=eval(definition.radius*ang+5); //would blow something up.
    
    There's a lot of hocus pocus (sorry for the technical term) when using FS. It's not javascript, values can have a lot of intelligence. definition.radius isn't just a variable with a value, but it drags around a lot of meta data.

            var ang = 360 / definition.count * degree;
            println('ang ' ~ ang);  


    OMG, everything's an object, var in FS isn't anything like var in javascript.


    check this out:

    depending on it's use you get a different result. Is this  operator overloading—less commonly known as operator ad hoc polymorphism?
  • ilya_baranilya_baran Onshape Employees, Developers, HDM Posts: 1,211
    @billy
    Regarding your first question: The centimeter bounds come from converting the meter bound to centimeters.

    Regarding how we deal with units and more generally how FS types work:

    There's no black magic here, but there are overloaded operators intended to make working with units easy.  They are defined in units.fs in std: https://cad.onshape.com/documents/12312312345abcabcabcdeff/w/a855e4161c814f2e9ab3698a/e/1d63f39bbbe543ab87196395

    Something like 3 inches is not just represented as a number (because we want to give you an error when you try to add inches to degrees).  It's represented as a map that maps the string "value" to 0.0762 (because internally we store lengths in meters) and the string "unit" to another map that specifies that you're dealing with a length.  In order to be able to add, multiply, etc. lengths together, we tag this map with a ValueWithUnits type tag and overload operators and other functions based on this type tag.  This representation is what you see printed when you convert the ValueWithUnits to a string using ~.

    Our implementation of vectors and matrices in FS also uses type tags and operator overloads.

    For additional reading, see: https://cad.onshape.com/FsDoc/values.html and https://cad.onshape.com/FsDoc/toplevel.html#overload-resolution


    Ilya Baran \ VP, Architecture and FeatureScript \ Onshape Inc
  • billy2billy2 Member, OS Professional, Mentor, Developers, User Group Leader Posts: 2,068 PRO
    edited February 2016
    There's a lot going on to make it easy. 
  • ilya_baranilya_baran Onshape Employees, Developers, HDM Posts: 1,211
    @billy
    Paraphrasing a well-known quote, we have tried to design FS to be as easy as possible, but no easier: the language needs to be complex enough to be self-consistent and get the job (of regenerating CAD models) done.

    FeatureScript looks similar to JavaScript, but that similarity is superficial -- they are very different languages underneath.  For example, because FS is intended for working with geometry, I felt that operator (and function) overloading is essential -- otherwise you wouldn't be able to add vectors (or lengths).  The type tags system was born out of the idea of having type-based overloading without the complexity of a full type system.  Other design decisions were driven by the desire to be able to isolate features from each other: for instance if m is the map { "foo" : "bar" } and I call f(m), f is guaranteed not to have changed m (or anything else).  That is not the case in JS.  Unlike JS, FS also doesn't have eval, so I'm not sure what you're referring to...

    Regarding your question about ang vs. ang * cnt -- I can't see how you defined cnt, so it's hard to tell what's going on.  My guess is that cnt has units of angle^-1 so that when you multiply it with ang, which has angle units, you get the unitless value 240.  If you show the result of printing cnt or share the document, I'll be able to say more.
    Ilya Baran \ VP, Architecture and FeatureScript \ Onshape Inc
Sign In or Register to comment.