Skip to content

Commit d61bd56

Browse files
committed
Add option to show DNP components in outline mode
Fixes #75
1 parent 4a24edf commit d61bd56

File tree

6 files changed

+64
-37
lines changed

6 files changed

+64
-37
lines changed

DATAFORMAT.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ pcbdata = {
3030
},
3131
// Describes footprints.
3232
// See footprint structure description below.
33+
// index of entry corresponds to component's numeric ID
3334
"modules": [
3435
footprint1,
3536
footprint2,
@@ -48,6 +49,8 @@ pcbdata = {
4849
"both": [bomrow1, bomrow2, ...],
4950
"F": [bomrow1, bomrow2, ...],
5051
"B": [bomrow1, bomrow2, ...],
52+
// numeric IDs of DNP components that are not in BOM
53+
"skipped": [id1, id2, ...]
5154
},
5255
// Contains parsed stroke data from newstroke font for
5356
// characters used on the pcb.

InteractiveHtmlBom/core/ibom.py

Lines changed: 35 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,11 @@ def warn(self, msg):
4646
wx.LogWarning(msg)
4747

4848

49-
log = None # type: Logger
49+
log = None # type: Logger or None
5050

5151

52-
def skip_component(m, config, extra_data, filter_layer):
53-
# type: (Component, Config, dict, str) -> bool
54-
# filter part by layer
55-
if filter_layer is not None and filter_layer != m.layer:
56-
return True
57-
52+
def skip_component(m, config, extra_data):
53+
# type: (Component, Config, dict) -> bool
5854
# skip blacklisted components
5955
ref_prefix = re.findall('^[A-Z]*', m.ref)[0]
6056
if m.ref in config.component_blacklist:
@@ -65,6 +61,10 @@ def skip_component(m, config, extra_data, filter_layer):
6561
if config.blacklist_empty_val and m.val in ['', '~']:
6662
return True
6763

64+
# skip virtual components if needed
65+
if config.blacklist_virtual and m.attr == 'Virtual':
66+
return True
67+
6868
# skip components with dnp field not empty
6969
if config.dnp_field and m.ref in extra_data \
7070
and config.dnp_field in extra_data[m.ref] \
@@ -87,15 +87,14 @@ def skip_component(m, config, extra_data, filter_layer):
8787
return False
8888

8989

90-
def generate_bom(pcb_modules, config, extra_data, filter_layer=None):
91-
# type: (list, Config, dict, str) -> list
90+
def generate_bom(pcb_modules, config, extra_data):
91+
# type: (list, Config, dict) -> dict
9292
"""
9393
Generate BOM from pcb layout.
9494
:param pcb_modules: list of modules on the pcb
9595
:param config: Config object
9696
:param extra_data: Extra fields data
97-
:param filter_layer: include only parts for given layer
98-
:return: BOM table (qty, value, footprint, refs)
97+
:return: dict of BOM tables (qty, value, footprint, refs) and dnp components
9998
"""
10099

101100
def convert(text):
@@ -114,18 +113,16 @@ def natural_sort(l):
114113

115114
# build grouped part list
116115
warning_shown = False
116+
skipped_components = []
117117
part_groups = {}
118118
for i, m in enumerate(pcb_modules):
119-
if skip_component(m, config, extra_data, filter_layer):
119+
if skip_component(m, config, extra_data):
120+
skipped_components.append(i)
120121
continue
121122

122123
# group part refs by value and footprint
123124
norm_value = units.componentValue(m.val)
124125

125-
# skip virtual components if needed
126-
if config.blacklist_virtual and m.attr == 'Virtual':
127-
continue
128-
129126
extras = []
130127
if config.extra_fields:
131128
if m.ref in extra_data:
@@ -134,11 +131,9 @@ def natural_sort(l):
134131
else:
135132
# Some components are on pcb but not in schematic data.
136133
# Show a warning about possibly outdated netlist/xml file.
137-
# Doing it only once when generating full bom is enough.
138-
if filter_layer is None:
139-
log.warn(
140-
'Component %s is missing from schematic data.' % m.ref)
141-
warning_shown = True
134+
log.warn(
135+
'Component %s is missing from schematic data.' % m.ref)
136+
warning_shown = True
142137
extras = [''] * len(config.extra_fields)
143138

144139
group_key = (norm_value, tuple(extras), m.footprint, m.attr)
@@ -169,7 +164,23 @@ def sort_func(row):
169164
config.component_sort_order.append('~')
170165
bom_table = sorted(bom_table, key=sort_func)
171166

172-
return bom_table
167+
result = {
168+
'both': bom_table,
169+
'skipped': skipped_components
170+
}
171+
172+
for layer in ['F', 'B']:
173+
filtered_table = []
174+
for row in bom_table:
175+
filtered_refs = [ref for ref in row[3]
176+
if pcb_modules[ref[1]].layer == layer]
177+
if filtered_refs:
178+
filtered_table.append((len(filtered_refs), row[1],
179+
row[2], filtered_refs, row[4]))
180+
181+
result[layer] = sorted(filtered_table, key=sort_func)
182+
183+
return result
173184

174185

175186
def open_file(filename):
@@ -274,14 +285,10 @@ def main(parser, config, logger):
274285
if not pcbdata or not components:
275286
logger.error('Parsing failed.')
276287
return
277-
pcbdata["bom"]["both"] = generate_bom(components, config, extra_fields)
278288

279-
# build BOM
280-
for layer in ['F', 'B']:
281-
bom_table = generate_bom(components, config, extra_fields,
282-
filter_layer=layer)
283-
pcbdata["bom"][layer] = bom_table
289+
pcbdata["bom"] = generate_bom(components, config, extra_fields)
284290

291+
# build BOM
285292
bom_file = generate_file(pcb_file_dir, pcb_file_name, pcbdata, config)
286293

287294
if config.open_browser:

InteractiveHtmlBom/web/ibom.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@
7171
<input id="valuesCheckbox" type="checkbox" checked onchange="valuesVisible(this.checked)">
7272
Values
7373
</label>
74+
<label class="menu-label">
75+
<input id="dnpOutlineCheckbox" type="checkbox" checked onchange="dnpOutline(this.checked)">
76+
DNP components outlined
77+
</label>
7478
<label class="menu-label">
7579
<input id="highlightpin1Checkbox" type="checkbox" onchange="setHighlightPin1(this.checked)">
7680
Highlight first pin

InteractiveHtmlBom/web/ibom.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ function valuesVisible(value) {
4646
redrawIfInitDone();
4747
}
4848

49+
function dnpOutline(value) {
50+
writeStorage("dnpOutline", value);
51+
renderDnpOutline = value;
52+
redrawIfInitDone();
53+
}
54+
4955
function setDarkMode(value) {
5056
if (value) {
5157
topmostdiv.classList.add("dark");
@@ -775,6 +781,10 @@ function initDefaults() {
775781
document.getElementById("valuesCheckbox").checked = b;
776782
valuesVisible(b);
777783

784+
b = getStorageBooleanOrDefault("dnpOutline", false);
785+
document.getElementById("dnpOutlineCheckbox").checked = b;
786+
dnpOutline(b);
787+
778788
b = getStorageBooleanOrDefault("redrawOnDrag", config.redraw_on_drag);
779789
document.getElementById("dragCheckbox").checked = b;
780790
setRedrawOnDrag(b);

InteractiveHtmlBom/web/render.js

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ var boardRotation = 0;
55
var renderPads = true;
66
var renderReferences = true;
77
var renderValues = true;
8+
var renderDnpOutline = false;
89

910
function deg2rad(deg) {
1011
return deg * Math.PI / 180;
@@ -182,7 +183,7 @@ function drawCircle(ctx, radius, ctxmethod) {
182183
ctxmethod();
183184
}
184185

185-
function drawPad(ctx, pad, color, outline) {
186+
function drawPad(ctx, pad, color, outline, hole) {
186187
ctx.save();
187188
ctx.translate(...pad.pos);
188189
ctx.rotate(deg2rad(pad.angle));
@@ -208,7 +209,8 @@ function drawPad(ctx, pad, color, outline) {
208209
} else if (pad.shape == "custom") {
209210
drawPolygons(ctx, color, pad.polygons, ctxmethod);
210211
}
211-
if (pad.type == "th" && !outline) {
212+
if (pad.type == "th" && hole) {
213+
ctxmethod = ctx.fill.bind(ctx);
212214
ctx.fillStyle = "#CCCCCC";
213215
if (pad.drillshape == "oblong") {
214216
drawOblong(ctx, "#CCCCCC", pad.drillsize, ctxmethod);
@@ -219,7 +221,7 @@ function drawPad(ctx, pad, color, outline) {
219221
ctx.restore();
220222
}
221223

222-
function drawModule(ctx, layer, scalefactor, module, padcolor, outlinecolor, highlight) {
224+
function drawModule(ctx, layer, scalefactor, module, padcolor, outlinecolor, highlight, outline) {
223225
if (highlight) {
224226
// draw bounding box
225227
if (module.layer == layer) {
@@ -248,9 +250,9 @@ function drawModule(ctx, layer, scalefactor, module, padcolor, outlinecolor, hig
248250
if (renderPads) {
249251
for (var pad of module.pads) {
250252
if (pad.layers.includes(layer)) {
251-
drawPad(ctx, pad, padcolor, false);
253+
drawPad(ctx, pad, padcolor, outline, true);
252254
if (pad.pin1 && highlightpin1) {
253-
drawPad(ctx, pad, outlinecolor, true);
255+
drawPad(ctx, pad, outlinecolor, true, false);
254256
}
255257
}
256258
}
@@ -271,14 +273,15 @@ function drawModules(canvas, layer, scalefactor, highlight) {
271273
var style = getComputedStyle(topmostdiv);
272274
var padcolor = style.getPropertyValue('--pad-color');
273275
var outlinecolor = style.getPropertyValue('--pin1-outline-color');
274-
if (highlight > 0) {
276+
if (highlight) {
275277
padcolor = style.getPropertyValue('--pad-color-highlight');
276278
outlinecolor = style.getPropertyValue('--pin1-outline-color-highlight');
277279
}
278280
for (var i = 0; i < pcbdata.modules.length; i++) {
279281
var mod = pcbdata.modules[i];
282+
var outline = renderDnpOutline && pcbdata.bom.skipped.includes(i);
280283
if (!highlight || highlightedModules.includes(i)) {
281-
drawModule(ctx, layer, scalefactor, mod, padcolor, outlinecolor, highlight);
284+
drawModule(ctx, layer, scalefactor, mod, padcolor, outlinecolor, highlight, outline);
282285
}
283286
}
284287
}

InteractiveHtmlBom/web/util.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -177,8 +177,8 @@ function initUtils() {
177177
"([GgMmKkUuNnPp])?" +
178178
"([0-9]*)" +
179179
"(\\b.*)?$", "");
180-
for (var bomtable of Object.values(pcbdata.bom)) {
181-
for (var row of bomtable) {
180+
for (var bom_type of ["both", "F", "B"]) {
181+
for (var row of pcbdata.bom[bom_type]) {
182182
row.push(parseValue(row[1], row[3][0][0]));
183183
}
184184
}

0 commit comments

Comments
 (0)