A New Sigil Generator

An in-depth look at a sigil generator using the Witches Circle pattern. Developed in Python and served as a Flask application through uWSGI. This is an extension and redevlopment of an earlier app that I originally hosted as a programming toy, but I’ve gone through and updated the generator code to create some far more æsthetic results, as well as using a new library for generating the result as an SVG


How does a computer factor into magick? Traditionally speaking, it wouldn’t even be a question. However, traditionally speaking also means that you should only ever be using beeswax candles, which are at a premium compared to paraffin wax. Of course, they may have something to do with the relative novelty of paraffin, being a product of the petroleum industry and far more recent a development1 when compared to the bee2. When you consider the fact that paraffin wasn’t even a candle option until the Victorian magickal renaissance, it makes for little wonder why texts would prescribe for a beeswax candle— They weren’t being snobs about materials, it would’ve just been vastly preferred over a tallow candle and been far more ubiquitous. Realistically, it was the period equivalent of popping out to a Yankee Candle and buying the first tea light that fits your needs.

Consider in that context, then, the application of other modern conveniences. For instance, why collect a library of reference books when you can easily pop online and look up any piece of information you could desire? Why sit and flip through notes when you can keep a personal wiki3 that lets you leverage search tools and regular expressions to find any thought you have ever recorded? In short: why not let a computer do some of the grunt work for you?

Using Computers to Generate Sigils

By my own loose interpretation of how and why magick works4: as long as the act of writing, debugging, testing, and deploying the code represents a significant investment of time, energy, focus, and Will, then it can also be considered to represent a deliberate ritual setting and scenario. Considering my personal belief that a ritual is only useful in the way that it “sets the mood” for a magickal event, which encourages the appropriate direction of Willful Energy, this seems to track. Taken, as well, within the occult framework of Chaos Magick5, why not give it a try?

The Plan

But, how can we get a meaningful result from a program? In Chaos Magick’s toolbox, frequently the first sledgehammer found is that old chestnut: Sigil Magick. The methods may vary from practitioner to practitioner, but the general gist is:

  1. Create a statement of intent: I WANT TO DEMONSTRATE MAGICK
  2. Rephrase the statement as an immediate affirmation: IT IS MY WILL THAT I DEMONSTRATE MAGICK
  3. Remove all vowels6: T S MY WLL THT DMNSTRT MGCK
  4. Remove all duplicate letters: TSMYWLHDNRGCK
  5. Mash those letters up into a sigil, let your inner vision of what “magick is supposed to look like” guide you. This is the artistic portion, and (for me) is the part where you really breathe that life and energy and intent into the creation. This is what plants the intent firmly into your subconscious and smuggles it past the “psychic censor”
  6. “Fire the sigil” in some manner. Maybe stare at it and masturbate, or light it on fire, or scream it into the void: Whichever way you want. This is not an instruction in magick, just an example.

Being a list of steps and instructions, and the overall iterative nature in the act of sigilizing the intent, it seems a prime candidate for automation. But: artistic vision isn’t really great for computers yet7, so an appropriate rules-based schema should be used. I considered a few different approaches, such as jittering and rotating letters, but none of them felt right until I remembered the Magick Square/“Kamea” methods of generating the sigils and seals of various planetary intelligences, and it was while researching the rules for generating a Magick Square that I stumbled upon the “Witches Circle” pattern.

The “Witches Circle” or “Witches Wheel”

A much simpler method of enciphering text, the wheel is constructed as follows:

  1. The 26 letters of the Latin alphabet are written in alphabetically in 3 concentric rings, moving clockwise with each letter
  2. The outer ring has the letters a through m
  3. The middle ring has the letters n through u
  4. The innermost ring has the letters v through z

To create a sigil representing some word, start at the first letter of the word and leave a mark of some sort to indicate beginning the word. Find the next letter of the word, and draw a line connecting your mark to that letter. Repeat this process until you get to the last letter, and then leave some mark indicating the end. Any which way you decide to interpret these rules is up to you, but I personally prefer the æsthetics that come from curving the lines to follow the circular pattern combined with sharp straight lines in or out to transition between the rings.

