Files
mimaki/generators/apollon/index.cgi
2026-02-03 21:28:22 +01:00

272 lines
7.4 KiB
Python
Executable File

#!/usr/bin/env python3
# Copyright (c) 2014 Ludger Sandig
# This file is part of apollon.
# Apollon is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# Apollon is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with Apollon. If not, see <http://www.gnu.org/licenses/>.
import cgi
import cgitb
from apollon import ApollonianGasket
from coloring import ColorScheme, ColorMap
from ag import ag_to_svg, impossible_combination
# Container for sanitized settings
class Settings(object):
def __init__(self, form):
self.color = form.getvalue("color", "Blues")
self.resolution = int(form.getvalue("res", 8))
self.c1 = float(form.getvalue("c1", 1))
self.c2 = float(form.getvalue("c2", 2))
self.c3 = float(form.getvalue("c3", 2))
self.rad_or_curv = form.getvalue("roc", "curvature")
self.depth = int(form.getvalue("depth", 3))
# Sanity check
if (self.resolution > 8) or (self.resolution < 3):
self.resolution = 8
if self.c1 <= 0: self.c1 = 1
if self.c2 <= 0: self.c2 = 1
if self.c3 <= 0: self.c3 = 1
if not (self.rad_or_curv == "curvature" or self.rad_or_curv == "radius"):
self.rad_or_curvature = "curvature"
if (self.depth < 0) or (self.depth > 5):
self.depth = 3
# For a more usable form
if self.rad_or_curv == "curvature":
self.rad_checked = ""
self.curv_checked = "checked"
else:
self.rad_checked = "checked"
self.curv_checked = ""
# Curvature or Radius?
if self.rad_or_curv == "radius":
self.c1 = 1/self.c1
self.c2 = 1/self.c2
self.c3 = 1/self.c3
# AG possible in the first place?
self.impossible = impossible_combination(self.c1, self.c2, self.c3)
# What to print: With form or only svg
action = form.getvalue("submit","Update")
self.onlysvg = False
if action == "Save":
self.onlysvg = True
def print_with_form(svg, settings, schemes):
# Print first chunk of html
print("Content-type: text/html")
print("""
<html>
<head>
<title>Apollonian Gasket Generator</title>
<style>
body {
font-family: sans-serif;
width: 800px;
margin: auto;
}
h1 {
padding: 10px;
border: 2px solid #ccc;
border-radius: 25px;
text-align: center;
width: 780px;
}
#gasket {
width: 500px;
float: left;
padding: 10px;
border: 2px solid #ccc;
border-radius: 25px;
}
#settings {
width: 250px;
float: right;
border: 2px solid #ccc;
border-radius: 25px;
padding: 10px;
}
#about {
width: 780px;
border: 2px solid #ccc;
border-radius: 25px;
padding: 10px;
clear: both;
}
div.wrapper {
padding-top: 2px;
clear: both;
}
input[type=number] {
width: 75px;
}
input, label, select {
display: table-cell;
}
form {
display: table;
width: inherit;
}
#settings p { display: table-row; }
</style>
</head>
<body>
<h1>Apollonian Gasket Generator</h1>
""")
print('<div id="gasket">\n%s\n</div>' % svg)
# Print form
print("""
<div id="settings">
<form method="post" action="index.cgi" id="params">
<p><label for="c1">First circle</label><input type="number" name="c1" id="c1" step="any" min="0" value="%.2f"/></p>
<p><label for="c2">Second circle</label> <input type="number" name="c2" id="c2" step="any" min="0" value="%.2f"/></p>
<p><label for="c3">Third circle</label><input type="number" name="c3" id="c3" step="any" min="0" value="%.2f"/></p>
<p><label for="curv">Curvature</label><input type="radio" name="roc" id="curv" value="curvature" %s/></p>
<p><label for="rad">Radius</label><input type="radio" name="roc" id="rad" value="radius" %s/></p>
<p><label for="depth">Recursion depth</label> <input type="number" name="depth" value="%d" min="0" max="5"/></p>
<p><label for="num">No. of colors</label><input type="number" name="res" value="%d" min="3" max="8"/> </p>
<p><label>Color scheme</label><select name="color">
""" % (settings.c1, settings.c2, settings.c3, settings.curv_checked, settings.rad_checked, settings.depth, settings.resolution))
# Sort color scheme names
info = schemes.info()
info.sort(key=lambda d: d["name"])
info.insert(0, {"name" : "none"})
for s in info:
if s["name"] == settings.color:
selected = "selected"
else:
selected = ""
print('<option value="%s" %s>%s</value>' % (s["name"], selected, s["name"]))
# Print last chunk of form and help text
print("""
</select></p>
<p><input type="submit" name="submit" value="Update"> <input type="submit" name="submit" value="Save"></p>
</form>
</div>
<div class="wrapper">
<div id="about">
<h2>About</h2>
<p> Apollonian Gaskets are groups of circles in which three are
mutally tangent to each other. You can think of it as tightly filling
a big circle with lots of smaller cicles. These sets of circles can be
computed recusively with relative ease. If you are interested in the
mathematical part, see <a
href="https://en.wikipedia.org/wiki/Apollonian_gasket">Wikipedia on
this subject</a>. </p>
<p>This site showcases a small command line program I wrote to
generate svg images of those circles. It can be found on <a
href="https://github.com/lsandig/apollon">github</a>.</p>
<p>For a more in-depth explanation of this implementation see <a
href="http://lsandig.org/blog/2014/08/apollon-python/en/">this
post</a> on my blog (English and German version)</p>
<p>Please note that the online version has a recursion limit of depth
5 to reduce the time and memory consumption on the server. With the
command line version only your RAM is the limit.</p>
<p>This is free software published under the GPLv3.</p>
</div>
</div>
</body>
</html>
""")
def print_only_image(svg):
print('Content-type: image/svg+xml\nContent-Disposition: attachment; filename="apollonian_gasket.svg"\n\n')
print(svg)
if __name__ == "__main__":
# Debugging
#cgitb.enable()
# Get settings from form
form = cgi.FieldStorage()
param = Settings(form)
# Construct color map
schemes = ColorScheme("colorbrewer.json")
if not param.impossible:
# Magic
ag = ApollonianGasket(param.c1, param.c2, param.c3)
ag.generate(param.depth)
# Get smallest and biggest radius
smallest = abs(min(ag.genCircles, key=lambda c: abs(c.r.real)).r.real)
biggest = abs(max(ag.genCircles, key=lambda c: abs(c.r.real)).r.real)
if param.color == 'none':
mp = ColorMap('none')
else:
mp = schemes.makeMap(smallest, biggest, param.color, param.resolution)
# Convert to svg
svg = ag_to_svg(ag.genCircles, mp, tresh=0.005)
# Output
if param.onlysvg:
print_only_image(svg)
else:
print_with_form(svg, param, schemes)
else:
errortext = "<h2>No Apollonian gasket possible for these curvatures :(</2>"
print_with_form(errortext, param, schemes)