Catenary Form-Finding

The Catenary form-finding is an interesting script that has its origins in Gaudi’s chain models that he developed for the Sagrada Familia cathedral. In these models, Gaudi explored structural plasticity using funicular forms (or catenary forms) that are dominated by axial forces (compression and tension forces). These models are efficient and elegant active structural forms based on simple forces. Gaudi’s models use a chain divided into segments that hangs scaled weights representing the forces. The chain curvature is under tension, creating a funicular form. If generated for is flipped, it results a curvature optimised to resist to compression. The catenary form is stable and optimises material usage.

Gaudi's model

Funicular polygon and force polygon for a system of forces - Asger Ostenfeld (1898)-Teknisk Elasticitetslære

Another architect that explored this theme was Heinz Isler. Isler’s experiments are free form physical models that use a fabric surface with a plaster formula. This method is called hanging cloth reversed.

Nowadays, several Grasshopper scripts simulate this process using Kangaroo physics. It is one of the most loved scripts that I have ever seen.

In this tutorial, we recreate this process in Blender/Sverchok using Pulga. Pulga, similar to Kangaroo for Grasshopper, is a physical simulation solver based on particles.

Catenary Form Finding

So, let’s do it.

Creating a Plane and Defining Structural Supports.

First, we create a Plane(Generator-> Plane). We change the values from Size X and Size Y to 30. Then, we use an A number(Number -> A Number) node to define the values for Num X and Num Y, which is the number of divisions of the plane. We use a Viewer Draw (Viz -> Viewer Draw to visualise the plane. In a notation form these operations and values can be defined as follow:

Plane { XY; Size; Size X: 30; Size Y: 30}

A Number {Int; an int: 20} * I did not change from float to int in the example, but using int it is more efficient.

Operations

(A Number (int) [->]) -> (Plane [Num X <-])

(A Number (int) [->]) -> (Plane [Num Y <-])

Creating the Plane

To set up structural supports (or pins) for the shell, we need to first understand the indexes of each vertex of the structure. We use the Viewer Index +(Viz -> Viewer Index +) to access the indexes.

Viewer Index + {Show; BG}

Operations

(Plane [Vertices ->]) -> (Viewer Index + [verts <-])

Let’s have a look at the vertices of the structure. If we want to have a stable structure, we can start humble and cautious by selecting vertices from the extremities (represented in the image in yellow ).

Vertices from the extremeties

But, to make it more dynamic, we can define two structural supports that randomically change position based on a seed value (represented in the image in cian ).

Random Supports

Before starting to create the vertices, we can set up Pulga Physics Solver to receive the pin indexes. We use a Pulga Physics Lite(Pulga Physics -> Pulga Physics Lite) node. Then we connect the Plane vertices and edges to Pulga Physics Lite

Pulga Physics Lite {Collisions; Springs, Drag, World, Pin; Gravity: Z : 1}

Operations

(Plane [vertices ->]) -> (Pulga Physics Lite [Initial_pos <-])

(Plane [edges ->]) -> (Pulga Physics Lite [Springs <-])

Pulga Physics Lite

Structural Supports from the Extremities

Now we can start to define the pins. First, let’s start with the vertices from the extremities.

To access the vertices from the extremities, we need the indexes [0,1,18,19,39,360,379,380,381,398,399]. We could type these numbers to set the pins, but if we decide to change the planes divisions, we have to rewrite the indexes. A better approach is to parametrically define these vertices based on the values that we already have.

For the first support [0,1,20], we can use an A Number (1) node for 0 and another A Number (2) node for 1, because no matters how many divisions the structure has, if it follows the same logic, the 0 and 1 will be included as indexes. Then, we can use the first A number that defines the number of divisions in the plane as our index number 20. If we decide to change the number of divisions, this index will be associated with this number. We create a List Join node to connect these values into a single list, then we connect the List Join output into the Pins input of the Pulga Physics Lite node.

List Join {JoinLevel list: 2}

Operations

(A Number [->]) -> (List Join [data 1 <-])

(A Number (1) [->]) -> (List Join [data 1.1 <-])

(A Number (2) [->]) -> (List Join [data 2.1 <-])

(List Join [data ->]) -> (Pulga Physics Lite [Pins <-])

First Support

For the second support [18,19,39], we can establish the following relation:

index 19 = number of plane divisions -1

index 18 = number of plane divisions -2

index 39 = (number of plane divisions * 2)-1

We use Scalar Math (Sub, Multiply)(Number -> Scalar Math) nodes to create these operations.

Scalar Math {Sub; y : 1}

Scalar Math (1) {Sub; y : 2}

Scalar Math (2) {Multiply; y : 2}

Scalar Math (3) {Sub; y : 1}

Operations

(A Number [->]) -> (Scalar Math [x 1 <-])

(A Number [->]) -> (Scalar Math (1)[x 1 <-])

(A Number [->]) -> (Scalar Math (2)[x 1 <-])

(A Number [->]) -> (Scalar Math (3)[x 1 <-])

(Scalar Math [Out ->]) -> (List Join [data 3.1 <-])

(Scalar Math (1) [Out ->]) -> (List Join [data 4.1 <-])

(Scalar Math (2) [Out ->]) -> (Scalar Math (3) [x <-])

(Scalar Math (3) [Out ->]) -> (List Join [data 5.1 <-])

Second Support

For the third support [379,398,399]:

index 399 = (number of plane divisions * number of plane divisions)-1

index 398 = (number of plane divisions * number of plane divisions)-2

index 379 = ((number of plane divisions * number of plane divisions)-number of plane divisions)-1

We use the Scalar Math node again to create these operations.

Scalar Math (4) {Multiply; x: A Number; y: A Number}

Scalar Math (5) {Sub; y: 1}

Scalar Math (6) {Sub; y: 1}

Scalar Math (7) {Sub; y: A Number}

Scalar Math (8) {Sub; y: 1}

Operations

(A Number [->]) -> (Scalar Math (4) [x,y <-])

(Scalar Math (4)[Out ->]) -> (Scalar Math (5) [x <-])

(Scalar Math (5)[Out ->]) -> (Scalar Math (6) [x <-])

(A Number [->]) -> (Scalar Math (7) [y <-])

(Scalar Math (4)[Out ->]) -> (Scalar Math (7) [x <-])

(Scalar Math (7)[Out ->]) -> (Scalar Math (8) [x <-])

(Scalar Math (5) [Out ->]) -> (List Join [data 6.1 <-])

(Scalar Math (6) [Out ->]) -> (List Join [data 7.1 <-])

(Scalar Math (8) [Out ->]) -> (List Join [data 8.1 <-])

Third Support

For the fourth support [360,380,381]:

index 380 = (number of plane divisions * number of plane divisions)-number of plane divisions

index 381 = ((number of plane divisions * number of plane divisions)-number of plane divisions)+1

index 360 = ((number of plane divisions * number of plane divisions)-number of plane divisions)-number of plane divisions)