For instance, the word “Zone” would be constructed by: - Drawing a mark at the letter “Z” (innermost ring) - Locating the letter “O” on the wheel, draw a segment of a circle following the inner ring of letters until aligned with the letter “O” (middle ring) - Draw a straight line outwards to meet the letter - Locate letter “N” also in the middle ring, and draw a segment of circle following the middle ring until reaching the letter “N” - Locating the letter “E” on the wheel, draw a segment of a circle following the middle ring of letters until aligned with the letter “E” (outer ring) - Draw a straight line outwards to meet the letter - End the segment with a marker

This would produce a sigil that looks something like this:

Zone Sigil

Sigil Generator

To accomplish this generation, I’m using the drawSvg library. This lets me take a few shortcuts in defining arcs and curves, while still allowing me to manually hack together a few things like the jumps to new rings and connecting arcs within the same letter ring.

import grimoire as g
import drawSvg as draw
import string, argparse, copy

There is also a custom module of mine, Grimoire, which contains some utility functions, such as: Removing duplicate letters, removing vowels, and cleaning the input so it only contains lowercase ascii characters.

The raw code for the new sigil generator consists of 3 main parts

Mapping the Wheel

Firstly, we need to generate the weel letters and assign them both an angle and a radius to represent where they live around the wheel as well as which ring they live in. The radii are defined arbitrarily with the middle ring living at \(r = 1\) and the two other rings living at \(\pm \, 0.5\) relative to there. The angles, expressed in euler degrees, are then calculated by dividing the circle into however many slices are needed for that ring, and arbitrarily rotated to align the letter “A” with the vertical axis.

These coordiantes are added as a tuple value in a dictionary with the key being the lowercase letter, and the resulting dictionary of coordinates is returned.

## -- Generate Polar Coordinates for each letter -- ##
def DoWheel():
    coords = {}
    outer = string.ascii_lowercase[0:13]
    mid = string.ascii_lowercase[13:21]
    inner = string.ascii_lowercase[21:26]

    # Generate Outer Ring
    for i in range(len(outer)):
        # Divide the circle into angles for each letter
        angle = i * (360/len(outer))
        # Move CW for each letter placement
        angle = 360 - angle
        # Rotate to align A with +Y axis
        angle += 90
        if angle > 360:
            angle -= 360
        coords[outer[i]] = (angle, 1.5)

    # Generate Middle Ring - Same as above
    for i in range(len(mid)):
        angle = i * (360/len(mid))
        angle = 360 - angle
        angle += 90
        if angle > 360:
            angle -= 360
        coords[mid[i]] = (angle, 1)

    # Generate Inner Ring - Same as above
    for i in range(len(inner)):
        angle = i * (360/len(inner))
        angle = 360 - angle
        angle += 90
        if angle > 360:
            angle -= 360
        coords[inner[i]] = (angle, 0.5)

    # Return coordinate dictionary
    return coords

Generating the Sigil

The easy stuff

The bulk of the generation happens in this function. In order to cut down on screenspace and annoyance in retyping the same variable names over and over, I’m using the argparse module to pass the query parameters in.

## -- Generate the Sigil SVG -- ##
def Generate(args):
    stroke_color = fill_color = '#8A8785'
    line_width = args.line_width
    radius = args.radius

    # Clean the text
    q = args.text
    instring = g.RemoveNonAscii(q)

    if args.remove_vowels:
        instring = g.RemoveVowels(instring)
    if args.remove_duplicates:
        instring = g.RemoveDuplicates(instring)

This first part is pretty self-explanatory: setting some basic parameters from the arguments, like the line width and radius, as well as parsing through the text. As explained above, the first step is to clean and normalize the input string, which is handled by the RemoveNonAscii method in the Grimoire library. This is then optionally passed through two other methods to remove the vowels and the duplicate letters from the input.

I’ve chosen to make these optional here because the sigil wheel has the vowels available to choose from, and duplicate letters can sometimes generate more interesting layered curves. But, of course, it all only matters how I feel like it looks so— I’m really at my own discretion here. It does also mean, however, that certain long phrases can be shortened down— reducing the visual clutter of the resulting sigil.

