Skip to content

Commit 3c76ce2

Browse files
author
kuro68k
committed
No fit list and footer support
1 parent 69c17e4 commit 3c76ce2

File tree

7 files changed

+183
-30
lines changed

7 files changed

+183
-30
lines changed

custom_fields.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ bom_footprint substitute footprint name
44
precision e.g. 1%, X5R
55
bom_note text of note
66
bom_partno manufacturer's part number
7-
bom_no_part excludes part from BOM if set to "true"
7+
bom_no_part excludes part from BOM if set to "true" or "no part"
8+
bom_no_fit adds part to no fit list if set to "true" or "no fit"

kibom/Component.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ class Component
3939
public string note = "";
4040
public string code;
4141
public bool no_part = false;
42+
public bool no_fit = false;
4243

4344
static List<DefaultComp> default_list = new List<DefaultComp>();
4445

kibom/HeaderBlock.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ class HeaderBlock
1515
public string company;
1616
public string revision;
1717
public string source;
18-
18+
public string comment1;
19+
1920
public bool ParseHeader(XmlDocument doc)
2021
{
2122
try
@@ -29,6 +30,12 @@ public bool ParseHeader(XmlDocument doc)
2930
company = title_block.SelectSingleNode("company").InnerText;
3031
revision = title_block.SelectSingleNode("rev").InnerText;
3132
source = title_block.SelectSingleNode("source").InnerText;
33+
var comment_list = title_block.SelectNodes("comment");
34+
foreach (XmlNode node in comment_list)
35+
{
36+
if (node.Attributes["number"].InnerText == "1")
37+
comment1 = node.Attributes["value"].InnerText;
38+
}
3239
}
3340
catch
3441
{

kibom/Output.cs

Lines changed: 131 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Linq;
44
using System.Text;
55
using System.Threading.Tasks;
6+
using System.Drawing;
67
using MigraDoc.DocumentObjectModel;
78
using MigraDoc.Rendering;
89
using MigraDoc.DocumentObjectModel.Tables;
@@ -18,7 +19,7 @@ namespace kibom
1819
{
1920
class Output
2021
{
21-
public static void OutputXLSX(List<DesignatorGroup> groups, HeaderBlock header, string file, string template)
22+
public static void OutputXLSX(string path, List<DesignatorGroup> groups, HeaderBlock header, string file, string template, string footer, bool nfl)
2223
{
2324
ExcelPackage p = null;
2425
ExcelWorksheet ws;
@@ -40,11 +41,11 @@ public static void OutputXLSX(List<DesignatorGroup> groups, HeaderBlock header,
4041
table_x--;
4142
foreach (DesignatorGroup g in groups)
4243
{
43-
// check for groups that are entire "no part"
44+
// check for groups that are entirly "no part" or "no fit"
4445
bool all_no_part = true;
4546
foreach (Component c in g.comp_list)
4647
{
47-
if (!c.no_part)
48+
if (!c.no_part && !c.no_fit)
4849
all_no_part = false;
4950
}
5051
if (all_no_part)
@@ -64,11 +65,7 @@ public static void OutputXLSX(List<DesignatorGroup> groups, HeaderBlock header,
6465
ws.Cells[row, table_x + 1].Value = g.designator;
6566
ws.Cells[row, table_x + 2].Value = g.comp_list.Count.ToString() + " value(s)";
6667
}
67-
ws.Cells[row, table_x + 1].Style.Font.Bold = true;
68-
r = ws.Cells[row, table_x + 1, row, table_x + 6];
69-
r.Style.Fill.PatternType = ExcelFillStyle.Solid;
70-
r.Style.Fill.BackgroundColor.SetColor(System.Drawing.Color.LightGray);
71-
r.Style.Border.BorderAround(ExcelBorderStyle.Thin, System.Drawing.Color.LightGray);
68+
XLXSStyleHeader(row, table_x, ref ws);
7269
row++;
7370

7471
// component list
@@ -100,10 +97,129 @@ public static void OutputXLSX(List<DesignatorGroup> groups, HeaderBlock header,
10097
r.Style.VerticalAlignment = ExcelVerticalAlignment.Top;
10198
r.Style.WrapText = true;
10299

100+
if (nfl) // no fit part list
101+
XLXSNoFitList(ref row, table_x, ref ws, groups);
102+
103+
XLXSFooter(path, footer, ref ws, ref row);
104+
105+
// generate output file
103106
byte[] bin = p.GetAsByteArray();
104107
File.WriteAllBytes(file, bin);
105108
}
106109

110+
// Excel has a limitation where it can't do auto cell sizing with merged cells, so we have to calculate it manually
111+
// https://stackoverflow.com/questions/41639278/autofit-row-height-of-merged-cell-in-epplus
112+
private static double XLXSMeasureTextHeight(string text, ExcelFont font, double width)
113+
{
114+
if (string.IsNullOrEmpty(text))
115+
return 0.0;
116+
var bitmap = new Bitmap(1, 1);
117+
var graphics = Graphics.FromImage(bitmap);
118+
119+
var pixelWidth = Convert.ToInt32(width * 7.5); // 7.5 pixels per excel column width
120+
var drawingFont = new System.Drawing.Font(font.Name, font.Size);
121+
var size = graphics.MeasureString(text, drawingFont, pixelWidth);
122+
123+
// 72 DPI and 96 points per inch. Excel height in points with max of 409 per Excel requirements.
124+
return Math.Min(Convert.ToDouble(size.Height) * 72 / 96, 409);
125+
}
126+
127+
private static void XLXSStyleHeader(int row, int table_x, ref ExcelWorksheet ws)
128+
{
129+
ExcelRange r;
130+
ws.Cells[row, table_x + 1].Style.Font.Bold = true;
131+
r = ws.Cells[row, table_x + 1, row, table_x + 6];
132+
r.Style.Fill.PatternType = ExcelFillStyle.Solid;
133+
r.Style.Fill.BackgroundColor.SetColor(System.Drawing.Color.LightGray);
134+
r.Style.Border.BorderAround(ExcelBorderStyle.Thin, System.Drawing.Color.LightGray);
135+
}
136+
137+
private static void XLXSNoFitList(ref int row, int table_x, ref ExcelWorksheet ws, List<DesignatorGroup> groups)
138+
{
139+
row++;
140+
// header
141+
ws.Cells[row, table_x + 1].Value = "No fit";
142+
XLXSStyleHeader(row, table_x, ref ws);
143+
row++;
144+
145+
StringBuilder sb = new StringBuilder();
146+
foreach (DesignatorGroup g in groups)
147+
{
148+
bool no_fit_found = false;
149+
bool newline = true;
150+
foreach (Component c in g.comp_list)
151+
{
152+
if (c.no_fit)
153+
{
154+
no_fit_found = true;
155+
if (!newline)
156+
sb.Append(", ");
157+
newline = false;
158+
sb.Append(c.reference);
159+
}
160+
}
161+
if (no_fit_found)
162+
sb.Append(Environment.NewLine);
163+
}
164+
165+
string s = sb.ToString();
166+
if (string.IsNullOrEmpty(s))
167+
s = "None";
168+
169+
ws.Cells[row, table_x + 1, row, table_x + 6].Merge = true;
170+
ws.Cells[row, table_x + 1].Style.WrapText = true;
171+
double width = 0;
172+
for (int i = table_x + 1; i < table_x + 6; i++)
173+
width += ws.Column(i).Width;
174+
double height = XLXSMeasureTextHeight(s + Environment.NewLine + ".", ws.Cells[row, table_x + 1].Style.Font, width);
175+
ws.Row(row).Height = height;
176+
ws.Cells[row, table_x + 1].Value = s;
177+
}
178+
179+
private static void XLXSFooter(string path, string footer, ref ExcelWorksheet ws, ref int row)
180+
{
181+
// footer
182+
if (!string.IsNullOrEmpty(footer))
183+
{
184+
//ws.Row(row).PageBreak = true;
185+
row++;
186+
187+
ExcelPackage footer_p = null;
188+
ExcelWorksheet footer_ws;
189+
190+
if (!File.Exists(footer))
191+
{
192+
footer = path + footer;
193+
if (!File.Exists(footer))
194+
throw new Exception("File not found " + footer);
195+
}
196+
FileInfo fi = new FileInfo(footer);
197+
footer_p = new ExcelPackage(fi);
198+
footer_ws = footer_p.Workbook.Worksheets[1];
199+
200+
int footer_row = 1;
201+
int footer_empty_coint = 0;
202+
do
203+
{
204+
bool empty_row = true;
205+
for (int x = 1; x < 6; x++)
206+
{
207+
if (footer_ws.Cells[footer_row, x].Value != null)
208+
empty_row = false;
209+
}
210+
footer_ws.Cells[footer_row, 1, footer_row, 6].Copy(ws.Cells[row, 1, row, 6]);
211+
footer_row++;
212+
row++;
213+
214+
if (empty_row)
215+
footer_empty_coint++;
216+
else
217+
footer_empty_coint = 0;
218+
219+
} while (footer_empty_coint < 2);
220+
}
221+
}
222+
107223
private static void XLXSLoadSheet(string template, ref ExcelPackage p, HeaderBlock header, out ExcelWorksheet ws, out int table_x, out int table_y)
108224
{
109225
if (!File.Exists(template))
@@ -125,6 +241,9 @@ private static void XLXSLoadSheet(string template, ref ExcelPackage p, HeaderBlo
125241
case "(title)":
126242
ws.Cells[y, x].Value = header.title;
127243
break;
244+
case "(number)":
245+
ws.Cells[y, x].Value = header.comment1;
246+
break;
128247
case "(documentnumber)":
129248
ws.Cells[y, x].Value = "not implemented";
130249
break;
@@ -199,8 +318,8 @@ private static void XLSXCreateDefaultSheet(ref ExcelPackage p, HeaderBlock heade
199318
r.Style.Fill.BackgroundColor.SetColor(System.Drawing.Color.Black);
200319
r.Style.Font.Color.SetColor(System.Drawing.Color.White);
201320
}
202-
203-
public static void OutputTSV(List<DesignatorGroup> groups, HeaderBlock header, string file)
321+
322+
public static void OutputTSV(string path, List<DesignatorGroup> groups, HeaderBlock header, string file)
204323
{
205324
Console.WriteLine("Generating " + file + "...");
206325
using (StreamWriter sw = new StreamWriter(file))
@@ -308,7 +427,7 @@ private static void PrettyMeasureWidths(List<DesignatorGroup> groups,
308427
precision_width = Math.Min(precision_width, 20);
309428
}
310429

311-
public static void OutputPretty(List<DesignatorGroup> groups, HeaderBlock header, string file)
430+
public static void OutputPretty(string path, List<DesignatorGroup> groups, HeaderBlock header, string file)
312431
{
313432
Console.WriteLine("Generating " + file + "...");
314433
using (StreamWriter sw = new StreamWriter(file))
@@ -408,7 +527,7 @@ public static void OutputPretty(List<DesignatorGroup> groups, HeaderBlock header
408527
}
409528
}
410529

411-
public static void OutputPDF(List<DesignatorGroup> groups, HeaderBlock header, string file, bool rtf = false)
530+
public static void OutputPDF(string path, List<DesignatorGroup> groups, HeaderBlock header, string file, bool rtf = false)
412531
{
413532
Console.WriteLine("Generating " + file + "...");
414533

kibom/Program.cs

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@ static void Main(string[] args)
2828
string path = "";
2929
string outputs = "";
3030
string template = "";
31-
if (!ParseArgs(args, out filename, out path, out outputs, out output_filename, out template))
31+
string footer = "";
32+
bool no_fit_list = false;
33+
if (!ParseArgs(args, out filename, out path, out outputs, out output_filename, out template, out footer, out no_fit_list))
3234
return;
3335

3436
if (!Footprint.LoadSubsFile(path) ||
@@ -37,22 +39,30 @@ static void Main(string[] args)
3739

3840
XmlDocument doc = new XmlDocument();
3941
doc.Load(path + filename);
40-
if (ParseKicadXML(doc, path, filename, outputs, output_filename, template))
42+
if (ParseKicadXML(doc, path, filename, outputs, output_filename, template, footer, no_fit_list))
4143
Console.WriteLine("BOM generated.");
4244
}
4345

44-
static bool ParseArgs(string[] args, out string filename, out string path, out string outputs, out string output_filename, out string template)
46+
static bool ParseArgs(string[] args,
47+
out string filename, out string path,
48+
out string outputs, out string output_filename,
49+
out string template, out string footer,
50+
out bool no_fit_list)
4551
{
4652
filename = "";
4753
path = "";
4854
outputs = "";
4955
output_filename = "";
5056
template = "";
57+
footer = "";
58+
no_fit_list = false;
5159

5260
string poutputs = string.Empty;
5361
string ptemplate = string.Empty;
62+
string pfooter = string.Empty;
5463
string pfilename = string.Empty;
5564
string poutput_filename = string.Empty;
65+
bool pnfl = false;
5666

5767
CmdArgs argProcessor = new CmdArgs() {
5868
{ new CmdArgument("debug", ArgType.Flag, help: "Generate debug output",
@@ -69,6 +79,10 @@ static bool ParseArgs(string[] args, out string filename, out string path, out s
6979
assign: (dynamic d) => { poutputs += "x"; }) },
7080
{ new CmdArgument("t,template", ArgType.String, help: "Template file for XLSX output",
7181
assign: (dynamic d) => { ptemplate = (string)d; }) },
82+
{ new CmdArgument("f,footer", ArgType.String, help: "Footer file for XLSX output",
83+
assign: (dynamic d) => { pfooter = (string)d; }) },
84+
{ new CmdArgument("nfl,no-fit-list", ArgType.Flag, help: "Add a list of no-fit parts",
85+
assign: (dynamic d) => { pnfl = (bool)d; }) },
7286
{ new CmdArgument("", ArgType.String, anonymous: true, required: true, parameter_help: "bom.xml",
7387
assign: (dynamic d) => { pfilename = (string)d; }) },
7488
{ new CmdArgument("", ArgType.String, anonymous: true, parameter_help: "output file",
@@ -86,6 +100,8 @@ static bool ParseArgs(string[] args, out string filename, out string path, out s
86100
output_filename = poutput_filename;
87101
outputs = poutputs;
88102
template = ptemplate;
103+
footer = pfooter;
104+
no_fit_list = pnfl;
89105

90106
if (!File.Exists(filename))
91107
{
@@ -100,7 +116,7 @@ static bool ParseArgs(string[] args, out string filename, out string path, out s
100116
return true;
101117
}
102118

103-
static bool ParseKicadXML(XmlDocument doc, string path, string filename, string outputs, string output_filename, string template)
119+
static bool ParseKicadXML(XmlDocument doc, string path, string filename, string outputs, string output_filename, string template, string footer, bool nfl)
104120
{
105121
HeaderBlock header = new HeaderBlock();
106122
if (!header.ParseHeader(doc))
@@ -127,28 +143,28 @@ static bool ParseKicadXML(XmlDocument doc, string path, string filename, string
127143
{
128144
string base_filename = Path.GetFileNameWithoutExtension(path + filename);
129145
if (outputs.Contains('t'))
130-
Output.OutputTSV(merged_groups, header, path + base_filename + ".tsv.txt");
146+
Output.OutputTSV(path, merged_groups, header, path + base_filename + ".tsv.txt");
131147
if (outputs.Contains('q'))
132-
Output.OutputPretty(merged_groups, header, path + base_filename + ".txt");
148+
Output.OutputPretty(path, merged_groups, header, path + base_filename + ".txt");
133149
if (outputs.Contains('x'))
134-
Output.OutputXLSX(merged_groups, header, path + base_filename + ".xlsx", template);
150+
Output.OutputXLSX(path, merged_groups, header, path + base_filename + ".xlsx", template, footer, nfl);
135151
if (outputs.Contains('p'))
136-
Output.OutputPDF(merged_groups, header, path + base_filename + ".pdf");
152+
Output.OutputPDF(path, merged_groups, header, path + base_filename + ".pdf");
137153
if (outputs.Contains('r'))
138-
Output.OutputPDF(merged_groups, header, path + base_filename + ".rtf", true);
154+
Output.OutputPDF(path, merged_groups, header, path + base_filename + ".rtf", true);
139155
}
140156
else
141157
{
142158
if (outputs.Contains('t'))
143-
Output.OutputTSV(merged_groups, header, output_filename);
159+
Output.OutputTSV(path, merged_groups, header, output_filename);
144160
if (outputs.Contains('q'))
145-
Output.OutputPretty(merged_groups, header, output_filename);
161+
Output.OutputPretty(path, merged_groups, header, output_filename);
146162
if (outputs.Contains('x'))
147-
Output.OutputXLSX(merged_groups, header, output_filename, template);
163+
Output.OutputXLSX(path, merged_groups, header, output_filename, template, footer, nfl);
148164
if (outputs.Contains('p'))
149-
Output.OutputPDF(merged_groups, header, output_filename);
165+
Output.OutputPDF(path, merged_groups, header, output_filename);
150166
if (outputs.Contains('r'))
151-
Output.OutputPDF(merged_groups, header, output_filename, true);
167+
Output.OutputPDF(path, merged_groups, header, output_filename, true);
152168
}
153169

154170
// debug output
@@ -228,9 +244,16 @@ static List<Component> ParseComponents(XmlDocument doc)
228244
break;
229245

230246
case "bom_no_part":
231-
if (field.InnerText.ToLower() == "true")
247+
if ((field.InnerText.ToLower() == "true") ||
248+
(field.InnerText.ToLower() == "no part"))
232249
comp.no_part = true;
233250
break;
251+
252+
case "bom_no_fit":
253+
if ((field.InnerText.ToLower() == "true") ||
254+
(field.InnerText.ToLower() == "no fit"))
255+
comp.no_fit = true;
256+
break;
234257
}
235258
}
236259
}

kibom/Properties/AssemblyInfo.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,5 @@
3232
// You can specify all the values or you can default the Build and Revision Numbers
3333
// by using the '*' as shown below:
3434
// [assembly: AssemblyVersion("1.0.*")]
35-
[assembly: AssemblyVersion("0.6.*")]
35+
[assembly: AssemblyVersion("0.7.*")]
3636
[assembly: AssemblyFileVersion("1.0.0.0")]

readme.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,5 @@ Can be used as a plug-in for Kicad. Creates tab separated (TSV) or PDF/RTF BOMs.
3131
`Custom_fields.txt` lists custom fields that can be added to components in Kicad, and which will then appear on the BOM.
3232

3333
Tab Separated Value (TSV) output can be copy/pasted into Google Sheets and most other spreadsheet programs. PDF output needs headers and footers adding to it. A notes section at the end would also be a good idea.
34+
35+
For XLSX output a header and footer can be specified. See the examples.

0 commit comments

Comments
 (0)