Skip to content

Commit

Permalink
Merge pull request #765 from nguyen-v/pcb_print_drill_table
Browse files Browse the repository at this point in the history
[PCB Print][Added] Tables in PDF
  • Loading branch information
set-soft authored Jan 7, 2025
2 parents d192019 + d4f7bd6 commit 3aa99a4
Show file tree
Hide file tree
Showing 38 changed files with 174 additions and 14 deletions.
3 changes: 3 additions & 0 deletions kibot/out_excellon.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ def __init__(self):
self.options = ExcellonOptions
""" *[dict={}] Options for the `excellon` output """

def get_csv_separator(self):
return ','

@staticmethod
def get_conf_examples(name, layers):
gb = {}
Expand Down
3 changes: 3 additions & 0 deletions kibot/out_gerb_drill.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ def __init__(self):
self.options = Gerb_DrillOptions
""" *[dict={}] Options for the `gerb_drill` output """

def get_csv_separator(self):
return ','

@staticmethod
def get_conf_examples(name, layers):
gb = {}
Expand Down
21 changes: 21 additions & 0 deletions kibot/out_pcb_print.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
from .drill_marks import DRILL_MARKS_MAP, add_drill_marks
from .kicad.drill_info import get_num_layer_pairs, get_layer_pair_name
from .kicad.pcb_draw_helpers import draw_drill_map
from .pre_include_table import IncludeTableOptions, update_table
from .layer import Layer, get_priority
from .kiplot import run_command, load_board, get_all_components, look_for_output, get_output_targets, run_output
from .svgutils.transform import ImageElement, GroupElement
Expand Down Expand Up @@ -454,6 +455,8 @@ def __init__(self):
""" Invert the meaning of the `use_for_center` layer option.
This can be used to just select the edge cuts for centering, in this case enable this option
and disable the `use_for_center` option of the edge cuts layer """
self.include_table = IncludeTableOptions
""" [boolean|dict=false] Use a boolean for simple cases or fine-tune its behavior """
self.drill = DrillOptions
""" [boolean|dict=false] Use a boolean for simple cases or fine-tune its behavior """
add_drill_marks(self)
Expand All @@ -470,6 +473,17 @@ def get_layers_for_page(self, page):

def config(self, parent):
super().config(parent)
self._include_table_output = False
if isinstance(self.include_table, bool):
if self.include_table:
self._include_table_output = True
self._include_table = IncludeTableOptions()
self._include_table.config(self)
else:
self._include_table_output = False
else:
self._include_table_output = True
self._include_table = self.include_table
if isinstance(self.drill, bool):
self._drill_unify_pth_and_npth = True
self._drill_group_slots_and_round_holes = True
Expand Down Expand Up @@ -1471,13 +1485,20 @@ def generate_output(self, output):
self.set_visible(edge_id)
# Move KiBot image groups away
self.move_kibot_image_groups()

# Update all tables
if GS.ki7 and self._include_table_output:
update_table(self._include_table, self)

# Generate the output, page by page
pages = []
for n, p in enumerate(self._pages):
if not GS.ki5:
if p._is_drill:
g_drill_map = PCB_GROUP(GS.board)
self.add_drill_map_drawing(p, g_drill_map)
if GS.ki7 and self._include_table_output:
update_table(self._include_table, self, p._drill_pair_index, True)
# Make visible only the layers we need
# This is very important when scaling, otherwise the results are controlled by the .kicad_prl (See #407)
if self.individual_page_scaling:
Expand Down
61 changes: 47 additions & 14 deletions kibot/pre_include_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from .error import KiPlotConfigurationError
from .gs import GS
from .kicad.pcb_draw_helpers import (draw_rect, draw_line, draw_text, get_text_width,
draw_marker, get_marker_best_pen_size,
GR_TEXT_HJUSTIFY_LEFT, GR_TEXT_HJUSTIFY_RIGHT,
GR_TEXT_HJUSTIFY_CENTER)
from .kiplot import load_board, get_output_targets, look_for_output
Expand All @@ -24,7 +25,8 @@
ALIGNMENT = {'left': GR_TEXT_HJUSTIFY_LEFT,
'center': GR_TEXT_HJUSTIFY_CENTER,
'right': GR_TEXT_HJUSTIFY_RIGHT}
VALID_OUTPUT_TYPES = {'bom', 'kibom', 'position', 'report'}
VALID_OUTPUT_TYPES = {'bom', 'kibom', 'position', 'report', 'excellon', 'gerb_drill'}
VALID_DRILL_TABLE_OUTPUTS = ['excellon', 'gerb_drill']


class IncTableOutputOptions(Optionable):
Expand Down Expand Up @@ -97,6 +99,9 @@ def __init__(self):
for example *kibot_table_out[:10]* would include all elements until the 10th
element (10th excluded), and *kibot_table_out[2][5:8]* would include the second
output's elements number 6 to 8 (python indexes start at 0). """
self.format_drill_table = True
""" If True, CSV drill tables will have drill marks displayed on the left and
an extra bottom rule for the total number of holes """
super().__init__()
self._unknown_is_error = True

