10.014 CTD
  • Overview
  • Schedule
  • Administrative
    • Accessing Rhino remotely
    • Rhino for Mac
  • ASSIGNMENTS
    • Dates and rubrics
    • Generative design
      • Generative design
    • Parametric design
      • Parametric design
    • Simulated design
      • Simulated design
      • Simulated design
  • SESSION 1B
    • Computer Aided Design
    • Ranges and expressions 1
      • Ranges and expressions 2
      • Ranges and expressions 3
      • Ranges and expressions 4
      • Ranges and expressions 5
      • Ranges and expressions 6
  • SESSION 2A
    • Visual programming 1
      • Visual programming 2
      • Visual programming 3
      • Visual programming 4
    • Associative modelling 1
      • Associative modelling 2
      • Associative modelling 3
  • SESSION 2B
    • Logical Patterns 1
      • Logical patterns 2
      • Logical patterns 3
  • SESSION 3A
    • Spatial geometry 1
      • Spatial geometry 2
      • Spatial geometry 3
      • Spatial geometry 4
      • Spatial geometry 5
      • Spatial geometry 6
      • Spatial geometry 7
    • Curve geometry 1
      • Curve geometry 2
      • Curve geometry 3
      • Curve geometry 4
  • SESSION 3B
    • Surface geometry
    • Parametric modelling 1
      • Parametric modelling 2
      • Parametric modelling 3
      • Parametric modelling 4
  • SESSION 4A
    • Information nesting 1
      • Information nesting 2
      • Information nesting 3
    • Data landscapes 1
      • Data landscapes 2
      • Data Landscapes 3
      • Data landscapes 4
  • SESSION 4B
    • Mesh geometry 1
      • Mesh geometry 2
      • Mesh geometry 3
  • SESSION 5A
    • Space and time 1
      • Space and time 2
    • Modelling entities 1
      • Modelling entities 2
      • Modelling entities 3
  • SESSION 5B
    • Multibody dynamics 1
      • Multibody dynamics 2
    • Material elasticity 1
      • Material elasticity 2
      • Material elasticity 3
  • SESSION 6A
    • Form-finding 1
      • Form-finding 2
      • Form-finding 3
      • Form-finding 4
  • SESSION 6B
    • AI Image generation 1
      • AI Image generation 2
      • AI Image generation 3
  • APPENDIX
    • Spirograph 1
      • Spirograph 2
    • Curves
    • Swarm Intelligence 1
      • Swarm Intelligence 2
    • Hybrid programming 1
      • Hybrid programming 2
Powered by GitBook
On this page
  • Revisiting the Potato Cannon
  • Python: initialise and saving state
  • Python: physics logic
  1. APPENDIX

Hybrid programming 1

Introducing python - Projectile physics

PreviousSwarm Intelligence 2NextHybrid programming 2

Last updated 7 months ago

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.

Revisiting the Potato Cannon

Python: initialise and saving state

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.

# import sticky dictionary from scriptcontext module
from scriptcontext import sticky

We now take a closer look at the code written in PyInitialise. The above statement imports the sticky dictionary from the scriptcontext module

# Initialise 
# -------------------------
if 'positions' not in sticky:
    sticky['positions'] = init_positions
if 'velocities' not in sticky:
    sticky['velocities'] = init_velocities
if 'masses' not in sticky:
    sticky['masses'] = init_masses

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!

# When buttons are pressed
# -------------------------
if reset:
    sticky['positions'] = init_positions
    sticky['velocities'] = init_velocities
    sticky['masses'] = init_masses

a = sticky['positions']
b = sticky['velocities'] 
c = sticky['masses']

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.

# Update dictionary with new values
sticky['positions'] = positions
sticky['velocities'] = velocities
sticky['masses'] = masses

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.

Python: physics logic

­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.

"""Computes and returns the future state of particle
    Inputs:
        position: Particle position (Point3d)
        velocity: Particle velocity (Vector3d)
        mass: Particle mass (float)
        k: Drag factor (float)
        dt: Time step (float)
    Output:
        a: Position
        b: Velocity
        c: Mass
        d: Force (Vector3d)"""

from Rhino.Geometry import Vector3d

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.

# 1) Analysis
vec_g = Vector3d(0, 0, -9.8)
f_grav = mass * vec_g
f_drag = -k * velocity
f_sum = f_grav + f_drag

# 2) Integrate
accel = f_sum/mass
dv = accel * dt
velocity_new = velocity + dv
dp = velocity_new * dt
position_new = position + dp

# 3) Constraints
if position_new.Z < 0:
    # Set z coordinate of position to zero
    position_new.Z = 0
    # Reverse z component of velocity
    velocity_new.Z *= -1

a = position_new
b = velocity_new
c = mass
d = f_sum

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.

>
20KB
1_Physics-01_toPy.gh