The Scalar Math operations:

Scalar Math (9) {Sub; x: Scalar Math (4); y: A Number}

Scalar Math (10) {Add; x: Scalar Math (9); y: 1)

Scalar Math (11) {Sub; x: Scalar Math (9); y: A Number}

Operations

(Scalar Math (4)[Out ->]) -> (Scalar Math (9)[x <-])

(A Number [->]) -> (Scalar Math (9)[y <-])

(Scalar Math (9)[Out ->]) -> (Scalar Math (10)[x <-])

(Scalar Math (9)[Out ->]) -> (Scalar Math (11)[x <-])

(A Number [->]) -> (Scalar Math (11)[y <-])

(Scalar Math (9) [Out ->]) -> (List Join [data 9.1 <-])

(Scalar Math (10) [Out ->]) -> (List Join [data 10.1 <-])

(Scalar Math (11) [Out ->]) -> (List Join [data 11.1 <-])

Four Support

We need to convert the values from the List Join node into integer values. We use a Float to Int(Number -> Float to int) node.

Operations

(List Join [Out ->]) -> (Float to Int [float <-])

Float to Int

It is time to visualise the shell. We use a Viewer Draw (1) connecting Pulga Physics Lite vertices and Plane edges and polygons outputs. Then, we change the parameters of Pulga Physics Lite, incrementing iterations one by one.

Pulga Physics Lite {Collisions; Springs; Drag; World; Pin; Gravity: Z : 1; Spring Length: 0.30; Spring Stifiness: 0.19; Radious: 0.45; Max Velocity: 0.22; Iterations: 43}

Operations

(Pulga Physics Lite [Vertices ->]) -> (Viewer Draw (1)[Vertices <-])

