[SVG][Added] Options to limit the view box to the used area.

This commit is contained in:
Salvador E. Tropea 2023-01-05 19:29:31 -03:00
parent b2f5612b77
commit 42f5dcd8d6
9 changed files with 140 additions and 37 deletions

View File

@ -27,6 +27,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Support for multi-boards as defined by KiKit
- iBoM:
- `hide_excluded` to hide excluded *.Fab drawings.
- SVG:
- Options to limit the view box to the used area.
### Fixed
- PCB_Print:
- Images not showing in custom frames. (#352)

View File

@ -3300,6 +3300,7 @@ Notes:
see `show_components`.
- `libs`: [list(string)=[]] List of libraries.
- `margin`: [number|dict] Margin around the generated image [mm].
Using a number the margin is the same in the four directions.
* Valid keys:
- `bottom`: [number=0] Bottom margin [mm].
- `left`: [number=0] Left margin [mm].
@ -4145,13 +4146,28 @@ Notes:
- `inner_extension_pattern`: [string=''] Used to change the Protel style extensions for inner layers.
The replacement pattern can contain %n for the inner layer number and %N for the layer number.
Example '.g%n'.
- `limit_viewbox`: [boolean=false] When enabled the view box is limited to a selected area.
- `line_width`: [number=0.25] [0.02,2] For objects without width [mm] (KiCad 5).
- `margin`: [number|dict] Margin around the view box [mm].
Using a number the margin is the same in the four directions.
See `limit_viewbox` option.
* Valid keys:
- `bottom`: [number=0] Bottom margin [mm].
- `left`: [number=0] Left margin [mm].
- `right`: [number=0] Right margin [mm].
- `top`: [number=0] Top margin [mm].
- `mirror_plot`: [boolean=false] Plot mirrored.
- `negative_plot`: [boolean=false] Invert black and white.
- `plot_footprint_refs`: [boolean=true] Include the footprint references.
- `plot_footprint_values`: [boolean=true] Include the footprint values.
- `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.
- `size_detection`: [string='kicad_edge'] [kicad_edge,kicad_all] Method used to detect the size of the view box.
The `kicad_edge` method uses the size of the board as reported by KiCad,
components that extend beyond the PCB limit will be cropped. You can manually
adjust the margin to make them visible.
The `kicad_all` method uses the whole size reported by KiCad. Usually includes extra space.
See `limit_viewbox` option.
- `sketch_pad_line_width`: [number=0.1] Line width for the sketched pads [mm], see `sketch_pads_on_fab_layers` (KiCad 6+)
Note that this value is currently ignored by KiCad (6.0.9).
- `sketch_pads_on_fab_layers`: [boolean=false] Draw only the outline of the pads on the *.Fab layers (KiCad 6+).

View File

@ -2038,7 +2038,8 @@ outputs:
highlight: []
# [list(string)=[]] List of libraries
libs: []
# [number|dict] Margin around the generated image [mm]
# [number|dict] Margin around the generated image [mm].
# Using a number the margin is the same in the four directions
margin:
# [number=0] Bottom margin [mm]
bottom: 0
@ -2823,8 +2824,22 @@ outputs:
# The replacement pattern can contain %n for the inner layer number and %N for the layer number.
# Example '.g%n'
inner_extension_pattern: ''
# [boolean=false] When enabled the view box is limited to a selected area
limit_viewbox: false
# [number=0.25] [0.02,2] For objects without width [mm] (KiCad 5)
line_width: 0.25
# [number|dict] Margin around the view box [mm].
# Using a number the margin is the same in the four directions.
# See `limit_viewbox` option
margin:
# [number=0] Bottom margin [mm]
bottom: 0
# [number=0] Left margin [mm]
left: 0
# [number=0] Right margin [mm]
right: 0
# [number=0] Top margin [mm]
top: 0
# [boolean=false] Plot mirrored
mirror_plot: false
# [boolean=false] Invert black and white
@ -2845,6 +2860,13 @@ outputs:
# [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='kicad_edge'] [kicad_edge,kicad_all] Method used to detect the size of the view box.
# The `kicad_edge` method uses the size of the board as reported by KiCad,
# components that extend beyond the PCB limit will be cropped. You can manually
# adjust the margin to make them visible.
# The `kicad_all` method uses the whole size reported by KiCad. Usually includes extra space.
# See `limit_viewbox` option
size_detection: 'kicad_edge'
# [number=0.1] Line width for the sketched pads [mm], see `sketch_pads_on_fab_layers` (KiCad 6+)
# Note that this value is currently ignored by KiCad (6.0.9)
sketch_pad_line_width: 0.1

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2022 Salvador E. Tropea
# Copyright (c) 2022 Instituto Nacional de Tecnología Industrial
# Copyright (c) 2022-2023 Salvador E. Tropea
# Copyright (c) 2022-2023 Instituto Nacional de Tecnología Industrial
# License: GPL-3.0
# Project: KiBot (formerly KiPlot)
# Note about the size:
@ -11,6 +11,10 @@ import re
from .. import log
logger = log.get_logger()
SVG_VIEW_BOX_REGEX = r'<svg (.*) width="(.*)" height="(.*)" viewBox="(\S+) (\S+) (\S+) (\S+)"'
SVG_VIEW_BOX_SUB_FIX = r'<svg \1 width="\3" height="\2" viewBox="\4 \5 \7 \6"'
SVG_VIEW_BOX_REGEX2 = r'width="(.*)" height="(.*)" viewBox="(\S+) (\S+) (\S+) (\S+)"'
SVG_VIEW_BOX_SUB_PAT = r'width="{}cm" height="{}cm" viewBox="{} {} {} {}"'
def patch_svg_file(file, remove_bkg=False, is_portrait=False):
@ -22,8 +26,7 @@ def patch_svg_file(file, remove_bkg=False, is_portrait=False):
with open(file, 'rt') as f:
text = f.read()
if not is_portrait:
text = re.sub(r'<svg (.*) width="(.*)" height="(.*)" viewBox="(\S+) (\S+) (\S+) (\S+)"',
r'<svg \1 width="\3" height="\2" viewBox="\4 \5 \7 \6"', text)
text = re.sub(SVG_VIEW_BOX_REGEX, SVG_VIEW_BOX_SUB_FIX, text)
if remove_bkg:
text = re.sub(r'<rect.*>', '', text)
elif not is_portrait:
@ -31,3 +34,12 @@ def patch_svg_file(file, remove_bkg=False, is_portrait=False):
r'<rect x="\1" y="\2" width="\4" height="\3"', text)
with open(file, 'wt') as f:
f.write(text)
def change_svg_viewbox(file, view_box, w, h):
with open(file, 'rt') as f:
text = f.read()
text = re.sub(SVG_VIEW_BOX_REGEX2, SVG_VIEW_BOX_SUB_PAT.format(w, h, view_box[0], view_box[1], view_box[2], view_box[3]),
text)
with open(file, 'wt') as f:
f.write(text)

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020-2022 Salvador E. Tropea
# Copyright (c) 2020-2022 Instituto Nacional de Tecnología Industrial
# Copyright (c) 2020-2023 Salvador E. Tropea
# Copyright (c) 2020-2023 Instituto Nacional de Tecnología Industrial
# License: GPL-3.0
# Project: KiBot (formerly KiPlot)
""" Base class for output options """

View File

@ -195,6 +195,7 @@ class AnyLayerOptions(VariantOptions):
# Restore the eliminated layers
if exclude:
self.unfilter_pcb_components()
self._generated_files = generated
def solve_extension(self, layer):
if self._plot_format == PLOT_FORMAT_GERBER and self.use_protel_extensions:

View File

@ -983,3 +983,27 @@ class VariantOptions(BaseOptions):
comps = self.variant.filter(comps)
self._sub_pcb = self.variant._sub_pcb
self._comps = comps
class PcbMargin(Optionable):
""" To adjust each margin """
def __init__(self):
super().__init__()
with document:
self.left = 0
""" Left margin [mm] """
self.right = 0
""" Right margin [mm] """
self.top = 0
""" Top margin [mm] """
self.bottom = 0
""" Bottom margin [mm] """
@staticmethod
def solve(margin):
if isinstance(margin, type):
return (0, 0, 0, 0)
if isinstance(margin, PcbMargin):
return (GS.from_mm(margin.left), GS.from_mm(margin.right), GS.from_mm(margin.top), GS.from_mm(margin.bottom))
margin = GS.from_mm(margin)
return (margin, margin, margin, margin)

View File

@ -29,7 +29,7 @@ from .misc import (PCBDRAW_ERR, PCB_MAT_COLORS, PCB_FINISH_COLORS, SOLDER_COLORS
from .gs import GS
from .layer import Layer
from .optionable import Optionable
from .out_base import VariantOptions
from .out_base import VariantOptions, PcbMargin
from .macros import macros, document, output_class # noqa: F401
from . import log
@ -183,21 +183,6 @@ class PcbDrawRemapComponents(Optionable):
raise KiPlotConfigurationError("The component remapping must specify a `ref`, a `lib` and a `comp`")
class PcbDrawMargin(Optionable):
""" To adjust each margin """
def __init__(self):
super().__init__()
with document:
self.left = 0
""" Left margin [mm] """
self.right = 0
""" Right margin [mm] """
self.top = 0
""" Top margin [mm] """
self.bottom = 0
""" Bottom margin [mm] """
class PcbDrawOptions(VariantOptions):
def __init__(self):
with document:
@ -248,8 +233,9 @@ class PcbDrawOptions(VariantOptions):
""" *[svg,png,jpg,bmp] Output format. Only used if no `output` is specified """
self.output = GS.def_global_output
""" *Name for the generated file """
self.margin = PcbDrawMargin
""" [number|dict] Margin around the generated image [mm] """
self.margin = PcbMargin
""" [number|dict] Margin around the generated image [mm].
Using a number the margin is the same in the four directions """
self.outline_width = 0.15
""" [0,10] Width of the trace to draw the PCB border [mm].
Note this also affects the drill holes """
@ -294,14 +280,7 @@ class PcbDrawOptions(VariantOptions):
else:
self.highlight = self.solve_kf_filters(self.highlight)
# Margin
if isinstance(self.margin, type):
self.margin = (0, 0, 0, 0)
elif isinstance(self.margin, PcbDrawMargin):
self.margin = (mm2ki(self.margin.left), mm2ki(self.margin.right),
mm2ki(self.margin.top), mm2ki(self.margin.bottom))
else:
margin = mm2ki(self.margin)
self.margin = (margin, margin, margin, margin)
self.margin = PcbMargin.solve(self.margin)
# Filter
if isinstance(self.show_components, type):
# Default option is 'none'

View File

@ -1,15 +1,22 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020 Salvador E. Tropea
# Copyright (c) 2020 Instituto Nacional de Tecnología Industrial
# Copyright (c) 2020-2023 Salvador E. Tropea
# Copyright (c) 2020-2023 Instituto Nacional de Tecnología Industrial
# Copyright (c) 2018 John Beard
# License: GPL-3.0
# Project: KiBot (formerly KiPlot)
# Adapted from: https://github.com/johnbeard/kiplot
from pcbnew import (PLOT_FORMAT_SVG, FromMM, ToMM)
from .out_any_layer import AnyLayer
import os
from pcbnew import PLOT_FORMAT_SVG, FromMM, ToMM
from .drill_marks import DrillMarks
from .gs import GS
from .kicad.patch_svg import change_svg_viewbox
from .misc import KICAD5_SVG_SCALE
from .out_base import PcbMargin
from .out_any_layer import AnyLayer
from .macros import macros, document, output_class # noqa: F401
from . import log
logger = log.get_logger()
class SVGOptions(DrillMarks):
@ -26,6 +33,19 @@ class SVGOptions(DrillMarks):
""" [0,6] Scale factor used to represent 1 mm in the SVG (KiCad 6).
The value is how much zeros has the multiplier (1 mm = 10 power `svg_precision` units).
Note that for an A4 paper Firefox 91 and Chrome 105 can't handle more than 5 """
self.limit_viewbox = False
""" When enabled the view box is limited to a selected area """
self.size_detection = 'kicad_edge'
""" [kicad_edge,kicad_all] Method used to detect the size of the view box.
The `kicad_edge` method uses the size of the board as reported by KiCad,
components that extend beyond the PCB limit will be cropped. You can manually
adjust the margin to make them visible.
The `kicad_all` method uses the whole size reported by KiCad. Usually includes extra space.
See `limit_viewbox` option """
self.margin = PcbMargin
""" [number|dict] Margin around the view box [mm].
Using a number the margin is the same in the four directions.
See `limit_viewbox` option """
self._plot_format = PLOT_FORMAT_SVG
def _configure_plot_ctrl(self, po, output_dir):
@ -44,6 +64,33 @@ class SVGOptions(DrillMarks):
self.negative_plot = po.GetNegative()
self.mirror_plot = po.GetMirror()
def config(self, parent):
super().config(parent)
# Margin
self.margin = PcbMargin.solve(self.margin)
def run(self, output_dir, layers):
super().run(output_dir, layers)
if not self.limit_viewbox:
return
# Limit the view box of the SVG
bbox = GS.board.ComputeBoundingBox(self.size_detection == 'kicad_edge').getWxRect()
# Apply the margin (left right top bottom)
bbox = (bbox[0]-self.margin[0], bbox[1]-self.margin[2],
bbox[2]+self.margin[0]+self.margin[1], bbox[3]+self.margin[2]+self.margin[3])
# Width/height of the used area in cm
width = ToMM(bbox[2])*0.1
height = ToMM(bbox[3])*0.1
# Scale factor to convert KiCad IU to the SVG units
mult = KICAD5_SVG_SCALE if GS.ki5 else 10.0 ** (self.svg_precision - 6)
# View port in SVG units
bbox = tuple(map(lambda x: int(x*mult), bbox))
logger.debug('Adjusting SVG viewBox to {} for width {} cm and height {} cm'.format(bbox, width, height))
for f in self._generated_files.values():
fname = os.path.join(output_dir, f)
logger.debugl(2, '- '+f)
change_svg_viewbox(fname, bbox, width, height)
@output_class
class SVG(AnyLayer):