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.

Why do op* functions require explicit id

brad_phelanbrad_phelan Member Posts: 80 ✭✭
Why is it that op* functions all take an explicit unique id instead of just taking the parent id and returnIng a new unique id.

After writing a few featurescript functions now I find myself repeating the construction  of ids and thinking up new names that really have no purpose other than being unique.

Am I missing some subtlety that gives an advantage to manually constructing the ids? 



Best Answer

  • kevin_o_toole_1kevin_o_toole_1 Posts: 446
    edited August 2017 Accepted Answer
    Great question. The answer is, it's all about the queries!

    The most obvious example of this is inside your feature, where you can use ids in a query like qCreatedBy for a very robust way of getting at the geometry you created.

    But an even more important reason is that outside your feature, downstream features are also going to reference the geometry from your feature. When they do so, these references should be robust and parametric when a user changes things upstream (something Onshape frankly excels at). When this happens, the queries generated are often going to involve the ids you provided in your code.

    Consider these two features (Which I've put in a public document here: https://cad.onshape.com/documents/473a6f032091ec947b930ab2/w/f668e1c6c409278ddd5451ba/e/eb4ebbf09f80c92678e002eb )
    FeatureScript 660;
    import(path : "onshape/std/geometry.fs", version : "660.0");
    
    annotation { "Feature Type Name" : "Arbitrary ids" }
    export const arbitraryIds = defineFeature(function(context is Context, id is Id, definition is map)
        precondition
        {
            annotation { "Name" : "Do optional thingy" }
            definition.doOptionalThingy is boolean;
            
        }
        {
            var globalCount = 0;
            if (definition.doOptionalThingy)
            {
                fCuboid(context, id + globalCount, {
                        "corner1" : vector(0, 0, 0) * inch,
                        "corner2" : vector(1, 1, 1) * inch
                });
                globalCount += 1;
            }
            
            fCuboid(context, id + globalCount, {
                    "corner1" : vector(3, 0, 0) * inch,
                    "corner2" : vector(4, 1, 1) * inch
            });
        });
    
    annotation { "Feature Type Name" : "Semantic ids" }
    export const semanticIds = defineFeature(function(context is Context, id is Id, definition is map)
        precondition
        {
            annotation { "Name" : "Do optional thingy" }
            definition.doOptionalThingy is boolean;
            
        }
        {
            if (definition.doOptionalThingy)
            {
                fCuboid(context, id + "cuboidForOptionalThing", {
                        "corner1" : vector(0, 0, 0) * inch,
                        "corner2" : vector(1, 1, 1) * inch
                });
            }
            
            fCuboid(context, id + "cuboidForMainThing", {
                    "corner1" : vector(3, 0, 0) * inch,
                    "corner2" : vector(4, 1, 1) * inch
            });
        });
    
    Each feature has an if statement where different geometry is created based on some condition (in this case, it's a user input, but you can imagine any check of inputs, surrounding geometry, etc.)

    In the first feature, I've imitated an Id system similar to what you've proposed: taking the parent id and returnIng a new unique id (In this case, I've used a simple global counter to make it unique).

    In the second feature, I've kept the id consistent for each piece of code (where "piece of code", in practice, corresponds pretty well with "I'm making the same thing here").

    Then, I built each feature in a Part Studio with the doOptionalThingy checkbox checked, and I filleted one of the edges of the main cuboid.

    Now here's the test: When I go back and uncheck the doOptionalThingy box, does my fillet still work? With the first feature, my fillet breaks. With the second feature, it remains intact.

    To find out exactly why you can right-click to "show code" on each Part Studio, where you'll see that the query used for the fillet involves the ids used in your feature: id+"FB64yUZr1UQrhdJ_0.1.extrude.opExtrude" for the first feature, and id+"FLq6FZ7GzZcIJwA_0.cuboidForMainThing.extrude.opExtrude" for the second. The reason the first part studio fails is that the id "1" (from our global counter) changed as soon as the if statement resolved the other way. Here it failed to resolve, but you could also imagine it successfully resolving to the wrong thing. This is the opposite of robust parametric references, and not something we want proliferating through the Part Studios of anyone using a custom feature.

    So yeah, it is annoying to keep thinking of ids like "cuboidForMainThing". But the upside is really robust parametric references to those things you create!

Answers

  • konstantin_shiriazdanovkonstantin_shiriazdanov Member Posts: 901 ✭✭✭✭✭
    as far as I understand op* functions doesn't create any explicit data and their result cannot be assigned to the variable, though it might be rather handy - to have more symbolic-style code, when those symbol contains some meta-data about operation
  • brad_phelanbrad_phelan Member Posts: 80 ✭✭
    as far as I understand op* functions doesn't create any explicit data and their result cannot be assigned to the variable, though it might be rather handy - to have more symbolic-style code, when those symbol contains some meta-data about operation
    That would be the solution. The op* functions could just return their id. In all the cases so far I've encountered this would do the job. 
  • kevin_o_toole_1kevin_o_toole_1 Onshape Employees, Developers, HDM Posts: 446
    edited August 2017 Accepted Answer
    Great question. The answer is, it's all about the queries!

    The most obvious example of this is inside your feature, where you can use ids in a query like qCreatedBy for a very robust way of getting at the geometry you created.

    But an even more important reason is that outside your feature, downstream features are also going to reference the geometry from your feature. When they do so, these references should be robust and parametric when a user changes things upstream (something Onshape frankly excels at). When this happens, the queries generated are often going to involve the ids you provided in your code.

    Consider these two features (Which I've put in a public document here: https://cad.onshape.com/documents/473a6f032091ec947b930ab2/w/f668e1c6c409278ddd5451ba/e/eb4ebbf09f80c92678e002eb )
    FeatureScript 660;
    import(path : "onshape/std/geometry.fs", version : "660.0");
    
    annotation { "Feature Type Name" : "Arbitrary ids" }
    export const arbitraryIds = defineFeature(function(context is Context, id is Id, definition is map)
        precondition
        {
            annotation { "Name" : "Do optional thingy" }
            definition.doOptionalThingy is boolean;
            
        }
        {
            var globalCount = 0;
            if (definition.doOptionalThingy)
            {
                fCuboid(context, id + globalCount, {
                        "corner1" : vector(0, 0, 0) * inch,
                        "corner2" : vector(1, 1, 1) * inch
                });
                globalCount += 1;
            }
            
            fCuboid(context, id + globalCount, {
                    "corner1" : vector(3, 0, 0) * inch,
                    "corner2" : vector(4, 1, 1) * inch
            });
        });
    
    annotation { "Feature Type Name" : "Semantic ids" }
    export const semanticIds = defineFeature(function(context is Context, id is Id, definition is map)
        precondition
        {
            annotation { "Name" : "Do optional thingy" }
            definition.doOptionalThingy is boolean;
            
        }
        {
            if (definition.doOptionalThingy)
            {
                fCuboid(context, id + "cuboidForOptionalThing", {
                        "corner1" : vector(0, 0, 0) * inch,
                        "corner2" : vector(1, 1, 1) * inch
                });
            }
            
            fCuboid(context, id + "cuboidForMainThing", {
                    "corner1" : vector(3, 0, 0) * inch,
                    "corner2" : vector(4, 1, 1) * inch
            });
        });
    
    Each feature has an if statement where different geometry is created based on some condition (in this case, it's a user input, but you can imagine any check of inputs, surrounding geometry, etc.)

    In the first feature, I've imitated an Id system similar to what you've proposed: taking the parent id and returnIng a new unique id (In this case, I've used a simple global counter to make it unique).

    In the second feature, I've kept the id consistent for each piece of code (where "piece of code", in practice, corresponds pretty well with "I'm making the same thing here").

    Then, I built each feature in a Part Studio with the doOptionalThingy checkbox checked, and I filleted one of the edges of the main cuboid.

    Now here's the test: When I go back and uncheck the doOptionalThingy box, does my fillet still work? With the first feature, my fillet breaks. With the second feature, it remains intact.

    To find out exactly why you can right-click to "show code" on each Part Studio, where you'll see that the query used for the fillet involves the ids used in your feature: id+"FB64yUZr1UQrhdJ_0.1.extrude.opExtrude" for the first feature, and id+"FLq6FZ7GzZcIJwA_0.cuboidForMainThing.extrude.opExtrude" for the second. The reason the first part studio fails is that the id "1" (from our global counter) changed as soon as the if statement resolved the other way. Here it failed to resolve, but you could also imagine it successfully resolving to the wrong thing. This is the opposite of robust parametric references, and not something we want proliferating through the Part Studios of anyone using a custom feature.

    So yeah, it is annoying to keep thinking of ids like "cuboidForMainThing". But the upside is really robust parametric references to those things you create!

  • brad_phelanbrad_phelan Member Posts: 80 ✭✭
    Now I think about it I can see why they must be explicit. It has to do with the feature tree and the user interaction with the UI. When the user selects features or bodies or faces etc the generated code uses the explicit id chains which are stable over rebuild. If you generated random ids over each rebuild then the stored references would break.
Sign In or Register to comment.