Hybrid programming 1
Introducing python - Projectile physics
Last updated
Introducing python - Projectile physics
Last updated
We previously implemented complex Grasshopper programs—graphs with more and more wires and nodes—to build our simulations. Increasingly complex graphs are difficult to comprehend. To some extent we tried to manage this complexity by decomposing the program into clear functional blocks and encapsulating logic inside cluster components. Nonetheless, the limitations of purely Visual programming paradigm were starting to become apparent. Here, we will look more extensively into Python coding to address some of these limitations.
When trying to implement the aspect of time, we had to circumvent the problem of creating forbidden cycles in Grasshopper by storing and reading text data from the Notes panel. We also had to pack and unpack the text data each time we read and saved the states of the particles. This was a rather unwieldy workaround.
Instead of the note panel, we will store particle states inside a Python dictionary called sticky
that can be imported from the scriptcontext
module. A dictionary is a Python data type that like a list, is a collection of objects. However, these objects are stored by key rather than positional offset. We want to create ‘positions’, ‘velocities’ and ‘masses’ keys in the sticky dictionary that map to our particles’ attributes. One advantage of this approach is that the sticky variable has global scope. This means it can be accessed throughout Grasshopper by any python component, which can fetch values from it.
There are a few steps to take note of before jumping straight into coding in the python component. First, set up the correct number of input and output parameters and give them descriptive names. Second, specify the type of input data (right click and select Type Hint). Third, specify how the input data is structured (Item, List or Tree Access). In the case of PyInitialise, init_positions, init_velocities
and init_masses
input data are specified as lists containing objects of Point3d, Vector3d
and float
types respectively.
We now take a closer look at the code written in PyInitialise. The above statement imports the sticky dictionary from the scriptcontext
module
Each ‘If’ statement checks whether sticky dictionary has a key such as position
; and creates the key as well as assigns initial values if it doesn’t. This is an example of a compound statement—statements with other statements nested in them. Note that the header line is terminated with a colon, followed by a nested block of code which is indented. Do not forget the colon or to indent properly, these are some of the most common beginner Python coding mistakes!
We handle the reset logic with another if statement, mapping initial position, velocity, and mass values to the associated keys if reset is True i.e., when the button is pressed.
Note that the code blocks nested in each if statement are never reached if the test expression evaluates to False. However, the last three statements will always be executed each time PyInitialise is triggered to update. Position, velocity and mass values are fetched from sticky and assigned to the output parameters of PyInitialise.
Having taken care of functionality for retrieving data from the sticky, we implement the inverse in PySave. The above statements assign new position, velocity and mass values to the associated keys in sticky after they have been computed.
We now turn our attention to the physics block. A key lesson from earlier sessions is that symbolic expressions allow for more compact representations of logic. Here, the three clusters—Analysis, Integrate and Constraint—are collapsed to a single component PyPhysics. We can compute the future state of particles in relatively few lines of code.
When a Python scripting component is added to the Grasshopper canvas, it already contains boilerplate code. The first chunk is known as a docstring. It spans multiple lines and is enclosed in triple quotes and is used to document the code. We edit the description of the component, as well as its inputs and outputs. If you right click on PyPhysics and select help afterwards, this documentation is displayed. The boilerplate code also contains a statement importing rhinoscriptsyntax
. This is a module of functions that we will not be using and hence can be deleted. Instead, we will import a class called Vector3d from the Rhino.Geometry
module.
The above code snippet has 3 code chunks relating to force analysis, integration and applying constraints. The sequence of statements mirrors how data flows through the original clusters, with each statement usually mapping to a corresponding Grasshopper component. Note that in the second line, we create an instance of a Vector3d class and assign that object to vec_g
variable. We can subsequently perform various vector arithmetic operations on it. Note as well that position_new
and velocity_new
variables are Point3d
and Vector3d
objects respectively. We can directly access their Z
attribute using the dot notation. Object Oriented Programming encapsulates this concept of classes of objects with attributes and methods. While outside the scope of this session, it will be addressed in greater detail later in this course.
We can argue that this refactored Potato Cannon program is more comprehensible than the original. Instead of having to trace how data flows through myriad wires in nested definitions, we can read the compacted Python code line by line.
Refactoring is the process of restructuring code without changing its original functionality to achieve such goals as improving readability and ease of maintenance. In the first example, we will refactor the ealier Potato Canon [] Grasshopper program and substitute several cluster components—Control/Persist (green) and Analyze/ Integrate/ Constraint (blue)—with equivalent Python components instead. Otherwise, the rest of the program remains untouched.