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.

Import sketch/body using FS?

I'm just starting out learning FeatureScript (again) and I did the first tutorial:

https://cad.onshape.com/FsDoc/tutorials/create-a-slot-feature.html

That's making sense to me so far, and then I get to the second part of the tutorial where they add some "bumps" to the inside of the slots:

https://cad.onshape.com/FsDoc/tutorials/add-sketch-geometry.html

I get what's happening here, you're essentially coding a sketch.

What I'm after is essentially learning how to easily create a lot of furniture/plywood joints as feature scripts, things like this:



The idea is to make all of these kinds of furniture fasteners so I can make creating furniture easier. So you're wondering.. where's the question?

Well, from reading this tutorial it definitely seems like it's possible to "code" a sketch that will create the above pockets, but I'm wondering if there's a way that I can just import a sketch that I've already created? This way, the featurescript itself would be very simple, and if I wanted to update the shape/size of the pocket it would be almost no coding. That would also allow non coders to be able to update the shape of a joint.

So... Could I bring in a sketch that I've create using featurescript, or perhaps even a surface or a body, extrude and subtract it from an existing body in a model? 
«1

Comments

  • tim_hess427tim_hess427 Member Posts: 648 ✭✭✭✭
    I don't know much about doing this in featurescript, but it sounds like for your example, I would make a model in the shape of the pocket, and then use Derive to bring it into the part studio where you're using that shape of pocket. Then, a boolean operation could subtract the shape of the pocket from your part. This gives you version control over shape of the pocket and anybody could work on the shape of the pocket without using featurescript. Depending on how often you're doing this, you could accomplish this without featurescript pretty easily. Derive the 3D shape of the pocket into your studio. Transform the geometry to get it into position. Then, do a boolean operation to subtract it from the furniture piece. 

    You're definitely on the right track. That said, I'm not sure how you would go about implementing the Derive and Boolean operations in the featurescript. 
  • kevin_o_toole_1kevin_o_toole_1 Onshape Employees, Developers, HDM Posts: 565
    edited November 2020
    Questions like this prove you've got exactly the right mindset, wanting to use code for the things code is good at and modeling for the things interactive CAD is good at 👍

    Check out this page documenting workflows for imports, including Part Studio imports: https://cad.onshape.com/FsDoc/imports.html

    As you can see there, you could use the instantiator with function calls like
    const instantiator = newInstantiator(id + "instantiate");
    addInstance(instantiator, MyImportedSketch::build, {
        "partQuery" : qSketchFilter(qEverything(EntityType.BODY), SketchObject.NO),
        "transform" : toWorld(cSysForSketch),
    });
    instantiate(context, instantiator);
    This would place the all the sketch bodies from some imported Part Studio into the feature's Part Studio, at the desired location.
  • eric_schimelpfenigeric_schimelpfenig Member Posts: 75 EDU
    @tim_hess427 That's pretty much what I've been doing now, but I want to make it easier for users. Plus, Featurescript will allow me to add some extra magic to these joints as I create them.

    @kevin_o_toole_1

    Thank you very much for that example. I gave it a shot, but I wasn't able to get it to work because I'm really new to FS and coding in general. Here's what I tried:

    https://cad.onshape.com/documents/df9fc898ac3275697989261f/w/bdd0739a147106b6ab1dc4b1/e/b9b3376923d80f285da95cbe

    I see the error messages in in the feature studio but I don't understand them...

    What I'm trying to accomplish with this example is very simply to get a sketch dropped into the drawing using the FS on the line that I select. Later I'll try and add the complexity of extruding it, subtracting, etc.


  • Jake_RosenfeldJake_Rosenfeld Moderator, Onshape Employees, Developers Posts: 1,646
    edited November 2020
    @eric_schimelpfenig

    The two problems in your feature studio:

    In the import at the top, you need to add the namespace that you want to reference later.
    // At the top:
    SomeNamespaceName::import(...)
    // Then later in the feature:
    addInstance(instantiator, SomNamespaceName::build, ...)

    The second, smaller problem right now is that in this map:
            addInstance(instantiator, MyImportedSketch::build, {
                "partQuery" : qSketchFilter(qEverything(EntityType.BODY), SketchObject.YES)
                "transform" : toWorld(cSysForSketch),
            });
    You are missing a comma at the end of the "partQuery" line.


    (Also worth mentioning that after you fix these two problems, it will complain because you have not defined a cSysForSketch, for now you could replace the transform with something like identityTransform(), and then worry about taking a mate connector for placement later)
    Jake Rosenfeld - Modeling Team
  • Jake_RosenfeldJake_Rosenfeld Moderator, Onshape Employees, Developers Posts: 1,646
    edited November 2020
    @eric_schimelpfenig

    Another problem, it looks like you have done:

    const instantiator = newInstantiator(d2c4fe66f0fb33f29e79d70a + "instantiate");

    As mentioned in the previous comment, the namespace is what is going to link the instantiate to what it is supposed to build.  This line is just giving the instantiator an id that it will use to build its geometry (similar to opExtrude(context, id + "extrude1", { ... }) ), so you do literally want to put `id + "instantiator"` there.

    The instantiator documentation has a nice example of how to do the whole thing:
    https://cad.onshape.com/FsDoc/library.html#module-instantiator.fs
    You can ignore the bit about configurations, unless you want to have the part studio you are importing expose a configuration
    Jake Rosenfeld - Modeling Team
  • eric_schimelpfenigeric_schimelpfenig Member Posts: 75 EDU
    Alright, I've made some changes that you suggested:

    https://cad.onshape.com/documents/df9fc898ac3275697989261f/w/bdd0739a147106b6ab1dc4b1/e/b9b3376923d80f285da95cbe

    But I'm not sure I fully understand...

    If you're up for it feel free to explain this like I'm a complete dummy...

    So "namespace" Is this something throwing the whole part studio a "bucket" that I can rummage through later with my code to pull out bits that I need? To put another way: I could import a whole part studio and put it into a "bucket" and then later say "go into THAT bucket and get me this solid, sketch or whatever?

    Your comment above:

    @eric_schimelpfenig

    Another problem, it looks like you have done:

    const instantiator = newInstantiator(d2c4fe66f0fb33f29e79d70a + "instantiate");

    As mentioned in the previous comment, the namespace is what is going to link the instantiate to what it is supposed to build.  This line is just giving the instantiator an id that it will use to build its geometry (similar to opExtrude(context, id + "extrude1", { ... }) ), so you do literally want to put `id + "instantiator"` there.

    The instantiator documentation has a nice example of how to do the whole thing:
    https://cad.onshape.com/FsDoc/library.html#module-instantiator.fs
    You can ignore the bit about configurations, unless you want to have the part studio you are importing expose a configuration
    So as you can see in my code (link at top) I got it to "work" as in there are no errors being reported in my FS, but it doesn't work in my model...

    Sorry if these questions are really stupid :/
  • Jake_RosenfeldJake_Rosenfeld Moderator, Onshape Employees, Developers Posts: 1,646
    edited November 2020
    So "namespace" Is this something throwing the whole part studio a "bucket" that I can rummage through later with my code to pull out bits that I need? To put another way: I could import a whole part studio and put it into a "bucket" and then later say "go into THAT bucket and get me this solid, sketch or whatever?

    That's a good way of looking at it!  More technically, it is just a way to give that import a name so that you can refer to it later, otherwise, there is no way to say "get stuff from THAT part studio", especially if you are importing a few different part studios.  The way that you then start to filter down is using queries (see the `partQuery` parameter of `addInstance(...)`)

    The current failure for your feature is as follows:


    If you click on the top line there (20:27 Feature Studio 2...), it will bring you to the line that is failing.  The bad line is:

    "transform" : toWorld(id),

    For now, just do:

    "transform" : identityTransform()

    toWorld takes a coordinate system, and you are passing in the feature's Id.

    More info on Ids here: https://cad.onshape.com/FsDoc/library.html#Id




    Jake Rosenfeld - Modeling Team
  • eric_schimelpfenigeric_schimelpfenig Member Posts: 75 EDU
    Alright! I got somewhere!!

    https://cad.onshape.com/documents/df9fc898ac3275697989261f/v/3aaba8f336bb7e8973c60bc6/e/b9b3376923d80f285da95cbe



    So, I checked out the ID link on the documentation, and it didn't make any sense to me. 

    So I assume what I've successfully done here is import the sketch, and just dropped it at the origin. What I'd like to do is place it on a point or a line. So imagine that on the larger rectangle I had a few lines facing inwards I'd like to be able to select and have the sketch "facing in"... So I guess "normal" to the line?

    So here's what I'd like to learn: How do I have to think about placing stuff? 

    There's a coordinate system for the part studio as a whole, is there also a coordinate system for each individual sketch or body/part?

    Assuming that is correct, my guess is that I have to somehow get the coordinate system for a particular sketch, or a line/point that I select and how that's related to the sketch that it's in?

    Or maybe it's simpler, maybe all I need is the location of the point or line I want the imported sketch on?

    Then from there I have to somehow place my imported sketch on whatever coordinate I collect... And then use that information to import and place my sketch?
  • Jake_RosenfeldJake_Rosenfeld Moderator, Onshape Employees, Developers Posts: 1,646
    @eric_schimelpfenig

    It is a bit simpler than you think.  By default (i.e. using the identity transform), that sketch is going to come into the Part Studio at exactly the same place it is in the original part studio.  By changing the transform, you can move where it comes in.  So if you were to do the following, it would come in one inch to the right of where it is in the original part studio:
    "transform" : transform(vector(1, 0, 0) * inch)

    To allow for generic placement by the end-user, I would take a mate connector as input:
            // In the precondition
            annotation { "Name" : "Placement", "Filter" : BodyType.MATE_CONNECTOR, "MaxNumberOfPicks" : 1 }
            definition.placement is Query;
    
    (Note that the end-user does not need to make a mate connector in advance, just doing this will allow them to make an implicit mate connector on-the-fly while working on your custom feature)

    And then use that mate connector to place the sketch:
    "transform" : toWorld(evMateConnector(context, { "mateConnector" : definition.placement }))

    Jake Rosenfeld - Modeling Team
  • eric_schimelpfenigeric_schimelpfenig Member Posts: 75 EDU
    edited November 2020
    Alright! I was able to get that to work, and as you said it's a bit easier to get your head around.

    So I want to take this a bit further:

    1: I want users to be able to insert multiple sketches/parts/whatever I want from the inserted part studio
    2: I want all of the sketches to "point" into the rectangle/outer shape (shape will always be closed, but might not be a rectangle)

    So basically if I had a shape like this:

    So how would I do this?

    My guess is that the first thing is to change the pre-condition to "line" and allow multiple picks? 

    And then for the transform I somehow need to figure out where the line intersects with the outer shape? And then if I have that figured out, somehow always guarantee that the part studio is "lined up" the right way?

    I noodled with it a bit, but I'm not sure how to approach the problem...

    PS this has been a great help so far!

    PS this is the code I tried to make: 

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

    TestNameSpace::import(path : "d2c4fe66f0fb33f29e79d70a", version : "45674d680b6603dcbf2d162d");



    annotation { "Feature Type Name" : "SketchYInserter" }
    export const SketchyInserter = defineFeature(function(context is Context, id is Id, definition is map)
        precondition
        {
            annotation { "Name" : "Location", "Filter" : EntityType.EDGE, "MaxNumberOfPicks" : 1 }
            definition.myQuery is Query;
            
        annotation { "Name" : "Placement", "Filter" : BodyType.POINT, "MaxNumberOfPicks" : 1 }
            definition.placement is Query;
            
        }
        {
            const instantiator = newInstantiator(id + "instantiate");
            addInstance(instantiator, TestNameSpace::build, {
                "partQuery" : qSketchFilter(qEverything(EntityType.BODY), SketchObject.YES),
                "transform" : toWorld(evVertexPoint(context, {
                        "point" : point
                })(context, { "point" : definition.placement }))
            });
            instantiate(context, instantiator);
        });




  • Jake_RosenfeldJake_Rosenfeld Moderator, Onshape Employees, Developers Posts: 1,646
    @eric_schimelpfenig

    I think the most reliable thing to do here really is to just allow mate connector selections, and then respect the position and orientation of the mate connector (since the mate connector is not just a location, it fully defines a coordinate system that the end user can edit as they wish)

    If you really do want your feature to be smarter than that, I would probably suggest having your user select a point, rather than a line, and then just having the sketch point "inwards" perpendicularly from the edge direction at that point.  But there may be a desired degree of freedom that the sketch does not always point straight inward, and could point "inward" at any angle that the line is?

    If you pick an approach from:
    1. just use mate connectors
    2. pick a point and point "straight inward"
    3. pick a line and point "inward" along the line

    I can show you how to do the geometry querying and math to get the sketch transform.
    Jake Rosenfeld - Modeling Team
  • eric_schimelpfenigeric_schimelpfenig Member Posts: 75 EDU


    I think the most reliable thing to do here really is to just allow mate connector selections, and then respect the position and orientation of the mate connector (since the mate connector is not just a location, it fully defines a coordinate system that the end user can edit as they wish)

    While I agree that it's a pretty simple way to do it, when you're placing a ton of these (which is how I intend for this to work) I think it will get really fidgety.

    Here's a use case I'm thinking about:




    Here I don't think it puts too much work on the user to get them to draw a bunch of lines pointing inward, and as you can see many of those lines are coming off of non straight edges.

    If they had to do mate connectors here it'd be a lot of clicking..

    If you really do want your feature to be smarter than that, I would probably suggest having your user select a point, rather than a line, and then just having the sketch point "inwards" perpendicularly from the edge direction at that point.  But there may be a desired degree of freedom that the sketch does not always point straight inward, and could point "inward" at any angle that the line is?

    I think if I get what you're saying here: You're saying that they'd pick a base point, so a point touching the outer edge of the shape, and then another point, or a line indicating the way they want the feature to "face"?

    if I get that, you're talking about your option 3?

  • Jake_RosenfeldJake_Rosenfeld Moderator, Onshape Employees, Developers Posts: 1,646
    edited November 2020
    @eric_schimelpfenig

    Regarding:
    If you really do want your feature to be smarter than that, I would probably suggest having your user select a point, rather than a line, and then just having the sketch point "inwards" perpendicularly from the edge direction at that point.  But there may be a desired degree of freedom that the sketch does not always point straight inward, and could point "inward" at any angle that the line is?
    I am trying to make a distinction between options two and three.

    In the image you have posted above, all of the notches are pointing directly perpendicular to the tangent direction of the edge at the selected point.  At any given point along your edge, the edge has a tangent line defining the direction it is currently moving in.  Within the plane of the face, there are two perpendicular directions to that direction: "straight out" and "straight in".  Here is an image that may make it more clear:


    What I am trying to say here is that if you always want your imported sketches to just point "straight in", then you could just have the user select one point per sketch, and use math the determine the "straight in" direction.

    If you do not expect that the user would want the sketches to always face "straight in", and want to expose a degree of freedom that the sketch could be pointing any direction along the 180 degrees of available "inward" directions, you could expose a line selection to let your end-user fully define the exact direction they want the imported sketch to face. 

    The problem with requiring the user to select lines is that if the most common use case is that the sketches should be "straight in", then requiring the user to draw a bunch of lines that face perfectly "straight in" will be tedious and burdensome for the user, when a point selection and a smarter feature would not require them to draw the lines in the perfectly correct direction.

    Of course, you could always make your feature accept points AND lines, and face "straight in" for each point that is selected, and face the line direction for each line that is selected.
    Jake Rosenfeld - Modeling Team
  • eric_schimelpfenigeric_schimelpfenig Member Posts: 75 EDU
    Alright, I've gone back and taken another crack at this... I had some folks at CADsharp give me an hour or two of training and that helped... I went back and tried to write my sketch importer from scratch and I got a lot of it to work, but not all of it.

    Right now I can get a sketch from another part studio imported and "transformed" off of the origin.

    What I really want to do is have it at a point on a line. Eventually I'll have it arrayed out from that point. This will let my users define the origin position easily by dropping points in sketches.

    Where I got a little lost is the variables. As I understand it you have to start a FS with importing stuff, like the FS version and the model you're working with, then you have to "ask" the users questions, in my case this would be the face and an edge.

    Then you collect those faces and edges into variables

    And then you can use those collected variables to then do something, like place my sketch, transform it along a line, etc.

    As you can see from my script I tried variables but I failed. 

    https://cad.onshape.com/documents/df9fc898ac3275697989261f/v/d1cc045edbd9846e669b6252/e/b5c3f6e857618355c3a380f3

    Assuming my strategy is right, I don't really understand how to start thinking about transforming my sketch onto a body...The only programing experience I have doing this sort of thing is doing this in SketchUp. There each "component" or part has it's own coordinate system and you just move parts off of that. Here I'm learning that it's quite different... I'm just fuzzy as to in what way it's different...
  • Jake_RosenfeldJake_Rosenfeld Moderator, Onshape Employees, Developers Posts: 1,646
    edited November 2020
    @eric_schimelpfenig

    Your use of definition parameters here looks fine.  If you want to check that you are using them correctly, you could do something like debug(context, definion.pickedge), and then when you have your feature dialog open, that edge will show up highlighted in red.

    You could also do something like println(definition.distfromstart) and that will print to the FeatureScript console, to make sure that your length is making it through correctly.

    The issue here is with the use of evFaceNormalAtEdge.  There are two problems:
    1. If you are using sketch faces and sketch edges, they are not on the same body.  Each sketch edge is a lone wire body, unattached from any of the sketch faces or other sketch edges that it borders.  Each sketch region is a sheet body with its own system of edges that borders the region, but these edges are only for the purpose of containing the face, and are not graphically presented to the end user at all, nor are they selectable by the end user.  So, unfortunately, you cannot use evFaceNormalAtEdge with a sketch face and a sketch edge that seems to bound it, because the sketch edge is actually totally unrelated to the face, except for the position it occupies in space.  They are not actually "connected" to each other the same way that an edge of a cube is "connected" to the two cube faces that it borders.
    2. The "parameter" that evFaceNormalAtEdge wants is a unitless number from 0 to 1, representing "how far along" the edge you want to get that face normal.  You are passing in a length, rather than a parameter.  See documentation of the interface here: https://cad.onshape.com/FsDoc/library.html#evFaceNormalAtEdge-Context-map

    Broader suggestions on how to procceed:
    • I actually think taking in vertices that are expected to be on the edge will make your life a lot easier.  Taking a length has a few problems, it is hard to turn the length into an edge parameter, the user may not know exactly how far along the edge they want the sketch to be, and it is unclear which side of the line is the "start" and the "end" (and this is also unstable with respect to upstream changes, so if the user goes and edits an earlier feature, you may find the location of your notches actually flips to being measured from the other end of the edge)
    • Could your `topface` selection be simplified to only take planar faces?  If so, you can change the filter selector to `EntityType.FACE && GeometryType.PLANE`.  This will make your life a lot easier because you will not need to evaluate the face normal at a specific point on the face; you can just call evPlane() because you know the face is planar, and its normal is the same everywhere. "Narrowing down" user selections like this often makes writing features easier, by letting you write more specific code instead of having to cover every generic case

    Barring those suggestions, with just the variables you have set up, this is how I would get the face normal at a specific distance along the edge:
    // -- First find the `distfromstart` point along the `pickedge` --
    const edgeTotalLength = evLength(context, { "entities" : definition.pickedge });
    const edgeParameter = definition.distfromstart / edgeTotalLength;
    const edgeTangent = evEdgeTangentLine(context, { 
            "edge" : definition.pickedge,
            "parameter" : edgeParameter
        });
    
    // edgeTangent now has two important pieces of information.  It is a Line, which can be looked up in the standard library docs.
    // edgeTangent.origin is the world-space location of the point on the edge that is `distfromstart` along the edge
    // edgeTangent.direction is the edge tangent (described in my picture a couple posts ago)
    
    // -- Use the found point to find the face normal on `topface` --
    // Calling evDistance will allow us to get the face parameter of the point in question
    const distanceResult = evDistance(context, { 
            "side0" : definition.topface,
            "side1" : edgeTangent.origin
        });
    const faceParameterOfEdgePoint = distanceResult.sides[0].parameter;
    const faceTangent = evFaceTangentPlane(context, { 
            "face" : definition.topface,
            "parameter" : faceParameterOfEdgePoint
        });
    
    // faceTangent is a Plane.  faceTangent.normal is the face normal at the desired point.
    
    

    As mentioned before, this example is without taking any of my suggestions into account, so if you decide that you would like some examples of how to do things with those suggestions in mind, I would be happy to provide those suggestions too.

    Happy coding!

    P.S. Maybe worth noting that I did not actually run the example code that I wrote, so there may be minor typos that make it fail.  The point was more to show what calls would be used to get the information you seek
    Jake Rosenfeld - Modeling Team
  • eric_schimelpfenigeric_schimelpfenig Member Posts: 75 EDU
    I tried fooling around with this code, and it's just not clicking for me.(yet!)

    Ok, let's back up for a second, I think I get what you're saying here, but I'm realizing now that this is a little too much for me to get my head around right now.. That must be why you were trying to push me to use mate connectors!

    The mate connectors do require more input from the user, but less coding it seems.

    So I walked back the complexity of what I'm trying to do back to this:

    https://cad.onshape.com/documents/df9fc898ac3275697989261f/v/6dbeab89eb8a51eaef4306b4/e/b9b3376923d80f285da95cbe

    All this does is drop the sketch in at a point where the mate connector is. The code is simple and I understand it (mostly). I think I should just build off of here one little bit at a time.

    I tried "opextrude" on my sketch and I failed to make even that work which to me really says that I should get that out of the way before I try anything more complex.

    So here's where I'm at thinking wise:

    I had to "import" the sketch at the beginning of the code

    Then I had to ask the user stuff (placement, etc)

    Then I had to "instantiate" the sketch, which I understand to be "take the batter that's on deck and bring them to the plate" The "plate" being the transform I did, which was to transform it to the location of the mate connector the user picked.

    To do an extrude I need a face, or a close loop of lines to start extruding. I struggled to get the face fed into the opextrude command, and my only guess is that it's similar to "instantiate" in that I need to define the face, or "put it on deck" as it were?

    If I'm right is that something like "var this face from this sketch is now 'faceiwanttoexude.definition'?

    And then in the opextrue I can say the entity that I want to extrude is faceiwanttoexude.definition?



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

    You won't need to mess around with the definition to get your opExtrude working, the definition is just the way that all of the user inputs are exposed to the body of your custom feature.

    Continuing the baseball metaphor, I would almost think of the instantiator as trading a player from another team.  You know there is another team (part studio) out there, and you want a player from that team (the sketch).  Before you call `instantiate()`, you don't have that player, but after, you do.  You can make them into an outfielder or a pitcher or shortstop by changing the transform.  Because we have cloning technology, the other team doesn't lose the player, and you can clone as many of them as you want :smiley:

    Before you call instantiate, there is the sketch does not exist, and after you call instantiate, the sketch exists in the part studio.  Once it exists, you can query for it.  Querying is the biggest learning curve of FeatureScript, so don't be discouraged if it takes a while to click.  Here is what to do:

    const instantiatorId = id + "instantiate"; // Store this so you can use it later
    const instantiator = newInstantiator(instantiatorId);
    addInstance(...);
    instantiate(...);
    
    // Query for what the instantiator created (you may be thinking "but the instantiator didn't create the sketch!  It just copied
    // it from a different part studio!" From the point of view of this part studio, the sketch didn't exist, then the instatiator was
    // called, and then the sketch existed, so the instantiator created the sketch)
    const sketchFaces = qCreatedBy(instantiatorId, EntityType.FACE);
    
    // If you uncomment this, you can see the sketch faces highlighted in red, as well as a 
    // message in the FeatureScript console about which entities are being debugged
    // debug(context, sketchFaces);
    
    const extrudeId = id + "extrude";
    opExtrude(context, extrudeId, {
            "entities" : sketchFaces,
            "direction" : evOwnerSketchPlane(context, { "entity" : sketchFaces }),
            "endBound" : BoundingType.BLIND,
            "endDepth" : 1 * inch
        });
    
    // Uncomment to see extrude body highlighted in red
    // debug(context, qCreatedBy(extrudeId, EntityType.BODY));

    A few reasons why your code wasn't working:
    • Your "entities" query for the opExtrude was qSketchFilter(qEverything(EntityType.BODY), SketchObject.YES).  This will grab all the sketches in your part studio, so if there were existing sketches, it would get all of those too.  Additionally here you are getting bodies, when extrude is an operation that takes places on faces.
    • You were calling evOwnerSketchPlane on definition.placement.  definition.placement is a mate connector, and evOwnerSketchPlane only works on sketches.  If you want to get the z direction of a mate conntector you can call evMateConnector on it.
    Jake Rosenfeld - Modeling Team
  • Jake_RosenfeldJake_Rosenfeld Moderator, Onshape Employees, Developers Posts: 1,646
    Also just a small sidenote, there is nothing special about using `const` or `var` or anything like that.  It is just a way to store values for later.  This:

    const extrudeId = id + "extrude";
    opExtrude(context, extrudeId, ...);
    
    debug(context, qCreatedBy(extrudeId));
    will do exactly the same thing as:
    opExtrude(context, id + "extrude", ...);
    
    debug(context, qCreatedBy(id + "extrude"));

    The former just uses a variable (in this case a constant) to give a name to a commonly used piece of data, so that it is obvious that it is being used in more than one spot. 
    Jake Rosenfeld - Modeling Team
  • eric_schimelpfenigeric_schimelpfenig Member Posts: 75 EDU
    You aren't kidding about these queries being hard.... I got as far as the debug thing working, but I only have a vague understanding about how I got there. I was not able to get extrude to work...

    Ok, let me go through this line by line:

    (doc I'm working on)
    https://cad.onshape.com/documents/df9fc898ac3275697989261f/w/bdd0739a147106b6ab1dc4b1/e/b9b3376923d80f285da95cbe
    See questions in bold

    const instantiatorId = id + "instantiate"; // Store this so you can use it later
    Ok, this confuses me... "const" is "constant" which is basically something I can store. I get that, but how can I store an instantiated thing if it hasn't been instantiated yet (because that's happening in the next section). And that aside what is this line actually saying? Is the name of the thing I'm storing (the name I'll reference later) right after "const" or is it the name that's in the quotation marks? I'm guessing by the later debug line below that asks for "instantiatorId" that the "name" is the thing right after "const"

            
            const instantiator = newInstantiator(instantiatorId);
    As I said above, my understanding is that "const" stores stuff, and at this point I've already stored the instiantiatorID, so why am I doing it again here? If instantiate clones a thing (in this case a sketch) from one doc to another, isn't it already saved by way of instantiating it?
            addInstance(instantiator, TestNameSpace::build, {
                        "partQuery" : qSketchFilter(qEverything(EntityType.BODY), SketchObject.YES),
                        "transform" : toWorld(evMateConnector(context, { "mateConnector" : definition.placement }))
                    });
            instantiate(context, instantiator);
    This I think I get, "Look here for a thing, this is the thing I'm looking for(or this part of a thing), and put it there"

            const sketchFaces = qCreatedBy(instantiatorId, EntityType.FACE);
    This means "store the faces that were created by InstantiatorID to the name "sketch faces" so we can reference it later. I still don't understand how "const instantiatorId = id + "instantiate";" was able to get the faces I need, because this was done before the faces were created because that happens below in the code?

            debug(context, sketchFaces);

            const extrudeId = id + "extrude";
            opExtrude(context, extrudeId, {
                        "entities" : sketchFaces,
                        "direction" : evOwnerSketchPlane(context, { "entity" : sketchFaces }),
                        "endBound" : BoundingType.BLIND,
                        "endDepth" : 1 * inch
                    });

    I was never able to get the extrude to work, which I though was going to be easy since I got the face to debug... However as you can see I'm not totally clear on how I got there so I know I'm missing something really obvious...

    I'll keep hacking around at this.. And look, I really really appreciate this hand holding. 

  • eric_schimelpfenigeric_schimelpfenig Member Posts: 75 EDU
    edited November 2020
    I tried this too to see if I could pull off a super basic extrude, I also failed:

    https://cad.onshape.com/documents/df9fc898ac3275697989261f/w/bdd0739a147106b6ab1dc4b1/e/1ab937e72bf67d36ad89dbb2<br>
    FeatureScript 1403;
    import(path : "onshape/std/geometry.fs", version : "1403.0");



    annotation { "Feature Type Name" : "RSE" }
    export const myFeature = defineFeature(function(context is Context, id is Id, definition is map)
        precondition
        {
            annotation { "Name" : "Face to extrude", "Filter" : EntityType.FACE, "MaxNumberOfPicks" : 1 }
            definition.fte is Query;
            
        }
        {
            
        const theface = qCreatedBy(id + "definition.fte", EntityType.FACE);
        
        debug(context, theface, DebugColor.RED);
        
            opExtrude(context, id + "extrude1", {
                    "entities" : theface,
                    "direction" : evOwnerSketchPlane(context, {"entity" : theface}).normal,
                    "endBound" : BoundingType.BLIND,
                    "endDepth" : 1 * inch
            });
        });

    I clearly don't know how to query faces :)

  • NeilCookeNeilCooke Moderator, Onshape Employees Posts: 5,683
    I tried this too to see if I could pull off a super basic extrude, I also failed:

    https://cad.onshape.com/documents/df9fc898ac3275697989261f/w/bdd0739a147106b6ab1dc4b1/e/1ab937e72bf67d36ad89dbb2<br>
    FeatureScript 1403;
    import(path : "onshape/std/geometry.fs", version : "1403.0");



    annotation { "Feature Type Name" : "RSE" }
    export const myFeature = defineFeature(function(context is Context, id is Id, definition is map)
        precondition
        {
            annotation { "Name" : "Face to extrude", "Filter" : EntityType.FACE, "MaxNumberOfPicks" : 1 }
            definition.fte is Query;
            
        }
        {
            
        const theface = qCreatedBy(id + "definition.fte", EntityType.FACE);
        
        debug(context, theface, DebugColor.RED);
        
            opExtrude(context, id + "extrude1", {
                    "entities" : theface,
                    "direction" : evOwnerSketchPlane(context, {"entity" : theface}).normal,
                    "endBound" : BoundingType.BLIND,
                    "endDepth" : 1 * inch
            });
        });

    I clearly don't know how to query faces :)

    definition.fte is already a Query, so const theface does nothing. Therefore, in your opExtrude, just use "entities" : definition.fte

    also you will have to define a direction for the opExtrude since the face is not a sketch
    Senior Director, Technical Services, EMEAI
  • NeilCookeNeilCooke Moderator, Onshape Employees Posts: 5,683
    Have you tried running through the tutorial? https://cad.onshape.com/FsDoc/tutorials/create-a-slot-feature.html
    Senior Director, Technical Services, EMEAI
  • eric_schimelpfenigeric_schimelpfenig Member Posts: 75 EDU
    Hey @NeilCooke I realized that definition.fte was a query already, and "re-quering" it was redundant, but I was really just trying to practice calling things for use later.

    I tried it without the redundant query and I couldn't get the extrude to work:

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

    annotation { "Feature Type Name" : "RSE" }
    export const myFeature = defineFeature(function(context is Context, id is Id, definition is map)
        precondition
        {
            annotation { "Name" : "Face to extrude", "Filter" : EntityType.FACE, "MaxNumberOfPicks" : 1 }
            definition.fte is Query;
            
        }
        {
            
       // const theface = qCreatedBy(id + "definition.fte", EntityType.FACE);
        
        debug(context, definition.fte, DebugColor.RED);
        
            opExtrude(context, id + "extrude1", {
                    "entities" : definition.fte,
                    "direction" : evOwnerSketchPlane(context, {"entity" : definition.fte}).normal,
                    "endBound" : BoundingType.BLIND,
                    "endDepth" : 1 * inch
            });
        });

    The face debugs (I can see it in red) but the extrude never happens...

    I did run through that tutorial, and maybe this is just me but it didn't really click for me. The only programming experience I have using BASIC back in the day, and now the programming I do is in G Code, which is very similar, and like BASIC very linear, as in things happen in order. I can hack around and do basic things in Arduino too, but only on a limited basis. The tutorial, while good isn't a challenge. It's just a walkthrough. This is why I'm trying to setup a challenge for myself so I can really get my head around how FS works. 

    FS seems like it's a very different way of thinking when compared to coding in BASIC or G Code. I had a similar thing happen years ago when I decided to learn Fusion, and then Onshape... Perviously I had extensively used AutoCAD and SketchUp, both direct, non timeline based modelers. Moving to Fusion/Onshape was really hard for me, but one day/model it all came together and I got it. I'm hoping that I can get there with FS, I just need to break my thought process again :)

    I'm good at Onshape modeling, and I'm not at a point where the complexity of my models really demands automating repetitive tasks so that's why I'm trying to learn this.
  • Jake_RosenfeldJake_Rosenfeld Moderator, Onshape Employees, Developers Posts: 1,646
    edited November 2020
    @eric_schimelpfenig

    Firstly, as Neil said, in your example you should just be able to do:
    opExtrude(context, id + "extrude", {
            "entities" : definition.fte,
            "direction" : vector(0, 0, 1) // You could replace this with evOwnerSketchPlane, or evPlane, but it's only safe to do if you add `&& SketchObject.YES` or `&& GeometryType.PLANE` (respectively) to the "Filter" of the annotation in the precondiditon,
            ... endBound and endDepth look fine
        });

    To answer your confusion above:

    const instantiatorId = id + "instantiate"; // Store this so you can use it later
    This is not storing the faces.  It is storing an id, or an "identifier" which you will use both to create your faces, and then to find them later.

    const instantiator = newInstantiator(instantiatorId);
    You are not storing the `instantiatorId` again here.  You are storing the instantiator itself, the object that is responsible for doing the work to get the sketch into your part studio.  Notice that you are passing the `instantiatorId` into the new instantiator.  What you are saying here is "create a new instantiator, and give it an identifier (`instantiatorId` which is equal to `id + "instantiate"`) that can later be used to find all of the things that this instantiator creates"

            addInstance(instantiator, TestNameSpace::build, {
                        "partQuery" : qSketchFilter(qEverything(EntityType.BODY), SketchObject.YES),
                        "transform" : toWorld(evMateConnector(context, { "mateConnector" : definition.placement }))
                    });
            instantiate(context, instantiator);
    As you mentioned above, you get this code already.  The important thing to see is that you are passing `instantiator` into both of the calls.  So what you are saying first is "add this specific instance to the instantiator" (you could do this many times if you wanted many sketches).  Then finally you are saying "let the instantiator do all the work to bring the instances I identified with `addInstance` into the part studio".  It is not until this point (calling `instantiate` on the `instantiator`) that anything new is added to your part studio.

    const sketchFaces = qCreatedBy(instantiatorId, EntityType.FACE);
    This means "store the faces that were created by InstantiatorID to the name "sketch faces" so we can reference it later. I still don't understand how "const instantiatorId = id + "instantiate";" was able to get the faces I need, because this was done before the faces were created because that happens below in the code?
    const instantiatorId = id + "instantiate" does not get any faces.  It is just storing an id.  The id is then passed to the instantator in the next line: const instantiator = newInstantiator(instantiatorId).  Passing the id to the instantiator is akin to saying "when you eventually create things, create them under the identifier `id + "instantiate"`".  Then when you say const sketchFaces = qCreatedBy(instantiatorId, EntityType.FACE), you are saying "find all the things created under the identifier `id + "instantiate"`".


    I was never able to get the extrude to work, which I though was going to be easy since I got the face to debug... However as you can see I'm not totally clear on how I got there so I know I'm missing something really obvious...
    The best place to look for why the extrude is failing is in the FeatureScript console.  If you see a problem there, you can screenshot and post here, and we can help interpret wheat is going wrong.



    I want to return to my simple example above, in attempt to show how there is nothing special about variables.  In this contrived example, I will do an extrude, and then immediately delete the body created by that extrude:
    // -- Using variables --
    // Only special thing about using `const` vs using `var` is that by using `const`, I am prevented from doing `extrudeId = ... something else;` later.
    const extrudeId = id + "extrude";
    opExtrude(context, extrudeId, {...});
    const bodyCreatedByExtrude = qCreatedBy(extrudeId, EntityType.BODY)
    
    opDeleteBodies(context, id + "delete", { "entities" : bodyCreatedByExtrude });
    
    // -- No variables --
    opExtrude(context, id + "extrude", {...});
    
    opDeleteBodies(context, id + "delete", { "entities" : qCreatedBy(id + "extrude", EntityType.BODY )});<br>
    The two pieces of code above to do exactly the same thing.  Setting some variables along the way just helps us organize the code into logical segments, separating things out so that we are just doing one thing at a time. 

    Additionally, setting variables can ensure that no mistakes are made.  By doing `const extrudeId = id + "extrude"`, and then using `extrudeId` in both spots where it is used ensures that both of the spots are definitely using the same identifier.  In the second example I could easily make a typo, so that the first time I put `id + "extrude"` and the second time I accidentally type `id + "extrudee"`, which would then cause opDeleteBodies to fail because the query does not specify any bodies, because `id + "extrudee"` was not an identifier used to create anything.

    And that aside what is this line actually saying? Is the name of the thing I'm storing (the name I'll reference later) right after "const" or is it the name that's in the quotation marks? I'm guessing by the later debug line below that asks for "instantiatorId" that the "name" is the thing right after "const"
    When you say `const <name> = ... some data` or `var <name> = ... some data` you are assigning a name (on the left of the equals sign) to the data on the right of the equals sign.  This can be done with any piece of data:

    const four = 2 + 2;
    var someInches = 2 * inch;
    someInches = 1 * inch; // since it was declared as `var`, not `const` it can be reassigned
    const sentenceWithAllTheLetters = "The quick brown fox jumped over the lazy dog";
    

    The point is to take a piece of data, and give it a name so it's clear what that data is supposed to represent, and the data can be used in multiple spots later without having to recreate the data every time.
    https://en.wikipedia.org/wiki/Variable_(computer_science)
    Jake Rosenfeld - Modeling Team
  • Jake_RosenfeldJake_Rosenfeld Moderator, Onshape Employees, Developers Posts: 1,646
    Hey @NeilCooke I realized that definition.fte was a query already, and "re-quering" it was redundant, but I was really just trying to practice calling things for use later.
    The re-query in question: `const theface = qCreatedBy(id + "definition.fte", EntityType.FACE);`

    This does not work because you are just telling the system to look for things created by the identifier "definition.fte".  Putting it in quotes like this just makes it a literal string, with the text "definition.fte", this does not have any link back to the actual definition itself.

    Doing something like `definition.fte` looks inside the `definition` map for the the field called `"fte"`.  Doing something like `"definition.fte"` just makes a string containing that text, and does not have anything to do with the `definition` variable itself.

    The identifier that creates the entities that are selected by the user is not available to you at all; you will not be able to/will not need to use `qCreatedBy` to find entities that are manually selected by the user because the system just passes you an appropriate query for those entities in the form of `definition.fte`.  The point of qCreatedBy is to find entities that were created during the execution of your feature, which you have access to the id of (since you are the one who created and specified that id as part of the call that creates the entities)

    If you did want to store definition.fte for use later under a simpler name, the right thing to do would just be:
    const theface = definition.fte;
    Jake Rosenfeld - Modeling Team
  • eric_schimelpfenigeric_schimelpfenig Member Posts: 75 EDU
    Ok, I made a little progress in understanding, but ultimately failed (for now)

    I think I'm getting the variables a bit more, I'm still fuzzy on some parts though, so this:

    const sketchstorage = id + "instantiate"; // Store this so you can use it later

    We're saying here "const sketchstorage..." this is essentially making an empty "file" into which we'll put stuff later. then we get to this "=id..." so I'm guessing we need to say that the variable "sketchstorage" is an "ID"? but then why the " + "instantiate""? Is the word instantiate there because we're saying we're going to put something into "sketchstorage" that is from an instantiator? or is the stuff in the quotations any text we want? (if so that's very confusing!)

    (note that I changed the variable name because I had too many version of things that had "instantiate" on them and my brain was hurting)

    const instantiator = newInstantiator(sketchstorage);

    This one is starting to make a little more sense too, so "const instantiator" is making another variable into which we put the actual instantiator which is "newInstantiator" and whatever we get from that we store in "sketchstorage" which I declared above.

    addInstance(instantiator, TestNameSpace::build, {
                        "partQuery" : qSketchFilter(qEverything(EntityType.BODY), SketchObject.YES),
                        "transform" : toWorld(evMateConnector(context, { "mateConnector" : definition.placement }))
                    });

    Now that we have a "place" to store what the instantiator is getting we have to "do the thing" which in this case is querying for the thing we want. So we start with a filter that catches all sketches, then bodies (which doesn't make sense, sketches aren't bodies but I'll go with it) then from the third level of the filter we ask for SketchObjects.YES. The result is one sketch because there is only one sketch in that part studio we're coming from.

    The transform line puts it on the mate connector we got from the user that has a variable of "definition.placement"

    Lastly we do this:

    instantiate(context, instantiator);

    Which is DO IT (but only do it in the object instiantator, don't "do it" anywhere else)

    Ok, so on to the extrude which is where I had a sliver of luck. First the code that did work:

    const extrudeId = id + "extrude";
            opExtrude(context, extrudeId, {
                        "entities" : sketchFaces,
                        "direction" : vector(0,0,1) ,
                       // "direction" : evPlane(qEverything(EntityType.FACE),SketchObject.YES),
                        "endBound" : BoundingType.BLIND,
                       "endDepth" : 1 * inch
                    });

    "direction" with that vector setup worked, I'm guessing because it's about as basic as it gets. So then I tried following your advice about using evPlane. I falied.

    I tried a few things, one in the commented out line above, and something like this:

     "direction" : evPlane(context, {
                               "face" : definition.placement
                       })
    The thinking here was that it says in the help popup for EV plane that you can use a mate connector, and we already asked the user for one so I thought I'd try that.

    I also tried this:

        "direction" : evPlane(context, {
                               "face" : sketchFaces
                       })

    The logic there being that if this worked:

     debug(context, sketchFaces);

    Why couldn't I just extrude using the same method? Obviously not :) 
  • Jake_RosenfeldJake_Rosenfeld Moderator, Onshape Employees, Developers Posts: 1,646
    Hi @eric_schimelpfenig !

    I don't think that thinking of the id as a "file into which we'll put stuff into later" is a great analogy.  The id passed into an operation is more like a label that you are using to identify all the changes made by that operation.  You can think of the pipeline here as:
    1. Choose an id
    2. Tell the system to do something, using that id as a label for all those changes that are going to be made
    3. Ask the system what changes were made with that label
    So it's not as if the id is some kind of magic box that your sketch is being put into.  Really your sketch is just being put into the Part Studio, but it's as if it has a sticker on it that says "I was created using this identifier: ..."

    Let's address some of the specific confusions you've laid out:
    We're saying here "const sketchstorage..." this is essentially making an empty "file" into which we'll put stuff later. then we get to this "=id..." so I'm guessing we need to say that the variable "sketchstorage" is an "ID"? but then why the " + "instantiate""? Is the word instantiate there because we're saying we're going to put something into "sketchstorage" that is from an instantiator? or is the stuff in the quotations any text we want? (if so that's very confusing!)
    First confusion here is how the language interprets `const/var name = a + bunch - of / operations`.  The right side of the equals sign is always evaluated before being stored as a variable.  So if it makes more sense:
    const sketchId = id + "thisCanBeLiterallyAnythingYouWant" // <- answering another question here: it can be anything; does not need to mention instantiator
    // is always evaluated as:
    const sketchId = (id + "thisCanBeLiterallyAnythingYouWant");

    To clear up what the `id + "..."` part means, let's look at what a blank custom feature looks like:
    annotation { "Feature Type Name" : "My Feature" }
    export const myFeature = defineFeature(function(context is Context, id is Id, definition is map)
        precondition
        {
    
        }
        {
    
        });
    The `(context is Context, id is Id, definition is map)` are the three variables that are being passed to your feature by the system.
    • `context` represents the state of the part studio.  It is what you are changing when you instantiate with the instantiator, perform an extrude, or do anything else that changes the state of the part studio.  It is also the thing that you ask questions of when running things like evOwnerSketchPlane or evPlane.
    • `id` is the feature id.  Each top level feature in the feature list has its own id in the system.  If you run `println(id)`, you will see that the feature id is just a randomly generated string, like "F_ajkfdhasjdkfhas".  I will come back to more description on this below.
    • `definition` is the set of parameters specified by the user in the feature dialog.
    The reason I point this out is to address the confusion of what it means to do `id + "someAdditionalString"`.  The `id` piece is important; it contains the root feature id of the entire feature, which allows the system to know which geometric entities were created and edited by which features in the feature list.  Doing `id + "someAdditionalString"` is a way of creating a more specific label, saying "the entities I am about to create belong to this feature as a whole, but more specifically to the 'someAdditionalString' part of the feature".

    An example that may help your understanding:
    opExtrude(context, id + "groupA" + "1", {...});
    opExtrude(context, id + "groupA" + "2", {...});
    
    opExtrude(context, id + "groupB" + "1", {...});
    opExtrude(context, id + "groupB" + "2", {...});
    
    // Show all the entities created by the first extrude
    debug(context, qCreatedBy(id + "groupA" + "1"));
    
    // Show all the entities created in group B
    debug(context, qCreatedBy(id + "groupB"));
    
    // Show all the entities created by this entire feature
    debug(context, qCreatedBy(id));
    Here we see that using `+` on an id creates a more specific label the is a subset of the label on the left hand side of the `+` sign.

    All the entities created by the whole feature will have the label of `id`.  All the entities created by the first two extrudes still have the label `id`, but also have the label `id + "groupA"`, and only entities from the second extrude has the label `id + "groupA" + "2"`, but of course those extruded entities also have the labels `id + "groupA"` and just `id`, because the ids exist in the hierarchy I am describing.

    The code above is also identical to:
    const groupAId = id + "groupA";
    const groupAExtrude1Id = groupAId + "1";
    opExtrude(context, groupAExtrude1Id, {...});
    opExtrude(context, groupAId + "2", {...});
    
    const groupBId = id + "groupB";
    opExtrude(context, groupBId + "1");
    opExtrude(context, groupBId + "2");
    
    debug(context, qCreatedBy(groupAExtrude1Id));
    debug(context, qCreatedBy(groupBId));
    debug(context, qCreatedBy(id));
    
    I made a working example of this using the opSphere call here: https://cad.onshape.com/documents/0c9e3ab2635f23cc5a740e99/w/472a9f18fee23f41be91ceb9/e/a57f1ca04f7ed0c041fb1dae

    To be continued... There are a few more things I want to answer, but I think this comment covers ids nicely.

    Jake Rosenfeld - Modeling Team
  • Jake_RosenfeldJake_Rosenfeld Moderator, Onshape Employees, Developers Posts: 1,646
    Now that we have a "place" to store what the instantiator is getting we have to "do the thing" which in this case is querying for the thing we want. So we start with a filter that catches all sketches, then bodies (which doesn't make sense, sketches aren't bodies but I'll go with it) then from the third level of the filter we ask for SketchObjects.YES. The result is one sketch because there is only one sketch in that part studio we're coming from.
    All faces, edges, and vertices MUST exist on a body.  There is no way around it.  What we nicely abstract as a "sketch" for the end user is actually just a collection of wire bodies (each sketch edge is a wire body that is disconnected from all the other edges in the sketch) and sheet bodies (the sketch regions that you can click on).  There may also be point bodies, if the user has placed any sketch points.  "Bringing in the sketch" from your part studio is actually just "Bringing in all of the bodies that form the sketch".

    Which is DO IT (but only do it in the object instiantator, don't "do it" anywhere else)
    Not quite sure what you meant here, but what's happening here is that `addInstance` doesn't actually DO anything to your part studio.  It just tells the instantiator "when it comes time for you to do your job, make sure you bring in these entities at this position".  Then `instantiate` tells the instantiator to actually bring all of those instances into the Part Studio.  A simple way of thinking of it is that `addInstance` adds instances to the instantiator (which is why it takes the instantiator as a parameter), and `instantiate` adds those instances to the Part Studio (which is why it takes context as a parameter).

     "direction" : evPlane(context, {
                               "face" : definition.placement
                       })
    "direction" takes a Vector, not a Plane. evPlane returns a Plane ( https://cad.onshape.com/FsDoc/library.html#evPlane-Context-map https://cad.onshape.com/FsDoc/library.html#Plane ), so `evPlane(context, {"face" : definition.placement }).normal` would fix this.

        "direction" : evPlane(context, {
                               "face" : sketchFaces
                       })
    Same here, the plane call should work fine, you just need to extract the normal from the Plane that the call returns.

    It would be useful to screenshot the error messages that appear when the extrude (or anything else) does not work, and include them in your comment, so that I can explain what those errors mean.  For example, if I do either of the direction calls that you have above, the error message looks like:

    Which explains that opExtrude was expecting a Vector, but received a Plane instead.  Clicking on the second line in the call stack (the line that says "35:9 Feature Studio 1"), will even bring you to the call that is failing in your Feature Studio.

    Jake Rosenfeld - Modeling Team
  • eric_schimelpfenigeric_schimelpfenig Member Posts: 75 EDU
    Ok, I read over these posts a few times (THANK YOU) and I took another crack at it:

    https://cad.onshape.com/documents/df9fc898ac3275697989261f/w/bdd0739a147106b6ab1dc4b1/e/5e412733a40ae3e1296e5bb5

    I tried as much as possible to figure this out by not looking at my previous attempt, which is why you'll see some of the names of things changed. I really wanted to avoid the temptation of copy/pasting stuff without understanding what it's doing.

    I got the code to (compile?) with no errors, but it doesn't work in my part studio:



    That error message is... greek to me... 

    I've got to be missing something dumb... I re-read the previous posts several times and if the answer is there (I'm sure it is) I'm just not seeing it...
  • MBartlett21MBartlett21 Member, OS Professional, Developers Posts: 2,050 ✭✭✭✭✭
    edited November 2020
    @eric_schimelpfenig

    The error message is saying that there should either be no transform (definition.transform == undefined) or the transform field should be a Transform [1] (definition.transform is Transform). At the moment, you are providing it with a Vector [2], which doesn't match either.

    [1]: Transform
    [2]: Vector

    In order to turn your vector into a Transform, you can use the function transform(translation is Vector) [3].

    E.g: "transform": transform(vector(0, 0, 1)),

    [3]: transform(Vector)
    mb - draftsman - also FS author: View FeatureScripts
    IR for AS/NZS 1100
Sign In or Register to comment.