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.

Help getting started with FS?

Hey everyone, I was wondering if I could get some help in writing my first FS.

I did the tutorial, and I watched @NeilCooke s webinar which was actually really helpful in getting my head somewhat around the context of programming FS.

For some background on me: I'm not a programmer but I am very well versed in modeling stuff in Onshape. I'd like to learn to automate a lot of the tedious tasks I do in Onshape. A lot of this stuff is furniture/CNC joinery.

@Aaron_Hoover had written this for me: https://cad.onshape.com/documents/3926df9f5f87e416bc920f0b/w/cac04c63becd1cb085f1f257/e/35e2df86eaf9a664427633a8 a while back. It's for these furniture connectors I use a lot. I wanted to tweak the code a little, and I realized that rebuilding it might be a really good opportunity to learn how to make a FS. Just hacking apart that code seems like not the best way to learn..

Here's what I understand:

I can build a UI, so I can ask the user for the relevant inputs (edges, faces, bodies, etc)

https://cad.onshape.com/documents/c8b9ff43446d2e7e88132990/w/407c5bd44194c6da3616a4d2/e/a66b1ccb7069351a04ea59a1

After this I essentially need to take that data and automate the tasks I need to do, which really just amounts to drilling a hole in each panel.

From what I can see I need to somehow use the user selected edge to query a face, and then somehow locate and drill a hole on that face. Then, query a face on the adjacent part and drill a hole there.

Assuming that my plan of attack is right, this is what I was trying (and failing) to do:

Query a face that's attached (or "near"?) that user selected line, and then use a debug command to confirm that I in fact had the right face selected.

I'm just not clear how to query a face like this... Again I lack a basic understanding of this sort of coding... Can anyone here guide me on this first step?


