Skip to content

Instantly share code, notes, and snippets.

@neizod
Last active May 26, 2021
Embed
What would you like to do?
Megagon SVG!
#!/usr/bin/env python3
# NOTE
# LAYERS is a non-empty list of integers.
# The n-gon can be determine by calc_ngon(LAYERS), e.g.,
#LAYERS = [1, 2, 2] # heptagon (not constructible)
#LAYERS = [1, 4, 3] # heptadecagon (17 sides)
#LAYERS = [2, 2, 3, 2] # tetracontadigon (42)
#LAYERS = [4, 1, 4, 3, 2, 2] # 360-gon
#LAYERS = [4, 3, 2, 4, 3, 2] # chiliagon (1,000)
#LAYERS = [1, 9, 10, 10, 10] # myriagon (10,000)
#LAYERS = [1, 16, 15, 16, 16] # 65537-gon
LAYERS = [100, 99, 100] # megagon (1,000,000)
# NOTE
# Otherwise, just use NGON to describe the polygon,
# and let the program determine a pattern for LAYERS automatically.
#NGON = 17
PADDING = 50
RADIUS = 700
PRECISION = 9
##############################################################################
import sys
from types import SimpleNamespace as namespace
from math import sin, cos, tan, radians
from operator import mul
from functools import reduce
from itertools import product
from collections import Counter
def translate(center, x, y):
return x+center, y+center
def _rotate(angle, x, y):
return x*cos(angle) - y*sin(angle), x*sin(angle) + y*cos(angle)
def rotate(x, y, angle, center):
return translate(center, *_rotate(angle, *translate(-center, x, y)))
def iter_primes(memo=namespace(pi=0, ps=[2, 3])):
k = 0
while True:
while len(memo.ps) <= k:
head = memo.ps[memo.pi]**2 + 1
memo.pi += 1
tail = memo.ps[memo.pi]**2
seive = list(range(head, tail))
for p in memo.ps[:memo.pi]:
size = 1 + (tail - head + (head % -p)) // p
seive[-head%p::p] = [0] * size
memo.ps += (p for p in seive if p)
yield memo.ps[k]
k += 1
def factors(n):
if n == 1:
return [1]
fs = []
for p in iter_primes():
if p**2 > n:
break
while n % p == 0:
fs += [p]
n //= p
if n > 1:
fs += [n]
return fs
def divisors(n):
gfs = ({p**i for i in range(k+1)} for p, k in Counter(factors(n)).items())
return sorted(reduce(mul, ts) for ts in product(*gfs))
def calc_ngon(layers):
r = 1
for v in reversed(layers):
r *= v
r += 1
return r - 1
def calc_layers(ngon, memo=[()]):
while len(memo) < ngon+1:
row = [(d, *memo[len(memo)//d-1]) for d in divisors(len(memo))]
memo += [min(row, key=lambda r: (sum(r), len(r)))]
return memo[ngon]
class Polygon(object):
def __init__(self, data):
if isinstance(data, int):
self.ngon = data
self.rel_pieces = calc_layers(data)
elif isinstance(data, list) or isinstance(data, iter):
self.rel_pieces = data
self.ngon = calc_ngon(data)
else:
raise KeyError('initial data for polygon must be int or list of int.')
if self.ngon < 3:
raise KeyError('its absurd to draw a polygon with number of sides less than 3.')
self.angle = 360/self.ngon
self.abs_pieces = [self.rel_pieces[0]]
self.tot_pieces = [self.rel_pieces[0]]
self._init_pieces_info()
def _init_pieces_info(self):
for i in range(1, len(self.rel_pieces)):
self.abs_pieces += [self.abs_pieces[i-1] * self.rel_pieces[i]]
self.tot_pieces += [self.tot_pieces[i-1] + self.abs_pieces[i]]
def base_group(self, radius, padding):
center = radius + padding
x = center - (radius * tan(radians(self.angle/2)))
y = padding
spec = []
for _ in range(self.rel_pieces[0]+1):
spec += f'{round(x, PRECISION)},{round(y, PRECISION)}',
x, y = rotate(x, y, radians(self.angle), center)
return f'<g id="a0"><polyline points="{" ".join(spec)}" stroke="#000" fill="none" /></g>\n'
def make_group(self, k, radius, padding, precision):
if k == 0:
return self.base_group(radius, padding)
center = radius + padding
theta = self.angle * self.abs_pieces[k-1]
s = f'<g id="a{k}">\n'
for i in range(1, 1+self.rel_pieces[k]):
rot = round(theta*i, precision)
s += f'<use xlink:href="#a{k-1}" transform="rotate({rot} {center} {center})" />\n'
s += '</g>\n'
return s
def svg(self, radius, padding, precision):
size = 2 * (radius + padding)
header = 'xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"'
s = f'<!-- {self.ngon}-gon with layers {self.rel_pieces}. -->\n'
s += f'<svg {header} height="{size}" width="{size}">\n'
for i, _ in enumerate(self.rel_pieces):
s += self.make_group(i, radius, padding, precision)
s += '</svg>'
return s
if __name__ == '__main__':
if len(sys.argv) == 2:
print(Polygon(int(sys.argv[1])).svg(RADIUS, PADDING, PRECISION))
elif len(sys.argv) > 2:
print(Polygon(list(map(int, sys.argv[1:]))).svg(RADIUS, PADDING, PRECISION))
elif 'LAYERS' in globals():
print(Polygon(LAYERS).svg(RADIUS, PADDING, PRECISION))
elif 'NGON' in globals():
print(Polygon(NGON).svg(RADIUS, PADDING, PRECISION))
else:
raise NotImplementedError
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment