Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@neizod
Last active May 26, 2021 05:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save neizod/506e32d7841b27399e67e8826445b4df to your computer and use it in GitHub Desktop.
Save neizod/506e32d7841b27399e67e8826445b4df to your computer and use it in GitHub Desktop.
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
Display the source blob
Display the rendered blob
Raw
<!-- 17-gon with layers (1, 4, 3). -->
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="1500" width="1500">
<g id="a0"><polyline points="619.147322024,50 880.852677976,50.0" stroke="#000" fill="none" /></g>
<g id="a1">
<use xlink:href="#a0" transform="rotate(21.176470588 750 750)" />
<use xlink:href="#a0" transform="rotate(42.352941176 750 750)" />
<use xlink:href="#a0" transform="rotate(63.529411765 750 750)" />
<use xlink:href="#a0" transform="rotate(84.705882353 750 750)" />
</g>
<g id="a2">
<use xlink:href="#a1" transform="rotate(84.705882353 750 750)" />
<use xlink:href="#a1" transform="rotate(169.411764706 750 750)" />
<use xlink:href="#a1" transform="rotate(254.117647059 750 750)" />
</g>
</svg>
Display the source blob
Display the rendered blob
Raw
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