This commit is contained in:
bogo
2024-08-30 05:46:12 +02:00
parent ce177e544f
commit 30593dd11f
15 changed files with 439 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
.idea

161
app.py Normal file
View File

@@ -0,0 +1,161 @@
import math
from shutil import posix
from flask import Flask, render_template, request, send_file
from weasyprint import HTML
import io
app = Flask(__name__)
# Original values dictionary
VALUES = {
"five_to_go": {
"t1": {"len_a": 3100, "len_h": 9100, 'width': 250, 'height': 250, 'elevation': 0, 'stop_plate': False, 'side': 'left'},
"t2": {"len_a": 1000, "len_h": 11000, 'width': 250, 'height': 250, 'elevation': 0, 'stop_plate': False, 'side': 'left'},
"t3": {"len_a": 1000, "len_h": 13700, 'width': 250, 'height': 250, 'elevation': 0, 'stop_plate': False, 'side': 'right'},
"t4": {"len_a": 3100, "len_h": 16500, 'width': 250, 'height': 250, 'elevation': 0, 'stop_plate': False, 'side': 'right'},
"t5": {"len_a": 5200, "len_h": 6400, 'width': 300, 'height': 300, 'elevation': 0, 'stop_plate': True, 'side': 'right'},
},
"showdown_left": {
"t1": {"len_a": 1800, "len_h": 22900, 'width': 400, 'height': 600, 'elevation': 200, 'stop_plate': False, 'side': 'left'},
"t2": {"len_a": 200, "len_h": 9000, 'width': 250, 'height': 250, 'elevation': 0, 'stop_plate': False, 'side': 'right'},
"t3": {"len_a": 1000, "len_h": 11000, 'width': 300, 'height': 300, 'elevation': 0, 'stop_plate': True, 'side': 'right'},
"t4": {"len_a": 3800, "len_h": 22900, 'width': 400, 'height': 600, 'elevation': 200, 'stop_plate': False, 'side': 'right'},
"t5": {"len_a": 2200, "len_h": 9000, 'width': 250, 'height': 250, 'elevation': 0, 'stop_plate': False, 'side': 'right'},
},
"showdown_right": {
"t1": {"len_a": 2200, "len_h": 9000, 'width': 250, 'height': 250, 'elevation': 0, 'stop_plate': False, 'side': 'left'},
"t2": {"len_a": 3800, "len_h": 22900, 'width': 400, 'height': 600, 'elevation': 200, 'stop_plate': False, 'side': 'left'},
"t3": {"len_a": 1000, "len_h": 11000, 'width': 300, 'height': 300, 'elevation': 0, 'stop_plate': True, 'side': 'left'},
"t4": {"len_a": 200, "len_h": 9000, 'width': 250, 'height': 250, 'elevation': 0, 'stop_plate': False, 'side': 'left'},
"t5": {"len_a": 1800, "len_h": 22900, 'width': 400, 'height': 600, 'elevation': 200, 'stop_plate': False, 'side': 'right'},
},
"smoke_and_hope": {
"t1": {"len_a": 4300, "len_h": 6400, 'width': 400, 'height': 600, 'elevation': 150, 'stop_plate': False, 'side': 'left'},
"t2": {"len_a": 2700, "len_h": 8200, 'width': 400, 'height': 600, 'elevation': 150, 'stop_plate': False, 'side': 'left'},
"t3": {"len_a": 0, "len_h": 12800, 'width': 300, 'height': 300, 'elevation': 0, 'stop_plate': True, 'side': 'right'},
"t4": {"len_a": 2700, "len_h": 8200, 'width': 400, 'height': 600, 'elevation': 150, 'stop_plate': False, 'side': 'right'},
"t5": {"len_a": 4300, "len_h": 6400, 'width': 400, 'height': 600, 'elevation': 150, 'stop_plate': False, 'side': 'right'},
},
"accelerator": {
"t1": {"len_a": 3600, "len_h": 9100, 'width': 250, 'height': 250, 'elevation': 0, 'stop_plate': False, 'side': 'left'},
"t2": {"len_a": 1200, "len_h": 9100, 'width': 400, 'height': 600, 'elevation': 150, 'stop_plate': False, 'side': 'left'},
"t3": {"len_a": 0, "len_h": 13700, 'width': 300, 'height': 300, 'elevation': 0, 'stop_plate': True, 'side': 'right'},
"t4": {"len_a": 1900, "len_h": 18300, 'width': 300, 'height': 300, 'elevation': 0, 'stop_plate': False, 'side': 'right'},
"t5": {"len_a": 6200, "len_h": 18300, 'width': 400, 'height': 600, 'elevation': 150, 'stop_plate': False, 'side': 'right'},
},
"pendulum": {
"t1": {"len_a": 3800, "len_h": 16500, 'width': 300, 'height': 300, 'elevation': 400, 'stop_plate': False, 'side': 'left'},
"t2": {"len_a": 1900, "len_h": 16500, 'width': 250, 'height': 250, 'elevation': 0, 'stop_plate': False, 'side': 'left'},
"t3": {"len_a": 0, "len_h": 9100, 'width': 300, 'height': 300, 'elevation': 0, 'stop_plate': True, 'side': 'right'},
"t4": {"len_a": 1900, "len_h": 16500, 'width': 250, 'height': 250, 'elevation': 0, 'stop_plate': False, 'side': 'right'},
"t5": {"len_a": 3800, "len_h": 16500, 'width': 300, 'height': 300, 'elevation': 400, 'stop_plate': False, 'side': 'right'},
},
"speed_option": {
"t1": {"len_a": 3700, "len_h": 9100, 'width': 300, 'height': 300, 'elevation': 0, 'stop_plate': False, 'side': 'left'},
"t2": {"len_a": 6600, "len_h": 32000, 'width': 400, 'height': 600, 'elevation': 150, 'stop_plate': True, 'side': 'left'},
"t3": {"len_a": 1800, "len_h": 18300, 'width': 300, 'height': 300, 'elevation': 0, 'stop_plate': False, 'side': 'left'},
"t4": {"len_a": 2000, "len_h": 7300, 'width': 300, 'height': 300, 'elevation': 0, 'stop_plate': False, 'side': 'right'},
"t5": {"len_a": 6400, "len_h": 13700, 'width': 300, 'height': 300, 'elevation': 0, 'stop_plate': False, 'side': 'right'},
},
"roundabout": {
"t1": {"len_a": 2700, "len_h": 13700, 'width': 300, 'height': 300, 'elevation': 0, 'stop_plate': False, 'side': 'left'},
"t2": {"len_a": 600, "len_h": 6400, 'width': 300, 'height': 300, 'elevation': 0, 'stop_plate': False, 'side': 'left'},
"t3": {"len_a": 600, "len_h": 9100, 'width': 300, 'height': 300, 'elevation': 0, 'stop_plate': True, 'side': 'right'},
"t4": {"len_a": 2500, "len_h": 13700, 'width': 300, 'height': 300, 'elevation': 0, 'stop_plate': False, 'side': 'right'},
"t5": {"len_a": 2500, "len_h": 6400, 'width': 300, 'height': 300, 'elevation': 0, 'stop_plate': False, 'side': 'right'},
}
}
def _target_info(distance, stage, size, target):
values = VALUES[stage][target]
a_len, h_len = values["len_a"], values["len_h"]
org_width, org_height, org_elevation = values['width'], values['height'], values['elevation']
x_len = math.sqrt(a_len ** 2 + h_len ** 2)
scale = distance / h_len
target_scale = distance / x_len
sim_a_len = a_len * scale
width, height, elevation = org_width * target_scale, org_height * target_scale, org_elevation * target_scale
post_width = 40 * target_scale
post_height = 150 if size == 'a3' else 100
if distance < 1000:
post_height = 15
if target == 't1':
position = 0
else:
first_target = _target_info(distance, stage, size, 't1')
if values['side'] == 'left':
position = first_target['sim_a_len'] - sim_a_len
else:
position = first_target['sim_a_len'] + sim_a_len
return {
'distance': distance,
'stage': stage,
'target': target,
'width': width,
'height': height,
'elevation': elevation,
'sim_a_len': sim_a_len,
'stop_plate': values['stop_plate'],
'post_height': post_height + elevation,
'post_width': post_width,
'side': values['side'],
'position': int(position),
}
@app.route('/')
def index():
return render_template('index.html')
@app.route('/generate-pdf', methods=['POST'])
def generate_pdf():
distance = int(request.form.get('distance', 0))
distance_in_mm = distance * 10
stage = request.form.get('stage')
size = request.form.get('size')
targets = [f't{i}' for i in range(1, 6)]
target_info = [_target_info(distance_in_mm, stage, size, target) for target in targets]
wall_extra_space_for_paper = 297 if size == 'a3' else 210
wall_length = target_info[-1]['position'] + wall_extra_space_for_paper
preview_size = 267 if size == 'a3' else 190
preview_scale = preview_size / target_info[-1]['position']
preview_distance = distance_in_mm * preview_scale
preview_target_info = [_target_info(preview_distance, stage, size, target) for target in targets]
box_position = int(target_info[0]['sim_a_len']) / 10
rendered_html = render_template(
'pdf_template.html',
distance=distance,
size=size,
stage=stage,
target_info=target_info,
preview_target_info=preview_target_info,
wall_length=wall_length,
box_position=box_position,
)
# return rendered_html
pdf_file = io.BytesIO()
HTML(string=rendered_html).write_pdf(pdf_file)
pdf_file.seek(0)
filename = 'Paper Challange - '+stage.replace('_', ' ')+' - '+str(distance)+'cm-'+size+'.pdf'
return send_file(pdf_file, download_name=filename, as_attachment=False)
if __name__ == '__main__':
app.run(debug=True)

