Initial webapp design
34
webapp/job_processor.py
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
|
||||
import signal
|
||||
import subprocess
|
||||
import logging
|
||||
import itertools
|
||||
|
||||
from job_queue import JobQueue
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('queue', help='job queue sqlite3 database file')
|
||||
parser.add_argument('--loglevel', '-l', default='info')
|
||||
args = parser.parse_args()
|
||||
|
||||
numeric_level = getattr(logging, args.loglevel.upper(), None)
|
||||
if not isinstance(numeric_level, int):
|
||||
raise ValueError('Invalid log level: %s' % loglevel)
|
||||
logging.basicConfig(level=numeric_level)
|
||||
|
||||
job_queue = JobQueue(args.queue)
|
||||
|
||||
signal.signal(signal.SIGALRM, lambda *args: None) # Ignore incoming alarm signals while processing jobs
|
||||
signal.setitimer(signal.ITIMER_REAL, 0.001, 1)
|
||||
while signal.sigwait([signal.SIGALRM, signal.SIGINT]) == signal.SIGALRM:
|
||||
logging.debug('Checking for jobs')
|
||||
for job in job_queue.job_iter('render'):
|
||||
logging.info(f'Processing {job.type} job {job.id} session {job["session_id"]} from {job.client} submitted {job.created}')
|
||||
with job:
|
||||
job.result = subprocess.call(['sudo', '/usr/local/sbin/pogojig_render.sh', job['session_id']])
|
||||
logging.info(f'Finishied processing {job.type} job {job.id}')
|
||||
logging.info('Caught SIGINT. Exiting.')
|
||||
|
||||
77
webapp/job_queue.py
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
|
||||
import json
|
||||
import sqlite3
|
||||
|
||||
class JobQueue:
|
||||
def __init__(self, dbfile):
|
||||
self.dbfile = dbfile
|
||||
self.db = sqlite3.connect(dbfile, check_same_thread=False)
|
||||
self.db.row_factory = sqlite3.Row
|
||||
with self.db as conn:
|
||||
conn.execute('''CREATE TABLE IF NOT EXISTS jobs
|
||||
(id INTEGER PRIMARY KEY,
|
||||
type TEXT,
|
||||
params TEXT,
|
||||
client TEXT,
|
||||
result INTEGER DEFAULT NULL,
|
||||
created DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
consumed DATETIME DEFAULT NULL,
|
||||
aborted DATETIME DEFAULT NULL,
|
||||
finished DATETIME DEFAULT NULL);''')
|
||||
|
||||
def enqueue(self, task_type:str, client, **params):
|
||||
""" Enqueue a job of the given type with the given params. Returns the new job ID. """
|
||||
with self.db as conn:
|
||||
return conn.execute('INSERT INTO jobs(type, client, params) VALUES (?, ?, ?)',
|
||||
(task_type, client, json.dumps(params))).lastrowid
|
||||
|
||||
def pop(self, task_type):
|
||||
""" Fetch the next job of the given type. Returns a sqlite3.Row object of the job or None if no jobs of the given
|
||||
type are queued. """
|
||||
with self.db as conn:
|
||||
job = conn.execute('SELECT * FROM jobs WHERE type=? AND consumed IS NULL AND aborted IS NULL ORDER BY created ASC LIMIT 1',
|
||||
(task_type,)).fetchone()
|
||||
if job is None:
|
||||
return None
|
||||
|
||||
# Atomically commit to this job
|
||||
conn.execute('UPDATE jobs SET consumed=datetime("now") WHERE id=?', (job['id'],))
|
||||
|
||||
return Job(self.db, job)
|
||||
|
||||
def job_iter(self, task_type):
|
||||
return iter(lambda: self.pop(task_type), None)
|
||||
|
||||
def __getitem__(self, key):
|
||||
""" Return the job with the given ID, or raise a KeyError if the key cannot be found. """
|
||||
with self.db as conn:
|
||||
job = conn.execute('SELECT * FROM jobs WHERE id=?', (key,)).fetchone()
|
||||
if job is None:
|
||||
raise KeyError(f'Unknown job ID "{key}"')
|
||||
|
||||
return Job(self.db, job)
|
||||
|
||||
class Job(dict):
|
||||
def __init__(self, db, row):
|
||||
super().__init__(json.loads(row['params']))
|
||||
self._db = db
|
||||
self._row = row
|
||||
self.id = row['id']
|
||||
self.type = row['type']
|
||||
self.client = row['client']
|
||||
self.created = row['created']
|
||||
self.consumed = row['consumed']
|
||||
self.finished = row['finished']
|
||||
self.result = row['result']
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, _exc_type, _exc_val, _exc_tb):
|
||||
with self._db as conn:
|
||||
conn.execute('UPDATE jobs SET finished=datetime("now"), result=? WHERE id=?', (self.result, self.id,))
|
||||
|
||||
def abort(self, job_id):
|
||||
with self.db as conn:
|
||||
conn.execute('UPDATE jobs SET aborted=datetime("now") WHERE id=?', (self.id,))
|
||||
|
||||
4
webapp/pogojig.cfg
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
MAX_CONTENT_LENGTH=10000000
|
||||
SECRET_KEY="FIXME: CHANGE THIS KEY"
|
||||
UPLOAD_PATH="/var/cache/pogojig/upload"
|
||||
JOB_QUEUE_DB="/var/cache/pogojig/job_queue.sqlite3"
|
||||
111
webapp/pogojig.py
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# TODO setup webserver user disk quota
|
||||
|
||||
import tempfile
|
||||
import uuid
|
||||
from functools import wraps
|
||||
from os import path
|
||||
import os
|
||||
import sqlite3
|
||||
|
||||
from flask import Flask, url_for, redirect, session, make_response, render_template, request, send_file, abort, flash
|
||||
from flask_wtf import FlaskForm
|
||||
from flask_wtf.file import FileField, FileRequired
|
||||
from wtforms.fields import RadioField
|
||||
from wtforms.validators import DataRequired
|
||||
from werkzeug.utils import secure_filename
|
||||
|
||||
from job_queue import JobQueue
|
||||
|
||||
app = Flask(__name__, static_url_path='/static')
|
||||
app.config.from_envvar('POGOJIG_SETTINGS')
|
||||
|
||||
class UploadForm(FlaskForm):
|
||||
upload_file = FileField(validators=[DataRequired()])
|
||||
|
||||
class ResetForm(FlaskForm):
|
||||
pass
|
||||
|
||||
job_queue = JobQueue(app.config['JOB_QUEUE_DB'])
|
||||
|
||||
def tempfile_path(namespace):
|
||||
""" Return a path for a per-session temporary file identified by the given namespace. Create the session tempfile
|
||||
dir if necessary. The application tempfile dir is controlled via the upload_path config value and not managed by
|
||||
this function. """
|
||||
sess_tmp = path.join(app.config['UPLOAD_PATH'], session['session_id'])
|
||||
os.makedirs(sess_tmp, exist_ok=True)
|
||||
return path.join(sess_tmp, namespace)
|
||||
|
||||
def require_session_id(fun):
|
||||
@wraps(fun)
|
||||
def wrapper(*args, **kwargs):
|
||||
if 'session_id' not in session:
|
||||
session['session_id'] = str(uuid.uuid4())
|
||||
return fun(*args, **kwargs)
|
||||
return wrapper
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
return app.send_static_file('index.html')
|
||||
|
||||
@app.route('/jigerator')
|
||||
@require_session_id
|
||||
def jigerator():
|
||||
forms = {
|
||||
'svg_form': UploadForm(),
|
||||
'reset_form': ResetForm() }
|
||||
|
||||
if 'render_job' in session:
|
||||
job = job_queue[session['render_job']]
|
||||
if job.finished:
|
||||
if job.result != 0:
|
||||
flash(f'Error processing SVG file', 'success') # FIXME make this an error, add CSS
|
||||
del session['render_job']
|
||||
|
||||
r = make_response(render_template('jigerator.html', has_renders=path.isfile(tempfile_path('output.zip')), **forms))
|
||||
if 'render_job' in session:
|
||||
r.headers.set('refresh', '10')
|
||||
return r
|
||||
|
||||
# NOTES about the SVG file upload routines
|
||||
# * The maximum upload size is limited by the MAX_CONTENT_LENGTH config setting.
|
||||
# * The uploaded files are deleted after a while by systemd tmpfiles.d
|
||||
# TODO: validate this setting applies *after* gzip transport compression
|
||||
|
||||
def render():
|
||||
if 'render_job' in session:
|
||||
job_queue[session['render_job']].abort()
|
||||
session['render_job'] = job_queue.enqueue('render',
|
||||
session_id=session['session_id'],
|
||||
client=request.remote_addr)
|
||||
|
||||
@app.route('/upload/svg', methods=['POST'])
|
||||
@require_session_id
|
||||
def upload_svg():
|
||||
upload_form = UploadForm()
|
||||
if upload_form.validate_on_submit():
|
||||
f = upload_form.upload_file.data
|
||||
f.save(tempfile_path('input.svg'))
|
||||
session['filename'] = secure_filename(f.filename) # Cache filename for later download
|
||||
|
||||
render()
|
||||
flash(f'SVG file successfully uploaded.', 'success')
|
||||
return redirect(url_for('jigerator'))
|
||||
|
||||
@app.route('/render/download')
|
||||
def render_download():
|
||||
return send_file(tempfile_path(f'renders.zip'),
|
||||
mimetype='application/zip',
|
||||
as_attachment=True,
|
||||
attachment_filename=f'{path.splitext(session["filename"])[0]}_pogojig.zip')
|
||||
|
||||
@app.route('/session_reset', methods=['POST'])
|
||||
@require_session_id
|
||||
def session_reset():
|
||||
if 'render_job' in session:
|
||||
job_queue[session['render_job']].abort()
|
||||
session.clear()
|
||||
flash('Session reset', 'success');
|
||||
return redirect(url_for('jigerator'))
|
||||
|
||||
BIN
webapp/static/3d_models.png
Normal file
|
After Width: | Height: | Size: 148 KiB |
BIN
webapp/static/arrow-small.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
webapp/static/arrow.png
Normal file
|
After Width: | Height: | Size: 73 KiB |
BIN
webapp/static/bg-gears.jpg
Normal file
|
After Width: | Height: | Size: 342 KiB |
47
webapp/static/bg.svg
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
id="svg8"
|
||||
version="1.1"
|
||||
viewBox="0 0 10 10"
|
||||
height="10mm"
|
||||
width="10mm">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
transform="translate(0,-287)"
|
||||
id="layer1">
|
||||
<path
|
||||
id="path853-7-5"
|
||||
d="m 4.9999974,291.19333 v -1.82507"
|
||||
style="fill:none;stroke:#505050;stroke-width:0.2;stroke-linecap:butt;" />
|
||||
<path
|
||||
id="path853-7-5-6"
|
||||
d="m 4.9999975,294.63169 v -1.82507"
|
||||
style="fill:none;stroke:#505050;stroke-width:0.2;stroke-linecap:butt;" />
|
||||
<path
|
||||
id="path853-7-5-6-2"
|
||||
d="m 5.8066428,291.99997 h 1.82507"
|
||||
style="fill:none;stroke:#505050;stroke-width:0.2;stroke-linecap:butt;" />
|
||||
<path
|
||||
id="path853-7-5-6-2-9"
|
||||
d="m 2.3682832,291.99997 h 1.82507"
|
||||
style="fill:none;stroke:#505050;stroke-width:0.2;stroke-linecap:butt;" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
BIN
webapp/static/bubble1-small.png
Normal file
|
After Width: | Height: | Size: 83 KiB |
BIN
webapp/static/bubble1.png
Normal file
|
After Width: | Height: | Size: 83 KiB |
BIN
webapp/static/bubble2-small.png
Normal file
|
After Width: | Height: | Size: 234 KiB |
BIN
webapp/static/bubble2.png
Normal file
|
After Width: | Height: | Size: 103 KiB |
587
webapp/static/cad_example.svg
Normal file
|
|
@ -0,0 +1,587 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
id="svg2371"
|
||||
version="1.1"
|
||||
viewBox="0 0 49.77145 43.68911"
|
||||
height="43.68911mm"
|
||||
width="49.77145mm">
|
||||
<defs
|
||||
id="defs2365" />
|
||||
<metadata
|
||||
id="metadata2368">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
transform="translate(-78.654536,-117.51911)"
|
||||
id="layer1">
|
||||
<g
|
||||
transform="translate(3.3973463,-8.6235988)"
|
||||
id="g3027"
|
||||
style="display:inline">
|
||||
<g
|
||||
style="stroke-width:1.78494132"
|
||||
id="g9144"
|
||||
transform="matrix(0.56024251,0,0,0.56024251,83.834048,-2.2500089)">
|
||||
<g
|
||||
style="stroke-width:1.78494132"
|
||||
id="g1990"
|
||||
transform="matrix(0.001,0,0,-0.001,-70.961805,164.78416)">
|
||||
<g
|
||||
style="stroke-width:1.78494132"
|
||||
id="g1988">
|
||||
<g
|
||||
class="CbY_IYrtRvGd5RWKriaG_sm"
|
||||
id="CbY_IYrtRvGd5RWKriaG_top_sm-copy"
|
||||
style="color:#000000;fill:currentColor;stroke:currentColor;stroke-width:1.78494132">
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1562"
|
||||
d="m 104826.94,-118483.48 191.27,-79.23 172.14,-115.02 146.38,-146.39 115.03,-172.14 79.22,-191.27 40.39,-203.05 v -207.03 l -40.39,-203.05 -79.22,-191.27 -115.03,-172.14 -146.38,-146.39 -172.14,-115.02 -191.27,-79.23 -203.05,-40.39 h -207.03 l -203.05,40.39 -191.27,79.23 -172.14,115.02 -146.39,146.39 -115.02,172.14 -79.23,191.27 -40.39,203.05 v 207.03 l 40.39,203.05 79.23,191.27 115.02,172.14 146.39,146.39 172.14,115.02 191.27,79.23 203.05,40.38 h 207.03 l 203.05,-40.38 v 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1564"
|
||||
d="m 90413.04,-118943.17 145.775,-544.04 28.392,-105.96 -1488.195,-398.76 -542.181,-145.28 v 0 l -442.093,1649.92 -101.944,380.46 v 0 l 1669.763,447.41 360.613,96.63 v 0 l 369.87,-1380.38 v 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1566"
|
||||
d="m 100101,-119626 h -1302 v 852 h 1302 v -852 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1568"
|
||||
d="m 102001,-119626 h -1302 v 852 h 1302 v -852 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1570"
|
||||
d="m 110514.04,-118483.48 91.27,-340.64 31.9,-119.05 -1601.31,-429.07 -429.07,-114.97 v 0 l -489.75,1827.79 -54.29,202.59 v 0 l 1601.31,429.07 429.07,114.97 v 0 l 420.87,-1570.69 v 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1572"
|
||||
d="m 94436.194,-116545.29 191.269,-79.23 172.142,-115.02 146.387,-146.39 115.022,-172.14 79.226,-191.27 40.389,-203.05 v -207.03 l -40.389,-203.05 -79.226,-191.27 -115.022,-172.14 -146.387,-146.39 -172.142,-115.02 -191.269,-79.23 -203.049,-40.39 h -207.032 l -203.049,40.39 -191.269,79.23 -172.142,115.02 -146.387,146.39 -115.022,172.14 -79.226,191.27 -40.389,203.05 v 207.03 l 40.389,203.05 79.226,191.27 115.022,172.14 146.387,146.39 172.142,115.02 191.269,79.23 203.049,40.38 h 207.032 l 203.049,-40.38 v 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1574"
|
||||
d="m 104351,-117176 h -1302 v 852 h 1302 v -852 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1576"
|
||||
d="m 102451,-117176 h -1302 v 852 h 1302 v -852 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1578"
|
||||
d="m 98151,-116926 h -1302 v 852 h 1302 v -852 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1580"
|
||||
d="m 100051,-116926 h -1302 v 852 h 1302 v -852 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1582"
|
||||
d="m 102551,-115051 h -1602 v 2102 h 1602 v -2102 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1584"
|
||||
d="m 97951,-115051 h -1602 v 2102 h 1602 v -2102 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1586"
|
||||
d="m 100251,-115051 h -1602 v 2102 h 1602 v -2102 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1588"
|
||||
d="m 111001,-112501 h -1002 v 1302 h 1002 v -1302 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1590"
|
||||
d="m 113676,-112301 h -1352 v 1102 h 1352 v -1102 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1592"
|
||||
d="m 108251,-112151 h -1002 v 902 h 1002 v -902 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1594"
|
||||
d="m 106251,-111201 h -1002 v 902 h 1002 v -902 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1596"
|
||||
d="m 84251,-110601 h -1302 v 1002 h 1302 v -1002 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1598"
|
||||
d="m 86451,-110601 h -1302 v 1002 h 1302 v -1002 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1600"
|
||||
d="m 111001,-110301 h -1002 v 1302 h 1002 v -1302 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1602"
|
||||
d="m 113676,-110301 h -1352 v 1102 h 1352 v -1102 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1604"
|
||||
d="m 108251,-110251 h -1002 v 902 h 1002 v -902 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1606"
|
||||
d="m 101401,-108751 h -3902 v 2102 h 3902 v -2102 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1608"
|
||||
d="m 82701,-108751 h -1002 v 1302 h 1002 v -1302 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1610"
|
||||
d="m 85276,-108551 h -1352 v 1102 h 1352 v -1102 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1612"
|
||||
d="m 87501,-108401 h -1002 v 902 h 1002 v -902 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1614"
|
||||
d="m 105051,-108001 h -1302 v 1002 h 1302 v -1002 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1616"
|
||||
d="m 107251,-108001 h -1302 v 1002 h 1302 v -1002 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1618"
|
||||
d="m 89501,-107451 h -1002 v 902 h 1002 v -902 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1620"
|
||||
d="m 85276,-106551 h -1352 v 1102 h 1352 v -1102 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1622"
|
||||
d="m 82701,-106551 h -1002 v 1302 h 1002 v -1302 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1624"
|
||||
d="m 87501,-106501 h -1002 v 902 h 1002 v -902 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1626"
|
||||
d="m 105051,-106001 h -1302 v 1002 h 1302 v -1002 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1628"
|
||||
d="m 107251,-106001 h -1302 v 1002 h 1302 v -1002 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1630"
|
||||
d="m 114676,-105751 h -1352 v 1102 h 1352 v -1102 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1632"
|
||||
d="m 116201,-104151 h -1002 v 1302 h 1002 v -1302 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1634"
|
||||
d="m 90051,-104101 h -3402 v 1802 h 3402 v -1802 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1636"
|
||||
d="m 84551,-104101 h -3402 v 1802 h 3402 v -1802 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1638"
|
||||
d="m 118601,-103801 h -902 v 1002 h 902 v -1002 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1640"
|
||||
d="m 120501,-103801 h -902 v 1002 h 902 v -1002 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1642"
|
||||
d="m 114676,-103751 h -1352 v 1102 h 1352 v -1102 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1644"
|
||||
d="m 97826,-103201 h -1552 v 6402 h 1552 v -6402 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1646"
|
||||
d="m 103726,-103201 h -1552 v 6402 h 1552 v -6402 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1648"
|
||||
d="m 108201,-102401 h -1602 v 1602 h 1602 v -1602 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1650"
|
||||
d="m 110901,-102401 h -1602 v 1602 h 1602 v -1602 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1652"
|
||||
d="m 116201,-101951 h -1002 v 1302 h 1002 v -1302 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1654"
|
||||
d="m 105926,-101901 h -852 v 1302 h 852 v -1302 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1656"
|
||||
d="m 119551,-101801 h -902 v 1002 h 902 v -1002 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1658"
|
||||
d="m 94501,-101251 h -1002 v 1302 h 1002 v -1302 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1660"
|
||||
d="m 90051,-100901 h -3402 v 1802 h 3402 v -1802 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1662"
|
||||
d="m 84551,-100901 h -3402 v 1802 h 3402 v -1802 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1664"
|
||||
d="m 109401,-100201 h -1802 v 3402 h 1802 v -3402 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1666"
|
||||
d="m 105926,-100001 h -852 v 1302 h 852 v -1302 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1668"
|
||||
d="m 116101,-99751 h -1002 v 1302 h 1002 v -1302 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1670"
|
||||
d="m 120751,-99351 h -3302 v 2802 h 3302 v -2802 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1672"
|
||||
d="m 113751,-99251 h -1302 v 1002 h 1302 v -1002 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1674"
|
||||
d="m 111551,-99251 h -1302 v 1002 h 1302 v -1002 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1676"
|
||||
d="m 94501,-99051 h -1002 v 1302 h 1002 v -1302 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1678"
|
||||
d="m 91651,-97651 h -1802 v 1802 h 1802 v -1802 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1680"
|
||||
d="m 88320.443,-95855.519 66.184,-6.518 113.226,-34.347 56.614,-17.173 138.62,-74.095 17.904,-9.57 35.738,-29.33 101.457,-83.262 83.262,-101.457 29.33,-35.738 h 10e-4 l 83.664,-156.522 v 0 l 51.52,-169.839 17.396,-176.627 -17.396,-176.627 -34.347,-113.226 -17.173,-56.614 -42.512,-79.533 -41.153,-76.991 -29.33,-35.738 -83.262,-101.457 -101.457,-83.262 -35.738,-29.33 v 0 l -156.522,-83.664 -56.614,-17.173 -113.226,-34.347 -66.185,-6.519 -66.182,-6.518 h -88.52 l -66.182,6.518 -66.185,6.519 -113.226,34.347 -56.614,17.173 -156.522,83.664 v 0 l -35.738,29.33 -101.457,83.262 -83.262,101.457 -29.33,35.738 -41.153,76.991 -42.512,79.533 -17.173,56.614 -34.347,113.226 -17.396,176.627 17.396,176.627 51.52,169.839 v 0 l 83.664,156.522 h 10e-4 l 29.33,35.738 83.262,101.457 101.457,83.262 35.738,29.33 17.904,9.57 138.62,74.095 56.614,17.173 113.226,34.347 66.184,6.518 66.183,6.519 h 88.52 l 66.183,-6.519 v 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1682"
|
||||
d="m 85780.444,-95855.519 66.184,-6.518 113.226,-34.347 56.614,-17.173 138.62,-74.095 17.904,-9.57 35.738,-29.33 101.457,-83.262 83.262,-101.457 29.33,-35.738 v 0 l 83.664,-156.522 v 0 l 51.52,-169.839 17.396,-176.627 -17.396,-176.627 -34.347,-113.226 -17.173,-56.614 -42.512,-79.533 -41.153,-76.991 -29.33,-35.738 -83.262,-101.457 -101.457,-83.262 -35.738,-29.33 v 0 l -156.522,-83.664 -56.614,-17.173 -113.226,-34.347 -66.185,-6.519 -66.182,-6.518 h -88.52 l -66.182,6.518 -66.185,6.519 -113.226,34.347 -56.614,17.173 -156.522,83.664 v 0 l -35.738,29.33 -101.457,83.262 -83.262,101.457 -29.33,35.738 -41.153,76.991 -42.512,79.533 -17.173,56.614 -34.347,113.226 -17.396,176.627 17.396,176.627 51.52,169.839 v 0 l 83.664,156.522 v 0 l 29.33,35.738 83.262,101.457 101.457,83.262 35.738,29.33 17.904,9.57 138.62,74.095 56.614,17.173 113.226,34.347 66.184,6.518 66.183,6.519 h 88.52 l 66.183,-6.519 v 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1684"
|
||||
d="m 83240.443,-95855.519 66.184,-6.518 113.226,-34.347 56.614,-17.173 138.62,-74.095 17.904,-9.57 35.738,-29.33 101.457,-83.262 83.262,-101.457 29.33,-35.738 h 10e-4 l 83.664,-156.522 v 0 l 51.52,-169.839 17.396,-176.627 -17.396,-176.627 -34.347,-113.226 -17.173,-56.614 -42.512,-79.533 -41.153,-76.991 -29.33,-35.738 -83.262,-101.457 -101.457,-83.262 -35.738,-29.33 v 0 l -156.522,-83.664 -56.614,-17.173 -113.226,-34.347 -66.185,-6.519 -66.182,-6.518 h -88.52 l -66.182,6.518 -66.185,6.519 -113.226,34.347 -56.614,17.173 -156.522,83.664 v 0 l -35.738,29.33 -101.457,83.262 -83.262,101.457 -29.33,35.738 -41.153,76.991 -42.512,79.533 -17.173,56.614 -34.347,113.226 -17.396,176.627 17.396,176.627 51.52,169.839 v 0 l 83.664,156.522 h 10e-4 l 29.33,35.738 83.262,101.457 101.457,83.262 35.738,29.33 17.904,9.57 138.62,74.095 56.614,17.173 113.226,34.347 66.184,6.518 66.183,6.519 h 88.52 l 66.183,-6.519 v 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1686"
|
||||
d="m 116101,-97551 h -1002 v 1302 h 1002 v -1302 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1688"
|
||||
d="m 106701,-97501 h -1602 v 1602 h 1602 v -1602 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1690"
|
||||
d="m 113601,-97176 h -1302 v 852 h 1302 v -852 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1692"
|
||||
d="m 111701,-97176 h -1302 v 852 h 1302 v -852 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1694"
|
||||
d="m 99301,-95101 h -1302 v 1502 h 1302 v -1502 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1696"
|
||||
d="m 101001,-95101 h -1302 v 1502 h 1302 v -1502 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1698"
|
||||
d="m 95001,-95001 h -1002 v 1302 h 1002 v -1302 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1700"
|
||||
d="m 106701,-95001 h -1602 v 1602 h 1602 v -1602 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1702"
|
||||
d="m 111501,-95001 h -902 v 1002 h 902 v -1002 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1704"
|
||||
d="m 113401,-95001 h -902 v 1002 h 902 v -1002 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1706"
|
||||
d="m 97176,-94901 h -852 v 1302 h 852 v -1302 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1708"
|
||||
d="m 102926,-94851 h -852 v 1302 h 852 v -1302 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1710"
|
||||
d="m 109401,-94701 h -1802 v 3402 h 1802 v -3402 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1712"
|
||||
d="m 97176,-93001 h -852 v 1302 h 852 v -1302 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1714"
|
||||
d="m 112451,-93001 h -902 v 1002 h 902 v -1002 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1716"
|
||||
d="m 102926,-92951 h -852 v 1302 h 852 v -1302 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1718"
|
||||
d="m 101001,-92901 h -1302 v 1502 h 1302 v -1502 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1720"
|
||||
d="m 99301,-92901 h -1302 v 1502 h 1302 v -1502 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1722"
|
||||
d="m 95001,-92801 h -1002 v 1302 h 1002 v -1302 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1724"
|
||||
d="m 120751,-91451 h -3302 v 2802 h 3302 v -2802 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1726"
|
||||
d="m 111101,-89951 h -3602 v 2402 h 3602 v -2402 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1728"
|
||||
d="m 116501,-89951 h -3602 v 2402 h 3602 v -2402 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1730"
|
||||
d="m 92679.544,-89863.48 h -3602 v 2402 h 3602 v -2402 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1732"
|
||||
d="m 87279.544,-89863.48 h -3602 v 2402 h 3602 v -2402 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1734"
|
||||
d="m 106451,-89151 h -1002 v 1302 h 1002 v -1302 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1736"
|
||||
d="m 104451,-89151 h -1002 v 1302 h 1002 v -1302 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1738"
|
||||
d="m 102501,-89151 h -1802 v 3402 h 1802 v -3402 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1740"
|
||||
d="m 97301,-89001 h -1302 v 1002 h 1302 v -1002 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1742"
|
||||
d="m 99501,-89001 h -1302 v 1002 h 1302 v -1002 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1744"
|
||||
d="m 104451,-86951 h -1002 v 1302 h 1002 v -1302 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1746"
|
||||
d="m 106451,-86951 h -1002 v 1302 h 1002 v -1302 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1748"
|
||||
d="m 99426,-86851 h -852 v 1302 h 852 v -1302 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1750"
|
||||
d="m 96201,-86751 h -902 v 1002 h 902 v -1002 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1752"
|
||||
d="m 116501,-84951 h -3602 v 2402 h 3602 v -2402 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1754"
|
||||
d="m 99426,-84951 h -852 v 1302 h 852 v -1302 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1756"
|
||||
d="m 111101,-84951 h -3602 v 2402 h 3602 v -2402 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1758"
|
||||
d="m 87279.544,-84863.48 h -3602 v 2402 h 3602 v -2402 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1760"
|
||||
d="m 92679.544,-84863.48 h -3602 v 2402 h 3602 v -2402 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1762"
|
||||
d="m 95251,-84751 h -902 v 1002 h 902 v -1002 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1764"
|
||||
d="m 97151,-84751 h -902 v 1002 h 902 v -1002 0" />
|
||||
<path
|
||||
style="fill:#666666;stroke-width:1.78494132"
|
||||
id="path1766"
|
||||
d="m 102501,-83651 h -1802 v 3402 h 1802 v -3402 0" />
|
||||
</g>
|
||||
<g
|
||||
class="CbY_IYrtRvGd5RWKriaG_sp"
|
||||
id="CbY_IYrtRvGd5RWKriaG_top_sp-copy"
|
||||
style="color:#dddddd;fill:currentColor;stroke:currentColor;stroke-width:1.78494132" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
style="stroke-width:1.78494132"
|
||||
id="g1998"
|
||||
transform="matrix(0.001,0,0,-0.001,-70.961805,164.78417)">
|
||||
<g
|
||||
style="stroke-width:1.78494132"
|
||||
id="g1996">
|
||||
<g
|
||||
style="color:#0000ff;fill:none;stroke:currentColor;stroke-width:1.78494132"
|
||||
class="CbY_IYrtRvGd5RWKriaG_out board-outline"
|
||||
id="CbY_IYrtRvGd5RWKriaG_top_out-copy">
|
||||
<path
|
||||
style="fill:none;stroke:#dc0000;stroke-width:356.98825073"
|
||||
id="path1993"
|
||||
d="m 87734.397,-126954.93 -2395.72,1805.06 2484.08,3296.96 2874.429,-1375.11 3013.627,-995.09 3115.408,-605.29 3179.779,-205.71 3204,214.11 3137.48,619.7 3032.7,1015.14 2889.67,1400.46 2535.77,-3365.17 -2395.72,-1805.06 3994.58,-5300.96 12778.04,9629.38 -3994.59,5300.45 -2395.72,-1805.06 -2956.93,3923.8 1181.33,1676.68 1035.05,1758.81 885.48,1831.3 732.62,1894.15 576.49,1947.36 417.07,1990.93 254.35,2024.86 88.36,2049.14 -84.93,2018.255 -246.01,1995.106 -404.01,1962.729 -558.91,1921.121 -710.73,1870.283 -859.45,1810.213 -1005.09,1740.914 -1147.62,1662.383 2802.93,3719.15 2395.72,-1805.06 3994.59,5300.45 -12778.04,9629.39 -3994.58,-5300.968 2395.72,-1805.058 -2321.83,-3080.944 -2933.49,1450.199 -3084.42,1051.007 -3194.91,640.919 -3264.97,219.939 -3237.426,-218.737 -3168.786,-632.721 -3060.712,-1036.141 -2913.199,-1428.995 -2287.2,3035.474 2395.72,1805.058 -3994.59,5300.968 -12778.03,-9629.39 3994.59,-5300.45 2395.72,1805.06 2740.4,-3636.47 -1162.565,-1668.416 -1018.418,-1748.528 -871.094,-1819.233 -720.593,-1880.531 -566.916,-1932.423 -410.063,-1974.908 -250.033,-2007.986 -86.828,-2031.659 87.259,-2064.49 255.732,-2039.86 420.835,-2005.37 582.565,-1961.03 740.926,-1906.85 895.915,-1842.81 1047.535,-1768.93 1195.783,-1685.18 -2880.44,-3822.51 -2395.72,1805.06 -3994.59,-5300.45 12778.03,-9629.38 3994.59,5300.96" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
id="g2895"
|
||||
style="stroke:#b50000">
|
||||
<path
|
||||
style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:#dc0000;stroke-width:1.78494263;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
d="m 204.19141,967.18164 c -12.45601,0 -22.48438,10.02834 -22.48438,22.48438 v 23.94138 c 0,12.4561 10.02837,22.4844 22.48438,22.4844 h 59.33203 v -68.91016 z"
|
||||
transform="scale(0.26458333)"
|
||||
id="rect2881-5" />
|
||||
<path
|
||||
style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:#dc0000;stroke-width:1.78494263;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
d="m -43.476562,967.18164 v 68.91016 h 59.332031 c 12.456049,0 22.484375,-10.0283 22.484375,-22.4844 v -23.94138 c 0,-12.45604 -10.028326,-22.48438 -22.484375,-22.48438 z"
|
||||
transform="scale(0.26458333)"
|
||||
id="rect2881-5-3" />
|
||||
<path
|
||||
style="opacity:1;vector-effect:none;fill:none;fill-opacity:1;stroke:#dc0000;stroke-width:1.78494382;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
d="m -43.476562,1078.7871 v 67.7324 H 263.52344 v -67.7324 z"
|
||||
transform="scale(0.26458333)"
|
||||
id="rect2881-5-3-5" />
|
||||
</g>
|
||||
<g
|
||||
id="g2759">
|
||||
<circle
|
||||
style="display:inline;opacity:1;vector-effect:none;fill:#b50000;fill-opacity:1;stroke:none;stroke-width:0.94453275;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path2834"
|
||||
cx="-0.77764279"
|
||||
cy="249.81239"
|
||||
r="1.6000022" />
|
||||
<circle
|
||||
style="display:inline;opacity:1;vector-effect:none;fill:#b50000;fill-opacity:1;stroke:none;stroke-width:0.94453353;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path2834-3"
|
||||
cx="58.998592"
|
||||
cy="249.81239"
|
||||
r="1.6000032" />
|
||||
<circle
|
||||
style="display:inline;opacity:1;vector-effect:none;fill:#b50000;fill-opacity:1;stroke:none;stroke-width:0.94453418;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path2834-3-6"
|
||||
cx="-0.77764279"
|
||||
cy="280.22067"
|
||||
r="1.6000044" />
|
||||
<circle
|
||||
style="display:inline;opacity:1;vector-effect:none;fill:#b50000;fill-opacity:1;stroke:none;stroke-width:0.94453353;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path2834-7"
|
||||
cx="58.998592"
|
||||
cy="280.22067"
|
||||
r="1.6000032" />
|
||||
</g>
|
||||
<g
|
||||
id="g2753"
|
||||
style="fill:#ffc022">
|
||||
<circle
|
||||
style="display:inline;opacity:1;vector-effect:none;fill:#ffc022;fill-opacity:1;stroke:none;stroke-width:0.94453353;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path3250"
|
||||
cx="34.938358"
|
||||
cy="258.98401"
|
||||
r="1.0000019" />
|
||||
<circle
|
||||
style="display:inline;opacity:1;vector-effect:none;fill:#ffc022;fill-opacity:1;stroke:none;stroke-width:0.94453418;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path3250-3"
|
||||
cx="36.438358"
|
||||
cy="266.38403"
|
||||
r="1.0000026" />
|
||||
<circle
|
||||
style="display:inline;opacity:1;vector-effect:none;fill:#ffc022;fill-opacity:1;stroke:none;stroke-width:0.94453418;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path3250-6"
|
||||
cx="34.938358"
|
||||
cy="261.48401"
|
||||
r="1.0000026" />
|
||||
<circle
|
||||
style="display:inline;opacity:1;vector-effect:none;fill:#ffc022;fill-opacity:1;stroke:none;stroke-width:0.94453418;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path3250-7"
|
||||
cx="39.138363"
|
||||
cy="266.38403"
|
||||
r="1.0000026" />
|
||||
<circle
|
||||
style="display:inline;opacity:1;vector-effect:none;fill:#ffc022;fill-opacity:1;stroke:none;stroke-width:0.94453418;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path3250-5"
|
||||
cx="17.248314"
|
||||
cy="261.53403"
|
||||
r="0.75000161" />
|
||||
<circle
|
||||
style="display:inline;opacity:1;vector-effect:none;fill:#ffc022;fill-opacity:1;stroke:none;stroke-width:0.94453478;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path3250-5-3"
|
||||
cx="19.788315"
|
||||
cy="261.53403"
|
||||
r="0.75000209" />
|
||||
<circle
|
||||
style="display:inline;opacity:1;vector-effect:none;fill:#ffc022;fill-opacity:1;stroke:none;stroke-width:0.94453478;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path3250-5-5"
|
||||
cx="14.708309"
|
||||
cy="261.53403"
|
||||
r="0.75000209" />
|
||||
<circle
|
||||
style="display:inline;opacity:1;vector-effect:none;fill:#ffc022;fill-opacity:1;stroke:none;stroke-width:0.94453478;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path3250-5-6"
|
||||
cx="12.168308"
|
||||
cy="261.53403"
|
||||
r="0.75000209" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 32 KiB |
BIN
webapp/static/favicon-1024.jpg
Normal file
|
After Width: | Height: | Size: 71 KiB |
BIN
webapp/static/favicon-512.jpg
Normal file
|
After Width: | Height: | Size: 92 KiB |
BIN
webapp/static/favicon-512.png
Normal file
|
After Width: | Height: | Size: 314 KiB |
BIN
webapp/static/fonts/SourceSansPro-Black.ttf
Normal file
BIN
webapp/static/fonts/SourceSansPro-Black.woff2
Normal file
BIN
webapp/static/fonts/SourceSansPro-BlackItalic.ttf
Normal file
BIN
webapp/static/fonts/SourceSansPro-BlackItalic.woff2
Normal file
BIN
webapp/static/fonts/SourceSansPro-Bold.ttf
Normal file
BIN
webapp/static/fonts/SourceSansPro-Bold.woff2
Normal file
BIN
webapp/static/fonts/SourceSansPro-BoldItalic.ttf
Normal file
BIN
webapp/static/fonts/SourceSansPro-BoldItalic.woff2
Normal file
BIN
webapp/static/fonts/SourceSansPro-ExtraLight.ttf
Normal file
BIN
webapp/static/fonts/SourceSansPro-ExtraLight.woff2
Normal file
BIN
webapp/static/fonts/SourceSansPro-ExtraLightItalic.ttf
Normal file
BIN
webapp/static/fonts/SourceSansPro-ExtraLightItalic.woff2
Normal file
BIN
webapp/static/fonts/SourceSansPro-Italic.ttf
Normal file
BIN
webapp/static/fonts/SourceSansPro-Italic.woff2
Normal file
BIN
webapp/static/fonts/SourceSansPro-Light.ttf
Normal file
BIN
webapp/static/fonts/SourceSansPro-Light.woff2
Normal file
BIN
webapp/static/fonts/SourceSansPro-LightItalic.ttf
Normal file
BIN
webapp/static/fonts/SourceSansPro-LightItalic.woff2
Normal file
BIN
webapp/static/fonts/SourceSansPro-Regular.ttf
Normal file
BIN
webapp/static/fonts/SourceSansPro-Regular.woff2
Normal file
BIN
webapp/static/fonts/SourceSansPro-SemiBold.ttf
Normal file
BIN
webapp/static/fonts/SourceSansPro-SemiBold.woff2
Normal file
BIN
webapp/static/fonts/SourceSansPro-SemiBoldItalic.ttf
Normal file
BIN
webapp/static/fonts/SourceSansPro-SemiBoldItalic.woff2
Normal file
BIN
webapp/static/fonts/Source_Sans_Pro.zip
Normal file
125
webapp/static/footer_love.svg
Normal file
|
After Width: | Height: | Size: 74 KiB |
66
webapp/static/index.html
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Pogojig Test Fixture Generator</title>
|
||||
<link rel="stylesheet" type="text/css" href="static/style.css">
|
||||
<link rel="icon" type="image/png" href="static/favicon-512.png">
|
||||
<link rel="apple-touch-icon" href="static/favicon-512.png">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
</head>
|
||||
<body>
|
||||
<div class="layout-container">
|
||||
<div class="header">
|
||||
<img class="title" alt="Pogojig" src="static/pogojig-title.png">
|
||||
<p class="blurb">
|
||||
Pogojig is a tool to generate pogo pin test fixtures for printed circuit board manufacturing and development.
|
||||
Pogojig generates a 3d printable board holder with holes for mounting pogo pins, along with a matching KiCAD
|
||||
PCB project for a pogo pin breakout and mounting PCB.
|
||||
|
||||
You can start with either Gerber files exported from any PCB toolchain, or you can do a free-form layout using
|
||||
the <a href="static/static_template.svg">static Inkscape SVG template</a> if you
|
||||
wan to work from construction drawings, scanned or photographed PCBs etc.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="annot top">
|
||||
<div class="desc right">
|
||||
<strong>1.</strong> Convert your Gerbers to an SVG template and draw pogo pins, mounting holes and cutouts
|
||||
into the template. Don't have gerbers? Start with an
|
||||
<a href="static/pogojig_template_empty.svg">empty template</a>.
|
||||
</div>
|
||||
<div class="bubble left">
|
||||
<div>click to open</div>
|
||||
<a class="btn red" href="pogospace">Pogospace</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="workflow-images">
|
||||
<img class="gerber" alt="Gerber files" src="static/realistic_render.png" width="11em">
|
||||
<img class="arrow" alt="arrow pointing right" src="static/arrow-small.png" width="6em">
|
||||
<img class="svg" alt="SVG blueprint" src="static/cad_example.svg" width="15em">
|
||||
<img class="arrow" alt="arrow pointing right" src="static/arrow-small.png" width="6em">
|
||||
<img class="models" alt="3D models of matching plastic holder and base PCB" src="static/3d_models.png" width="14em">
|
||||
</div>
|
||||
|
||||
<div class="annot bottom">
|
||||
<div class="desc left">
|
||||
<strong>2.</strong> Upload your SVG to generate STL and OpenSCAD files for the plastic holder, and KiCAD and
|
||||
DXF files for the base PCB.
|
||||
</div>
|
||||
<div class="bubble right">
|
||||
<div>click to open</div>
|
||||
<a class="btn red">Jigerator</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<img class="title" alt="Made with love in Berlin" src="static/footer_love.svg">
|
||||
|
||||
<a href="https://github.com/jaseg/pogojig">i can haz sources</a>
|
||||
<a href="https://blog.jaseg.net/imprint">imprint</a>
|
||||
|
||||
<div class="copyright">© 2019 jaseg</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
BIN
webapp/static/pogojig-title.png
Normal file
|
After Width: | Height: | Size: 212 KiB |
BIN
webapp/static/realistic_render.png
Normal file
|
After Width: | Height: | Size: 269 KiB |
176
webapp/static/style.css
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: normal;
|
||||
font-weight: 300;
|
||||
font-display: swap;
|
||||
src: local('Source Sans Pro Light'), local('SourceSansPro-Light'), url(fonts/SourceSansPro-Light.woff2) format('woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: local('Source Sans Pro Regular'), local('SourceSansPro-Regular'), url(fonts/SourceSansPro-Regular.woff2) format('woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Source Sans Pro';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: local('Source Sans Pro Bold'), local('SourceSansPro-Bold'), url(fonts/SourceSansPro-Bold.woff2) format('woff2');
|
||||
}
|
||||
|
||||
html {
|
||||
background: #2d2829;
|
||||
background-image: url(bg.svg);
|
||||
color: #ffffff;
|
||||
font-family: 'Source Sans Pro';
|
||||
font-size: 14pt;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
a:active, a:hover, a:visited, a:link {
|
||||
color: #fff;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin: 0 0 0 0;
|
||||
padding: 3mm 3mm 3mm 3mm;
|
||||
}
|
||||
|
||||
.layout-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 38em;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.header > img.title {
|
||||
width: 100%;
|
||||
padding-top: 50px;
|
||||
padding-bottom: 50px;
|
||||
}
|
||||
|
||||
.header > .blurb {
|
||||
text-align: justify;
|
||||
hyphens: auto;
|
||||
padding-bottom: 50px;
|
||||
}
|
||||
|
||||
|
||||
.annot {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-content: start;
|
||||
}
|
||||
|
||||
.annot.top {
|
||||
align-items: end;
|
||||
flex-direction: row-reverse;
|
||||
align-content: start;
|
||||
}
|
||||
|
||||
.annot.bottom {
|
||||
align-items: start;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.annot.top > .desc {
|
||||
padding-left: 1em;
|
||||
padding-bottom: 2em;
|
||||
max-width: 60%;
|
||||
}
|
||||
|
||||
.annot.bottom > .desc {
|
||||
padding-right: 1em;
|
||||
padding-top: 4.5em;
|
||||
max-width: 60%;
|
||||
}
|
||||
|
||||
.annot > .bubble {
|
||||
max-width: 30%;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
text-align: center;
|
||||
font-size: .7rem;
|
||||
}
|
||||
|
||||
.annot.top > .bubble {
|
||||
background-image: url(bubble1-small.png);
|
||||
padding: 2em 2em 5em 2em;
|
||||
}
|
||||
|
||||
.annot.bottom > .bubble {
|
||||
background-image: url(bubble2-small.png);
|
||||
padding: 7em 2em 5em 2em;
|
||||
}
|
||||
|
||||
a.btn {
|
||||
display: block;
|
||||
font-weight: 700;
|
||||
background-color: #b50000;
|
||||
border-radius: 0.5em;
|
||||
padding: .4em 1em .4em 1em;
|
||||
margin-top: .2em;
|
||||
}
|
||||
|
||||
a.btn:active, a.btn:hover, a.btn:visited, a.btn:link {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
|
||||
.workflow-images {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.workflow-images > .gerber {
|
||||
width: 220px;
|
||||
}
|
||||
|
||||
.workflow-images > .svg {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.workflow-images > .models {
|
||||
width: 280px;
|
||||
}
|
||||
|
||||
.workflow-images > .arrow {
|
||||
width: 120px
|
||||
}
|
||||
|
||||
|
||||
.footer {
|
||||
opacity: 0.5;
|
||||
text-align: center;
|
||||
padding-top: 5em;
|
||||
}
|
||||
|
||||
.footer > img {
|
||||
padding-bottom: .5em;
|
||||
}
|
||||
|
||||
.footer > a {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.footer > .copyright {
|
||||
padding-top: 1em;
|
||||
}
|
||||
|
||||
1596
webapp/static/workflow.svg
Normal file
|
After Width: | Height: | Size: 806 KiB |
12004
webapp/static/workflow.svg.2019_09_27_14_18_13.0.svg
Normal file
|
After Width: | Height: | Size: 821 KiB |
1582
webapp/static/workflow2.svg
Normal file
|
After Width: | Height: | Size: 805 KiB |
1596
webapp/static/workflow3.svg
Normal file
|
After Width: | Height: | Size: 806 KiB |
61
webapp/templates/jigerator.html
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Pogojig SVG Upload</title>
|
||||
<link rel="stylesheet" type="text/css" href="{{url_for('static', filename='style.css')}}">
|
||||
<link rel="icon" type="image/png" href="{{url_for('static', filename='favicon-512.png')}}">
|
||||
<link rel="apple-touch-icon" href="{{url_for('static', filename='favicon-512.png')}}">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
</head>
|
||||
<body>
|
||||
<div class="layout-container">
|
||||
<div class="header">
|
||||
<img class="title" alt="Pogojig" src="{{url_for('static', filename='pogojig-title.png')}}">
|
||||
</div>
|
||||
|
||||
{% with messages = get_flashed_messages(with_categories=True) %}
|
||||
{% if messages %}
|
||||
<div class="flashes">
|
||||
{% for category, message in messages %}
|
||||
<div class="flash flash-{{category}}">{{ message }}</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
<form id="reset-form" method="POST" action="{{url_for('session_reset')}}" class="reset-form">{{reset_form.csrf_token}}</form>
|
||||
|
||||
<div class="controls">
|
||||
<form id="svg-upload-form" method="POST" action="{{url_for('upload_svg')}}" enctype="multipart/form-data">
|
||||
{{svg_form.csrf_token}}
|
||||
</form>
|
||||
<div class="form-controls">
|
||||
<div class="upload-label">Upload completed SVG file:</div>
|
||||
<input class='upload-button' form="gerber-upload-form" name="upload_file" size="20" type="file">
|
||||
</div>
|
||||
<div class="submit-buttons">
|
||||
<input class='reset-button' form="reset-form" type="submit" value="Start over">
|
||||
<input class='submit-button' form="svg-upload-form" type="submit" value="Submit">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if 'render_job' in session or has_renders %}
|
||||
<div class="render_output">
|
||||
{% if 'render_job' in session %}
|
||||
<div class="loading-message">
|
||||
<div class="lds-ring"><div></div><div></div><div></div><div></div></div>
|
||||
<div><strong>Processing...</strong></div>
|
||||
<div>(this may take several minutes!)</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="preview-images">
|
||||
<a href="{{url_for('render_download')}}" class="render" style="background-image:url('{{url_for('render_preview')}}');">
|
||||
<div class="overlay">Download model and PCB</div>
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %} {# render job #}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||