Mapping and creating some markers

    # Create the SVG object
    d = draw.Drawing(args.size, args.size, origin='center')

    # Map the wheel
    mapping = DoWheel()

    ## --- Marker Objects -- ##
    # Arrow
    m_arrow = draw.Marker(-0.3, -0.5, 1, 0.5, scale=4, orient='auto')
    m_arrow.append(draw.Lines(-0.3, -0.5, 0, 0, -0.3, 0.5, 0.9, 0, fill=fill_color, close=True))
    # --- Bar
    m_bar = draw.Marker(0, -0.5, 1, 0.5, scale=4, orient='auto')
    m_bar.append(draw.Lines(-0.25, -0.5, -0.25, 0.5, 0.25, 0.5, 0.25, -0.5, fill=fill_color, close=True))
    # --- Circle
    m_circle = draw.Marker(-1, -1, 1, 1, scale=2, orient='auto')
    m_circle.append(draw.Circle(0, 0, .75, fill=fill_color, stroke_width=0.25, stroke=stroke_color))

This next section of code is just a bit of housekeeping and setting up for the generation. We create a drawSvg object to construct the sigil within, and we generate the mapping coordinate dictionary as described above. We also set up some markers that might be useful later: An arrow, a bar, and a circle (just to keep it interesting)

Generating the curve segments

    ## --- Generate and space the arcs --- ##
    arcs = []
    # Generate
    for i in range(len(instring) - 1):
        start_a, start_r = mapping[instring[i]]
        end_a, end_r = mapping[instring[i + 1]]
        arc = {'id':i, 'start_a':start_a, 'end_a':end_a, 'radius':start_r}
        arcs.append(arc)

    # Add last letter
    start_a, start_r = mapping[instring[-2]]
    end_a, end_r = mapping[instring[-1]]
    arc = {'id':len(arcs), 'start_a':end_a, 'end_a':start_a, 'radius':end_r}
    arcs.append(arc)

    # Space
    last_r = 0
    spaced_arcs = []
    for arc in sorted(arcs, key=lambda a: a['radius'])[::-1]:
        if arc['radius'] != last_r:
            last_r = arc['radius']
            x = t = len([a for a in arcs if a['radius'] == last_r])
            max_r = last_r + 0.25
            min_r = last_r - 0.25
        else:
            t -= 1

        # Calculate an even spread of radii inside the concentric ring of letters
        nR = max_r - t * (max_r - min_r) / (x + 1)
        newArc = copy.deepcopy(arc)
        newArc['radius'] = nR
        spaced_arcs.append(newArc)
    
    # Re-order the spaced arcs by ID. Could be done by deep copying the origin list for spacing
    spaced_arcs = sorted(spaced_arcs, key=lambda a: a['id'])

The real meat and potatoes of the generation algorithm: creating a list of arcs, complete with their start and end angles and relative radii. It’s a pretty simple matter, just a workhorse loop. We iterate through each letter of the string, looking at what letter we begin at and what letter we end at8, get the radius and angle of both points, and create a new object with an ID representing its position in the string9, the start angle, end angle, and the radius. This arc will always have a radius defined as matching the origin letter. However, this will also mean that we need to run the mapping and generation one extra time, because of the fact that we’re only incremementing until len - 1

The next thing that this code block does is space apart arcs on the same ring. This is important because I wanted to see the distinct separation in the curve sements as they traverse wheel. It also just looks cooler, and the rule of cool must always apply. For this, sort the arcs by their radius, and then iterate from largest to smallest with the clever python slicing [::-1] and generate an even distribution of numbers within the range defined as \(min, max = radius \pm \, 0.25\) using the clever calculation: \(newRadius = max - t \times (max - min) / (x + 1)\) for \(t\) being the ordinal location in the ring, and \(x\) being the number of values to generate.

These new arc segments are then added to a list. This could have been made simpler with some clever use of the copy library, but since I still need the original radii later10, I opted to hold that list harmless in all permutations. Finally, we reorder the new list using their IDs to get the segments back in their initial order.

