Compare commits
3 Commits
2ccaf1c242
...
fc77d0381d
| Author | SHA1 | Date | |
|---|---|---|---|
| fc77d0381d | |||
| bb066fb7c6 | |||
| 52c3c1bc88 |
19
Readme.md
19
Readme.md
@@ -123,13 +123,13 @@ Place the presensitized copper board on the **Anycubic Photon Mono 4** printer's
|
||||
|
||||
### 7. Etching
|
||||
|
||||
For the developed Bungard photoresist PCB, etch the board using either `sodium persulfate` or `ferric chloride`.
|
||||
For the developed **Bungard** photoresist PCB, etch the board using either **sodium persulfate** or **ferric chloride**.
|
||||
|
||||
- For `sodium persulfate`, prepare a solution with approximately 200–250 g/L and heat it to 40–50 °C.
|
||||
- For **sodium persulfate**, prepare a solution with approximately **200–250 g/L** and heat it to **40–50 °C**.
|
||||
|
||||
- For `ferric chloride`, use a ready-to-use solution or prepare it according to the manufacturer's instructions and maintain a temperature of 35–45 °C.
|
||||
- For **ferric chloride**, use a ready-to-use solution or prepare it according to the manufacturer's instructions and maintain a temperature of **35–45 °C**.
|
||||
|
||||
Immerse the board in the etchant and agitate gently until all exposed copper has been removed, typically within 5–15 minutes. Rinse the board thoroughly with water after etching. To remove the remaining photoresist, immerse the board in a 2–5% `sodium hydroxide (NaOH)` solution at room temperature for 1–2 minutes, then rinse thoroughly with water and dry the board.
|
||||
Immerse the board in the etchant and agitate gently until all exposed copper has been removed, typically within 5–15 minutes. Rinse the board thoroughly with water after etching. To remove the remaining photoresist, immerse the board in a **2–5% sodium hydroxide (NaOH)** solution at room temperature for **1–2 minutes**, then rinse thoroughly with water and dry the board.
|
||||
|
||||
### 8. Drilling holes and milling board outlines
|
||||
|
||||
@@ -182,14 +182,13 @@ Traces should appear black on white background.
|
||||
- background = UV exposed = resist removed = etched away
|
||||
- traces = dark = resist kept = copper stays
|
||||
|
||||
Start with `--exposure 60` and bracket from there — **Bungard** presensitized at 405nm typically lands between 30–180s depending on board vintage and storage.
|
||||
|
||||
UV light curing Solder Mask from **Mechanic** needs 15 minutes when prebaked (heat it to 80C for 15 minutes) otherwise 30 minutes. Use green as the solder mask and white for the silkscreen.
|
||||
Start with `--exposure 60` and bracket from there — **Bungard** presensitized at **405nm** typically lands between **30–180s** depending on board vintage and storage.
|
||||
|
||||
**UV light curing Solder Mask** needs **15 minutes** exposure time when prebaked (heated to **80 °C** for **15 minutes**), otherwise expose for **30 minutes**. Use *green* color for the **solder mask** and *white* color for the **silkscreen**.
|
||||
|
||||
#### 4.1. Export print files for copper layers
|
||||
|
||||
Export the front and back **copper** layers for 120 seconds exposure
|
||||
Export the front and back **copper** layers for **120 seconds** exposure
|
||||
```bash
|
||||
pcb-expose.sh Dummy.pm4n gerbers/Flow_Controller_Panel-Front.gtl \
|
||||
--invert --mirror --exposure 120 \
|
||||
@@ -202,7 +201,7 @@ pcb-expose.sh Dummy.pm4n gerbers/Flow_Controller_Panel-Back.gbl \
|
||||
|
||||
#### 4.2. Export print files for soldermask layers
|
||||
|
||||
Export the front and back **soldermask** layers for 900 seconds exposure
|
||||
Export the front and back **soldermask** layers for **900 seconds** exposure
|
||||
```bash
|
||||
pcb-expose.sh Dummy.pm4n gerbers/Flow_Controller_Panel-F_Mask.gts \
|
||||
--invert --mirror --exposure 900 \
|
||||
@@ -215,7 +214,7 @@ pcb-expose.sh Dummy.pm4n gerbers/Flow_Controller_Panel-B_Mask.gbs \
|
||||
|
||||
#### 4.1. Export print files for silkscreen layers
|
||||
|
||||
Export the front and back **silkscreen** layers for 900 seconds exposure
|
||||
Export the front and back **silkscreen** layers for **900 seconds** exposure
|
||||
```bash
|
||||
pcb-expose.sh Dummy.pm4n gerbers/Flow_Controller_Panel-F_Silkscreen.gto \
|
||||
--mirror --exposure 900 \
|
||||
|
||||
@@ -1,192 +0,0 @@
|
||||
# UVtools PCB Exposure CLI — Build & Usage Guide
|
||||
|
||||
This guide covers building a wrapper for the`UVtoolsCmd`, for headless Gerber → `.pm4n` (or any slicer format) conversion on Ubuntu.
|
||||
|
||||
> **Note:** The UVtools maintainer is implementing an official equivalent via
|
||||
> the generic `run` command and a new `FileArray` reflection property
|
||||
> (`-p FileArray=file.gbr`). Once that ships in a release
|
||||
> ([issue 1127](https://github.com/sn4k3/UVtools/issues/1127)), you can switch to
|
||||
> `pcb-expose-official.sh` and skip the patch/compile steps entirely — see
|
||||
> [Option B](#option-b-official-release-no-compiling-required) below.
|
||||
|
||||
---
|
||||
|
||||
## Option A: Patched local build (`pcb-expose` command)
|
||||
|
||||
### 1. Install .NET SDK
|
||||
|
||||
```bash
|
||||
sudo snap install dotnet
|
||||
export DOTNET_ROOT=/var/snap/dotnet/common/dotnet
|
||||
dotnet --version
|
||||
```
|
||||
|
||||
Add the `export` line to your `~/.bashrc` so it persists across shells:
|
||||
|
||||
```bash
|
||||
echo 'export DOTNET_ROOT=/var/snap/dotnet/common/dotnet' >> ~/.bashrc
|
||||
```
|
||||
|
||||
### 2. Clone the repo
|
||||
|
||||
```bash
|
||||
cd ~/proj
|
||||
git clone --depth 1 https://github.com/sn4k3/UVtools.git
|
||||
cd UVtools
|
||||
```
|
||||
|
||||
### 3. Apply the patch
|
||||
|
||||
Copy `pcb-expose.patch` into the repo root, then:
|
||||
|
||||
```bash
|
||||
git apply ../kicad2msla/UVtools/pcb-expose.patch
|
||||
```
|
||||
|
||||
This does two things:
|
||||
- Adds `UVtools.Cmd/Symbols/PcbExposeCommand.cs` (new `pcb-expose` subcommand)
|
||||
- Registers it in `UVtools.Cmd/Program.cs`
|
||||
|
||||
Verify it applied cleanly:
|
||||
|
||||
```bash
|
||||
git add .
|
||||
git status
|
||||
# modified: UVtools.Cmd/Program.cs
|
||||
# new file: UVtools.Cmd/Symbols/PcbExposeCommand.cs
|
||||
```
|
||||
|
||||
### 4. Restore & build
|
||||
|
||||
```bash
|
||||
dotnet restore
|
||||
|
||||
dotnet publish UVtools.Cmd/UVtools.Cmd.csproj \
|
||||
-c Release \
|
||||
-r linux-x64 \
|
||||
--self-contained false \
|
||||
-o ./publish/
|
||||
```
|
||||
|
||||
### 5. Copy the OpenCV native library
|
||||
|
||||
The published output doesn't include `libcvextern.so` — copy it from the repo:
|
||||
|
||||
```bash
|
||||
cp build/platforms/linux-x64/libcvextern.so ./publish/
|
||||
```
|
||||
|
||||
### 6. Test it
|
||||
|
||||
```bash
|
||||
./publish/UVtoolsCmd pcb-expose \
|
||||
~/proj/kicad2msla/Dummy.pm4n \
|
||||
~/proj/kicad2msla/gerbers/Flow_Controller_Panel-Front.gtl \
|
||||
--invert \
|
||||
--mirror \
|
||||
--exposure 120 \
|
||||
--output ~/proj/kicad2msla/Flow_Controller_Panel-Front.pm4n
|
||||
```
|
||||
|
||||
### 7. Install the wrapper
|
||||
|
||||
Copy `pcb-expose-patched.sh` somewhere on your `$PATH`, e.g.:
|
||||
|
||||
```bash
|
||||
# Copy the script
|
||||
cp ~/proj/kicad2msla/UVtools/pcb-expose-patched.sh ~/.local/bin
|
||||
# Make it executable
|
||||
chmod +x ~/.local/bin/pcb-expose-patched.sh
|
||||
# Create a symlink
|
||||
ln -s ~/.local/bin/pcb-expose-patched.sh ~/.local/bin/pcb-expose.sh
|
||||
```
|
||||
|
||||
By default the wrapper looks for the build at `~/proj/UVtools/publish/UVtoolsCmd`.
|
||||
If yours is elsewhere, either edit the `UVTOOLSCMD` variable at the top of the
|
||||
script, or set it as an environment variable each time:
|
||||
|
||||
```bash
|
||||
export UVTOOLSCMD=~/proj/UVtools/publish/UVtoolsCmd
|
||||
```
|
||||
|
||||
### 8. Usage
|
||||
|
||||
```bash
|
||||
pcb-expose.sh ~/proj/kicad2msla/Dummy.pm4n \
|
||||
~/proj/kicad2msla/gerbers/Flow_Controller_Panel-Front.gtl \
|
||||
--invert --mirror --exposure 120 \
|
||||
--output ~/proj/kicad2msla/Flow_Controller_Panel-Front.pm4n
|
||||
```
|
||||
|
||||
Multiple files / per-file polarity inversion / merging into one layer:
|
||||
|
||||
```bash
|
||||
pcb-expose.sh Dummy.pm4n \
|
||||
Board-F.Cu.gtl Board-B.Cu.gtl \
|
||||
--merge --invert-polarity --mirror --exposure 120 \
|
||||
--output Board-combined.pm4n
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Option B: Official release (no compiling required)
|
||||
|
||||
Once the maintainer's `FileArray` property ships in an official UVtools
|
||||
release:
|
||||
|
||||
### 1. Download & install the release
|
||||
|
||||
Grab the Linux release from the
|
||||
[UVtools releases page](https://github.com/sn4k3/UVtools/releases) and
|
||||
extract it, e.g. to `~/UVtools/`. `libcvextern.so` is bundled in official
|
||||
releases, so no extra steps are needed there.
|
||||
|
||||
### 2. Install the wrapper
|
||||
|
||||
```bash
|
||||
# Copy the script
|
||||
cp ~/proj/kicad2msla/UVtools/pcb-expose-official.sh ~/.local/bin
|
||||
# Make it executable
|
||||
chmod +x ~/.local/bin/pcb-expose-official.sh
|
||||
# Create a symlink
|
||||
ln -s ~/.local/bin/pcb-expose-official.sh ~/.local/bin/pcb-expose.sh
|
||||
```
|
||||
|
||||
By default it looks for `~/UVtools/UVtoolsCmd`. Override with:
|
||||
|
||||
```bash
|
||||
export UVTOOLSCMD=~/UVtools/UVtoolsCmd
|
||||
```
|
||||
|
||||
### 3. Usage
|
||||
|
||||
Same interface as Option A (minus `--invert-polarity`, which the official
|
||||
`FileArray` property doesn't support per-file):
|
||||
|
||||
```bash
|
||||
pcb-expose.sh ~/proj/kicad2msla/Dummy.pm4n \
|
||||
~/proj/kicad2msla/gerbers/Flow_Controller_Panel-Front.gtl \
|
||||
--invert --mirror --exposure 120 \
|
||||
--output ~/proj/kicad2msla/Flow_Controller_Panel-Front.pm4n
|
||||
```
|
||||
|
||||
Internally this runs:
|
||||
|
||||
```bash
|
||||
UVtoolsCmd run Dummy.pm4n PCBExposure \
|
||||
-p FileArray=Board-F.Cu.gtl \
|
||||
-p InvertColor=true \
|
||||
-p Mirror=true \
|
||||
-p ExposureTime=120 \
|
||||
--output Board-F.Cu.pm4n
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Files in this bundle
|
||||
|
||||
| File | Purpose |
|
||||
|---|---|
|
||||
| `pcb-expose.patch` | `git apply`-able patch adding the `pcb-expose` command (Option A) |
|
||||
| `pcb-expose-patched.sh` | Wrapper for the patched local build |
|
||||
| `pcb-expose-official.sh` | Wrapper for the official release using `run` + `FileArray` |
|
||||
@@ -1,28 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Wrapper for a locally patched UVtoolsCmd build that provides the
|
||||
# native `pcb-expose` command (see pcb-expose.patch).
|
||||
#
|
||||
# Usage:
|
||||
# pcb-expose-patched.sh <dummy-file> <gerber-files...> [options]
|
||||
#
|
||||
# Example:
|
||||
# pcb-expose-patched.sh Dummy.pm4n \
|
||||
# Board-F.Cu.gtl \
|
||||
# --invert --mirror --exposure 120 \
|
||||
# --output Board-F.Cu.pm4n
|
||||
#
|
||||
# Configure the path to your patched build below, or override via:
|
||||
# UVTOOLSCMD=/path/to/UVtoolsCmd pcb-expose-patched.sh ...
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
UVTOOLSCMD="${UVTOOLSCMD:-$HOME/proj/UVtools/publish/UVtoolsCmd}"
|
||||
|
||||
if [ ! -x "$UVTOOLSCMD" ]; then
|
||||
echo "Error: UVtoolsCmd not found or not executable at: $UVTOOLSCMD" >&2
|
||||
echo "Set UVTOOLSCMD=/path/to/UVtoolsCmd or edit this script." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exec "$UVTOOLSCMD" pcb-expose "$@"
|
||||
@@ -1,173 +0,0 @@
|
||||
diff --git a/UVtools.Cmd/Program.cs b/UVtools.Cmd/Program.cs
|
||||
index 6d2d1ff..38da048 100644
|
||||
--- a/UVtools.Cmd/Program.cs
|
||||
+++ b/UVtools.Cmd/Program.cs
|
||||
@@ -45,6 +45,7 @@ public static async Task<int> Main(params string[] args)
|
||||
ExtractCommand.CreateCommand(),
|
||||
CopyParametersCommand.CreateCommand(),
|
||||
SetThumbnailCommand.CreateCommand(),
|
||||
+ PcbExposeCommand.CreateCommand(),
|
||||
|
||||
CompareCommand.CreateCommand(),
|
||||
|
||||
diff --git a/UVtools.Cmd/Symbols/PcbExposeCommand.cs b/UVtools.Cmd/Symbols/PcbExposeCommand.cs
|
||||
new file mode 100644
|
||||
index 0000000..e67e876
|
||||
--- /dev/null
|
||||
+++ b/UVtools.Cmd/Symbols/PcbExposeCommand.cs
|
||||
@@ -0,0 +1,155 @@
|
||||
+/*
|
||||
+ * GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
+ * Version 3, 19 November 2007
|
||||
+ * Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
+ * Everyone is permitted to copy and distribute verbatim copies
|
||||
+ * of this license document, but changing it is not allowed.
|
||||
+ */
|
||||
+
|
||||
+using System;
|
||||
+using System.CommandLine;
|
||||
+using System.IO;
|
||||
+using UVtools.Core.FileFormats;
|
||||
+using UVtools.Core.Operations;
|
||||
+
|
||||
+namespace UVtools.Cmd.Symbols;
|
||||
+
|
||||
+internal static class PcbExposeCommand
|
||||
+{
|
||||
+ internal static Command CreateCommand()
|
||||
+ {
|
||||
+ var gerberArgument = new Argument<FileInfo[]>("gerber-files")
|
||||
+ {
|
||||
+ Description = "One or more Gerber/drill files (.gbr .gtl .gbl .gts .gbs .drl etc.)",
|
||||
+ };
|
||||
+
|
||||
+ var exposureOption = new Option<decimal>("-e", "--exposure")
|
||||
+ {
|
||||
+ Description = "Exposure time in seconds (0 = keep value from input file)",
|
||||
+ DefaultValueFactory = _ => 0m,
|
||||
+ };
|
||||
+
|
||||
+ var invertOption = new Option<bool>("-i", "--invert")
|
||||
+ {
|
||||
+ Description = "Invert image color",
|
||||
+ };
|
||||
+
|
||||
+ var mirrorOption = new Option<bool>("-m", "--mirror")
|
||||
+ {
|
||||
+ Description = "Mirror the image horizontally",
|
||||
+ };
|
||||
+
|
||||
+ var mergeOption = new Option<bool>("--merge")
|
||||
+ {
|
||||
+ Description = "Merge all gerber files into a single layer",
|
||||
+ };
|
||||
+
|
||||
+ var invertPolarityOption = new Option<bool>("--invert-polarity")
|
||||
+ {
|
||||
+ Description = "Invert Gerber drawing polarity for all supplied files",
|
||||
+ };
|
||||
+
|
||||
+ var antiAliasingOption = new Option<bool>("--aa")
|
||||
+ {
|
||||
+ Description = "Enable anti-aliasing",
|
||||
+ };
|
||||
+
|
||||
+ var offsetXOption = new Option<decimal>("--offset-x")
|
||||
+ {
|
||||
+ Description = "X offset in mm",
|
||||
+ DefaultValueFactory = _ => 0m,
|
||||
+ };
|
||||
+
|
||||
+ var offsetYOption = new Option<decimal>("--offset-y")
|
||||
+ {
|
||||
+ Description = "Y offset in mm",
|
||||
+ DefaultValueFactory = _ => 0m,
|
||||
+ };
|
||||
+
|
||||
+ var command = new Command("pcb-expose", "Inject a Gerber file into a slicer file for PCB UV exposure")
|
||||
+ {
|
||||
+ GlobalArguments.InputFileArgument,
|
||||
+ gerberArgument,
|
||||
+
|
||||
+ exposureOption,
|
||||
+ invertOption,
|
||||
+ mirrorOption,
|
||||
+ mergeOption,
|
||||
+ invertPolarityOption,
|
||||
+ antiAliasingOption,
|
||||
+ offsetXOption,
|
||||
+ offsetYOption,
|
||||
+ GlobalOptions.OutputFile,
|
||||
+ };
|
||||
+
|
||||
+ command.SetAction(result =>
|
||||
+ {
|
||||
+ var inputFile = result.GetRequiredValue(GlobalArguments.InputFileArgument);
|
||||
+ var gerbers = result.GetRequiredValue(gerberArgument);
|
||||
+ var exposure = result.GetValue(exposureOption);
|
||||
+ var invert = result.GetValue(invertOption);
|
||||
+ var mirror = result.GetValue(mirrorOption);
|
||||
+ var merge = result.GetValue(mergeOption);
|
||||
+ var invertPolarity = result.GetValue(invertPolarityOption);
|
||||
+ var aa = result.GetValue(antiAliasingOption);
|
||||
+ var offsetX = result.GetValue(offsetXOption);
|
||||
+ var offsetY = result.GetValue(offsetYOption);
|
||||
+ var outputFile = result.GetValue(GlobalOptions.OutputFile);
|
||||
+
|
||||
+ if (gerbers.Length == 0)
|
||||
+ {
|
||||
+ Program.WriteLineError("Specify at least one gerber file.");
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ foreach (var g in gerbers)
|
||||
+ {
|
||||
+ if (!g.Exists)
|
||||
+ {
|
||||
+ Program.WriteLineError($"Gerber file not found: {g.FullName}");
|
||||
+ return;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ var slicerFile = Program.OpenInputFile(inputFile);
|
||||
+
|
||||
+ var op = new OperationPCBExposure(slicerFile)
|
||||
+ {
|
||||
+ MergeFiles = merge,
|
||||
+ Mirror = mirror,
|
||||
+ InvertColor = invert,
|
||||
+ EnableAntiAliasing = aa,
|
||||
+ OffsetX = offsetX,
|
||||
+ OffsetY = offsetY,
|
||||
+ };
|
||||
+
|
||||
+ if (exposure > 0)
|
||||
+ op.ExposureTime = exposure;
|
||||
+
|
||||
+ foreach (var g in gerbers)
|
||||
+ op.Files.Add(new OperationPCBExposure.PCBExposureFile(g.FullName, invertPolarity));
|
||||
+
|
||||
+ var spawnError = op.ValidateSpawn();
|
||||
+ if (!string.IsNullOrWhiteSpace(spawnError))
|
||||
+ {
|
||||
+ Program.WriteLineError(spawnError);
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ var validationError = op.ValidateInternally();
|
||||
+ if (!string.IsNullOrWhiteSpace(validationError))
|
||||
+ {
|
||||
+ Program.WriteLineError(validationError.TrimEnd());
|
||||
+ return;
|
||||
+ }
|
||||
+
|
||||
+ Program.ProgressBarWork(
|
||||
+ $"PCB exposure: {string.Join(", ", Array.ConvertAll(gerbers, g => g.Name))}",
|
||||
+ () => op.Execute(Program.Progress));
|
||||
+
|
||||
+ Program.SaveFile(slicerFile, outputFile);
|
||||
+ });
|
||||
+
|
||||
+ return command;
|
||||
+ }
|
||||
+}
|
||||
10
UVtools/pcb-expose-official.sh → pcb-expose.sh
Normal file → Executable file
10
UVtools/pcb-expose-official.sh → pcb-expose.sh
Normal file → Executable file
@@ -3,7 +3,7 @@
|
||||
# Wrapper for the OFFICIAL UVtoolsCmd release, using the generic `run` command
|
||||
# with the new FileArray-capable PCBExposure operation.
|
||||
#
|
||||
# Mirrors the interface of pcb-expose-patched.sh, but maps everything onto:
|
||||
# It maps everything onto:
|
||||
#
|
||||
# UVtoolsCmd run <dummy-file> PCBExposure \
|
||||
# -p FileArray=<gerber1> -p FileArray=<gerber2> ... \
|
||||
@@ -11,10 +11,10 @@
|
||||
# --output <output-file>
|
||||
#
|
||||
# Usage:
|
||||
# pcb-expose-official.sh <dummy-file> <gerber-files...> [options]
|
||||
# pcb-expose.sh <dummy-file> <gerber-files...> [options]
|
||||
#
|
||||
# Example:
|
||||
# pcb-expose-official.sh Dummy.pm4n \
|
||||
# pcb-expose.sh Dummy.pm4n \
|
||||
# gerbers/Flow_Controller_Panel-Front.gtl \
|
||||
# --invert --mirror --exposure 120 \
|
||||
# --output Flow_Controller_Panel-Front.pm4n
|
||||
@@ -35,7 +35,7 @@
|
||||
# use pcb-expose-patched.sh instead.
|
||||
#
|
||||
# Configure the path to your official build below, or override via:
|
||||
# UVTOOLSCMD=/path/to/UVtoolsCmd pcb-expose-official.sh ...
|
||||
# UVTOOLSCMD=/path/to/UVtoolsCmd pcb-expose.sh ...
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
@@ -98,7 +98,7 @@ done
|
||||
|
||||
if [ "$INVERT_POLARITY" = "true" ]; then
|
||||
echo "Warning: --invert-polarity is not supported via the official FileArray" >&2
|
||||
echo " property and will be ignored. Use pcb-expose-patched.sh instead." >&2
|
||||
echo " property and will be ignored." >&2
|
||||
fi
|
||||
|
||||
if [ ${#GERBERS[@]} -eq 0 ]; then
|
||||
Reference in New Issue
Block a user