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.

Preserve ID through a try/catch?

EvanReeseEvanReese Member, Mentor Posts: 2,188 ✭✭✭✭✭
edited May 2023 in FeatureScript
I have a feature that I know will fail in certain scenarios, and I'd like to use a try/catch statement to first try my ideal one, and, if it fails, change some settings and try again. Here's what I've tested:
var mainBody;
        try silent
        {
            extrude(context, id + "mainBody", mainDef);
            mainBody = qCreatedBy(id + "mainBody", EntityType.BODY);        
        }
        catch (error)
        {
            mainDef.hasDraft = false;
            mainDef.hasSecondDirectionDraft = false;
            extrude(context, id + "mainBody", mainDef);
            mainBody = qCreatedBy(id + "mainBody", EntityType.BODY);
        }
This isn't working for me. If I change the id of the function in the catch statement as follows, it works
            extrude(context, id + "mainBodyWithoutDraft", mainDef);
            mainBody = qCreatedBy(id + "mainBodyWithoutDraft", EntityType.BODY);
The issue is that anything downstream related to this fails, because the id of the resulting body is different. How can I make featurescript treat the bodies from either function as the same thing? is opNameEntity() helpful here?
Evan Reese

Best Answers

  • NeilCookeNeilCooke Moderator, Onshape Employees Posts: 5,715
    Answer ✓
    Here's a simple example I coded just to check my syntax:

            const extrudeId = id + "extrude";
    
            for (var i in [0, 1]) // max 2 loops - change these numbers to make extrude fail/succeed in this test
            {
                startFeature(context, extrudeId);
    
                try silent
                {
                    opExtrude(context, extrudeId + "body", {
                                "entities" : definition.myQuery,
                                "direction" : evOwnerSketchPlane(context, { "entity" : definition.myQuery }).normal,
                                "endBound" : BoundingType.BLIND,
                                "endDepth" : i * inch
                            });
    
                    endFeature(context, extrudeId); // execution gets this far so feature completed
    
                    break;
                }
                catch
                {
                    abortFeature(context, extrudeId); // execution fails so do not commit/keep id
                }
            }
            
            opFillet(context, id + "fillet", {
                        "entities" : qCreatedBy(extrudeId, EntityType.EDGE),
                        "radius" : 0.1 * inch
                    });

    Senior Director, Technical Services, EMEAI
  • lanalana Onshape Employees Posts: 711
    Answer ✓
    @Evan_Reese
    try using id + unstableComponent("extrudeWDraft" ) and id + unstableComponent("extrudeNoDraft")  .  unstableComponent will prepend * to the input string, that would make results of two features interchangeable for downstream references.

