Created
March 2, 2020 19:57
-
-
Save 99991/4fcf3093321ebe29d73bd34cbdb707a2 to your computer and use it in GitHub Desktop.
A python code snippet to generate a hexagonal svg grid
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import math | |
class Line: | |
def __init__(self, x1, y1, x2, y2): | |
self.x1 = x1 | |
self.y1 = y1 | |
self.x2 = x2 | |
self.y2 = y2 | |
def to_svg(self, style): | |
return f'<line x1="{self.x1}" y1="{self.y1}" x2="{self.x2}" y2="{self.y2}" style="{style}" vector-effect="non-scaling-stroke" />' | |
class Circle: | |
def __init__(self, x, y, radius): | |
self.x = x | |
self.y = y | |
self.radius = radius | |
def to_svg(self, style): | |
return f'<circle cx="{self.x}" cy="{self.y}" r="{self.radius}" style="{style}" vector-effect="non-scaling-stroke" />' | |
class NGon: | |
def __init__(self, x, y, radius, n): | |
self.x = x | |
self.y = y | |
self.radius = radius | |
self.n = n | |
def to_svg(self, style): | |
n = self.n | |
starting_angle = 0.5 * 2.0 * math.pi / n | |
points = [] | |
for i in range(n): | |
angle = 2.0 * math.pi * i / n + starting_angle | |
x = self.x + self.radius * math.cos(angle) | |
y = self.y + self.radius * math.sin(angle) | |
points.append(f"{x},{y}") | |
points = " ".join(points) | |
return f'<polygon points="{points}" style="{style}" vector-effect="non-scaling-stroke" />' | |
class Ring: | |
def __init__(self, x, y, inner_radius, outer_radius): | |
self.x = x | |
self.y = y | |
self.inner_radius = inner_radius | |
self.outer_radius = outer_radius | |
def to_svg(self, style): | |
r0 = self.inner_radius | |
r1 = self.outer_radius | |
path = f''' | |
M {self.x} {self.y - r1} | |
A {r1} {r1} 0 1 0 {self.x} {self.y + r1} | |
A {r1} {r1} 0 1 0 {self.x} {self.y - r1} | |
Z | |
M {self.x} {self.y - r0} | |
A {r0} {r0} 0 1 1 {self.x} {self.y + r0} | |
A {r0} {r0} 0 1 1 {self.x} {self.y - r0} | |
Z | |
''' | |
path = " ".join(path.split()) | |
return f'<path d="{path}" style="{style}" vector-effect="non-scaling-stroke" />' | |
def make_svg_header(width, height): | |
return f'''<svg version="1.1" | |
baseProfile="full" | |
width="{width}" height="{height}" | |
xmlns="http://www.w3.org/2000/svg">''' | |
def draw_walls( | |
filename, | |
walls, | |
nx, | |
ny, | |
padding, | |
scale, | |
stroke_width, | |
): | |
# squish vertically to make triangles equilateral | |
squish = math.sqrt(1 - 0.5**2) | |
width = (nx + 2 * padding) * scale | |
height = (ny + 2 * padding) * scale * squish | |
svg = [] | |
svg.append(make_svg_header(width, height)) | |
svg.append(f'<g transform="scale({scale} {scale})">') | |
svg.append('<g transform="translate(0.5, 0.5)">') | |
def draw_line(ax, ay, bx, by): | |
style = f'stroke: rgb(0,0,0); stroke-width: {stroke_width}' | |
svg.append(Line(ax, ay, bx, by).to_svg(style)) | |
# draw horizontal lines | |
for y in range(ny + 1): | |
shift = 0.0 if y % 2 == 0 else 0.5 | |
draw_line(shift, y * squish, shift + nx - 1, y * squish) | |
# draw diagonal lines which start at 0 | |
for x2 in range(nx): | |
x3 = min(nx - 0.5, x2 + 0.5 * ny) | |
draw_line(x2, 0, x3, (x3 - x2) * 2 * squish) | |
x4 = max(0, x2 - 0.5 * ny) | |
draw_line(x2, 0, x4, (x2 - x4) * 2 * squish) | |
# draw diagonal lines that don't start at 0 | |
for y2 in range(1, ny): | |
shift = 0.0 if y2 % 2 == 0 else 0.5 | |
y3 = min(ny, y2 + ny) | |
draw_line(shift, y2 * squish, shift + 0.5 * (y3 - y2), y3 * squish) | |
x = shift + nx - 1 | |
draw_line(x, y2 * squish, x - 0.5 * (y3 - y2), y3 * squish) | |
# draw circles at intersection points | |
for y in range(ny + 1): | |
shift = 0.0 if y % 2 == 0 else 0.5 | |
for x in range(nx): | |
style = f'stroke: rgb(0,0,0); stroke-width: {stroke_width}' | |
svg.append(Circle(x + shift, y * squish, 0.05).to_svg(style)) | |
# draw walls | |
for x, y, has_green_circle in walls: | |
shift = 0.0 if y % 2 == 0 else 0.5 | |
style = f'fill:rgb(52, 174, 235); stroke:black;stroke-width: {stroke_width}' | |
svg.append(NGon(x + shift, y * squish, 0.5, 6).to_svg(style)) | |
# draw green ring | |
if has_green_ring: | |
style = f'fill:rgb(3, 252, 40); stroke:black;stroke-width: {stroke_width}' | |
svg.append(Ring(x + shift, y * squish, 0.3, 0.5 * squish).to_svg(style)) | |
svg.append('</g>') | |
svg.append('</g>') | |
svg.append('</svg>\n') | |
svg = "\n".join(svg) | |
with open(filename, "w") as f: | |
f.write(svg) | |
def main(): | |
# format: (x, y, has_green_ring) | |
walls = [ | |
(1, 1, True), | |
(2, 1, False), | |
(3, 1, True), | |
(4, 1, False), | |
(5, 1, True), | |
(6, 1, False), | |
(7, 2, True), | |
(7, 3, False), | |
(7, 4, True), | |
(6, 5, False), | |
(6, 6, True), | |
(5, 6, False), | |
(4, 6, True), | |
(3, 6, False), | |
(2, 6, True), | |
] | |
draw_walls( | |
walls=walls, | |
nx=10, | |
ny=10, | |
scale=60, | |
padding=0.5, | |
stroke_width=1.5, | |
filename="test.svg") | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment