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.

New to FeatureScript

sebastian_sombrasebastian_sombra Member Posts: 16
Hi, 
When creating parts for 3d printing sometimes I add a sacrificial layer to avoid using supports. In the image below for instance, I would extrude upwards 0.2mm from the selected edge. The way I normally do it is creating a sketch, projecting the circle to the sketch and extruding it. It could be a circle, a rectangle or any other shape. 

I thought that a featurescript would be useful for that, and that it would be a nice exercise to learn about featurescripts. However, after reading a lot of forum entries and watching some tutorials I just can't find my way to do it. In my mind, the idea was to ask the user to select an edge (in this example the selected circle) and extrude it 0.2mm (or any length). In order to do that I used a query parameter. However, If I use a query parameter with EDGE as Entity type, The result from the extrusion is a surface:

annotation { "Name" : "Figura", "Filter" : EntityType.EDGE, "MaxNumberOfPicks" : 1 }
        definition.myFig is Query;

opExtrude(context, id + "extrude1", {
                    "entities" : definition.myFig,
                    "direction" : face.normal,
                    "endBound" : BoundingType.BLIND,
                    "endDepth" : definition.myCapa 
                });


but If I use FACE as Entity Type, the Extrusion is the ring and not the inner circle:

annotation { "Name" : "Figura", "Filter" : EntityType.FACE, "MaxNumberOfPicks" : 1 }
        definition.myFig is Query;

I also tried to do it in a similar way I use to do it (create a sketch in the face of the part, project the shape and extrude it), but I was not able to find a way to "project" the circle to the sketch in a featurescript. I realize that my knowledge is very limited but I do like to learn, so if someone can point me to an example or where to look at, it would be great. 





Comments

  • Alex_KempenAlex_Kempen Member Posts: 64 EDU
    Hello Sebastian, and welcome to the FeatureScript forum! As you noticed, opExtrude likes to extrude edges as surfaces, and faces as solid bodies. So, you need a way to convert a selection of one or more edges into a face for the region those edges bound. Unfortunately, that's actually quite a difficult task, since queries can only point towards stuff that actually exists, and there is no way to easily project stuff onto sketches in FeatureScript. As such, using a FeatureScript sketch probably isn't very practical here.

    However, there is a potential work around using surfaces; if we first create a group of surfaces which enclose the desired region, we can then use enclose to get the actual desired body. It's fairly complex, but I thought this was a good opportunity to make an example feature for the community, which is something I've been wanting to do more and more lately. So, here's an example of a feature that does what you're trying to do:
    https://cad.onshape.com/documents/4c21d0c3c89c0a81aadfdac6/w/a7ccf556a74ce09cd04151e0/e/9e183a644bcc124310d7dfe3

    I tried my best to explain the rationale behind everything in the code. Hopefully it makes sense, and serves as a springboard for improving your own understanding of FeatureScript. I would encourage you to try adapting it to fit your own needs as you see fit. Good luck!
  • konstantin_shiriazdanovkonstantin_shiriazdanov Member Posts: 1,113 ✭✭✭✭✭
    edited April 7
    Since extrude needs face to create a solid we need to create this face first. There is a standard library function opFillSurface() which creates a planar surface region if closed loop of coplanar edges was passed. Then you get a face of surface created by this operation by qCreatedBy(..., EntityType.FACE) query and pass it to the extrude function. And finally delete unnecessary surface body using opDeleteBodies()
  • Alex_KempenAlex_Kempen Member Posts: 64 EDU
    Konstantin, you're absolutely right. I guess I just forgot that I could simply extrude the face created by opFillSurface, which is a lot easier than making a bunch of surfaces and then using opEnclose. Whoops! I've updated the example feature to use the simpler method. At the very least, it should be easier to understand now.
    https://cad.onshape.com/documents/4c21d0c3c89c0a81aadfdac6/w/a7ccf556a74ce09cd04151e0/e/9e183a644bcc124310d7dfe3

  • NeilCookeNeilCooke Moderator, Onshape Employees Posts: 3,715
    In situations like this I find it useful to model the result manually using the built-in features to work out the most efficient way to automate it. 
    Director, Technical Services, EMEAI
  • sebastian_sombrasebastian_sombra Member Posts: 16
    You guys are great, thank you very much for the explanation and for taking the time to create an example, that is definitely the best way for me to understand how to do it. I will analyze it deeply, as there are things beyond my current understanding. I always thought it would be an easy exercise, but I see it is not :smile:

    One last question:
    The code does work as expected, BUT the extrude operation creates a second part. Ideally, I would like the extrude operation to ADD to the existing part instead to create a new part (assuming that the 2 parts intersect, of course). I mean, I can always go and add the parts together with the boolean feature, but why not incorporating that to the script. So I got into the forums and found this little example using the opBoolean:

    opBoolean(context, id + "boolean1", {
                            "tools" : qUnion([qBodyType(qEverything(EntityType.BODY), BodyType.SOLID)]),
                            "operationType" : BooleanOperationType.UNION
                    });

    which did the job. Is that the best solution?



  • konstantin_shiriazdanovkonstantin_shiriazdanov Member Posts: 1,113 ✭✭✭✭✭
    edited April 7
    The query you passing to boolean would join all the solid bodies, which may be unwanted. This one is more predictable, it joins only the result of extruded body with the owner body of selected edges.

    opBoolean(context, id + "boolean", {
                            "tools" : qUnion(qOwnerBody(definition.edgesQuery), qCreatedBy (id+"extrude")),
                            "operationType" : BooleanOperationType.UNION
                    });
  • Alex_KempenAlex_Kempen Member Posts: 64 EDU
    If you do do an opBoolean, it's important to consider some of the different use cases of your FeatureScript. For example, there may be situations where your edges come from different bodies, or don't have a useable owner body at all (like if they are all from a sketch). There may also be some situations where you want to make a part, but there aren't any adjacent parts to merge to - in that case, skipping the opBoolean operation could be a logical thing to do.

    To go one step further, lots of features, such as the Onshape extrude feature, offer a merge scope and a merge with all option when performing opBoolean operations. This is a good way to prevent, for example, a single feature accidentally joining a couple different parts together into a single part, and to give the user greater control over the feature's performance. You can also use editing logic to automatically select the merge scope when a user first makes their edge selections. Features can get quite advanced, depending on the level of adaptability and utility you want to get out of them - that's what makes writing FeatureScripts so interesting and rewarding. Fortunately, a lot of this stuff is also optional. After all, custom features only have to be as functional as their users desire - the way you choose to do it is ultimately up to you.


  • sebastian_sombrasebastian_sombra Member Posts: 16
    The query you passing to boolean would join all the solid bodies, which may be unwanted. This one is more predictable, it joins only the result of extruded body with the owner body of selected edges.

    opBoolean(context, id + "boolean", {
                            "tools" : qUnion(qOwnerBody(definition.edgesQuery), qCreatedBy (id+"extrude")),
                            "operationType" : BooleanOperationType.UNION
                    });

    Thanks, I will try with this one.

  • sebastian_sombrasebastian_sombra Member Posts: 16
    If you do do an opBoolean, it's important to consider some of the different use cases of your FeatureScript. For example, there may be situations where your edges come from different bodies, or don't have a useable owner body at all (like if they are all from a sketch). There may also be some situations where you want to make a part, but there aren't any adjacent parts to merge to - in that case, skipping the opBoolean operation could be a logical thing to do.

    To go one step further, lots of features, such as the Onshape extrude feature, offer a merge scope and a merge with all option when performing opBoolean operations. This is a good way to prevent, for example, a single feature accidentally joining a couple different parts together into a single part, and to give the user greater control over the feature's performance. You can also use editing logic to automatically select the merge scope when a user first makes their edge selections. Features can get quite advanced, depending on the level of adaptability and utility you want to get out of them - that's what makes writing FeatureScripts so interesting and rewarding. Fortunately, a lot of this stuff is also optional. After all, custom features only have to be as functional as their users desire - the way you choose to do it is ultimately up to you.


    I understand. Things could get very complicated if one tries to think in all possibilities and user needs. For this particular case, I will use it always in the same way, which is to add a layer to an existing part, so there will always be an owner body and the edge will always come from that body. 
  • sebastian_sombrasebastian_sombra Member Posts: 16
    The query you passing to boolean would join all the solid bodies, which may be unwanted. This one is more predictable, it joins only the result of extruded body with the owner body of selected edges.

    opBoolean(context, id + "boolean", {
                            "tools" : qUnion(qOwnerBody(definition.edgesQuery), qCreatedBy (id+"extrude")),
                            "operationType" : BooleanOperationType.UNION
                    });
    When trying this example, I get a regenerate error "invalid input selections". I wrote the following:

    opBoolean(context, id + "boolean1", {
                            "tools" : qUnion(qOwnerBody(definition.edgesQuery), qCreatedBy (id + "extrudePart")), 
                            "operationType" : BooleanOperationType.UNION
                    });

    The only thing I changed was the name of the opExtrude result to extrudePart, following the code.
  • konstantin_shiriazdanovkonstantin_shiriazdanov Member Posts: 1,113 ✭✭✭✭✭
    make sure you passing correct definition field to qOwnerBody(), in your first example you named it "myFig". So if nothing changed you need to use qOwnerBody(definition.myFig)
  • sebastian_sombrasebastian_sombra Member Posts: 16
    make sure you passing correct definition field to qOwnerBody(), in your first example you named it "myFig". So if nothing changed you need to use qOwnerBody(definition.myFig)
    I am using part of the code that Alex_Kempen posted to get the edges:

            annotation { "Name" : "Outer edges", "Filter" : EntityType.EDGE }
            definition.edgesQuery is Query;

    The extrude operation:

            opExtrude(context, id + "extrudePart", {

    so I changed the code you posted to match that: 

    "tools" : qUnion(qOwnerBody(definition.edgesQuery), qCreatedBy (id + "extrudePart")), 


    Bur I get that error 
  • NeilCookeNeilCooke Moderator, Onshape Employees Posts: 3,715
    Add the entity type to qCreatedBy otherwise it will try to Boolean edges and vertices etc. 
    Director, Technical Services, EMEAI
  • Evan_ReeseEvan_Reese Member Posts: 867 PRO
    @sebastian_sombra
    I'm glad to hear you're working on this feature! A Bridge Layers feature like this been on my backlog to write myself. I figure I'll go ahead and share some ideas around the approach since I've already been considering it a bit. Feel free to use what you like and ignore the rest:
    • Automatically find all holes and fill them - Specify a body and a printer Z direction in the UI, then find every parallel planar face, and search it for internal geometry to fill. I'd make this option the default since I probably don't want to leave any holes open, but there could be an enum to pick each face or edge instead.
    • Face to exclude - if it's going to automatically find the faces, you could add a query field for faces to exclude instead of which ones to include.
    • Base Plane- if the thing you pick for the Z direction is a plane, you could check a box to say that it's on the print bed, so it will ignore all co-planar openings.
    I'd be glad to make an icon for you too if you're planning on making the feature public and aren't feeling up to doing it yourself.
    Evan Reese / Principal and Industrial Designer with Ovyl
    Website: ovyl.io
    Instagram: @evan.reese.designs
  • sebastian_sombrasebastian_sombra Member Posts: 16
    NeilCooke said:
    Add the entity type to qCreatedBy otherwise it will try to Boolean edges and vertices etc. 

    That worked! Thanks
  • sebastian_sombrasebastian_sombra Member Posts: 16
    @sebastian_sombra
    I'm glad to hear you're working on this feature! A Bridge Layers feature like this been on my backlog to write myself. I figure I'll go ahead and share some ideas around the approach since I've already been considering it a bit. Feel free to use what you like and ignore the rest:
    • Automatically find all holes and fill them - Specify a body and a printer Z direction in the UI, then find every parallel planar face, and search it for internal geometry to fill. I'd make this option the default since I probably don't want to leave any holes open, but there could be an enum to pick each face or edge instead.
    • Face to exclude - if it's going to automatically find the faces, you could add a query field for faces to exclude instead of which ones to include.
    • Base Plane- if the thing you pick for the Z direction is a plane, you could check a box to say that it's on the print bed, so it will ignore all co-planar openings.
    I'd be glad to make an icon for you too if you're planning on making the feature public and aren't feeling up to doing it yourself.
    Hi Evan.
    Great ideas, in my particular case, I would prefer to select the holes I want to fill. Unfortunately these improvements are way beyond my capabilities :smile:. I started using OnShape to be able to design things for 3D printing RC parts. While my design skills are very limited (I am an economist), my programming skills are even worst :blush:, as you can see by the questions I ask here in the forums. Having said that, I really like to learn new things and I am sure that I will keep trying Featurescripts as new ideas come. 

    Regarding this particular need, the script works so far and I will be more than happy to share it (I don´t know how by the way :)), although most of the code was actually made by others. It is designed to fill one hole at a time, the user selects the hole and a layer is created and added to the main body (0.2mm is the depth default, but it could be changed by the user). I am sure that there are much more things to improve in tjhe code and I hope to have the skills and the time to continue doing it as I learn more. 



  • Evan_ReeseEvan_Reese Member Posts: 867 PRO
    Got it. Guess I'll just have to make my own for the automatic one then B) You've already found out how helpful some of the people are on the forum, so post back anytime if you're stuck on this or any modeling stuff.

    If you care to make it publicly available, click the  blue "Share" button at the top right, and go to the "Public" tab like this, then post a link here.
    Evan Reese / Principal and Industrial Designer with Ovyl
    Website: ovyl.io
    Instagram: @evan.reese.designs
  • sebastian_sombrasebastian_sombra Member Posts: 16
    Got it. Guess I'll just have to make my own for the automatic one then B) You've already found out how helpful some of the people are on the forum, so post back anytime if you're stuck on this or any modeling stuff.

    If you care to make it publicly available, click the  blue "Share" button at the top right, and go to the "Public" tab like this, then post a link here.
    Indeed, very helpful people around. thanks
  • sebastian_sombrasebastian_sombra Member Posts: 16
    My script works well thanks to the support of the members of the forum, I can select the edges of a hole and the script adds a sacrificial layer to that hole. The only catch is that you need to apply the feature one hole at a time, so I want to take a step forward and add the possibility to do more than one at a time (@Evan_Reese is the one to blame here :smiley:).
    For instance in this example:




    However, the way it is designed now makes it difficult as the user has to select all edges of a hole with one query parameter, putting all edges in the same query, making difficult to build paths for more than one hole. So I thought that the solution would be to select faces instead of edges, then looping the query to take one face at a time and getting the edges for that particular face. However, I didn't find a way to take only the inner edges of a face. I read a lot of forum entries without finding a solution. 

    Currently, my workaround is to do a query for all the edges in a face and then use qSmallest to filter the small ones. This workaround only works when all edges of a hole are the same length (i.e. a circle, a square, etc...), which is OKish as I mostly use it for circular holes when designing parts (usually for recessed screws), but I would like it to be more universal. 

    Could someone suggest me a solution to get the inner edges of a face? actually the outer edges will work as well.
  • Evan_ReeseEvan_Reese Member Posts: 867 PRO
    edited April 9
    You'd gotten me thinking more about this feature, so I already started playing around with it last night more like I'd described above. I got as far as picking planar faces and making bridges. It seems pretty snappy too. Here's the trick to my approach; when you sketch on a face, it creates separate faces from the regions of it. That means you can sketch on a face, and not even add any geometry, and you'll get a face for the holes (no need for opFillSurface). See the gif below for an example.


    Granted, this doesn't filter for only the internal hole, so the whole thing will be extruded, but I can't think of a scenario where I care since the bridge layers are so small. There might be a way to query just the hole if that turns out to be a problem. Here's a link to the start of some code. I used forEachEntity which can process more than one face selection. Since the query parameter is filtered for planar faces on geometry (not construction planes) you can just box select them and it won't pick the cylindrical hole faces inside.

    Evan Reese / Principal and Industrial Designer with Ovyl
    Website: ovyl.io
    Instagram: @evan.reese.designs
  • sebastian_sombrasebastian_sombra Member Posts: 16
    You'd gotten me thinking more about this feature, so I already started playing around with it last night more like I'd described above. I got as far as picking planar faces and making bridges. It seems pretty snappy too. Here's the trick to my approach; when you sketch on a face, it creates separate faces from the regions of it. That means you can sketch on a face, and not even add any geometry, and you'll get a face for the holes (no need for opFillSurface). See the gif below for an example.


    Granted, this doesn't filter for only the internal hole, so the whole thing will be extruded, but I can't think of a scenario where I care since the bridge layers are so small. There might be a way to query just the hole if that turns out to be a problem. Here's a link to the start of some code. I used forEachEntity which can process more than one face selection. Since the query parameter is filtered for planar faces on geometry (not construction planes) you can just box select them and it won't pick the cylindrical hole faces inside.


    Thanks for this example, I will change my script with your approach. Just a quick note, while I can use a box select with most faces, there are some instances where the box select picks edges and the script then throws an error. This one works:


    But this one doesn't, and both holes are on the same part:



    I, however, can select that irregular part by clicking directly on the face.

  • Evan_ReeseEvan_Reese Member Posts: 867 PRO
    yep, that's what I'd expect since it's just filtering for planar faces, and the sides of this one are all planar too. I think you'll have to just click each one you want in that case, but this is also part of my reason for suggesting that the best way is to just pick a body and a direction and let featurescript find the planar faces that are perpendicular to that direction. That could knock this whole shape out in two clicks. You could also check how many faces your sketch makes. If it only makes one, you know there are no holes, and you don't have to do the extrude step. That might also prevent the error above, since it wouldn't try extruding of booleaning, which I bet is what's failing.
    Evan Reese / Principal and Industrial Designer with Ovyl
    Website: ovyl.io
    Instagram: @evan.reese.designs
  • Evan_ReeseEvan_Reese Member Posts: 867 PRO
    edited April 12
    I've been working on one take of this feature. Here's a link.

    I feel ambivalent about posting this here since this feature is your project-based-learning project, and I don't want to ruin it by posting a solution, but, on the other hand, I've also been wanting to make a Bridge Layer feature for a year or two, and you might find it really useful for learning and actual modeling.

    Here's the most distilled logic I could get to:
    • If you're using this feature, that means you don't plan to print with support, which means there's a face you plan to print directly on the build platform.
    • The face on the platform can (1) identify the body you're printing, and (2) define the Z direction of the print.
    • Once I have a body to work on and a Z direction I can find faces whose normals point in the -Z direction, and select only those.
    • From there I do what I've shown above, which is create an "empty" sketch on those faces which will contain the face itself (sketch regions are "faces"), but also faces for the holes.
    • Next, I count the sketch faces. If it's >2 I know there's no hole and I skip it.
    • Last, I extrude it, and boolean it to the body.
    The great thing about this logic is that it's just one click.

    One case this logic doesn't handle right is something like this, where the hole isn't totally enclosed inside the sketch face. I don't think I've ever had this case in my life, so I think I'm okay with it. Let me know if you run into this one.



    I hope this is helpful!
    Evan Reese / Principal and Industrial Designer with Ovyl
    Website: ovyl.io
    Instagram: @evan.reese.designs
  • sebastian_sombrasebastian_sombra Member Posts: 16
    I've been working on one take of this feature. Here's a link.

    I feel ambivalent about posting this here since this feature is your project-based-learning project, and I don't want to ruin it by posting a solution, but, on the other hand, I've also been wanting to make a Bridge Layer feature for a year or two, and you might find it really useful for learning and actual modeling.

    Here's the most distilled logic I could get to:
    • If you're using this feature, that means you don't plan to print with support, which means there's a face you plan to print directly on the build platform.
    • The face on the platform can (1) identify the body you're printing, and (2) define the Z direction of the print.
    • Once I have a body to work on and a Z direction I can find faces whose normals point in the -Z direction, and select only those.
    • From there I do what I've shown above, which is create an "empty" sketch on those faces which will contain the face itself (sketch regions are "faces"), but also faces for the holes.
    • Next, I count the sketch faces. If it's >2 I know there's no hole and I skip it.
    • Last, I extrude it, and boolean it to the body.
    The great thing about this logic is that it's just one click.

    One case this logic doesn't handle right is something like this, where the hole isn't totally enclosed inside the sketch face. I don't think I've ever had this case in my life, so I think I'm okay with it. Let me know if you run into this one.



    I hope this is helpful!
    Indeed, looking into functional examples is a great way to learn, most of my learning process has been like that and a lot of trial and error. I will check it tomorrow in detail, thanks for sharing it. 
  • sebastian_sombrasebastian_sombra Member Posts: 16
    edited April 12


  • sebastian_sombrasebastian_sombra Member Posts: 16
    I've been working on one take of this feature. Here's a link.

    I feel ambivalent about posting this here since this feature is your project-based-learning project, and I don't want to ruin it by posting a solution, but, on the other hand, I've also been wanting to make a Bridge Layer feature for a year or two, and you might find it really useful for learning and actual modeling.

    Here's the most distilled logic I could get to:
    • If you're using this feature, that means you don't plan to print with support, which means there's a face you plan to print directly on the build platform.
    • The face on the platform can (1) identify the body you're printing, and (2) define the Z direction of the print.
    • Once I have a body to work on and a Z direction I can find faces whose normals point in the -Z direction, and select only those.
    • From there I do what I've shown above, which is create an "empty" sketch on those faces which will contain the face itself (sketch regions are "faces"), but also faces for the holes.
    • Next, I count the sketch faces. If it's >2 I know there's no hole and I skip it.
    • Last, I extrude it, and boolean it to the body.

    @Evan_Reese, I been playing around with your script and it works great, it's very useful and it helped me understand some new things like the way to add options to the UI and react depending on the user choice, or working with substractions, unions, etc. 
    I have a question, which is the correct way to add a Custom Feature created by another user to our collection? should I search in the add custom features and add it from there (if I find it...)? make a copy of the document?
    Thanks again for taking the time to write and share this code.


  • Evan_ReeseEvan_Reese Member Posts: 867 PRO
    I'm glad it's helpful! here's the documentation for how to add features, but the short answer is use the "+ Add custom features" button and don't make a copy.
    Evan Reese / Principal and Industrial Designer with Ovyl
    Website: ovyl.io
    Instagram: @evan.reese.designs
Sign In or Register to comment.