
One of the most famous pieces of digital art is also one of the first, Georg Nees’ Schotter (German for ‘Gravel’) was produced in 1965, years before digital displays and monitors made it possible to see what was being produced. Instead, Schotter would have been programmed by Nees and only made visible once sent to a plotter. Commenting on the piece in 1974, architect Robert Krawczyk said: “What intrigues me with this ‘ancient’ piece was the use of exact mathematical computations to model a chaotic image and the progression from the ordered to the disordered.” Many others have commented on this image, and it has served as the basis of numerous further artistic explorations and designs. In this first of two examples, we will be looking at applying varying degrees of random rotation to squares in a gridded data structure to achieve the desired results. In the next example we will be combining this with a random jitter to achieve results close to what Nees invented six decades ago.
Step 1: Initial setup

We start by setting up a simple Square Grid of cells. We’ll keep the size rather limited in the testing phase with 5 cells in the X direction and 8 cells in the Y axis. The cells for now will be 5 units wide.
After this we draw a square at the centerpoint of each cell using the Centroid output from Area component. The initial squares will be 3.5 units wide. The squares are drawn using the Rectangle component. To get these centered on each centroid point, we need to set the X size and Y size of our squares in a numerical domain from half the total square width in the negative direction to half the total square width in the positive direction. This is done by using a Division component with the fixed constant ‘2’, and using the positive result on the ‘B’ input of a Construct Domain component, and using a Negative component to give us the negative result of our division operation, feeding this into the ‘A’ input of the same construct domain component. This domain is then used for both the ‘X’ and ‘Y’ inputs of our Rectangle component, with the centroids supply the values for the ‘P’ input.
After this relatively straightforward initial setup, we now need to put our thinking caps on and setup the fundamental logic of this script. We will need to think carefully about data structures! There are basically two fundamental logics going on in Schotter. First, each row of Schotter applies a progressively higher amount of random rotation to the individual squares. This amount is quite small in the early rows, but by the later rows the amount of random rotation is very high. Secondly, we notice that each individual square has its own random rotation, independent of its neighbors in the row. This implies we need to think about two different data structures, and then combine the results to get our final result.
Step 2: Generate random rotation values for each individual square

We will start with obtaining a random rotation value for each individual square in the entire composition. To get a value for each square, we will need to ‘Flatten’ our data structure first by right clicking on our geometry list, and ticking the ‘Flatten’ box. Next we add a Random number generator with three inputs. For the ‘R’ (Range) input, we construct a domain from -90 to 90 degrees. I’ve done this by typing into a ‘Panel’, but alternatively this could be done using the Construct Domain component. For the N (Number) input, we use a List Length component to count the number of items we are generating random values for. A number slider goes into the third ‘Seed’ input. Any number will do, but using the same numbers I use will give the same results as in the images shown.
We are now ready to effect the rotation itself, adding a Rotate component with three inputs. The ‘G’ (geometry) input is our flattened list of squares, the ‘A’ (angle) input requires the results of our random number generator, converted from degrees into radians. Here I’ve used the Radians component to effect this conversion, but you can also right click the ‘A’ input and tick ‘degrees’ depending on your preference. Finally, for the ‘P’ input we need the centerpoint of each square, which uses the ‘Centroid’ output from the Area component taken from the flattened list. If this is done correctly, you will get results as shown in the image 2B. There is no pattern to the random rotation — each square regardless of row has an equal chance of being rotated within the given domain.
If you haven’t flattened your geometry list, you will get results where the same random rotation is applied to each square in the row as in the image above 2A. This is not what we want, but is useful for seeing how the data is currently being structured, which will help in the next step.
Step 3: Generate progressively higher rotation values for each individual row

This next step starts to set up the logic of the progressive rotation, and will be heavily modified in Step 4 so if you are comfortable jumping ahead to Step 4, you can do so. For now though, we will put aside what we’ve just done and do a similar process on the unflattened data to generate progressively higher amounts of rotation for each successive row. Starting with the geometry output from Step 1 (ignoring step 2 for now), we use the Range component to generate a list of values. For now we will use a range from 0 to 45 for the ‘D’ input on range. This domain can be created either typing into a Panel as shown or alternatively using the Construct Domain component. For the number of steps, we need a count of the total number of rows obtained through the List Length component counting the list of unflattened geometry. As almost always happens with the range component, we need one value fewer than this count. For clarity I’ve used the Expression component to show the formula, but this is more commonly done by simply right clicking the N component and selecting the option to type the expression in there.
As in the previous step and only for demonstration purposes, we will now Rotate each square, using the unflattened geometry list for our ‘G’ input, the results of our series converted from degrees to Radians for the ‘A’ input, and the centroids of our squares obtained with the Area component for the ‘P’ input. If you’ve followed along, your results will look like the image above 2B.
Step 4: Combining: Progressive rotation on rows meets individual random rotation values.

In this final step in setting up the basic logic of the Schotter script, we will combine the logic of steps 2 and 3 into a single process. We’ll delete many of the backend components from step 2 and 3 first, including the two rotation and centrepoints. We will also change the domain of our range to 0 to 1 as this will now be in affect a weighting factor which will tell how much of the random rotation angle from the step 2 list will be applied.
Now we combine the flattened list of random rotation values from step 2 with the progressive range of steps from the ‘unflattened,’ structured data from step 3. We do this with the Unflatten component under Set>Tree>Unflatten. This requires two inputs. The first input ‘T’ is the list to unflatten, the second input ‘G’ is the ‘guide’ list, which is our structured data. You should think about which list applies here, but if you need a spoiler, the ‘flattened’ list of random values goes into T while the results of Step 3 go into input T.

We then multiply this ‘unflattened’ list of random rotations by the progressive factor list from step 3 using a Multiply component. We reconstruct circle rotation process using Rotate and Area components as before, and make sure our units are in Radians.
To finish off this part, we then use the Boundary Surfaces component to give each square a shade, along with the Custom Preview component and a Swatch color of your choice. Once this is done, you can start to play around with the initial setup parameters, changing the overall size of the layout, along with the random seed to achieve different compositions.
Optional Improvements
After playing around a bit, you may be feeling like you want the script to do more, which is a good sign, and if you are ambitious you can start to edit the script to your hearts content. I will go through one possible improvement here, with additional refinements in the next example.
Improvement One: Changing the pattern of the original data structure

First, you may want your cells to start their rotation in the top row and progressively rotate more as you go down the page as in the original Schotter, or from left to right or right to left. We can go back to step one and introduce the components Reverse List, Flip Matrix, as well as Reverse List followed by Flip Matrix to achieve one of four possible initial patterns. We can plug the wire for each one into the subsequent script to easily generate results in line with each of the four patterns.
To make switching between these easier, you may consider adding a Stream Filter component which acts as a kind of electrical switch which allows you active one stream input from amongst 2 or more options. Here I have added two input ports to the default component by zooming into the bottom and clicking the + button on the component twice. You can control the filter with a simple slider with values from 0 to 3, or you can do as I have done and use the Value List parameter input which lets you select options from a list, with each value being coded to the integer value of the stream gates.
Hopefully this script has given a bit of a better understanding of working with data structures. Stay tuned for Part 2 where we will finish it off and introduce further refinements!
