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.

Dual counterbore featurescript help throwing error but idk whats wrong

michael_mccartymichael_mccarty Member Posts: 7

Hi All,

Starting with FS trying to stay positive but it seems like an uphill journey. Basically I want the same as the hole feature but I need a counterbore on both sides. I want to select the vertex/point and it will create a hole with the counterbore and then flip and do the same on the other side but as of now I cant even get a hole, and the error it throws does not really tell me anything? Help? Thanks !

For context I'm making parts that fit lego technic stuff trying to recreate "Catalog: Parts: Technic, Liftarm: 64179" (google and you will see)

FeatureScript 2559;
import(path : "onshape/std/geometry.fs", version : "2559.0");

annotation {
    "Feature Type Name" : "Double Counterbore Hole",
    "Feature Type Description" : "Creates a hole with counterbores on both ends at a selected point"
}
export const doubleCounterboreHole = defineFeature(function(context is Context, id is Id, definition is map)
    precondition
    {
        annotation { "Name" : "Point", "Filter" : EntityType.VERTEX }
        definition.point is Query;

        annotation { "Name" : "Hole Diameter", "Min" : 1, "Max" : 100, "Default" : 4.9}
        isLength(definition.holeDiameter, { (millimeter) : [1, 5.0, 100] } as LengthBoundSpec);

        annotation { "Name" : "Hole Depth", "Min" : 1, "Max" : 100, "Default" : 8.0 }
        isLength(definition.holeDepth, { (millimeter) : [1, 10.0, 100] } as LengthBoundSpec);

        annotation { "Name" : "Counterbore Diameter", "Min" : 1, "Max" : 100, "Default" : 6.2 }
        isLength(definition.counterboreDiameter, { (millimeter) : [1, 8.0, 100] } as LengthBoundSpec);

        annotation { "Name" : "Counterbore Depth", "Min" : 0.1, "Max" : 100, "Default" : 0.8 }
        isLength(definition.counterboreDepth, { (millimeter) : [0.1, 2.0, 100] } as LengthBoundSpec);
    }
    {
         //const pointPosition = evVertexPoint(context, { "vertex" : definition.point });
        
                // Define the hole parameters
        var holeDefinition = {
            "position" : definition.point,
            "diameter" : definition.holeDiameter,
            "depth" : definition.holeDepth,
            "counterboreDiameter" : definition.counterboreDiameter,
            "counterboreDepth" : definition.counterboreDepth,
            "flip" : false
        };

        // Create the hole
        hole(context, id + "hole", holeDefinition);

        // Create the flipped hole for the counterbore on the other end
        //holeDefinition.flip = true;
        //hole(context, id + "flippedHole", holeDefinition);

    });

and this is the error I get

throw Execution error
78:21
onshape/std/feature.fs (defineFeature)
40:9
HoleTwoCounterbore (const doubleCounterboreHole)
57:17
onshape/std/feature.fs (defineFeature)

Part Studio 2 (Double Counterbore Hole 1)

Part Studio 2
Precondition failed (definition.locations is Query)
369:9
onshape/std/hole.fs (const hole)
57:17
onshape/std/feature.fs (defineFeature)
40:9
HoleTwoCounterbore (const doubleCounterboreHole)
57:17
onshape/std/feature.fs (defineFeature)

Part Studio 2 (Double Counterbore Hole 1)

Part Studio 2

