Welcome to the Onshape forum! Ask questions and join in the discussions about everything Onshape.
First time visiting? Here are some places to start:- Looking for a certain topic? Check out the categories filter or use Search (upper right).
- Need support? Ask a question to our Community Support category.
- Please submit support tickets for bugs but you can request improvements in the Product Feedback category.
- 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.
opdraft 101
EvanReese
Member, Mentor Posts: 2,191 ✭✭✭✭✭
I'm trying to understand the opdraft command, and I am sure I'm missing something simple. Why doesn't this code work? If I comment out the opdraft command it works again.
I can extrude a body, and I've queried the side face of the cylinder, but I can't seem to apply draft. I also have been looking at the code of the Onshape draft feature, especially really early versions of it (which is so awesome to be able to do to see the simpler beginning), but I don't understand how it's getting the info it needs since all I see is.
I can extrude a body, and I've queried the side face of the cylinder, but I can't seem to apply draft. I also have been looking at the code of the Onshape draft feature, especially really early versions of it (which is so awesome to be able to do to see the simpler beginning), but I don't understand how it's getting the info it needs since all I see is.
opDraft(context, id, draftDefinition);I'm betting this is pretty straight forward once I understand it. Any tips on how I could troubleshoot this myself next time?
Evan Reese
0
Best Answers
-
jakeramsley Member, Moderator, Onshape Employees, Developers, csevp Posts: 661Hi Evan_Reese,
The error is due to the type passed in to your neutral plane.
opDraft has the given preconditions:/** * Applies a given draft angle to faces. * @param id : @autocomplete `id + "draft1"` * @param definition {{ * @field draftType {DraftType} : * Specifies a neutral plane or reference entity draft. * @eg `DraftType.NEUTRAL_PLANE` for a neutral plane draft * * @field neutralPlane {Query} : @requiredif { `draftType` is `NEUTRAL_PLANE` } * The face defining the neutral plane for a `NEUTRAL_PLANE` draft. The intersection of the drafted faces * and the neutral plane remains unchanged. * @autocomplete `neutralPlane` * @field draftFaces {Query} : @requiredif { `draftType` is `NEUTRAL_PLANE` } * The faces to draft for a `NEUTRAL_PLANE` draft. * @autocomplete `draftFaces` * * @field referenceEntityDraftOptions {array} : @requiredif { `draftType` is `REFERENCE_ENTITY` } * An array of maps of the form ("face", "references", "angle"). "face" should be a [Query] for exactly one * face. "references" should be a [Query] for at least one edge attached to the face. The "face" will * be drafted while the geometry of the "references" remains unchanged. "angle" is an optional [ValueWithUnits] * parameter between -89.9 and 89.9 degrees which overrides the default `angle` parameter. * * @field pullVec {Vector} : The 3d direction relative to which the draft is applied. * @eg `evPlane(context, {"face" : neutralPlane}).normal` will draft uniformly away from the neutral plane. * @field angle {ValueWithUnits} : The draft angle, must be between 0 and 89.9 degrees. * @eg `3 * degree` * * @field tangentPropagation {boolean} : @optional * For a `NEUTRAL_PLANE` draft, `true` to propagate draft across tangent faces. * Default is `false`. * @field referenceEntityPropagation {boolean} : @optional * For a `REFERENCE_ENTITY` draft, `true` to collect new reference entities and faces by pulling in edges * connected to the specified reference edges. Connected edges on the same face or on tangent connected * faces will be pulled in. * Default is `false`. * * @field reFillet {boolean} : @optional * `true` to attempt to defillet draft faces before the draft and reapply the fillets * after. Default is `false`. * }} */
in it, you can see that neutralPlane is type query.
In your example, if you do a:debug(context, skplane)
on your code, you get the response</code>debug: Plane normal (0, 0, 1) origin (0 meter, 0 meter, 0 meter) x (1, 0, 0)</pre>which means it is an already evaluated plane, not a query pointing to the plane (which opDraft is expecting).<br><br>If you were to replace your draft with<br><pre class="CodeBlock"><code>opDraft(context, id + "draft1", { "draftType" : DraftType.NEUTRAL_PLANE, "neutralPlane" : qCreatedBy(makeId("Top"), EntityType.FACE), "draftFaces" : draftFaces, "pullVec" : skplane.normal, "angle" : 3 * degree });
You can see it starts to workJake RamsleyDirector of Quality Engineering & Release Manager onshape.com7 -
Jake_Rosenfeld Moderator, Onshape Employees, Developers Posts: 1,646@Evan_Reese
To shed a bit more light on this. Just because you have already made a sketch, does not mean that a plane actually exists in the part studio.
Imagine the following scenario:- I create a part with a planar face
- I sketch a single vertex on that face of the part
- I delete the part
- I use your custom feature, and select the sketched vertex
In general, though, opDraft should have an option to take the mathematical description of a plane rather than a query for a plane. I agree with you that the interface is a bit lacking here, and you really shouldn't have to make a plane and delete it to do the draft.
The main reason I wanted to reply here is that there is some confusion surrounding "evaluated plane". This could mean two things:- A "Plane" object, either returned by "evPlane(...)" or constructed by calling one of the "plane(...)" constructors. This is the mathematical description of a plane. It holds information about the origin, the normal, and the x direction of the plane. No geometry needs to exist in the context to create this. It is just a variable describing a theoretical mathematical plane.
- An evaluated Query for a planar face. A Query is just a set of instructions telling the system how to find a piece of geometry. "qCreatedBy(makeId("Top"), EntityType.FACE)" is an instruction saying "find all the faces created by the id 'Top'". If you call "evaluateQuery(...)" on this query, you will get back an array with one item in it. This item is an "evaluated Query", but all that means is that it's a very specific query that refers specifically to the face of the top plane. The type of query is a "transient Query" and it refers to the top face of the plane using the face's "transient Id". These are called transient because they are very specific references to one specific piece of geometry whose id could change any time the geometry present in the system changes (hence transient Queries are not very reliable once the geometry starts changing). The qCreatedBy and its corresponding evaluated Query are both valid inputs to opDraft. Both describe the same face, and there is nothing special about the evaluated query except that it is a different, more specific way of describing the same face. Queries do not need to be evaluated before they are used.
Sorry for being long-windedJake Rosenfeld - Modeling Team1
Answers
The error is due to the type passed in to your neutral plane.
opDraft has the given preconditions:
in it, you can see that neutralPlane is type query.
In your example, if you do a:
on your code, you get the response
You can see it starts to work
Thanks! that does explain a lot. opDraft is looking for a query, and I'm feeding it an evaluated plane. I'm new to this, so i'll ask a possibly ignorant question. Is it right to think of the evaluated plane as an array with only one index?
Also, when I plug in your snippet for the neutral plane, it does work, but I don't understand what that bit is doing. It seems like it's querying the world top plane? If so, it only works if I have my original sketch on the top plane, otherwise my draft neutral plane is wrong. I need to use the sketch plane of my input point. How can I get a query for that plane? See the issue below
I made some progress by using opPlane to make a new plane to use for my neutral plane, then deleting it afterward. (see how the bases of each extrusion line up with the sketch that created it) is there a better way, though? that version is here
Is there any way that opDraft could be set to use an already-evaluated plane?
IR for AS/NZS 1100
or anyone has a few minutes to clear that up.
Since my sketch plane is already evaluated and opDraft needs a query, I'm using opPlane to make a new plane query to call, then deleting it after. It feels extra.
I think it's big deal for this feature since i'm only doing it one time, but if I had to move the opPlane inside my for loop (for some reason) then it could weigh it down if it's not efficient. I want to instill good practice from the beginning of my learning.
To shed a bit more light on this. Just because you have already made a sketch, does not mean that a plane actually exists in the part studio.
Imagine the following scenario:
- I create a part with a planar face
- I sketch a single vertex on that face of the part
- I delete the part
- I use your custom feature, and select the sketched vertex
There are lots of other ways you could get into a state like this, but the point is, the piece of planar geometry that you sketched on does not necessarily still exist just because the sketch vertex exists. So the choices you have in your feature are: create a piece of planar geometry using opPlane, or add another selection box asking the user to identify a neutral plane.In general, though, opDraft should have an option to take the mathematical description of a plane rather than a query for a plane. I agree with you that the interface is a bit lacking here, and you really shouldn't have to make a plane and delete it to do the draft.
The main reason I wanted to reply here is that there is some confusion surrounding "evaluated plane". This could mean two things:
- A "Plane" object, either returned by "evPlane(...)" or constructed by calling one of the "plane(...)" constructors. This is the mathematical description of a plane. It holds information about the origin, the normal, and the x direction of the plane. No geometry needs to exist in the context to create this. It is just a variable describing a theoretical mathematical plane.
- An evaluated Query for a planar face. A Query is just a set of instructions telling the system how to find a piece of geometry. "qCreatedBy(makeId("Top"), EntityType.FACE)" is an instruction saying "find all the faces created by the id 'Top'". If you call "evaluateQuery(...)" on this query, you will get back an array with one item in it. This item is an "evaluated Query", but all that means is that it's a very specific query that refers specifically to the face of the top plane. The type of query is a "transient Query" and it refers to the top face of the plane using the face's "transient Id". These are called transient because they are very specific references to one specific piece of geometry whose id could change any time the geometry present in the system changes (hence transient Queries are not very reliable once the geometry starts changing). The qCreatedBy and its corresponding evaluated Query are both valid inputs to opDraft. Both describe the same face, and there is nothing special about the evaluated query except that it is a different, more specific way of describing the same face. Queries do not need to be evaluated before they are used.
In any case, the point I'm trying to make is that a better distinction is "Plane object" and "Query for a planar face" rather than confusing ourselves with what evaluation we are talking about (evPlane vs evaluateQuery) when we say "evaluated plane".Sorry for being long-winded
In my opinion, there aren't enough long-winded people in the world (who are friendly and actually know what they are talking about, anyway). Thanks for the detail.
Let me try to paraphrase back to see if I'm getting it.
the "plane object" is just a few vectors to describe a plane with math, but it isn't actually a "real plane" like I'd get from the plane feature ("real plane" just to call it something). opDraft doesn't know what to do with the "plane object".
On the other hand, a plane query points to an existing "real plane", and even the evaluated query pretty much just returns another mini-query (aka transient) pointing to the same "real plane" not the mathematical description of it like the "plane object".
Am I close? if so, why can't I query [whatever the sketch is sketched on] and just use that query?
The reason you can't query what planar face a sketch entity was sketched on has to do with the first part of my answer. There wouldn't really be a point of us making some kind of sketch plane query because there may not be any "real plane" backing that sketch. It may have been deleted, or it may not have ever existed in the first place (when sketching in FeatureScript, you can use a Plane object rather than a plane Query as the sketch plane).
I can't see your feature because it is no longer public, but I thought of a couple things that you could actually do pretty easily to avoid calling opPlane:
- Does the feature take the circular face as input? Or does it take a vertex and sketch the circular face on its own? In either case, you can pass in a query for the circular sketch face as the neutral plane. After all, it is a planar face coincident with the sketch plane that you care about. Whatever query you are passing into the "entities" field of the extrude will do fine as the "neutralPlane" of the draft.
- You could query for the start cap of the extruded body, and use that as the neutral plane. The query is qCapEntity(...). This works for the same reason as the previous (it is a planar face coincident with the plane that you care about). This is akin using the Draft feature through the UI and selecting a planar face of the body as the neutral plane, which would be a very normal thing to do.
- You could use extrude(...) instead of opExtrude(...). `extrude` is the actual extrude feature that is invoked when you press the extrude button through the UI. So, it has all the bells and whistles of extrude built-in including the draft. I don't really recommend this approach, as calling our features (extrude) is a lot more complicated than calling the operations that back the features (opExtrude), and comes with a lot more overhead. But if you have a clear idea of what needs to happen and want it done in one fell swoop, you could try this.
Hope that helps!I think the idea here that best fits my needs are to use the cap face, but that would break if I ever wanted to update the feature to take a custom pattern body as input. I'll leave it as-is for now since calling one opPlane and one opDeleteBodies isn't a slowdown.
Btw, I just moved it all to a new public document so I could leave behind some of the other experimental feature studios and part studios that I want to keep, but aren't relevant for the actual feature. The new doc is here.
context
) and are referred to with queries.The best explanation of this difference is probably in the second tutorial, where this note talks about queries for entities vs. geometric data.
Everyone here is right that taking a raw
Plane
in opDraft would be helpful, but Evan you're also right that creating the plane and deleting should be a very small overhead. To check this you can always profile a Part Studio in the workspace.Thanks, that's a helpful distinction. "entity" is the word I was looking for. Hopefully if I leave some breadcrumbs of my own personal lay-speak around someone else reading it that understands this stuff as little as I do can benefit
I'm always checking my model rebuild time, so now I get to enjoy doing the same for my custom features.