Introduction to Python and Parametrics (topic)

Also see Python 3 upgrades for more information.


Python and Parametrics: Questions & Answers

Q. What is Python?

A . Python is the name of the programming language used to create parametrics in SDS2 programs . Python is also used for custom members and custom components and other plugins. New features are often introduced to SDS2 programs using Python coding, then converted to C++ when the new feature proves its utility. Plugins may be placed in the plugins folder in the data directory used by your current version of this SDS2 program, or they may be placed in the plugins folder in your current Job. In the event that you, for example, click an icon to invoke a plugin whose name can be found in both of these plugins folders, the SDS2 program starts up the plugin that is in the plugins folder in the Job. To find out what version of Python your program uses, enter the following at the SDS2 Python Prompt .

SDS2 Python Prompt

>>> import sys
>>> print(sys.version )
3.10.8 (tags/v3.10.8:aaaf517, Oct 11 2022, 16:50:30) [MSC v.1933 64 bit (AMD64)]

You need to know the version of Python used by SDS2 programs when purchasing books and viewing online materials . Python 2 and Python 3 are not compatible.

----------

Q. What is a parametric?

A . A parametric is a Python script that can be Run in Modeling to create members or materials or holes or bolts or welds, or to update member attributes , or to read in information about members or materials or holes or bolts or welds that already reside in the model. They are often created to manage and update metadata such as member status or to read member or material or Job custom properties or to edit member or material or Job custom properties: You can create such a script using a text editor and save the script as a plain text file with a .py extension. Custom members and custom components are parametrically generated.

----------

Q . What is parametrics?

A . Parametrics is the name of the application programming interface (API) used in SDS2 programs to create Python scripts called parametrics. Since parametrics is a Python-based API, it is made up of modules that define objects, object attributes, methods and functions.

----------

Q. Do SDS2 programs have other tools for developing parametrics?

The Python Prompt icon ( ) can be used in Modeling to launch the SDS2 Python Interpreter .

A . The Python Prompt is an interface for interactive communication with the Python interpreter. It can be used for testing Python code in the Modeling environment. You can use it with the built-in Python functions dir() and help() to find out information about modules and their methods and functions. You can use it to import entire modules , or to import methods from modules. Since the Python Prompt operates in Modeling , any operations that you perform at the Python Prompt will produce results that are equivalent to a Python script that is Run in Modeling .

----------

Q. Does a Python script have a basic pattern or form?

A . A parametric is a Python script. Generally a script begins with startup code . It may then feature a dialog, which allows the user who Runs the parametric to specify desired parameters for the operation to be performed. The operational code, which usually requires input from the startup code and the dialog, generally runs last and therefore is placed toward the end of the script. The operational code does the work of actually generating parametric results such as creating materials, or changing member attributes, or reporting back information.

----------

Q. Speaking of startup code. What's up with that * symbol?

# startup code begin
from param import *
Units("feet")
from point import Point, PointLocate
from member import Member, MemberLocate
from cons_line import MtrlLocate
from rnd_plate import RndPlate
from rect_plate import RectPlate
from rolled_section import RolledSection
from weld_add import Weld
# startup code end
An example of some startup code.

A . In an import statement, an asterisk (*) may be used as a "wild card" to import the contents of a module . In other contexts within a Python script, the asterisk may be used as the multiplication operator. In the above example, from param import * imports all methods from the param module, which is a module that includes methods for prompting and dimension units. On the other hand, statements in the form from __ import __ specify that particular functions be imported from a particular module. For example, the statement from member import Member, MemberLocate imports Member() and MemberLocate() from the member module.

----------

Q. Why is startup code needed?

SDS2 Python Prompt
>>> Shape("W10x33").Depth
Traceback (most recent call last):
   File "<console", line 1, in ?
NameError: name 'Shape' is not defined
>>> from shape import Shape
>>> from param import Units
>>> Units("feet")
>>> Shape("W10x33").Depth
9.73000000000004
Shape("W10x22").Depth generates an error when Shape has not yet been imported from the shape module. After Shape is imported, Shape("W10x33").Depth returns the depth of the "W10x22" section, which is 9.73 inches.

A . Python has a number of built-in functions and methods that you can use without having to import anything. However, to get the capabilities that you need to parametrically generate members and materials in the SDS2 model, you need to import functions from modules that were created specifically for SDS2 programs.

Tip: If you are creating a script using a text editor , it is generally good practice to import only those functions that are needed.

----------

Q. Why import from a module? Why not just import the module?

SDS2 Python Prompt
>>> import math
>>> math.pi
3.1415926535897931
>>> pi
Traceback (most recent call last):
   File "<consolength,;", line 1, in ?
NameError: name 'pi' is not defined
>>> from math import *
>>> pi
3.1415926535897931
When the math module is imported, math.pi must be typed to call pi . When the contents of the math module is imported, typing pi calls pi .

A. Importing a module does give you access to everything in that module. However, to call a method from that module, you need to type the module name followed by a period followed by the method name ( math.pi ). On the other hand, when you import the entire contents of a module ( from math import * ) or a particular name from the module ( from math import pi ), you can type in the name by itself ( pi ).

----------

Q. What does Units("feet") do?

SDS2 Python Prompt
>>> Units("feet")
>>> Shape("W10x33").Depth
9.73000000000004
>>> Units("metric")
>>> Shape("W10x33").Depth
247.142
Units("feet") sets the output to be in decimal inches. Units("metric") converts the inch value to millimeters by multiplying that value by 25.4.
Note: Units and Shape were imported in a previous example. If they hadn't been, you would get an error instead of the results that are shown.

