Python for Designers

by Roberto Arista

Fork me on GitHub

Cookbook

abstract-cookbook.svg

Input/Output

Load CSV table (.py)

import csv

def loadTable(filePath, delimiter, quotechar):
    with open(filePath, mode='r', encoding='utf-8') as csvFile:
        tableReader = csv.reader(csvFile, delimiter=delimiter, quotechar=quotechar)
        return list(tableReader)


if __name__ == '__main__':
    table = loadTable('table.csv', '\t', '|')
    # [['city', 'country'], ['Shanghai', 'China'], ['New York', 'United States'], ['Roma', 'Italy']]

Load CSV table with keys (.py)

import csv

def loadTableWithKeys(filePath, delimiter, quotechar):
    with open(filePath, mode='r', encoding='utf-8') as csvFile:
        tableReader = csv.DictReader(csvFile, delimiter=delimiter, quotechar=quotechar)
        return list(tableReader)


if __name__ == '__main__':
    table = loadTableWithKeys('table.csv', '\t', '|')
    # [
    #     OrderedDict([('city', 'Shanghai'), ('country', 'China')]),
    #     OrderedDict([('city', 'New York'), ('country', 'United States')]),
    #     OrderedDict([('city', 'Roma'), ('country', 'Italy')])
    # ]

Write csv table (.py)

import csv

def writeTable(data, fileName, delimiter):
    with open(fileName, mode='w', encoding='utf-8') as csvFile:
        tableWriter = csv.writer(csvFile, delimiter='\t')
        tableWriter.writerows(data)


if __name__ == '__main__':
    data = [
        ('city', 'country'),
        ('Shanghai', 'China'),
        ('New York', 'United States'),
        ('Roma', 'Italy')
    ]
    writeTable(data, 'table.csv', delimiter='\t')

read lines from text file (.py)

def readStringsFromFile(fileName):
    with open(fileName, mode='r', encoding='utf-8') as txtFile:
        return [ll.rstrip() for ll in txtFile.readlines()]


if __name__ == '__main__':
    words = readStringsFromFile('words.txt')
    # ['immediatamente', 'realmente', 'estremamente', 'solennemente', 'precedentemente', 'sicuramente', 'magnificamente', 'geneticamente', 'originariamente', 'seriamente', 'ingiustamente', 'ovviamente', 'sessualmente', 'particolarmente', 'indipendentemente']

write lines to text file (.py)

def writeStringsToFile(fileName, strings):
    with open(fileName, mode='w', encoding='utf-8') as txtFile:
        for eachStr in strings:
            txtFile.write(f'{eachStr}\n')


if __name__ == '__main__':
    words = ['immediatamente', 'realmente', 'estremamente', 'solennemente', 'precedentemente', 'sicuramente', 'magnificamente', 'geneticamente', 'originariamente', 'seriamente', 'ingiustamente', 'ovviamente', 'sessualmente', 'particolarmente', 'indipendentemente']
    writeStringsToFile('words.txt', words)

Col­lect­ing files from a folder (.py)

from os.path import join, isfile
from os import listdir

def collectFilesPaths(folder, extension=''):
    """hidden files (starting with a dot) are filtered out"""
    paths = []
    for eachFileName in [nn for nn in listdir(folder) if not nn.startswith('.')]:
        eachPath = join(folder, eachFileName)
        if isfile(eachPath) and eachPath.endswith(extension):
            paths.append(eachPath)
    return paths

if __name__ == '__main__':
    filePaths = collectFilesPaths('someFolder')

Col­lect­ing fold­ers from a folder (.py)

from os.path import join, isdir
from os import listdir

def collectSubFolders(folder):
    return [join(folder, sub)
            for sub in listdir(folder)
            if isdir(join(folder, sub))]

if __name__ == '__main__':
    subFolders = collectSubFolders('someFolder')

Clean­ing a folder (.py)

from shutil import rmtree
from os import mkdir
from os.path import exists

def clean(folder):
    if exists(folder):
        rmtree(folder)
    mkdir(folder)

if __name__ == '__main__':
    clean('someFolder')

Delet­ing file with a spe­cific ex­ten­sion from a folder (.py)

from os import remove, listdir

