[Stencil_3D] New output
- KiKit's "stencil createprinted"
This commit is contained in:
parent
5767a03868
commit
2a46ab1cff
|
|
@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
- `populate` to create step-by-step assembly instructions
|
- `populate` to create step-by-step assembly instructions
|
||||||
With support for `pcbdraw` and `render_3d`.
|
With support for `pcbdraw` and `render_3d`.
|
||||||
- `panelize` to create a PCB panel containing N copies of the PCB.
|
- `panelize` to create a PCB panel containing N copies of the PCB.
|
||||||
|
- `stencil_3d` to create 3D self-registering printable stencils.
|
||||||
- generic filters: options to filter by PCB side
|
- generic filters: options to filter by PCB side
|
||||||
- BoM:
|
- BoM:
|
||||||
- Option to link to Mouser site.
|
- Option to link to Mouser site.
|
||||||
|
|
|
||||||
60
README.md
60
README.md
|
|
@ -97,6 +97,8 @@ For example, it's common that you might want for each board rev:
|
||||||
* PCB 3D model in STEP format
|
* PCB 3D model in STEP format
|
||||||
* PCB 3D render in PNG format
|
* PCB 3D render in PNG format
|
||||||
* Compare PCB/SCHs
|
* Compare PCB/SCHs
|
||||||
|
* Panelization
|
||||||
|
* Stencil creation
|
||||||
|
|
||||||
You want to do this in a one-touch way, and make sure everything you need to
|
You want to do this in a one-touch way, and make sure everything you need to
|
||||||
do so is securely saved in version control, not on the back of an old
|
do so is securely saved in version control, not on the back of an old
|
||||||
|
|
@ -142,6 +144,9 @@ Notes:
|
||||||
- Show KiAuto installation information for `info` (v2.0.0)
|
- Show KiAuto installation information for `info` (v2.0.0)
|
||||||
- Print the page frame in GUI mode for `pcb_print` (v1.6.7)
|
- Print the page frame in GUI mode for `pcb_print` (v1.6.7)
|
||||||
|
|
||||||
|
[**KiKit**](https://github.com/yaqwsx/KiKit) [](https://github.com/yaqwsx/KiKit) 
|
||||||
|
- Mandatory for: `panelize`, `stencil_3d`
|
||||||
|
|
||||||
[**LXML**](https://pypi.org/project/LXML/) [](https://pypi.org/project/LXML/) [](https://packages.debian.org/bullseye/python3-lxml) 
|
[**LXML**](https://pypi.org/project/LXML/) [](https://pypi.org/project/LXML/) [](https://packages.debian.org/bullseye/python3-lxml) 
|
||||||
- Mandatory for: `pcb_print`, `pcbdraw`
|
- Mandatory for: `pcb_print`, `pcbdraw`
|
||||||
|
|
||||||
|
|
@ -158,12 +163,12 @@ Notes:
|
||||||
[**KiCad PCB/SCH Diff**](https://github.com/INTI-CMNB/KiDiff) v2.4.3 [](https://github.com/INTI-CMNB/KiDiff) 
|
[**KiCad PCB/SCH Diff**](https://github.com/INTI-CMNB/KiDiff) v2.4.3 [](https://github.com/INTI-CMNB/KiDiff) 
|
||||||
- Mandatory for `diff`
|
- Mandatory for `diff`
|
||||||
|
|
||||||
[**KiKit**](https://github.com/yaqwsx/KiKit) [](https://github.com/yaqwsx/KiKit) 
|
|
||||||
- Mandatory for `panelize`
|
|
||||||
|
|
||||||
[**mistune**](https://pypi.org/project/mistune/) [](https://pypi.org/project/mistune/) [](https://packages.debian.org/bullseye/python3-mistune)
|
[**mistune**](https://pypi.org/project/mistune/) [](https://pypi.org/project/mistune/) [](https://packages.debian.org/bullseye/python3-mistune)
|
||||||
- Mandatory for `populate`
|
- Mandatory for `populate`
|
||||||
|
|
||||||
|
[**OpenSCAD**](https://openscad.org/) [](https://openscad.org/) [](https://packages.debian.org/bullseye/openscad)
|
||||||
|
- Mandatory for `stencil_3d`
|
||||||
|
|
||||||
[**QRCodeGen**](https://pypi.org/project/QRCodeGen/) [](https://pypi.org/project/QRCodeGen/) [](https://pypi.org/project/QRCodeGen/) [](https://packages.debian.org/bullseye/python3-qrcodegen) 
|
[**QRCodeGen**](https://pypi.org/project/QRCodeGen/) [](https://pypi.org/project/QRCodeGen/) [](https://pypi.org/project/QRCodeGen/) [](https://packages.debian.org/bullseye/python3-qrcodegen) 
|
||||||
- Mandatory for `qr_lib`
|
- Mandatory for `qr_lib`
|
||||||
|
|
||||||
|
|
@ -3555,6 +3560,55 @@ Notes:
|
||||||
Internally we use 10 for low priority, 90 for high priority and 50 for most outputs.
|
Internally we use 10 for low priority, 90 for high priority and 50 for most outputs.
|
||||||
- `run_by_default`: [boolean=true] When enabled this output will be created when no specific outputs are requested.
|
- `run_by_default`: [boolean=true] When enabled this output will be created when no specific outputs are requested.
|
||||||
|
|
||||||
|
* 3D Printed Stencils
|
||||||
|
* Type: `stencil_3d`
|
||||||
|
* Description: Creates a 3D self-registering model of a stencil you can easily print on
|
||||||
|
SLA printer, you can use it to apply solder paste to your PCB.
|
||||||
|
These stencils are quick solution when you urgently need a stencil but probably
|
||||||
|
they don't last long and might come with imperfections.
|
||||||
|
It currently uses KiKit, so please read
|
||||||
|
[KiKit docs](https://github.com/yaqwsx/KiKit/blob/master/doc/stencil.md).
|
||||||
|
Note that we don't implement `--ignore` option, you should use a variant for this
|
||||||
|
* Valid keys:
|
||||||
|
- **`comment`**: [string=''] A comment for documentation purposes.
|
||||||
|
- **`dir`**: [string='./'] Output directory for the generated files.
|
||||||
|
If it starts with `+` the rest is concatenated to the default dir.
|
||||||
|
- **`name`**: [string=''] Used to identify this particular output definition.
|
||||||
|
- **`options`**: [dict] Options for the `Stencil_3D` output.
|
||||||
|
* Valid keys:
|
||||||
|
- **`output`**: [string='%f-%i%I%v.%x'] Filename for the output (%i='stencil_3d_top'|'stencil_3d_bottom'|'stencil_3d_edge',
|
||||||
|
%x='stl'|'scad'|'dxf'). Affected by global options.
|
||||||
|
- **`thickness`**: [number=0.15] Stencil thickness [mm]. Defines amount of paste dispensed.
|
||||||
|
- `cutout`: [string|list(string)] List of components to add a cutout based on the component courtyard.
|
||||||
|
This is useful when you have already pre-populated board and you want to populate more
|
||||||
|
components.
|
||||||
|
- `dnf_filter`: [string|list(string)='_none'] Name of the filter to mark components as not fitted.
|
||||||
|
A short-cut to use for simple cases where a variant is an overkill.
|
||||||
|
- *enlarge_holes*: Alias for enlarge_holes.
|
||||||
|
- `enlargeholes`: [number=0] Enlarge pad holes by x mm.
|
||||||
|
- *frame_clearance*: Alias for frameclearance.
|
||||||
|
- *frame_width*: Alias for framewidth.
|
||||||
|
- `frameclearance`: [number=0] Clearance for the stencil register [mm].
|
||||||
|
- `framewidth`: [number=1] Register frame width.
|
||||||
|
- `include_scad`: [boolean=true] Include the generated OpenSCAD files. Note that this also includes the DXF files.
|
||||||
|
- *pcb_thickness*: Alias for pcbthickness.
|
||||||
|
- `pcbthickness`: [number=0] PCB thickness [mm]. If 0 we will ask KiCad.
|
||||||
|
- `pre_transform`: [string|list(string)='_none'] Name of the filter to transform fields before applying other filters.
|
||||||
|
A short-cut to use for simple cases where a variant is an overkill.
|
||||||
|
- `side`: [string='auto'] [top,bottom,auto,both] Which side of the PCB we want. Using `auto` will detect which
|
||||||
|
side contains solder paste.
|
||||||
|
- `variant`: [string=''] Board variant to apply.
|
||||||
|
- `category`: [string|list(string)=''] The category for this output. If not specified an internally defined category is used.
|
||||||
|
Categories looks like file system paths, i.e. PCB/fabrication/gerber.
|
||||||
|
- `disable_run_by_default`: [string|boolean] Use it to disable the `run_by_default` status of other output.
|
||||||
|
Useful when this output extends another and you don't want to generate the original.
|
||||||
|
Use the boolean true value to disable the output you are extending.
|
||||||
|
- `extends`: [string=''] Copy the `options` section from the indicated output.
|
||||||
|
- `output_id`: [string=''] Text to use for the %I expansion content. To differentiate variations of this output.
|
||||||
|
- `priority`: [number=50] [0,100] Priority for this output. High priority outputs are created first.
|
||||||
|
Internally we use 10 for low priority, 90 for high priority and 50 for most outputs.
|
||||||
|
- `run_by_default`: [boolean=true] When enabled this output will be created when no specific outputs are requested.
|
||||||
|
|
||||||
* STEP (ISO 10303-21 Clear Text Encoding of the Exchange Structure)
|
* STEP (ISO 10303-21 Clear Text Encoding of the Exchange Structure)
|
||||||
* Type: `step`
|
* Type: `step`
|
||||||
* Description: Exports the PCB as a 3D model.
|
* Description: Exports the PCB as a 3D model.
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,8 @@ For example, it's common that you might want for each board rev:
|
||||||
* PCB 3D model in STEP format
|
* PCB 3D model in STEP format
|
||||||
* PCB 3D render in PNG format
|
* PCB 3D render in PNG format
|
||||||
* Compare PCB/SCHs
|
* Compare PCB/SCHs
|
||||||
|
* Panelization
|
||||||
|
* Stencil creation
|
||||||
|
|
||||||
You want to do this in a one-touch way, and make sure everything you need to
|
You want to do this in a one-touch way, and make sure everything you need to
|
||||||
do so is securely saved in version control, not on the back of an old
|
do so is securely saved in version control, not on the back of an old
|
||||||
|
|
|
||||||
|
|
@ -2460,6 +2460,52 @@ outputs:
|
||||||
title: ''
|
title: ''
|
||||||
# [string=''] Board variant to apply
|
# [string=''] Board variant to apply
|
||||||
variant: ''
|
variant: ''
|
||||||
|
# 3D Printed Stencils:
|
||||||
|
# SLA printer, you can use it to apply solder paste to your PCB.
|
||||||
|
# These stencils are quick solution when you urgently need a stencil but probably
|
||||||
|
# they don't last long and might come with imperfections.
|
||||||
|
# It currently uses KiKit, so please read
|
||||||
|
# [KiKit docs](https://github.com/yaqwsx/KiKit/blob/master/doc/stencil.md).
|
||||||
|
# Note that we don't implement `--ignore` option, you should use a variant for this
|
||||||
|
- name: 'stencil_3d_example'
|
||||||
|
comment: 'Creates a 3D self-registering model of a stencil you can easily print on'
|
||||||
|
type: 'stencil_3d'
|
||||||
|
dir: 'Example/stencil_3d_dir'
|
||||||
|
options:
|
||||||
|
# [string|list(string)] List of components to add a cutout based on the component courtyard.
|
||||||
|
# This is useful when you have already pre-populated board and you want to populate more
|
||||||
|
# components
|
||||||
|
cutout: ''
|
||||||
|
# [string|list(string)='_none'] Name of the filter to mark components as not fitted.
|
||||||
|
# A short-cut to use for simple cases where a variant is an overkill
|
||||||
|
dnf_filter: '_none'
|
||||||
|
# `enlarge_holes` is an alias for `enlarge_holes`
|
||||||
|
# [number=0] Enlarge pad holes by x mm
|
||||||
|
enlargeholes: 0
|
||||||
|
# `frame_clearance` is an alias for `frameclearance`
|
||||||
|
# `frame_width` is an alias for `framewidth`
|
||||||
|
# [number=0] Clearance for the stencil register [mm]
|
||||||
|
frameclearance: 0
|
||||||
|
# [number=1] Register frame width
|
||||||
|
framewidth: 1
|
||||||
|
# [boolean=true] Include the generated OpenSCAD files. Note that this also includes the DXF files
|
||||||
|
include_scad: true
|
||||||
|
# [string='%f-%i%I%v.%x'] Filename for the output (%i='stencil_3d_top'|'stencil_3d_bottom'|'stencil_3d_edge',
|
||||||
|
# %x='stl'|'scad'|'dxf'). Affected by global options
|
||||||
|
output: '%f-%i%I%v.%x'
|
||||||
|
# `pcb_thickness` is an alias for `pcbthickness`
|
||||||
|
# [number=0] PCB thickness [mm]. If 0 we will ask KiCad
|
||||||
|
pcbthickness: 0
|
||||||
|
# [string|list(string)='_none'] Name of the filter to transform fields before applying other filters.
|
||||||
|
# A short-cut to use for simple cases where a variant is an overkill
|
||||||
|
pre_transform: '_none'
|
||||||
|
# [string='auto'] [top,bottom,auto,both] Which side of the PCB we want. Using `auto` will detect which
|
||||||
|
# side contains solder paste
|
||||||
|
side: 'auto'
|
||||||
|
# [number=0.15] Stencil thickness [mm]. Defines amount of paste dispensed
|
||||||
|
thickness: 0.15
|
||||||
|
# [string=''] Board variant to apply
|
||||||
|
variant: ''
|
||||||
# STEP (ISO 10303-21 Clear Text Encoding of the Exchange Structure):
|
# STEP (ISO 10303-21 Clear Text Encoding of the Exchange Structure):
|
||||||
# This is the most common 3D format for exchange purposes.
|
# This is the most common 3D format for exchange purposes.
|
||||||
# This output is what you get from the 'File/Export/STEP' menu in pcbnew.
|
# This output is what you get from the 'File/Export/STEP' menu in pcbnew.
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,10 @@ Dependencies:
|
||||||
debian: python3-lxml
|
debian: python3-lxml
|
||||||
arch: python-lxml
|
arch: python-lxml
|
||||||
downloader: python
|
downloader: python
|
||||||
|
- name: KiKit
|
||||||
|
github: yaqwsx/KiKit
|
||||||
|
pypi: KiKit
|
||||||
|
downloader: pytool
|
||||||
"""
|
"""
|
||||||
import importlib
|
import importlib
|
||||||
import os
|
import os
|
||||||
|
|
@ -559,6 +563,9 @@ def run_command(cmd, only_first_line=False, pre_ver_text=None, no_err_2=False):
|
||||||
return None
|
return None
|
||||||
last_stderr = res_run.stderr.decode()
|
last_stderr = res_run.stderr.decode()
|
||||||
res = res_run.stdout.decode().strip()
|
res = res_run.stdout.decode().strip()
|
||||||
|
if len(res) == 0 and len(last_stderr) != 0:
|
||||||
|
# Ok, yes, OpenSCAD prints its version to stderr!!!
|
||||||
|
res = last_stderr
|
||||||
if only_first_line:
|
if only_first_line:
|
||||||
res = res.split('\n')[0]
|
res = res.split('\n')[0]
|
||||||
pre_vers = (cmd[0]+' version ', cmd[0]+' ', pre_ver_text)
|
pre_vers = (cmd[0]+' version ', cmd[0]+' ', pre_ver_text)
|
||||||
|
|
|
||||||
13
kibot/gs.py
13
kibot/gs.py
|
|
@ -377,6 +377,19 @@ class GS(object):
|
||||||
copy2(pro_name, pro_copy)
|
copy2(pro_name, pro_copy)
|
||||||
return pro_copy
|
return pro_copy
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_pcb_and_pro_names(name):
|
||||||
|
if GS.ki5:
|
||||||
|
return [name, name.replace('kicad_pcb', 'pro')]
|
||||||
|
return [name, name.replace('kicad_pcb', 'kicad_pro'), name.replace('kicad_pcb', 'kicad_prl')]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def remove_pcb_and_pro(name):
|
||||||
|
""" Used to remove temporal PCB and project files """
|
||||||
|
for fn in GS.get_pcb_and_pro_names(name):
|
||||||
|
if os.path.isfile(fn):
|
||||||
|
os.remove(fn)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def load_board():
|
def load_board():
|
||||||
""" Will be repplaced by kiplot.py """
|
""" Will be repplaced by kiplot.py """
|
||||||
|
|
|
||||||
|
|
@ -242,6 +242,7 @@ W_PCBDRAW = '(W103) '
|
||||||
W_NOCRTYD = '(W104) '
|
W_NOCRTYD = '(W104) '
|
||||||
W_PANELEMPTY = '(W105) '
|
W_PANELEMPTY = '(W105) '
|
||||||
W_ONWIN = '(W106) '
|
W_ONWIN = '(W106) '
|
||||||
|
W_AUTONONE = '(W106) '
|
||||||
# Somehow arbitrary, the colors are real, but can be different
|
# Somehow arbitrary, the colors are real, but can be different
|
||||||
PCB_MAT_COLORS = {'fr1': "937042", 'fr2': "949d70", 'fr3': "adacb4", 'fr4': "332B16", 'fr5': "6cc290"}
|
PCB_MAT_COLORS = {'fr1': "937042", 'fr2': "949d70", 'fr3': "adacb4", 'fr4': "332B16", 'fr5': "6cc290"}
|
||||||
PCB_FINISH_COLORS = {'hal': "8b898c", 'hasl': "8b898c", 'imag': "8b898c", 'enig': "cfb96e", 'enepig': "cfb96e",
|
PCB_FINISH_COLORS = {'hal': "8b898c", 'hasl': "8b898c", 'imag': "8b898c", 'enig': "cfb96e", 'enepig': "cfb96e",
|
||||||
|
|
|
||||||
|
|
@ -252,6 +252,11 @@ class VariantOptions(BaseOptions):
|
||||||
return []
|
return []
|
||||||
return [c.ref for c in self._comps if not c.fitted or not c.included]
|
return [c.ref for c in self._comps if not c.fitted or not c.included]
|
||||||
|
|
||||||
|
# Here just to avoid pulling pcbnew for this
|
||||||
|
@staticmethod
|
||||||
|
def to_mm(val):
|
||||||
|
return ToMM(val)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create_module_element(m):
|
def create_module_element(m):
|
||||||
if GS.ki6:
|
if GS.ki6:
|
||||||
|
|
@ -334,6 +339,22 @@ class VariantOptions(BaseOptions):
|
||||||
for line in restore:
|
for line in restore:
|
||||||
m.Remove(line)
|
m.Remove(line)
|
||||||
|
|
||||||
|
def detect_solder_paste(self, board):
|
||||||
|
""" Detects if the top and/or bottom layer has solder paste """
|
||||||
|
fpaste = board.GetLayerID('F.Paste')
|
||||||
|
bpaste = board.GetLayerID('B.Paste')
|
||||||
|
top = bottom = False
|
||||||
|
for m in GS.get_modules_board(board):
|
||||||
|
for p in m.Pads():
|
||||||
|
pad_layers = p.GetLayerSet()
|
||||||
|
if not top and fpaste in pad_layers.Seq():
|
||||||
|
top = True
|
||||||
|
if not bottom and bpaste in pad_layers.Seq():
|
||||||
|
bottom = True
|
||||||
|
if top and bottom:
|
||||||
|
return top, bottom
|
||||||
|
return top, bottom
|
||||||
|
|
||||||
def remove_paste_and_glue(self, board, comps_hash):
|
def remove_paste_and_glue(self, board, comps_hash):
|
||||||
""" Remove from solder paste layers the filtered components. """
|
""" Remove from solder paste layers the filtered components. """
|
||||||
if comps_hash is None or not (GS.global_remove_solder_paste_for_dnp or GS.global_remove_adhesive_for_dnp):
|
if comps_hash is None or not (GS.global_remove_solder_paste_for_dnp or GS.global_remove_adhesive_for_dnp):
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,7 @@
|
||||||
# Project: KiBot (formerly KiPlot)
|
# Project: KiBot (formerly KiPlot)
|
||||||
"""
|
"""
|
||||||
Dependencies:
|
Dependencies:
|
||||||
- name: KiKit
|
- from: KiKit
|
||||||
github: yaqwsx/KiKit
|
|
||||||
pypi: KiKit
|
|
||||||
downloader: pytool
|
|
||||||
role: mandatory
|
role: mandatory
|
||||||
"""
|
"""
|
||||||
import collections
|
import collections
|
||||||
|
|
@ -708,10 +705,7 @@ class PanelizeOptions(VariantOptions):
|
||||||
fname = self.save_tmp_board()
|
fname = self.save_tmp_board()
|
||||||
self.unfilter_pcb_components(GS.board, do_3D=True)
|
self.unfilter_pcb_components(GS.board, do_3D=True)
|
||||||
self.restore_title()
|
self.restore_title()
|
||||||
to_remove.append(fname)
|
to_remove.extend(GS.get_pcb_and_pro_names(fname))
|
||||||
to_remove.append(fname.replace('kicad_pcb', 'kicad_pro'))
|
|
||||||
to_remove.append(fname.replace('kicad_pcb', 'kicad_prl'))
|
|
||||||
to_remove.append(fname.replace('kicad_pcb', 'pro'))
|
|
||||||
logger.debug('- Modified PCB: '+fname)
|
logger.debug('- Modified PCB: '+fname)
|
||||||
else:
|
else:
|
||||||
fname = GS.pcb_file
|
fname = GS.pcb_file
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,180 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2022 Salvador E. Tropea
|
||||||
|
# Copyright (c) 2022 Instituto Nacional de Tecnología Industrial
|
||||||
|
# License: GPL-3.0
|
||||||
|
# Project: KiBot (formerly KiPlot)
|
||||||
|
"""
|
||||||
|
Dependencies:
|
||||||
|
- from: KiKit
|
||||||
|
role: mandatory
|
||||||
|
- name: OpenSCAD
|
||||||
|
url: https://openscad.org/
|
||||||
|
url_down: https://openscad.org/downloads.html
|
||||||
|
command: openscad
|
||||||
|
debian: openscad
|
||||||
|
arch: openscad
|
||||||
|
role: mandatory
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import tempfile
|
||||||
|
from .error import PlotError
|
||||||
|
from .gs import GS
|
||||||
|
from .kiplot import run_command
|
||||||
|
from .out_base import VariantOptions
|
||||||
|
from .misc import W_AUTONONE
|
||||||
|
from .macros import macros, document, output_class # noqa: F401
|
||||||
|
from . import log
|
||||||
|
|
||||||
|
logger = log.get_logger()
|
||||||
|
|
||||||
|
|
||||||
|
class Stencil_3D_Options(VariantOptions):
|
||||||
|
def __init__(self):
|
||||||
|
with document:
|
||||||
|
self.output = GS.def_global_output
|
||||||
|
""" *Filename for the output (%i='stencil_3d_top'|'stencil_3d_bottom'|'stencil_3d_edge',
|
||||||
|
%x='stl'|'scad'|'dxf') """
|
||||||
|
self.side = 'auto'
|
||||||
|
""" [top,bottom,auto,both] Which side of the PCB we want. Using `auto` will detect which
|
||||||
|
side contains solder paste """
|
||||||
|
self.include_scad = True
|
||||||
|
""" Include the generated OpenSCAD files. Note that this also includes the DXF files """
|
||||||
|
self.cutout = ''
|
||||||
|
""" [string|list(string)] List of components to add a cutout based on the component courtyard.
|
||||||
|
This is useful when you have already pre-populated board and you want to populate more
|
||||||
|
components """
|
||||||
|
self.pcbthickness = 0
|
||||||
|
""" PCB thickness [mm]. If 0 we will ask KiCad """
|
||||||
|
self.pcb_thickness = None
|
||||||
|
""" {pcbthickness} """
|
||||||
|
self.thickness = 0.15
|
||||||
|
""" *Stencil thickness [mm]. Defines amount of paste dispensed """
|
||||||
|
self.framewidth = 1
|
||||||
|
""" Register frame width """
|
||||||
|
self.frame_width = None
|
||||||
|
""" {framewidth} """
|
||||||
|
self.frameclearance = 0
|
||||||
|
""" Clearance for the stencil register [mm] """
|
||||||
|
self.frame_clearance = None
|
||||||
|
""" {frameclearance} """
|
||||||
|
self.enlargeholes = 0
|
||||||
|
""" Enlarge pad holes by x mm """
|
||||||
|
self.enlarge_holes = None
|
||||||
|
""" {enlarge_holes} """
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
def config(self, parent):
|
||||||
|
super().config(parent)
|
||||||
|
self.cutout = ','.join(self.force_list(self.cutout))
|
||||||
|
|
||||||
|
def move_output(self, src_dir, src_file, id, ext, replacement=None, patch=False):
|
||||||
|
self._expand_id = id
|
||||||
|
self._expand_ext = ext
|
||||||
|
dst_name = self._parent.expand_filename(self._parent.output_dir, self.output)
|
||||||
|
src_name = os.path.join(src_dir, src_file)
|
||||||
|
if not os.path.isfile(src_name):
|
||||||
|
raise PlotError('Missing output file {}'.format(src_name))
|
||||||
|
if patch:
|
||||||
|
# Adjust the names of the DXF files
|
||||||
|
with open(src_name, 'r') as f:
|
||||||
|
content = f.read()
|
||||||
|
for k, v in replacement.items():
|
||||||
|
content = content.replace(k, v)
|
||||||
|
with open(dst_name, 'w') as f:
|
||||||
|
f.write(content)
|
||||||
|
else:
|
||||||
|
shutil.move(src_name, dst_name)
|
||||||
|
if replacement is not None:
|
||||||
|
replacement[src_name] = os.path.basename(dst_name)
|
||||||
|
|
||||||
|
def get_targets(self, out_dir):
|
||||||
|
# TODO: auto side is tricky, needs variants applied
|
||||||
|
return [self._parent.expand_filename(out_dir, self.output)]
|
||||||
|
|
||||||
|
def run(self, output):
|
||||||
|
cmd_kikit = self.ensure_tool('KiKit')
|
||||||
|
self.ensure_tool('OpenSCAD')
|
||||||
|
super().run(output)
|
||||||
|
# Apply variants and filters
|
||||||
|
filtered = self.filter_pcb_components(GS.board)
|
||||||
|
if self.side == 'auto':
|
||||||
|
detected_top, detected_bottom = self.detect_solder_paste(GS.board)
|
||||||
|
fname = self.save_tmp_board() if filtered else GS.pcb_file
|
||||||
|
if filtered:
|
||||||
|
self.unfilter_pcb_components(GS.board)
|
||||||
|
# Avoid running the tool if we will generate useless models
|
||||||
|
if self.side == 'auto' and not detected_top and not detected_bottom:
|
||||||
|
logger.warning(W_AUTONONE+'No solder paste detected, skipping 3D stencil generation')
|
||||||
|
return
|
||||||
|
# If no PCB thickness indicated ask KiCad
|
||||||
|
if not self.pcbthickness:
|
||||||
|
ds = GS.board.GetDesignSettings()
|
||||||
|
self.pcbthickness = self.to_mm(ds.GetBoardThickness())
|
||||||
|
# Create the command line
|
||||||
|
cmd = [cmd_kikit, 'stencil', 'createprinted',
|
||||||
|
'--thickness', str(self.thickness),
|
||||||
|
'--framewidth', str(self.framewidth),
|
||||||
|
'--pcbthickness', str(self.pcbthickness)]
|
||||||
|
if self.cutout:
|
||||||
|
cmd.extend(['--coutout', self.cutout])
|
||||||
|
if self.frameclearance:
|
||||||
|
cmd.extend(['--frameclearance', str(self.frameclearance)])
|
||||||
|
if self.enlargeholes:
|
||||||
|
cmd.extend(['--enlargeholes', str(self.enlargeholes)])
|
||||||
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
|
cmd.append(fname)
|
||||||
|
cmd.append(tmp)
|
||||||
|
try:
|
||||||
|
run_command(cmd)
|
||||||
|
finally:
|
||||||
|
# Remove temporal variant
|
||||||
|
if filtered:
|
||||||
|
GS.remove_pcb_and_pro(fname)
|
||||||
|
# Now copy the files we want
|
||||||
|
# - Which side?
|
||||||
|
do_top = do_bottom = False
|
||||||
|
if self.side == 'top':
|
||||||
|
do_top = True
|
||||||
|
elif self.side == 'bottom':
|
||||||
|
do_bottom = True
|
||||||
|
elif self.side == 'both':
|
||||||
|
do_top = True
|
||||||
|
do_bottom = True
|
||||||
|
else: # auto
|
||||||
|
do_top = detected_top
|
||||||
|
do_bottom = detected_bottom
|
||||||
|
prj_name = os.path.splitext(os.path.basename(fname))[0]
|
||||||
|
replacements = {}
|
||||||
|
# The edge is needed by any of the OpenSCAD files
|
||||||
|
if (do_top or do_bottom) and self.include_scad:
|
||||||
|
self.move_output(tmp, prj_name+'-EdgeCuts.dxf', 'stencil_3d_edge', 'dxf', replacements)
|
||||||
|
# Top side
|
||||||
|
if do_top:
|
||||||
|
self.move_output(tmp, 'topStencil.stl', 'stencil_3d_top', 'stl')
|
||||||
|
if self.include_scad:
|
||||||
|
self.move_output(tmp, prj_name+'-PasteTop.dxf', 'stencil_3d_top', 'dxf', replacements)
|
||||||
|
self.move_output(tmp, 'topStencil.scad', 'stencil_3d_top', 'scad', replacements, patch=True)
|
||||||
|
# Bottom side
|
||||||
|
if do_bottom:
|
||||||
|
self.move_output(tmp, 'bottomStencil.stl', 'stencil_3d_bottom', 'stl')
|
||||||
|
if self.include_scad:
|
||||||
|
self.move_output(tmp, prj_name+'-PasteBottom.dxf', 'stencil_3d_bottom', 'dxf', replacements)
|
||||||
|
self.move_output(tmp, 'bottomStencil.scad', 'stencil_3d_bottom', 'scad', replacements, patch=True)
|
||||||
|
|
||||||
|
|
||||||
|
@output_class
|
||||||
|
class Stencil_3D(BaseOutput): # noqa: F821
|
||||||
|
""" 3D Printed Stencils
|
||||||
|
Creates a 3D self-registering model of a stencil you can easily print on
|
||||||
|
SLA printer, you can use it to apply solder paste to your PCB.
|
||||||
|
These stencils are quick solution when you urgently need a stencil but probably
|
||||||
|
they don't last long and might come with imperfections.
|
||||||
|
It currently uses KiKit, so please read
|
||||||
|
[KiKit docs](https://github.com/yaqwsx/KiKit/blob/master/doc/stencil.md).
|
||||||
|
Note that we don't implement `--ignore` option, you should use a variant for this """
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
with document:
|
||||||
|
self.options = Stencil_3D_Options
|
||||||
|
""" *[dict] Options for the `Stencil_3D` output """
|
||||||
|
|
@ -558,7 +558,7 @@ deps = '{\
|
||||||
"extra_arch": null,\
|
"extra_arch": null,\
|
||||||
"extra_deb": null,\
|
"extra_deb": null,\
|
||||||
"help_option": "--version",\
|
"help_option": "--version",\
|
||||||
"importance": 10000,\
|
"importance": 20000,\
|
||||||
"in_debian": false,\
|
"in_debian": false,\
|
||||||
"is_kicad_plugin": false,\
|
"is_kicad_plugin": false,\
|
||||||
"is_python": false,\
|
"is_python": false,\
|
||||||
|
|
@ -575,6 +575,13 @@ deps = '{\
|
||||||
"max_version": null,\
|
"max_version": null,\
|
||||||
"output": "panelize",\
|
"output": "panelize",\
|
||||||
"version": null\
|
"version": null\
|
||||||
|
},\
|
||||||
|
{\
|
||||||
|
"desc": null,\
|
||||||
|
"mandatory": true,\
|
||||||
|
"max_version": null,\
|
||||||
|
"output": "stencil_3d",\
|
||||||
|
"version": null\
|
||||||
}\
|
}\
|
||||||
],\
|
],\
|
||||||
"tests": [],\
|
"tests": [],\
|
||||||
|
|
@ -622,6 +629,39 @@ deps = '{\
|
||||||
"url": null,\
|
"url": null,\
|
||||||
"url_down": null\
|
"url_down": null\
|
||||||
},\
|
},\
|
||||||
|
"OpenSCAD": {\
|
||||||
|
"arch": "openscad",\
|
||||||
|
"command": "openscad",\
|
||||||
|
"comments": [],\
|
||||||
|
"deb_package": "openscad",\
|
||||||
|
"downloader": null,\
|
||||||
|
"downloader_str": null,\
|
||||||
|
"extra_arch": null,\
|
||||||
|
"extra_deb": null,\
|
||||||
|
"help_option": "--version",\
|
||||||
|
"importance": 10000,\
|
||||||
|
"in_debian": true,\
|
||||||
|
"is_kicad_plugin": false,\
|
||||||
|
"is_python": false,\
|
||||||
|
"name": "OpenSCAD",\
|
||||||
|
"no_cmd_line_version": false,\
|
||||||
|
"no_cmd_line_version_old": false,\
|
||||||
|
"output": "stencil_3d",\
|
||||||
|
"plugin_dirs": null,\
|
||||||
|
"pypi_name": "OpenSCAD",\
|
||||||
|
"roles": [\
|
||||||
|
{\
|
||||||
|
"desc": null,\
|
||||||
|
"mandatory": true,\
|
||||||
|
"max_version": null,\
|
||||||
|
"output": "stencil_3d",\
|
||||||
|
"version": null\
|
||||||
|
}\
|
||||||
|
],\
|
||||||
|
"tests": [],\
|
||||||
|
"url": "https://openscad.org/",\
|
||||||
|
"url_down": "https://openscad.org/downloads.html"\
|
||||||
|
},\
|
||||||
"Pandoc": {\
|
"Pandoc": {\
|
||||||
"arch": "pandoc",\
|
"arch": "pandoc",\
|
||||||
"command": "pandoc",\
|
"command": "pandoc",\
|
||||||
|
|
@ -1027,7 +1067,7 @@ def run_command(cmd, only_first_line=False, pre_ver_text=None, no_err_2=False):
|
||||||
cmd[0] = cmd_full
|
cmd[0] = cmd_full
|
||||||
last_cmd = None
|
last_cmd = None
|
||||||
try:
|
try:
|
||||||
cmd_output = subprocess.check_output(cmd, stderr=subprocess.DEVNULL)
|
res_run = subprocess.run(cmd, check=True, capture_output=True)
|
||||||
last_cmd = cmd[0]
|
last_cmd = cmd[0]
|
||||||
except FileNotFoundError as e:
|
except FileNotFoundError as e:
|
||||||
last_ok = False
|
last_ok = False
|
||||||
|
|
@ -1039,7 +1079,9 @@ def run_command(cmd, only_first_line=False, pre_ver_text=None, no_err_2=False):
|
||||||
print('Output from command: '+e.output.decode())
|
print('Output from command: '+e.output.decode())
|
||||||
last_ok = False
|
last_ok = False
|
||||||
return UNKNOWN
|
return UNKNOWN
|
||||||
res = cmd_output.decode().strip()
|
res = res_run.stdout.decode().strip()
|
||||||
|
if len(res) == 0:
|
||||||
|
res = res_run.stderr.decode().strip()
|
||||||
if only_first_line:
|
if only_first_line:
|
||||||
res = res.split('\n')[0]
|
res = res.split('\n')[0]
|
||||||
pre_vers = (cmd[0]+' version ', cmd[0]+' ', pre_ver_text)
|
pre_vers = (cmd[0]+' version ', cmd[0]+' ', pre_ver_text)
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,7 @@ def run_command(cmd, only_first_line=False, pre_ver_text=None, no_err_2=False):
|
||||||
cmd[0] = cmd_full
|
cmd[0] = cmd_full
|
||||||
last_cmd = None
|
last_cmd = None
|
||||||
try:
|
try:
|
||||||
cmd_output = subprocess.check_output(cmd, stderr=subprocess.DEVNULL)
|
res_run = subprocess.run(cmd, check=True, capture_output=True)
|
||||||
last_cmd = cmd[0]
|
last_cmd = cmd[0]
|
||||||
except FileNotFoundError as e:
|
except FileNotFoundError as e:
|
||||||
last_ok = False
|
last_ok = False
|
||||||
|
|
@ -92,7 +92,9 @@ def run_command(cmd, only_first_line=False, pre_ver_text=None, no_err_2=False):
|
||||||
print('Output from command: '+e.output.decode())
|
print('Output from command: '+e.output.decode())
|
||||||
last_ok = False
|
last_ok = False
|
||||||
return UNKNOWN
|
return UNKNOWN
|
||||||
res = cmd_output.decode().strip()
|
res = res_run.stdout.decode().strip()
|
||||||
|
if len(res) == 0:
|
||||||
|
res = res_run.stderr.decode().strip()
|
||||||
if only_first_line:
|
if only_first_line:
|
||||||
res = res.split('\n')[0]
|
res = res.split('\n')[0]
|
||||||
pre_vers = (cmd[0]+' version ', cmd[0]+' ', pre_ver_text)
|
pre_vers = (cmd[0]+' version ', cmd[0]+' ', pre_ver_text)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
# Example KiBot config file for a basic 3D stencil
|
||||||
|
kibot:
|
||||||
|
version: 1
|
||||||
|
|
||||||
|
outputs:
|
||||||
|
- name: 'stencil'
|
||||||
|
comment: "Creates a 3D printable stencil"
|
||||||
|
type: stencil_3d
|
||||||
|
dir: stencil/3D
|
||||||
Loading…
Reference in New Issue