parent
4d5220006f
commit
278de8fb16
|
|
@ -60,6 +60,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Support for `--subst-models` option for KiCad 6's kicad2step. (#137)
|
||||
- Added global options to define the PCB details (`pcb_material`,
|
||||
`solder_mask_color`, `silk_screen_color` and `pcb_finish`)
|
||||
- Report generation (for design house) (#93)
|
||||
|
||||
### Changed
|
||||
- Internal BoM: now components with different Tolerance, Voltage, Current
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
include MANIFEST.in
|
||||
include LICENSE
|
||||
include README.md
|
||||
include kibot/report_templates/*.txt
|
||||
|
|
|
|||
19
README.md
19
README.md
|
|
@ -1771,6 +1771,25 @@ Next time you need this list just use an alias, like this:
|
|||
- `output_id`: [string=''] Text to use for the %I expansion content. To differentiate variations of this output.
|
||||
- `run_by_default`: [boolean=true] When enabled this output will be created when no specific outputs are requested.
|
||||
|
||||
* Design report
|
||||
* Type: `report`
|
||||
* Description: Generates a report about the design.
|
||||
Mainly oriented to be sent to the manufacturer or check PCB details.
|
||||
* 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.
|
||||
- `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.
|
||||
- `name`: [string=''] Used to identify this particular output definition.
|
||||
- `options`: [dict] Options for the `report` output.
|
||||
* Valid keys:
|
||||
- `output`: [string='%f-%i%v.%x'] Output file name (%i='report', %x='txt'). Affected by global options.
|
||||
- `template`: [string='full'] Name for one of the internal templates (full, simple) or a custom template file.
|
||||
- `output_id`: [string=''] Text to use for the %I expansion content. To differentiate variations of this output.
|
||||
- `run_by_default`: [boolean=true] When enabled this output will be created when no specific outputs are requested.
|
||||
|
||||
* Schematic with variant generator
|
||||
* Type: `sch_variant`
|
||||
* Description: Creates a copy of the schematic with all the filters and variants applied.
|
||||
|
|
|
|||
|
|
@ -1232,6 +1232,18 @@ outputs:
|
|||
# Same result as using the mouse wheel in the 3D viewer
|
||||
zoom: 0
|
||||
|
||||
# Design report:
|
||||
# Mainly oriented to be sent to the manufacturer or check PCB details.
|
||||
- name: 'report_example'
|
||||
comment: 'Generates a report about the design.'
|
||||
type: 'report'
|
||||
dir: 'Example/report_dir'
|
||||
options:
|
||||
# [string='%f-%i%v.%x'] Output file name (%i='report', %x='txt'). Affected by global options
|
||||
output: '%f-%i%v.%x'
|
||||
# [string='full'] Name for one of the internal templates (full, simple) or a custom template file
|
||||
template: 'full'
|
||||
|
||||
# Schematic with variant generator:
|
||||
# This copy isn't intended for development.
|
||||
# Is just a tweaked version of the original where you can look at the results.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,464 @@
|
|||
# -*- 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)
|
||||
import os
|
||||
import re
|
||||
import pcbnew
|
||||
|
||||
from .gs import GS
|
||||
from .misc import UI_SMD, UI_VIRTUAL, MOD_THROUGH_HOLE, MOD_SMD, MOD_EXCLUDE_FROM_POS_FILES
|
||||
from .out_base import BaseOptions
|
||||
from .error import KiPlotConfigurationError
|
||||
from .macros import macros, document, output_class # noqa: F401
|
||||
from . import log
|
||||
|
||||
logger = log.get_logger()
|
||||
INF = float('inf')
|
||||
|
||||
|
||||
def do_round(v, dig):
|
||||
v = round(v+1e-9, dig)
|
||||
return v if dig else int(v)
|
||||
|
||||
|
||||
def to_mm(iu, dig=2):
|
||||
""" KiCad Internal Units to millimeters """
|
||||
return do_round(iu/pcbnew.IU_PER_MM, dig)
|
||||
|
||||
|
||||
def to_mils(iu, dig=0):
|
||||
""" KiCad Internal Units to mils (1/1000 inch) """
|
||||
return do_round(iu/pcbnew.IU_PER_MILS, dig)
|
||||
|
||||
|
||||
def to_inches(iu, dig=2):
|
||||
""" KiCad Internal Units to inches """
|
||||
return do_round(iu/(pcbnew.IU_PER_MILS*1000), dig)
|
||||
|
||||
|
||||
def get_class_index(val, lst):
|
||||
""" Used to search in an Eurocircuits class vector.
|
||||
Returns the first match that is >= to val. """
|
||||
val = to_mm(val, 3)
|
||||
for c, v in enumerate(lst):
|
||||
if val >= v:
|
||||
return c
|
||||
return c+1
|
||||
|
||||
|
||||
def get_pattern_class(track, clearance, oar):
|
||||
""" Returns the Eurocircuits Pattern class for a track width, clearance and OAR """
|
||||
c1 = (0.25, 0.2, 0.175, 0.150, 0.125, 0.1, 0.09)
|
||||
c2 = (0.2, 0.15, 0.15, 0.125, 0.125, 0.1, 0.1)
|
||||
ct = get_class_index(track, c1)
|
||||
cc = get_class_index(clearance, c1)
|
||||
co = get_class_index(oar, c2)
|
||||
cf = max(ct, max(cc, co))
|
||||
return cf + 3
|
||||
|
||||
|
||||
def get_drill_class(via_drill):
|
||||
""" Returns the Eurocircuits Drill class for a drill size.
|
||||
This is the real (tool) size. """
|
||||
c3 = (0.6, 0.45, 0.35, 0.25, 0.2)
|
||||
cd = get_class_index(via_drill, c3)
|
||||
return chr(ord('A') + cd)
|
||||
|
||||
|
||||
def to_top_bottom(front, bottom):
|
||||
""" Returns a text indicating if the feature is in top/bottom layers """
|
||||
if front and bottom:
|
||||
return "TOP / BOTTOM"
|
||||
elif front:
|
||||
return "TOP"
|
||||
elif bottom:
|
||||
return "BOTTOM"
|
||||
return "NONE"
|
||||
|
||||
|
||||
def to_smd_tht(smd, tht):
|
||||
""" Returns a text indicating if the components are SMD/THT """
|
||||
if smd and tht:
|
||||
return "SMD + THT"
|
||||
elif smd:
|
||||
return "SMD"
|
||||
elif tht:
|
||||
return "THT"
|
||||
return "NONE"
|
||||
|
||||
|
||||
class ReportOptions(BaseOptions):
|
||||
def __init__(self):
|
||||
with document:
|
||||
self.output = GS.def_global_output
|
||||
""" Output file name (%i='report', %x='txt') """
|
||||
self.template = 'full'
|
||||
""" Name for one of the internal templates (full, simple) or a custom template file """
|
||||
super().__init__()
|
||||
self._expand_id = 'report'
|
||||
self._expand_ext = 'txt'
|
||||
self._mm_digits = 2
|
||||
self._mils_digits = 0
|
||||
self._in_digits = 2
|
||||
|
||||
def config(self, parent):
|
||||
super().config(parent)
|
||||
if self.template.lower() in ('full', 'simple'):
|
||||
self.template = os.path.abspath(os.path.join(os.path.dirname(__file__), 'report_templates',
|
||||
'report_'+self.template.lower()+'.txt'))
|
||||
if not os.path.isfile(self.template):
|
||||
raise KiPlotConfigurationError("Missing report template: `{}`".format(self.template))
|
||||
|
||||
def do_replacements(self, line, defined):
|
||||
""" Replace ${VAR} patterns """
|
||||
for var in re.findall(r'\$\{([^\s\}]+)\}', line):
|
||||
if var[0] == '_':
|
||||
# Prevent access to internal data
|
||||
continue
|
||||
units = None
|
||||
var_ori = var
|
||||
if var.endswith('_mm'):
|
||||
units = to_mm
|
||||
digits = self._mm_digits
|
||||
var = var[:-3]
|
||||
elif var.endswith('_in'):
|
||||
units = to_inches
|
||||
digits = self._in_digits
|
||||
var = var[:-3]
|
||||
elif var.endswith('_mils'):
|
||||
units = to_mils
|
||||
digits = self._mils_digits
|
||||
var = var[:-5]
|
||||
if var in defined:
|
||||
val = defined[var]
|
||||
if val == INF:
|
||||
val = 'N/A'
|
||||
elif units is not None and isinstance(val, (int, float)):
|
||||
val = units(val, digits)
|
||||
line = line.replace('${'+var_ori+'}', str(val))
|
||||
else:
|
||||
print('Error: Unable to expand `{}`'.format(var))
|
||||
return line
|
||||
|
||||
def context_defined_tracks(self, line):
|
||||
""" Replace iterator for the `defined_tracks` context """
|
||||
text = ''
|
||||
for t in sorted(self._track_sizes):
|
||||
if not t:
|
||||
continue # KiCad 6
|
||||
text += self.do_replacements(line, {'track': t})
|
||||
return text
|
||||
|
||||
def context_used_tracks(self, line):
|
||||
""" Replace iterator for the `used_tracks` context """
|
||||
text = ''
|
||||
for t in sorted(self._tracks_m.keys()):
|
||||
text += self.do_replacements(line, {'track': t, 'count': self._tracks_m[t],
|
||||
'defined': 'yes' if t in self._tracks_defined else 'no'})
|
||||
return text
|
||||
|
||||
def context_defined_vias(self, line):
|
||||
""" Replace iterator for the `defined_vias` context """
|
||||
text = ''
|
||||
for v in self._via_sizes_sorted:
|
||||
text += self.do_replacements(line, {'pad': v[1], 'drill': v[0]})
|
||||
return text
|
||||
|
||||
def context_used_vias(self, line):
|
||||
""" Replace iterator for the `used_vias` context """
|
||||
text = ''
|
||||
for v in self._vias_m:
|
||||
d = v[1]
|
||||
h = v[0]
|
||||
aspect = round(self.thickness/d, 1)
|
||||
# IPC-2222 Table 9.4
|
||||
producibility_level = 'C'
|
||||
if aspect < 9:
|
||||
if aspect < 5:
|
||||
producibility_level = 'A'
|
||||
else:
|
||||
producibility_level = 'B'
|
||||
defined = {'pad': v[1], 'drill': v[0]}
|
||||
defined['count'] = self._vias[v]
|
||||
defined['aspect'] = aspect
|
||||
defined['producibility_level'] = producibility_level
|
||||
defined['defined'] = 'yes' if (h, d) in self._vias_defined else 'no'
|
||||
text += self.do_replacements(line, defined)
|
||||
return text
|
||||
|
||||
def context_hole_sizes_no_vias(self, line):
|
||||
""" Replace iterator for the `hole_sizes_no_vias` context """
|
||||
text = ''
|
||||
for d in sorted(self._drills.keys()):
|
||||
text += self.do_replacements(line, {'drill': d, 'count': self._drills[d]})
|
||||
return text
|
||||
|
||||
@staticmethod
|
||||
def is_pure_smd_5(m):
|
||||
return m.GetAttributes() == UI_SMD
|
||||
|
||||
@staticmethod
|
||||
def is_pure_smd_6(m):
|
||||
return m.GetAttributes() & (MOD_THROUGH_HOLE | MOD_SMD) == MOD_SMD
|
||||
|
||||
@staticmethod
|
||||
def is_not_virtual_5(m):
|
||||
return m.GetAttributes() != UI_VIRTUAL
|
||||
|
||||
@staticmethod
|
||||
def is_not_virtual_6(m):
|
||||
return not (m.GetAttributes() & MOD_EXCLUDE_FROM_POS_FILES)
|
||||
|
||||
def get_attr_tests(self):
|
||||
if GS.ki5():
|
||||
return self.is_pure_smd_5, self.is_not_virtual_5
|
||||
return self.is_pure_smd_6, self.is_not_virtual_6
|
||||
|
||||
def meassure_pcb(self, board):
|
||||
edge_layer = board.GetLayerID('Edge.Cuts')
|
||||
x1 = y1 = x2 = y2 = None
|
||||
draw_type = 'DRAWSEGMENT' if GS.ki5() else 'PCB_SHAPE'
|
||||
for d in board.GetDrawings():
|
||||
if d.GetClass() == draw_type and d.GetLayer() == edge_layer:
|
||||
if x1 is None:
|
||||
p = d.GetStart()
|
||||
x1 = x2 = p.x
|
||||
y1 = y2 = p.y
|
||||
for p in [d.GetStart(), d.GetEnd()]:
|
||||
x2 = max(x2, p.x)
|
||||
y2 = max(y2, p.y)
|
||||
x1 = min(x1, p.x)
|
||||
y1 = min(y1, p.y)
|
||||
if x1 is None:
|
||||
self.bb_w = self.bb_h = INF
|
||||
else:
|
||||
self.bb_w = x2-x1
|
||||
self.bb_h = y2-y1
|
||||
|
||||
def collect_data(self, board):
|
||||
ds = board.GetDesignSettings()
|
||||
###########################################################
|
||||
# Board size
|
||||
###########################################################
|
||||
# The value returned by ComputeBoundingBox(True) adds the drawing width!
|
||||
bb = board.ComputeBoundingBox(True)
|
||||
self.bb_w_d = bb.GetWidth()
|
||||
self.bb_h_d = bb.GetHeight()
|
||||
self.meassure_pcb(board)
|
||||
###########################################################
|
||||
# Board thickness
|
||||
###########################################################
|
||||
self.thickness = ds.GetBoardThickness()
|
||||
###########################################################
|
||||
# Number of layers
|
||||
###########################################################
|
||||
self.layers = ds.GetCopperLayerCount()
|
||||
###########################################################
|
||||
# Solder mask layers
|
||||
###########################################################
|
||||
fmask = board.IsLayerEnabled(board.GetLayerID('F.Mask'))
|
||||
bmask = board.IsLayerEnabled(board.GetLayerID('B.Mask'))
|
||||
self.solder_mask = to_top_bottom(fmask, bmask)
|
||||
###########################################################
|
||||
# Silk screen
|
||||
###########################################################
|
||||
fsilk = board.IsLayerEnabled(board.GetLayerID('F.SilkS'))
|
||||
bsilk = board.IsLayerEnabled(board.GetLayerID('B.SilkS'))
|
||||
self.silk_screen = to_top_bottom(fsilk, bsilk)
|
||||
###########################################################
|
||||
# Clearance
|
||||
###########################################################
|
||||
self.clearance = ds.GetSmallestClearanceValue()
|
||||
# This seems to be bogus:
|
||||
# h2h = ds.m_HoleToHoleMin
|
||||
###########################################################
|
||||
# Track width (min)
|
||||
###########################################################
|
||||
self.track_d = ds.m_TrackMinWidth
|
||||
tracks = board.GetTracks()
|
||||
self.oar_vias = self.track = INF
|
||||
self._vias = {}
|
||||
self._tracks_m = {}
|
||||
track_type = 'TRACK' if GS.ki5() else 'PCB_TRACK'
|
||||
via_type = 'VIA' if GS.ki5() else 'PCB_VIA'
|
||||
for t in tracks:
|
||||
tclass = t.GetClass()
|
||||
if tclass == track_type:
|
||||
w = t.GetWidth()
|
||||
self.track = min(w, self.track)
|
||||
self._tracks_m[w] = self._tracks_m.get(w, 0) + 1
|
||||
elif tclass == via_type:
|
||||
via = t.Cast()
|
||||
via_id = (via.GetDrill(), via.GetWidth())
|
||||
self._vias[via_id] = self._vias.get(via_id, 0) + 1
|
||||
self.oar_vias = min(self.oar_vias, via_id[1] - via_id[0])
|
||||
self.track_min = min(self.track_d, self.track)
|
||||
###########################################################
|
||||
# Drill (min)
|
||||
###########################################################
|
||||
modules = board.GetModules() if GS.ki5() else board.GetFootprints()
|
||||
self._drills = {}
|
||||
self._drills_oval = {}
|
||||
self.oar_pads = self.pad_drill = INF
|
||||
self.slot = INF
|
||||
self.top_smd = self.top_tht = self.bot_smd = self.bot_tht = 0
|
||||
top_layer = board.GetLayerID('F.Cu')
|
||||
bottom_layer = board.GetLayerID('B.Cu')
|
||||
is_pure_smd, is_not_virtual = self.get_attr_tests()
|
||||
for m in modules:
|
||||
layer = m.GetLayer()
|
||||
if layer == top_layer:
|
||||
if is_pure_smd(m):
|
||||
self.top_smd += 1
|
||||
elif is_not_virtual(m):
|
||||
self.top_tht += 1
|
||||
elif layer == bottom_layer:
|
||||
if is_pure_smd(m):
|
||||
self.bot_smd += 1
|
||||
elif is_not_virtual(m):
|
||||
self.bot_tht += 1
|
||||
pads = m.Pads()
|
||||
for pad in pads:
|
||||
dr = pad.GetDrillSize()
|
||||
if not dr.x:
|
||||
continue
|
||||
self.pad_drill = min(dr.x, self.pad_drill)
|
||||
self.pad_drill = min(dr.y, self.pad_drill)
|
||||
if dr.x == dr.y:
|
||||
self._drills[dr.x] = self._drills.get(dr.x, 0) + 1
|
||||
else:
|
||||
if dr.x < dr.y:
|
||||
m = (dr.x, dr.y)
|
||||
else:
|
||||
m = (dr.y, dr.x)
|
||||
self._drills_oval[m] = self._drills_oval.get(m, 0) + 1
|
||||
self.slot = min(self.slot, m[0])
|
||||
# print('{} @ {}'.format(dr, pad.GetPosition()))
|
||||
pad_sz = pad.GetSize()
|
||||
oar_x = pad_sz.x - dr.x
|
||||
oar_y = pad_sz.y - dr.y
|
||||
oar_t = min(oar_x, oar_y)
|
||||
if oar_t:
|
||||
self.oar_pads = min(self.oar_pads, oar_t)
|
||||
self._vias_m = list(sorted(self._vias.keys()))
|
||||
# Via Pad size
|
||||
self.via_pad_d = ds.m_ViasMinSize
|
||||
self.via_pad = self._vias_m[0][1]
|
||||
self.via_pad_min = min(self.via_pad_d, self.via_pad)
|
||||
# Via Drill size
|
||||
self.via_drill_d = ds.m_ViasMinDrill if GS.ki5() else ds.m_MinThroughDrill
|
||||
self.via_drill = self._vias_m[0][0]
|
||||
self.via_drill_min = min(self.via_drill_d, self.via_drill)
|
||||
# Via Drill size minus 0.1 mm
|
||||
self.via_drill_1_d = self.via_drill_d - pcbnew.IU_PER_MM/10
|
||||
self.via_drill_1 = self.via_drill - pcbnew.IU_PER_MM/10
|
||||
self.via_drill_1_min = self.via_drill_min - pcbnew.IU_PER_MM/10
|
||||
# Pad Drill
|
||||
# No minimum defined
|
||||
self.pad_drill_min = self.pad_drill if GS.ki5() else ds.m_MinThroughDrill
|
||||
# Pad Drill size minus 0.1 mm
|
||||
self.pad_drill_1 = self.pad_drill_1_min = self.pad_drill - pcbnew.IU_PER_MM/10
|
||||
# Drill overall
|
||||
self.drill_d = min(self.via_drill_d, self.pad_drill)
|
||||
self.drill = min(self.via_drill, self.pad_drill)
|
||||
self.drill_min = min(self.via_drill_min, self.pad_drill_min)
|
||||
# Drill overall size minus 0.1 mm
|
||||
self.drill_1_d = self.drill_d - pcbnew.IU_PER_MM/10
|
||||
self.drill_1 = self.drill - pcbnew.IU_PER_MM/10
|
||||
self.drill_1_min = self.drill_min - pcbnew.IU_PER_MM/10
|
||||
self.top_comp_type = to_smd_tht(self.top_smd, self.top_tht)
|
||||
self.bot_comp_type = to_smd_tht(self.bot_smd, self.bot_tht)
|
||||
###########################################################
|
||||
# Vias
|
||||
###########################################################
|
||||
self.micro_vias = 'yes' if ds.m_MicroViasAllowed else 'no'
|
||||
self.blind_vias = 'yes' if ds.m_BlindBuriedViaAllowed else 'no'
|
||||
self.uvia_pad = ds.m_MicroViasMinSize
|
||||
self.uvia_drill = ds.m_MicroViasMinDrill
|
||||
via_sizes = board.GetViasDimensionsList()
|
||||
self._vias_defined = set()
|
||||
self._via_sizes_sorted = []
|
||||
self.oar_vias_d = INF
|
||||
for v in sorted(via_sizes, key=lambda x: (x.m_Diameter, x.m_Drill)):
|
||||
d = v.m_Diameter
|
||||
h = v.m_Drill
|
||||
if not d and not h:
|
||||
continue # KiCad 6
|
||||
self.oar_vias_d = min(self.oar_vias_d, d - h)
|
||||
self._vias_defined.add((h, d))
|
||||
self._via_sizes_sorted.append((h, d))
|
||||
###########################################################
|
||||
# Outer Annular Ring
|
||||
###########################################################
|
||||
self.oar_pads_min = self.oar_pads
|
||||
self.oar_d = min(self.oar_vias_d, self.oar_pads)
|
||||
self.oar = min(self.oar_vias, self.oar_pads)
|
||||
self.oar_min = min(self.oar_d, self.oar)
|
||||
self.oar_vias_min = min(self.oar_vias_d, self.oar_vias)
|
||||
###########################################################
|
||||
# Eurocircuits class
|
||||
# https://www.eurocircuits.com/pcb-design-guidelines-classification/
|
||||
###########################################################
|
||||
# Pattern class
|
||||
self.pattern_class_min = get_pattern_class(self.track_min, self.clearance, self.oar_min)
|
||||
self.pattern_class = get_pattern_class(self.track, self.clearance, self.oar)
|
||||
self.pattern_class_d = get_pattern_class(self.track_d, self.clearance, self.oar_d)
|
||||
# Drill class
|
||||
self.drill_class_min = get_drill_class(self.via_drill_min)
|
||||
self.drill_class = get_drill_class(self.via_drill)
|
||||
self.drill_class_d = get_drill_class(self.via_drill_d)
|
||||
###########################################################
|
||||
# General stats
|
||||
###########################################################
|
||||
self._track_sizes = board.GetTrackWidthList()
|
||||
self._tracks_defined = set(self._track_sizes)
|
||||
|
||||
def do_template(self, template_file, output_file):
|
||||
text = ''
|
||||
logger.debug("Report template: `{}`".format(template_file))
|
||||
with open(template_file, "rt") as f:
|
||||
for line in f:
|
||||
done = False
|
||||
if line[0] == '#' and ':' in line:
|
||||
context = line[1:].split(':')[0]
|
||||
logger.debug("Report context: `{}`".format(context))
|
||||
try:
|
||||
# Contexts are members called context_*
|
||||
line = getattr(self, 'context_'+context)(line[len(context)+2:])
|
||||
done = True
|
||||
except AttributeError:
|
||||
pass
|
||||
if not done:
|
||||
raise KiPlotConfigurationError("Unknown context: `{}`".format(context))
|
||||
if not done:
|
||||
# Just replace using any data member (_* excluded)
|
||||
line = self.do_replacements(line, self.__dict__)
|
||||
text += line
|
||||
logger.debug("Report output: `{}`".format(output_file))
|
||||
with open(output_file, "wt") as f:
|
||||
f.write(text)
|
||||
|
||||
def get_targets(self, out_dir):
|
||||
return [self._parent.expand_filename(out_dir, self.output)]
|
||||
|
||||
def run(self, fname):
|
||||
self.pcb_material = GS.global_pcb_material
|
||||
self.solder_mask_color = GS.global_solder_mask_color
|
||||
self.silk_screen_color = GS.global_silk_screen_color
|
||||
self.pcb_finish = GS.global_pcb_finish
|
||||
self.collect_data(GS.board)
|
||||
self.do_template(self.template, fname)
|
||||
|
||||
|
||||
@output_class
|
||||
class Report(BaseOutput): # noqa: F821
|
||||
""" Design report
|
||||
Generates a report about the design.
|
||||
Mainly oriented to be sent to the manufacturer or check PCB details. """
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
with document:
|
||||
self.options = ReportOptions
|
||||
""" [dict] Options for the `report` output """
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
# PCB
|
||||
|
||||
Board size: ${bb_w_mm}x${bb_h_mm} mm (${bb_w_in}x${bb_h_in} inches)
|
||||
- This is the size of the rectangle that contains the board
|
||||
- Thickness: ${thickness_mm} mm (${thickness_mils} mils)
|
||||
- Material: ${pcb_material}
|
||||
- Finish: ${pcb_finish}
|
||||
- Layers: ${layers}
|
||||
Solder mask: ${solder_mask}
|
||||
- Color: ${solder_mask_color}
|
||||
Silk screen: ${silk_screen}
|
||||
- Color: ${silk_screen_color}
|
||||
|
||||
|
||||
# Important sizes
|
||||
|
||||
Clearance: ${clearance_mm} mm (${clearance_mils} mils)
|
||||
Track width: ${track_mm} mm (${track_mils} mils)
|
||||
- By design rules: ${track_d_mm} mm (${track_d_mils} mils)
|
||||
Drill: ${drill_mm} mm (${drill_mils} mils)
|
||||
- Vias: ${via_drill_mm} mm (${via_drill_mils} mils) [Design: ${via_drill_d_mm} mm (${via_drill_d_mils} mils)]
|
||||
- Pads: ${pad_drill_mm} mm (${pad_drill_mils} mils)
|
||||
Via: ${via_pad_mm}/${via_drill_mm} mm (${via_pad_mils}/${via_drill_mils} mils)
|
||||
- By design rules: ${via_pad_d_mm}/${via_drill_d_mm} mm (${via_pad_d_mils}/${via_drill_d_mils} mils)
|
||||
- Micro via: ${micro_vias} [${uvia_pad_mm}/${uvia_drill_mm} mm (${uvia_pad_mils}/${uvia_drill_mils} mils)]
|
||||
- Burried/blind via: ${blind_vias}
|
||||
Outer Annular Ring: ${oar_mm} mm (${oar_mils} mils)
|
||||
- By design rules: ${oar_d_mm} mm (${oar_d_mils} mils)
|
||||
|
||||
Eurocircuits class: ${pattern_class}${drill_class}
|
||||
|
||||
|
||||
# General stats
|
||||
|
||||
Components count: (SMD/THT)
|
||||
- Top: ${top_smd}/${top_tht} (${top_comp_type})
|
||||
- Bottom: ${bot_smd}/${bot_tht} (${bot_comp_type})
|
||||
|
||||
Defined tracks:
|
||||
#defined_tracks:- ${track_mm} mm (${track_mils} mils)
|
||||
|
||||
Used tracks:
|
||||
#used_tracks:- ${track_mm} mm (${track_mils} mils) (${count}) defined: ${defined}
|
||||
|
||||
Defined vias:
|
||||
#defined_vias:- ${pad_mm}/${drill_mm} mm (${pad_mils}/${drill_mils} mils)
|
||||
|
||||
Used vias:
|
||||
#used_vias:- ${pad_mm}/${drill_mm} mm (${pad_mils}/${drill_mils} mils) (Count: ${count}, Aspect: ${aspect} ${producibility_level}) defined: ${defined}
|
||||
|
||||
Holes (excluding vias):
|
||||
#hole_sizes_no_vias:- ${drill_mm} mm (${drill_mils} mils) (${count})
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
PCB Specifications:
|
||||
Size:
|
||||
- ${bb_w_mm}x${bb_h_mm} mm
|
||||
|
||||
Class: ${pattern_class}${drill_class}
|
||||
Track width: ≥ ${track_mm} mm
|
||||
Insulation distance: ≥ ${clearance_mm} mm
|
||||
Minimum drill size: ≥ ${drill_mm} mm (finished metalized hole: ${drill_1_mm} mm)
|
||||
Minimum slot width: ≥ ${slot_mm} mm
|
||||
Ring collar: ≥ ${oar_mm} mm
|
||||
|
||||
|
||||
Materials:
|
||||
- ${pcb_material}, ${thickness_mm} mm
|
||||
- ${pcb_finish}
|
||||
- ${layers} layers
|
||||
- 35 µm copper thickness
|
||||
|
||||
Solder mask:
|
||||
- ${solder_mask}
|
||||
- ${solder_mask_color}
|
||||
|
||||
Marking:
|
||||
- ${silk_screen} screen printing
|
||||
- Silk: ${silk_screen_color}
|
||||
|
||||
Other markings:
|
||||
- ROHS / UL / Date - Yes if available
|
||||
1
setup.py
1
setup.py
|
|
@ -21,6 +21,7 @@ setup(name='kibot',
|
|||
packages=find_packages(),
|
||||
scripts=['src/kibot', 'src/kiplot'],
|
||||
install_requires=['kiauto', 'pyyaml', 'xlsxwriter', 'colorama', 'requests', 'qrcodegen'],
|
||||
include_package_data=True,
|
||||
classifiers=['Development Status :: 5 - Production/Stable',
|
||||
'Environment :: Console',
|
||||
'Intended Audience :: Developers',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
# PCB
|
||||
|
||||
Board size: 59.69x48.26 mm (2.35x1.9 inches)
|
||||
- This is the size of the rectangle that contains the board
|
||||
- Thickness: 1.6 mm (63 mils)
|
||||
- Material: FR4
|
||||
- Finish: ENIG
|
||||
- Layers: 4
|
||||
Solder mask: TOP / BOTTOM
|
||||
- Color: blue
|
||||
Silk screen: TOP / BOTTOM
|
||||
- Color: white
|
||||
|
||||
|
||||
# Important sizes
|
||||
|
||||
Clearance: 0.15 mm (6 mils)
|
||||
Track width: 0.15 mm (6 mils)
|
||||
- By design rules: 0.13 mm (5 mils)
|
||||
Drill: 0.25 mm (10 mils)
|
||||
- Vias: 0.25 mm (10 mils) [Design: 0.2 mm (8 mils)]
|
||||
- Pads: 0.6 mm (24 mils)
|
||||
Via: 0.51/0.25 mm (20/10 mils)
|
||||
- By design rules: 0.46/0.2 mm (18/8 mils)
|
||||
- Micro via: no [0.2/0.1 mm (8/4 mils)]
|
||||
- Burried/blind via: no
|
||||
Outer Annular Ring: 0.25 mm (10 mils)
|
||||
- By design rules: 0.25 mm (10 mils)
|
||||
|
||||
Eurocircuits class: 6D
|
||||
|
||||
|
||||
# General stats
|
||||
|
||||
Components count: (SMD/THT)
|
||||
- Top: 61/12 (SMD + THT)
|
||||
- Bottom: 0/0 (NONE)
|
||||
|
||||
Defined tracks:
|
||||
- 0.15 mm (6 mils)
|
||||
- 0.25 mm (10 mils)
|
||||
- 0.3 mm (12 mils)
|
||||
- 0.64 mm (25 mils)
|
||||
|
||||
Used tracks:
|
||||
- 0.15 mm (6 mils) (276) defined: yes
|
||||
- 0.3 mm (12 mils) (11) defined: yes
|
||||
- 0.64 mm (25 mils) (175) defined: yes
|
||||
|
||||
Defined vias:
|
||||
- 0.51/0.25 mm (20/10 mils)
|
||||
- 0.8/0.4 mm (31/16 mils)
|
||||
- 0.89/0.51 mm (35/20 mils)
|
||||
|
||||
Used vias:
|
||||
- 0.51/0.25 mm (20/10 mils) (Count: 23, Aspect: 3.1 A) defined: yes
|
||||
- 0.89/0.51 mm (35/20 mils) (Count: 33, Aspect: 1.8 A) defined: yes
|
||||
|
||||
Holes (excluding vias):
|
||||
- 0.8 mm (31 mils) (4)
|
||||
- 0.85 mm (33 mils) (2)
|
||||
- 0.95 mm (37 mils) (3)
|
||||
- 1.2 mm (47 mils) (20)
|
||||
- 3.2 mm (126 mils) (4)
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
PCB Specifications:
|
||||
Size:
|
||||
- 59.69x48.26 mm
|
||||
|
||||
Class: 6D
|
||||
Track width: ≥ 0.15 mm
|
||||
Insulation distance: ≥ 0.15 mm
|
||||
Minimum drill size: ≥ 0.25 mm (finished metalized hole: 0.15 mm)
|
||||
Minimum slot width: ≥ 0.6 mm
|
||||
Ring collar: ≥ 0.25 mm
|
||||
|
||||
|
||||
Materials:
|
||||
- FR4, 1.6 mm
|
||||
- ENIG
|
||||
- 4 layers
|
||||
- 35 µm copper thickness
|
||||
|
||||
Solder mask:
|
||||
- TOP / BOTTOM
|
||||
- blue
|
||||
|
||||
Marking:
|
||||
- TOP / BOTTOM screen printing
|
||||
- Silk: white
|
||||
|
||||
Other markings:
|
||||
- ROHS / UL / Date - Yes if available
|
||||
|
|
@ -0,0 +1 @@
|
|||
../5_1_6/light_control-report.txt
|
||||
|
|
@ -0,0 +1 @@
|
|||
../5_1_6/light_control-report_simple.txt
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
# PCB
|
||||
|
||||
Board size: 59.69x48.26 mm (2.35x1.9 inches)
|
||||
- This is the size of the rectangle that contains the board
|
||||
- Thickness: 1.6 mm (63 mils)
|
||||
- Material: FR4
|
||||
- Finish: ENIG
|
||||
- Layers: 4
|
||||
Solder mask: TOP / BOTTOM
|
||||
- Color: blue
|
||||
Silk screen: TOP / BOTTOM
|
||||
- Color: white
|
||||
|
||||
|
||||
# Important sizes
|
||||
|
||||
Clearance: 0.15 mm (6 mils)
|
||||
Track width: 0.15 mm (6 mils)
|
||||
- By design rules: 0.13 mm (5 mils)
|
||||
Drill: 0.25 mm (10 mils)
|
||||
- Vias: 0.25 mm (10 mils) [Design: 0.2 mm (8 mils)]
|
||||
- Pads: 0.6 mm (24 mils)
|
||||
Via: 0.51/0.25 mm (20/10 mils)
|
||||
- By design rules: 0.46/0.2 mm (18/8 mils)
|
||||
- Micro via: no [0.2/0.1 mm (8/4 mils)]
|
||||
- Burried/blind via: no
|
||||
Outer Annular Ring: 0.25 mm (10 mils)
|
||||
- By design rules: 0.25 mm (10 mils)
|
||||
|
||||
Eurocircuits class: 6D
|
||||
|
||||
|
||||
# General stats
|
||||
|
||||
Components count: (SMD/THT)
|
||||
- Top: 61/12 (SMD + THT)
|
||||
- Bottom: 0/0 (NONE)
|
||||
|
||||
Defined tracks:
|
||||
- 0.15 mm (6 mils)
|
||||
- 0.3 mm (12 mils)
|
||||
- 0.64 mm (25 mils)
|
||||
|
||||
Used tracks:
|
||||
- 0.15 mm (6 mils) (276) defined: yes
|
||||
- 0.3 mm (12 mils) (11) defined: yes
|
||||
- 0.64 mm (25 mils) (175) defined: yes
|
||||
|
||||
Defined vias:
|
||||
- 0.51/0.25 mm (20/10 mils)
|
||||
- 0.89/0.51 mm (35/20 mils)
|
||||
|
||||
Used vias:
|
||||
- 0.51/0.25 mm (20/10 mils) (Count: 23, Aspect: 3.1 A) defined: yes
|
||||
- 0.89/0.51 mm (35/20 mils) (Count: 33, Aspect: 1.8 A) defined: yes
|
||||
|
||||
Holes (excluding vias):
|
||||
- 0.8 mm (31 mils) (4)
|
||||
- 0.85 mm (33 mils) (2)
|
||||
- 0.95 mm (37 mils) (3)
|
||||
- 1.2 mm (47 mils) (20)
|
||||
- 3.2 mm (126 mils) (4)
|
||||
|
|
@ -0,0 +1 @@
|
|||
../5_1_6/light_control-report_simple.txt
|
||||
|
|
@ -991,3 +991,14 @@ def test_qr_lib_1(test_dir):
|
|||
if os.path.isfile(bkp):
|
||||
# Not always there, pcbnew_do can remove it
|
||||
os.remove(bkp)
|
||||
|
||||
|
||||
def test_report_simple_1(test_dir):
|
||||
prj = 'light_control'
|
||||
ctx = context.TestContext(test_dir, 'test_report_simple_1', prj, 'report_simple_1', POS_DIR)
|
||||
ctx.run()
|
||||
ctx.expect_out_file(prj+'-report.txt')
|
||||
ctx.expect_out_file(prj+'-report_simple.txt')
|
||||
ctx.compare_txt(prj+'-report.txt')
|
||||
ctx.compare_txt(prj+'-report_simple.txt')
|
||||
ctx.clean_up()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
# Example KiBot config file
|
||||
kibot:
|
||||
version: 1
|
||||
|
||||
global:
|
||||
solder_mask_color: blue
|
||||
pcb_finish: ENIG
|
||||
|
||||
outputs:
|
||||
- name: 'report_full'
|
||||
comment: "Full design report"
|
||||
type: report
|
||||
|
||||
- name: 'report_simple'
|
||||
comment: "Simple design report"
|
||||
type: report
|
||||
output_id: _simple
|
||||
options:
|
||||
template: simple
|
||||
|
||||
Loading…
Reference in New Issue