def deleteFilesFromFolder(folder, extension):
    paths = [nn for nn in listdir(folder) if nn.endswith(extension)]
    for eachP in paths:
        remove(eachP)

if __name__ == '__main__':
    deleteFilesFromFolder('someFolder', '.jpg')

Colors

Con­vert­ing hexa­dec­i­mal to RGB (255) (.py)

def HEX2RGB(hexColor):
    offset = 1 if hexColor.startswith('#') else 0
    rr = int(hexColor[offset:offset+2], 16)
    gg = int(hexColor[offset+2:offset+4], 16)
    bb = int(hexColor[offset+4:], 16)
    return rr, gg, bb

if __name__ == '__main__':
    rgbColor = HEX2RGB('#DC7814')
    print(rgbColor)

Con­vert­ing RGB (255) to hexa­dec­i­mal (.py)

def RGB2HEX(rr, gg, bb):
    return f'#{rr:0>2X}{gg:0>2X}{bb:0>2X}'

if __name__ == '__main__':
    hexColor = RGB2HEX(220, 120, 20)
    # '#DC7814'

In­ter­po­lat­ing RGB col­ors (.py)

def lerp(aa, bb, factor):
    return aa + (bb - aa) * factor

def lerpRGB(colorOne, colorTwo, factor):
    rr = lerp(colorOne[0], colorTwo[0], factor)
    gg = lerp(colorOne[1], colorTwo[1], factor)
    bb = lerp(colorOne[2], colorTwo[2], factor)
    return rr, gg, bb

if __name__ == '__main__':
    red = 255, 0, 0
    blue = 0, 0, 255
    gradient = lerpRGB(red, blue, .5)
    # (127.5, 0.0, 127.5)

    red = 1, 0, 0
    blue = 0, 0, 1
    gradient = lerpRGB(red, blue, .5)
    # (0.5, 0.0, 0.5)

Calculation

In­ter­po­lat­ing sin­gle value (.py)

def lerp(aa, bb, factor):
    return aa + (bb - aa) * factor

if __name__ == '__main__':
    mid = lerp(10, 20, .5)
    # 15.0

Ex­tract­ing fac­tor from ex­tremes and in­ner value (.py)

def getFactor(aa, bb, innerValue):
    return (innerValue-aa)/(bb-aa)

if __name__ == '__main__':
    factor = getFactor(10, 20, 15)
    # 0.5

Map­ping value to a new range of ex­tremes (.py)

def lerp(aa, bb, factor):
    return aa + (bb - aa) * factor

def getFactor(aa, bb, innerValue):
    return (innerValue-aa)/(bb-aa)

def remapValue(val, pMin, pMax, nMin, nMax):
    factor = getFactor(pMin, pMax, val)
    nVal = lerp(nMin, nMax, factor)
    return nVal

if __name__ == '__main__':
    newVal = remapValue(2, 0, 10, -1, +1)
    # -0.6

UFO Data

flat kern­ing (.py)

### Modules
from fontParts.world import OpenFont

### Functions
if __name__ == '__main__':
    ff = OpenFont('someFont.ufo')
    flatKerning = ff.getFlatKerning()
    print(flatKerning)
    # {('A', 'V'): -80, ('Agrave', 'V'): -80, ('Aacute', 'V'): -80}

Geometry

Calc an­gle from two points (.py)

from math import atan2, degrees

def calcAngle(pt1, pt2, mode='degrees'):
    assert mode == 'degrees' or mode == 'radians'
    ang = atan2((pt2[1] - pt1[1]), (pt2[0] - pt1[0]))
    if mode == 'radians':
        return ang
    else:
        return degrees(ang)


if __name__ == '__main__':
    angle = calcAngle((50, 50), (10, 80), mode='degrees')
    # 143.13010235415598

    angle = calcAngle((50, 50), (10, 80), mode='radians')
    # 2.498091544796509

Calc dis­tance be­tween two points (.py)

from math import sqrt

def calcDistance(pt1, pt2):
    return sqrt((pt1[0] - pt2[0])**2 + (pt1[1] - pt2[1])**2)

if __name__ == '__main__':
    dist = calcDistance((20, 30), (100, 80))
    # 94.33981132056604
closestPoint.png

clos­est point (.py)

from math import sqrt

