[PcbDraw] Embedded the PcbDraw.plot code
- Now PcbDraw is not a dependency of out_pcbdraw.py
This commit is contained in:
parent
1fdd3c26d1
commit
838129e69c
|
|
@ -1,3 +1,6 @@
|
|||
[submodule "submodules/KiCost"]
|
||||
path = submodules/KiCost
|
||||
url = https://github.com/hildogjr/KiCost.git
|
||||
[submodule "kibot/PcbDraw/resources/footprints"]
|
||||
path = kibot/PcbDraw/resources/footprints
|
||||
url = https://github.com/yaqwsx/PcbDraw-Lib.git
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ exclude:
|
|||
experiments/.*|
|
||||
submodules/.*|
|
||||
kibot/PyPDF2/.*|
|
||||
kibot/PcbDraw/.*|
|
||||
tests/yaml_samples/simple_position_csv_pre.kibot.yaml
|
||||
)$
|
||||
repos:
|
||||
|
|
|
|||
28
README.md
28
README.md
|
|
@ -7,7 +7,7 @@
|
|||
[](https://pypi.org/project/kibot/)
|
||||
[](https://www.paypal.com/donate/?hosted_button_id=K2T86GDTTMRPL)
|
||||
|
||||
# **This is the documentation for the current development KiBot, not yet released.**
|
||||
# **This is the documentation for KiBot v1.4.0 for the current development read [here](https://github.com/INTI-CMNB/KiBot/tree/dev).**
|
||||
|
||||
|
||||
**Important for CI/CD**:
|
||||
|
|
@ -142,18 +142,13 @@ Notes:
|
|||
- Mandatory for `kicost`
|
||||
- Optional to find components costs and specs for `bom`
|
||||
|
||||
[**PcbDraw**](https://github.com/INTI-CMNB/pcbdraw) v0.9.0.3 (<1.0) [](https://github.com/INTI-CMNB/pcbdraw) 
|
||||
- Mandatory for `pcbdraw`
|
||||
- Optional to create realistic solder masks for `pcb_print`
|
||||
- Note: Currently the upstream version is broken, please use the mentioned fork
|
||||
|
||||
[**Interactive HTML BoM**](https://github.com/INTI-CMNB/InteractiveHtmlBom) v2.4.1.4 [](https://github.com/INTI-CMNB/InteractiveHtmlBom) 
|
||||
- Mandatory for `ibom`
|
||||
|
||||
[**KiBoM**](https://github.com/INTI-CMNB/KiBoM) v1.8.0 [](https://github.com/INTI-CMNB/KiBoM) 
|
||||
- Mandatory for `kibom`
|
||||
|
||||
[**KiCad PCB/SCH Diff**](https://github.com/INTI-CMNB/KiDiff) v2.4.2 [](https://github.com/INTI-CMNB/KiDiff) 
|
||||
[**KiCad PCB/SCH Diff**](https://github.com/INTI-CMNB/KiDiff) v2.4.3 [](https://github.com/INTI-CMNB/KiDiff) 
|
||||
- Mandatory for `diff`
|
||||
|
||||
[**LXML**](https://pypi.org/project/LXML/) [](https://pypi.org/project/LXML/) [](https://packages.debian.org/bullseye/python3-lxml) 
|
||||
|
|
@ -177,23 +172,25 @@ Notes:
|
|||
- Create outputs preview for `navigate_results`
|
||||
- Create PNG icons for `navigate_results`
|
||||
- Create PDF, PNG, PS and EPS formats for `pcb_print`
|
||||
- Create PNG and JPG images for `pcbdraw`
|
||||
|
||||
[**ImageMagick**](https://imagemagick.org/) [](https://imagemagick.org/) [](https://packages.debian.org/bullseye/imagemagick) 
|
||||
- Optional to:
|
||||
- Create outputs preview for `navigate_results`
|
||||
- Create monochrome prints and scaled PNG files for `pcb_print`
|
||||
- Create JPG images for `pcbdraw`
|
||||
|
||||
[**Ghostscript**](https://www.ghostscript.com/) [](https://www.ghostscript.com/) [](https://packages.debian.org/bullseye/ghostscript) 
|
||||
- Optional to:
|
||||
- Create outputs preview for `navigate_results`
|
||||
- Create PNG, PS and EPS formats for `pcb_print`
|
||||
|
||||
[**ImageMagick**](https://imagemagick.org/) [](https://imagemagick.org/) [](https://packages.debian.org/bullseye/imagemagick) 
|
||||
- Optional to:
|
||||
- Create outputs preview for `navigate_results`
|
||||
- Create monochrome prints and scaled PNG files for `pcb_print`
|
||||
|
||||
[**Pandoc**](https://pandoc.org/) [](https://pandoc.org/) [](https://packages.debian.org/bullseye/pandoc)
|
||||
- Optional to create PDF/ODF/DOCX files for `report`
|
||||
- Note: In CI/CD environments: the `kicad_auto_test` docker image contains it.
|
||||
|
||||
[**PcbDraw**](https://github.com/INTI-CMNB/pcbdraw) v0.9.0.3 (<1.0) [](https://github.com/INTI-CMNB/pcbdraw) 
|
||||
- Optional to create realistic solder masks for `pcb_print`
|
||||
- Note: Currently the upstream version is broken, please use the mentioned fork
|
||||
|
||||
[**RAR**](https://www.rarlab.com/) [](https://www.rarlab.com/) [](https://packages.debian.org/bullseye/rar) 
|
||||
- Optional to compress in RAR format for `compress`
|
||||
|
||||
|
|
@ -1746,6 +1743,7 @@ Notes:
|
|||
Use `multivar` to specify a reference file when `new_type` is also `multivar`.
|
||||
- `only_different`: [boolean=false] Only include the pages with differences in the output PDF.
|
||||
Note that when no differeces are found we get a page saying *No diff*.
|
||||
- `only_first_sch_page`: [boolean=false] Compare only the main schematic page (root page).
|
||||
- `pcb`: [boolean=true] Compare the PCB, otherwise compare the schematic.
|
||||
- `threshold`: [number=0] [0,1000000] Error threshold for the `stats` mode, 0 is no error. When specified a
|
||||
difference bigger than the indicated value will make the diff fail.
|
||||
|
|
@ -2566,7 +2564,7 @@ Notes:
|
|||
- **`mirror`**: [boolean=false] Mirror the board.
|
||||
- **`output`**: [string='%f-%i%I%v.%x'] Name for the generated file. Affected by global options.
|
||||
- **`show_components`**: [list(string)|string=none] [none,all] List of components to draw, can be also a string for none or all.
|
||||
The default is none.
|
||||
The default is none. IMPORTANT! This option is relevant only when no filters or variants are applied.
|
||||
- **`style`**: [string|dict] PCB style (colors). An internal name, the name of a JSON file or the style options.
|
||||
* Valid keys:
|
||||
- **`board`**: [string='#208b47'] Color for the board without copper (covered by solder mask).
|
||||
|
|
|
|||
|
|
@ -533,6 +533,8 @@ outputs:
|
|||
# [boolean=false] Only include the pages with differences in the output PDF.
|
||||
# Note that when no differeces are found we get a page saying *No diff*
|
||||
only_different: false
|
||||
# [boolean=false] Compare only the main schematic page (root page)
|
||||
only_first_sch_page: false
|
||||
# [string='%f-%i%I%v.%x'] Filename for the output (%i=diff_pcb/diff_sch, %x=pdf). Affected by global options
|
||||
output: '%f-%i%I%v.%x'
|
||||
# [boolean=true] Compare the PCB, otherwise compare the schematic
|
||||
|
|
@ -1374,7 +1376,7 @@ outputs:
|
|||
# [dict|None] Replacements for PCB references using components (lib:component)
|
||||
remap:
|
||||
# [list(string)|string=none] [none,all] List of components to draw, can be also a string for none or all.
|
||||
# The default is none
|
||||
# The default is none. IMPORTANT! This option is relevant only when no filters or variants are applied
|
||||
show_components: none
|
||||
# [string|dict] PCB style (colors). An internal name, the name of a JSON file or the style options
|
||||
style:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,68 @@
|
|||
# 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
|
||||
|
||||
### convert_common.py
|
||||
|
||||
No current changes
|
||||
|
||||
### convert_unix.py
|
||||
|
||||
- Made the `pcbdraw` import relative
|
||||
|
||||
### convert_windows.py
|
||||
|
||||
- Made the `pcbdraw` import relative
|
||||
|
||||
### unit.py
|
||||
|
||||
No current changes
|
||||
|
||||
### plot.py
|
||||
|
||||
- Made the `pcbdraw` import relative
|
||||
- Disabled `shrink_svg`
|
||||
- Changes the old behavior, so this should be optional
|
||||
- Pulls a problematic dependency: svgpathtool
|
||||
- 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
|
||||
```
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
# Author: Jan Mrázek
|
||||
# License: MIT
|
||||
import platform
|
||||
import subprocess
|
||||
import textwrap
|
||||
import os
|
||||
from typing import Union
|
||||
from tempfile import TemporaryDirectory
|
||||
from PIL import Image
|
||||
from lxml.etree import _ElementTree # type: ignore
|
||||
|
||||
# Converting SVG to bitmap is a hard problem. We used Wand (and thus
|
||||
# imagemagick) to do the conversion. However, imagemagick is really hard to
|
||||
# configure properly and it breaks often. Therefore, we provide a custom module
|
||||
# that has several conversion strategies that reflect the platform. We also try
|
||||
# to provide descriptive enough message so the user can detect what is wrong.
|
||||
|
||||
if platform.system() == "Windows":
|
||||
from .convert_windows import detectInkscape
|
||||
else:
|
||||
from .convert_unix import detectInkscape, rsvgSvgToPng
|
||||
|
||||
def inkscapeSvgToPng(inputFilename: str, outputFilename: str, dpi: int) -> None:
|
||||
"""
|
||||
A strategy to convert an SVG file into a PNG file using Inkscape
|
||||
"""
|
||||
command = [detectInkscape(), "--export-type=png", f"--export-dpi={dpi}",
|
||||
f"--export-filename={outputFilename}", inputFilename]
|
||||
def reportError(message: str) -> None:
|
||||
raise RuntimeError(f"Cannot convert {inputFilename} to {outputFilename}. Inkscape failed with:\n"
|
||||
+ textwrap.indent(message, " "))
|
||||
try:
|
||||
r = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
output = r.stdout.decode("utf-8") + "\n" + r.stderr.decode("utf-8")
|
||||
# Inkscape doesn't respect error codes
|
||||
if "Can't open file" in output:
|
||||
reportError(output)
|
||||
except subprocess.CalledProcessError as e:
|
||||
output = e.stdout.decode("utf-8") + "\n" + e.stderr.decode("utf-8")
|
||||
reportError(output)
|
||||
|
||||
def svgToPng(inputFilename: str, outputFilename: str, dpi: int=300) -> None:
|
||||
"""
|
||||
Convert SVG file into a PNG file based on platform-dependent strategies
|
||||
"""
|
||||
if platform.system() == "Windows":
|
||||
strategies = {
|
||||
"Inkscape": inkscapeSvgToPng
|
||||
}
|
||||
else:
|
||||
strategies = {
|
||||
"RSVG": rsvgSvgToPng, # We prefer it over Inkscape as it is much faster
|
||||
"Inkscape": inkscapeSvgToPng
|
||||
}
|
||||
|
||||
errors = {}
|
||||
for name, strategy in strategies.items():
|
||||
try:
|
||||
strategy(inputFilename, outputFilename, dpi)
|
||||
return
|
||||
except Exception as e:
|
||||
errors[name] = str(e)
|
||||
message = "Cannot convert PNG to SVG; all strategies failed:\n"
|
||||
for name, error in errors.items():
|
||||
m = f"- Strategy '{name}' failed with: {textwrap.indent(error, ' ')}\n"
|
||||
message += textwrap.indent(m, " ")
|
||||
raise RuntimeError(message)
|
||||
|
||||
def save(image: Union[_ElementTree, Image.Image], filename: str, dpi: int=600) -> 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
|
||||
if isinstance(image, _ElementTree):
|
||||
if ftype == "svg":
|
||||
image.write(filename)
|
||||
return
|
||||
with TemporaryDirectory() as d:
|
||||
svg_filename = os.path.join(d, "image.svg")
|
||||
if ftype == "png":
|
||||
png_filename = filename
|
||||
else:
|
||||
png_filename = os.path.join(d, "image.png")
|
||||
image.write(svg_filename)
|
||||
svgToPng(svg_filename, png_filename, dpi=dpi)
|
||||
if ftype == "png":
|
||||
return
|
||||
Image.open(png_filename).convert("RGB").save(filename)
|
||||
return
|
||||
raise TypeError(f"Unknown image type: {type(image)}")
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
# Author: Jan Mrázek
|
||||
# License: MIT
|
||||
import subprocess
|
||||
from typing import List
|
||||
|
||||
def isValidInkscape(executable: str) -> bool:
|
||||
try:
|
||||
out = subprocess.check_output([executable, "--version"]).decode("utf-8")
|
||||
parts = out.split(" ")
|
||||
if parts[0] != "Inkscape":
|
||||
return False
|
||||
version = parts[1].split(".")
|
||||
return int(version[0]) == 1
|
||||
except FileNotFoundError:
|
||||
return False
|
||||
except subprocess.CalledProcessError as e:
|
||||
return False
|
||||
|
||||
def chooseInkscapeCandidate(candidates: List[str]) -> str:
|
||||
for candidate in candidates:
|
||||
if isValidInkscape(candidate):
|
||||
return candidate
|
||||
raise RuntimeError("No Inkscape executable found. Please check:\n" +
|
||||
"- if Inkscape is installed\n" +
|
||||
"- if it is version at least 1.0\n" +
|
||||
"If the conditions above are true, please ensure Inkscape is in PATH or\n" +
|
||||
"ensure there is environmental variable 'PCBDRAW_INKSCAPE' pointing to the Inkscape executable\n\n" +
|
||||
"Checked paths: \n" +
|
||||
"\n".join([f"- {x}" for x in candidates]))
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
# Author: Jan Mrázek
|
||||
# License: MIT
|
||||
import subprocess
|
||||
import os
|
||||
import textwrap
|
||||
from .convert_common import chooseInkscapeCandidate
|
||||
|
||||
def detectInkscape() -> str:
|
||||
"""
|
||||
Return path to working Inkscape >v1.0 executable
|
||||
"""
|
||||
candidates = []
|
||||
if "PCBDRAW_INKSCAPE" in os.environ:
|
||||
# Ensure there is the .com extension needed for CLI interface
|
||||
path = os.path.splitext(os.environ["PCBDRAW_INKSCAPE"])[0] + ".com"
|
||||
candidates.append(path)
|
||||
candidates.append("inkscape") # Inkscape in path
|
||||
return chooseInkscapeCandidate(candidates)
|
||||
|
||||
def rsvgSvgToPng(inputFilename: str, outputFilename: str, dpi: int) -> None:
|
||||
tool = os.environ.get("PCBDRAW_RSVG", "rsvg-convert")
|
||||
command = [tool, "--dpi-x", str(dpi), "--dpi-y", str(dpi),
|
||||
"--output", outputFilename, "--format", "png", inputFilename]
|
||||
def reportError(message: str) -> None:
|
||||
raise RuntimeError(f"Cannot convert {inputFilename} to {outputFilename}. RSVG failed with:\n"
|
||||
+ textwrap.indent(message, " "))
|
||||
try:
|
||||
r = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
if r.returncode != 0:
|
||||
output = r.stdout.decode("utf-8") + "\n" + r.stderr.decode("utf-8")
|
||||
reportError(output)
|
||||
except subprocess.CalledProcessError as e:
|
||||
output = e.stdout.decode("utf-8") + "\n" + e.stderr.decode("utf-8")
|
||||
reportError(output)
|
||||
except FileNotFoundError:
|
||||
reportError("rsvg-convert is not available. Please make sure it is installed.\n" +
|
||||
f"It was executed via invoking '{tool}'")
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
# Author: Jan Mrázek
|
||||
# License: MIT
|
||||
import os
|
||||
# Reports false error on Linux as LnkParse3 is Windows-only dependency
|
||||
import LnkParse3 # type: ignore
|
||||
from typing import List
|
||||
from .convert_common import chooseInkscapeCandidate
|
||||
|
||||
def detectInkscape() -> str:
|
||||
"""
|
||||
Return path to working Inkscape >v1.0 executable
|
||||
"""
|
||||
candidates = []
|
||||
if "PCBDRAW_INKSCAPE" in os.environ:
|
||||
# Ensure there is the .com extension needed for CLI interface
|
||||
path = os.path.splitext(os.environ["PCBDRAW_INKSCAPE"])[0] + ".com"
|
||||
candidates.append(path)
|
||||
candidates.append("inkscape") # Inkscape in path
|
||||
candidates += readInkscapeFromStartMenu()
|
||||
|
||||
return chooseInkscapeCandidate(candidates)
|
||||
|
||||
def readInkscapeFromStartMenu() -> List[str]:
|
||||
candidates = []
|
||||
for profile in [os.environ.get("ALLUSERSPROFILE", ""), os.environ.get("USERPROFILE", "")]:
|
||||
path = os.path.join(profile, "Microsoft", "Windows", "Start Menu",
|
||||
"Programs", "Inkscape", "Inkscape.lnk")
|
||||
try:
|
||||
with open(path, "rb") as f:
|
||||
lnk = LnkParse3.lnk_file(f)
|
||||
abspath = os.path.realpath(lnk.string_data.relative_path())
|
||||
# The .com version provides CLI interface
|
||||
abspath = os.path.splitext(abspath)[0] + ".com"
|
||||
candidates.append(abspath)
|
||||
except FileNotFoundError:
|
||||
continue
|
||||
return candidates
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(detectInkscape())
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1 @@
|
|||
Subproject commit d582bc725fe987e35f291bc06e318cf6d3b263d2
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"copper": "#417e5a",
|
||||
"board": "#4ca06c",
|
||||
"silk": "#f0f0f0",
|
||||
"pads": "#b5ae30",
|
||||
"clad": "#9c6b28",
|
||||
"outline": "#000000",
|
||||
"vcut": "#bf2600",
|
||||
"highlight-on-top": false,
|
||||
"highlight-style": "stroke:none;fill:#ff0000;opacity:0.5;",
|
||||
"highlight-padding": 1.5,
|
||||
"highlight-offset": 0
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"copper": "#208b47",
|
||||
"board": "#285e3a",
|
||||
"silk": "#f0f0f0",
|
||||
"pads": "#b5ae30",
|
||||
"clad": "#9c6b28",
|
||||
"outline": "#000000",
|
||||
"vcut": "#bf2600",
|
||||
"highlight-on-top": false,
|
||||
"highlight-style": "stroke:none;fill:#ff0000;opacity:0.5;",
|
||||
"highlight-padding": 1.5,
|
||||
"highlight-offset": 0
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"copper": "#208b47",
|
||||
"board": "#285e3a",
|
||||
"silk": "#f0f0f0",
|
||||
"pads": "#bfba9e",
|
||||
"clad": "#9c6b28",
|
||||
"outline": "#000000",
|
||||
"vcut": "#bf2600",
|
||||
"highlight-on-top": false,
|
||||
"highlight-style": "stroke:none;fill:#ff0000;opacity:0.5;",
|
||||
"highlight-padding": 1.5,
|
||||
"highlight-offset": 0
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"board": "#6c651d",
|
||||
"copper": "#f2a756",
|
||||
"silk": "#d5dce4",
|
||||
"pads": "#8b898c",
|
||||
"clad": "#656e5b",
|
||||
"outline": "#000000",
|
||||
"vcut": "#bf2600",
|
||||
"highlight-on-top": false,
|
||||
"highlight-style": "stroke:none;fill:#ff0000;opacity:0.5;",
|
||||
"highlight-padding": 1.5,
|
||||
"highlight-offset": 0
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"copper": "#af6640",
|
||||
"board": "#323232",
|
||||
"silk": "#d8dae7",
|
||||
"pads": "#dec500",
|
||||
"clad": "#5d4e44",
|
||||
"outline": "#000000",
|
||||
"vcut": "#f0f0f0",
|
||||
"highlight-on-top": false,
|
||||
"highlight-style": "stroke:none;fill:#ff0000;opacity:0.5;",
|
||||
"highlight-padding": 1.5,
|
||||
"highlight-offset": 0
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"copper": "#451d70",
|
||||
"board": "#30234a",
|
||||
"silk": "#d8dae7",
|
||||
"pads": "#ede8b9",
|
||||
"clad": "#5d4e44",
|
||||
"outline": "#000000",
|
||||
"vcut": "#f0f0f0",
|
||||
"highlight-on-top": false,
|
||||
"highlight-style": "stroke:none;fill:#ff0000;opacity:0.5;",
|
||||
"highlight-padding": 1.5,
|
||||
"highlight-offset": 0
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"board": "#1d1918",
|
||||
"copper": "#2d2522",
|
||||
"silk": "#d5dce4",
|
||||
"pads": "#d39751",
|
||||
"clad": "#72786c",
|
||||
"outline": "#000000",
|
||||
"vcut": "#bf2600",
|
||||
"highlight-on-top": false,
|
||||
"highlight-style": "stroke:none;fill:#ff0000;opacity:0.5;",
|
||||
"highlight-padding": 1.5,
|
||||
"highlight-offset": 0
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"board": "#1d1918",
|
||||
"copper": "#2d2522",
|
||||
"silk": "#d5dce4",
|
||||
"pads": "#cfb96e",
|
||||
"clad": "#72786c",
|
||||
"outline": "#000000",
|
||||
"vcut": "#bf2600",
|
||||
"highlight-on-top": false,
|
||||
"highlight-style": "stroke:none;fill:#ff0000;opacity:0.5;",
|
||||
"highlight-padding": 1.5,
|
||||
"highlight-offset": 0
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"board": "#1d1918",
|
||||
"copper": "#2d2522",
|
||||
"silk": "#d5dce4",
|
||||
"pads": "#8b898c",
|
||||
"clad": "#72786c",
|
||||
"outline": "#000000",
|
||||
"vcut": "#bf2600",
|
||||
"highlight-on-top": false,
|
||||
"highlight-style": "stroke:none;fill:#ff0000;opacity:0.5;",
|
||||
"highlight-padding": 1.5,
|
||||
"highlight-offset": 0
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"board": "#1b1f44",
|
||||
"copper": "#00406a",
|
||||
"silk": "#d5dce4",
|
||||
"pads": "#d39751",
|
||||
"clad": "#72786c",
|
||||
"outline": "#000000",
|
||||
"vcut": "#bf2600",
|
||||
"highlight-on-top": false,
|
||||
"highlight-style": "stroke:none;fill:#ff0000;opacity:0.5;",
|
||||
"highlight-padding": 1.5,
|
||||
"highlight-offset": 0
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"board": "#1b1f44",
|
||||
"copper": "#00406a",
|
||||
"silk": "#d5dce4",
|
||||
"pads": "#cfb96e",
|
||||
"clad": "#72786c",
|
||||
"outline": "#000000",
|
||||
"vcut": "#bf2600",
|
||||
"highlight-on-top": false,
|
||||
"highlight-style": "stroke:none;fill:#ff0000;opacity:0.5;",
|
||||
"highlight-padding": 1.5,
|
||||
"highlight-offset": 0
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"board": "#1b1f44",
|
||||
"copper": "#00406a",
|
||||
"silk": "#d5dce4",
|
||||
"pads": "#8b898c",
|
||||
"clad": "#72786c",
|
||||
"outline": "#000000",
|
||||
"vcut": "#bf2600",
|
||||
"highlight-on-top": false,
|
||||
"highlight-style": "stroke:none;fill:#ff0000;opacity:0.5;",
|
||||
"highlight-padding": 1.5,
|
||||
"highlight-offset": 0
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"board": "#812e2a",
|
||||
"copper": "#be352b",
|
||||
"silk": "#d5dce4",
|
||||
"pads": "#d39751",
|
||||
"clad": "#72786c",
|
||||
"outline": "#000000",
|
||||
"vcut": "#bf2600",
|
||||
"highlight-on-top": false,
|
||||
"highlight-style": "stroke:none;fill:#ff0000;opacity:0.5;",
|
||||
"highlight-padding": 1.5,
|
||||
"highlight-offset": 0
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"board": "#812e2a",
|
||||
"copper": "#be352b",
|
||||
"silk": "#d5dce4",
|
||||
"pads": "#cfb96e",
|
||||
"clad": "#72786c",
|
||||
"outline": "#000000",
|
||||
"vcut": "#bf2600",
|
||||
"highlight-on-top": false,
|
||||
"highlight-style": "stroke:none;fill:#ff0000;opacity:0.5;",
|
||||
"highlight-padding": 1.5,
|
||||
"highlight-offset": 0
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"board": "#812e2a",
|
||||
"copper": "#be352b",
|
||||
"silk": "#d5dce4",
|
||||
"pads": "#8b898c",
|
||||
"clad": "#72786c",
|
||||
"outline": "#000000",
|
||||
"vcut": "#bf2600",
|
||||
"highlight-on-top": false,
|
||||
"highlight-style": "stroke:none;fill:#ff0000;opacity:0.5;",
|
||||
"highlight-padding": 1.5,
|
||||
"highlight-offset": 0
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"board": "#bdccc7",
|
||||
"copper": "#b7b7ad",
|
||||
"silk": "#0b1013",
|
||||
"pads": "#d39751",
|
||||
"clad": "#72786c",
|
||||
"outline": "#000000",
|
||||
"vcut": "#bf2600",
|
||||
"highlight-on-top": false,
|
||||
"highlight-style": "stroke:none;fill:#ff0000;opacity:0.5;",
|
||||
"highlight-padding": 1.5,
|
||||
"highlight-offset": 0
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"board": "#bdccc7",
|
||||
"copper": "#b7b7ad",
|
||||
"silk": "#0b1013",
|
||||
"pads": "#cfb96e",
|
||||
"clad": "#72786c",
|
||||
"outline": "#000000",
|
||||
"vcut": "#bf2600",
|
||||
"highlight-on-top": false,
|
||||
"highlight-style": "stroke:none;fill:#ff0000;opacity:0.5;",
|
||||
"highlight-padding": 1.5,
|
||||
"highlight-offset": 0
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"board": "#bdccc7",
|
||||
"copper": "#b7b7ad",
|
||||
"silk": "#0b1013",
|
||||
"pads": "#cfcfd7",
|
||||
"clad": "#72786c",
|
||||
"outline": "#000000",
|
||||
"vcut": "#bf2600",
|
||||
"highlight-on-top": false,
|
||||
"highlight-style": "stroke:none;fill:#ff0000;opacity:0.5;",
|
||||
"highlight-padding": 1.5,
|
||||
"highlight-offset": 0
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"board": "#73823d",
|
||||
"copper": "#f79e64",
|
||||
"silk": "#d5dce4",
|
||||
"pads": "#d39751",
|
||||
"clad": "#525341",
|
||||
"outline": "#000000",
|
||||
"vcut": "#bf2600",
|
||||
"highlight-on-top": false,
|
||||
"highlight-style": "stroke:none;fill:#ff0000;opacity:0.5;",
|
||||
"highlight-padding": 1.5,
|
||||
"highlight-offset": 0
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"board": "#73823d",
|
||||
"copper": "#f79e64",
|
||||
"silk": "#d5dce4",
|
||||
"pads": "#cfb96e",
|
||||
"clad": "#525341",
|
||||
"outline": "#000000",
|
||||
"vcut": "#bf2600",
|
||||
"highlight-on-top": false,
|
||||
"highlight-style": "stroke:none;fill:#ff0000;opacity:0.5;",
|
||||
"highlight-padding": 1.5,
|
||||
"highlight-offset": 0
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"board": "#73823d",
|
||||
"copper": "#f79e64",
|
||||
"silk": "#d5dce4",
|
||||
"pads": "#8b898c",
|
||||
"clad": "#525341",
|
||||
"outline": "#000000",
|
||||
"vcut": "#bf2600",
|
||||
"highlight-on-top": false,
|
||||
"highlight-style": "stroke:none;fill:#ff0000;opacity:0.5;",
|
||||
"highlight-padding": 1.5,
|
||||
"highlight-offset": 0
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
# Author: Jan Mrázek
|
||||
# License: MIT
|
||||
from decimal import Decimal
|
||||
from typing import List
|
||||
|
||||
|
||||
def erase(string: str, what: List[str]) -> str:
|
||||
"""
|
||||
Given a string and a list of string, removes all occurrences of items from
|
||||
what in the string
|
||||
"""
|
||||
for x in what:
|
||||
string = string.replace(x, "")
|
||||
return string
|
||||
|
||||
|
||||
def read_resistance(value: str) -> Decimal:
|
||||
"""
|
||||
Given a string, try to parse resistance and return it as Ohms (Decimal)
|
||||
|
||||
This function can raise a ValueError if the value is invalid
|
||||
"""
|
||||
p_value = erase(value, ["Ω", "Ohms", "Ohm"]).strip()
|
||||
p_value = p_value.replace(" ", "") # Sometimes there are spaces after decimal place
|
||||
unit_prefixes = {
|
||||
"m": Decimal('1e-3'),
|
||||
"R": Decimal('1'),
|
||||
"K": Decimal('1e3'),
|
||||
"k": Decimal('1e3'),
|
||||
"M": Decimal('1e6'),
|
||||
"G": Decimal('1e9')
|
||||
}
|
||||
try:
|
||||
numerical_value = None
|
||||
for prefix, table in unit_prefixes.items():
|
||||
if prefix in p_value:
|
||||
# Example: 4k7 will have the 4 converted to Decimal(4) and 7 to Decimal(0.7)
|
||||
# Then each gets multiplied by the factor and added, so 4000 + 700
|
||||
# This method ensures that 4k7 and 4k700 for example yields the same result
|
||||
split = p_value.split(prefix)
|
||||
n_whole = Decimal(split[0]) if split[0] != "" else Decimal(0)
|
||||
n_dec = Decimal('.'+split[1]) if split[1] != "" else Decimal(0)
|
||||
numerical_value = n_whole * table + n_dec * table
|
||||
break
|
||||
if numerical_value is None:
|
||||
# If this fails, a decimal.InvalidOperation is raised which is handled by the Exception catch
|
||||
numerical_value = Decimal(p_value)
|
||||
return numerical_value
|
||||
except Exception:
|
||||
pass
|
||||
raise ValueError(f"Cannot parse '{value}' to resistance")
|
||||
|
|
@ -238,6 +238,7 @@ W_MISSREF = '(W099) '
|
|||
W_COPYOVER = '(W100) '
|
||||
W_PARITY = '(W101) '
|
||||
W_MISSFPINFO = '(W102) '
|
||||
W_PCBDRAW = '(W103) '
|
||||
# Somehow arbitrary, the colors are real, but can be different
|
||||
PCB_MAT_COLORS = {'fr1': "937042", 'fr2': "949d70", 'fr3': "adacb4", 'fr4': "332B16", 'fr5': "6cc290"}
|
||||
PCB_FINISH_COLORS = {'hal': "8b898c", 'hasl': "8b898c", 'imag': "8b898c", 'enig': "cfb96e", 'enepig': "cfb96e",
|
||||
|
|
|
|||
|
|
@ -3,31 +3,39 @@
|
|||
# Copyright (c) 2020-2022 Instituto Nacional de Tecnología Industrial
|
||||
# License: GPL-3.0
|
||||
# Project: KiBot (formerly KiPlot)
|
||||
"""
|
||||
Dependencies:
|
||||
- from: RSVG
|
||||
role: Create PNG and JPG images
|
||||
- from: ImageMagick
|
||||
role: Create JPG images
|
||||
- from: PcbDraw
|
||||
role: mandatory
|
||||
"""
|
||||
# TODO: PIL dependency? pcbnewTransition? numpy?
|
||||
# TODO: Package resources
|
||||
# """
|
||||
# Dependencies:
|
||||
# - from: RSVG
|
||||
# role: Create PNG and JPG images
|
||||
# - from: ImageMagick
|
||||
# role: Create JPG images
|
||||
# - from: PcbDraw
|
||||
# role: mandatory
|
||||
# """
|
||||
import os
|
||||
from tempfile import NamedTemporaryFile
|
||||
import shlex
|
||||
# Here we import the whole module to make monkeypatch work
|
||||
import subprocess
|
||||
from .misc import (PCBDRAW_ERR, W_AMBLIST, W_UNRETOOL, W_USESVG2, W_USEIMAGICK, PCB_MAT_COLORS,
|
||||
PCB_FINISH_COLORS, SOLDER_COLORS, SILK_COLORS)
|
||||
from .error import KiPlotConfigurationError
|
||||
from .misc import (PCBDRAW_ERR, W_AMBLIST, PCB_MAT_COLORS, PCB_FINISH_COLORS, SOLDER_COLORS, SILK_COLORS,
|
||||
W_PCBDRAW)
|
||||
from .gs import GS
|
||||
from .optionable import Optionable
|
||||
from .out_base import VariantOptions
|
||||
from .macros import macros, document, output_class # noqa: F401
|
||||
from . import log
|
||||
from .PcbDraw.plot import (PcbPlotter, PlotPaste, PlotPlaceholders, PlotSubstrate, PlotVCuts, mm2ki, PlotComponents)
|
||||
from .PcbDraw.convert import save
|
||||
|
||||
|
||||
logger = log.get_logger()
|
||||
|
||||
|
||||
def pcbdraw_warnings(tag, msg):
|
||||
logger.warning('{}({}) {}'.format(W_PCBDRAW, tag, msg))
|
||||
|
||||
|
||||
class PcbDrawStyle(Optionable):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
|
@ -102,31 +110,6 @@ class PcbDrawRemap(Optionable):
|
|||
pass
|
||||
|
||||
|
||||
def _get_tmp_name(ext):
|
||||
with NamedTemporaryFile(mode='w', suffix=ext, delete=False) as f:
|
||||
f.close()
|
||||
return f.name
|
||||
|
||||
|
||||
def _run_command(cmd, tmp_remap=False, tmp_style=False):
|
||||
logger.debug('Executing: '+shlex.join(cmd))
|
||||
try:
|
||||
cmd_output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
|
||||
except subprocess.CalledProcessError as e:
|
||||
logger.error('Failed to run %s, error %d', cmd[0], e.returncode)
|
||||
if e.output:
|
||||
logger.debug('Output from command: '+e.output.decode())
|
||||
exit(PCBDRAW_ERR)
|
||||
finally:
|
||||
if tmp_remap:
|
||||
os.remove(tmp_remap)
|
||||
if tmp_style:
|
||||
os.remove(tmp_style)
|
||||
out = cmd_output.decode()
|
||||
if out.strip():
|
||||
logger.debug('Output from command:\n'+out)
|
||||
|
||||
|
||||
class PcbDrawOptions(VariantOptions):
|
||||
def __init__(self):
|
||||
with document:
|
||||
|
|
@ -148,7 +131,7 @@ class PcbDrawOptions(VariantOptions):
|
|||
""" [list(string)=[]] List of components to highlight """
|
||||
self.show_components = Optionable
|
||||
""" *[list(string)|string=none] [none,all] List of components to draw, can be also a string for none or all.
|
||||
The default is none """
|
||||
The default is none. IMPORTANT! This option is relevant only when no filters or variants are applied """
|
||||
self.vcuts = False
|
||||
""" Render V-CUTS on the Cmts.User layer """
|
||||
self.warnings = 'visible'
|
||||
|
|
@ -170,32 +153,39 @@ class PcbDrawOptions(VariantOptions):
|
|||
super().config(parent)
|
||||
# Libs
|
||||
if isinstance(self.libs, type):
|
||||
self.libs = None
|
||||
self.libs = ['KiCAD-base']
|
||||
else:
|
||||
self.libs = ','.join(self.libs)
|
||||
# Highlight
|
||||
if isinstance(self.highlight, type):
|
||||
self.highlight = None
|
||||
else:
|
||||
self.highlight = ','.join(self.highlight)
|
||||
# Filter
|
||||
if isinstance(self.show_components, type):
|
||||
self.show_components = ''
|
||||
self.show_components = None
|
||||
elif isinstance(self.show_components, str):
|
||||
if self.variant or self.dnf_filter:
|
||||
logger.warning(W_AMBLIST + 'Ambiguous list of components to show `{}` vs variant/filter'.
|
||||
format(self.show_components))
|
||||
if self.show_components == 'none':
|
||||
self.show_components = ''
|
||||
else:
|
||||
self.show_components = None
|
||||
else:
|
||||
self.show_components = ','.join(self.show_components)
|
||||
else:
|
||||
self.show_components = []
|
||||
|
||||
# Remap
|
||||
# TODO: Better remap option, like - ref: xxx\nlib: xxxx\ncomponent: xxxx
|
||||
if isinstance(self.remap, type):
|
||||
self.remap = None
|
||||
self.remap = {}
|
||||
elif isinstance(self.remap, PcbDrawRemap):
|
||||
self.remap = self.remap._tree
|
||||
parsed_remap = {}
|
||||
for ref, v in self.remap._tree.items():
|
||||
if not isinstance(v, str):
|
||||
raise KiPlotConfigurationError("Wrong PcbDraw remap, must be `ref: lib:component` ({}: {})".format(ref, v))
|
||||
lib_comp = v.split(':')
|
||||
if len(lib_comp) == 2:
|
||||
parsed_remap[ref] = lib_comp
|
||||
else:
|
||||
raise KiPlotConfigurationError("Wrong PcbDraw remap, must be `ref: lib:component` ({}: {})".format(ref, v))
|
||||
self.remap = parsed_remap
|
||||
# Style
|
||||
if isinstance(self.style, type):
|
||||
# Apply the global defaults
|
||||
|
|
@ -207,20 +197,6 @@ class PcbDrawOptions(VariantOptions):
|
|||
self._expand_id = 'bottom' if self.bottom else 'top'
|
||||
self._expand_ext = self.format
|
||||
|
||||
def _create_remap(self):
|
||||
with NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
|
||||
f.write('{\n')
|
||||
first = True
|
||||
for k, v in self.remap.items():
|
||||
if first:
|
||||
first = False
|
||||
else:
|
||||
f.write(',\n')
|
||||
f.write(' "{}": "{}"'.format(k, v))
|
||||
f.write('\n}\n')
|
||||
f.close()
|
||||
return f.name
|
||||
|
||||
def _create_style(self):
|
||||
with NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
|
||||
f.write('{\n')
|
||||
|
|
@ -240,89 +216,99 @@ class PcbDrawOptions(VariantOptions):
|
|||
f.close()
|
||||
return f.name
|
||||
|
||||
def _append_output(self, cmd, output):
|
||||
svg = None
|
||||
if self.format == 'svg':
|
||||
cmd.append(output)
|
||||
else:
|
||||
# PNG and JPG outputs are unreliable
|
||||
self.rsvg_command = self.check_tool('RSVG')
|
||||
if self.rsvg_command is None:
|
||||
logger.warning(W_UNRETOOL + '`RSVG` not installed, using unreliable PNG/JPG conversion')
|
||||
logger.warning(W_USESVG2 + 'If you experiment problems install it')
|
||||
cmd.append(output)
|
||||
else:
|
||||
self.convert_command = self.check_tool('ImageMagick')
|
||||
if self.convert_command is None:
|
||||
logger.warning(W_UNRETOOL + '`ImageMagick` not installed, using unreliable PNG/JPG conversion')
|
||||
logger.warning(W_USEIMAGICK + 'If you experiment problems install it')
|
||||
cmd.append(output)
|
||||
else:
|
||||
svg = _get_tmp_name('.svg')
|
||||
cmd.append(svg)
|
||||
return svg
|
||||
|
||||
def get_targets(self, out_dir):
|
||||
return [self._parent.expand_filename(out_dir, self.output)]
|
||||
|
||||
def build_plot_components(self):
|
||||
remapping = self.remap
|
||||
|
||||
def remapping_fun(ref, lib, name):
|
||||
if ref in remapping:
|
||||
remapped_lib, remapped_name = remapping[ref]
|
||||
if name.endswith('.back'):
|
||||
return remapped_lib, remapped_name + '.back'
|
||||
else:
|
||||
return remapped_lib, remapped_name
|
||||
return lib, name
|
||||
|
||||
resistor_values = {}
|
||||
# TODO: Implement resistor_values_input and resistor_flip
|
||||
# for mapping in resistor_values_input:
|
||||
# key, value = tuple(mapping.split(":"))
|
||||
# resistor_values[key] = ResistorValue(value=value)
|
||||
# for ref in resistor_flip:
|
||||
# field = resistor_values.get(ref, ResistorValue())
|
||||
# field.flip_bands = True
|
||||
# resistor_values[ref] = field
|
||||
|
||||
plot_components = PlotComponents(remapping=remapping_fun,
|
||||
resistor_values=resistor_values,
|
||||
no_warn_back=self.warnings == 'visible')
|
||||
|
||||
if self._comps or self.show_components:
|
||||
comps = self.get_fitted_refs()
|
||||
if self.show_components:
|
||||
comps += self.show_components
|
||||
filter_set = set(comps)
|
||||
plot_components.filter = lambda ref: ref in filter_set
|
||||
|
||||
if self.highlight is not None:
|
||||
highlight_set = set(self.highlight)
|
||||
plot_components.highlight = lambda ref: ref in highlight_set
|
||||
return plot_components
|
||||
|
||||
def run(self, name):
|
||||
super().run(name)
|
||||
pcbdraw_command = self.ensure_tool('PcbDraw')
|
||||
# Base command with overwrite
|
||||
cmd = [pcbdraw_command]
|
||||
# Add user options
|
||||
tmp_style = None
|
||||
if self.style:
|
||||
if isinstance(self.style, str):
|
||||
cmd.extend(['-s', self.style])
|
||||
else:
|
||||
tmp_style = self._create_style()
|
||||
cmd.extend(['-s', tmp_style])
|
||||
if self.libs:
|
||||
cmd.extend(['-l', self.libs])
|
||||
if self.placeholder:
|
||||
cmd.append('--placeholder')
|
||||
if self.no_drillholes:
|
||||
cmd.append('--no-drillholes')
|
||||
if self.bottom:
|
||||
cmd.append('-b')
|
||||
if self.mirror:
|
||||
cmd.append('--mirror')
|
||||
if self.highlight:
|
||||
cmd.extend(['-a', self.highlight])
|
||||
if self.show_components is not None:
|
||||
to_add = ','.join(self.get_fitted_refs())
|
||||
if self.show_components and to_add:
|
||||
self.show_components += ','
|
||||
self.show_components += to_add
|
||||
cmd.extend(['-f', self.show_components])
|
||||
if self.vcuts:
|
||||
cmd.append('-v')
|
||||
if self.warnings == 'visible':
|
||||
cmd.append('--no-warn-back')
|
||||
elif self.warnings == 'none':
|
||||
cmd.append('--silent')
|
||||
if self.dpi:
|
||||
cmd.extend(['--dpi', str(self.dpi)])
|
||||
if self.remap:
|
||||
tmp_remap = self._create_remap()
|
||||
cmd.extend(['-m', tmp_remap])
|
||||
else:
|
||||
tmp_remap = None
|
||||
# The board & output
|
||||
cmd.append(GS.pcb_file)
|
||||
svg = self._append_output(cmd, name)
|
||||
# Execute and inform is successful
|
||||
_run_command(cmd, tmp_remap, tmp_style)
|
||||
if svg is not None:
|
||||
# Manually convert the SVG to PNG
|
||||
png = _get_tmp_name('.png')
|
||||
_run_command([self.rsvg_command, '-d', str(self.dpi), '-p', str(self.dpi), svg, '-o', png], svg)
|
||||
cmd = [self.convert_command, '-trim', png]
|
||||
if self.format == 'jpg':
|
||||
cmd += ['-quality', '85%']
|
||||
cmd.append(name)
|
||||
_run_command(cmd, png)
|
||||
|
||||
try:
|
||||
# TODO: Avoid loading the PCB again
|
||||
plotter = PcbPlotter(GS.pcb_file)
|
||||
# TODO: Review the paths, most probably add the system KiBot dir
|
||||
# Read libs from current dir
|
||||
# plotter.setup_arbitrary_data_path(".")
|
||||
# Libs indicated by PCBDRAW_LIB_PATH
|
||||
plotter.setup_env_data_path()
|
||||
# Libs from resources relative to the script
|
||||
plotter.setup_builtin_data_path()
|
||||
# Libs from the user HOME and the system
|
||||
plotter.setup_global_data_path()
|
||||
plotter.yield_warning = pcbdraw_warnings
|
||||
plotter.libs = self.libs
|
||||
plotter.render_back = self.bottom
|
||||
plotter.mirror = self.mirror
|
||||
# TODO: Allow margin configuration
|
||||
plotter.margin = mm2ki(1.5)
|
||||
# TODO: Pass it directly? If no: remove file?
|
||||
tmp_style = None
|
||||
if self.style:
|
||||
if isinstance(self.style, str):
|
||||
plotter.resolve_style(self.style)
|
||||
else:
|
||||
tmp_style = self._create_style()
|
||||
plotter.resolve_style(tmp_style)
|
||||
# TODO: Make aoutline_width configurable
|
||||
plotter.plot_plan = [PlotSubstrate(drill_holes=not self.no_drillholes, outline_width=mm2ki(0.15))]
|
||||
# TODO: Make paste optional
|
||||
plotter.plot_plan.append(PlotPaste())
|
||||
if self.vcuts:
|
||||
# TODO: Make layer configurable
|
||||
plotter.plot_plan.append(PlotVCuts(layer=41))
|
||||
# Two filtering mechanism: 1) Specified list and 2) KiBot filters and variants
|
||||
if self.show_components is not None or self._comps:
|
||||
plotter.plot_plan.append(self.build_plot_components())
|
||||
if self.placeholder:
|
||||
plotter.plot_plan.append(PlotPlaceholders())
|
||||
|
||||
image = plotter.plot()
|
||||
# Most errors are reported as RuntimeError
|
||||
# When the PCB can't be loaded we get IOError
|
||||
# When the SVG contains errors we get SyntaxError
|
||||
except (RuntimeError, SyntaxError, IOError) as e:
|
||||
logger.error('PcbDraw error: '+str(e))
|
||||
exit(PCBDRAW_ERR)
|
||||
|
||||
save(image, name, self.dpi)
|
||||
return
|
||||
|
||||
|
||||
@output_class
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ max-complexity = 21
|
|||
exclude = experiments/kicad/v6/
|
||||
experiments/JLC/
|
||||
kibot/mcpyrate/
|
||||
kibot/PcbDraw/
|
||||
kibot/PyPDF2/
|
||||
submodules/
|
||||
pp/
|
||||
|
|
|
|||
|
|
@ -159,7 +159,7 @@ deps = '{\
|
|||
],\
|
||||
"extra_deb": null,\
|
||||
"help_option": "--version",\
|
||||
"importance": 3,\
|
||||
"importance": 2,\
|
||||
"in_debian": true,\
|
||||
"is_kicad_plugin": false,\
|
||||
"is_python": false,\
|
||||
|
|
@ -183,13 +183,6 @@ deps = '{\
|
|||
"max_version": null,\
|
||||
"output": "pcb_print",\
|
||||
"version": null\
|
||||
},\
|
||||
{\
|
||||
"desc": "Create JPG images",\
|
||||
"mandatory": false,\
|
||||
"max_version": null,\
|
||||
"output": "pcbdraw",\
|
||||
"version": null\
|
||||
}\
|
||||
],\
|
||||
"tests": [],\
|
||||
|
|
@ -485,7 +478,7 @@ deps = '{\
|
|||
"version": [\
|
||||
2,\
|
||||
4,\
|
||||
2\
|
||||
3\
|
||||
]\
|
||||
}\
|
||||
],\
|
||||
|
|
@ -627,7 +620,7 @@ deps = '{\
|
|||
"extra_arch": null,\
|
||||
"extra_deb": null,\
|
||||
"help_option": "--version",\
|
||||
"importance": 10001,\
|
||||
"importance": 1,\
|
||||
"in_debian": false,\
|
||||
"is_kicad_plugin": false,\
|
||||
"is_python": false,\
|
||||
|
|
@ -652,21 +645,6 @@ deps = '{\
|
|||
0,\
|
||||
3\
|
||||
]\
|
||||
},\
|
||||
{\
|
||||
"desc": null,\
|
||||
"mandatory": true,\
|
||||
"max_version": [\
|
||||
1,\
|
||||
0\
|
||||
],\
|
||||
"output": "pcbdraw",\
|
||||
"version": [\
|
||||
0,\
|
||||
9,\
|
||||
0,\
|
||||
3\
|
||||
]\
|
||||
}\
|
||||
],\
|
||||
"tests": [],\
|
||||
|
|
@ -784,7 +762,7 @@ deps = '{\
|
|||
"extra_arch": null,\
|
||||
"extra_deb": null,\
|
||||
"help_option": "--version",\
|
||||
"importance": 4,\
|
||||
"importance": 3,\
|
||||
"in_debian": true,\
|
||||
"is_kicad_plugin": false,\
|
||||
"is_python": false,\
|
||||
|
|
@ -815,13 +793,6 @@ deps = '{\
|
|||
"max_version": null,\
|
||||
"output": "pcb_print",\
|
||||
"version": null\
|
||||
},\
|
||||
{\
|
||||
"desc": "Create PNG and JPG images",\
|
||||
"mandatory": false,\
|
||||
"max_version": null,\
|
||||
"output": "pcbdraw",\
|
||||
"version": null\
|
||||
}\
|
||||
],\
|
||||
"tests": [\
|
||||
|
|
|
|||
|
|
@ -468,7 +468,7 @@ def test_pcbdraw_fail(test_dir):
|
|||
prj = 'bom'
|
||||
ctx = context.TestContext(test_dir, prj, 'pcbdraw_fail')
|
||||
ctx.run(PCBDRAW_ERR)
|
||||
assert ctx.search_err('Failed to run')
|
||||
assert ctx.search_err('Cannot locate resource bogus')
|
||||
ctx.clean_up()
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -8,11 +8,8 @@ import coverage
|
|||
import logging
|
||||
from shutil import which
|
||||
from os import access
|
||||
from importlib import reload
|
||||
from . import context
|
||||
from kibot.mcpyrate import activate # noqa: F401
|
||||
from kibot.out_pcbdraw import PcbDrawOptions
|
||||
import kibot.log
|
||||
|
||||
OUT_DIR = 'PcbDraw'
|
||||
cov = coverage.Coverage()
|
||||
|
|
@ -86,63 +83,63 @@ DEPS = {'Dependencies': [{'name': 'pcbdraw', 'command': 'pcbdraw', 'role': 'mand
|
|||
{'name': 'ImageMagick', 'command': 'convert', 'role': 'XXXX', 'debian': 'imagemagick'}]}
|
||||
|
||||
|
||||
def test_pcbdraw_miss_rsvg(caplog, monkeypatch):
|
||||
""" Check missing rsvg-convert """
|
||||
with monkeypatch.context() as m:
|
||||
# Make which('rsvg-convert') fail
|
||||
m.setattr("shutil.which", no_rsvg_convert)
|
||||
# Make the call to determine the version fail
|
||||
m.setattr("subprocess.check_output", no_run)
|
||||
# Make os.access(...rsvg-convert', EXEC) fail
|
||||
m.setattr("os.access", no_rsvg_convert_access)
|
||||
# Make platform.system() return a bogus OS
|
||||
m.setattr("platform.system", platform_system_bogus)
|
||||
# Reload the module so we get the above patches
|
||||
mod = reload(kibot.dep_downloader)
|
||||
mod.register_deps('pcbdraw', DEPS)
|
||||
logging.error(mod.used_deps)
|
||||
old_lev = kibot.log.debug_level
|
||||
kibot.log.debug_level = 2
|
||||
o = PcbDrawOptions()
|
||||
o.style = ''
|
||||
o.remap = None
|
||||
o.format = 'jpg'
|
||||
o.config(None)
|
||||
o._parent = DummyPcbDraw()
|
||||
cov.load()
|
||||
cov.start()
|
||||
o.run('')
|
||||
cov.stop()
|
||||
cov.save()
|
||||
kibot.log.debug_level = old_lev
|
||||
assert 'using unreliable PNG/JPG' in caplog.text, caplog.text
|
||||
assert 'librsvg2-bin' in caplog.text, caplog.text
|
||||
|
||||
|
||||
def test_pcbdraw_miss_convert(caplog, monkeypatch):
|
||||
""" Check missing convert """
|
||||
with monkeypatch.context() as m:
|
||||
m.setattr("shutil.which", no_convert)
|
||||
m.setattr("subprocess.check_output", no_run)
|
||||
m.setattr("os.access", no_convert_access)
|
||||
# Make platform.system() return a bogus OS
|
||||
m.setattr("platform.system", platform_system_bogus)
|
||||
# Reload the module so we get the above patches
|
||||
mod = reload(kibot.dep_downloader)
|
||||
mod.register_deps('pcbdraw', DEPS)
|
||||
o = PcbDrawOptions()
|
||||
o.style = ''
|
||||
o.remap = None
|
||||
o.format = 'jpg'
|
||||
o.config(None)
|
||||
o._parent = DummyPcbDraw()
|
||||
cov.load()
|
||||
cov.start()
|
||||
o.run('')
|
||||
cov.stop()
|
||||
cov.save()
|
||||
assert 'using unreliable PNG/JPG' in caplog.text, caplog.text
|
||||
assert 'imagemagick' in caplog.text, caplog.text
|
||||
# def test_pcbdraw_miss_rsvg(caplog, monkeypatch):
|
||||
# """ Check missing rsvg-convert """
|
||||
# with monkeypatch.context() as m:
|
||||
# # Make which('rsvg-convert') fail
|
||||
# m.setattr("shutil.which", no_rsvg_convert)
|
||||
# # Make the call to determine the version fail
|
||||
# m.setattr("subprocess.check_output", no_run)
|
||||
# # Make os.access(...rsvg-convert', EXEC) fail
|
||||
# m.setattr("os.access", no_rsvg_convert_access)
|
||||
# # Make platform.system() return a bogus OS
|
||||
# m.setattr("platform.system", platform_system_bogus)
|
||||
# # Reload the module so we get the above patches
|
||||
# mod = reload(kibot.dep_downloader)
|
||||
# mod.register_deps('pcbdraw', DEPS)
|
||||
# logging.error(mod.used_deps)
|
||||
# old_lev = kibot.log.debug_level
|
||||
# kibot.log.debug_level = 2
|
||||
# o = PcbDrawOptions()
|
||||
# o.style = ''
|
||||
# o.remap = None
|
||||
# o.format = 'jpg'
|
||||
# o.config(None)
|
||||
# o._parent = DummyPcbDraw()
|
||||
# cov.load()
|
||||
# cov.start()
|
||||
# o.run('')
|
||||
# cov.stop()
|
||||
# cov.save()
|
||||
# kibot.log.debug_level = old_lev
|
||||
# assert 'using unreliable PNG/JPG' in caplog.text, caplog.text
|
||||
# assert 'librsvg2-bin' in caplog.text, caplog.text
|
||||
#
|
||||
#
|
||||
# def test_pcbdraw_miss_convert(caplog, monkeypatch):
|
||||
# """ Check missing convert """
|
||||
# with monkeypatch.context() as m:
|
||||
# m.setattr("shutil.which", no_convert)
|
||||
# m.setattr("subprocess.check_output", no_run)
|
||||
# m.setattr("os.access", no_convert_access)
|
||||
# # Make platform.system() return a bogus OS
|
||||
# m.setattr("platform.system", platform_system_bogus)
|
||||
# # Reload the module so we get the above patches
|
||||
# mod = reload(kibot.dep_downloader)
|
||||
# mod.register_deps('pcbdraw', DEPS)
|
||||
# o = PcbDrawOptions()
|
||||
# o.style = ''
|
||||
# o.remap = None
|
||||
# o.format = 'jpg'
|
||||
# o.config(None)
|
||||
# o._parent = DummyPcbDraw()
|
||||
# cov.load()
|
||||
# cov.start()
|
||||
# o.run('')
|
||||
# cov.stop()
|
||||
# cov.save()
|
||||
# assert 'using unreliable PNG/JPG' in caplog.text, caplog.text
|
||||
# assert 'imagemagick' in caplog.text, caplog.text
|
||||
|
||||
|
||||
def test_pcbdraw_variant_1(test_dir):
|
||||
|
|
|
|||
Loading…
Reference in New Issue