Comments

  • Jake_RosenfeldJake_Rosenfeld Moderator, Onshape Employees, Developers Posts: 1,646
    edited February 2020
    Hi @eric_schimelpfenig !

    First, let's talk about the qAdjacent query:
    https://cad.onshape.com/FsDoc/library.html#qAdjacent-Query-AdjacencyType-EntityType

    The concept of adjacency we are using is "topological adjacency", so rather than adjacency meaning a face is physically close to another face in the world, it means that a face is attached to another face by an edge or a vertex. An edge is adjacent to a face if it is one of the edges bounding that face, or if it shares a vertex with an edge bordering that face. So if the faces you are looking to find are on two different parts, they will never be adjacent, because they do not share edges or vertices.

    This will be a useful tool, but I don't think its the first step you want to take.

    I am going to outline how I would do what you are asking to do, but it seems like you're interested in the programming aspect, so I'm going to leave out the code snippets.  Let me know if you would like more tangible examples and I will post those too!

    Looking at Aaron's doc, I think there are two distinct cases you will need to be handling:

    In the Left Case, the selected edge does not have a corresponding edge on the other body, in the Right Case, there are two overlapping edges, and it is basically random whether you are going to be selecting the one from the blue body or the grey body. So, given the edge selection, and these two possible cases, what is the goal?
    • Find the Major Face (I'm sure theres a better term for it, but this is what I'm going to call the face that gets the big hole drilled into it)
    • Find the Minor Face (Again, stupid name, but its the face that gets the small hole)
    So, where's a good place to start?  Lets find all the faces in play at the joint.  Since they are not on the same body, we cannot rely on topological connections, so we're going to have to rely on physical location. We can start by finding a point shared by all the faces: the midpoint of the selected edge.  Using evEdgeTangentLine with a "parameter" of 0.5 will get us that point:
    https://cad.onshape.com/FsDoc/library.html#evEdgeTangentLine-Context-map

    Now we have a point, and we want to find all the planar faces touching it.  The user has not given us any explicit bodies that are supposed to be cut into, so we'll just have to look globally using qEverything.  This is somewhat problematic because its possible that a part studio might contain unrelated geometry passing through this joint that will be picked up accidentally, but for the purpose of a custom feature, we can not worry about it and make the assumption that the part studio is formatted nicely for us.  So, the query for all the faces touching that point is:
    qContainsPoint(qGeometery(qEverything(EntityType.FACE), GeometryType.PLANE), edgeMidpoint)
    https://cad.onshape.com/FsDoc/library.html#qEverything-EntityType
    https://cad.onshape.com/FsDoc/library.html#qGeometry-Query-GeometryType
    https://cad.onshape.com/FsDoc/library.html#qGeometry-Query-GeometryType

    Now we have all planar faces in the part studio that touch the edge midpoint.  If we call `evaluateQuery` on the above query, we will have them each individually in an array.  Here we can notice a difference between the Left Case and the Right Case by noticing the Left case will have found three faces and the Right case will have found 4.  It's not going to make a difference to the algorithm going forward, but it's good to make sure we are covering all the cases as we go along.
    https://cad.onshape.com/FsDoc/library.html#evaluateQuery-Context-Query

    Next, to start narrowing down our faces, lets find the two faces that abut each other. We can find the Plane (the mathematical object describing the actual plane the face represents) of each face by calling evPlane on all of the faces.
    https://cad.onshape.com/FsDoc/library.html#evPlane-Context-map

    Then, look through the faces and find the two that are opposites:
    planesAreOpposed = dot(planeA.normal, planeB.normal) < (-1 + TOLERANCE.zeroAngle)

    Once you find those two opposing faces, the Minor Face is the larger of the two. qLargest will query for this.
    https://cad.onshape.com/FsDoc/library.html#qLargest-Query



    Now, this last one is going to take a bit of a leap, but to get the Major face, I would do the following:
    // Things we have from earlier
    facesContainingEdgeMidpoint // The 3 or 4 edges which contain the edge midpoint
    opposedFaces // The two faces that are opposing each other
    minorFace // The larger of the opposed faces
    // Find the major face
    const smallerOpposedFace = qSubtraction(opposedFaces, minorFaces);
    const candidateMajorFaces = qAdjacent(smallerOpposedFace, AdjacencyType.EDGE, EntityType.FACE);
    const majorFace = qIntersection([candidateMajorFaces, facesContainingEdgeMidpoint]);
    smallerOpposedFace is somewhat self explanatory. Its just the opposed face that we didn't pick as the minor face
    https://cad.onshape.com/FsDoc/library.html#qSubtraction-Query-Query
    candidateMajor faces is all of the faces surrounding the smallerOpposedFace.  AdjacencyType.EDGE means we want all neighbor faces that we can reach by hopping over an edge.  The excludes neighbor faces that we can reach by hopping over a vertex, but not by hopping over an edge.  EntityType.FACE means we are asking for faces.
    https://cad.onshape.com/FsDoc/library.html#qAdjacent-Query-AdjacencyType-EntityType
    Finally, we intersect two sets.  The 3 or 4 faces we found at the beginning, and all of the faces neighboring the smaller opposed face.  These sets share one common face, the Major Face.
    https://cad.onshape.com/FsDoc/library.html#qIntersection-array



    Feel free to ask more questions, and let me know if you want this written as a block of code.  Hope it helps!

    Jake Rosenfeld - Modeling Team
  • eric_schimelpfenigeric_schimelpfenig Member Posts: 75 EDU
    Ok, first of all THANK YOU for this incredibly detailed response. I really appreciate it!!

    I read it over a few times and I hacked around with some code and I think I got it ... a little... But I have questions.
    Looking at Aaron's doc, I think there are two distinct cases you will need to be handling:

    In the Left Case, the selected edge does not have a corresponding edge on the other body, in the Right Case, there are two overlapping edges, and it is basically random whether you are going to be selecting the one from the blue body or the grey body. So, given the edge selection, and these two possible cases, what is the goal?
    • Find the Major Face (I'm sure theres a better term for it, but this is what I'm going to call the face that gets the big hole drilled into it)
    • Find the Minor Face (Again, stupid name, but its the face that gets the small hole)
    You definitely got the use cases right, and your terminology works. I had actually thought a better approach would be to ask a bit more from the user, perhaps have them click on the "major" face, "minor" face, and a vertical line... More on that in a moment...



    So, where's a good place to start?  Lets find all the faces in play at the joint.  Since they are not on the same body, we cannot rely on topological connections, so we're going to have to rely on physical location. We can start by finding a point shared by all the faces: the midpoint of the selected edge.  Using evEdgeTangentLine with a "parameter" of 0.5 will get us that point:
    https://cad.onshape.com/FsDoc/library.html#evEdgeTangentLine-Context-map

    Ok, it took me a few tries but I think I got this part worked out:

    https://cad.onshape.com/documents/c8b9ff43446d2e7e88132990/v/1305c7363ae4f65a960fad03/e/a66b1ccb7069351a04ea59a1



    I was able to find that line using the debug command (I changed it to .5 later)

    You say: 

    "Now we have a point, and we want to find all the planar faces touching it.  The user has not given us any explicit bodies that are supposed to be cut into, so we'll just have to look globally using qEverything.  This is somewhat problematic because its possible that a part studio might contain unrelated geometry passing through this joint that will be picked up accidentally, but for the purpose of a custom feature, we can not worry about it and make the assumption that the part studio is formatted nicely for us.  So, the query for all the faces touching that point is:"

    Referencing my earlier comment to me looking at "everything" seems hard/potentially problematic? Perhaps it makes more sense just to have the user identify the faces for us? I feel like two clicks isn't too much to ask :)

    But, going along with what you said I did try that next command and I guess I got it to work (insofar as it didn't throw an error) but I'm not sure what I did...

    https://cad.onshape.com/documents/c8b9ff43446d2e7e88132990/v/66c7be0d1ea942d447bb1bba/e/a66b1ccb7069351a04ea59a1

    This is the command I built: qEverything(qContainsPoint(qGeometry(qEverything(EntityType.FACE), GeometryType.PLANE), superedge).VERTEX);

    So if I'm reading this right, is this saying "Look at everything, then find stuff that contains a point, then something that's geometry? then everything that's a face, then everything that's a plane (face?) that's touching "superedge" (that's the line that the user selects) 

    If my vague understanding is correct, should have have returned those two "major" and "minor" faces that are touching that line? Or since you say 4 faces, are you talking about the top and bottom faces too? I'm not totally sure how you're getting to four...

    If I'm right on that, it looks like you want to use the area of each face to determine which is the larger, and smaller one? While in this particular model that would work (again if I'm understanding the logic here) but in the "real world" the larger hole doesn't always go on the larger face...

    I'm wondering if this would make a lot more sense and be easier to code:

    https://cad.onshape.com/documents/c8b9ff43446d2e7e88132990/v/1d0d4c6d23c11754ba1e77ca/e/a66b1ccb7069351a04ea59a1

    Ask the user to identify the face for the large hole (turn lock thinger) and the stud side. I'm also asking for the edge too so the holes can start at the center of the edge they select ( at some point I'd like for the user to be able to use sketch points to place these connectors at explicit places, but I'm trying to keep it simple to start with)

    Again, I cannot thank you enough for helping me on these first few steps here!









  • owen_sparksowen_sparks Member, Developers Posts: 2,660 PRO
    Hi guys.  Awesome thread :+1:
    I'm not sure if this is an international term but over here those fixings are called Cam (turn lock thinger)) and Dowel (stud) fixings.  If you were to share this feature once you're done with it I feel you'd get more interest if you named it with familiar terms.
    Please forgive the pedantry, getting it to work as you wish is obviously more important. :p
    Happy coding,
    Owen S.

    Business Systems and Configuration Controller
    HWM-Water Ltd
  • eric_schimelpfenigeric_schimelpfenig Member Posts: 75 EDU
    You're totally right @owen_sparks.

    It's actually this system: https://www.hafele.com/us/en/product/connector-housing-rafix-tab-20-system/0000001700027b3a00060023/

    Once I get it working I'll get it over to the marketing folks to give it some real branding :):)

  • Jake_RosenfeldJake_Rosenfeld Moderator, Onshape Employees, Developers Posts: 1,646
    Hi again @eric_schimelpfenig ! Some great questions here, let me see if I can answer them in order: 

    1.

    Referencing my earlier comment to me looking at "everything" seems hard/potentially problematic? Perhaps it makes more sense just to have the user identify the faces for us? I feel like two clicks isn't too much to ask

    [...]

    I'm wondering if this would make a lot more sense and be easier to code:

    https://cad.onshape.com/documents/c8b9ff43446d2e7e88132990/v/1d0d4c6d23c11754ba1e77ca/e/a66b1ccb7069351a04ea59a1

    Ask the user to identify the face for the large hole (turn lock thinger) and the stud side. I'm also asking for the edge too so the holes can start at the center of the edge they select ( at some point I'd like for the user to be able to use sketch points to place these connectors at explicit places, but I'm trying to keep it simple to start with)

    I definitely agree here that it makes it much clearer for both the programmer and the user to have to make full selection of what is being cut.  One of the values, though, of Aaron's feature is that you can make as many edge selections as you want, and a joint will be built at each of them.  You can get the best of both worlds using an array parameter to get multiple sets of edge-face-face selections, if you want.  I can provide an example of a precondition like that if you'd like.  There's also a button for it in the feature studio:


    2.

    But, going along with what you said I did try that next command and I guess I got it to work (insofar as it didn't throw an error) but I'm not sure what I did...

    https://cad.onshape.com/documents/c8b9ff43446d2e7e88132990/v/66c7be0d1ea942d447bb1bba/e/a66b1ccb7069351a04ea59a1

    This is the command I built: qEverything(qContainsPoint(qGeometry(qEverything(EntityType.FACE), GeometryType.PLANE), superedge).VERTEX);

    So if I'm reading this right, is this saying "Look at everything, then find stuff that contains a point, then something that's geometry? then everything that's a face, then everything that's a plane (face?) that's touching "superedge" (that's the line that the user selects) 

    First thing to notice here is that this code actually is throwing an error:

    The feature is red, when you highlight it it says "error regenerating" in the tooltip.  You can click on the "FeatureScript console" button in the upper right corner (the {!} button) to reveal the featurescript console, which will reveal that the error is that qContainsPoint wants a Vector as its second parameter, but it is receiving a Line instead. The reason your debug is showing up is that it happens before you get to this failure in the code.

    Before we get into fixing that, lets discuss queries a bit more.  They can be a bit daunting at first, but they really just act as normal function calls. Let me clarify what this query does:
    qContainsPoint(qGeometery(qEverything(EntityType.FACE), GeometryType.PLANE), edgeMidpoint)
    You have to read a query like this from the inside, out.  Just like if I had a function call that said:
    println(addTwoToTheNumber(findTheNumber()));
    First, findTheNumber() is going to be run, and return a number.  Then addTwoToTheNumber(...) is going to be run, taking the return value of of findTheNumber() as its input, and returning (if it keeps its promise), two more than that number.  Finally println(...) will be run to print it out.  

    In the same way, the queries are going to be run from the inside out, with each filtering down on what we are looking for.  To make it clear though, the one-liner above is just shorthand for:
    const allFacesInPartStudio = qEverything(EntityType.FACE); // The core of the query.  The seed that we are going to filter down on to find what we want
    const allPlanarFacesInPartStudio = qGeometry(allFacesInPartStudio, GeometryType.PLANE); // This is a filter.  It takes a query, and excludes everything that isn't a PLANE
    const planarFacesContainingEdgeMidpoint = qContainsPoint(allPlanarFacesInPartStudio, edgeMidpoint); // Another filter.  This one excludes anything that doesn't contain the edge midpoint
    
    Most querying in FeatureScript is done like this.  Finding some "core" or "seed" query that provides too much geometry (usually a qCreatedBy or qEverything), and then filtering down to what you actually want by whittling down on the query in different ways.


    So, back to the point at hand, why is your query failing?
    </code>qEverything(qContainsPoint(qGeometry(qEverything(EntityType.FACE), GeometryType.PLANE), superedge).VERTEX)</pre>To start, superedge is a Line, not a point, and qContainsPoint asks for a point (aka a Vector) as its second parameter. The point that you want is the `origin` of superedge, which you can get as `superedge.origin`.<br><a rel="nofollow" href="https://cad.onshape.com/FsDoc/library.html#qContainsPoint-Query-Vector">https://cad.onshape.com/FsDoc/library.html#qContainsPoint-Query-Vector</a><br><a rel="nofollow" href="https://cad.onshape.com/FsDoc/library.html#Line">https://cad.onshape.com/FsDoc/library.html#Line</a>&nbsp;<br><br>Secondly, the wrapping that you are doing with qEverything is actually doing something quite unintuitive.&nbsp; I did not actually expect that it would get past the interpreter, but it is because of how FeatureScript types work.&nbsp; What is happening right now, is that the inner part of the query:&nbsp;<pre class="CodeBlock"><code>qContainsPoint(qGeometry(qEverything(EntityType.FACE), GeometryType.PLANE), superedge)
    is being evaluated and returning a Query.  Because of how types work in FeatureScript, a Query is actually just a map. So when you then say 
    qContainsPoint(qGeometry(qEverything(EntityType.FACE), GeometryType.PLANE), superedge).VERTEX
    you are asking that map whether it has some field called "VERTEX", which is does not, so it returns `undefined`. So really this whole call is now just saying:
    qEverything(undefined)

    In short, your query will be correct if you rephrase it as:
    qContainsPoint(qGeometry(qEverything(EntityType.FACE), GeometryType.PLANE), superedge.origin)
    but also feel free to break it out and go one query at a time if that makes it more readable for you.  Reading from the inside-out can be annoying once you start nesting a few deep.




    To be continued... 

    This is getting pretty long so I am going to post it and answer the third question in a follow-up.
    Jake Rosenfeld - Modeling Team
  • Jake_RosenfeldJake_Rosenfeld Moderator, Onshape Employees, Developers Posts: 1,646
    @eric_schimelpfenig continued...

    3.

    If my vague understanding is correct, should have have returned those two "major" and "minor" faces that are touching that line? Or since you say 4 faces, are you talking about the top and bottom faces too? I'm not totally sure how you're getting to four...

    If I'm right on that, it looks like you want to use the area of each face to determine which is the larger, and smaller one? While in this particular model that would work (again if I'm understanding the logic here) but in the "real world" the larger hole doesn't always go on the larger face...

    Here is a new example part studio I made to show which faces I am taking about.  I've pulled the geometry apart a little bit and made all the parts a bit transparent because it will be easier to see what I am taking about that way.

    The raw part studio:

    In the Left Case, the user selects this edge:

    In the Right Case, the user is selecting either one of these edges, at random, because they are completely overlapping each other:

    A better name for these cases would be "Interior Case" and "Exterior Case" respectively.  You'll notice that in what I've been calling the Left Case, if I select the exterior edge instead of the interior edge, you could just rotate this whole picture around 180 degrees and call it the right case.  So from now on I will refer to it as the Interior and Exterior cases instead.

    The Interior Case edge-midpoint and the three faces that midpoint touches:

    The Exterior Case edge-midpoint and the four faces that midpoint touches:




    The algorithm, then, isn't simply to take the smaller or larger of all of those faces, but rather to find the two faces that are opposed to each other:

    And see that the larger of those two faces is the "minor face" i.e. the face that wants the small hole drilled into it.

    Then from there, there are a number of ways to get the "major face" i.e. the face that wants the big hole drilled into it.  But basically, in both cases its just the face from the original 3 or 4 faces (depending on case) which is on the same part as the smaller "opposing face" (i.e. the "opposing face" that was not picked to be the minor face).


    Describing this gave me an idea of how to write a simpler query for the major face than in my original post (based on how I described it above), so here's that query:
    // Things we have from earlier
    facesContainingEdgeMidpoint // The 3 or 4 edges which contain the edge midpoint
    opposedFaces // The two faces that are opposing each other
    minorFace // The larger of the opposed faces
    // Find the major face
    const otherOpposedFace = qSubtraction(opposedFaces, minorFace);
    const majorFacePart = qOwnerBody(otherOpposedFace);  // The part that owns the other opposed face (i.e. the light blue part or navy blue part depending on case)
    const majorFaceAndOtherOpposedFace = qOwnedByBody(facesContainingEdgeMidpoint, majorFacePart); // Filter original 3 or 4 faces down to only the ones that are on a blue part
    const majorFace = qSubtraction(majorFaceAndOtherOpposedFace, otherOpposedFace); // Strip away the other opposed face
    https://cad.onshape.com/FsDoc/library.html#qOwnerBody-Query
    https://cad.onshape.com/FsDoc/library.html#qOwnedByBody-Query-Query

    Note that qOwnerBody is not always a filter, there are other interfaces that can just get you all the entities of a specific part, so that it can be used as a "seed" or "core" query, as discussed earlier:
    https://cad.onshape.com/FsDoc/library.html#qOwnedByBody-Query-EntityType








    Anyway I think that covers all the questions.  Please keep em coming!
    Jake Rosenfeld - Modeling Team
  • eric_schimelpfenigeric_schimelpfenig Member Posts: 75 EDU
    Alright, I read this over once and my take away is this: I really need to get my head around queries. 

    I was looking at your examples and they seem to read right to left, not left to right as I was reading them.

    I'm wondering if I should just hand draw some super basic stuff like a cube, cylinder, etc and just practice using queries on them so I can really get them down as best as I can before I move on to the next thing.

    Maybe could use queries with the visual debugging as sort of "target practice" to see what I could get to work? 

    For example, make the user select a single body (like a cube) and see if I could target a particular face, edge, point, etc? 

  • eric_schimelpfenigeric_schimelpfenig Member Posts: 75 EDU
    Ok, so I did this:

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

    annotation { "Feature Type Name" : "Query Test" }
    export const myFeature = defineFeature(function(context is Context, id is Id, definition is map)
        precondition
        {
            annotation { "Name" : "Pick a Body", "Filter" : EntityType.BODY, "MaxNumberOfPicks" : 1 }
            definition.UserBody is Query;
            
        }
        {
           var test = qEverything(EntityType.FACE);
           
           debug(context, test);
        });

    And I got this:



    This makes sense, I basically said "give me everything" and then "only show me the faces" right?

    Let's say I wanted to narrow this down to the faces that are just on the cube, since I had the user pick the cube, How do I:

    Say "hey, start with what the user picked" 

    and then from there how do I say "Ok, the user picked this cube so ignore everything but the faces"

    Can/should I build that like I did here as a "var" (variable). I only did that because that's the only way I know how to get it to show up in debug...

  • Jake_RosenfeldJake_Rosenfeld Moderator, Onshape Employees, Developers Posts: 1,646
    edited February 2020

    Can/should I build that like I did here as a "var" (variable). I only did that because that's the only way I know how to get it to show up in debug...
    var test = qEverything(EntityType.FACE);
    debug(context, test);

    is the same as:

    debug(context, qEverything(EntityType.FACE));

    Storing the return value of the qEverything(...) call into a variable called "test" just lets you use that name as a placeholder for the value you are trying to get at.  So, if you don't want to store a variable, you can just use the query directly as the argument to debug(...).

    But, it's not wrong to use var.  Your only two choices are "var" and "const", they both allow you to store variables, the "const" ones just can't be changed later.  I like to use "const" for everything except things that absolutely must be "var".

    This makes sense, I basically said "give me everything" and then "only show me the faces" right?

    I'm going to be pedantic, but just as a way to give out more examples.  I would think of qEverything(EntityType.FACE) one thing, rather than two: it's more like "give me all the faces". Something that's more "give me everything", then "filter it down so its only faces" would look like:

    // Get everything (bodies, faces, edges, and vertices) without caring about entity type
    const absolutelyEverything = qEverything();
    // Filter down by entity type
    const allFaces = qEntityFilter(absolutelyEverything, EntityType.FACE);
    
    // ----- Using some nesting, like in the last example, the above code is equivalent to -----
    const allFaces = qEntityFilter(qEverything(), EntityType.FACE);
    I wouldn't ever use the code that's above, because qEverything(EntityType.FACE) is exactly the same and simpler, but the point was just that qEverything(EntityType.FACE) is just one query, doing one thing (not two queries or two steps).
    Let's say I wanted to narrow this down to the faces that are just on the cube, since I had the user pick the cube, How do I:

    Say "hey, start with what the user picked" 

    and then from there how do I say "Ok, the user picked this cube so ignore everything but the faces"

    Your body can be referred to as definition.UserBody, and the qOwnedByBody query does exactly what you want here:
    const userBodyFaces = qOwnedByBody(definition.UserBody, EntityType.FACE);
    debug(context, userBodyFaces);
    should do the trick!



    A side note on something that I see being confusing.  You may be asking, why can I call "qEverything(EntityType.FACE)" but also "qEverything()". Is this some kind of weird FeatureScript syntax magic?? Is it two queries in one? Theres actually nothing special going on here.  We just provide two different overloads of the 'qEverything' function, one of which takes an EntityType parameter and one which doesn't.
    https://cad.onshape.com/FsDoc/library.html#qEverything-EntityType
    https://cad.onshape.com/FsDoc/library.html#qEverything
    We do this for lots of our queries that fetch geometry (as opposed to filtering down geometry), since it is a lot easier than having to call qEntityFilter right after, every time.

    Another example is qOwnedByBody:
    https://cad.onshape.com/FsDoc/library.html#qOwnedByBody-Query-EntityType
    https://cad.onshape.com/FsDoc/library.html#qOwnedByBody-Query

    And another is qCreatedBy:
    https://cad.onshape.com/FsDoc/library.html#qCreatedBy-Id-EntityType
    https://cad.onshape.com/FsDoc/library.html#qCreatedBy-Id
    Jake Rosenfeld - Modeling Team
  • kevin_o_toole_1kevin_o_toole_1 Onshape Employees, Developers, HDM Posts: 565
    Maybe could use queries with the visual debugging as sort of "target practice" to see what I could get to work? 
    Keep your eye out on the FeatureScript forum the next few days :wink:
  • MBartlett21MBartlett21 Member, OS Professional, Developers Posts: 2,050 ✭✭✭✭✭
    @kevin_o_toole_1
    Why???

    Are we going to be able to inspect local variables while the feature is executing?
    mb - draftsman - also FS author: View FeatureScripts
    IR for AS/NZS 1100
  • Jake_RosenfeldJake_Rosenfeld Moderator, Onshape Employees, Developers Posts: 1,646
    @MBartlett21
    Don't get that excited :sweat_smile:
    Jake Rosenfeld - Modeling Team
  • owen_sparksowen_sparks Member, Developers Posts: 2,660 PRO
    @MBartlett21
    Don't get that excited :sweat_smile:
    One day one of the devs will let a screen shot slip out of what the real IDE looks like :p
    Owen S.

    Business Systems and Configuration Controller
    HWM-Water Ltd
  • kevin_o_toole_1kevin_o_toole_1 Onshape Employees, Developers, HDM Posts: 565
    edited February 2020
    One day one of the devs will let a screen shot slip out of what the real IDE looks like
    To be honest I use Feature Studios when working on internal changes to Onshape's standard library!

    We do have a separate process for keeping that code versioned with the rest of Onshape and deploying it with the rest of Onshape, so it's actually many more steps for me to temporarily get code into a Feature Studio and copy it back out (vs just editing it in a local text editor). But, when it comes to interactive, fast development of FeatureScript, nothing beats a Feature Studio so that's the route I usually go.
  • Jake_RosenfeldJake_Rosenfeld Moderator, Onshape Employees, Developers Posts: 1,646
    @owen_sparks

    We actually mostly write FeatureScript using a Feature Studio as the editor.  

    That or your favorite text editor of choice...
    Jake Rosenfeld - Modeling Team
  • owen_sparksowen_sparks Member, Developers Posts: 2,660 PRO
    edited February 2020

    Wow I'm shocked. I'm amazed you don't end up throwing your mice against the walls.  No bookmarks, no watch functionality, no project explorer, no dark mode, no peek definition, no extract to new method etc. etc. that would drive me to distraction.  That you guys do such a good job in that environment is interesting.  The fact you have the power to change all that, and presumably don't feel the need, must mean it's working for you, or other needs are taking precedence. Kudos :+1:

    Please forgive the sarcasm earlier, I'd just always thought there was some magic dev-only key-combo that opened up a richer IDE that you all used, but it wasn't for public consumption as it had a bunch of access to stuff that it wasn't appropriate for us to see...

    I'd heard rumors of "real" coders using text editors but I'm yet to actually catch one doing it. ;)

    Thanks for the chat,
    Owen.

    Business Systems and Configuration Controller
    HWM-Water Ltd
  • Jake_RosenfeldJake_Rosenfeld Moderator, Onshape Employees, Developers Posts: 1,646
    Obligatory: https://xkcd.com/378/

    No need to apologize though :) Most of the developments that come for the Feature Studio are driven by things we need (autocomplete, monitor, profile, etc.) and we all agree we need debugging.  Just not badly enough yet to justify the development time.
    Jake Rosenfeld - Modeling Team
  • Jake_RosenfeldJake_Rosenfeld Moderator, Onshape Employees, Developers Posts: 1,646
    @eric_schimelpfenig

    Still hanging in there?  Let us know if you are making good progress or have any questions.  Sorry this thread has gone a bit off the rails.
    Jake Rosenfeld - Modeling Team
  • ilya_baranilya_baran Onshape Employees, Developers, HDM Posts: 1,212
    @owen_sparks
    For TypeScript, Java and C++ we have full-fledged IDEs, but other than "watch" I pretty much never use any other features that you mentioned.  Go-to-definition, doc-on-hover, autocomplete, project search I use all the time and (not entirely by coincidence) feature studios have those.
    In any case, if we had a better way of doing FS development than a feature studio, why would we not release it?
    Ilya Baran \ VP, Architecture and FeatureScript \ Onshape Inc
  • eric_schimelpfenigeric_schimelpfenig Member Posts: 75 EDU
    @Jake_Rosenfeld

    I am! I had a busy day or two and I'm back at this now. More soon!

  • eric_schimelpfenigeric_schimelpfenig Member Posts: 75 EDU
    Ok, I fooled around with this a bit more and I was able to do some really basic stuff with this bit of code:

    https://cad.onshape.com/documents/c8b9ff43446d2e7e88132990/v/942c6df6a81b26f6d81f3f4a/e/82529ac66085d717ef7f2fb8

    I was able to get all of the edges:



    And even the faces if I wanted to. That wasn't too hard to pull off, but now how do I narrow it down more? Let's say I wanted the top or front face?

    This line filters the user selection down to either edges or faces (depending on the enitytype setting I choose)

    const userBodyFaces = qOwnedByBody(definition.UserBody, EntityType.EDGE);

    Can I filter further in that line of code to get a particular element? 

    Sorry if these are super basic questions, but at this point I'm so new to coding I'm not even sure where to start looking for the answer? :)

  • konstantin_shiriazdanovkonstantin_shiriazdanov Member Posts: 1,221 ✭✭✭✭✭
    since the body was created by extrude feature you can use qCapEntity()/qNonCapEntity() to filter cap or non cap entities, or qFarthestAlong() to filter farthest entites along some direction, the direction should be a unitless 3d vector

  • eric_schimelpfenigeric_schimelpfenig Member Posts: 75 EDU
    Would I add that into this line somehow to filter for a "cap" on a body that was extruded?

    const userBodyFaces = qOwnedByBody(definition.UserBody, EntityType.EDGE);

    I tried this: const userBodyFaces = qOwnedByBody(definition.UserBody, EntityType.EDGE, qCapEntity(id + "definition.UserBody", CapType.END, EntityType.FACE));

    But it didn't work, and I'm not sure why... Perhaps it's because there's no "extruding" happening here? The body already exists and I'm just having the user select it...
  • NeilCookeNeilCooke Moderator, Onshape Employees Posts: 5,686
    Correct - it is just a body. If you want to get the top face, you should have the user select the face not the body. It is making a big assumption that the top face of a part is in the +Z direction. 
    Senior Director, Technical Services, EMEAI
  • konstantin_shiriazdanovkonstantin_shiriazdanov Member Posts: 1,221 ✭✭✭✭✭
    edited February 2020
    Would I add that into this line somehow to filter for a "cap" on a body that was extruded?

    I'm sorry, you could use qCapEntity() if your input field was of FeatureList type. Since you use query you can't get operation id to pass into qCapEntity().So I think qFarthestAlong() is the only solution to get specific face.

  • eric_schimelpfenigeric_schimelpfenig Member Posts: 75 EDU
    @NeilCooke I get what you're saying, what I was trying to learn was more about queries and how to find particular parts of a model:

    https://forum.onshape.com/discussion/comment/59757/#Comment_59757

    As you said in one of your webinars I watched: If you can learn queries you can do a lot with FS (obviously paraphrasing)

    I was able to target all of the faces, and edges separately in this cube, but I want to get more specific to practice.. I'm just not sure what makes the most sense to try out as I really don't have a good sense of what I can do with queries at all...
  • NeilCookeNeilCooke Moderator, Onshape Employees Posts: 5,686
    If the body was created from a previous feature, you can get the id like so:
    qCapEntity(lastModifyingOperationId(context, definition.UserBody), CapType.END, EntityType.FACE));
    but that is entirely dependent upon the feature having being created using an extrude or sweep.
    Senior Director, Technical Services, EMEAI
  • Jake_RosenfeldJake_Rosenfeld Moderator, Onshape Employees, Developers Posts: 1,646
    I would only use qCapEntity if I had created that body within my feature (then you know how it was created and exactly what its id is).  Otherwise it is a total crapshoot whether it will get you anything or not.

    @eric_schimelpfenig

    Here are a couple things to try:
    const allFaces = qOwnedByBody(definition.UserBody, EntityType.FACE);
    
    const nThFace = qNthElement(allFaces, <some integer>);
    const highestFace = qFarthestAlong(allFaces, vector(0, 0, 1));
    const lowestFace = qFarthestAlong(allFaces, vector(0, 0, -1));
    const faceContainingPoint = qContainsPoint(allFaces, vector(0, 0, 1) * inch);
    

    I tried this: const userBodyFaces = qOwnedByBody(definition.UserBody, EntityType.EDGE, qCapEntity(id + "definition.UserBody", CapType.END, EntityType.FACE));

    There are a few things wrong with this that cause it not to work.  1) There is no `qOwnedByBody` that takes three parameters. See here all the forms of `qOwnedByBody`: https://cad.onshape.com/FsDoc/library.html#qOwnedByBody-Query-EntityType . 2) `id + "definition.UserBody"` is not an id in the system.  There is not really any good way to find the id creating the UserBody, since FeatureScript was not set up to work this way.




    Generally it is actually a bit hard to do what you are doing.  When you set up a Query parameter, you usually already know what you are looking for, and don't need to do much post-processing on it.  Queries are a little more powerful when you are creating geometry inside of your own feature.  This is because you'll have the Id that creates that geometry, and a lot of Queries are keyed based off of Id.

    Try the following:
    use fCuboid to make a box
    https://cad.onshape.com/FsDoc/library.html#fCuboid-Context-Id-map
    const cuboidId = id + "cuboid";
    fCuboid(context, cuboidId, {
        "corner1" : vector(-1, -1, 0) * inch,
        "corner2" : vector(1, 1, 2) * inch
    });
    Then query using the cap entity query:
    debug(context, qCapEntity(cuboidId, CapType.START, EntityType.FACE));
    https://cad.onshape.com/FsDoc/library.html#qCapEntity-Id-CapType-EntityType


    Jake Rosenfeld - Modeling Team
  • eric_schimelpfenigeric_schimelpfenig Member Posts: 75 EDU
    I'm going to keep hacking away at this over the weekend... stay tuned!
Sign In or Register to comment.