RADIUS = 5

def calcDistance(pt1, pt2):
    return sqrt((pt1[0] - pt2[0])**2 + (pt1[1] - pt2[1])**2)

def closestPoint(refPt, points):
    return min(points, key=lambda x: calcDistance(refPt, x))

def drawPt(point):
    oval(point[0]-RADIUS, point[1]-RADIUS, RADIUS*2, RADIUS*2)


if __name__ == '__main__':
    points = [(311, 168), (204, 320), (134, 339), (185, 23), (0, 171), (227, 139), (172, 372), (388, 231), (142, 169), (167, 102), (368, 0), (40, 97), (166, 206), (263, 27), (157, 53), (5, 168), (285, 249), (34, 228), (174, 86), (55, 271), (350, 240), (238, 71), (56, 154), (116, 297), (165, 122), (297, 296), (392, 386), (124, 50), (193, 58), (389, 117), (387, 67), (353, 260), (245, 332), (133, 155), (73, 165), (231, 192), (89, 238), (300, 356), (31, 158), (100, 375), (12, 54), (358, 136), (179, 123), (293, 207), (59, 331), (377, 309), (50, 47), (277, 243), (117, 2), (18, 189)]
    refPt = 95, 180
    closest = closestPoint(refPt, points)

    newPage(400, 400)
    fill(0)
    for eachPt in points:
        if eachPt != closest:
            drawPt(eachPt)

    fill(1, 0, 0)
    drawPt(closest)

    fill(0, 1, 0)
    drawPt(refPt)

Curves

oval.png

oval (.py)

from math import sin, cos, radians

def ovalCurve(wdt, hgt):
    points = []
    for angle in range(360):
        xx = sin(radians(angle)) * wdt/2
        yy = cos(radians(angle)) * hgt/2
        points.append((xx, yy))
    return points


if __name__ == '__main__':
    points = ovalCurve(400, 150)
    newPage(500, 500)
    translate(width()*.5, height()*.5)
    stroke(0)
    fill(None)
    strokeWidth(10)
    polygon(*points, close=True)
lissajous.png

lis­sajous (.py)

from math import pi, sin

def calcLissajous(ptsAmount, amps, angFreqs, angPhases):
    ampX, ampY = amps
    angFreqX, angFreqY = angFreqs
    angPhaseX, angPhaseY = angPhases
    points = []
    for ii in range(0, ptsAmount):
        tt = ii*pi*2/ptsAmount
        xx = ampX * sin(angFreqX * tt + angPhaseX)
        yy = ampY * sin(angFreqY * tt + angPhaseY)
        points.append((xx, yy))
    return points


if __name__ == '__main__':
    pointsAmount = 500

    ampX = ampY = 200
    angFreqX = 3
    angFreqY = 1
    angPhaseX = pi
    angPhaseY = pi/2

    lissajousPoints = calcLissajous(pointsAmount, (ampX, ampY), (angFreqX, angFreqY), (angPhaseX, angPhaseY))
    newPage(500, 500)
    translate(width()/2, height()/2)
    stroke(0)
    strokeWidth(10)
    fill(None)
    polygon(*lissajousPoints)
welch.png

welch (.py)