Comments

  • Caden_ArmstrongCaden_Armstrong Member Posts: 225 PRO

    If you look at the hole feature it requires a field called "locations" as a query.
    You are using "position" which is not a field in the hole feature.

    www.smartbenchsoftware.com --- fs.place --- Renaissance
    Custom FeatureScript and Onshape Integrated Applications
  • GregBrownGregBrown Member, Onshape Employees, csevp, pcbaevp Posts: 265

    It may be easier to call opHole in your feature, something like this…

                opHole(context, id + "hole1", {
                            "holeDefinition" : holeDefinition([
                                    holeProfile(HolePositionReference.AXIS_POINT, 0 * millimeter, definition.counterboreDiameter / 2),
                                    holeProfile(HolePositionReference.AXIS_POINT, definition.counterboreDepth, definition.counterboreDiameter / 2),
                                    holeProfile(HolePositionReference.AXIS_POINT, definition.counterboreDepth, definition.holeDiameter / 2),
                                    holeProfile(HolePositionReference.AXIS_POINT, calcDepth - definition.counterboreDepth, definition.holeDiameter / 2),
                                    holeProfile(HolePositionReference.AXIS_POINT, calcDepth - definition.counterboreDepth, definition.counterboreDiameter / 2),
                                    holeProfile(HolePositionReference.AXIS_POINT, calcDepth, definition.counterboreDiameter / 2),
                                    holeProfile(HolePositionReference.AXIS_POINT, calcDepth, 0 * millimeter),
                                ], { "faceNames" : ["1", "WALL1", "SPOT1", "4", "SPOT2", "WALL2", "7"] }),
                            "axes" : [line(holeLocation, holeDirection), ],
                            "targets" : targets,
                            "keepTools" : false
                        });
    

    In your feature do you intend to always have the same counterbore depth/diam for start and end of hole. That's what I assumed above ^^^

    The hole feature is a tricky one to wrap one's head around, but it can be very powerful once you do! I hacked this together quickly.

  • michael_mccartymichael_mccarty Member Posts: 7
    edited January 25

    @GregBrown

    Thank you so much that's great, I wish you would have shared a bit more of your code because I'm still lost on my own :)

    That's basically exactly what I'm trying to do, but what I'm doing is even simpler because the depth, and diameters are always constant.

    Basically my issue now is I don't know how to make:

    HolePositionReference.AXIS_POINT ←- figured this out just a enum
    holeLocation, holeDirection
    targets
    

    I was thinking the holepositionreference was a point, and the holelocation/direction were a point and a line and the "targets" was the body, but that did not work.

        precondition
        {
            annotation { "Name" : "Point", "Filter" : EntityType.VERTEX }
            definition.point is Query;
            // Input for selecting a line
            annotation { "Name" : "Direction Line", "Filter" : EntityType.EDGE } 
            definition.line is Query;
            annotation { "Name" : "Body", "Filter" : EntityType.BODY } 
            definition.body is Query;
    
        }
        {
            const holeDiameter = 5.0 * millimeter; // Hole diameter
            const holeDepth = 10.0 * millimeter; // Depth of the hole
            const counterBoreDia = 6.2 * millimeter;
            
            
            
            opHole(context, id + "hole1", {
                "holeDefinition" : holeDefinition([
                        holeProfile(HolePositionReference.AXIS_POINT, 0 * millimeter, counterBoreDia / 2),
                        holeProfile(HolePositionReference.AXIS_POINT, holeDepth * millimeter, counterBoreDia / 2),
                        holeProfile(HolePositionReference.AXIS_POINT, holeDepth, 0 * millimeter),
                    ], { "faceNames" : ["1","2","3"] }),
                "axes" : [line(vector(-1,-1,0)*millimeter,vector(0,0,-1))],
                "targets" : definition.body,
                "keepTools" : false
            });
    

    I'm wanting to just select a point and a line as the direction and it will make the dual counterbore at 8mm spacing along the line for n instances.

    Edit: Working on this I figured out the holepositionref and the first error I needed units on counterboredia

    Edit: I think I got a bit further, not sure what hole_no_hits means? I google and get nothing, where are the docs?

    This is the error I get ?

    	
    @opHole: HOLE_NO_HITS
    957:20
    onshape/std/geomOperations.fs (const opHole)
    29:9
    Feature Studio 3 (const singleHole)
    57:17
    onshape/std/feature.fs (defineFeature)

    Part Studio 1 (Single Hole 1)

    Part Studio 1

    Thinking through this and working on it I got holes all over the place but never really normal to the point. I think the best way to do this is just that a user selects a face, the fs will calculate the center of that face and generate the dualcounterbore holes at 8mm intervals from center to each side.

  • michael_mccartymichael_mccarty Member Posts: 7

    Also could someone please point me at the docs for ophole, I cannot find a single example online and chatgpt is clueless. I would expect there is online documentation with examples of how to use the function. Basically every function takes a "definition" which does not help me because that's an object and we dont know what's in the object.

    Thanks!

  • GregBrownGregBrown Member, Onshape Employees, csevp, pcbaevp Posts: 265

    The documentation is here:
    https://cad.onshape.com/FsDoc/library.html#opHole-Context-Id-map

    You might need to follow some of the links to dig one level further into the types, eg. HoleDefinition https://cad.onshape.com/FsDoc/library.html#HoleDefinition

  • GregBrownGregBrown Member, Onshape Employees, csevp, pcbaevp Posts: 265

    Here is a rough redo of your code:



    https://cad.onshape.com/documents/094f8898e036922eb050009b/w/6ca25cb8756081fe90a9ffcb/e/7506a4da25f351526d0fa470

    I kept a lot of what you had posted, but I did have to make a number of changes. I did not restructure it as much as I would have for my own feature, but I did use some artistic license with the selections. Have a look at how I used Mate connectors for the location (it also allows you to select a sketch point..)
    Also:

    • added direction flip options for the pattern and the hole itself
    • added a lot of filters on the precondition to protect from bad input
    • included a hardcoded counterbore depth which seems important…
    • have a look at how I evaluated the hole direction and hole location, needed in line 63
    • used a simple pattern in the direction extracted from the user input line, also with number and spacing
    • I kept the "tools" (the bodies created in the opHole) so I could pattern them then do one Boolean subtract at the end.
    • etc…

    There are many, many more things to optimize here, but perhaps this will get you started. Particularly in patterning there are a million different approaches. I am not sure what your intent is, but again, use this and the docs and experiment. Folks here will probably happily weigh in on how to do this properly :)

  • michael_mccartymichael_mccarty Member Posts: 7

    Again stellar answer thank you Greg! I like the way you did things. I eventually got things to work. I tried to keep it as simple as possible and basically allow it to poke a hole (dual counter bore) that fits the lego pieces perfect given the holeDefinition. This way I can just put points all over where I want to be able to poke lego pats in and then I just select the ones I want to have holes in.

    This way I dont have to deal with the direction of the holes and updating the vertex as I go in the for loop. I can just do a linear pattern of points and then select the ones I want to turn into lego holes.

    I could probably do this a lot better but it's pretty hard for me to understand everything. The library documentation was very helpful. Also just keeping the tools allowed me to see where they were when I was messing up the normal. If I wanted to make this better I would make all the user elements configurable and get the script to figure out where to put the points spacing them every or every other and stopping at the end of the body.

    I tried to do a bunch of stuff to get the body the point was on and get the face the point was on allowing the user to just select the point, I should be able to get the body and the face from the point right? But it never worked for me.

    FeatureScript 2559;
    import(path : "onshape/std/common.fs", version : "2559.0");
    import(path : "onshape/std/hole.fs", version : "2559.0");

        var selectedVertices = evaluateQuery(context, definition.points);
        // get the start point based off poitn the user chose
        var startPoint = evVertexPoint(context, { "vertex" : selectedVertices[0] });
        // get the body we want to work on based off the face
        //var body = qOwnerBody(definition.face); // did not work :(
        var body = definition.body;
    
        // get the plane tangent to the face they choose 
        var uv = vector(0.5, 0.5);  // Midpoint in parametric space
        var tangentPlane = evFaceTangentPlane(context, { "face" : definition.face, "parameter" : uv });
    
        var normalVector = tangentPlane.normal;
               
        for (var i = 0; i < size(selectedVertices); i += 1)
        {
            var startPoint = evVertexPoint(context, { "vertex" : selectedVertices[i] });
            //var startPoint = selectedVertices[i];
             opHole(context, id + "hole"+i, {
                "holeDefinition" : holeDefinition([
                        holeProfile(HolePositionReference.AXIS_POINT, 0 * millimeter, counterBoreDiameter / 2),
                        holeProfile(HolePositionReference.AXIS_POINT, counterBoreDepth, counterBoreDiameter / 2),
                        holeProfile(HolePositionReference.AXIS_POINT, counterBoreDepth, holeDiameter / 2),
                        holeProfile(HolePositionReference.AXIS_POINT, totalDepth-counterBoreDepth, holeDiameter / 2),
                        holeProfile(HolePositionReference.AXIS_POINT, totalDepth-counterBoreDepth, counterBoreDiameter / 2),
                        holeProfile(HolePositionReference.AXIS_POINT, totalDepth, counterBoreDiameter / 2),
                        holeProfile(HolePositionReference.AXIS_POINT, totalDepth, 0 * millimeter),
                    ]),
                "axes" : [line(startPoint, -normalVector)],
                "targets" : body,
                "subtractFromTargets" : true,
                "keepTools" : false
            });           
        }
    });
    

  • michael_mccartymichael_mccarty Member Posts: 7

    Adding a bit to this I was able to keep the tool and use it to make a "post" instead of a hole, so you can fit lego pieces. Not perfect but I thought it might be of interest to anyone who finds this discussion.

    FeatureScript 2559;
    import(path : "onshape/std/common.fs", version : "2559.0");
    import(path : "onshape/std/hole.fs", version : "2559.0");

    annotation {
    "Feature Type Name" : "Lego Hole Creator",
    "Feature Type Description" : "Creates lego compatible holes across face"
    }
    export const legoHole = defineFeature(function(context is Context, id is Id, definition is map)
    precondition
    {
    annotation { "Name" : "body", "Filter" : EntityType.BODY }
    definition.body is Query;
    annotation { "Name" : "Face", "Filter" : EntityType.FACE }
    definition.face is Query;
    annotation { "Name" : "Start point", "Filter" : EntityType.VERTEX }
    definition.points is Query;
    }
    {
    // const stuff related to the hole profile (defined by lego)
    const holeDiameter = 5.0 * millimeter; // Hole diameter
    const holeDepth = (7.8-1.6) * millimeter; // Depth of the hole
    const counterBoreDepth = 0.8 * millimeter;
    const counterBoreDiameter = 6.2 * millimeter;
    const totalDepth = 7.8 * millimeter;

        var selectedVertices = evaluateQuery(context, definition.points);
        // get the start point based off poitn the user chose
        var startPoint = evVertexPoint(context, { "vertex" : selectedVertices[0] });
        // get the body we want to work on based off the face
        //var body = qOwnerBody(definition.face); // did not work :(
        var body = definition.body;
    
        // get the plane tangent to the face they choose 
        var uv = vector(0.5, 0.5);  // Midpoint in parametric space
        var tangentPlane = evFaceTangentPlane(context, { "face" : definition.face, "parameter" : uv });
    
        var normalVector = tangentPlane.normal;
               
        for (var i = 0; i < size(selectedVertices); i += 1)
        {
            var startPoint = evVertexPoint(context, { "vertex" : selectedVertices[i] });
            //var startPoint = selectedVertices[i];
             opHole(context, id + "hole"+i, {
                "holeDefinition" : holeDefinition([
                        holeProfile(HolePositionReference.AXIS_POINT, 0 * millimeter, counterBoreDiameter / 2),
                        holeProfile(HolePositionReference.AXIS_POINT, counterBoreDepth, counterBoreDiameter / 2),
                        holeProfile(HolePositionReference.AXIS_POINT, counterBoreDepth, holeDiameter / 2),
                        holeProfile(HolePositionReference.AXIS_POINT, totalDepth-counterBoreDepth, holeDiameter / 2),
                        holeProfile(HolePositionReference.AXIS_POINT, totalDepth-counterBoreDepth, counterBoreDiameter / 2),
                        holeProfile(HolePositionReference.AXIS_POINT, totalDepth, counterBoreDiameter / 2),
                        holeProfile(HolePositionReference.AXIS_POINT, totalDepth, 0 * millimeter),
                    ]),
                "axes" : [line(startPoint, -normalVector)],
                "targets" : body,
                "subtractFromTargets" : true,
                "keepTools" : false
            });           
        }
    });
    

    annotation {
    "Feature Type Name" : "Lego Post Creator",
    "Feature Type Description" : "Creates lego compatible Post across face"
    }
    export const legoPost = defineFeature(function(context is Context, id is Id, definition is map)
    precondition
    {
    annotation { "Name" : "body", "Filter" : EntityType.BODY }
    definition.body is Query;
    annotation { "Name" : "Face", "Filter" : EntityType.FACE }
    definition.face is Query;
    annotation { "Name" : "Start point", "Filter" : EntityType.VERTEX }
    definition.points is Query;
    annotation { "Name" : "Opposite direction", "UIHint" : UIHint.OPPOSITE_DIRECTION }
    definition.doOpposite is boolean;
    }
    {
    // const stuff related to the hole profile (defined by lego)
    const holeDiameter = 5.0 * millimeter; // Hole diameter
    const holeDepth = (7.8-1.6) * millimeter; // Depth of the hole
    const counterBoreDepth = 0.8 * millimeter;
    const counterBoreDiameter = 6.2 * millimeter;
    const totalDepth = (7.8 + 0.25) * millimeter;

        var selectedVertices = evaluateQuery(context, definition.points);
        // get the start point based off poitn the user chose
        var startPoint = evVertexPoint(context, { "vertex" : selectedVertices[0] });
        // get the body we want to work on based off the face
        //var body = qOwnerBody(definition.face); // did not work :(
        var body = definition.body;
    
        // get the plane tangent to the face they choose 
        var uv = vector(0.5, 0.5);  // Midpoint in parametric space
        var tangentPlane = evFaceTangentPlane(context, { "face" : definition.face, "parameter" : uv });
    
        var normalVector = tangentPlane.normal;
        
        // hopefully perfect definition of the lego hole or post 
        var holedef = holeDefinition([
            holeProfile(HolePositionReference.AXIS_POINT, 0 * millimeter, counterBoreDiameter / 2),
            holeProfile(HolePositionReference.AXIS_POINT, counterBoreDepth, counterBoreDiameter / 2),
            holeProfile(HolePositionReference.AXIS_POINT, counterBoreDepth, holeDiameter / 2),
            holeProfile(HolePositionReference.AXIS_POINT, totalDepth-counterBoreDepth, holeDiameter / 2),
            holeProfile(HolePositionReference.AXIS_POINT, totalDepth-counterBoreDepth, counterBoreDiameter / 2),
            holeProfile(HolePositionReference.AXIS_POINT, totalDepth, counterBoreDiameter / 2),
            holeProfile(HolePositionReference.AXIS_POINT, totalDepth, 0 * millimeter),
        ]);
        
        // it has to go in the body a bit or we cant have the tool it will say no_hits
        // Step 6: Define the offset distance (1mm)
        var offsetDistance = 0.01 * millimeter;
        // Step 7: Calculate the new position by moving along the negative normal direction
        var offsetPosition = startPoint - normalVector * offsetDistance;
        
        
        var normalVectorVar = definition.doOpposite ? normalVector : -normalVector;       
        for (var i = 0; i < size(selectedVertices); i += 1)
        {
            var startPoint = evVertexPoint(context, { "vertex" : selectedVertices[i] });
            //var startPoint = selectedVertices[i];
            opHole(context, id + "hole"+i, {
                "holeDefinition" : holedef,
                "axes" : [line(offsetPosition, normalVectorVar)],
                "targets" : body,
                //"subtractFromTargets" : true,
                "keepTools" : true
            }); 
            
            // this does work we can split it, but not using rn
            //var post = qAllNonMeshSolidBodies();
            // var post = qCreatedBy(id + "hole" + i, EntityType.BODY);
            // debug(context, qCreatedBy(id +  "hole" + i, EntityType.BODY));
            // var postBoundingBox = evBox3d(context, { "topology" : post });
            // debug(context, postBoundingBox);
            // debug(context, postBoundingBox.minCorner);
            // var midPoint = (postBoundingBox.minCorner + postBoundingBox.maxCorner) * 0.5;
            // var cuttingPlane = plane(midPoint, vector(1, 0, 0)); // Adjust the normal vector as needed
            // debug(context, cuttingPlane);
            
            // // Step 2: Split the post using the cutting plane
            // opSplitPart(context, id + "splitPost", {
            //     "targets" : post,
            //     "tool" : cuttingPlane
            // });
            
            // splitting and all this does not work
            // var splitBodies = qInBody(opBodyType(BODY_TYPE_SOLID));
            // var bodyToRemove = qFarthestAlong(splitBodies, vector(1, 0, 0)); // Adjust direction as needed
            
            // // Delete the identified body
            // opDeleteBodies(context, id + "deleteHalf", {
            //     "entities": bodyToRemove
            // });
            
            // this does work well to merge the post 
            // opBoolean(context, id + "mergePost", {
            //     "tools": qAllNonMeshSolidBodies(),
            //     "operationType": BooleanOperationType.UNION
            // });
        }
    });
    
Sign In or Register to comment.