Working on the design

This commit is contained in:
jaseg 2019-03-29 22:09:16 +09:00 committed by jaseg
parent 84148e368d
commit 23d392c2f7
6 changed files with 388 additions and 89 deletions

View file

@ -65,7 +65,7 @@ http {
include /etc/nginx/default.d/*.conf;
location ^~ /static/ {
root /var/lib/gerboweb/static;
root /var/lib/gerboweb;
}
location / {

View file

@ -152,12 +152,6 @@
name: uwsgi-app@gerboweb.socket
enabled: yes
- name: Enable and launch uwsgi systemd service
systemd:
name: uwsgi-app@gerboweb.service
enabled: yes
state: restarted
- name: Enable and launch job processor
systemd:
name: gerboweb-job-processor.service

View file

@ -1,5 +1,4 @@
[uwsgi]
chmod-socket = 660
master = True
cheap = True
idle = 600

View file

@ -9,7 +9,7 @@ from os import path
import os
import sqlite3
from flask import Flask, url_for, redirect, session, make_response, render_template, request, send_file, abort
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
@ -26,7 +26,7 @@ class UploadForm(FlaskForm):
class OverlayForm(UploadForm):
upload_file = FileField(validators=[FileRequired()])
side = RadioField('Side', choices=[('top', 'Top'), ('bottom', 'Bottom')])
side = RadioField('Side', choices=[('top', 'Top'), ('bottom', 'Bottom')], default=lambda: session.get('last_download'))
class ResetForm(FlaskForm):
pass
@ -56,6 +56,7 @@ def require_session_id(fun):
@app.route('/')
@require_session_id
def index():
flash(f'Gerber file successfully uploaded.', 'success')
forms = {
'gerber_form': UploadForm(),
'overlay_form': OverlayForm(),
@ -108,6 +109,7 @@ def upload(namespace):
session_id=session['session_id'],
side=upload_form.side.data)
flash(f'{"Gerber" if namespace == "gerber" else "Overlay"} file successfully uploaded.', 'success')
return redirect(url_for('index'))
@app.route('/render/preview/<side>')
@ -120,10 +122,12 @@ def render_preview(side):
def render_download(side):
if not side in ('top', 'bottom'):
return abort(400, 'side must be either "top" or "bottom"')
session['last_download'] = side
return send_file(tempfile_path(f'render_{side}.png'),
mimetype='image/png',
as_attachment=True,
attachment_filename=f'{path.splitext(session["filename"])[0]}_render.png')
attachment_filename=f'{path.splitext(session["filename"])[0]}_render_{side}.png')
@app.route('/output/download')
def output_download():

241
gerboweb/static/style.css Normal file
View file

@ -0,0 +1,241 @@
:root {
--c-blue1: #19aeff;
--c-blue2: #0084c8;
--c-blue3: #005c94;
--c-red1: #ff4141;
--c-red2: #dc0000;
--c-red3: #b50000;
--c-orange1: #ffff3e;
--c-orange2: #ff9900;
--c-orange3: #ff6600;
--c-brown1: #ffc022;
--c-brown2: #b88100;
--c-brown3: #804d00;
--c-green1: #ccff42;
--c-green2: #9ade00;
--c-green3: #009100;
--c-purple1: #f1caff;
--c-purple2: #d76cff;
--c-purple3: #ba00ff;
--c-metallic1: #bdcdd4;
--c-metallic2: #9eabb0;
--c-metallic3: #364e59;
--c-metallic4: #0e232e;
--c-grey1: #ffffff;
--c-grey2: #cccccc;
--c-grey3: #999999;
--c-grey4: #666666;
--c-grey5: #2d2d2d;
--cg1: #003018;
--cg2: #006130;
--cg3: #00964a;
--cg4: #00d167;
--cg5: #4cffa4;
--cg6: #b7ffda;
--cg7: #e1fff0;
}
body {
font-family: 'Helvetica', 'Arial', sans-serif;
color: var(--c-metallic4);
display: flex;
flex-direction: row;
justify-content: center;
margin: 0;
background-color: hsl(10 10% 97%);
}
.layout-container {
flex-basis: 55em;
flex-shrink: 1;
flex-grow: 0;
padding: 3em;
background-color: white;
}
div.flash-success {
background-color: var(--c-green1);
color: hsl(80 20% 20%);
text-shadow: 0 0 2px var(--c-green1);
border-radius: 5px;
margin: 1em;
padding-left: 3em;
padding-right: 3em;
padding-top: 2em;
padding-bottom: 2em;
}
div.flash-success::before {
content: "Success!";
display: block;
font-weight: bold;
font-size: 16pt;
margin-right: 1em;
margin-bottom: 0.5em;
}
div.desc {
margin-top: 5em;
margin-bottom: 7em;
}
div.loading-message {
text-align: center;
margin-top: 2em;
}
.steps {
display: flex;
flex-direction: column;
}
.step {
display: flex;
flex-direction: row;
align-items: flex-start;
justify-content: center;
flex-wrap: wrap;
width: 100%;
padding-top: 20px;
}
.step > .description {
flex-basis: 20em;
flex-shrink: 0;
flex-grow: 0;
margin-left: 20px;
overflow-wrap: break-word;
word-wrap: break-word;
hyphens: auto;
text-align: justify;
}
.step > .description > h2 {
text-align: right;
margin-top: 0
}
.step > .controls {
flex-grow: 1;
flex-shrink: 1;
display: flex;
flex-direction: column;
align-items: stretch;
margin-right: 20px;
margin-left: 20px;
padding: 1em;
background-color: hsl(210 40% 97%);
border-radius: 5px;
}
input.reset-button {
background-color: var(--c-red1);
color: var(--c-grey1);
text-shadow: 0 0 2px var(--c-red3);
border: 0;
border-radius: 5px;
padding: 0.5em 1em 0.5em 1em;
}
input.submit-button {
background-color: var(--c-green2);
color: hsl(80 20% 20%);
text-shadow: 0 0 2px var(--c-green1);
font-weight: bold;
margin-left: 1em;
border: 0;
border-radius: 5px;
padding: 0.5em 1em 0.5em 1em;
}
.controls > .form-controls {
margin-bottom: 1em;
}
.controls > .submit-buttons {
margin-top: 1em;
text-align: right;
}
.controls > .download-controls {
padding: 1em;
margin-bottom: 1em;
display: flex;
flex-direction: column;
align-items: center;
}
a.output-download:link, a.output-download:hover, a.output-download:visited, a.output-download:active {
font-size: 30pt;
font-weight: bold;
color: var(--c-metallic4);
text-shadow: 0.5px 0.5px 0.5px var(--c-metallic2);
}
.preview-images {
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-items: flex-start;
justify-content: space-around;
}
.preview {
width: 200px;
height: 200px;
border-radius: 5px;
display: flex;
justify-content: center;
align-items: center;
}
a.overlay:link, a.overlay:hover, a.overlay:visited, a.overlay:active {
text-align: center;
font-size: 30pt;
font-weight: bold;
color: var(--c-metallic4);
text-shadow: 0.5px 0.5px 0.5px var(--c-metallic2);
}
/* Spinner from https://loading.io/css/ */
.lds-ring {
display: inline-block;
position: relative;
width: 64px;
height: 64px;
}
.lds-ring div {
box-sizing: border-box;
display: block;
position: absolute;
width: 51px;
height: 51px;
margin: 6px;
border: 6px solid var(--c-metallic4);
border-radius: 50%;
animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
border-color: var(--c-metallic4) transparent transparent transparent;
}
.lds-ring div:nth-child(1) {
animation-delay: -0.45s;
}
.lds-ring div:nth-child(2) {
animation-delay: -0.3s;
}
.lds-ring div:nth-child(3) {
animation-delay: -0.15s;
}
@keyframes lds-ring {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}

View file

@ -1,86 +1,147 @@
<!DOCTYPE html>
<html>
<head>
<title>Gerbolyze Raster image to PCB renderer</title>
</head>
<body>
<div class="desc">
<h1>Raster image to PCB converter</h1>
<p>
Gerbolyze is a tool for rendering black and white raster (PNG) images directly onto gerber layers. You can
use this to put art on a PCB's silkscreen, solder mask or copper layers. The input is a black-and-white PNG
image that is vectorized and rendered into an existing gerber file. Gerbolyze works with gerber files
produced with any EDA toolchain and has been tested to work with both Altium and KiCAD.
</p>
</div>
<html lang="en">
<head>
<title>Gerbolyze Raster image to PCB renderer</title>
<link rel="stylesheet" type="text/css" href="{{url_for('static', filename='style.css')}}">
</head>
<body>
<div class="layout-container">
<div class="desc">
<h1>Raster image to PCB converter</h1>
<p>
Gerbolyze is a tool for rendering black and white raster (PNG) images directly onto gerber layers. You can
use this to put art on a PCB's silkscreen, solder mask or copper layers. The input is a black-and-white PNG
image that is vectorized and rendered into an existing gerber file. Gerbolyze works with gerber files
produced with any EDA toolchain and has been tested to work with both Altium and KiCAD.
</p>
</div>
<div class="step" id="step1">
<h2>Step 1: Upload zipped gerber files</h2>
<p>
First, upload a zip file containing all your gerber files. The default file names used by KiCAD, Eagle
and Altium are supported.
</p>
{% 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 method="POST" action="{{url_for('upload', namespace='gerber')}}" enctype="multipart/form-data">
{{gerber_form.csrf_token}}
{{gerber_form.upload_file.label}} {{gerber_form.upload_file(size=20)}}
<input type="submit" value="Submit">
</form>
</div>
<form id="reset-form" method="POST" action="{{url_for('session_reset')}}" class="reset-form">{{reset_form.csrf_token}}</form>
{% if 'render_job' in session or has_renders %}
<div class="step" id="step2">
<h2>Step 2: Download the target side's preview image</h2>
<p>
Second, download either the top or bottom preview image and use it to align and scale your own artwork
in an image editing program such as Gimp. Then upload your overlay image below.
<div class="steps">
<div class="step" id="step1">
<div class="description">
<h2>Step 1: Upload zipped gerber files</h2>
<p>
First, upload a zip file containing all your gerber files. The default file names used by KiCAD, Eagle
and Altium are supported.
</p>
</div>
Note that you will have to convert grayscale images into binary images yourself. Gerbolyze can't do this
for you since there are lots of variables involved. Our <a href="{{url_for('static',
filename='image_processing_guide.html')}}">Guideline on image processing</a> gives an overview on
<i>one</i> way to produce agreeable binary images from grayscale source material.
</p>
{% if 'render_job' in session %}
<strong>Processing...</strong> (this may take several minutes!)
{% else %}
<img src="{{url_for('render_preview', side='top')}}"> <a href="{{url_for('render_download', side='top')}}">Download</a>
<img src="{{url_for('render_preview', side='bottom')}}"> <a href="{{url_for('render_download', side='bottom')}}">Download</a>
{% endif %}
<form method="POST" action="{{url_for('session_reset')}}">
{{reset_form.csrf_token}}
<input type="submit" value="Start over">
</form>
</div>
<div class="controls">
<form id="gerber-upload-form" method="POST" action="{{url_for('upload', namespace='gerber')}}" enctype="multipart/form-data">
{{gerber_form.csrf_token}}
</form>
<div class="form-controls">
<div class="upload-label">Upload Gerber 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="gerber-upload-form" type="submit" value="Submit">
</div>
</div>
</div>
<div class="step" id="step3">
<h2>Step 3: Upload overlay image</h2>
<p>
Now, upload your binary overlay image as a PNG and let gerbolyze render it onto the target layer. The PNG
file should be a black and white binary file with details generally above about 10px size. <b>Antialiased
edges are supported.</b>
</p>
<form method="POST" action="{{url_for('upload', namespace='overlay')}}" enctype="multipart/form-data">
{{overlay_form.csrf_token}}
{{overlay_form.upload_file.label}} {{overlay_form.upload_file(size=20)}}
{{overlay_form.side.label}} {{overlay_form.side()}}
<input type="submit" value="Submit">
</form>
</div>
{% if 'render_job' in session or has_renders %}
<div class="step" id="step2">
<div class="description">
<h2>Step 2: Download the target side's preview image</h2>
<p>
Second, download either the top or bottom preview image and use it to align and scale your own artwork
in an image editing program such as Gimp. Then upload your overlay image below.
{% if 'vector_job' in session or has_output %}
<div class="step" id="step4">
<h2> Step 4: Download the processed gerber files</h2>
{% if 'vector_job' in session %}
<strong>Processing...</strong> (this may take several minutes!)
{% else %}
<a href="{{url_for('output_download')}}">Download</a>
{% endif %}
<form method="POST" action="{{url_for('session_reset')}}">
{{reset_form.csrf_token}}
<input type="submit" value="Start over">
</form>
</div>
{% endif %} {# vector job #}
{% endif %} {# render job #}
</body>
Note that you will have to convert grayscale images into binary images yourself. Gerbolyze can't do this
for you since there are lots of variables involved. Our <a href="https://github.com/jaseg/gerbolyze/blob/master/README.rst#image-preprocessing-guide">Guideline on image processing</a> gives an overview on
<i>one</i> way to produce agreeable binary images from grayscale source material.
</p>
</div>
<div class="controls">
{% if 'render_job' in session %}
<strong>Processing...</strong> (this may take several minutes!)
{% else %}
<div class="preview-images">
<div class="preview preview-top" style="background-image:url('{{url_for('render_preview', side='top')}}');">
<a class="overlay" href="{{url_for('render_download', side='top')}}" onclick="document.querySelector('#side-0').checked=true">Download<br/>top layer</a>
</div>
<div class="preview preview-bottom" style="background-image:url('{{url_for('render_preview', side='bottom')}}');">
<a class="overlay" href="{{url_for('render_download', side='bottom')}}" onclick="document.querySelector('#side-1').checked=true">Download<br/>bottom layer</a>
</div>
</div>
{% endif %}
<div class="submit-buttons">
<input class='reset-button' form="reset-form" type="submit" value="Start over">
</div>
</div>
</div>
<div class="step" id="step3">
<div class="description">
<h2>Step 3: Upload overlay image</h2>
<p>
Now, upload your binary overlay image as a PNG and let gerbolyze render it onto the target layer. The PNG
file should be a black and white binary file with details generally above about 10px size. <b>Antialiased
edges are supported.</b>
</p>
</div>
<div class="controls">
<form id="overlay-upload-form" method="POST" action="{{url_for('upload', namespace='overlay')}}" enctype="multipart/form-data">
{{overlay_form.csrf_token}}
</form>
<div class="form-controls">
<div class="form-label upload-label">Upload Overlay PNG file:</div>
<input class='upload-button' form="overlay-upload-form" name="upload_file" size="20" type="file">
</div>
<div class="form-controls">
<div class="form-label target-label">Target layer:</div>
<input form="overlay-upload-form" name="side" id="side-0" type="radio" value="top">
<label for="side-0">Top</label>
<input form="overlay-upload-form" name="side" id="side-1" type="radio" value="top">
<label for="side-1">Bottom</label>
</div>
<div class="submit-buttons">
<input class='reset-button' form="reset-form" type="submit" value="Start over">
<input class='submit-button' form="overlay-upload-form" type="submit" value="Submit">
</div>
</div>
</div>
{% if 'vector_job' in session or has_output %}
<div class="step" id="step4">
<div class="description">
<h2> Step 4: Download the processed gerber files</h2>
</div>
<div class="controls">
{# if 'vector_job' in session FIXME #}
{% if True %}
<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='download-controls'>
<a class='output-download' href="{{url_for('output_download')}}">Click to download</a>
</div>
{% endif %}
<div class="submit-buttons">
<input class='reset-button' form="reset-form" type="submit" value="Start over">
</div>
</div>
</div>
{% endif %} {# vector job #}
{% endif %} {# render job #}
</div>
</div>
</body>
</html>