aboutsummaryrefslogtreecommitdiff
path: root/gerboweb
diff options
context:
space:
mode:
Diffstat (limited to 'gerboweb')
-rwxr-xr-xgerboweb/deploy/render.sh4
-rw-r--r--gerboweb/gerboweb.py69
-rw-r--r--gerboweb/static/bg.jpgbin0 -> 331765 bytes
-rw-r--r--gerboweb/static/bg10.jpgbin0 -> 177533 bytes
-rw-r--r--gerboweb/static/sample1.jpgbin0 -> 299841 bytes
-rw-r--r--gerboweb/static/sample2.jpgbin0 -> 251440 bytes
-rw-r--r--gerboweb/static/sample3.jpgbin0 -> 171160 bytes
-rw-r--r--gerboweb/static/style.css149
-rw-r--r--gerboweb/templates/index.html63
9 files changed, 207 insertions, 78 deletions
diff --git a/gerboweb/deploy/render.sh b/gerboweb/deploy/render.sh
index c3920de..eefe7f0 100755
--- a/gerboweb/deploy/render.sh
+++ b/gerboweb/deploy/render.sh
@@ -13,8 +13,8 @@ rm -f /mnt/render_top.png /mnt/render_bottom.png /mnt/render_top.small.png /mnt/
date; echo 'Rendering bottom layer'
gerbolyze render top /tmp/gerber /mnt/render_top.png
date; echo 'Scaling down'
-convert /mnt/render_top.png -resize 500x500 /mnt/render_top.small.png
+convert /mnt/render_top.png -resize 500x500 -negate -brightness-contrast 30x30 -colorspace gray /mnt/render_top.small.png
date; echo 'Rendering top layer'
gerbolyze render bottom /tmp/gerber /mnt/render_bottom.png
date; echo 'Scaling down'
-convert /mnt/render_bottom.png -resize 500x500 /mnt/render_bottom.small.png"
+convert /mnt/render_bottom.png -resize 500x500 -negate -brightness-contrast 30x30 -colorspace gray /mnt/render_bottom.small.png"
diff --git a/gerboweb/gerboweb.py b/gerboweb/gerboweb.py
index 1f8d884..a276d74 100644
--- a/gerboweb/gerboweb.py
+++ b/gerboweb/gerboweb.py
@@ -26,7 +26,8 @@ class UploadForm(FlaskForm):
class OverlayForm(UploadForm):
upload_file = FileField(validators=[FileRequired()])
- side = RadioField('Side', choices=[('top', 'Top'), ('bottom', 'Bottom')], default=lambda: session.get('last_download'))
+ side = RadioField('Side', choices=[('top', 'Top'), ('bottom', 'Bottom')],
+ default=lambda: session.get('side_selected', session.get('last_download')))
class ResetForm(FlaskForm):
pass
@@ -56,7 +57,6 @@ 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(),
@@ -79,37 +79,49 @@ def index():
# * The uploaded files are deleted after a while by systemd tmpfiles.d
# TODO: validate this setting applies *after* gzip transport compression
-@app.route('/upload/<namespace>', methods=['POST'])
+def vectorize():
+ if 'vector_job' in session:
+ job_queue.drop(session['vector_job'])
+ session['vector_job'] = job_queue.enqueue('vector',
+ client=request.remote_addr,
+ session_id=session['session_id'],
+ side=session['side_selected'])
+
+def render():
+ if 'render_job' in session:
+ job_queue.drop(session['render_job'])
+ session['render_job'] = job_queue.enqueue('render',
+ session_id=session['session_id'],
+ client=request.remote_addr)
+
+@app.route('/upload/gerber', methods=['POST'])
@require_session_id
-def upload(namespace):
- if namespace not in ('gerber', 'overlay'):
- return abort(400, 'Invalid upload type')
+def upload_gerber():
+ upload_form = UploadForm()
+ if upload_form.validate_on_submit():
+ f = upload_form.upload_file.data
+ f.save(tempfile_path('gerber.zip'))
+ session['filename'] = secure_filename(f.filename) # Cache filename for later download
- upload_form = UploadForm() if namespace == 'gerber' else OverlayForm()
+ render()
+ if path.isfile(tempfile_path('overlay.png')): # Re-vectorize when gerbers change
+ vectorize()
+
+ flash(f'Gerber file successfully uploaded.', 'success')
+ return redirect(url_for('index'))
+
+@app.route('/upload/overlay', methods=['POST'])
+@require_session_id
+def upload_overlay():
+ upload_form = OverlayForm()
if upload_form.validate_on_submit():
f = upload_form.upload_file.data
+ f.save(tempfile_path('overlay.png'))
+ session['side_selected'] = upload_form.side.data
+
+ vectorize()
- if namespace == 'gerber':
- f.save(tempfile_path('gerber.zip'))
- session['filename'] = secure_filename(f.filename) # Cache filename for later download
- if 'render_job' in session:
- job_queue.drop(session['render_job'])
- session['render_job'] = job_queue.enqueue('render',
- session_id=session['session_id'],
- client=request.remote_addr)
- else: # namespace == 'vector'
- f.save(tempfile_path('overlay.png'))
-
- # Re-vectorize if either file has changed
- if path.isfile(tempfile_path('gerber.zip')) and path.isfile(tempfile_path('overlay.png')):
- if 'vector_job' in session:
- job_queue.drop(session['vector_job'])
- session['vector_job'] = job_queue.enqueue('vector',
- client=request.remote_addr,
- session_id=session['session_id'],
- side=upload_form.side.data)
-
- flash(f'{"Gerber" if namespace == "gerber" else "Overlay"} file successfully uploaded.', 'success')
+ flash(f'Overlay file successfully uploaded.', 'success')
return redirect(url_for('index'))
@app.route('/render/preview/<side>')
@@ -144,5 +156,6 @@ def session_reset():
if 'vector_job' in session:
session['vector_job'].abort()
session.clear()
+ flash('Session reset', 'success');
return redirect(url_for('index'))
diff --git a/gerboweb/static/bg.jpg b/gerboweb/static/bg.jpg
new file mode 100644
index 0000000..94856fc
--- /dev/null
+++ b/gerboweb/static/bg.jpg
Binary files differ
diff --git a/gerboweb/static/bg10.jpg b/gerboweb/static/bg10.jpg
new file mode 100644
index 0000000..9d14fd3
--- /dev/null
+++ b/gerboweb/static/bg10.jpg
Binary files differ
diff --git a/gerboweb/static/sample1.jpg b/gerboweb/static/sample1.jpg
new file mode 100644
index 0000000..948da6f
--- /dev/null
+++ b/gerboweb/static/sample1.jpg
Binary files differ
diff --git a/gerboweb/static/sample2.jpg b/gerboweb/static/sample2.jpg
new file mode 100644
index 0000000..ef47bd4
--- /dev/null
+++ b/gerboweb/static/sample2.jpg
Binary files differ
diff --git a/gerboweb/static/sample3.jpg b/gerboweb/static/sample3.jpg
new file mode 100644
index 0000000..780c080
--- /dev/null
+++ b/gerboweb/static/sample3.jpg
Binary files differ
diff --git a/gerboweb/static/style.css b/gerboweb/static/style.css
index 975c7f2..ede89d4 100644
--- a/gerboweb/static/style.css
+++ b/gerboweb/static/style.css
@@ -35,30 +35,61 @@
--cg5: #4cffa4;
--cg6: #b7ffda;
--cg7: #e1fff0;
+
+ --cr1: #300900;
+ --cr2: #611200;
+ --cr3: #961c00;
+ --cr4: #d12700;
+ --cr5: #ff6e4c;
+ --cr6: #ffc5b7;
+ --cr7: #ffe7e1;
+
+ --cb1: #001b30;
+ --cb2: #003761;
+ --cb3: #005596;
+ --cb4: #0076d1;
+ --cb5: #4cb1ff;
+ --cb6: #b7e0ff;
+ --cb7: #e1f2ff;
+ --cb8: #f5fbff;
}
body {
font-family: 'Helvetica', 'Arial', sans-serif;
- color: var(--c-metallic4);
+ color: var(--cb1);
display: flex;
flex-direction: row;
justify-content: center;
margin: 0;
- background-color: hsl(10 10% 97%);
+ background-color: var(--cb8);
}
.layout-container {
flex-basis: 55em;
flex-shrink: 1;
flex-grow: 0;
- padding: 3em;
+ padding: 45px;
background-color: white;
}
+div.header {
+ background-image: url("/static/bg10.jpg");
+ background-position: center;
+ background-size: cover;
+ background-repeat: no-repeat;
+ display: flex;
+ margin-left: -45px;
+ margin-right: -45px;
+ margin-bottom: 3em;
+ padding-left: 3em;
+ padding-right: 3em;
+ text-shadow: 1px 1px 1px black;
+}
+
div.flash-success {
- background-color: var(--c-green1);
- color: hsl(80 20% 20%);
- text-shadow: 0 0 2px var(--c-green1);
+ background-color: var(--cg6);
+ color: var(--cg1);
+ text-shadow: 0 0 2px var(--cg7);
border-radius: 5px;
margin: 1em;
padding-left: 3em;
@@ -79,6 +110,13 @@ div.flash-success::before {
div.desc {
margin-top: 5em;
margin-bottom: 7em;
+
+ overflow-wrap: break-word;
+ word-wrap: break-word;
+ hyphens: auto;
+ text-align: justify;
+
+ color: white;
}
div.loading-message {
@@ -89,6 +127,7 @@ div.loading-message {
.steps {
display: flex;
flex-direction: column;
+ counter-reset: step;
}
.step {
@@ -99,6 +138,29 @@ div.loading-message {
flex-wrap: wrap;
width: 100%;
padding-top: 20px;
+ position: relative;
+ margin-bottom: 1em;
+ margin-top: 2em;
+}
+
+.step > .description::before {
+ counter-increment: step;
+ content: counter(step);
+
+ font-weight: 700;
+ font-size: 30px;
+ text-align: center;
+ border-radius: 50%;
+ background-color: var(--cg5);
+
+ display: block;
+ position: absolute;
+ top: 15px;
+ left: 0;
+ width: 60px;
+
+ line-height: 50px;
+ padding-top: 10px;
}
.step > .description {
@@ -115,7 +177,9 @@ div.loading-message {
.step > .description > h2 {
text-align: right;
- margin-top: 0
+ margin-top: 0;
+ padding-left: 60px;
+ height: 60px;
}
.step > .controls {
@@ -124,28 +188,28 @@ div.loading-message {
display: flex;
flex-direction: column;
align-items: stretch;
- margin-right: 20px;
- margin-left: 20px;
+ margin-right: 1em;
+ margin-left: 1em;
padding: 1em;
- background-color: hsl(210 40% 97%);
+ background-color: var(--cb8);
border-radius: 5px;
}
input.reset-button {
- background-color: var(--c-red1);
- color: var(--c-grey1);
- text-shadow: 0 0 2px var(--c-red3);
+ background-color: var(--cr4);
+ color: white;
+ text-shadow: 0 0 2px var(--cr1);
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);
+ background-color: var(--cg4);
+ color: var(--cg1);
+ text-shadow: 0 0 2px var(--cg7);
font-weight: bold;
margin-left: 1em;
border: 0;
@@ -173,8 +237,8 @@ input.submit-button {
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);
+ color: var(--cb1);
+ text-shadow: 0.5px 0.5px 0.5px var(--cb6);
}
.preview-images {
@@ -185,21 +249,56 @@ a.output-download:link, a.output-download:hover, a.output-download:visited, a.ou
justify-content: space-around;
}
-.preview {
+a.preview:link, a.preview:hover, a.preview:visited, a.preview:active {
+ text-decoration: none;
width: 200px;
height: 200px;
border-radius: 5px;
+ margin: 1em;
display: flex;
justify-content: center;
align-items: center;
+ background-color: var(--cb3);
+ background-blend-mode: multiply;
+ background-size: contain;
+ background-repeat: no-repeat;
+ background-position: 50% 50%;
+ box-shadow: 1px 1px 5px 1px #001b304d;
}
-a.overlay:link, a.overlay:hover, a.overlay:visited, a.overlay:active {
+.overlay {
text-align: center;
- font-size: 30pt;
+ font-size: 50pt;
font-weight: bold;
- color: var(--c-metallic4);
- text-shadow: 0.5px 0.5px 0.5px var(--c-metallic2);
+ color: var(--cg4);
+ mix-blend-mode: screen;
+}
+
+.sample-images {
+ text-align: center;
+}
+
+.sample-images > h1 {
+ color: white;
+ padding-top: 5px;
+ line-height: 70px;
+ /* background-image: linear-gradient(to top right, var(--cg5), var(--cg6)); */
+
+ background-image: url("/static/bg10.jpg");
+ background-position: center;
+ background-size: cover;
+ background-repeat: no-repeat;
+
+ margin-left: -45px;
+ margin-right: -45px;
+ margin-top: 3em;
+ text-shadow: 1px 1px 1px black;
+}
+
+.sample-images > img {
+ width: 300px;
+ height: 300px;
+ margin: 1em;
}
/* Spinner from https://loading.io/css/ */
@@ -216,10 +315,10 @@ a.overlay:link, a.overlay:hover, a.overlay:visited, a.overlay:active {
width: 51px;
height: 51px;
margin: 6px;
- border: 6px solid var(--c-metallic4);
+ border: 6px solid var(--cb1);
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;
+ border-color: var(--cb1) transparent transparent transparent;
}
.lds-ring div:nth-child(1) {
animation-delay: -0.45s;
diff --git a/gerboweb/templates/index.html b/gerboweb/templates/index.html
index 3e4c255..eeece65 100644
--- a/gerboweb/templates/index.html
+++ b/gerboweb/templates/index.html
@@ -6,14 +6,16 @@
</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 class="header">
+ <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>
{% with messages = get_flashed_messages(with_categories=True) %}
@@ -31,7 +33,7 @@
<div class="steps">
<div class="step" id="step1">
<div class="description">
- <h2>Step 1: Upload zipped gerber files</h2>
+ <h2>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.
@@ -39,7 +41,7 @@
</div>
<div class="controls">
- <form id="gerber-upload-form" method="POST" action="{{url_for('upload', namespace='gerber')}}" enctype="multipart/form-data">
+ <form id="gerber-upload-form" method="POST" action="{{url_for('upload_gerber')}}" enctype="multipart/form-data">
{{gerber_form.csrf_token}}
</form>
<div class="form-controls">
@@ -56,7 +58,7 @@
{% 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>
+ <h2>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.
@@ -68,15 +70,19 @@
</div>
<div class="controls">
{% if 'render_job' in session %}
- <strong>Processing...</strong> (this may take several minutes!)
+ <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">
- <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>
+ <a href="{{url_for('render_download', side='top')}}" onclick="document.querySelector('#side-0').checked=true" class="preview preview-top" style="background-image:url('{{url_for('render_preview', side='top')}}');">
+ <div class="overlay">top</div>
+ </a>
+ <a href="{{url_for('render_download', side='bottom')}}" onclick="document.querySelector('#side-1').checked=true" class="preview preview-bottom" style="background-image:url('{{url_for('render_preview', side='bottom')}}');">
+ <div class="overlay">bot<br/>tom</div>
+ </a>
</div>
{% endif %}
<div class="submit-buttons">
@@ -87,7 +93,7 @@
<div class="step" id="step3">
<div class="description">
- <h2>Step 3: Upload overlay image</h2>
+ <h2>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
@@ -95,7 +101,7 @@
</p>
</div>
<div class="controls">
- <form id="overlay-upload-form" method="POST" action="{{url_for('upload', namespace='overlay')}}" enctype="multipart/form-data">
+ <form id="overlay-upload-form" method="POST" action="{{url_for('upload_overlay')}}" enctype="multipart/form-data">
{{overlay_form.csrf_token}}
</form>
<div class="form-controls">
@@ -119,11 +125,10 @@
{% 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>
+ <h2>Download the processed gerber files</h2>
</div>
<div class="controls">
- {# if 'vector_job' in session FIXME #}
- {% if True %}
+ {% if 'vector_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>
@@ -137,11 +142,23 @@
<div class="submit-buttons">
<input class='reset-button' form="reset-form" type="submit" value="Start over">
</div>
+ <!--4>Debug foo</h4>
+ <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-->
</div>
</div>
{% endif %} {# vector job #}
{% endif %} {# render job #}
</div>
+ <div class="sample-images">
+ <h1>Sample images</h1>
+ <img src="{{url_for('static', filename='sample1.jpg')}}">
+ <img src="{{url_for('static', filename='sample2.jpg')}}">
+ <img src="{{url_for('static', filename='sample3.jpg')}}">
+ </div>
</div>
</body>
</html>