A Python program based on Inkscape. Recurrink is used to create these recurring patterns that you can see on Instagram.
Rather than call them patterns, we shall call them rinks. Blocks, Cells and Models make a rink.
- A model is a template made from repeating blocks
- A block is a collection of cells, marked by the red box above
- A cell is the smallest unit as shown in blue
An example rink is shown below.
source file soleares.svg
Seen another way, a rink is a vector graphic with embedded style sheets.
Recurrink has been tested on Ubuntu and Mac. These notes are for a Mac π».
- Apple M1 Pro
- Ventura 13.4.1
Pre-requisite casks
- brew install pygobject3
- brew install cairo
- brew install pkg-config
The cairo and pkg-config casks are only needed because Instagram does not accept SVG π
Python wrappers
- pip3 install pycairo
- pip3 install inkex
Installing Inkscape is not necessary. The Python modules used by Recurrink are for Inkscape Extension developement and run independently of the GUI.
Postgres was also installed with homebrew and created as follows.
$ createdb -U ${USER} recurrink
$ psql -d recurrink
On linux it was a bit different.
sudo -i -u postgres # only needed once
postgres@host:$ psql -d recurrink # connects as logged in user
Once postgres is installed and this repo has been cloned then cd sql
and start postgres. Then setup the database as follows.
recurrink=# \i ddl.sql # create tables required by recurrink
recurrink=# \i dml.sql # add two models
Now your local machine should be able to run Recurrink and make rinks π
- Read about Inkscape Extensions and visit the gallery.
- Tutorial on Inkscape Extensions
- Inkscape reference documentation
- Post on the Inkscape Extension Forum
This section is aimed at those wanting to build rinks. Following sections will cover software development. The lifecycle of a rink has three main phases
- Initialise
- Update
- Commit
The objects that change state during the cycle are:
Config
a temporary text file that holds the attributes of each cell.SVG
the image built from the config by Inkscape.Database
permenant record that makeup a library of all rinks made by one user.
This table shows at what phase an object will change during the cycle.
Config | SVG | Database | |
---|---|---|---|
Init | Write | - | - |
Update | Read | Write | - |
Commit | Read | - | Write |
Before initialisation all objects are empty. After initialisation the Config is ready. Where the config data is sourced from depends on which of the following options are given at build time.
- No arguments (default)
- Model
- View
The first option randomly generates a model name and randomly creates attributes for each cell. The second option also randomly generates values for cells. But for the model that was defined. The third option populates the Config by cloning from the database, using the view reference as an index.
Between Initialisation and Update is when editing of the Config takes place. This is where the artistry happens π§βπ¨ as the cell attibutes define the appearance of the rink. For example, to convert all shapes to square open the config in your favorite text editor.
$ vi /tmp/soleares.txt
cell shape size facing top fill bg fo stroke sw sd so
b square small all False #FFF #FFF 1.0 #000 1 0 1.0
a square medium all False #FFA500 #FFF 0.5 #FFA500 6 0 0.3
c square medium all False #FFF #000 1.0 #000 0 0 1.0
d circle small all True #FFA500 #FFF 1.0 #FFA500 9 1 1.0
Navigate to cell d on the fifth line and replace circle
with square
. Save exit and run an update. Then open /tmp/soleares.svg
and expect to see only squares.
This process repeats until commit time. π At the commit phase, cell attributes are written to the database and become the Primary, Immutable source. Although no further editing is possible, rinks can be cloned from the database (unless they are deleted).
Making a rink is done at the command-line. Use the help to get a list of options
$ ./recurrink.py -h
usage: recurrink [-h] {list,read,init,commit,delete,update} ...
positional arguments:
{list,read,init,commit,delete,update}
sub-command help
list list models in db
read get view metadata
init set config for new image
commit write immutable entry to db
update update svg from config
optional arguments:
-h, --help show this help message and exit
The three main doing commands are Init, Update and Commit. You can also drill down and get more detail on the sub-commands.
$ ./recurrink.py read --help
usage: recurrink read [-h] [-m MODEL] [-v VIEW]
options:
-h, --help show this help message and exit
-m MODEL, --model MODEL name of base model
-v VIEW, --view VIEW hex name with 32 char
List is a good place to start because it shows the models that are available in the database. Most commands need you to supply a model name. For example,init -m soleares
will create the Config file in /tmp
that is shown above.
The command read -v c152bcd1dee8915e90dfe28c05bf3774
is only used to provide the publisher with metadata. And delete
is for clearing up any mess π©
But when read
is combined with MODEL it displays how cells are organised in a block.
The positon for each cell in the block is shown by the read
sub-command.
a b a
c d c
source file soleares.csv
π‘ Knowing how cells are laid out in the block is the secret ingridient of good pattern design.
Another technique for locating cells in the block is labelling. Cells are named a-z. Referring to them as A-Z will cause Recurrink to print a label instead of rendering an SVG object. Try to capitalise a cell in the configuration file and then run an update and check the SVG output.
Scale and control are applied during the Update phase. The control is passed in via the CLI as a two digit code. The first digit defines the scale and the second the control.
-
Scale
To zoom out use an odd number [1-9] and an even number [2-8] to zoom in. A zero or no arguments keeps the aspect at 1:1. -
Control
Styles and Geometries can be transformed using the control parameter. For example,update --control 01
replaces all shapes with square and removes all the strokes are removed. The default zero control means that no transformations are done and the Config is written to the SVG unchanged.
Rinks are published to Instagram using another program. As the publisher program is not (yet) in the public domain, we just describe the high-level flow:
- read symlinks from the
pubq/
folder and selected the oldest by default - run cairo to generate a PNG from the SVG by following the symlink to
rinks/MODEL/VIEW.svg
- run LFTP to transfer the PNG to a public endpoint, in this case recurringart.com
- post the location to the Facebook graphQL service as explained on post to page.
The PNGs are formatted according to the recommended specification by Instagram
- 1080 x 1080 pixels at 1:1 aspect ratio (square)
This section is aimed at anyone wanting to extend the Python program.
At the root of the data hierarchy is a model
. A model is the base template that defines how the rink repeats itself. Most models have musical names.
βββ afroclave
βββ arpeggio
βββ soleares *
...
βββ koto *
βββ bossa
* included in sql/
The database has the following tables.
Table | Description |
---|---|
models | Base template |
blocks | Cell co-ordinates for each model |
views | Instance of a model |
cells | Combine geometries and styles to form a cell |
geometry | Defines a linear shape |
styles | CSS attributes to style a shape |
Creating a new model cannot be done through the CLI. You will need to write SQL statements to create new table entries. There are some examples in sql
directory. The tables that are written to by Recurrink are: views, cells, geometry and styles.
Each cell is formed from shape and style definitions. These definitions are not pre-computed, but generated on-demand.
The software is designed around the data model as shown in the class diagram.
source: classv2.svg
Classes are named after the following convention.
create | INSERTS given items |
read | query logic to apply given filters |
validate | checks inputs before create or update |
generate | makes a new set of cells with some randomness |
transform | applies changes to input data based on control |
delete | has a given ID and removes rows |
Instead of CRUD think CRVGTD π
- Cells and views can be CREATED or DELETED.
- Geometries and styles can only be CREATED.
- Nothing can be UPDATED
The rationale is that immutability guarantees isolation. The design objective is to guarantee that when a rink (at whatever point in the future) is cloned from the database it will render the same SVG that was committed.
There are a finite number of cells that are possible from the combination of geometries and styles. Even though they are not infinite, there is still a high number (~0.5 billion) permutations.
The more cells that are defined in the database, the greater available for random selection and greater diversity in the final output. However, pre-defining is wasteful because the database would have to store all those redundant records.
The trade-off is to create new cells on-demand. Demand is provided by the .generate() functions, or when tmpfiles are edited. Using the on-demand approach means that before a rink is written, Recurrink first checks that each cell is uniq. Only when the cell is uniq is a new entry INSERTED.
A geometry can be marked as top equal to True in either the config file, or the database. At build time the cell marked as top will will be rendered last using paint order. When another object has already been allocated those co-ordinates then the last cell appears on top.
There are two use-cases where top affects the output. Shapes with a large
size may overlap a neighhouring cell. In this case top can control the overlapping region. The other use case is when shapes are combined to create a new shape. Consider this sample entry in the blocks table.
point | cell | top |
---|---|---|
0, 0 | a | m |
Recurrink will place both cells a and m at the same co-ordinate. If, for example a is defined as a north-facing line and m as an easterly-facing line then a cross + shape will appear.
When the init command is run from the CLI the drawing objects will have their generate() method called. Generate() produces a tmpfile to start the authoring process. Cells that are generated exclusively to be on top of another cell do not have a home position. We call them a virtual top cell. When a virtual top cell is marked as top:False it is hidden. A normal cell may also double as a top cell. In this case top:False stops it appearing on top of its paired cell, but it still appears in its home position.
- run one test
python3 -m unittest t.cells.TestCells.testGetCell
- run all tests in
t/cells.py
python3 -m unittest t.cells
Test card is a tool to visually test the Draw class. It creates a sample shape for each SVG object supported by the class.
$ cp samples/testcard.txt /tmp
$ ./recurrink.py update -m testcard
factor 1.0 control 0
........................................
/tmp/testcard.svg
$ open -a safari /tmp/testcard.svg
My Dad was a self-taught mathematician. I studied Fine Art. My career took me into Software Engineering and then Architecture where I started using diagramming tools and vector graphics. Then in my own time I learnt something about Afro-Cuban rhythms and got into patterns. Recurrink was initially a website for drawing patterns, then an Inkscape extension and now a CLI tool making daily-patterns for Instagram. What next π€·