(Plane [edges ->]) -> (Viewer Draw (1)[Edges <-])

(Plane [Polygons ->]) -> (Viewer Draw (1)[Faces <-])

Running Pulga

Random Structural Supports

Now we need to create random structural supports. First, we use a List Length(List -> List Main -> List Length node to count the vertices of the plane. Then, we use a Random Num Gen(Number -> Random Num Gen) to generate the two random numbers that will define the structural supports.

Random Num Gen {Int; Size: 2}

Operations

Plane [vertices ->]) -> (List Length [data <-])

(List Length [Length ->]) -> (Random Num Gen [Int High <-])

Random Number

There are two methods for structural supports creation that follows the same logic:

Method 1

Random Number

Random Number + 1

Random Number + Number of Plane Divisions

Random Number + Number of Plane Divisions + 1

Method 2

Random Number

Random Number - 1

Random Number + Number of Plane Divisions

Random Number + Number of Plane Divisions - 1

Random Support Logic

We use Method 1 in this example, using the Scalar Math to create these operations:

Scalar Math (12) {Add; x: Random Num Gen; y: 1}

Scalar Math (13) {Add; x: Random Num Gen ; y: A Number)

Scalar Math (14) {Sub; x: Scalar Math (13); y: 1}

Operations

(Random Num Gen [Value ->]) -> (Scalar Math (12,13) [x <-])

(A Number [->]) -> (Scalar Math (13) [y <-])

(Scalar Math (13) [Out ->]) -> (Scalar Math (14) [x <-])

Random Operations

We use a List Item(List -> List Struct -> List Item) node for each Random parameter stabilised before. It will divide the random values–that are composed of a list of two values–in a single number.

(Random Num Gen [Value ->]) -> (List Item [Data <-])

(Scalar Math (12) [Value ->]) -> (List Item (1)[Data <-])

(Scalar Math (13) [Value ->]) -> (List Item (2)[Data <-])

(Scalar Math (14) [Value ->]) -> (List Item (3)[Data <-])

List Item

Then, we connect those List Item nodes into the List Join node.

(List Item [Item ->]) -> (List Join [data 12.1 <-])

(List Item [Other ->]) -> (List Join [data 13.1 <-])

(List Item (1) [Item ->]) -> (List Join [data 14.1 <-])

(List Item (1) [Other ->]) -> (List Join [data 15.1 <-])

(List Item (2) [Item ->]) -> (List Join [data 16.1 <-])

(List Item (2) [Other ->]) -> (List Join [data 17.1 <-])

(List Item (3) [Item ->]) -> (List Join [data 18.1 <-])

(List Item (3) [Other ->]) -> (List Join [data 19.1 <-])

Connecting List Items

Creating Panels

Now we create panels. First, we use the Triangulate Mesh(Modifiers -> Modifiers Change -> Triangulate Mesh). We connect the vertices from Pulga Physics Lite and edges and polygons from Plane.

Triangulate Panels

Then, we use the Dual Mesh(Modifiers -> Modifiers Make -> Dual Mesh) node to create the dual mesh based on the triangles. We connect the corresponding outputs from Triangulate Mesh into Dual Mesh input.

Dual Mesh

Now, we use Inset Faces(CAD -> Inset Faces) to create openings in the panels. We connect Dual Mesh output into Inset Faces input.

Inset Faces {Out; Individual; Thickness: 0.19}

Inset Faces

Finally, we use a List Mask (Out)(List -> List Mask (Out)) node to filter opening and frames.

List Mask (Out) {Levels: 2}

Operations

Inset Faces [Faces ->]) -> ( List Mask (Out) [data <-])

Inset Faces [Mask ->]) -> ( List Mask (Out) [mask <-])

List Mask (Out)

To finalise, we connect List Mask (Out) outputs in two Mesh Viewer(Viz -> Mesh Viewer) nodes.

Operations

(Inset Faces [Verts ->]) -> (Mesh Viewer [vertices <-])

(Inset Faces [Verts ->]) -> (Mesh Viewer (1) [vertices <-])

(List Mask (Out) [dataTrue ->]) -> (Mesh Viewer [Faces <-])

(List Mask (Out) [dataFalse ->]) -> (Mesh Viewer (1) [Faces <-])

Mesh Viewer

Now, we can test change the random structural supports, trying to find a more appealing form.

Testing

You can download the blend file here

Have fun. Monkey working