Working on the design
This commit is contained in:
parent
84148e368d
commit
23d392c2f7
6 changed files with 388 additions and 89 deletions
|
|
@ -65,7 +65,7 @@ http {
|
|||
include /etc/nginx/default.d/*.conf;
|
||||
|
||||
location ^~ /static/ {
|
||||
root /var/lib/gerboweb/static;
|
||||
root /var/lib/gerboweb;
|
||||
}
|
||||
|
||||
location / {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
[uwsgi]
|
||||
chmod-socket = 660
|
||||
master = True
|
||||
cheap = True
|
||||
idle = 600
|
||||
|
|
|
|||
|
|
@ -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
241
gerboweb/static/style.css
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue