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.

Can someone make / Teach me how to make this simple script?

john_mcclaryjohn_mcclary Member, Developers Posts: 2,059 PRO
I HATE the FS "tutorial" and "documentation". Tutorial is too specific to one feature script. and the documentation has no example snippets to learn from.

I cant even create a sketch plane. All I've managed to do is add a hole in my closet door...
Can someone show me how to accomplish this? OR just do it for me and I'll try and learn from it?

I use slots all the time and drawing them is very tedious. I've been trying to make this simple thing since I first heard of FS.
I just keep going backwards.

I just want to select a direction, and points, then a slot will be drawn at every point with the same direction.
It should behave like hole feature.

Crap, looks like the i forgot to show an end type (Through all, blind, etc)


Best Answers

  • Jake_RosenfeldJake_Rosenfeld Posts: 1,398
    Accepted Answer
    Hi @john_mcclary

    Sorry you're having trouble! More FeatureScript training is definitely on our radar, and I hope I can help you as best I can.  I will lay out the way I would approach this feature in this post.  Depending on how you like to learn, you may want to follow along and build your own feature from scratch, but if you prefer working top-down instead of bottom-up, here is the final feature I prototyped to do this:

    https://cad.onshape.com/documents/36755892d3f4c46241ca1efc/w/957d604a6b3a05fa3667eab7/e/f07fbad5ce8ae248b5cfc210

    That feature is basically what I'm about to lay out here, but it has a little bit nicer error handling than the raw examples I'm going to give.  Remember that as you build your own features, the debug and println calls can be extremely useful to debugging exactly what's going on in your feature.
    https://cad.onshape.com/FsDoc/library.html#module-debug.fs
    https://cad.onshape.com/FsDoc/library.html#println-

    Debug can be used to highlight geometric entities in red, and println prints to the console (accesible by pressing the `{✔}` at the top of the page).

    Let's jump in:

    1. Sketch the profile for the first slot.  Using the owner sketch plane of the first selected point, and the direction of the direction entity, it is easy to set up a sketch plane where sketching the first slot profile is fairly trivial.

    const slotPoint = qNthElement(definition.slotPoints, 0);
    
    const slotPointPlane = evOwnerSketchPlane(context, { "entity" : slotPoint });
    const slotPointLoc = evVertexPoint(context, { "vertex" : slotPoint });
    
    const direction = extractDirection(context, directionEntity);
    
    // set up sketchPlane such that the origin sits on the slot point, "up"
    // is the normal of the sketch plane of the slot point, and "right" is
    // the direction chosen by the user.
    const sketchPlane = plane(slotPointLoc, slotPointPlane.normal, slotDirection);
    
    const slotSketch = newSketchOnPlane(context, id + "slotSketch", { "sketchPlane" : sketchPlane });
    // Because of the way we've set up sketchPlane, the sketching is easy. The "X" direction
    // of this sketch is along the slot direction.  The "Y" direction is perpendicular to
    // the slot direction.
    skRectangle(slotSketch, "rectangle1", {
                "firstCorner" : vector(-definition.length / 2, -definition.diameter / 2),
                "secondCorner" : vector(definition.length / 2, definition.diameter / 2)
            });
    skCircle(slotSketch, "circle1", {
                "center" : vector(-definition.length / 2, 0 * inch),
                "radius" : definition.diameter / 2
            });
    skCircle(slotSketch, "circle2", {
                "center" : vector(definition.length / 2, 0 * inch),
                "radius" : definition.diameter / 2
            });
    skSolve(slotSketch);

    2. Extrude the profile "through all" as a new part (or with some other depth as desired).

    opExtrude(context, id + "extrude", {
            "entities" : qCreatedBy(id + "slotSketch", EntityType.FACE),
            "direction" : slotPointPlane.normal,
            "endBound" : BoundingType.THROUGH_ALL,
            "startBound" : BoundingType.THROUGH_ALL
    });

    3. Pattern the solid body you've created to all the other sketch point locations.

    var i = 0;
    var patternTransforms = [];
    var instanceNames = [];
    const slotPointsExculdingFirst = qSubtraction(definition.slotPoints, slotPoint);
    for (var patternSlotPoint in evaluateQuery(context, slotPointsExculdingFirst))
    {
        const patternSlotPointLoc = evVertexPoint(context, { "vertex" : patternSlotPoint });
        // Translate the body from it's current position to the patternSlotPointLoc position
        const transform = transform(patternSlotPointLoc - slotPointLoc);
        
        patternTransforms = append(patternTransforms, transform);
        instanceNames = append(instanceNames, "" ~ i);
        i += 1;
    }
    
    if (i != 0)
    {
        opPattern(context, id + "pattern", {
                "entities" : qCreatedBy(id + "extrude", EntityType.BODY),
                "transforms" : patternTransforms,
                "instanceNames" : instanceNames
        });
    }

    4. Subtract all the solid bodies you've created from the selected merge scope.

    const tools = qUnion([qCreatedBy(id + "extrude", EntityType.BODY), qCreatedBy(id + "pattern", EntityType.BODY)]);
    opBoolean(context, id + "boolean1", {
                "operationType" : BooleanOperationType.SUBTRACTION,
                "tools" : tools,
                "targets" : definition.booleanScope
            });

    5. Clean up any left behind tools.  In this case, the opBoolean deletes all of the solid tools we've created, but we do have to remember to clean up the sketch we made in the first step.

    opDeleteBodies(context, id + "deleteBodies", {
            "entities" : qCreatedBy(id + "slotSketch", EntityType.BODY)
    });
    Hope this helps!  I'm sure I've probably left you with more questions than answers, feel free to ask any question you have about this approach.
    Jake Rosenfeld - Modeling Team

Answers

  • Jake_RosenfeldJake_Rosenfeld Moderator, Onshape Employees, Developers Posts: 1,398
    Accepted Answer
    Hi @john_mcclary

    Sorry you're having trouble! More FeatureScript training is definitely on our radar, and I hope I can help you as best I can.  I will lay out the way I would approach this feature in this post.  Depending on how you like to learn, you may want to follow along and build your own feature from scratch, but if you prefer working top-down instead of bottom-up, here is the final feature I prototyped to do this:

    https://cad.onshape.com/documents/36755892d3f4c46241ca1efc/w/957d604a6b3a05fa3667eab7/e/f07fbad5ce8ae248b5cfc210

    That feature is basically what I'm about to lay out here, but it has a little bit nicer error handling than the raw examples I'm going to give.  Remember that as you build your own features, the debug and println calls can be extremely useful to debugging exactly what's going on in your feature.
    https://cad.onshape.com/FsDoc/library.html#module-debug.fs
    https://cad.onshape.com/FsDoc/library.html#println-

    Debug can be used to highlight geometric entities in red, and println prints to the console (accesible by pressing the `{✔}` at the top of the page).

    Let's jump in:

    1. Sketch the profile for the first slot.  Using the owner sketch plane of the first selected point, and the direction of the direction entity, it is easy to set up a sketch plane where sketching the first slot profile is fairly trivial.

    const slotPoint = qNthElement(definition.slotPoints, 0);
    
    const slotPointPlane = evOwnerSketchPlane(context, { "entity" : slotPoint });
    const slotPointLoc = evVertexPoint(context, { "vertex" : slotPoint });
    
    const direction = extractDirection(context, directionEntity);
    
    // set up sketchPlane such that the origin sits on the slot point, "up"
    // is the normal of the sketch plane of the slot point, and "right" is
    // the direction chosen by the user.
    const sketchPlane = plane(slotPointLoc, slotPointPlane.normal, slotDirection);
    
    const slotSketch = newSketchOnPlane(context, id + "slotSketch", { "sketchPlane" : sketchPlane });
    // Because of the way we've set up sketchPlane, the sketching is easy. The "X" direction
    // of this sketch is along the slot direction.  The "Y" direction is perpendicular to
    // the slot direction.
    skRectangle(slotSketch, "rectangle1", {
                "firstCorner" : vector(-definition.length / 2, -definition.diameter / 2),
                "secondCorner" : vector(definition.length / 2, definition.diameter / 2)
            });
    skCircle(slotSketch, "circle1", {
                "center" : vector(-definition.length / 2, 0 * inch),
                "radius" : definition.diameter / 2
            });
    skCircle(slotSketch, "circle2", {
                "center" : vector(definition.length / 2, 0 * inch),
                "radius" : definition.diameter / 2
            });
    skSolve(slotSketch);

    2. Extrude the profile "through all" as a new part (or with some other depth as desired).

    opExtrude(context, id + "extrude", {
            "entities" : qCreatedBy(id + "slotSketch", EntityType.FACE),
            "direction" : slotPointPlane.normal,
            "endBound" : BoundingType.THROUGH_ALL,
            "startBound" : BoundingType.THROUGH_ALL
    });

    3. Pattern the solid body you've created to all the other sketch point locations.

    var i = 0;
    var patternTransforms = [];
    var instanceNames = [];
    const slotPointsExculdingFirst = qSubtraction(definition.slotPoints, slotPoint);
    for (var patternSlotPoint in evaluateQuery(context, slotPointsExculdingFirst))
    {
        const patternSlotPointLoc = evVertexPoint(context, { "vertex" : patternSlotPoint });
        // Translate the body from it's current position to the patternSlotPointLoc position
        const transform = transform(patternSlotPointLoc - slotPointLoc);
        
        patternTransforms = append(patternTransforms, transform);
        instanceNames = append(instanceNames, "" ~ i);
        i += 1;
    }
    
    if (i != 0)
    {
        opPattern(context, id + "pattern", {
                "entities" : qCreatedBy(id + "extrude", EntityType.BODY),
                "transforms" : patternTransforms,
                "instanceNames" : instanceNames
        });
    }

    4. Subtract all the solid bodies you've created from the selected merge scope.

    const tools = qUnion([qCreatedBy(id + "extrude", EntityType.BODY), qCreatedBy(id + "pattern", EntityType.BODY)]);
    opBoolean(context, id + "boolean1", {
                "operationType" : BooleanOperationType.SUBTRACTION,
                "tools" : tools,
                "targets" : definition.booleanScope
            });

    5. Clean up any left behind tools.  In this case, the opBoolean deletes all of the solid tools we've created, but we do have to remember to clean up the sketch we made in the first step.

    opDeleteBodies(context, id + "deleteBodies", {
            "entities" : qCreatedBy(id + "slotSketch", EntityType.BODY)
    });
    Hope this helps!  I'm sure I've probably left you with more questions than answers, feel free to ask any question you have about this approach.
    Jake Rosenfeld - Modeling Team
  • 3dcad3dcad Member, OS Professional, Mentor Posts: 2,399 PRO
    edited May 2018
    I too have been struggling to really understand FS, usually when I think I have figured something out and ask for some help in finishing - my 20 lines revert to 200 lines of something I couldn't ever figure out on my own..

    //rami
  • owen_sparksowen_sparks Member, Developers Posts: 2,422 PRO
    Nice work gents.

    A suggestion if I may?

    If you were to add a couple of extra parameters (offsets / hole spacing) then could you select the corner vertex of the part and an edge and not need the initial layout sketch at all?

    Owen S
    Production Engineer
    HWM-Water Ltd
  • john_mcclaryjohn_mcclary Member, Developers Posts: 2,059 PRO
    edited May 2018
    @Jake_Rosenfeld thanks! That is exactly what I was looking for, it looks like I was on the right track as far as strategy, except I wasn't thinking about transform / boolean subtract. I was gonna sketch everything in a loop and extrude cut at the end... I see why this way is much better.

    The biggest problem I have been having is not understanding the code, It makes sense when I read it.
    But I just can't figure out WHEN to use certain functions, that's where the tutorials and documentation is falling short IMHO.




    @emagdalenaC2C
    I remember that one now, I completely forgot about that!

    @owen_sparks
    Well I don't always want a perfect pattern. otherwise I would just pattern the slots in the part studio. But this way I can select points and place slots at any given point.

    I'm sure I will need to append some more parameters to this, to get it 100% but now that there is a working start point, I hope I can get through the rest.
    Thanks all
  • john_mcclaryjohn_mcclary Member, Developers Posts: 2,059 PRO
    edited May 2018
    @john_mcclary
    If you use booleanBodies to subtract it, it will be compatible with sheet metal as well (if you haven't already done so 
    @mbartlett21

    How can I do that?
    Again the documentation is pretty much useless... No example code, apparently it does not just replace opBoolean...


    Here is the section of code I believe it would go:

    const tools = qUnion([qCreatedBy(id + "extrude", EntityType.BODY), qCreatedBy(id + "pattern", EntityType.BODY)]); opBoolean(context, id + "boolean1", { "operationType" : BooleanOperationType.SUBTRACTION, "tools" : tools, "targets" : definition.scope });

    My first guess was to just replace opBoolean with BooleanBodies, but it says "Function BooleanBodies with (3) arguments not found."
    Even though the definition out of the documentation and the code snippet both show a definition with 3 arguments.




    EDIT: 

    OMG! I typed it out 3 times and got the same error, even hit enter to autocomplete, but of course I tried one more time after I posted, and found I had the B capitalized... <smacks head>

    It's working now.


    Question to onshape: Why is there even a booleanbodies and an opBoolean? Why doesn't opBoolean also boolean sheet-metal? Why did you not use the same naming convention prefix "op"?
  • john_mcclaryjohn_mcclary Member, Developers Posts: 2,059 PRO
    @paul_chastell
    So opBoolean should be used to reduce processing time, as it has fewer error proofing to muddle through?

    But to be honest, all features should be compatible with sheet-metal, so there is still that...
  • paul_chastellpaul_chastell Onshape Employees Posts: 116
    Yes, in general I would advise using operations instead of features where possible. Of course, as you've noted, it isn't possible in the sheet metal case. Sheet metal is interesting because it has two different end results, bent and not. Effectively every change made to sheet metal isn't made to the solid part that you see, it is made to an underlying sheet body that is then thickened in two different ways to make the bent and unbent sheetmetal bodies. When we make them available, features applied to the flattened sheet metal will also result in changes that produce different bent and unbent results. Unfortunately the flip side of working in this way means that features don't automatically work with sheet metal.
    Paul Chastell / VP of R&D / Onshape Inc.
Sign In or Register to comment.