This repo is for generative art with PostScript output that can be printed to standard 3 1/8 inch (80 mm) thermal receipt paper. I personally use this Rongta point-of-sale (POS) printer to print my artwork. For example, here is the same banner above printed out:
I like to make my art at art trading card size (2.5 x 3.5 in), so this code is designed around that. This size is conveniently just a little bit narrower than the paper, so it pairs well.
I jokingly refer to my receipt printer as a "paper toaster" since a thermal printer works by heating the thermal paper, darkening it. No ink is used.
GhostScript must be installed for PDF and PNG exports to work
# Generate the postscript file in output/<artwork_id>.ps
# see -h for options.
python main.py [global_options] <artwork_id> -- [artwork_options]
The output documents are written to output/<artwork_id>_*
. This includes:
<artwork_id>.ps
- The PostScript file generated by the script<artwork_id>.pdf
- (Requires GhostScript) a PDF version of the document. I find this to be the easiest to use for printing<artwork_id>_print.png
- (Requires GhostScript) a PNG preview version at 300 DPI. This size is usable for printing, though the PDF version<artwork_id>_web.png
- (Requires GhostScript) a PNG version at 200 DPI. I use this to make larger screenshots for my website.<artwork_id>_thumbnail.png
- (Requires GhostScript) a PNG version at 100 DPI. I use this for thumbnails in the README and on my website.
There are a few options that can be used with any artwork to control the page size and layout. However, some artworks may be designed for specific configurations, such as one trading-card sized page.
main.py
and the subcommand!
E.g. main.py --seed 100 robot_walks
works, but not
main.py robot_walks --seed 100
.
Option | Description |
---|---|
--num-cards N |
How many 2.5x3.5 inch trading cards tall will the receipt be. For example, I sometimes print receipts that are 3 cards tall. |
--page-width WIDTH_INCHES |
Override the width of the page to be any size in inches. |
--page-height HEIGHT_INCHES |
Override the height of the page to be any size in inches. |
--landscape |
Make the ouput document landscape rather than portrait. |
--seed SEED |
If provided, random.seed() will be called with this value to make the results reproducible. Otherwise, a seed will be chosen automatically and printed to the console. |
As with many of my projects, I keep a log of what I worked on over time. This includes notes, experiments, links to resources I used along the way, etc.
You can find it here:
The sections below give a summary of the different artworks and explain the parameters. They are listed in reverse chronological order to feature newest artworks at the top.
Examples marked with 🧪 indicate artistic experiments by messing with the parameters in ways I didn't originally intend.
Examples with a Seed column can be reproduced exactly using the --seed N
global option to set the random seed.
This receipt creates patterns inspired by the Project Euler problem #208: Robot Walks. This script does not solve the puzzle, but it does visualize some of the paths. This script produces two types of paths:
- Looping paths with 5-fold symmetry (most of the time)
- Infinitely repeating paths (sometimes)
There are more possibilities, but these ones have more symmetry.
Parameters:
Parameter | Description |
---|---|
-n/--num-steps N |
How many steps the robot will take. The path will be repeated 5 times as this usually causes the robot to loop back to the start. |
-l/--loop |
If set, the script will keep trying until it finds a path that loops. |
-d/--dots |
If set, draw a dot at each position on the robot's path. |
-p/--path-type {arc, polyline} |
Select between curved arcs and straight polylines. |
-r/--render-type {stroke,fill,eofill} |
Select between stroked, filled, or even-odd filled paths. |
Examples:
This receipt is more of a utility than an artwork. It simply draws a to do list template with checkboxes that you can print out and write on. It's also nice for a shopping list!
Parameters:
Parameter | Description |
---|---|
-s/--row-size |
Size of a row in inches (including padding). Defaults to 1/4 inch |
Examples:
Example | Command | Description |
---|---|---|
![]() |
main.py todo |
Default settings |
This tiling was an artistic way of visualizing flow in a grid.
First, a grid of cells is created. Then, for each edge between cells, a direction across the edge is chosen randomly. Then, for each cell, based on which directions the flow in/out of the tile is going, one of several tiles is chosen.
Randomly placing the tiles would be an easier implementation, but this method ensures that if you follow any path from a source (closed circle) to a sink (open circle), the flow direction will look consistent.
Parameters:
Parameter | Description |
---|---|
-s/--square-size SQUARE_SIZE |
The size of each grid square. Defaults to 1/4 inch |
Examples:
Example | Command | Description |
---|---|---|
![]() |
main.py edge_directions |
Simple example |
![]() |
main.py edge_directions -s 0.125 |
Set the square size to 1/8 of an inch |
A colorized version of the older braids
tiling. Obviously, this looks better
on a screen or a color printer rather than the grayscale of a receipt printer.
Parameters:
Parameter | Description |
---|---|
-s/--square-size SQUARE_SIZE |
The size of each grid square. Defaults to 1/4 inch |
-w/--stroke-width WIDTH_POINTS |
The width of a single strand in points (1/72 of an inch) |
-c/--swap-chance CHANCE |
The chance of swapping a braid strand with its neighbor as a number between 0.0 and 1.0 |
-i/--invert-colors |
Invert the colors so the braids stand out |
-g/--groups GROUPS_CSV |
CSV of positive integers the determines groups of strands to weave. E.g. 3,4 means weave the first 3 strands, and the next 4 strands separately. If not specified, all strands are woven. If there are not enough groups for the number of strands, the list of groups will be repeated |
Examples:
I've always liked the look of isometric grids in video games and art, so this artwork makes a mountain-like scene on an isometric grid.
The scene is an illusion. I'm just overlaying a bunch of solid-color parallelograms from back to front to make the scene. This is somewhat inspired by 2D tile-based iso rendering, but much simpler to implement. Of course, it only works well for this carefully curated scene.
To make the mountain shape, I randomly generate heights in each cell such that a cell is equal or taller than any of its neighbors closer to the "camera".
Parameters:
This script generates a random isometric scene each time. There are no parameters.
Examples:
Example | Command |
---|---|
![]() |
main.py iso_grid |
![]() |
main.py iso_grid |
A pattern of strands of rope being woven together in a braid pattern. The crossings are randomly chosen.
Parameters:
Parameter | Description |
---|---|
-s/--square-size SQUARE_SIZE |
The size of each grid square. Defaults to 1/4 inch |
-i/--invert-colors |
If true, invert the color scheme |
Examples:
Example | Command | Description |
---|---|---|
![]() |
main.py braids |
Default settings |
![]() |
main.py braids -i |
Inverted colors |
This script is a simple implementation of an elementary cellular automaton.
There are a few differences from the description on Wolfram Mathworld:
- Instead of starting from a single seed point, the first row is randomly generated
- When examining neighbors, this script wraps around the boundary
- The pattern is generated from bottom to top given PostScript's coordinate system
Parameters:
Parameter | Description |
---|---|
RULE |
The rule number from 0 to 255 |
Examples:
Example | Command | Description |
---|---|---|
![]() |
main.py elementary_ca 30 |
Rule 30. This pattern is chaotic and is like a simplified model for the patterns on sea snail shells (see Olivia porphria) |
![]() |
main.py elementary_ca 112 |
Rule 112 |
![]() |
main.py elementary_ca 161 |
Rule 161 |
Barcodes are ubiquitous, but I didn't know much about how they encode data. So I tried implementing Code 128 barcodes since these can store not just numbers but ASCII characters too.
This receipt works best with the --landscape
global option, and for longer
strings of text, increasing the --num-cards
is necessary.
Also note that barcodes are easier to scan on paper than on a screen, and it works best when the barcode is flat. I'm able to use my Android phone's camera to scan it, but sometimes third-party apps are necessary.
Parameters:
Parameter | Description |
---|---|
TEXT |
An ASCII string to turn into a barcode |
Examples:
Example | Command | Description |
---|---|---|
![]() |
main.py --landscape barcode128 "Paper Toaster" |
Simple example that encodes the text "Paper Toaster" |
Turtle graphics patterns based on the Bridges math art paper "Let the Numbers Do the Walking: Generating Turtle Dances on the Plane from Integer Sequences"
This implementation allows using different sequences of integers:
- The natural numbers
- The square numbers
- The triangle numbers
- The fibonnaci sequence
Parameters:
Parameter | Description |
---|---|
SEQUENCE |
One of natural , square , triangle , fibonacci |
A |
The first integer modulus |
B |
The second integer modulus |
-d/--divisions |
A divisor of 360 degrees used to determine the minimum turn angle. |
-f/--fill |
If set, the path will be filled with odd/even fill |
Examples:
Sometimes I want a large number of dice rolls (e.g. for drawing dice-generated art on paper), but rolling that many dice would be inconvenient (because it would be tedious, noisy, or I don't have dice on hand).
Certainly it's easy to find apps to do dice rolls, but sometimes I want to get away from my phone. So it would be handy to roll many dice up front, then cross off entries from the list as I use them.
This script simply generates a bunch of dice rolls of the form (NdX + M) in standard dice notation and prints them out in a table.
Parameters:
Parameter | Description |
---|---|
N |
(required) How many dice are summed for each roll. For example, 2 would mean roll two dice and add their results |
SIDES |
(required) Each die rolled will have this many sides. |
-m/--modifier MODIFIER |
A constant value to add to the result. |
Examples:
Example | Command | Description |
---|---|---|
![]() |
main.py quiet_dice 1 6 |
Simple example |
![]() |
main.py quiet_dice 2 10 |
Example where each roll adds two 10-sided dice. |
![]() |
main.py quiet_dice 1 20 -m 2 |
Example with a modifier |
This artwork is based on Hitomezashi embroidery patterns, inspired by this Numberphile video. Hitomezashi is a form of Sashiko embroidery, decorative running stitch patterns that help reinforce clothing.
This script lets you specify two integers to set the bit pattern for the rows and columns (see the video to see how this works). Large numbers with a variety of 1s and 0s work best.
Parameters:
Parameter | Description |
---|---|
ROW_BITS |
An integer representing the bits for the rows (least significant bit first). It can be a decimal number or a binary number prefixed with 0b . Rows are numbered from bottom to top of the document. |
COL_BITS |
An integer representing the bits for the columns (least significant bit first). It can be a decimal number or a binary number prefixed with 0b . Columns are numbered from left to right. |
-o/--odd-even |
If true, the pattern is filled with an odd/even coloring. |
-s/--square-size |
Size of each square in inches. Defaults to a quarter of an inch |
Examples:
Hex grid with some optional gaps between hexagons. It also can render dots instead of lines.
This artwork only supports a single card-length receipt.
Parameters:
Parameter | Description |
---|---|
-s/--side-length SIDE_LENGTH |
The side length of each hexagon in inches. Defaults to 1/4 inch |
-e/--expand-factor EXPAND_FACTOR |
How much to expand/contract the grid for artistic effect |
-d/--dots |
Draw the hexagon vertices, but do not connect them. This can be used to make a hex grid |
Examples:
This was a warm-up exercise, this makes a card with graph paper.
Parameters:
Parameter | Description |
---|---|
-s/--square-size SQUARE_SIZE |
The size of each grid square. Defaults to 1/4 inch |
Examples:
Example | Command | Description |
---|---|---|
![]() |
python main.py grid |
Default settings (1/4 inch grid when printed) |
![]() |
python main.py grid -s 0.125 |
finer 1/8 inch grid |