Banging together the final SVG

    ## --- SVG Path Construction --- ##
    # Create a parent path object : Will receive the concatenated 'd' parameter
    s = draw.Path()

    # Seed last-radius for CW/CCW and Arc generation
    last_r = arcs[0]['radius']
    
    # CW or CCW?
    _cw = abs(spaced_arcs[0]['start_a'] - spaced_arcs[0]['end_a']) <= 180

    # Iterate through all paths and generate 'd' parameter
    for i, arc in enumerate(spaced_arcs):
        # Path object to generate 'd' parameter. Quick and dirty so I don't have to calculate curves
        p = draw.Path()
        start_a = arc['start_a']
        _radius = arc['radius']
        end_a = arc['end_a']

        # Determine 'Same Line' by looking at original arcs list for un-spaced radii
        same_line = True if arcs[i]['radius'] == last_r else False
        
        # Switch CW and CCW if sequential same-ring letters
        if same_line:
            _cw = not _cw

        # Sweep flag for 'A' command
        sweep = 0 if _cw else 1

        # Generate the arc
        p.arc(0, 0, radius * _radius, start_a, end_a, cw = _cw)

        if i == 0:
            # If we're at the start, append the first arc with no funny connections
            s.append(p.args['d'])
        elif i == len(spaced_arcs) - 1:
            svg_d = p.args['d'][1:]
            if not same_line:
                coord = svg_d.split(' ')[0]
                s.append(f' L{coord}')
        else:
            # Otherwise generate either a jump line or a sweep to connect to the next arc
            # Trim the 'M' move-to command to get the raw coords
            svg_d = p.args['d'][1:]
            if same_line:
                # Generate a sweep if the next arc is in the same leter ring
                # Calculate the sweep ellipse radius
                sweep_r = abs(spaced_arcs[i - 1]['radius'] - _radius)
                # Grab the coordinates from the extracted parameter
                coord = svg_d.split(' ')[0]
                # append the 'A' curve-to command
                s.append(f' A{sweep_r},{sweep_r} 0 0,{sweep} {coord}')
                # Attatch the sweep
                s.append(p.args['d'])
            else:
                # Generate a jump line to a distant ring and update the last radius for same-line checking
                last_r = arcs[i]['radius']
                s.append(f' L{svg_d}')

If the generation was the engine in this car, then this would be the transmission. Meaning, at least to a not-so-mechanically-inclined software developer like myself, if the first part breaks: I can fix it. If this part broke, I’d rather just throw it out and get a brand new algorithm. There is a lot of funkiness here that involved digging into the SVG standard, so I’m going to make my best effort attempt at explaining what I’m doing here.

Firstly, I’m creating a drawSvg Path object, because having one makes it easy to generate curve segments, and I can pull path data from it later. This is really just a quick hack to avoid doing trigonometry to generate the curve properties11. I’m also doing some artistically flavored parsing: There is no rhyme or reason to why I’m assigning whatever I am to the _cw variable, other than what I wound up with looks a lot cooler. However, flipping this variable really just makes the spiral begin in the opposite direction.

For each generated arc, we then step through and create a path that is defined to move through its designated start/end points as calculated above. If the path is in the same ring segment, as determined by the same_line variable, the _cw variable reverses, which means that a line between multiple letters on the same ring will double back on itself to reach its destination. This is both æsthetically pleasing, and allows for a little ease in decoding the output12.

  • The code then decides whether it should create an arc along an elipse to join the current segment (if it’s on the same line) or if it should generate a line jump to another ring, and then either inserts the A (curve-to) or L (line-to) SVG instruction, which is then appended to the path data for the current path. This is done by stripping the M (move-to) tag from the next arc in the list and calculating based on the absolute coordinates that follow.

Rendering the path

    ## --- Render a path for color fill under hard lines --- ##
    if args.do_fill:
        s_fill = copy.deepcopy(s)
        s_fill.args['fill'] = '#721e1e'
        s_fill.args['fill-opacity'] = '53%'
        d.append(s_fill)

    ## --- Draw Boundary Rings --- ##
    if args.draw_bounds:
        d.append(draw.Circle(0, 0, radius * 1.75, fill='none', stroke='#111111', stroke_width=3.5))
        d.append(draw.Circle(0, 0, radius * 1.25, fill='none', stroke='#111111', stroke_width=3.5))
        d.append(draw.Circle(0, 0, radius * 0.75, fill='#111111', stroke='#111111', stroke_width=3.5))

    ## --- Draw the Sigil --- ##
    s.args['fill'] = 'none'
    s.args['marker-start'] = m_bar
    s.args['marker-end'] = m_arrow
    s.args['stroke'] = stroke_color
    s.args['stroke-width'] = line_width
    d.append(s)

    # Return the drawing object for output
    return d