Answers

  • NeilCookeNeilCooke Moderator, Onshape Employees Posts: 5,715
    You can either use the construct of id + "mainBody" + "firstTry"/"secondTry" with qCreatedBy(id + "mainBody") which will get all its children or startFeature and abortFeature (see https://cad.onshape.com/documents/57361ad4e4b00e5012c3857c/w/afc99caf9d2bcd4451843939/e/2262facc4328a254dd7deb6a for usage)
    Senior Director, Technical Services, EMEAI
  • EvanReeseEvanReese Member, Mentor Posts: 2,188 ✭✭✭✭✭
    Thanks, @NeilCooke the Id method is more elegant that what I did, but has the same problem.

    I case I'm actually asking the wrong thing altogether, I'm solving for cases where extruding with draft will fail because there won't be any faces to draft. In those cases I'd like to just switch to extruding without draft without the user having to know that's happening, and without wrecking any downstream relationships. One workaround I'd like to avoid is copying the entire extrude feature code base and making the draft part a "try" statement instead of failing the feature. I might be okay with just always extruding without draft and manually adding the draft code to my feature, but intuition says there's a much better way to solve that, and I'd like to know what it is.

    I looked at your example of the  startFeature() and abortFeature() functions, but I still don't understand how to use them. Here's what I tried, which I'm sure will look silly once I know what they actually do, but there's not documentation on them because they're marked @internal.
            const extrudeId = id + "mainBody";
            startFeature(context, extrudeId);
            try silent
            {
                extrude(context, extrudeId, mainDef);
            }
            catch (error)
            {
                abortFeature(context, extrudeId);
                mainDef.hasDraft = false;
                mainDef.hasSecondDirectionDraft = false;
                extrude(context, extrudeId + "test", mainDef);
            }
            const mainBody = qCreatedBy(id + "mainBody", EntityType.BODY);
    Evan Reese
  • Jacob_CorderJacob_Corder Member Posts: 137 PRO
       const extrudeId = id + "mainBody";
            startFeature(context, extrudeId);
            var needsAbort =true;
            try silent
            {
                extrude(context, extrudeId, mainDef);
                needsAbort =false;
                endFeature(context, extrudeId));//This can throw an error. if it does, catch below will try to abort the already ended feature which results in a error
                     
                 
            }
            catch (error)
            {
                if(needsAbort==true) abortFeature(context, extrudeId);
                mainDef.hasDraft = false;
                mainDef.hasSecondDirectionDraft = false;
                extrude(context, extrudeId + "test", mainDef);
            }
            const mainBody = qCreatedBy(extrudeId, EntityType.BODY);
  • EvanReeseEvanReese Member, Mentor Posts: 2,188 ✭✭✭✭✭
    @Jacob_Corder
    Thanks! It still doesn't solve the issue I'm trying to solve, which is that the "try extrude" body is different than the "catch extrude" body, which will make downstream things fail. A hack I can think of which I think would solve my issue might be:
    1. Do a dummy "test extrude" to check whether it will fail
    2. Use that work/fail to set a boolean
    3. Based on the bool, turn draft off for the "real extrude"
    4. delete the "test extrude body"
    That just feels like a big kludge, which is usually a clue to me that I'm doing it "wrong" and I have something I can learn. I also don't want to add that extra overhead if I don't need to. Any other ideas?
    Evan Reese
  • EvanReeseEvanReese Member, Mentor Posts: 2,188 ✭✭✭✭✭
    This one produces the function I'm after, but I'd love to learn a better way if anyone knows it:
    var draftCausesFailure = false;
            try silent
            {
                extrude(context, id + "testExtrude", mainDef);
            }
            catch (error)
            {
                draftCausesFailure = true;
            }
    
            if (draftCausesFailure)
            {
                mainDef.hasDraft = false;
                mainDef.hasSecondDirectionDraft = false;
            }
            else
            {
                opDeleteBodies(context, id + "deleteTestExtrude", {
                            "entities" : qCreatedBy(id + "testExtrude", EntityType.BODY)
                        });
            }
    
            extrude(context, id + "mainBody", mainDef);
            const mainBody = qCreatedBy(id + "mainBody", EntityType.BODY);

    Evan Reese
  • NeilCookeNeilCooke Moderator, Onshape Employees Posts: 5,715
    Answer ✓
    Here's a simple example I coded just to check my syntax:

            const extrudeId = id + "extrude";
    
            for (var i in [0, 1]) // max 2 loops - change these numbers to make extrude fail/succeed in this test
            {
                startFeature(context, extrudeId);
    
                try silent
                {
                    opExtrude(context, extrudeId + "body", {
                                "entities" : definition.myQuery,
                                "direction" : evOwnerSketchPlane(context, { "entity" : definition.myQuery }).normal,
                                "endBound" : BoundingType.BLIND,
                                "endDepth" : i * inch
                            });
    
                    endFeature(context, extrudeId); // execution gets this far so feature completed
    
                    break;
                }
                catch
                {
                    abortFeature(context, extrudeId); // execution fails so do not commit/keep id
                }
            }
            
            opFillet(context, id + "fillet", {
                        "entities" : qCreatedBy(extrudeId, EntityType.EDGE),
                        "radius" : 0.1 * inch
                    });

    Senior Director, Technical Services, EMEAI
  • tabetha_bulnestabetha_bulnes Member, Developers, HON-TS Posts: 26 PRO
    So you can try doing something like this below. Note that this is only updating the Body id so that really only works if the lower tree items are calling the Body and not a face or edge. I have tested with the Boolean feature and it seems to work. I have not tested if this works with changing the id of a face/edge instead if you are trying to do something like a fillet later in the tree. 

    for a very simple feature, here i create 2 sketches, if sketch 1 query is defined (selected) then it creates an extrude. if  sketch 1 is undefined (unselected. if you break the sketch instead this will break the query for the sketch) then it goes to sketch2. with this you can boolean another part and unselect/reselect the sketch 1 and have the boolean work. 

    annotation { "Feature Type Name" : "id test",
                 "Editing Logic Function" : "assignIdentityEditingLogic" }
    export const idtest = defineFeature(function(context is Context, id is Id, definition is map)
        precondition
        {
            annotation { "Name" : "Sketch", "Filter" : SketchObject.YES, "MaxNumberOfPicks" : 1 }
            definition.sketch is Query;
            
             annotation { "Name" : "Sketch2", "Filter" : SketchObject.YES, "MaxNumberOfPicks" : 1 }
            definition.sketch2 is Query;
            
            annotation { "Name" : "Identity", "UIHint" : UIHint.READ_ONLY} // "UIHint" : "UNCONFIGURABLE" }
            definition.identity is string;
            
        }
        {
            var ent;
            
            try silent
            {
                opExtrude(context, id + "extrude1", {
                        "entities" : definition.sketch,
                        "direction" : evOwnerSketchPlane(context, {"entity" : definition.sketch}).normal,
                        "endBound" : BoundingType.BLIND,
                        "endDepth" : 1 * inch
                });
                ent = qCreatedBy(id + "extrude1", EntityType.BODY);
            }
            catch 
            {
               opExtrude(context, id + "extrude2", {
                        "entities" : definition.sketch2,
                        "direction" : evOwnerSketchPlane(context, {"entity" : definition.sketch2}).normal,
                        "endBound" : BoundingType.BLIND,
                        "endDepth" : 1 * inch
                });
                
                 ent = qCreatedBy(id + "extrude2", EntityType.BODY);
            }
            
            opNameEntity(context, id, { "entity" : ent, "entityName" : definition.identity});
            
        });
        
        
        export function assignIdentityEditingLogic(context is Context, id is Id, oldDefinition is map, definition is map,
        specifiedParameters is map) returns map
    {
        if (specifiedParameters.identity != true)
            definition.identity = toIdentity(id);
    
        return definition;
    }
    
    function toIdentity(id is Id) returns string
    {
        var out is string = id[0];
        for (var i = 1; i < size(id); i += 1)
        {
            out ~= "." ~ id[i];
        }
        return out;
    }
    
    
    
    


  • EvanReeseEvanReese Member, Mentor Posts: 2,188 ✭✭✭✭✭
    NeilCooke said:
    Here's a simple example I coded just to check my syntax:

            const extrudeId = id + "extrude";
    
            for (var i in [0, 1]) // max 2 loops - change these numbers to make extrude fail/succeed in this test
            {
                startFeature(context, extrudeId);
    
                try silent
                {
                    opExtrude(context, extrudeId + "body", {
                                "entities" : definition.myQuery,
                                "direction" : evOwnerSketchPlane(context, { "entity" : definition.myQuery }).normal,
                                "endBound" : BoundingType.BLIND,
                                "endDepth" : i * inch
                            });
    
                    endFeature(context, extrudeId); // execution gets this far so feature completed
    
                    break;
                }
                catch
                {
                    abortFeature(context, extrudeId); // execution fails so do not commit/keep id
                }
            }
            
            opFillet(context, id + "fillet", {
                        "entities" : qCreatedBy(extrudeId, EntityType.EDGE),
                        "radius" : 0.1 * inch
                    });

    Oooh yeah! That's the ticket! Thanks, Neil. I learned quite a bit from that.
    Evan Reese
  • EvanReeseEvanReese Member, Mentor Posts: 2,188 ✭✭✭✭✭
    edited May 2023
    @tabetha_bulnes
    thanks! This looks like a variation of the Assign Identity feature code. This is definitely a handy thing to know for another scenario. I tried something like this, but as you say, I'd need to assign the ID for every entity (body, edge, vertex), which in my case, could be thousands. I ended up going with the startFeature() method, which is exactly what I was looking for.
    Evan Reese
  • EvanReeseEvanReese Member, Mentor Posts: 2,188 ✭✭✭✭✭
    @NeilCooke
    Actually that wasn't the ticket after all, but it feels like the right solution if it can be made to work. Here's an example document of exactly the case I'm trying to solve for. The reason draft makes it fail is that there are no side faces to draft. With the native extrude, this doesn't matter, because you just un-check draft, but for my thing I want to do downstream things to that part either way, which might also have draft.


    Here's the code I'm working with based on what you sent.
    var mainDef = definition;
    
            const extrudeId = id + "mainExtrude";
    
            for (var i = 0; i < 2; i += 1)
            {
    
                startFeature(context, extrudeId);
    
                try
                {
                    extrude(context, extrudeId, mainDef);
                    
                    endFeature(context, extrudeId); // execution gets this far so feature completed
    
                    break;
                }
                catch
                {
                    abortFeature(context, extrudeId); // execution fails so do not commit/keep id
                    mainDef.hasDraft = false;
                    mainDef.hasSecondDirectionDraft = false;
                }
            }
    
            const mainBody = qCreatedBy(extrudeId, EntityType.BODY);
            
            debug(context, mainBody, DebugColor.RED);


    Evan Reese
  • NeilCookeNeilCooke Moderator, Onshape Employees Posts: 5,715
    I will take a look at it later, but I’m struggling to understand why you would want to do this? I would always opExtrude qNonCapEntity and opDraft every time. 
    Senior Director, Technical Services, EMEAI
  • EvanReeseEvanReese Member, Mentor Posts: 2,188 ✭✭✭✭✭
    NeilCooke said:
    I will take a look at it later, but I’m struggling to understand why you would want to do this? I would always opExtrude qNonCapEntity and opDraft every time. 
    Fair. I certainly don't claim to be doing this feature The Right Way, but it is going to call 3 different extrudes, each of which have nearly identical definitions, but each with a few tweaks, and the UI is almost identical to the native extrude feature, so I thought I'd save myself some time and just use extrude(). It has painted me into this corner though, because otherwise I could just qNonCapEntity() -> isQueryEmpty() and draft only if there's anything to draft, or just draft in a Try. I might still do something like that in the end even with the extrude() implementation, but extrude() does have some additional logic, like knowing when to split the draft faces if you have 2 directions with different draft. If your eyes are glazing over, I can send you a quick video of what I'm working on.
    Evan Reese
  • S1monS1mon Member Posts: 3,046 PRO
    Is this going to be a public feature or is it for internal Ovyl use? I'm very curious....
  • EvanReeseEvanReese Member, Mentor Posts: 2,188 ✭✭✭✭✭
    S1mon said:
    Is this going to be a public feature or is it for internal Ovyl use? I'm very curious....
    I'll release it.
    Evan Reese
  • lanalana Onshape Employees Posts: 711
    Answer ✓
    @Evan_Reese
    try using id + unstableComponent("extrudeWDraft" ) and id + unstableComponent("extrudeNoDraft")  .  unstableComponent will prepend * to the input string, that would make results of two features interchangeable for downstream references.

  • EvanReeseEvanReese Member, Mentor Posts: 2,188 ✭✭✭✭✭
    @lana
    That solved it! Quite simply I should add. Thanks so much for taking the time.

    I can't say I understand what's going on here though. I'm looking at the context.fs feature studio and see that unstableComponent() just prepends ANY_ID to a string and that ANY_ID is declared as "*", and that treats it as a wildcard, which a google search tells me it's kind of like "." in regex, but that's about where I fizzle out. Is there somewhere good for me to read up on it?
    Evan Reese
  • lanalana Onshape Employees Posts: 711
    It comes into play when resolving references. If reference points to result of operation with id featureId.subId.*opId1, we would consider results of all the operations whose ids look like featureId.subId.*... . Typically unstableComponent() is used when operations are performed in a loop. often together with setExternalDisambiguation().
  • EvanReeseEvanReese Member, Mentor Posts: 2,188 ✭✭✭✭✭
    Thanks, Lana.
    Evan Reese
Sign In or Register to comment.