Initial commit
This commit is contained in:
212
Readme.md
Normal file
212
Readme.md
Normal file
@@ -0,0 +1,212 @@
|
||||
# Using a Resin 3D Printer for PCB Exposure
|
||||
|
||||
This guide documents the process of using a resin 3D printer, such as the Anycubic Photon Mono 4, as a UV light source to expose photosensitive materials like pre-sensitized copper boards or solder masks. The printer's LCD screen acts as a precise digital mask.
|
||||
|
||||
## The Printer: Anycubic Photon Mono 4
|
||||
|
||||
Based on the user manual, here are the technical specifications for the printer, followed by an analysis of why this specific machine is excellent for DIY PCB fabrication.
|
||||
|
||||
### Technical Specifications
|
||||
* **System:** Photon Mono 4
|
||||
* **Screen / Display:** 7-inch 10K Monochrome LCD
|
||||
* **Resolution (XY):** 9024 x 5120 pixels
|
||||
* **Light Source:** Matrix LED light source (405nm wavelength)
|
||||
* **Build Volume:** 153.4 mm (L) x 87 mm (W) x 165 mm (H)
|
||||
* **Z-Axis Accuracy:** 0.01 mm
|
||||
* **Layer Thickness:** 0.01 ~ 0.15 mm
|
||||
* **Connectivity:** USB Type-A (2.0)
|
||||
* **Machine Dimensions:** 230 mm x 235 mm x 391 mm
|
||||
* **Weight:** 4 kg
|
||||
|
||||
### Why is it a good tool for DIY PCBs?
|
||||
|
||||
Using an MSLA (Masked Stereolithography) resin printer for creating PCBs is a technique known as "Direct UV Exposure." Instead of printing a plastic part, you use the printer's LCD screen as a dynamic digital mask to cure photosensitive materials on a copper board.
|
||||
|
||||
Here is why the **Anycubic Photon Mono 4** specifically is well-suited for this task:
|
||||
|
||||
**1. Extremely High Resolution (10K Screen)**
|
||||
The standout feature of this printer is the **10K (9024 x 5120)** resolution on a 7-inch screen.
|
||||
* **Why it helps:** In PCB fabrication, resolution dictates how thin your traces can be. A 10K screen provides an exceptionally small pixel pitch. To put this in perspective, this is equivalent to a print resolution of approximately **1500 DPI**.
|
||||
* *Calculation: The screen width is 153.4mm (6.04 inches). 9024 pixels / 6.04 inches ≈ 1494 DPI.*
|
||||
* This allows you to expose traces for fine-pitch SMD components (like TQFP or 0402 packages) with professional-grade sharpness, far exceeding what is possible with inkjet or laser-printed transparencies.
|
||||
|
||||
**2. Uniform Light Source**
|
||||
* **Why it helps:** The "Matrix LED" ensures uniform light distribution across the entire build area. When exposing a photosensitized board, uneven lighting can cause some areas to be under-exposed (washing away traces) or over-exposed (causing traces to merge). The matrix source ensures the center and corners of your PCB receive the same energy, resulting in a reliable etch.
|
||||
|
||||
**3. Perfect Wavelength (405nm)**
|
||||
* **Why it helps:** The UV LEDs in resin printers operate at **405nm**. This wavelength is perfectly compatible with most standard dry film photoresists and UV-curable solder masks used in PCB manufacturing. You do not need to modify the light source.
|
||||
|
||||
## Workflow Overview
|
||||
|
||||
The process involves exporting 2D layer images from your PCB design, converting them into a flat 3D model, and then "printing" this model with specific exposure settings in your slicer.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
* **Software:**
|
||||
* KiCad (for PCB design and Gerber export)
|
||||
* Inkscape (for SVG manipulation)
|
||||
* Blender (for 3D model creation)
|
||||
* Chitubox (or your printer's slicing software)
|
||||
* **Hardware:**
|
||||
* A resin 3D printer (e.g., Anycubic Photon Mono 4)
|
||||
* Photosensitive PCB. For consistent results, a pre-sensitized board like the **Bungard positive photoresist boards** is highly recommended due to its uniform coating. Available on [www.conrad.sk](https://www.conrad.sk/sk/p/bungard-150306e50-100x60mm-zakladny-material-jednostranna-35-m-foto-vrstva-pozitiv-d-x-s-100-mm-x-60-mm-1-ks-525472.html)
|
||||
* UV light curing Solder Mask e.g. from Mechanic
|
||||
|
||||
## Step-by-Step Guide
|
||||
|
||||
### Installation
|
||||
|
||||
Ensure you have both installed:
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt install inkscape blender
|
||||
```
|
||||
|
||||
Make sure you have the correct versions:
|
||||
```bash
|
||||
inkscape --version
|
||||
Inkscape 1.2.2
|
||||
|
||||
blender --version
|
||||
Blender 5.0.0
|
||||
```
|
||||
|
||||
### 1. Export Layers from KiCad
|
||||
|
||||
1. Open your PCB project in the KiCad PCB Editor.
|
||||
2. Go to **File -> Plot** and select `Plot format` as `SVG`, set the `Output directory`.
|
||||
3. In the `Include Layers` section select one layer (e.g. `F.Cu`, `F.Mask` or `F.Silkscreen`). If you select multiple layers they will be ploted as separate files.
|
||||
4. Optionaly, select additional layer(s) e.g. `Edge.Cuts` in the `Plot on All Layers` section. This way you can merge layers (e.g. front copper layer with board outset) into a single svg file.
|
||||
5. Click **Plot** and **Close**.
|
||||
|
||||
### 2. Prepare the SVG
|
||||
|
||||
Step 1: Open Your SVG in Inkscape
|
||||
|
||||
File → Open → Select your SVG.
|
||||
|
||||
Press Ctrl + F (or go to Edit → Find/Replace...).
|
||||
A panel will open (usually on the right).
|
||||
Look for the "Object Type" section `Options`.
|
||||
Uncheck "All Types" (if selected) and check only "Text".
|
||||
Click the Find button at the bottom of the panel.
|
||||
Result: All hidden text objects in the document will be selected (you will see their bounding boxes appear).
|
||||
Press Delete on your keyboard (Edit -> Delete).
|
||||
|
||||
Step 2: Select Everything
|
||||
|
||||
Press Ctrl + A to select all.
|
||||
|
||||
Step 3: Convert Strokes to Paths
|
||||
|
||||
Go to Path → Stroke to Path (Shift + Alt + C).
|
||||
|
||||
This converts any lines into editable paths.
|
||||
|
||||
Step 4: Unionize Paths
|
||||
|
||||
Now you want all separate paths to become one:
|
||||
|
||||
Path → Union (Ctrl ++).
|
||||
|
||||
Now your SVG should appear as a single curve object.
|
||||
|
||||
Step 5: In the top toolbar click on the `lock proportions` icon and set the width (W:) or height (H:) of the object.
|
||||
|
||||
Step 6: Export Your Cleaned SVG
|
||||
|
||||
Go to File → Export
|
||||
|
||||
Set `DPI` to `1494`and select `Plain SVG`, set the export path and click `Export`.
|
||||
|
||||
Make sure it’s in Plain SVG format (not Inkscape SVG) for best Blender compatibility.
|
||||
|
||||
### 3. Convert the SVG to a 3D Model
|
||||
|
||||
Step 1: Open Blender in the Scene Collection and delete the default cube
|
||||
|
||||
Step 2: Units to Millimeters
|
||||
|
||||
Go to Scene Properties (the cone/sphere/cube icon on the right sidebar) and open Units.
|
||||
|
||||
Set: Unit Scale: 0.001, Length: Millimeters
|
||||
|
||||
This tells Blender that 1 Blender unit = 1 mm.
|
||||
|
||||
Step 3: Import SVG
|
||||
|
||||
File → Import → SVG
|
||||
|
||||
Select the imported object by clicking on it's edge.
|
||||
|
||||
Step 4: Object -> Convert -> Mesh
|
||||
|
||||
Step 5: Press key 'N' and look at the `Dimensions`. The z-axis is zero.
|
||||
|
||||
Step 6: Click Add Modifier -> Generate -> Solidify (Thickness: 0.1, Even Thickness: yes, Fill Rim: ON)
|
||||
|
||||
Keep looking what is the real thickness showing at the z-axis in the `Dimensions`.
|
||||
|
||||
Step 7: Export -> File format `stl`. Check `apply modifiers` and save the file.
|
||||
|
||||
This entire workflow can be automated in the Ubuntu terminal using **Inkscape's command-line mode** and **Blender's Python scripting API**.
|
||||
|
||||
Use the single shell script [svg2stl.sh](svg2stl.sh) to do the whole job in one go:
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
chmod +x svg2stl.sh
|
||||
./svg2stl.sh my_design.svg
|
||||
```
|
||||
|
||||
### 3. Slicing in Chitubox
|
||||
|
||||
This is where you set the exposure time. The slicer (without hacking) requires at least one bottom and one normal layer to be defined. So, the idea is to split the thickness of the model into two or more layers and define the exposure time for each layer.
|
||||
|
||||
1. Open Chitubox and import your STL file. Lay it flat on the build plate.
|
||||
2. Go to the **Parameter Settings**.
|
||||
|
||||
#### Exposure Time Examples:
|
||||
Since the minimum exposure time per layer is 0.1s and maximum 200s, calculate the total exposure time. Pre-sensitized boards from Bungard requre 2 minutes to cure. Solder mask in a syringe from Mechanic require 10 minutes to harden.
|
||||
|
||||
* **1 minutes (60 seconds)**
|
||||
* Model Thickness (Blender): `0.1 mm`
|
||||
* Bottom Layer Count: `1`
|
||||
* Layer Height: `0.05 mm` (creates 2 layer)
|
||||
* **Bottom Exposure Time:** `60 s`
|
||||
* **Normal Exposure Time**: `0.1 s` (minimum value)
|
||||
|
||||
* **4 minutes (240 seconds)**
|
||||
* Model Thickness (Blender): `0.1 mm`
|
||||
* Bottom Layer Count: `1`
|
||||
* Layer Height: `0.05 mm` (creates 2 layers)
|
||||
* **Bottom Exposure Time:** `200 s` (max for the first layer)
|
||||
* **Normal Exposure Time:** `40 s` (for the second layer)
|
||||
|
||||
* **10 minutes (600 seconds)**
|
||||
* Model Thickness (Blender): `0.1 mm`
|
||||
* Layer Height: `0.025 mm` (creates 4 layers)
|
||||
* Bottom Layer Count: `1`
|
||||
* **Bottom Exposure Time:** `0.1 s` (minimum value)
|
||||
* **Normal Exposure Time:** `200 s` (This will apply to the 3 normal layers, giving 3*200 = 600s total).
|
||||
|
||||
4. **Crucially, turn off Anti-aliasing**. Go to the "Advanced" tab and set **Anti-aliasing** and **Grey Level** to `0`. This ensures the sharpest possible pixels for crisp traces.
|
||||
5. Slice the model and save the file to the USB drive.
|
||||
|
||||
### 4. Exposing the Board
|
||||
|
||||
1. **IMPORTANT:** Remove the resin vat and the build plate from your printer. You only need the LCD screen and the UV light source. Protect the screen with a spare FEP film or a screen protector.
|
||||
2. Place your pre-sensitized PCB directly on the protected LCD screen.
|
||||
3. Put same weights on the PCB to keep it flat. You also can create a jig that would keep the double-sided board aligned.
|
||||
4. Insert the USB drive containing the 3D model.
|
||||
5. Start the "print". The printer will expose the board using the layer images and times you configured.
|
||||
|
||||
### 5. Developing
|
||||
|
||||
1. After exposure, you must develop the board to reveal the pattern.
|
||||
2.
|
||||
- For a **positive photoresist board** (like the Bungard brand), the developer is a weak alkaline solution. A common and effective developer is a **1% solution of Sodium Hydroxide (NaOH)** in distilled or demineralised water.
|
||||
- For the solder mask just clean the uncured part with Izopropanol.
|
||||
3. **Safety First:** When working with NaOH, always wear gloves and eye protection, as it is caustic.
|
||||
4. Submerge the board in the developer solution and gently agitate it. The UV-exposed areas of the resist will dissolve, revealing the copper underneath. This should take 30-60 seconds.
|
||||
5. Rinse the board thoroughly with water and proceed to etching.
|
||||
BIN
assets/Bungard_Fotobeschichtetes_Basismaterial.pdf
Normal file
BIN
assets/Bungard_Fotobeschichtetes_Basismaterial.pdf
Normal file
Binary file not shown.
BIN
assets/Bungard_Produkt_Katalog.pdf
Normal file
BIN
assets/Bungard_Produkt_Katalog.pdf
Normal file
Binary file not shown.
BIN
assets/Photon Mono 4-EN-V0.0.4.pdf
Normal file
BIN
assets/Photon Mono 4-EN-V0.0.4.pdf
Normal file
Binary file not shown.
429
example/Anycubic_Photon_Mono_4_profile_00.cfgx
Normal file
429
example/Anycubic_Photon_Mono_4_profile_00.cfgx
Normal file
@@ -0,0 +1,429 @@
|
||||
{
|
||||
"create_time": "",
|
||||
"id": "",
|
||||
"version": "",
|
||||
"printerinfo": {
|
||||
"vendor": "AnyCubic",
|
||||
"direction_type": "Front",
|
||||
"input_slice_suffixs": "",
|
||||
"output_slice_suffixs": "",
|
||||
"net_send_slice_suffixs": "",
|
||||
"netsending": {
|
||||
"currentvalue": false
|
||||
},
|
||||
"machinetype": {
|
||||
"currentvalue": "Anycubic Photon Mono 4"
|
||||
},
|
||||
"machinename": {
|
||||
"currentvalue": "Anycubic Photon Mono 4"
|
||||
},
|
||||
"projecttype": {
|
||||
"currentvalue": 0
|
||||
},
|
||||
"resolutionx": {
|
||||
"currentvalue": 9024
|
||||
},
|
||||
"resolutiony": {
|
||||
"currentvalue": 5120
|
||||
},
|
||||
"machinewidth": {
|
||||
"currentvalue": 153.4080047607422
|
||||
},
|
||||
"machinedepth": {
|
||||
"currentvalue": 87.04000091552734
|
||||
},
|
||||
"machineheight": {
|
||||
"currentvalue": 165.0
|
||||
},
|
||||
"blockxysizeRatio": {
|
||||
"currentvalue": false
|
||||
},
|
||||
"resolutionLock": {
|
||||
"currentvalue": false
|
||||
},
|
||||
"sizeLock": {
|
||||
"currentvalue": false
|
||||
},
|
||||
"bdisplaycorrectenable": {
|
||||
"currentvalue": false
|
||||
},
|
||||
"displaycorrectlefttopx": {
|
||||
"currentvalue": 0.0
|
||||
},
|
||||
"displaycorrectlefttopy": {
|
||||
"currentvalue": 0.0
|
||||
},
|
||||
"displaycorrectrighttopx": {
|
||||
"currentvalue": 0.0
|
||||
},
|
||||
"displaycorrectrighttopy": {
|
||||
"currentvalue": 0.0
|
||||
},
|
||||
"displaycorrectleftbottomx": {
|
||||
"currentvalue": 0.0
|
||||
},
|
||||
"displaycorrectleftbottomy": {
|
||||
"currentvalue": 0.0
|
||||
},
|
||||
"displaycorrectrightbottomx": {
|
||||
"currentvalue": 0.0
|
||||
},
|
||||
"displaycorrectrightbottomy": {
|
||||
"currentvalue": 0.0
|
||||
},
|
||||
"bbuildareaoffsetenable": {
|
||||
"currentvalue": false
|
||||
},
|
||||
"buildareaoffsetleft": {
|
||||
"currentvalue": 0.0
|
||||
},
|
||||
"buildareaoffsetright": {
|
||||
"currentvalue": 0.0
|
||||
},
|
||||
"buildareaoffsettop": {
|
||||
"currentvalue": 0.0
|
||||
},
|
||||
"buildareaoffsetbottom": {
|
||||
"currentvalue": 0.0
|
||||
},
|
||||
"printtype": {
|
||||
"currentvalue": 0
|
||||
},
|
||||
"mirrortype": {
|
||||
"currentvalue": 0
|
||||
},
|
||||
"sliceformat": {
|
||||
"currentvalue": 0
|
||||
},
|
||||
"zliftheightinsupportmode": {
|
||||
"currentvalue": 4.74e-322
|
||||
},
|
||||
"raftshape": {
|
||||
"currentvalue": 0
|
||||
},
|
||||
"autoSupportMode": {
|
||||
"currentvalue": 0
|
||||
},
|
||||
"autoSupportType": {
|
||||
"currentvalue": 0
|
||||
}
|
||||
},
|
||||
"current_support": 1,
|
||||
"current_material": 0,
|
||||
"current_magic": 24568,
|
||||
"material": [
|
||||
{
|
||||
"name": "Default",
|
||||
"visible": true,
|
||||
"current_slice": 0,
|
||||
"current_support": 0,
|
||||
"slice": [
|
||||
{
|
||||
"visible": true,
|
||||
"name": "profile",
|
||||
"resin": {
|
||||
"visible": true,
|
||||
"resintype": {
|
||||
"currentvalue": "normal"
|
||||
},
|
||||
"resindensity": {
|
||||
"currentvalue": 1.100000023841858
|
||||
},
|
||||
"resincost": {
|
||||
"currentvalue": 30.0
|
||||
},
|
||||
"resinunit": {
|
||||
"currentvalue": 1
|
||||
},
|
||||
"resincostmeasure": {
|
||||
"currentvalue": 0
|
||||
},
|
||||
"resinmassunit": {
|
||||
"currentvalue": 0
|
||||
},
|
||||
"color": {
|
||||
"currentvalue": 0
|
||||
},
|
||||
"is_transparent": {
|
||||
"currentvalue": true
|
||||
},
|
||||
"name": {
|
||||
"currentvalue": "normal"
|
||||
}
|
||||
},
|
||||
"print": {
|
||||
"visible": true,
|
||||
"bottom_layer_drop_height_visible": false,
|
||||
"normal_layer_drop_height_visible": false,
|
||||
"matchingresinmodel": {
|
||||
"currentvalue": 0
|
||||
},
|
||||
"matchingresintips": {
|
||||
"currentvalue": ""
|
||||
},
|
||||
"layerheight": {
|
||||
"currentvalue": 0.25
|
||||
},
|
||||
"bottomlayercount": {
|
||||
"currentvalue": 1
|
||||
},
|
||||
"normalexposuretime": {
|
||||
"currentvalue": 60.0
|
||||
},
|
||||
"bottomlayerexposuretime": {
|
||||
"currentvalue": 60.0
|
||||
},
|
||||
"transitionlayercount": {
|
||||
"currentvalue": 0
|
||||
},
|
||||
"transitionlayertype": {
|
||||
"currentvalue": 0
|
||||
},
|
||||
"printwaitmode": {
|
||||
"currentvalue": 1
|
||||
},
|
||||
"normalresttimebeforelift": {
|
||||
"currentvalue": 0.0
|
||||
},
|
||||
"bottomresttimebeforelift": {
|
||||
"currentvalue": 0.0
|
||||
},
|
||||
"normalresttimeafterretract": {
|
||||
"currentvalue": 0.0
|
||||
},
|
||||
"bottomresttimeafterretract": {
|
||||
"currentvalue": 0.0
|
||||
},
|
||||
"normalresttimeafterlift": {
|
||||
"currentvalue": 0.0
|
||||
},
|
||||
"bottomresttimeafterlift": {
|
||||
"currentvalue": 0.0
|
||||
},
|
||||
"lightofftime": {
|
||||
"currentvalue": 0.0
|
||||
},
|
||||
"bottomlightofftime": {
|
||||
"currentvalue": 5.0
|
||||
},
|
||||
"bottomlayerliftheight": {
|
||||
"currentvalue": 5.0
|
||||
},
|
||||
"normallayerliftheight": {
|
||||
"currentvalue": 2.0
|
||||
},
|
||||
"bottomlayerliftheight2": {
|
||||
"currentvalue": 3.0
|
||||
},
|
||||
"normallayerliftheight2": {
|
||||
"currentvalue": 4.0
|
||||
},
|
||||
"bottomlayerliftspeed": {
|
||||
"currentvalue": 120.0
|
||||
},
|
||||
"normallayerliftspeed": {
|
||||
"currentvalue": 120.0
|
||||
},
|
||||
"bottomlayerliftspeed2": {
|
||||
"currentvalue": 180.0
|
||||
},
|
||||
"normallayerliftspeed2": {
|
||||
"currentvalue": 360.0
|
||||
},
|
||||
"bottomlayerdropspeed": {
|
||||
"currentvalue": 240.0
|
||||
},
|
||||
"normallayerdropspeed": {
|
||||
"currentvalue": 360.0
|
||||
},
|
||||
"bottomlayerdropspeed2": {
|
||||
"currentvalue": 180.0
|
||||
},
|
||||
"normallayerdropspeed2": {
|
||||
"currentvalue": 180.0
|
||||
},
|
||||
"normallayerdropheight2": {
|
||||
"currentvalue": 3.0
|
||||
},
|
||||
"bottomlayerdropheight2": {
|
||||
"currentvalue": 4.0
|
||||
},
|
||||
"edgeexposuremm": {
|
||||
"currentvalue": 0.0
|
||||
},
|
||||
"bottomedgeexposuremm": {
|
||||
"currentvalue": 0.0
|
||||
},
|
||||
"edgeexposuretimes": {
|
||||
"currentvalue": 4.0
|
||||
},
|
||||
"bottomedgeexposuretimes": {
|
||||
"currentvalue": 4.0
|
||||
},
|
||||
"ac_mode_type": {
|
||||
"currentvalue": true
|
||||
}
|
||||
},
|
||||
"gcode": {
|
||||
"visible": false,
|
||||
"bgcode": {
|
||||
"currentvalue": true
|
||||
},
|
||||
"startgcode": {
|
||||
"currentvalue": ""
|
||||
},
|
||||
"layergcode": {
|
||||
"currentvalue": ""
|
||||
},
|
||||
"endgcode": {
|
||||
"currentvalue": ""
|
||||
}
|
||||
},
|
||||
"advanced": {
|
||||
"visible": true,
|
||||
"bottomlightintensitypwm": {
|
||||
"currentvalue": 255
|
||||
},
|
||||
"bottomlightintensitypwmshowmode": {
|
||||
"currentvalue": 0
|
||||
},
|
||||
"normallightintensitypwm": {
|
||||
"currentvalue": 255
|
||||
},
|
||||
"normallightintensitypwmshowmode": {
|
||||
"currentvalue": 0
|
||||
},
|
||||
"bantialiasing": {
|
||||
"currentvalue": false
|
||||
},
|
||||
"edgeblurtype": {
|
||||
"currentvalue": 0
|
||||
},
|
||||
"antialiaslevel": {
|
||||
"currentvalue": 0
|
||||
},
|
||||
"midgreylevel": {
|
||||
"currentvalue": 255
|
||||
},
|
||||
"mingreylevel": {
|
||||
"currentvalue": 63
|
||||
},
|
||||
"maxgreylevel": {
|
||||
"currentvalue": 255
|
||||
},
|
||||
"bimageblur": {
|
||||
"currentvalue": true
|
||||
},
|
||||
"imageblurpixel": {
|
||||
"currentvalue": 1
|
||||
},
|
||||
"binnerouttercircleoffsetenable": {
|
||||
"currentvalue": false
|
||||
},
|
||||
"bprinttimecompensationenable": {
|
||||
"currentvalue": false
|
||||
},
|
||||
"innercircleoffset": {
|
||||
"currentvalue": 0.0
|
||||
},
|
||||
"outtercircleoffset": {
|
||||
"currentvalue": 0.0
|
||||
},
|
||||
"circleparitydifference": {
|
||||
"currentvalue": 0.0
|
||||
},
|
||||
"bbottominnerouttercircleoffsetenable": {
|
||||
"currentvalue": false
|
||||
},
|
||||
"bottominnercircleoffset": {
|
||||
"currentvalue": 0.0
|
||||
},
|
||||
"bottomouttercircleoffset": {
|
||||
"currentvalue": 0.0
|
||||
},
|
||||
"bimagemask": {
|
||||
"currentvalue": false
|
||||
},
|
||||
"imagemaskfile": {
|
||||
"currentvalue": ""
|
||||
},
|
||||
"bsurfacerougheditable": {
|
||||
"currentvalue": false
|
||||
},
|
||||
"surfaceroughlevel": {
|
||||
"currentvalue": 1
|
||||
},
|
||||
"maskgrayscaleunit": {
|
||||
"currentvalue": 4
|
||||
},
|
||||
"modelsupporttimesub": {
|
||||
"currentvalue": 0.0
|
||||
},
|
||||
"softwareprintingtimehour": {
|
||||
"currentvalue": 0
|
||||
},
|
||||
"softwareprintingtimeminute": {
|
||||
"currentvalue": 0
|
||||
},
|
||||
"softwareprintingtimesecond": {
|
||||
"currentvalue": 0
|
||||
},
|
||||
"actualprintingtimehour": {
|
||||
"currentvalue": 0
|
||||
},
|
||||
"actualprintingtimeminute": {
|
||||
"currentvalue": 0
|
||||
},
|
||||
"actualprintingtimesecond": {
|
||||
"currentvalue": 0
|
||||
},
|
||||
"printinglayer": {
|
||||
"currentvalue": 1
|
||||
},
|
||||
"layerprinttime": {
|
||||
"currentvalue": 0.0
|
||||
},
|
||||
"enable_obscure": {
|
||||
"currentvalue": true
|
||||
},
|
||||
"obscure_level": {
|
||||
"currentvalue": 0
|
||||
},
|
||||
"enable_scale_compensation": {
|
||||
"currentvalue": false
|
||||
},
|
||||
"compensation_x": {
|
||||
"currentvalue": 100.0
|
||||
},
|
||||
"compensation_y": {
|
||||
"currentvalue": 100.0
|
||||
},
|
||||
"compensation_z": {
|
||||
"currentvalue": 100.0
|
||||
},
|
||||
"centerOffsetX": {
|
||||
"currentvalue": 0.0
|
||||
},
|
||||
"centerOffsetY": {
|
||||
"currentvalue": 0.0
|
||||
},
|
||||
"centerCount": {
|
||||
"currentvalue": 5
|
||||
},
|
||||
"centerRadius": {
|
||||
"currentvalue": 50.0
|
||||
},
|
||||
"matrixXCount": {
|
||||
"currentvalue": 5
|
||||
},
|
||||
"matrixYCount": {
|
||||
"currentvalue": 5
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"support": [],
|
||||
"magic": []
|
||||
}
|
||||
BIN
example/Step_Down_Carrier_Board.stl
Normal file
BIN
example/Step_Down_Carrier_Board.stl
Normal file
Binary file not shown.
BIN
example/Step_Down_Carrier_Board.stl_0.05_0.1.pm4n
Normal file
BIN
example/Step_Down_Carrier_Board.stl_0.05_0.1.pm4n
Normal file
Binary file not shown.
1921
example/Step_Down_Carrier_Board.svg
Normal file
1921
example/Step_Down_Carrier_Board.svg
Normal file
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 35 KiB |
84
pcb_to_stl.py
Normal file
84
pcb_to_stl.py
Normal file
@@ -0,0 +1,84 @@
|
||||
import bpy
|
||||
import sys
|
||||
import os
|
||||
|
||||
# --- Parse Arguments ---
|
||||
# structure: [..., 'input.svg', 'output.stl', 'thickness', 'width']
|
||||
try:
|
||||
argv = sys.argv
|
||||
argv = argv[argv.index("--") + 1:]
|
||||
|
||||
input_svg = argv[0]
|
||||
output_stl = argv[1]
|
||||
thickness_mm = float(argv[2])
|
||||
target_width_mm = float(argv[3])
|
||||
except (ValueError, IndexError) as e:
|
||||
print(f"Error parsing arguments: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
# --- Step 1: Clean & Setup Units ---
|
||||
bpy.ops.wm.read_factory_settings(use_empty=True)
|
||||
|
||||
scene = bpy.context.scene
|
||||
scene.unit_settings.system = 'METRIC'
|
||||
scene.unit_settings.length_unit = 'MILLIMETERS'
|
||||
# This scale allows us to work in mm directly (1.0 = 1mm)
|
||||
scene.unit_settings.scale_length = 0.001
|
||||
|
||||
# --- Step 2: Import SVG ---
|
||||
if not os.path.exists(input_svg):
|
||||
print(f"FATAL: File not found: {input_svg}")
|
||||
sys.exit(1)
|
||||
|
||||
bpy.ops.import_curve.svg(filepath=input_svg)
|
||||
|
||||
# --- Step 3: Geometry Processing ---
|
||||
bpy.ops.object.select_all(action='SELECT')
|
||||
|
||||
if bpy.context.selected_objects:
|
||||
# Set active object
|
||||
bpy.context.view_layer.objects.active = bpy.context.selected_objects[0]
|
||||
|
||||
# 1. Join all parts into one
|
||||
bpy.ops.object.join()
|
||||
obj = bpy.context.active_object
|
||||
|
||||
# 2. Convert to Mesh (Flat 2D)
|
||||
bpy.ops.object.convert(target='MESH')
|
||||
|
||||
# --- Step 3.5: RESIZE TO TARGET WIDTH (MM) ---
|
||||
if target_width_mm > 0:
|
||||
# Get current width (X dimension)
|
||||
current_width = obj.dimensions.x
|
||||
|
||||
if current_width > 0.000001:
|
||||
scale_factor = target_width_mm / current_width
|
||||
print(f"Resizing: {current_width:.2f}mm -> {target_width_mm:.2f}mm (Factor: {scale_factor:.4f})")
|
||||
|
||||
# Apply Scale to X and Y (Maintain Aspect Ratio)
|
||||
# We do NOT scale Z because Z is 0 (flat mesh)
|
||||
obj.scale[0] = scale_factor
|
||||
obj.scale[1] = scale_factor
|
||||
|
||||
# Freeze the scale transformation so dimensions are real
|
||||
bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
|
||||
else:
|
||||
print("Warning: Object has 0 width, skipping resize.")
|
||||
|
||||
# 3. Extrude (Z-Height)
|
||||
# Now that X/Y are sized correctly, we extrude Z
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
bpy.ops.mesh.select_all(action='SELECT')
|
||||
|
||||
# Extrude exactly the thickness value
|
||||
bpy.ops.mesh.extrude_region_move(TRANSFORM_OT_translate={"value":(0, 0, thickness_mm)})
|
||||
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
# --- Step 4: Export STL ---
|
||||
print(f"Exporting to: {output_stl}")
|
||||
bpy.ops.wm.stl_export(filepath=output_stl, global_scale=1.0)
|
||||
|
||||
else:
|
||||
print("Error: No objects found in SVG.")
|
||||
sys.exit(1)
|
||||
61
svg2stl.sh
Executable file
61
svg2stl.sh
Executable file
@@ -0,0 +1,61 @@
|
||||
#!/bin/bash
|
||||
|
||||
# --- CONFIGURATION ---
|
||||
|
||||
# 1. Dimensions (Millimeters)
|
||||
# Set the desired width in mm (e.g. 55.15).
|
||||
# Leave as "0" to keep the original size from the SVG.
|
||||
TARGET_WIDTH_MM="45.08"
|
||||
|
||||
# Thickness of the 3D model in mm
|
||||
THICKNESS_MM="0.1"
|
||||
|
||||
# 2. Inkscape Settings
|
||||
# DPI based on the 3D printer resolution (e.g. 1494 for Anycubic Photon Mono 4)
|
||||
EXPORT_DPI=1494
|
||||
|
||||
# ---------------------
|
||||
|
||||
# 1. Check Input
|
||||
if [ -z "$1" ]; then
|
||||
echo "Error: No input file provided."
|
||||
echo "Usage: ./svg2stl.sh <input.svg>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 2. Setup Absolute Paths
|
||||
INPUT_FILE="$(readlink -f "$1")"
|
||||
WORK_DIR="$(dirname "$INPUT_FILE")"
|
||||
BASENAME="$(basename "$INPUT_FILE" .svg)"
|
||||
TEMP_SVG="$WORK_DIR/${BASENAME}_temp_processed.svg"
|
||||
OUTPUT_STL="$WORK_DIR/$BASENAME.stl"
|
||||
|
||||
# 3. Inkscape Processing
|
||||
# Note: We do NOT resize here. We export high-res geometry and resize in Blender.
|
||||
ACTIONS="select-by-element:text;delete;select-all;object-stroke-to-path;path-union;export-plain-svg;export-filename:$TEMP_SVG;export-do"
|
||||
INK_ARGS="--export-dpi=$EXPORT_DPI --actions=$ACTIONS"
|
||||
|
||||
echo "------------------------------------------------"
|
||||
echo "Step 1: Inkscape Processing"
|
||||
echo "Input: $INPUT_FILE"
|
||||
echo "------------------------------------------------"
|
||||
|
||||
inkscape "$INPUT_FILE" $INK_ARGS
|
||||
|
||||
if [ ! -f "$TEMP_SVG" ]; then
|
||||
echo "ERROR: Inkscape failed to create the temp file."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "------------------------------------------------"
|
||||
echo "Step 2: Blender Processing"
|
||||
echo "Target Width: ${TARGET_WIDTH_MM} mm"
|
||||
echo "Target Thickness: ${THICKNESS_MM} mm"
|
||||
echo "------------------------------------------------"
|
||||
|
||||
# Pass Width and Thickness to Python
|
||||
blender --background --python pcb_to_stl.py -- "$TEMP_SVG" "$OUTPUT_STL" "$THICKNESS_MM" "$TARGET_WIDTH_MM"
|
||||
|
||||
# 4. Cleanup
|
||||
rm "$TEMP_SVG"
|
||||
echo "Done! Saved to $OUTPUT_STL"
|
||||
Reference in New Issue
Block a user