A . If your script returns any values related to distances, you also need to specify the Units("...") that you want distance values in your script to be interpreted as. Units("feet") or Units("inch") or Units("inch-fraction") or Units("inch-decimal") all require that decimal inches be used for distance entries to a script (or at the Python Prompt ). Units("metric") requires that millimeters be used for distance entries to a script.

Tip: The Units("...") set in the script do not have to match the " Units " set in Drawing Presentation . The parametric will Run properly even if the units do not match.

Warning : Always include the startup code Units("...") in any script that returns distances. If you do not, the script will still Run , but it will use the Units("...") that were set by the last parametric that was Run , or by the loading of custom member plugins at the time that Modeling is started, or by using Advanced Selection .

----------

Q. What's with those # symbols in the startup code?

SDS2 Python Prompt
>>> # num = 4.5
>>> num
Traceback (most recent call last):
   File "<console", line 1, in ?
NameError: name 'num' is not defined

>>> num = 4.5
>>> num
4.5
Compare the red code with the black code . Python disregards statements beginning with #. In this example, the statement num = 4.5 correctly assigns the value 4.5 to num only when it is not preceded by # .

A . The pound symbol (#) signifies to Python that the text following the # is a comment. A comment is informational only. Comments do not affect the functionality of a script. They communicate information about the code, but Python does not run them as code.

----------

Q. What about adding members?

# member begin
memadd1= Member('Beam')
memadd1.Left.Location = (0, 0, 1374)
memadd1.Right.Location = (0, 330, 1374)
memadd1.SectionSize = "w21x44"
memadd1.Grade = "A992"
   ---- lines removed to save space ----
memadd1= member1.Add()
# member end

A block of code

----------

Q. What's with all those equal signs?

# member begin
memadd1= Member('Beam')
memadd1.Left.Location = (0, 0, 1374)
memadd1.Right.Location = (0, 330, 1374)
memadd1.SectionSize = "w21x44"
memadd1.Grade = "A992"
   ---- lines removed to save space ----
memadd1= member1.Add()
# member end

A block of code

A . Typically, code for the adding of a member or material begins with an assignment statement. For example, in the code shown above, the to-be-added beam is given the variable name memadd1 in the initial assignment statement memadd1= Member('Beam') . That same variable ( memadd1 ) was used to assign attributes such as a section size to the beam, as with the statement memadd1.SectionSize = "w21x44" . Finally, the script instructs the SDS2 program to actually generate the beam with the statement: memadd1= member1.Add(). In an assignment statement, the equal sign (=) assigns the value to the right of = to the variable on the equal sign's left.

----------

Q. Can any characters be used as variables?

SDS2 Python Prompt
>>> pi = 3.14
>>> radius = 4
>>> pi * 2 * radius
25.12000000000001
>>> radius = [1, 4]
>>> for r in radius:
. . .     circumference = pi * 2 * r
. . .     print(circumference)
. . .     
6.28
25.12

The same variable can be reassigned new values . In this example, the variable radius is initially assigned the value 4 . Later, radius is assigned a list. Similarly, the variable r is first assigned the value 1 , then the value 4 . So be careful, there's a lot of pitfalls to not keeping your variables consistent.

A . A variable name can begin with any letter or an underscore. Case matters. Numbers can be included in the name, but not as its initial character. The name cannot be a reserved word (keyword) such as add , assert , break , class , continue , def , del , elif , else , except , exec , finally , for , from , global , if , import , in , is , lambda , not , or , pass , print , raise , return , try or while .


Strings : " abc xyz "

A Python string is a sequence of characters or blank spaces enclosed with quotation marks. Single quotes ( 'single' ) or double quotes ( "double" ) can be used to designate a string. Do not use mixed single and double quotes for the same string ( 'mixed" ).

Some examples of strings are section sizes, sequences, member types and dimension strings:

SDS2 Python Prompt
>>> from shape import Shape
>>> Shape("W10x33").SectionSize
'W10x33'                               # a string

Note that output of a string using print takes the quotes off the string:

SDS2 Python Prompt
>>> dim_string = "1-2 1/2"
>>> dim_string
'1-2 1/2'                               # a dimension string
>>> print(dim_string)
1-2 1/2                                 # a string, but doesn't look like one

You can use mathematical operators such as + or * on a string, but those operators behave differently than they do for numbers.

SDS2 Python Prompt
>>> dim_string + "foo"
'1-2 1/2foo'
>>> dim_string * 5
'1-2 1/2 1-2 1/2 1-2 1/2 1-2 1/2 1-2 1/2 1-2 1/2'

Tip: To transform a dimension string into a number so that operations like + and * can yield numerical results, you can use the dim() function. To output a number as a dimension string, you can use dim_print() . Since these functions are not built-in Python functions, you need to import them.

SDS2 Python Prompt
>>> from param import Units, dim, dim_print
>>> Units("feet")
>>> dim_string
'1-2 1/2'                                  # a string
>>> num = dim(dim_string)
14.5                                        # a number
>>> num * 2
29.0
>>> dim_print(num)
'1-2 1/2'                                  # a string
>>> dim_string * 2
'1-2 1/21-2 1/2'                      # a string

Warning : If you fail to put a string in quotes, Python thinks that you are defining a variable. If that variable name does not exist, you will get a NameError . The following example shows a name error that results from trying to define a list -- list1 -- using the variable a , which in this example is not defined. Notice that, when list1 is redefined using the string ' a ' instead of the undefined variable a , Python accepts the list as valid, and the name error does not occur.

SDS2 Python Prompt
>>> b, c = 2, 3
>>> list1 = [a, b, c]
Traceback (most recent call last):
File "<console>", line 1, in ?
NameError: name 'a' is not defined
>>> list1 = ['a', b, c]
>>> list1
['a', 2, 3]

Tip: You can use dir() and help() to get information on built-in methods for strings. Here is an example:

SDS2 Python Prompt

>>> dir("")
['__add__', '__class__', '__contains__', '__delattr__', '__doc__', '__eq__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__getslice__', '__gt__', '__hash__', '__init__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__str__', 'capitalize', 'center', 'count', 'decode', 'encode', 'endswith', 'expandtabs', 'find', 'index', 'isalnum', 'isalpha', 'isdigit', 'islower', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'replace', 'rfind', 'rindex', 'rjust', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', ' upper ', 'zfill']

>>> help("". upper )
help on built-in function upper:
upper(...)
S.upper() -> string
Return a copy of the string S converted to uppercase.

>>> str = "upper case"
>>> str. upper ()
'UPPER CASE'

Also see: Consult any of the books or web sites listed here for more information on strings.


Numbers ( floating point numbers & integers ):

Python has two basic number object types that you should be aware of. These are floating point numbers (number.decimal) and integers (number). There are other numerical object types in Python, but they are beyond the scope of this introduction. For our purposes, a number that does not have a decimal point is interpreted by Python as being an integer. If the number does have a decimal point, it is a floating point number. Numbers in quotes are not numbers -- they are strings .

When an operation such as * or / or + or - is performed on two integers, Python returns an integer. As shown in the examples below, the / operator returns the floor (rounds down). On the other hand, when a / or * or + or - operation is performed on two floating point numbers or an integer and a floating point number, Python returns a floating point number.

SDS2 Python Prompt
>>> 2 * 4
8                                     # integer * integer returns an integer
>>> 2 * 4.0
8.0                                   # integer * float returns an float
>>> 2.0 / 4.0
0.5                                   # float / float returns an float
>>> 2 / 4
0                                      # integer / integer returns an integer
>>> 3 / 4
0                                      # integer / integer returns an integer
>>> 3.0 / 8                 
0.375                               # float / integer returns a float
>>> 5 / 4
1                                      # integer / integer returns an integer

Warning : As the above examples show, never enter a fraction comprised of whole numbers when you want a floating point number. Also, dimension strings such as '1-2 1/2' are not numbers -- they are strings .

Tip: The common-sense rule of thumb is that integers are for counts and floating point numbers are for measurements. If you forget the decimal point, problems may or may not occur. The Point() function, shown in the example below, interprets any number that is entered as a floating point number representing a distance, even if you do not include a decimal point.

SDS2 Python Prompt
>>> from point import Point
pt1 = Point(22, 44, 88)            # no decimal points in numbers
>>> pt1
22, 44, 88
>>> pt1.Z
88.0                                          # a floating point number

A special type of floating point number is a distance value. Object attributes that return floating point numbers that represent distances reference the startup code Units("...") to calculate the exact floating point number that needs to be returned. Note that pt1 in this next example was defined to be Point(22, 33, 88) in the example above, which makes it a point object. On the other hand, the variable float_num in this next example is simply a floating point number and is therefore not affected by the Units("...") .

SDS2 Python Prompt
>>> from param import Units
>>> Units("inch")
>>> float_num = 3.425
>>> pt1.Z
88.0                                                    # eighty-eight inches
>>> float_num
3.42499999999999                            # 3.425, no units
>>> Units("metric")
>>> pt1.Z
2235.19999999998                            # 2235.2 millimeters
>>> float_num
3.42499999999999                            # 3.425, no units

Warning : When comparing two floating point numbers for equivalency, you should compare them within a particular tolerance. The FloatComparison module contains six functions that can help you to do this. Each of these functions take two numbers and an optional third argument, for tolerance. If the tolerance is unspecified, the default is .0001. In the example below, num1 and num2 are determined to be equal within the default tolerance, whereas num1 and num3 differ by .0002, which exceeds the default tolerance. When .0002 is specified as the tolerance, num1 and num2 are determined to be the equal.

SDS2 Python Prompt
>>> num1 = 7.0001
>>> num2 = 7.0
>>> from FloatComparison import EqualsAbsoluteTolerance
>>> EqualsAbsoluteTolerance(num1, num2)
True
>>> num3 = 7.0002
>>> EqualsAbsoluteTolerance(num1, num3)
False
>>> EqualsAbsoluteTolerance(num1, num3, .0002)
True

Also see: For more information on integers and floating point numbers as well as other object types that are built in to Python, you can consult the books and web sites that are listed here .


Lists : [ i0 , i1 , i2 ]

A Python list is a sequence of items that are separated by commas and enclosed in square brackets. Some parametric functions that return lists are MultiMemberLocate("prompt string") and MtrlLocate("prompt") and HoleLocate("prompt string") . The GroupMemberCreate(mem_list) function can take a list as its argument. As shown in the example below, the Job().bolt_sched() method can be used to return a list of the bolt types from the tab in the Bolt Specifications that corresponds to the " Connection design method " that you are using. To access the Job().bolt_sched() list, you need to import Job from the job module.

SDS2 Python Prompt
>>> from job import Job
>>> print(Job().bolt_sched() )
[ 'A325N', 'A325SC', 'A325X', 'A490N', 'A490SC', 'A490X' ]

Python provides a number of ways to access the items in a list. For example, each item in a list is indexed according to its position in the list. The first item in the list has an index of 0.

SDS2 Python Prompt
>>> Job().bolt_sched()[0]                 # returns item 0
A325N
>>> Job()bolt_sched()[0:2]              # returns items 0 and 1
['A325N', 'A325SC']
>>> Job()bolt_sched()[1:]                # returns all items but 0
[ 'A325SC', 'A325X', 'A490N', 'A490SC', 'A490X' ]

To iterate over the items in a list, you can use a for statement:

SDS2 Python Prompt
>>> for bolt in Job().bolt_sched():
. . .         print(bolt )
. . .
A325N
A325SC
A325X
A490N
A490SC
A490X

To iterate over the indexes of the items in a list, you can combine range() and len() in a for statement. Since these are built-in Python functions, you don't need to import them.

SDS2 Python Prompt
>>> s = Job()bolt_sched()
>>> for i in range(len(s)):
. . .         print(i, s[i] )
. . .
0 A325N
1 A325SC
2 A325X
3 A490N
4 A490SC
5 A490X

list_name.append(x) is a built-in Python method that appends the item x to the end of the list_name list. In the example below, lista is thus changed in place.

SDS2 Python Prompt
>>> lista = ['c', 'b']
>>> lista.append('a')
>>> lista
['c', 'b', 'a']

L1.extend(L2) is another built-in Python method. It appends all items in the list named L2 to the list named L1 . This can be done because lists are mutable; that is, they can be changed in place.

SDS2 Python Prompt
>>> lista
['c', 'b', 'a']
>>> listb = ['f', 'x', 'z']
>>> lista.extend(listb)
>>> lista
['c', 'b', 'a', 'f', 'x', 'z']

list_name.sort() sorts the items in the list named list_name . It is another built-in Python method that can be used to change a list in place.

SDS2 Python Prompt
>>> lista
['c', 'b', 'a', 'f', 'x', 'z']
>>> lista.sort()
>>> lista
['a', 'b', 'c', 'f', 'x', 'z']

Tip : The built-in Python functions dir() and help() can be used to get information on some of the built-in methods you can use with lists. Here is an example:

SDS2 Python Prompt

>>> dir([])
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__delslice__', '__doc__', '__eq__', '__ge__', '__getattribute__', '__getitem__', '__getslice__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmul__', '__setattr__', '__setitem__', '__setslice__', '__str__', ''append'', 'count', 'extend', 'index', 'insert', 'pop', ' remove ', 'reverse', 'sort']

>>> help([]. remove )
Help on built-in function remove:
remove(...)
L.remove(value) -- remove first occurrence of value

>>> lst = [2, 'two', 22, 'twenty-two']
>>> lst
[2, 'two', 22, 'twenty-two']
>>> lst. remove ('twenty-two')
>>> lst
[2, 'two', 22]

Also see: As you have seen from the last four examples, lists are mutable and can be changed in place. You should also be aware that lists can be composed of various object types, such as strings , numbers and tuples . You can even nest lists within lists. For more information about lists, click here for some references to books and web sites.


Tuples ( i0 , i1 , i2 ):

A tuple is a sequence of items that are separated by commas and, on output, enclosed in parentheses (i0, i1, i2) . Functionally, tuples are similar to lists , but are immutable, and therefore not as flexible. You do not have to enclose tuples in parentheses when defining them. On output, Python encloses them in parentheses.

SDS2 Python Prompt
>>> tup1 = 1, "tuple", "without", "parentheses"
>>> tup1
(1, 'tuple', 'without', 'parentheses')
>>> print(tup1 )
(1, 'tuple', 'without', 'parentheses')

Also, be aware that a tuple consisting of a single item must be constructed with a trailing comma. It is not sufficient to put the single item in parentheses.

SDS2 Python Prompt
>>> not_a_tuple = ("one")
>>> not_a_tuple
'one'
>>> is_a_tuple = ("one",)
>>> is_a_tuple
('one',)

Points can be defined using tuples that are composed of three comma-separated numbers, but points are not themselves tuples.

SDS2 Python Prompt
>>> from point import Point
>>> pt1 = Point(22, 44, 88)       # the variable pt1 is a point
>>> pt1
22, 44, 88                                    # no parentheses
>>> tup2 = 222, 444, 888           # tup2 is a tuple
>>> tup2
(222, 444, 888)                           # parentheses
>>> pt2 = Point(tup2)                 # defining a point with a tuple
>>> pt2                                       # pt 2 is a point
222, 444, 888

A sequence is an ordered set of objects that is indexed by non-negative integers beginning with 0. You can use the index of an object in a sequence such as a list or string or tuple to extract the value of that object. You cannot do this with points -- instead use the X, Y, Z attributes of a point to extract its values. Notice in the example, below, that tup2[0] returns an integer , but that pt1.Z returns a floating point number .

SDS2 Python Prompt
>>> tup2
(222, 444, 888)
>>> tup2[0]                # to extract the value at index 0
222                             # the value at index 0, an integer
>>> pt1
22, 44, 88
>>> pt1.Z
88.0                            # a floating point number

You can "unpack" a tuple by assigning a number of variables (three variables -- a , b , c -- in this example) that is equal to the number of the objects that are in the tuple.

SDS2 Python Prompt
>>> tup2
(222, 444, 888)
>>> a, b, c = tup2
>>> a
222
>>> b
444

Tuples are used to parametrically store information from SDS2 setup windows and other locations. For example, Job().NutSchedule() returns a list of tuples in the form [(grade1, nut_type1), (grade2, nut_type2), ... (' ', 'None'), (' ', 'None') ... ] . Each tuple stores the entries to a line under the " Nut " tab in setup's Nut and Washer Schedule .

SDS2 Python Prompt
>>> from job import Job
>>> Job().NutSchedule()
[( 'A563', 'Heavy hex' ), ( 'A563-C', 'Heavy hex'), ( 'A194', 'Heavy hex'), ( ' ' , 'None' ), ( ' ' , 'None' ), ( ' ' , 'None' ), ( ' ' , 'None' ), ( ' ' , 'None' ), ( ' ' , 'None' ), ( ' ' , 'None' ), ( ' ' , 'None' ), ( ' ' , 'None' ), ( ' ' , 'None' )]

The techniques that you can use to extract the values stored in tuples are similar to those techniques used to unpack lists . The following code iterates over the Job().NutSchedule list and strips out the tuples with 'None' as the nut type and prints the grade name and nut type for only those lines in the Nut and Washer Schedule that have entries. Compare this example to the raw, unstripped list that was generated in the previous example.

SDS2 Python Prompt
>>> for grade, type in Job().NutSchedule():
. . .       if type != 'None':
. . .           print(grade, type )
. . .
A563 Heavy hex
A563-C Heavy hex
A194 Heavy hex

list_a.append('a') appends the item 'a' to the end of the list that is named list_a . Since the list_a list was changed in place from [1, 'b'] to [1, 'b', 'a'] , we can say that lists are mutable. On the other hand, tuple_a.append('a') results in an AttributeError . Since tuples are immutable, they cannot be changed in place, and the append method cannot be used on a tuple.

SDS2 Python Prompt
>>> list_a = [' 1 ', 'b']
>>> list_a.append('a')
>>> list_a
[' 1 ', 'b', 'a']
>>>
>>> tuple_a = (' 1 ', 'b')
>>> tuple_a.append('a')
Traceback (most recent call last):
File "<console", line 1, in ?:
AttributeError: 'tuple'object has no attribute 'append'

So why use tuples if you can't change them in place? One answer to this question is that a tuple uses less memory than a comparable list, which means that you can potentially make a script run faster by using tuples instead of lists. Also, sometimes you may want an object that stores data to be immutable.

Also see: For more information on tuples and other Python built-in object types, you can consult the books and web sites that are listed here .


Dictionaries { k1 : v1 , k2 : v2 , k3 : v3 }:

A dictionary contains values indexed by keys and enclosed in curly braces. Each key : value pair is separated by a comma. In the following example, the Job().steel_grades("Channel") method is used to return a dictionary that tells you the setup choices made at Home > Project Settings > Job > Channel Grades . You can create a dictionary of your own from a parametric dialog by using Dlg0.done() .

SDS2 Python Prompt
>>> from job import Job
>>> Job().steel_grades("Channel")
{'A36' : [36.0, 58.0, 'None'], 'A572-42' : [42.0, 60.0, 'None'], 'A572-50' : [50.0, 70.0, 'None'], 'A572-'60' : [60.0, 75.0, 'None'], 'A588' : [50.0, 70.0, 'None']}

Since the steel grades in the Job().steel_grades("Channel") dictionary correspond to the dictionary keys , the built-in Python keys() method can be used to output a list of the steel grades.

SDS2 Python Prompt
>>> Job().steel_grades("Channel").keys()
['A36', 'A572-42', 'A572-50', 'A572-'60', 'A588']

Similarly, since lists of each grade's Fy, Fu, and nonstandard notation correspond to the dictionary values , the built-in Python values() method can be used to output a list of these lists.

SDS2 Python Prompt
>>> Job().steel_grades("Channel").values()
[[36.0, 58.0, 'None'], [42.0, 60.0, 'None'], [50.0, 70.0, 'None'], [60.0, 75.0, 'None'], [50.0, 70.0, 'None']]

You can use a dictionary's key to return the value associated with that key. In this next example, 'A36' is the key:

SDS2 Python Prompt
>>> Job().steel_grades("Channel")['A36']
[36.0, 58.0, 'None']

This next example shows how to return the Fy value that is assigned to steel grade A36 at Home > Project Settings > Job > Channel Grades . The key ['A36'] is used to access the list of the Fy, Fu and nonstandard notation values for the A36 steel grade. The index [0] is used to access the Fy value from that list.

SDS2 Python Prompt
>>> Job().steel_grades("Channel")['A36'][0]
36.0

A dictionary is not a sequence. You cannot use an index to access data that is stored in a dictionary. If you attempt to do so, and the Python interpreter can't find a key that corresponds to the index you enter, you will get a KeyError .

SDS2 Python Prompt
>>> Job().steel_grades("Channel")[0]
Traceback (most recent call last):
   File "<console>", line 1, in ?
   File "src/lib/parametric/UserDict.py"
KeyError: 0

Lists [i0, i1, i2] , tuples (i0, i1, i2) and strings "012" are sequences. Each item that is stored in a list, tuple or string is indexed according to its sequential position. The first item in a sequence has an index of 0. In this next example, dict_1 is stored in list_1 at index 2 since it is the third item in the list. The name of the architect, 'Miss Muffit' , is a value stored in a dictionary, dict_1 , and therefore must be accessed with a key, which in this example happens to be 'Architect' .

SDS2 Python Prompt
>>> dict_1 = {'Architect' : 'Miss Muffit' , 'Engineer' : 'P. Piper' }
>>> list_1 = ['job_name', 'fab_name', dict_1]
>>> list_1[2]
{'Architect' : 'Miss Muffit' , 'Engineer' : 'P. Piper' }
>>> list_1[2]['Architect']
'Miss Muffit'

To iterate through the keys of a dictionary with a for loop:

SDS2 Python Prompt
>>> for key in d1:
. . .      print(key, d1[key] )
. . .
Architect  Miss Muffit
Engineer  P. Piper

Tip : The built-in Python functions dir() and help() can be used to get information on some of the built-in methods you can use with dictionaries. Here is an example:

SDS2 Python Prompt

>>> dir({})
['__class__', '__cmp__', '__contains__', '__delattr__', '__delitem__', '__doc__', '__eq__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setitem__', '__str__', 'clear', 'copy', 'fromkeys', 'get', 'has_key', 'items', 'iteritems', 'iterkeys', 'itervalues', ' keys ', 'pop', 'popitem', 'setdefault', 'update', 'values']
>>> dict = {1;"one"; 2:"two"; 3:"three", 4,"four"}
File "<console>", line 1
dict = {1;"one"; 2:"two"; 3:"three", 4,"four"}

>>> help({}. keys )
Help on built-in function keys:
keys(...)
D.keys() -> list of D's keys

>>> dict = {1:"one", 2:"two", 3:"three", 4:"four"}
>>> dict. keys ()
[1, 2, 3, 4]

Also see: For more information on dictionaries and other Python built-in object types, you can consult the books and web sites that are listed here .


Mathematical operators ( / - * + ):

Python has a number of built-in operators for performing mathematical operations. Four of these operators will be discussed in this introduction. They are as follows:

For division, Python uses the slash symbol ( / ).

For subtraction, Python uses the minus symbol ( - ).

For multiplication, Python uses the asterisk ( * ).

For addition, Python uses the plus symbol ( + ).

When Python evaluates a mathematical expression in which no operation has evaluation precedence over another and in which there are no parentheses, the expression is read from left to right.

SDS2 Python Prompt
>>> 22 - 11 - 3
8
>>> 18 / 2 / 3
3

Introducing parentheses causes the parenthetical part of the expression to have evaluation precedence over the non-parenthetical part. That is to say, the parts of the mathematical expression that are in parentheses will be evaluated first.

SDS2 Python Prompt
>>> 64 / 4 / 2
8
>>> 64 / (4 / 2)
32

Operators also have evaluation precedence. Multiplication and division are at the same level of evaluation precedence and are at a higher level of precedence than addition and subtraction. Addition and subtraction are at the same level. Because of this, Python evaluates expressions that use both multiplication and division from left to right if no parentheses or other operators are present.

SDS2 Python Prompt
>>> 64 * 2 / 4
32
>>> 64 * 4 / 2
128
>>> 64 / 4 * 2
32

This same principle holds true for addition and subtraction. Since both are at the same level of evaluation precedence, an expression that uses them is evaluated from left to right if no parentheses or other operators are present.

SDS2 Python Prompt
>>> 6 + 4 - 5 - 3
2
>>> 6 + 4 - (5 - 3)
8

When operators with a higher level of evaluation precedence are mixed together with operators that have a lower level of precedence, the operations governed by the operator with higher precedence are evaluated first. Since multiplication has evaluation precedence over addition, multiplication will be evaluated first in a mathematical expression without parentheses that involves both operations.

SDS2 Python Prompt
>>> 6 + 4 * 5
26
>>> (6 + 4) * 5
50

Warning 1 : These examples all use integers for their examples. As was discussed in the section on numbers , integer division returns the floor. Division with a floating point number , on the other hand, will return an exact floating point value.

SDS2 Python Prompt
>>> 7 / 3             # integer divinsion
2                          # the floor
>>> 7 / -3            # integer divinsion
- 3                        # the floor
>>> 7 / 3.0           # division by a floating point number
2.3333333333335

Warning 2 : In Python, mathematical operators are not just for numbers . They, or at least some of them, can also be used on other types of objects, such as strings , lists , tuples and dictionaries .

SDS2 Python Prompt
>>> "YES" * 5
YESYESYESYESYES
>>> ("YES" + " | ") * 5
' YES | YES | YES | YES | YES | '
>>> tup1 = (1, 2, 3)
>>> tup2 = (4, 5, 6)
>>> tup3 = tup1 + tup2
>>> tup3
(1, 2, 3, 4, 5, 6)

Also see: For more information on mathematical operators in Python, you can consult the books and web sites that are listed here .


Boolean comparison operators ( < > == != >= <= ):

There are six boolean comparison operators. They are as follows:

Operator Example Description
< x < y less than
> x > y greater than
== x == y equal to
!= x != y not equal to
>= x >= y greater than or equal to
<= x <= y less than or equal to

Boolean comparison operators are used to compare values. They return True or False .

SDS2 Python Prompt
>>> num1 = 14.5
>>> num2 = 5
>>> num1 == num2
False
>>> num1 != num2
True
>>> num2 > num1
False
>>> num2 <= num1
True

Boolean comparison operators are frequently used in if statements and while statements and in expressions for Advanced Selection .

SDS2 Python Prompt
>>> num1
14.5
>>> if num1 < 15:
. . .      print("The number is less than 15.")
. . .  else:
. . .      print("The number is 15 or greater.")
. . .  
The number is less than 15.

Indentation in Python :

For if statements, an initial line of code beginning with if and ending with : states the condition under which an indented line of code will run. Indentation of the code block under the initial if ...: condition is required. Not indenting the block produces an IndentationError . This is shown in the following example:

SDS2 Python Prompt
>>> list1 = [ ]
>>> if list1:
. . .   print("The list has contents.")

IntentationError: no indented block
>>> if list1:
. . .      print("The list has contents.")
. . .   else:
. . .      print("The list is empty." )
. . .     
The list is empty.

Since the code in black is properly indented, it produces the expected result. The red code produces an IndentationError because the code block after the if clause is not indented.

In the above example, the if list1 test is false since an empty list is false. Consequently, the else clause is executed and the string "The list is empty" is printed. That list1 is an empty list can be corroborated with the Boolean expression not list1 .

SDS2 Python Prompt
>>> list1
[ ]
>>> not list1
True

Indentation is Python's way of grouping information. The convention is to use four spaces for each indent. Inconsistent spacing produces errors.

SDS2 Python Prompt
>>> list2 = [1, 2, 3]
>>> if list2:
. . .     print("The list has contents")
. . .     print(list2)
. . .     
The list has contents
[1, 2, 3]

The second line in the indented block would have produced an IndentationError if it had not been indented with exactly the same number of spaces as the first line.

The previous examples showed if-this-is-true, do-this code. Here's some if-this-is-true, test-if-this-is-true, then-if-all-the-if-conditions-are-true, do-this code. The important concept to understand here is that, when an if condition is indented under another if condition, the indented if condition applies only if the previous if condition was found to be true. If the first (outer) if condition is false, the second (inner) if condition is skipped over by the interpreter because it does not apply. The final message, which is red in this example, is printed only when all three of the if tests are true.

SDS2 Python Prompt
>>> from point import Point
>>> pt1 = Point(33, 66, 99)
>>> pt2 = Point(33, 66, 99)
>>> if pt1.X == pt2.X:
. . .      if pt1.Y == pt2.Y:
. . .          if pt1.Z == pt2.Z:
. . .              print("Points are the same.")
. . . 
Points are the same.

If the X coordinates are the same , the Y coordinates are tested to see if they are the same. The final message -- Points are the same -- is printed only if the X, Y and Z coordinates are the same.

Disclaimer: This code was created to illustrate a concept. The code would not be practical for actual testing of points in real-world situations since point coordinates are floating point numbers , which should be tested for equivalency within an allowable tolerance. The FloatComparison module contains functions for comparing floating point numbers.

Here's a more elaborate version of the previous example. Note that pt1 and pt3 are different with respect to their Y coordinates , which are highlighted is red . Therefore, the result that is output is also different. The output message -- X coordinates are the same, Y are different -- is printed because if pt1.Y == pt3.Y is false. Be aware that, in order for this code to run, the else statements must be properly indented so that they align with the if statements that they provide contingent actions for.

SDS2 Python Prompt
>>> pt1
33, 66 , 99
>>> pt3 = Point(33, 70 , 99)
>>> if pt1.X == pt3.X:
. . .       if pt1.Y == pt3.Y:
. . .          if p1.Z == pt3.Z:
. . .              print("The points are the same." )
. . .          else:
. . .              print("X, Y coordinates are the same, Z are different.")
. . .       else:
. . .           print("X coordinates are the same, Y are different.")
. . .  else:
. . .      print("The X coordinates are different.")
. . . 
X coordinates are the same, Y are different.

Also see: For more information on how the indentation in Python code affects the results that code produces, you can consult the books and web sites that are listed here .


The built-in Python functions dir() and help() :

The Python Prompt and the built-in dir() and help() functions can be used to get information about a module. In this example, dir(dialog) is used to get a list of names in the dialog module. Since dir() is a built-in function, no imports are needed to make it run. However, the dialog module is not built-in and must therefore first be imported in order for you to get information about it.

SDS2 Python Prompt
# methods for new dialog fields are green
>>> import dialog
>>> dir(dialog)
['Dialog', 'DialogBase', 'ResponseNotOK', 'SaveError', 'StartupException', 'TkWrap', 'Tkinter', 'WindowRelease', 'Wizard', '__all__', '__builtins__', '__doc__', '__file__', '__name__', '__path__', 'abstract', 'builder', ' button ', ' checkbox ', 'choose_fi.e. 'color', ' combobox ', 'controller', 'date', 'decorators', 'dialog', 'dimension', 'displaymodel', 'entry', 'field', 'filefacade', 'frame', ' hidden ', 'image', 'item', 'kind', 'labeledfield', 'listindex', 'modelroot', 'noop', 'queueable', ' radio ', 'ref', 'rule', 'rules', ' scrollbar ', 'simulate', ' table ', 'tabset', 'value', 'weak', 'weak_bind', 'widget']
Note: 'choose_file' is ' File browse ' & ' File save ' & ' Material browse '; 'combobox' is ' Menu '; 'entry' is ' Integer ' and ' Float ' and ' Dimension ' and ' String '; 'frame' is ' Column group ' & ' Column '; 'image' is ' Image '; 'tabset' is ' Tabset ' & ' Tab '

You can then plug into help() one on the names that were returned using dir() . This will provide you with information specifically related to that name. In this example help(dialog.radio) is typed at the Python Prompt .

SDS2 Python Prompt

>>> import dialog
>>> help(dialog.radio)

Help on module dialog.radio in dialog:

NAME
    dialog.radio

# ----- lines cut out -------

radio(...)
    Creates a set of radio buttons.
    @param name The name of this field.
    @param values The list of items in the radio button.
    @param initial_value A string from the value list.
    @param label A string to the left of the entry box.
    @return the radio.Radio object

# ------ and so on ... -------

You can also print help information to the report viewer by running a python script like the following. This script uses the dir() , help() and getattr() functions to output built-in help information about functions in the Point3D module.

# Paste to a .py text file. Run in Modeling.
import Point3D
for a in dir(Point3D):
    if a[0] != '_':
        print(help(getattr(Point3D, a)))

Also see: For more examples showing how to use dir() and help() , click here (strings), here (lists), here (dictionaries) and here (the evu module).


Other Built-in Python functions :

Python has a number of built-in functions and methods that you can use without having to import anything. You can use the Python Prompt to get help on these functions. Note that, even though you don't need to import the __builtin__ module to have access to the built-in functions within it, you do need to import it if you want to do a dir() on it. This is because the namespace "__builtin__" is not itself contained within the module. For detailed information on how to use built-in functions, consult the Python resources that are listed later on this page.

SDS2 Python Prompt
>>> import __builtin__
>>> dir(__builtin__)
['ArithmeticError', 'AssertionError', 'AttributeError', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError',
          --- some names have been removed to save space ---
'help', 'hex', 'id', 'input', 'int', 'intern', 'isinstance', 'issubclass', 'iter', 'len', 'list', 'locals', 'long', 'map', 'max', 'min', 'object', 'oct', 'open', 'ord', 'pow', 'property', 'range', 'raw_input', 'reduce', 'reload', 'repr', 'round', 'setattr', 'slice' 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'unichr', 'unicode', 'vars', 'xrange', 'zip']

In this next example, the function name zip , which was obtained in the previous example using dir(__builtin__) , is plugged into help(zip) to obtain help on the syntax and functionality of zip.

SDS2 Python Prompt

>>> help(zip)
Help on built-in function zip:
zip(...) 

    zip(seq1 [, seq2 [...]]) -> [(seq1[0], seq2[0] ...), (...)]

Return a list of tuples, where each tuple contains the i-th element from each of the argument sequences. The returned list is truncated to the length of the shortest argument sequence.


Entering dimensions, distances & mathematical expressions:

If your startup code specifies imperial Units("...") , make entries in decimal inches, not fractions. Also, do not use hyphens (-). If you enter a fraction using integers, Python truncates the results of the division operation to an integer (examples: 1/8 = 0 ; 5/4 = 1 ). If you enter fractions with decimal points, Python calculates the precise decimal value (examples: 1.0/8.0 = .125 ; 5.0/4.0 = 1.25 ). If you enter a hyphen, Python interprets the hyphen as a minus sign (examples: 1-2 = -1 ; 3-2 = 1 ). See numbers (floating point numbers and integers).

To enter a dimension string and convert it to a floating point distance that is properly factored based on the startup code Units("...") , you can use the dim("...") function. For example, dim("1-4 1/2") is converted to 16.5 if Units("feet") are your startup code units.

Want to look at the script?

Open the .py file with a text editor such as WordPad.

Using a text editor:

Generic text editors: A Python script can be edited using a text editor such as Notepad or WordPad in a Windows operating system (choose Start > Programs > Accessories > WordPad ). Give the file a .py extension.

Python text editors: PythonWin is a Python text editor for the Windows operating system. Idle is an integrated development environment which works in Windows and Linux.

Copying and pasting scripts from Help: You can copy ( Ctrl+c ) lines of code from example scripts shown in Help, then start up WordPad (or another text editor), paste ( Ctrl+v ) into a new document (or over the text in an existing document). Then save ( File > Save As ) the file as a plain text document with a .py extension. The script should Run in Modeling . If it doesn't Run properly, the indentation probably isn't right, and you can easily fix that by adding spaces in the text editor.

Formatting tips: Code blocks are grouped together in a script using indentation. Use spaces (not tabs) to indent, and always use the same number of spaces. Be aware that when you are using a proportional font rather than a fix-pitched font that the equivalent number of spaces may not always look the same (even though they are the same). As previously mentioned, the file must then be saved as ASCII text (plain text) without any format codes.


Python books, web sites and tutorials :

Note: Only Python version 3 is used to create parametrics for SDS2 programs.

Learning Python , 5th ed. (2013)
by Mark Lutz
O'Reilly Media
A comprehensive introduction to Python.

Think Python , 2nd ed. (2015)
by Allen Downey
O'Reilly Media
An introduction to Python for those with no prior programming experience.

Python Pocket Reference , 5th ed. (2014)
by Mark Lutz
O'Reilly Media

Python Cookbook , 3rd ed. (2013)
by David Beazley and Brian K. Jones
O'Reilly Media
(Members of O'Reilly's Safari reference service can read an HTML version here: https://www.oreilly.com/library/view/python-cookbook-3rd/9781449357337/ .)

Python Essential Reference , 4th ed. (2009)
by David M. Beazley
Addison-Wesley

https://www.python.org is the official Python web site of the Python Software Foundation, and is an important source of Python documentation, software, and news.

https://docs.python.org/3.10/faq answers frequently asked questions about Python version 3.10.

https://developers.google.com/edu/python/ is a free class for people with a little bit of programming experience.