21
requirements.txt Normal file
View File

@@ -0,0 +1,21 @@
blinker==1.8.2
Brotli==1.1.0
cffi==1.17.0
click==8.1.7
cssselect2==0.7.0
Flask==3.0.3
fonttools==4.53.1
html5lib==1.1
itsdangerous==2.2.0
Jinja2==3.1.4
MarkupSafe==2.1.5
pillow==10.4.0
pycparser==2.22
pydyf==0.11.0
pyphen==0.16.0
six==1.16.0
tinycss2==1.3.0
weasyprint==62.3
webencodings==0.5.1
Werkzeug==3.0.4
zopfli==0.2.3

BIN
static/images/a3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
static/images/a4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
static/images/pendulum.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

145
templates/index.html Normal file
View File

@@ -0,0 +1,145 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Paper Challenge</title>
<script src="https://cdn.tailwindcss.com?plugins=forms,typography,aspect-ratio,line-clamp,container-queries"></script>
<style>
/* Hide the radio buttons */
input[type="radio"] {
display: none;
}
/* Style the labels to act like buttons */
label {
display: block;
cursor: pointer;
border: 2px solid gray;
border-radius: 8px;
overflow: hidden;
transition: border-color 0.2s, box-shadow 0.2s;
text-align: center;
}
/* Highlight the selected label */
input[type="radio"]:checked + label {
border-color: #3b82f6; /* Blue color */
box-shadow: 0 0 10px #3b82f6;
}
/* Add hover effect */
label:hover {
border-color: #3b82f6;
}
/* Style the images */
label img {
width: 100%;
height: auto;
max-height: 150px; /* Limit image height */
}
/* Style the text below images */
label p {
margin-top: 8px;
font-size: 1rem;
font-weight: bold;
color: #374151; /* Gray color */
}
</style>
</head>
<body class="bg-gray-100 font-sans">
<div class="max-w-2xl mx-auto p-6 bg-white shadow-md rounded-lg mt-10">
<h1 class="text-2xl font-bold mb-6 text-center text-gray-700">Paper Challenge</h1>
<form action="/generate-pdf" method="post">
<div class="mb-6">
<h2>Format druku</h2>
<div id="size" class="flex place-content-center">
<input type="radio" id="a4" name="size" value="a4" class="absolute inset-0 opacity-0" required>
<label for="a4" class="cursor-pointer border-2 border-transparent rounded-lg overflow-hidden transition-all hover:border-blue-400">
<img src="{{ url_for('static', filename='images/a4.png') }}" alt="A4 (210mm x 297mm)" class="w-24 h-auto">
</label>
<input type="radio" id="a3" name="size" value="a3" class="absolute inset-0 opacity-0">
<label for="a3" class="cursor-pointer border-2 border-transparent rounded-lg overflow-hidden transition-all hover:border-blue-400">
<img src="{{ url_for('static', filename='images/a3.png') }}" alt="A3 (297mm x 420mm)" class="w-24 h-auto">
</label>
</div>
</div>
<div class="mb-6">
<h2>Tor</h2>
<div id="stage" class="grid grid-cols-1 gap-4">
<div>
<input type="radio" id="five_to_go" name="stage" value="five_to_go" required>
<label for="five_to_go">
<p>Five To Go</p>
<img src="{{ url_for('static', filename='images/five_to_go.png') }}" alt="Five To Go">
</label>
</div>
<div>
<input type="radio" id="showdown_left" name="stage" value="showdown_left">
<label for="showdown_left">
<p>Showdown (left box)</p>
<img src="{{ url_for('static', filename='images/showdown_left.png') }}" alt="Showdown (left box)">
</label>
</div>
<div>
<input type="radio" id="showdown_right" name="stage" value="showdown_right">
<label for="showdown_right">
<p>Showdown (right box)</p>
<img src="{{ url_for('static', filename='images/showdown_right.png') }}" alt="Showdown (right box)">
</label>
</div>
<div>
<input type="radio" id="smoke_and_hope" name="stage" value="smoke_and_hope">
<label for="smoke_and_hope">
<p>Smoke & Hope</p>
<img src="{{ url_for('static', filename='images/smoke_and_hope.png') }}" alt="Smoke & Hope">
</label>
</div>
<div>
<input type="radio" id="accelerator" name="stage" value="accelerator">
<label for="accelerator">
<p>Accelerator</p>
<img src="{{ url_for('static', filename='images/accelerator.png') }}" alt="Accelerator">
</label>
</div>
<div>
<input type="radio" id="pendulum" name="stage" value="pendulum">
<label for="pendulum">
<p>Pendulum</p>
<img src="{{ url_for('static', filename='images/pendulum.png') }}" alt="Pendulum">
</label>
</div>
<div>
<input type="radio" id="speed_option" name="stage" value="speed_option">
<label for="speed_option">
<p>Speed Option</p>
<img src="{{ url_for('static', filename='images/speed_option.png') }}" alt="Speed Option">
</label>
</div>
<div>
<input type="radio" id="roundabout" name="stage" value="roundabout">
<label for="roundabout">
<p>Roundabout</p>
<img src="{{ url_for('static', filename='images/roundabout.png') }}" alt="RoundAbout">
</label>
</div>
</div>
</div>
<div class="mb-6">
<h2>Odległość od ściany</h2>
<input type="number" id="distance" name="distance" class="w-full p-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-400" required>
</div>
<div class="text-center">
<button type="submit" class="bg-blue-500 text-white py-2 px-4 rounded-md hover:bg-blue-600 transition-colors">
Generuj
</button>
</div>
</form>
</div>
</body>
</html>

