diff --git a/kibot/PcbDraw/README.md b/kibot/PcbDraw/README.md index 58940d35..87774718 100644 --- a/kibot/PcbDraw/README.md +++ b/kibot/PcbDraw/README.md @@ -206,3 +206,8 @@ This file comes from KiKit, but it has too much in common with `populate.py`. - Adapted Template._renderBoards to just copy the files (keeping the extensions) - Added listResources, with some changes to copyRelativeTo and _copyResources - The command used for git is now configurable + +## 2023-02-14 Update + +- Changed to transition 0.3.2 (is a tag, detached from main?!) +- Applied v7 compatibility patches from 9c676a7494995c5aeab086e041bc0ca3967f472d to 6e9c0b7077b5cfed58866f13ad745130e8920185 (2023-01-12) diff --git a/kibot/PcbDraw/pcbnew_transition.py b/kibot/PcbDraw/pcbnew_transition.py index b6bdb2c9..d61c7c72 100644 --- a/kibot/PcbDraw/pcbnew_transition.py +++ b/kibot/PcbDraw/pcbnew_transition.py @@ -36,6 +36,51 @@ def setAuxOrigin(self, o): def NewBoard(filename): return pcbnew.BOARD() +def patchRotate(item): + if hasattr(item, "Rotate"): + originalRotate = item.Rotate + if not getattr(originalRotate, "patched", False): + newRotate = lambda self, center, angle: originalRotate(self, center, angle.AsTenthsOfADegree()) + setattr(newRotate, "patched", True) + item.Rotate = newRotate + # We have to ignore PCB_DIMs objects as the orientation has different meaning + if hasattr(item, "SetOrientation") and not hasattr(item, "GetUnitsMode"): + originalSetOrientation = item.SetOrientation + if not getattr(originalSetOrientation, "patched", False): + newSetOrientation = lambda self, angle: originalSetOrientation(self, angle.AsTenthsOfADegree()) + setattr(newSetOrientation, "patched", True) + item.SetOrientation = newSetOrientation + # We have to ignore PCB_DIMs objects as the orientation has different meaning + if hasattr(item, "GetOrientation") and not hasattr(item, "GetUnitsMode"): + originalGetOrientation = item.GetOrientation + if not getattr(originalGetOrientation, "patched", False): + newGetOrientation = lambda self: pcbnew.EDA_ANGLE(originalGetOrientation(self), pcbnew.TENTHS_OF_A_DEGREE_T) + setattr(newGetOrientation, "patched", True) + item.GetOrientation = newGetOrientation + if hasattr(item, "GetDrawRotation"): + originalGetDrawRotation = item.GetDrawRotation + if not getattr(originalGetDrawRotation, "patched", False): + newGetDrawRotation = lambda self: pcbnew.EDA_ANGLE(originalGetDrawRotation(self), pcbnew.TENTHS_OF_A_DEGREE_T) + setattr(newGetDrawRotation, "patched", True) + item.GetDrawRotation = newGetDrawRotation + if hasattr(item, "SetTextAngle"): + originalSetTextAngle = item.SetTextAngle + if not getattr(originalSetTextAngle, "patched", False): + newSetTextAngle = lambda self, angle: originalSetTextAngle(self, angle.AsTenthsOfADegree()) + setattr(newSetTextAngle, "patched", True) + item.SetTextAngle = newSetTextAngle + if hasattr(item, "SetHatchOrientation"): + originalSetHatchOrientation = item.SetHatchOrientation + if not getattr(originalSetHatchOrientation, "patched", False): + newSetHatchOrientation = lambda self, angle: originalSetHatchOrientation(self, angle.AsTenthsOfADegree()) + setattr(newSetHatchOrientation, "patched", True) + item.SetHatchOrientation = newSetHatchOrientation + +def pathGetItemDescription(item): + if hasattr(item, "GetSelectMenuText") and not hasattr(item, "GetItemDescription"): + setattr(item, "GetItemDescription", getattr(item, "GetSelectMenuText")) + + KICAD_VERSION = getVersion() def isV6(version=KICAD_VERSION): @@ -43,7 +88,12 @@ def isV6(version=KICAD_VERSION): return True return version[0] == 6 -if not isV6(KICAD_VERSION): +def isV7(version=KICAD_VERSION): + if version[0] == 6 and version[1] == 99: + return True + return version[0] == 7 + +if not isV6(KICAD_VERSION) and not isV7(KICAD_VERSION): # Introduce new functions pcbnew.NewBoard = NewBoard @@ -89,3 +139,70 @@ if not isV6(KICAD_VERSION): # PCB_TEXT pcbnew.PCB_TEXT.SetTextThickness = pcbnew.PCB_TEXT.SetThickness +#### V7 compatibility section +try: + from pcbnew import EDA_ANGLE as _transition_EDA_ANGLE +except ImportError: + from .eda_angle import EDA_ANGLE_T, EDA_ANGLE + pcbnew.EDA_ANGLE = EDA_ANGLE + pcbnew.DEGREES_T = EDA_ANGLE_T.DEGREES_T + pcbnew.RADIANS_T = EDA_ANGLE_T.RADIANS_T + pcbnew.TENTHS_OF_A_DEGREE_T = EDA_ANGLE_T.TENTHS_OF_A_DEGREE_T + +if not isV7(KICAD_VERSION): + # VECTOR2I & wxPoint + class _transition_VECTOR2I(pcbnew.wxPoint): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + pcbnew.VECTOR2I = _transition_VECTOR2I + + # EDA_RECT and BOX2I + class _transition_BOX2I(pcbnew.EDA_RECT): + def __init__(self, *args): + # We now use this to construct BOX2I and the points are VECTOR2I + if len(args) == 2 and isinstance(args[0], pcbnew.wxPoint) and isinstance(args[1], pcbnew.wxPoint): + super().__init__(pcbnew.wxPoint(*args[0]), pcbnew.wxSize(*args[1])) + else: + super().__init__(*args) + pcbnew.BOX2I = _transition_BOX2I + + # DRILL_MARKS + pcbnew.DRILL_MARKS_NO_DRILL_SHAPE = 0 + pcbnew.DRILL_MARKS_SMALL_DRILL_SHAPE = 1 + pcbnew.DRILL_MARKS_FULL_DRILL_SHAPE = 2 + + # ZONE + pcbnew.ZONE.SetAssignedPriority = pcbnew.ZONE.SetPriority + pcbnew.ZONE.GetAssignedPriority = pcbnew.ZONE.GetPriority + + # Orientation + for x in dir(pcbnew): + patchRotate(getattr(pcbnew, x)) + + originalCalcArcAngles = pcbnew.EDA_SHAPE.CalcArcAngles + if not getattr(originalCalcArcAngles, "patched", False): + def newCalcArcAngles(self, start, end): + start.value = self.GetArcAngleStart() / 10 + if self.GetShape() == pcbnew.SHAPE_T_CIRCLE: + end.value = start.value + 360 + else: + end.value = start.value + self.GetArcAngle() / 10 + setattr(newCalcArcAngles, "patched", True) + pcbnew.EDA_SHAPE.CalcArcAngles = newCalcArcAngles + + # GetSelectMenuText + for x in dir(pcbnew): + pathGetItemDescription(getattr(pcbnew, x)) + + # EDA_TEXT + originalTextSize = pcbnew.EDA_TEXT.SetTextSize + pcbnew.EDA_TEXT.SetTextSize = lambda self, size: originalTextSize(self, pcbnew.wxSize(size[0], size[1])) + + # PAD + originalSetDrillSize = pcbnew.PAD.SetDrillSize + pcbnew.PAD.SetDrillSize = lambda self, size: originalSetDrillSize(self, pcbnew.wxSize(size[0], size[1])) + + originalSetSize = pcbnew.PAD.SetSize + pcbnew.PAD.SetSize = lambda self, size: originalSetSize(self, pcbnew.wxSize(size[0], size[1])) + diff --git a/kibot/PcbDraw/plot.py b/kibot/PcbDraw/plot.py index 6a0606b8..8ca660e6 100644 --- a/kibot/PcbDraw/plot.py +++ b/kibot/PcbDraw/plot.py @@ -17,7 +17,7 @@ from typing import Callable, Dict, List, Optional, Tuple, TypeVar, Union, Any from . import np from .unit import read_resistance from lxml import etree, objectify # type: ignore -from .pcbnew_transition import KICAD_VERSION, isV6, pcbnew # type: ignore +from .pcbnew_transition import KICAD_VERSION, isV6, isV7, pcbnew # type: ignore T = TypeVar("T") Numeric = Union[int, float] @@ -30,6 +30,8 @@ PKG_BASE = os.path.dirname(__file__) etree.register_namespace("xlink", "http://www.w3.org/1999/xlink") +LEGACY_KICAD = not isV6() and not isV7() + default_style = { "copper": "#417e5a", "board": "#4ca06c", @@ -96,7 +98,10 @@ class SvgPathItem: def is_same(p1: Point, p2: Point) -> bool: dx = p1[0] - p2[0] dy = p1[1] - p2[1] - return math.sqrt(dx*dx+dy*dy) < 100 + pseudo_distance = dx*dx + dy*dy + if isV7(): + return pseudo_distance < 0.01 ** 2 + return pseudo_distance < 100 ** 2 def format(self, first: bool) -> str: ret = "" @@ -521,7 +526,7 @@ def remove_inkscape_annotation(tree: etree.Element) -> None: @dataclass class Hole: position: Tuple[int, int] - orientation: int + orientation: pcbnew.EDA_ANGLE drillsize: Tuple[int, int] def get_svg_path_d(self, ki2svg: Callable[[int], float]) -> str: @@ -570,14 +575,14 @@ def collect_holes(board: pcbnew.BOARD) -> List[Hole]: orientation=pad.GetOrientation(), drillsize=(drs.x, drs.y) )) - via_type = 'VIA' if not isV6(KICAD_VERSION) else 'PCB_VIA' + via_type = 'VIA' if LEGACY_KICAD else 'PCB_VIA' for track in board.GetTracks(): if track.GetClass() != via_type: continue pos = track.GetPosition() holes.append(Hole( position=(pos[0], pos[1]), - orientation=0, + orientation=pcbnew.EDA_ANGLE(0, pcbnew.DEGREES_T), drillsize=(track.GetDrillValue(), track.GetDrillValue()) )) return holes @@ -676,7 +681,7 @@ class PlotSubstrate(PlotInterface): el = etree.SubElement(layer, "path") el.attrib["d"] = hole.get_svg_path_d(self._plotter.ki2svg) el.attrib["transform"] = "translate({} {}) rotate({})".format( - position[0], position[1], -hole.orientation / 10) + position[0], position[1], -hole.orientation.AsDegrees()) def _process_baselayer(self, name: str, source_filename: str) -> None: clipPath = self._plotter.get_def_slot(tag_name="clipPath", id="cut-off") @@ -747,7 +752,7 @@ class PlotSubstrate(PlotInterface): el.attrib["stroke-width"] = str(stroke) el.attrib["points"] = points el.attrib["transform"] = "translate({} {}) rotate({})".format( - position[0], position[1], -hole.orientation / 10) + position[0], position[1], -hole.orientation.AsDegrees()) @dataclass class PlacedComponentInfo: @@ -1019,8 +1024,15 @@ class PcbPlotter(): self.yield_warning: Callable[[str, str], None] = lambda tag, msg: None # Handle warnings - self.ki2svg = self._ki2svg_v6 if isV6(KICAD_VERSION) else self._ki2svg_v5 - self.svg2ki = self._svg2ki_v6 if isV6(KICAD_VERSION) else self._svg2ki_v5 + if isV7(): + self.ki2svg = self._ki2svg_v7 + self.svg2ki = self._svg2ki_v7 + elif isV6(): + self.ki2svg = self._ki2svg_v6 + self.svg2ki = self._svg2ki_v6 + else: + self.ki2svg = self._ki2svg_v5 + self.svg2ki = self._svg2ki_v5 @property def svg_precision(self) -> int: @@ -1074,7 +1086,7 @@ class PcbPlotter(): value = footprint.GetValue().strip() ref = footprint.GetReference().strip() center = footprint.GetPosition() - orient = math.radians(footprint.GetOrientation() / 10) + orient = math.radians(footprint.GetOrientation().AsDegrees()) pos = (center.x, center.y, orient) callback(lib, name, ref, value, pos) @@ -1198,8 +1210,10 @@ class PcbPlotter(): # Method does not exist in older versions of KiCad pass popt.SetTextMode(pcbnew.PLOT_TEXT_MODE_STROKE) - if isV6(KICAD_VERSION): + if isV6(): popt.SetSvgPrecision(self.svg_precision, False) + elif isV7(): + popt.SetSvgPrecision(self.svg_precision) for action in to_plot: if len(action.layers) == 0: continue @@ -1238,6 +1252,12 @@ class PcbPlotter(): def _svg2ki_v5(self, x: float) -> int: return dmil2ki(x) + def _svg2ki_v7(self, x: float) -> int: + return int(pcbnew.FromMM(x)) + + def _ki2svg_v7(self, x: int) -> float: + return float(pcbnew.ToMM(x)) + def _shrink_svg(self, svg: etree.ElementTree, margin: tuple, compute_bbox: bool=False) -> None: """ Shrink the SVG canvas to the size of the drawing. Add margin in diff --git a/tests/reference/7_0_0/batteryPack-top_connector.svg b/tests/reference/7_0_0/batteryPack-top_connector.svg deleted file mode 120000 index e4622d83..00000000 --- a/tests/reference/7_0_0/batteryPack-top_connector.svg +++ /dev/null @@ -1 +0,0 @@ -../6_0_8/batteryPack-top_connector.svg \ No newline at end of file diff --git a/tests/reference/7_0_0/batteryPack-top_connector.svg b/tests/reference/7_0_0/batteryPack-top_connector.svg new file mode 100644 index 00000000..7ec1f9b6 --- /dev/null +++ b/tests/reference/7_0_0/batteryPack-top_connector.svg @@ -0,0 +1,722 @@ + + + Picture generated by PcbDraw + Picture generated by PcbDraw + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +MINUS +MINUS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +github.com/RoboticsBrno/ + +github.com/RoboticsBrno/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +RB0002-BatteryPack + +RB0002-BatteryPack + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +MID + +MID + + + + + + + + + + + + + + + + + + + +PLUS +PLUS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/reference/7_0_0/kibom-variant_3-top-C1.png b/tests/reference/7_0_0/kibom-variant_3-top-C1.png deleted file mode 120000 index 2c925fe4..00000000 --- a/tests/reference/7_0_0/kibom-variant_3-top-C1.png +++ /dev/null @@ -1 +0,0 @@ -../6_0_8/kibom-variant_3-top-C1.png \ No newline at end of file diff --git a/tests/reference/7_0_0/kibom-variant_3-top-C1.png b/tests/reference/7_0_0/kibom-variant_3-top-C1.png new file mode 100644 index 00000000..d1caa75a Binary files /dev/null and b/tests/reference/7_0_0/kibom-variant_3-top-C1.png differ diff --git a/tests/reference/7_0_0/kibom-variant_3-top.png b/tests/reference/7_0_0/kibom-variant_3-top.png deleted file mode 120000 index f0a6eea7..00000000 --- a/tests/reference/7_0_0/kibom-variant_3-top.png +++ /dev/null @@ -1 +0,0 @@ -../6_0_8/kibom-variant_3-top.png \ No newline at end of file diff --git a/tests/reference/7_0_0/kibom-variant_3-top.png b/tests/reference/7_0_0/kibom-variant_3-top.png new file mode 100644 index 00000000..18cd03cd Binary files /dev/null and b/tests/reference/7_0_0/kibom-variant_3-top.png differ