246 lines
9.7 KiB
Markdown
246 lines
9.7 KiB
Markdown
# PcbDraw code
|
|
|
|
## Why?
|
|
|
|
- Important dependency: PcbDraw is currently a core functionality of KiBot because its used for the `pcb_print` output
|
|
- Increased number of dependencies: The upstream code pulls too much dependencies, some of them optional, others that we don't need.
|
|
This is a constant problem.
|
|
- Incompatible interface and behavior: This should be fixed now that 1.0.0 is out, but I don't agree with the idea of doing small
|
|
changes just because they look more elegant.
|
|
- Now integrable: This is one of the changes in 1.0.0, now the code is easier to call as module.
|
|
- Repeated functionality: The `render` stuff is already implemented by KiAuto.
|
|
|
|
## Details
|
|
|
|
Currently only the `plot` module is included.
|
|
|
|
### convert.py
|
|
|
|
- Made the `pcbdraw` import relative
|
|
- Removed PIL as dependency
|
|
- So now the save function only supports SVG as input and SVG/PNG as output
|
|
- All other cases are handled from outside
|
|
- This is because KiBot heavily uses ImageMagick and migrating to PIL is not something simple
|
|
- There is no point in using PIL just for file conversion, as we don't use `render` this is the only use
|
|
|
|
```diff
|
|
diff --git a/kibot/PcbDraw/convert.py b/kibot/PcbDraw/convert.py
|
|
index ba856a69..7fe64738 100644
|
|
--- a/kibot/PcbDraw/convert.py
|
|
+++ b/kibot/PcbDraw/convert.py
|
|
@@ -6,7 +6,7 @@ import textwrap
|
|
import os
|
|
from typing import Union
|
|
from tempfile import TemporaryDirectory
|
|
-from PIL import Image
|
|
+# from PIL import Image
|
|
from lxml.etree import _ElementTree # type: ignore
|
|
|
|
# Converting SVG to bitmap is a hard problem. We used Wand (and thus
|
|
@@ -66,17 +66,17 @@ def svgToPng(inputFilename: str, outputFilename: str, dpi: int=300) -> None:
|
|
message += textwrap.indent(m, " ")
|
|
raise RuntimeError(message)
|
|
|
|
-def save(image: Union[_ElementTree, Image.Image], filename: str, dpi: int=600) -> None:
|
|
+def save(image: _ElementTree, filename: str, dpi: int=600, format: str=None) -> None:
|
|
"""
|
|
Given an SVG tree or an image, save to a filename. The format is deduced
|
|
from the extension.
|
|
"""
|
|
- ftype = os.path.splitext(filename)[1][1:].lower()
|
|
- if isinstance(image, Image.Image):
|
|
- if ftype not in ["jpg", "jpeg", "png", "bmp"]:
|
|
- raise TypeError(f"Cannot save bitmap image into {ftype}")
|
|
- image.save(filename)
|
|
- return
|
|
+ ftype = os.path.splitext(filename)[1][1:].lower() if format is None else format
|
|
+# if isinstance(image, Image.Image):
|
|
+# if ftype not in ["jpg", "jpeg", "png", "bmp"]:
|
|
+# raise TypeError(f"Cannot save bitmap image into {ftype}")
|
|
+# image.save(filename)
|
|
+# return
|
|
if isinstance(image, _ElementTree):
|
|
if ftype == "svg":
|
|
image.write(filename)
|
|
@@ -91,6 +91,6 @@ def save(image: Union[_ElementTree, Image.Image], filename: str, dpi: int=600) -
|
|
svgToPng(svg_filename, png_filename, dpi=dpi)
|
|
if ftype == "png":
|
|
return
|
|
- Image.open(png_filename).convert("RGB").save(filename)
|
|
- return
|
|
+# Image.open(png_filename).convert("RGB").save(filename)
|
|
+# return
|
|
raise TypeError(f"Unknown image type: {type(image)}")
|
|
```
|
|
|
|
### convert_common.py
|
|
|
|
No current changes
|
|
|
|
### convert_unix.py
|
|
|
|
- Made the `pcbdraw` import relative
|
|
|
|
### convert_windows.py
|
|
|
|
- Made the `pcbdraw` import relative
|
|
|
|
### unit.py
|
|
|
|
- Replaced `unit` code.
|
|
- So we have only one units conversion
|
|
- I think the only difference is that KiBot code currently supports the locales decimal point
|
|
|
|
### plot.py
|
|
|
|
- Made the `pcbdraw` import relative
|
|
- Disabled `shrink_svg`
|
|
- Changes the old behavior, so this should be optional
|
|
- Pulls a problematic dependency: svgpathtool
|
|
- But we keep the margin addition
|
|
- Changed calls to `ComputeBoundingBox()` to use `aBoardEdgesOnly=True`
|
|
- To get the same behavior as 0.9.0-5
|
|
- This changes the size of the SVG to the size of the board
|
|
- `shrink_svg` must be disabled or it reverts the size to the detected
|
|
- Added `no_warn_back` option to disable warnings on the opposite side
|
|
|
|
```diff
|
|
@@ -813,6 +813,7 @@
|
|
highlight: Callable[[str], bool] = lambda x: False # References to highlight
|
|
remapping: Callable[[str, str, str], Tuple[str, str]] = lambda ref, lib, name: (lib, name)
|
|
resistor_values: Dict[str, ResistorValue] = field(default_factory=dict)
|
|
+ no_warn_back: bool = False
|
|
|
|
def render(self, plotter: PcbPlotter) -> None:
|
|
self._plotter = plotter
|
|
@@ -848,7 +849,8 @@
|
|
else:
|
|
ret = self._create_component(lib, name, ref, value)
|
|
if ret is None:
|
|
- self._plotter.yield_warning("component", f"Component {lib}:{name} has not footprint.")
|
|
+ if name[-5:] != '.back' or not self.no_warn_back:
|
|
+ self._plotter.yield_warning("component", f"Component {lib}:{name} has not footprint.")
|
|
return
|
|
component_element, component_info = ret
|
|
self._used_components[unique_name] = component_info
|
|
```
|
|
|
|
- Added option to plot only the solder mask. The PCB_Print output uses it and plotting all the stuff looks stupid.
|
|
The patch added too much nested conditionals so I moved the information to data structures.
|
|
The patch looks big, but is just a mechanism to skip the unneeded layers.
|
|
|
|
```diff
|
|
diff --git a/kibot/PcbDraw/plot.py b/kibot/PcbDraw/plot.py
|
|
index af473cdb..f8990722 100644
|
|
--- a/kibot/PcbDraw/plot.py
|
|
+++ b/kibot/PcbDraw/plot.py
|
|
@@ -648,35 +648,44 @@ class PlotInterface:
|
|
raise NotImplementedError("Plot interface wasn't implemented")
|
|
|
|
|
|
+SUBSTRATE_ELEMENTS = {
|
|
+ "board": (pcbnew.Edge_Cuts, pcbnew.Edge_Cuts),
|
|
+ "clad": (pcbnew.F_Mask, pcbnew.B_Mask),
|
|
+ "copper": (pcbnew.F_Cu, pcbnew.B_Cu),
|
|
+ "pads": (pcbnew.F_Cu, pcbnew.B_Cu),
|
|
+ "pads-mask": (pcbnew.F_Mask, pcbnew.B_Mask),
|
|
+ "silk": (pcbnew.F_SilkS, pcbnew.B_SilkS),
|
|
+ "outline": (pcbnew.Edge_Cuts, pcbnew.Edge_Cuts)
|
|
+}
|
|
+ELEMENTS_USED = (
|
|
+ # Normal plot, all the elements
|
|
+ ("board", "clad", "copper", "pads", "pads-mask", "silk", "outline"),
|
|
+ # Solder mask plot
|
|
+ ("board", "pads-mask")
|
|
+)
|
|
+
|
|
+
|
|
@dataclass
|
|
class PlotSubstrate(PlotInterface):
|
|
drill_holes: bool = True
|
|
outline_width: int = mm2ki(0.1)
|
|
+ only_mask: bool = False
|
|
|
|
def render(self, plotter: PcbPlotter) -> None:
|
|
self._plotter = plotter # ...so we don't have to pass it explicitly
|
|
+ SUBSTRATE_PROCESS = {
|
|
+ "board": self._process_baselayer,
|
|
+ "clad": self._process_layer,
|
|
+ "copper": self._process_layer,
|
|
+ "pads": self._process_layer,
|
|
+ "pads-mask": self._process_mask,
|
|
+ "silk": self._process_layer,
|
|
+ "outline": self._process_outline
|
|
+ }
|
|
|
|
to_plot: List[PlotAction] = []
|
|
- if plotter.render_back:
|
|
- to_plot = [
|
|
- PlotAction("board", [pcbnew.Edge_Cuts], self._process_baselayer),
|
|
- PlotAction("clad", [pcbnew.B_Mask], self._process_layer),
|
|
- PlotAction("copper", [pcbnew.B_Cu], self._process_layer),
|
|
- PlotAction("pads", [pcbnew.B_Cu], self._process_layer),
|
|
- PlotAction("pads-mask", [pcbnew.B_Mask], self._process_mask),
|
|
- PlotAction("silk", [pcbnew.B_SilkS], self._process_layer),
|
|
- PlotAction("outline", [pcbnew.Edge_Cuts], self._process_outline)
|
|
- ]
|
|
- else:
|
|
- to_plot = [
|
|
- PlotAction("board", [pcbnew.Edge_Cuts], self._process_baselayer),
|
|
- PlotAction("clad", [pcbnew.F_Mask], self._process_layer),
|
|
- PlotAction("copper", [pcbnew.F_Cu], self._process_layer),
|
|
- PlotAction("pads", [pcbnew.F_Cu], self._process_layer),
|
|
- PlotAction("pads-mask", [pcbnew.F_Mask], self._process_mask),
|
|
- PlotAction("silk", [pcbnew.F_SilkS], self._process_layer),
|
|
- PlotAction("outline", [pcbnew.Edge_Cuts], self._process_outline)
|
|
- ]
|
|
+ for e in ELEMENTS_USED[self.only_mask]:
|
|
+ to_plot.append(PlotAction(e, [SUBSTRATE_ELEMENTS[e][plotter.render_back]], SUBSTRATE_PROCESS[e]))
|
|
|
|
self._container = etree.Element("g", id="substrate")
|
|
self._container.attrib["clip-path"] = "url(#cut-off)"
|
|
```
|
|
|
|
- Fixed the `collect_holes` function to support KiCad 5
|
|
- pad.GetDrillSizeX() and pad.GetDrillSizeY() are KiCad 6 specific, you must use pad.GetDrillSize()
|
|
- KiCad 5 vias were skipped
|
|
- Vias detection crashed on KiCad 5
|
|
|
|
```diff
|
|
diff --git a/kibot/PcbDraw/plot.py b/kibot/PcbDraw/plot.py
|
|
index f8990722..17f90185 100644
|
|
--- a/kibot/PcbDraw/plot.py
|
|
+++ b/kibot/PcbDraw/plot.py
|
|
@@ -626,13 +626,15 @@ def collect_holes(board: pcbnew.BOARD) -> List[Hole]:
|
|
continue
|
|
for pad in module.Pads():
|
|
pos = pad.GetPosition()
|
|
+ drs = pad.GetDrillSize()
|
|
holes.append(Hole(
|
|
position=(pos[0], pos[1]),
|
|
orientation=pad.GetOrientation(),
|
|
- drillsize=(pad.GetDrillSizeX(), pad.GetDrillSizeY())
|
|
+ drillsize=(drs.x, drs.y)
|
|
))
|
|
+ via_type = 'VIA' if not isV6(KICAD_VERSION) else 'PCB_VIA'
|
|
for track in board.GetTracks():
|
|
- if not isinstance(track, pcbnew.PCB_VIA) or not isV6(KICAD_VERSION):
|
|
+ if track.GetClass() != via_type:
|
|
continue
|
|
pos = track.GetPosition()
|
|
holes.append(Hole(
|
|
```
|
|
|
|
- Changed `pcbnewTransition` to be locally included.
|
|
- Just 2.8 kiB no worth the effort of pulling a dependency
|
|
|
|
- Replaced `numpy` by a very simple code
|
|
- Currently svgpathtool is disabled, it really needs numpy
|
|
- `numpy` is used to:
|
|
- Multiply matrices (1 line code)
|
|
- Find the index of the smaller element (1 line code)
|
|
- I added a replacemt for the `array` function, it just makes all matrix elements float
|
|
|
|
- Allow constructing PcbPlotter() using an already loaded board
|
|
- So we don't load it again
|