111
templates/pdf_template.html Normal file
View File

@@ -0,0 +1,111 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Paper Challenge - {{ stage.split('_') | map('capitalize') | join(' ') }} - {{ distance }} cm - {{ size|capitalize }}</title>
<style>
@page {
size: {{ size }};
margin: 5mm;
}
body {
font-family: sans-serif;
margin-top: 0;
}
h1, h2 {
text-align: center;
margin-top: 20mm;
}
.page-break {
page-break-after: always;
}
.target {
margin: 0 auto;
background-color: dodgerblue;
position: absolute;
top: 0%;
left: 50%;
transform: translate(-50%, -50%);
}
.circle {
border-radius: 50%;
}
.post {
margin: auto;
background-color: black;
position: relative;
}
.elevator {
position: absolute;
bottom: 0;
left: 50%;
transform: translate(-50%, 0);
}
.stop-plate {
background-color: red !important;
}
.mounting-point {
height: 10mm;
width: 0.5mm;
background-color: #ccc;
margin: auto;
}
.mounting-info {
font-size: xx-small;
color: #ccc;
}
.preview {
position: relative;
display: block;
border-bottom: solid 1px black;
border-top: solid 1px black;
width: 100%;
height: 75mm;
}
.preview-post {
position: absolute;
}
</style>
</head>
<body>
<div>
<h1>Paper Challange</h1>
<h2>{{ stage.split('_') | map('capitalize') | join(' ') }}</h2>
<div class="preview">
{% for target in preview_target_info %}
<div class="post preview-post {% if target.stop_plate %} stop-plate {% endif %}" style="top: {{ 50 - target.post_height }}mm; left: {{ target.position + 3 }}mm; width: {{ target.post_width }}mm; height: {{ target.height }}mm;">
<div class="target {% if target.width == target.height %} circle {% endif %}" style="width: {{ target.width }}mm; height: {{ target.height }}mm"></div>
</div>
{% endfor %}
</div>
<p>Minimalna długość ściany: {{ wall_length/10 }}cm</p>
<p>Odległość od ściany: {{ distance }}cm</p>
<p>Przygotowanie toru:</p>
<ul>
<li>Umieść z lewej strony ściany pierwszy cel ze znacznikiem "punkt ZERO"</li>
<li>Wyznacz pole startowe {{ box_position }}cm od punktu "zero"</li>
<li>Upewnij się, że pole startowe znjaduje się w dogodnym miejscu, pomieszczenia, tak aby nic nie przeszkazdało w swobodnym dobyciu i składaniu do celów na całej szerokości ściany</li>
<li>W razie potrzeby dostosuj pierwszy cel wraz z punktem ZERO i powtórz dwa powyższe kroki</li>
<li>Umieść na ścianie pozostałe cele zgodnie z odległościami podanymi na znacznikach pozycji na dole każdego celu. Upewnij się, że wszystkie cele są umieszczone w jednej linii równoległej do podłoża</li>
</ul>
</div>
<div class="page-break"></div>
{% for target in target_info %}
<div class="page-break"></div>
<div class="elevator">
<div class="post {% if target.stop_plate %} stop-plate {% endif %}" style="width: {{ target.post_width }}mm; height: {{ target.post_height }}mm;">
<div class="target {% if target.width == target.height %} circle {% endif %}" style="width: {{ target.width }}mm; height: {{ target.height }}mm"></div>
</div>
{% if target.target == 't1' %}
<div class="mounting-info">punkt ZERO ({{ stage.split('_') | map('capitalize') | join(' ') }})</div>
{% else %}
<div class="mounting-info">{{ target.position / 10 }}cm from 0 point ({{ stage.split('_') | map('capitalize') | join(' ') }})</div>
{% endif %}
<div class="mounting-point"></div>
</div>
{% endfor %}
</body>
</html>