def welchCurve(aa, wdt):
    points = []
    for xx in range(-wdt//2, wdt//2+1):
        yy = 1 - (xx**2 / aa**2)
        points.append((xx, yy))
    return points


if __name__ == '__main__':
    points = welchCurve(5, 200)
    newPage(500, 500)
    translate(width()*.5, height()*.9)
    stroke(0)
    fill(None)
    strokeWidth(10)
    polygon(*points, close=False)
bartlett.png

bartlett (.py)

def bartlettCurve(aa, wdt):
    points = []
    for xx in range(-wdt//2, wdt//2):
        yy = 1-(abs(xx))/aa
        points.append((xx, yy))
    return points


if __name__ == '__main__':
    points = bartlettCurve(.5, 400)
    newPage(500, 500)
    translate(width()*.5, height()*.9)
    stroke(0)
    fill(None)
    strokeWidth(10)
    polygon(*points, close=False)
sine.png

sine (.py)

from math import sin, radians

def sineCurve(wdt, radius):
    points = []
    for xx in range(0, wdt):
        yy = sin(radians(xx*(360/wdt))) * radius
        points.append((xx, yy))
    return points


if __name__ == '__main__':
    points = sineCurve(450, 200)
    newPage(500, 500)
    translate(25, height()*.5)
    stroke(0)
    fill(None)
    strokeWidth(10)
    polygon(*points, close=False)
cosine.png

co­sine (.py)

from math import cos, radians

def cosineCurve(wdt, radius):
    points = []
    for xx in range(0, wdt):
        yy = cos(radians(xx*(360/wdt))) * radius
        points.append((xx, yy))
    return points


if __name__ == '__main__':
    points = cosineCurve(450, 200)
    newPage(500, 500)
    translate(25, height()*.5)
    stroke(0)
    fill(None)
    strokeWidth(10)
    polygon(*points, close=False)
sqRoot.png

square root (.py)

from math import sqrt

def sqRootCurve(wdt, yScale):
    points = []
    for xx in range(0, wdt):
        yy = sqrt(xx)
        points.append((xx, yy*yScale))
    return points


if __name__ == '__main__':
    points = sqRootCurve(wdt=450, yScale=12)
    newPage(500, 500)
    translate(25, 100)
    stroke(0)
    fill(None)
    strokeWidth(10)
    polygon(*points, close=False)
quadratic.png

qua­dratic (.py)

def quadraticCurve(wdt, aa, bb):
    points = []
    for xx in range(-wdt//2, wdt//2):
        yy = (aa*xx)**2 + bb*xx
        points.append((xx, yy))
    return points


if __name__ == '__main__':
    points = quadraticCurve(wdt=300, aa=.13, bb=0)
    newPage(500, 500)
    translate(width()/2, 50)
    stroke(0)
    fill(None)
    strokeWidth(10)
    polygon(*points, close=False)

Drawing

Draw a UFO Glyph Ob­ject (.py)

import fontParts.world as fp

def drawGlyph(glyph):
    """
    from https://gist.github.com/roberto-arista/e916e3c9c0ab445b2bfb714af80dab6b#gistcomment-2828756
    thanks Frederik!
    """
    glyphPath = BezierPath(glyphSet=glyph.layer)
    glyph.draw(glyphPath)
    drawPath(glyphPath)


if __name__ == '__main__':
    myFont = fp.OpenFont('someFont.ufo')
    drawGlyph(myFont['a'])
regularPolygon.png

Draw a reg­u­lar poly­gon (.py)

from math import cos, sin, radians

def regularPolygon(sides, radius):
    vertices = []
    sliceAngle = 360/sides
    for ii in range(sides):
        xx = cos(radians(ii*sliceAngle)) * radius
        yy = sin(radians(ii*sliceAngle)) * radius
        vertices.append((xx, yy))
    polygon(*vertices)


if __name__ == '__main__':
    newPage(500, 500)
    translate(width()/2, height()/2)
    stroke(0)
    fill(None)
    strokeWidth(10)
    regularPolygon(sides=8, radius=200)
grid.png

Draw a grid (.py)

def drawGrid(hElems, vElems, cellSize):
    """
    hElems = horizontal elements
    vElems = vertical elements
    """
    for jj in range(vElems):
        with savedState():
            for ii in range(hElems):
                rect(0, 0, cellSize, cellSize)
                translate(cellSize, 0)
        translate(0, cellSize)


if __name__ == '__main__':
    newPage(400, 400)
    stroke(0)
    strokeWidth(10)
    fill(None)
    translate(60, 60)
    drawGrid(8, 8, 30)
background.png

back­ground (.py)

WHITE = 1, 1, 1

def background(clr=WHITE):
    fill(*clr)
    rect(0, 0, width(), height())

if __name__ == '__main__':
    newPage(400, 400)
    pink = 255/255, 51/255, 102/255
    background(clr=pink)

Text

re­trieve name from uni­code data­base (.py)

import unicodedata

def getUnicodeName(char):
    """Provide a character and I will return a description
    if available in the Unicode database"""
    try:
        return unicodedata.name(char)
    except ValueError:
        return ''


if __name__ == '__main__':
    name = getUnicodeName('ü')
    # LATIN SMALL LETTER U WITH DIAERESIS