Understanding how Fusion uses units is very important in successfully using the API. When using the API the units used by Fusion are always consistent. Getting and setting values always use the internal unit type for that category of units. These unit types are known as database units because these are the same units that Fusion uses internally and how data is saved in the file. There are some differences in the unit types used in Design and CAM. For Design, these units are:
Lengths - Centimeters (cm)
Angles - Radians (rad)
Mass - Kilograms (kg)
For CAM, the units used by the API are:
Length - centimeters (cm)
Angle - degrees (deg)
Linear velocity – millimeters per minute (mm/min)
Rotational velocity - revolutions per minute (rpm)
Time - seconds (s)
Weight - kilograms (kg)
Power - Watts (W)
Flow rate - liters/minute (l/min)
Area - square centimeters (cm²)
Volume - cubic centimeters (cm³)
Temperature - degrees Celsius (C)
The internal units always use these types without any exceptions. For example, if you call the SketchCurve.length property to get the length of an entity in a sketch, the value returned will always be in centimeters. If you use the Vector3D.angleTo method to measure the angle between two vectors, the resulting angle will always be returned in radians (remember that π radians equals 180 degrees). At first this might not seem ideal because you might want to work in other units. However, this actually makes most things easier because you can always count on the units being consistent and don't have to worry about the current active unit which the user can change. You just write your program to work in the units listed above and it will always work as expected regardless of the active units. The only time you need to worry about unit conversions is when you need to interact with the user by having them enter a value or to display a value.
Units become a bit more complicated when interacting with the user. This is because of several reasons. First, the user can choose one of several length units as the default as shown below. This has the side effect of also setting the default mass units. For example, if you choose inches the mass unit is pounds, but if you choose centimeters it is grams. Angles for the user are always in degrees.
A second reason working with the user is more difficult is because when the user enters a value the result is a string that needs to be evaluated to make sure what they entered is valid and then interpreted into a real value. A third reason is that what they enter doesn't necessarily have to be a simple value. Here are three examples of valid entries when specifying the depth of a hole:
Because the user has a lot of flexibility in the way they can specify values and because they can also change the active unit it would be difficult to write code to correctly interpret any string entered by the user. To help with this, the API supports some utilities that convert a user string into internal units. This allows you to take any of the strings in the example above and convert them to a distance value in database units (centimeters).
This is also how Fusion works internally. Any time a user enters any data, it is a string and Fusion has to parse the string and figure out if it's valid and what the real value is. It converts the string into the real value in database units and uses that for all operations within Fusion. If a value needs to be displayed to the user, a string is created that is based on the current active unit and other unit settings and displayed to the user.
The UnitsManager object supports functions that make working with units much easier. The code below prompts the user to enter a length using the input box. The input box allows the user to enter any string without any expectation on what the string represents. The code then validates that the entry is a valid length expression and then displays the evaluated results in centimeters.
# Prompt the user for a string and validate it's valid. isValid = False input = '1 in' # The initial default value. while not isValid: # Get a string from the user. (input, isCancelled) = ui.inputBox('Enter a distance', 'Distance', input) # Exit the program if the dialog was cancelled. if isCancelled: adsk.terminate() return # Check that a valid length was entered. unitsMgr = design.unitsManager if unitsMgr.isValidExpression(input, unitsMgr.defaultLengthUnits): realValue = unitsMgr.evaluateExpression(input, unitsMgr.defaultLengthUnits) isValid = True else: # Invalid expression so display an error and set the flag to allow them # to enter a value again. ui.messageBox('"' + input + '" is not a valid length expression.', 'Invalid entry', adsk.core.MessageBoxButtonTypes.OKButtonType, adsk.core.MessageBoxIconTypes.CriticalIconType) isValid = False # Use the value for something. ui.messageBox('Input: ' + input + '\nResult: ' + str(realValue) + ' cm')
When you get a value from Fusion or compute it yourself and need to display it to the user you can use the UnitsManager object to format the value so it's in the correct unit and has the correct number of decimals based on the current user preferences.
# Display the result using the current active units.
unitsMgr = design.unitsManager
displayLength = unitsMgr.formatInternalValue(length, unitsMgr.defaultLength, True)
ui.messageBox('The length of all edges in the selected body is: ' + displayLength)
The length passed in is always expected to be in database units (centimeters) and the formatInternalValue method formats it to be in the specified units. The most common thing to do will be to use the default length, which is what this sample is doing. If you need to specify another unit type besides the default length you can specify it by name.
Most of the functions available on the UnitsManager object have arguments where you need to specify a unit. Units are specified using a string and use the same strings you use when specifying units for a parameter, although the UnitManager is more flexible in how you can combine the units. For example, Fusion won't let you create a parameter for an acceleration by using a combination of existing known unit types such as "m/s/s" or "m/s^2". However, these are valid unit descriptions when used within the UnitsManager. The sample below defines the units using meters and seconds so the result returned is "0.20 m / s^2".
result = unitsMgr.formatInternalValue(20, 'm/s/s', True)
The last argument indicates if the unit specifier should be appended to the result or if it should just be the value. This ends up displaying the dialog shown below.
When discussing units it's important to understand their use with respect to parameters; both editing existing parameters and when creating new objects that rely on parameters. For example, when you create an extrusion that is a defined depth there is a parameter automatically created that controls the depth of the extrusion. In the user interface you enter the depth in the Extrude dialog, which is really a string that is used as the expression of the parameter that will be created. When creating an extrusion using the API there is a little more flexibility in specifying the depth. You can mimic the user interface and provide a string that defines a valid length but the API also supports specifying a real value, which is always in database units (centimeters in this case since it is a length). To be able to specify either a string expression or a real value for the depth, Fusion supports an object called ValueInput that is used any time you're providing a value that will become a parameter. A ValueInput object is a relatively simple object that contains either a real value or a string. When creating a ValueInput object you can use either the ValueInput.createByReal or ValueInput.createByString methods. If you create a ValueInput using a string then the ValueInput will be evaluated the same as other strings entered by the user, as described above. If you create a ValueInput using a real value then the value is assumed to be in database units of whatever unit is needed. For example, in the case of the extrusion the value will be assumed to be in centimeters since the extrusion depth is a length. You can use either option depending on the data you have. If you have a string provided by the user or for some reason want to define an equation you can use createByString. If you've calculated a value then you can use createByReal. The code below demonstrates this. The profile and the part depth have already been obtained.
# Create an extrusion input to be able to define the input needed for an extrusion. extrudes = rootComp.features.extrudeFeatures extInput = extrudes.createInput(prof, adsk.fusion.FeatureOperations.NewComponentFeatureOperation) # Define that the extent is a distance extent and is half the depth of the part. distVal = adsk.core.ValueInput.createByReal(partDepth / 2) distExtentDef = adsk.fusion.DistanceExtentDefinition.create(distVal) extInput.setOneSideExtent(distExtentDef, adsk.fusion.ExtentDirections.PositiveExtentDirection) # Create the extrusion. extrude = extrudes.add(extInput)
The ValueInput object serves as a way to pass in either a string or a real value through a single argument. If you pass in a string, that string is used as the equation of the parameter that's created. This means it can include references to other parameters, functions, etc. If you pass in a real value, an equation is computed by Fusion and displayed in the parameters dialog.
When changing the value of a parameter using the API, you have the option of using the expression property, which is a string and is the same as the user using the Parameters dialog to change the value of a parameter. Using the expression property you can enter any valid parameter expression. Internally, Fusion takes this string and evaluates it to make sure it is valid. In addition to setting the expression, the API also supports setting a parameter using a real value through the valueInput property. The real value is assumed to be in database units for whatever unit type is associated with the parameter, and directly assigns it to the parameter. This will override the existing expression and Fusion will create the equivalent expression to display in the dialog. The value shown in the "Value" column is actually the real internal value converted into a string using something similar to the formatInternalValue method discussed above. It's shown in the current document default unit with the number of decimals specified in preferences. It's only through the API that you have direct access to read and write the internal value.