KiBot/kibot/fil_rot_footprint.py

116 lines
5.0 KiB
Python

# -*- coding: utf-8 -*-
# Copyright (c) 2020-2021 Salvador E. Tropea
# Copyright (c) 2020-2021 Instituto Nacional de Tecnología Industrial
# License: GPL-3.0
# Project: KiBot (formerly KiPlot)
"""
Implements a filter to rotate footprints.
This is inspired in JLCKicadTools by Matthew Lai.
"""
from re import compile
from .gs import GS
from .optionable import Optionable
from .error import KiPlotConfigurationError
from .macros import macros, document, filter_class # noqa: F401
from . import log
logger = log.get_logger(__name__)
# Known rotations for JLC
DEFAULT_ROTATIONS = [["^R_Array_Convex_", 90.0],
["^R_Array_Concave_", 90.0],
["^SOT-223", 180.0],
["^SOT-23", 180.0],
["^TSOT-23", 180.0],
["^SOT-353", 180.0],
["^QFN-", 270.0],
["^LQFP-", 270.0],
["^TQFP-", 270.0],
["^SOP-(?!18_)", 270.0],
["^TSSOP-", 270.0],
["^DFN-", 270.0],
["^SOIC-", 270.0],
# ["^SOP-18_", 0],
["^VSSOP-10_", 270.0],
["^CP_EIA-3216-18_", 180.0],
["^CP_EIA-3528-15_AVX-H", 180.0],
["^CP_EIA-3528-21_Kemet-B", 180.0],
["^CP_Elec_8x10.5", 180.0],
["^CP_Elec_6.3x7.7", 180.0],
["^CP_Elec_8x6.7", 180.0],
["^CP_Elec_8x10", 180.0],
["^(.*?_|V)?QFN-(16|20|24|28|40)(-|_|$)", 270.0],
["^Bosch_LGA-8_2x2.5mm_P0.65mm_ClockwisePinNumbering", 90.0],
["^PowerPAK_SO-8_Single", 270.0],
["^HTSSOP-28-1EP_4.4x9.7mm*", 270.0],
]
@filter_class
class Rot_Footprint(BaseFilter): # noqa: F821
""" Rot_Footprint
This filter can rotate footprints, used for the positions file generation.
Some manufacturers use a different rotation than KiCad.
The internal `_rot_footprint` filter implements the simplest case """
def __init__(self):
super().__init__()
self._is_transform = True
with document:
self.extend = True
""" Extends the internal list of rotations with the one provided.
Otherwise just use the provided list """
self.negative_bottom = True
""" Rotation for bottom components is computed via subtraction as `(component rot - angle)` """
self.invert_bottom = False
""" Rotation for bottom components is negated, resulting in either: `(- component rot - angle)`
or when combined with `negative_bottom`, `(angle - component rot)` """
self.rotations = Optionable
""" [list(list(string))] A list of pairs regular expression/rotation.
Components matching the regular expression will be rotated the indicated angle """
self.skip_bottom = False
""" Do not rotate components on the bottom """
self.skip_top = False
""" Do not rotate components on the top """
def config(self, parent):
super().config(parent)
self._rot = []
if isinstance(self.rotations, list):
for r in self.rotations:
if len(r) != 2:
raise KiPlotConfigurationError("Each regex/angle pair must contain exactly two values, not {} ({})".
format(len(r), r))
regex = compile(r[0])
try:
angle = float(r[1])
except ValueError:
raise KiPlotConfigurationError("The second value in the regex/angle pairs must be a number, not {}".
format(r[1]))
self._rot.append([regex, angle])
if self.extend:
for regex_str, angle in DEFAULT_ROTATIONS:
self._rot.append([compile(regex_str), angle])
if not self._rot:
raise KiPlotConfigurationError("No rotations provided")
def filter(self, comp):
""" Apply the rotation """
if (self.skip_top and not comp.bottom) or (self.skip_bottom and comp.bottom):
# Component should be excluded
return
for regex, angle in self._rot:
if regex.search(comp.footprint):
old_angle = comp.footprint_rot
if self.negative_bottom and comp.bottom:
comp.footprint_rot -= angle
else:
comp.footprint_rot += angle
if self.invert_bottom and comp.bottom:
comp.footprint_rot = -comp.footprint_rot
comp.footprint_rot = comp.footprint_rot % 360
if GS.debug_level > 2:
logger.debug('Rotating ref: {} {}: {} -> {}'.
format(comp.ref, comp.footprint, old_angle, comp.footprint_rot))
return