Expand All @@ -116,7 +121,7 @@ def __init__(self, header='', width=10):
self.data = [] # List to hold data for the column


def update_table_group(g, pos_x, pos_y, width, tlayer, ops, out, csv_file, slice_str=None):
def update_table_group(g, pos_x, pos_y, width, tlayer, ops, out, csv_file, out_type, slice_str=None):
"""Extend the function to handle slicing of rows based on the slice_str."""
if not os.path.isfile(csv_file):
raise KiPlotConfigurationError(f'Missing `{csv_file}`, create it first using the `{out.name}` output')
Expand All @@ -131,23 +136,31 @@ def update_table_group(g, pos_x, pos_y, width, tlayer, ops, out, csv_file, slice

cols = []

format_drill_table = out_type in VALID_DRILL_TABLE_OUTPUTS and ops.format_drill_table

with open(csv_file) as csvfile:
reader = csv.reader(csvfile, delimiter=out._obj.get_csv_separator())

# Parse the header if present
if out.has_header:
headers = next(reader)
if format_drill_table:
cols.append(ITColumns(header='Symbol'))
for header in headers:
cols.append(ITColumns(header=header))
else:
first_row = next(reader)
if format_drill_table:
cols.append(ITColumns())
for _ in range(len(first_row)):
cols.append(ITColumns())
for i, value in enumerate(first_row):
cols[i].data.append(value)

# Add the rest of the CSV rows to the column data
for row in reader:
if format_drill_table:
row.insert(0, '   ') # for the drill symbol we reserve 3 em spaces
for i, value in enumerate(row):
if i < len(cols):
cols[i].data.append(value)
Expand Down Expand Up @@ -176,15 +189,22 @@ def update_table_group(g, pos_x, pos_y, width, tlayer, ops, out, csv_file, slice
xpos_x = int(pos_x + col_spacing_width / 2)
max_row_data = 0

# KiCad adds some padding around the texts, we add some padding to align
# markers with the texts
marker_padding = font_w/4

for c in cols:
c.w = int(c.width / total_rel_w * width)
c.x = xpos_x
if out._text_alignment == GR_TEXT_HJUSTIFY_LEFT:
c.xoffset = 0
c.xoffset_marker = int(font_w/2 + marker_padding)
elif out._text_alignment == GR_TEXT_HJUSTIFY_RIGHT:
c.xoffset = int(c.w - col_spacing_width)
c.xoffset_marker = int(-font_w/2 - marker_padding)
elif out._text_alignment == GR_TEXT_HJUSTIFY_CENTER:
c.xoffset = int(c.w / 2 - col_spacing_width / 2)
c.xoffset_marker = 0
xpos_x += c.w
max_row_data = max(max_row_data, len(c.data))

Expand All @@ -202,12 +222,19 @@ def update_table_group(g, pos_x, pos_y, width, tlayer, ops, out, csv_file, slice
rule_y = int(y + (i + 1) * row_h)
draw_line(g, pos_x, rule_y, pos_x + width, rule_y, tlayer, line_w=GS.from_mm(out.horizontal_rule_width))

if format_drill_table:
rule_y = int(y + (max_row_data - 1) * row_h)
draw_line(g, pos_x, rule_y, pos_x + width, rule_y, tlayer, line_w=GS.from_mm(out.bottom_rule_width))

table_h = 0
for c in cols:
for i, c in enumerate(cols):
row_y = int(y + row_h / 2)
for d in c.data:
for j, d in enumerate(c.data):
txt, _ = draw_text(g, c.x + c.xoffset, int(row_y - font_w), d, font_w, font_w,
tlayer, alignment=out._text_alignment, font=font)
if format_drill_table and i == 0 and j != len(c.data)-1:
marker_w = get_marker_best_pen_size(font_w)
draw_marker(g, int(c.x + c.xoffset + c.xoffset_marker), int(row_y), font_w, tlayer, j, marker_w)
row_y += row_h
table_h = int(max(table_h, row_y - pos_y) - row_h / 2)

Expand Down Expand Up @@ -238,7 +265,7 @@ def measure_table(cols, out, bold_headers, font=None):
c.width = c.max_len/tot_len


def update_table(ops, parent):
def update_table(ops, parent, force_index=-1, only_drill_tables=False):
logger.debug('Starting include table preflight')
load_board()
csv_files = []
Expand All @@ -254,7 +281,7 @@ def update_table(ops, parent):
logger.debug(f' - {out.name} no CSV')
continue
out._obj = csv
targets, _, _ = get_output_targets(out.name, parent)
targets, _, o = get_output_targets(out.name, parent)

csv_targets = [file for file in targets if file.endswith('.csv')]
for file in csv_targets:
Expand All @@ -263,7 +290,7 @@ def update_table(ops, parent):
file_name = os.path.basename(file)
name_without_ext = os.path.splitext(file_name)[0]
csv_name.append(name_without_ext)
out_to_csv_mapping[out.name] = (out, csv_targets)
out_to_csv_mapping[out.name] = (out, csv_targets, o.type)
logger.debug(f' - {out.name} -> {csv_targets}')

group_found = False
Expand All @@ -287,32 +314,38 @@ def update_table(ops, parent):
# Check for number of brackets in the group name
bracket_matches = re.findall(r'\[.*?\]', group_suffix)

out, csv, out_type = out_to_csv_mapping.get(group_suffix.split('[')[0], (None, None, None))
if not csv:
logger.warning(W_NOMATCHGRP + f'No output to handle `{group_name}` found')
continue

if only_drill_tables and out_type not in VALID_DRILL_TABLE_OUTPUTS:
continue

if len(bracket_matches) == 2:
# Two brackets: second is slicing expression
slice_str = bracket_matches[1]
index = int(bracket_matches[0][1:-1]) - 1 # First bracket is the index
group_suffix = re.sub(r'\[.*?\]', '', group_suffix, count=2) # Remove both brackets
elif len(bracket_matches) == 1:
# One bracket: determine if it's an index or a slice
if len(csv_targets) == 1:
if len(csv) == 1:
slice_str = bracket_matches[0] # Single CSV means it must be a slice
else:
index = int(bracket_matches[0][1:-1]) - 1 # Multiple CSVs mean it's an index
group_suffix = re.sub(r'\[.*?\]', '', group_suffix, count=1) # Remove the bracket

logger.debug(f' - Parsed group_suffix: {group_suffix}, index: {index}, slice_str: {slice_str}')

out, csv = out_to_csv_mapping.get(group_suffix, (None, None))
if not csv:
logger.warning(W_NOMATCHGRP + f'No output to handle `{group_name}` found')
continue
if force_index != -1:
index = force_index

# Default index to 0 if csv has only one element
if index is None:
index = 0

if index < 0 or index >= len(csv):
msg = f'Index {index + 1} is out of range, '
msg = f'Index {index + 1} is out of range for output {out.name}, '
raise KiPlotConfigurationError(msg)

x1, y1, x2, y2 = GS.compute_group_boundary(g)
Expand All @@ -322,7 +355,7 @@ def update_table(ops, parent):
f' ({GS.to_mm(x2 - x1)}x{GS.to_mm(y2 - y1)} mm) layer {layer}'
f' with name {g.GetName()}')

update_table_group(g, x1, y1, x2 - x1, layer, ops, out, csv[index], slice_str)
update_table_group(g, x1, y1, x2 - x1, layer, ops, out, csv[index], out_type, slice_str)

updated = True

Expand Down
10 changes: 10 additions & 0 deletions tests/board_samples/kicad_7/drill_types.kicad_pcb
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,10 @@
)
)

