FeatureScript is an integral part of Onshape, and in my eyes is key to what makes Onshape unique (aside from the whole cloud thing). I like to tout how simple it is to get started writing FeatureScript, because it really is. There's nothing to install, download, or setup. New Tab > Create Feature Studio, two clicks and you're writing FeatureScript. While it is easy to get started, the documentation for FeatureScript makes one big assumption: you already know how to program. For professional developer like myself, this isn't an issue, I have plenty of transferable programming experience and a university degree. But many people trying FeatureScript are designers who might be programming for the first time ever. Learning FeatureScript requires learning how to program, which entails learning how to debug. The biggest hurdle for a new FeatureScripter is learning how to debug, which can be frustrated without a bit of wisdom and experience. I have heard debugging described as solving a murder mystery where you are both detective and murderer. I've written this guide not as a comprehensive textbook, but rather as a quick start guide for someone who is both new to FeatureScript and programming in general. Hopefully the steps, tips, and tricks and help make FeatureScript even more accessible.
Syntax Errors
The first type of error you are likely to encounter is a syntax error. Programming has rules, and those rules need to be followed exactly. When something unexpected appears in your program, the computer cannot fix it, so it complains until you fix it. You'll recognize it quickly by the little red squiggle under your text.
The first tip here is for seeking out missing brackets. Its pretty common to type the start bracket, but not the closing bracket. For large loops and if statements, its easy to accidentally delete a bracket, and if you copy paste enough, you're sure to copy a start but not an end. If you are looking for the pair of brackets, but putting your cursor on one bracket, the other one will have a little box placed around it. When I write code, I tend to type both brackets in before typing the code that goes inside the brackets. Prevents me from forgetting to close things up.
The second tip is to prevent a FeatureScript from getting messy in the first place. Well formatted code makes it easier to know if you are copying incomplete brackets, or accidentally deleting the wrong one. The FeatureScript editor comes with this magic button (the brush) that formats your code for you. Its magic, use it regularly. It makes everything better.
FeatureScript Notices
You've written some code, and you've added your Feature to your Part Studio, but you now have a red feature. Something is wrong, but what? You read the notices! Look for the exclamation mark at the top right of the Part Studio
Clicking this opens up a tab at the bottom of the screen. All of your error messages and text output for your feature will appear here. Opening the tab, I can see two messages: the blue message is an information level message letting me know that I have an unused variable in my Script. While this won't prevent the feature from running, it might indicate that I've made a mistake in my code leaving a variable declared but unused.
Below the information messages, I have the red highlighted error message. This is what is causing my feature to appear red in the Feature tree. This error is preventing my Feature from running. The error consists of a message: "Can not add number and ValueWithUnits (map)", and a stack trace below it. This message is telling us what is wrong: that we are trying to add two things together, one a number, and another a value with units, which cannot be added together. Most of the time, reading the error message explains the error. If it doesn't, read it again.
Below the message is the stack trace. The stack trace helps us understand where in the code the error came from, and how it got there. A stack trace drills down from the bottom up. Part Studio 1 - This is the tab the error is in
Part Studio 1 (My Feature 1) - The feature My Feature 1 is causing the error
56:17 onshape/std/feature.fs (defineFeature) - (this line can generally be ignored)
16:17 Feature Studio 1 (const myFeature) - This line is showing us that the error happens in the tab Feature Studio 1, on line 16, at character 17 from the left, which is inside of the function myFeature
Clicking any line in the stack trace will bring us to that line of code, making it easy to find where the error happened. Sometimes the error can happen in the Onshape std library if you give bad inputs into a std library function. Typically, I start looking at the first line of the stack trace that does not start with onshape/std/ , as I want to know where my own code is throwing an error. Onshape most likely did not let any bugs slip into std code.
When a feature calls a function, and that function calls a function, all of those layers are shown in the stack trace. If a function is called from multiple places, this can help drill down to which function was the source of the error.
While this shows where the error is ultimately found, it doesn't always show the source of the bug. In my example, the error is adding together incompatible types, but the bug is that one of my values is different than expected. At some point before that error, I have assigned the wrong value to a variable - and that is the bug that needs to be fixed.
Println
The println statement is as old as time itself. Some even believe our universe started when someone wrote println("hello world") into the universe console.
In compiled languages like C#, debug tools can run a program line by line, allow you to stop at any point, look at what values are set in memory, and even rewind, in order to help track down bugs. Unfortunately, FeatureScript does not have this power. In order to see what value a variable has at some point in the code, we have to write code to understand our code.
The function println() does a toString() of the input, appends a new line character, and outputs it to the FeatureScript notices tab. This allows us to "see" a value at a point in time. We can check our expectations vs reality by using the println() statement to either print the value of a variable, or even meta data such as just tags to check if a section of code has been run.
Println is a valuable debug tool, even just to keep your sanity. Here are a few tips:
Printing simple and unique strings can help determine if the code in a function even ran, like little checkpoints
println("My function name")
Instead of just printing the value, printing a label to go with it can help keep things organized in your output
println("Var a = "~toString(a))
Getting unexpected results? Sanity check the inputs of the function or operation by printing all of the inputs
Debugging Geometry and Queries
Println really only works for data that can be represented with strings and numbers. FeatureScript is unique in that the output is geometry. It becomes a new challenge in validating what a query actually is vs what we expect it to be. But we are given some unique tools to help debug.
AddDebugEntities - this function highlights the given query when the Feature Dialog is open. Most importantly, it highlights the entities as they are at that point in the code. This function gives as snap shot of what is contained within a query. This is incredibly useful for validating that a query is pointed at the right entities. Whenever a write a complex query, I will double check my code by running an addDebugEntities and verifying that my query is correct. Catching an incorrect query later on in my code is tricky. Is my boolean failing because of logic or because of the query?
Clean and robust code should validate and filter entities. Lets take for example a opBoolean. We are going to be putting in solid bodies to join into a single body. Using the query qEverything(EntityType.BODY) , we get a failure. But why? That query contains all bodies, but does not check if they are solid bodies. Before we call opBoolean we can improve our query:
In this section of code, we have filtered our query to just solid bodies, and before running the boolean, we check if the query is empty. If the query is actually empty, it might be good to do an else statement and either display a message or run some alternate code. In a similar fashion, we can also verify single entity queries with a qNthElement(myQuery, 0);
Here's a few more tricks to working with queries:
Check the documentation of the operation for what query type it is expecting.
opBoolean does work on sheet bodies, but not faces. Mixing up up face queries and sheet body queries is a common mistake.
qOwnerBody and qOwnedByBody can both be used to go between body and face/edge/vertex queries.
Tips and Tricks
Most bugs are made either from simple mistakes or bad assumptions. Sounds deep, but in reality this is what I look for first. If I had a nickle for each time I used Entity.BODY instead of Entity.FACE. Knowing that you are likely to make a simple mistake, I know to look for things like:
copy pasting a line of code, but not switching a variable
using auto complete for a query, but not using the right entity type
did I validate that the query was non empty?
Test early, test often. When designing a model in the feature tree, you have the ability to validate what you are doing each step of the way. Its a good idea to do the same when writing a FeatureScript. Stopping to check your output after each context modifying operation helps development go smoothly.
Adddebugpoint and adddebugarrow are both super helpful for understanding vectors, lines, and planes. Like debugentities, it lets you visualize in Onshape what the value is at a point in time. Its helpful for tracking down opposite direction issues, flipped planes, or bad vector math.
If after all that, you feel like giving up, shoot me an email. I have reasonable rates, and can deliver quality custom features quickly: caden.armstrong@smartbenchsoftware.com
Comments