Finally, the path is added to the Drawing object we created in the first section. If the optional parameters to generate a “fill” or a “boundary rings” visual layer were set, then these elements are constructed. The fill is rather simply just telling the renderer to do a fill on the arbitrary path created by our lines. I did it by accident, honestly, and just sort of liked the visual interest it gives the sigil. This is added first so the “boundary rings” can be drawn over it.

The “boundary rings” are simple concentric circles marking the innermost and outermost bounds of each letter ring. As described above, these can be seen as each letter ring’s relative radial offset \(r\) being expanded to \(r \pm \, 0.25\) units. The center is filled with the same color, creating a dark circle over either the background color or the fill color, if the “fill” parameter was toggled.

Finally, the sigil is drawn on top of the other layers, and the object is returned to its caller for processing. This is also where the start and end markers are added to the path. I decided to go with the “bar” for a traditional feeling, while the flair of the “arrow” invokes the spirit of the energy moving through the spiral.

Returning the Image

The resulting SVG is then returned as a data uri with the following utility method. This data uri is then parsed in the flask application using the datauri module and returned, allowing for a dynamic endpoint that can generate an image on the fly and directly insert it into a page. I (will) use this to generate the sigilized versions of the article titles on each page.

## --- Render for Web --- ##
def RenderWebSigil(args):
    sigil = Generate(args)
    sigil.setRenderSize(0, 0)
    return sigil.asDataUri()

The render size is set to 0 so I can specify the height and width in code, either HTML or here in my markdown editor. From here, it is up to the site and browser to render the resulting image, and the sigil is considered to be out of my hands.

Lessons Learned

Overall, this was a fun and informative project to work on. I had originally had the idea to generate sigils a few years ago when working on some small applications for use in my cyberoccult experiments, but they were very pixelated and locked in to a single size or format. This was mostly a limitation of using Pillow and aggdraw modules in python, but I had always had the plan to turn the generator into something that could be used for SVG.

This desire mostly came from a place of really really not liking how I had to generate rasterized curves in those libraries. They always looked too jagged. I also had some issues getting the spiral to line up correctly or offset itself on the same radius, which made for some interesting but unsatisfying images. I’ll see if I can dig one up for this article some time.

Overall, I’m really pleased with these results. The path feels simultaneously labyrinthine and sci-fi. Like a polyamorous love child between a crop circle, a generic cyberpunk greeble, and a stone circle. There’s something soothing about following the path as it winds through the letters, making sure that it stops at all the places it needs to, and retracing the original phrase in my head. Almost meditative in my ability to contemplate the resulting image, which I feel has a great benefit to its occult significance and utility.

I’m starting to think of ways I can use this new python module to generate other types of sigils, too. A rework of my kamea sigil generator may be upcoming, including the ability to choose the planetary square being used for the generation, which could conceptually lead to the creation of any number of new “angelic seals” for uses in talismanic magick or just as symbols of power. But, of course, that remains to be seen.


  1. First created by Carl Reichenbach in 1830.↩︎

  2. Estimated to have first evolved somewhere around 130 million years ago.↩︎

  3. Or a personal website.↩︎

  4. Or otherwise, at least appears to work.↩︎

  5. Wherein all rules can be considered suggestions, as long as breaking them doesn’t betray personal belief for the sake of conveninence.↩︎

  6. I’m not really sure the reason why. But I also don’t count ‘Y’ as a vowel in my personal sigilization practice. For me, it’s just too convenient of a shape to remove.↩︎

  7. This might actually be an interesting experiment to try out, and would coincide with my current personal projects in Machine Learning and GAN art. At the very least, it could be fun to run the statements of intent through a CLIP guided neural net to see what it creates. These could then, potentially, be used as a visual sigil while also smuggling the magickal intent into a digital subconsciousness while the NN iterates on the same thought like a digital mantra↩︎

  8. Hence the rogue - 1 in the range(len(instring) - 1)… Gotta love those magic constants (magick constants?)↩︎

  9. Used later to reorder the list↩︎

  10. For calculating ring jump maneuvers, which sounds sci-fi as hell↩︎

  11. Actually, it was just written before I dug into the SVG standards and definitions, so it might be easier than I think to hand-craft a curve with a defined radius.↩︎

  12. Be Sure to Drink your Ovaltine (no duplicates)
    ↩︎