(gr_rect (start 52.5 57.5) (end 147.5 152.5)
(stroke (width 0.1) (type default)) (fill none) (layer "Cmts.User") (tstamp 8d7edf18-5b01-4e99-b249-8da236cc1f47))
(gr_rect (start 50 55) (end 150 155)
(stroke (width 0.1) (type default)) (fill none) (layer "Cmts.User") (tstamp a757db21-387f-4a33-8e86-dbb0b22b01e7))
(gr_line (start 100 25) (end 100 50)
(stroke (width 0.05) (type solid)) (layer "Edge.Cuts") (tstamp 00000000-0000-0000-0000-00005ea7192f))
(gr_line (start 125 25) (end 125 50)
Expand All @@ -252,4 +256,10 @@
(via blind (at 120.75 36.25) (size 0.8) (drill 0.4) (layers "In1.Cu" "In2.Cu") (net 0) (tstamp 922058ca-d09a-45fd-8394-05f3e2c1e03a))
(via blind (at 119.25 34.5) (size 0.8) (drill 0.4) (layers "In2.Cu" "B.Cu") (net 0) (tstamp 0f54db53-a272-4955-88fb-d7ab00657bb0))

(group "kibot_table_drill_table" (id 0d020107-262c-422c-8a17-e26f4adef897)
(members
8d7edf18-5b01-4e99-b249-8da236cc1f47
a757db21-387f-4a33-8e86-dbb0b22b01e7
)
)
)
15 changes: 15 additions & 0 deletions tests/board_samples/kicad_8/drill_types.kicad_pcb
Original file line number Diff line number Diff line change
Expand Up @@ -827,6 +827,17 @@
)
)
)
(gr_rect
(start 50 55)
(end 150 155)
(stroke
(width 0.1)
(type default)
)
(fill none)
(layer "Cmts.User")
(uuid "8f769ff7-0ab1-44a8-ac30-8af10f3c087c")
)
(gr_line
(start 100 25)
(end 100 50)
Expand Down Expand Up @@ -935,4 +946,8 @@
(net 0)
(uuid "0f54db53-a272-4955-88fb-d7ab00657bb0")
)
(group "kibot_table_drill_table"
(uuid "b0bd6bf1-b019-4a3f-aac0-2d0e494e009d")
(members "8f769ff7-0ab1-44a8-ac30-8af10f3c087c")
)
)
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 3aa99a4

Please sign in to comment.