New output to compute differences between PCBs and SCHs.
Fixes INTI-CMNB/KiAuto#14
This commit is contained in:
parent
5134c252db
commit
464b72e8f1
|
|
@ -6,6 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
## [Unreleased]
|
||||
### Added
|
||||
- New output to compute differences between PCBs and SCHs.(INTI-CMNB/KiAuto#14)
|
||||
- Try to download missing tools and Python modules.
|
||||
The user also gets more information when something is missing.
|
||||
It can be disabled from the command line.
|
||||
|
|
|
|||
53
README.md
53
README.md
|
|
@ -83,6 +83,7 @@ For example, it's common that you might want for each board rev:
|
|||
* Pick and place files
|
||||
* PCB 3D model in STEP format
|
||||
* PCB 3D render in PNG format
|
||||
* Compare PCB/SCHs
|
||||
|
||||
You want to do this in a one-touch way, and make sure everything you need to
|
||||
do so is securely saved in version control, not on the back of an old
|
||||
|
|
@ -140,6 +141,9 @@ Notes:
|
|||
[**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) [](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) 
|
||||
- Mandatory for `pcb_print`
|
||||
|
||||
|
|
@ -159,6 +163,7 @@ Notes:
|
|||
|
||||
[**Git**](https://git-scm.com/) [](https://git-scm.com/) [](https://packages.debian.org/bullseye/git) 
|
||||
- Optional to:
|
||||
- Compare with files in the repo for `diff`
|
||||
- Find commit hash and/or date for `pcb_replace`
|
||||
- Find commit hash and/or date for `sch_replace`
|
||||
- Find commit hash and/or date for `set_text_variables`
|
||||
|
|
@ -1477,6 +1482,54 @@ Notes:
|
|||
Internally we use 10 for low priority, 90 for high priority and 50 for most outputs.
|
||||
- `run_by_default`: [boolean=true] When enabled this output will be created when no specific outputs are requested.
|
||||
|
||||
* Diff
|
||||
* Type: `diff`
|
||||
* Description: Generates a PDF with the differences between two PCBs or schematics
|
||||
* Valid keys:
|
||||
- **`comment`**: [string=''] A comment for documentation purposes.
|
||||
- **`dir`**: [string='./'] Output directory for the generated files.
|
||||
If it starts with `+` the rest is concatenated to the default dir.
|
||||
- **`layers`**: [list(dict)|list(string)|string] [all,selected,copper,technical,user]
|
||||
List of PCB layers to use. When empty all available layers are used.
|
||||
Note that if you want to support adding/removing layers you should specify a list here.
|
||||
* Valid keys:
|
||||
- `description`: [string=''] A description for the layer, for documentation purposes.
|
||||
- `layer`: [string=''] Name of the layer. As you see it in KiCad.
|
||||
- `suffix`: [string=''] Suffix used in file names related to this layer. Derived from the name if not specified.
|
||||
- **`name`**: [string=''] Used to identify this particular output definition.
|
||||
- **`options`**: [dict] Options for the `diff` output.
|
||||
* Valid keys:
|
||||
- **`output`**: [string='%f-%i%I%v.%x'] Filename for the output (%i=diff, %x=pdf). Affected by global options.
|
||||
- `cache_dir`: [string=''] Directory to cache the intermediate files. Leave it blank to disable the cache.
|
||||
- `diff_mode`: [string='red_green'] [red_green,stats] In the `red_green` mode added stuff is green and red when removed.
|
||||
The `stats` mode is used to meassure the amount of difference. In this mode all
|
||||
changes are red, but you can abort if the difference is bigger than certain threshold.
|
||||
- `fuzz`: [number=5] [0,100] Color tolerance (fuzzyness) for the `stats` mode.
|
||||
- `new`: [string=''] The file you want to compare. Leave it blank for the current PCB/SCH.
|
||||
- `new_type`: [string='file'] [git,file] How to interpret the `new` name. Use `git` for a git hash, branch, etc.
|
||||
Use `file` for a file name.
|
||||
- `old`: [string='HEAD'] Reference file. When using git use `HEAD` to refer to the last commit.
|
||||
Use `HEAD~` to refer the previous to the last commit.
|
||||
As `HEAD` is for the whole repo you can use `KIBOT_LAST-n` to make
|
||||
reference to the changes in the PCB/SCH. The `n` value is how many
|
||||
changes in the history you want to go back. A 0 is the same as `HEAD`,
|
||||
a 1 means the last time the PCB/SCH was changed, etc.
|
||||
- `old_type`: [string='git'] [git,file] How to interpret the `old` name. Use `git` for a git hash, branch, etc.
|
||||
Use `file` for a file name.
|
||||
- `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.
|
||||
- `category`: [string|list(string)=''] The category for this output. If not specified an internally defined category is used.
|
||||
Categories looks like file system paths, i.e. PCB/fabrication/gerber.
|
||||
- `disable_run_by_default`: [string|boolean] Use it to disable the `run_by_default` status of other output.
|
||||
Useful when this output extends another and you don't want to generate the original.
|
||||
Use the boolean true value to disable the output you are extending.
|
||||
- `extends`: [string=''] Copy the `options` section from the indicated output.
|
||||
- `output_id`: [string=''] Text to use for the %I expansion content. To differentiate variations of this output.
|
||||
- `priority`: [number=50] [0,100] Priority for this output. High priority outputs are created first.
|
||||
Internally we use 10 for low priority, 90 for high priority and 50 for most outputs.
|
||||
- `run_by_default`: [boolean=true] When enabled this output will be created when no specific outputs are requested.
|
||||
|
||||
* Datasheets downloader
|
||||
* Type: `download_datasheets`
|
||||
* Description: Downloads the datasheets for the project
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ Architecture: all
|
|||
Multi-Arch: foreign
|
||||
Depends: ${misc:Depends}, ${python3:Depends}, python3-yaml, kicad (>= 5.1.6), python3-wxgtk4.0
|
||||
Recommends: kibom.inti-cmnb (>= 1.8.0), kicost (>= 1.1.8), interactivehtmlbom.inti-cmnb (>= 2.4.1), pcbdraw (>= 0.9.0), imagemagick, librsvg2-bin, python3-xlsxwriter, rar, ghostscript, python3-lxml
|
||||
Suggests: pandoc, texlive-latex-base, texlive-latex-recommended, git, poppler-utils
|
||||
Suggests: pandoc, texlive-latex-base, texlive-latex-recommended, git, poppler-utils, kidiff
|
||||
Description: KiCad Bot
|
||||
KiBot is a program which helps you to automate the generation of KiCad
|
||||
output documents easily, repeatable, and most of all, scriptably.
|
||||
|
|
|
|||
|
|
@ -83,6 +83,7 @@ For example, it's common that you might want for each board rev:
|
|||
* Pick and place files
|
||||
* PCB 3D model in STEP format
|
||||
* PCB 3D render in PNG format
|
||||
* Compare PCB/SCHs
|
||||
|
||||
You want to do this in a one-touch way, and make sure everything you need to
|
||||
do so is securely saved in version control, not on the back of an old
|
||||
|
|
|
|||
|
|
@ -407,6 +407,43 @@ outputs:
|
|||
# [string='%f-%i%I%v.%x'] Name for the generated archive (%i=name of the output %x=according to format). Affected by global options
|
||||
output: '%f-%i%I%v.%x'
|
||||
# `remove_files` is an alias for `move_files`
|
||||
# Diff:
|
||||
- name: 'diff_example'
|
||||
comment: 'Generates a PDF with the differences between two PCBs or schematics'
|
||||
type: 'diff'
|
||||
dir: 'Example/diff_dir'
|
||||
options:
|
||||
# [string=''] Directory to cache the intermediate files. Leave it blank to disable the cache
|
||||
cache_dir: ''
|
||||
# [string='red_green'] [red_green,stats] In the `red_green` mode added stuff is green and red when removed.
|
||||
# The `stats` mode is used to meassure the amount of difference. In this mode all
|
||||
# changes are red, but you can abort if the difference is bigger than certain threshold
|
||||
diff_mode: 'red_green'
|
||||
# [number=5] [0,100] Color tolerance (fuzzyness) for the `stats` mode
|
||||
fuzz: 5
|
||||
# [string=''] The file you want to compare. Leave it blank for the current PCB/SCH
|
||||
new: ''
|
||||
# [string='file'] [git,file] How to interpret the `new` name. Use `git` for a git hash, branch, etc.
|
||||
# Use `file` for a file name
|
||||
new_type: 'file'
|
||||
# [string='HEAD'] Reference file. When using git use `HEAD` to refer to the last commit.
|
||||
# Use `HEAD~` to refer the previous to the last commit.
|
||||
# As `HEAD` is for the whole repo you can use `KIBOT_LAST-n` to make
|
||||
# reference to the changes in the PCB/SCH. The `n` value is how many
|
||||
# changes in the history you want to go back. A 0 is the same as `HEAD`,
|
||||
# a 1 means the last time the PCB/SCH was changed, etc
|
||||
old: 'HEAD'
|
||||
# [string='git'] [git,file] How to interpret the `old` name. Use `git` for a git hash, branch, etc.
|
||||
# Use `file` for a file name
|
||||
old_type: 'git'
|
||||
# [string='%f-%i%I%v.%x'] Filename for the output (%i=diff, %x=pdf). Affected by global options
|
||||
output: '%f-%i%I%v.%x'
|
||||
# [boolean=true] Compare the PCB, otherwise compare the schematic
|
||||
pcb: true
|
||||
# [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
|
||||
threshold: 0
|
||||
layers: all
|
||||
# Datasheets downloader:
|
||||
- name: 'download_datasheets_example'
|
||||
comment: 'Downloads the datasheets for the project'
|
||||
|
|
|
|||
|
|
@ -0,0 +1,279 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2022 Salvador E. Tropea
|
||||
# Copyright (c) 2022 Instituto Nacional de Tecnología Industrial
|
||||
# License: GPL-3.0
|
||||
# Project: KiBot (formerly KiPlot)
|
||||
"""
|
||||
Dependencies:
|
||||
- name: KiCad PCB/SCH Diff
|
||||
role: mandatory
|
||||
github: INTI-CMNB/KiDiff
|
||||
command: kicad-diff.py
|
||||
pypi: kidiff
|
||||
downloader: pytool
|
||||
id: KiDiff
|
||||
- from: Git
|
||||
role: Compare with files in the repo
|
||||
"""
|
||||
from hashlib import sha1
|
||||
import os
|
||||
from shutil import rmtree
|
||||
from subprocess import run, CalledProcessError, STDOUT, PIPE
|
||||
from tempfile import mkdtemp, NamedTemporaryFile
|
||||
from .error import KiPlotConfigurationError
|
||||
from .gs import GS
|
||||
from .kiplot import load_any_sch
|
||||
from .layer import Layer
|
||||
from .misc import FAILED_EXECUTE
|
||||
from .optionable import BaseOptions
|
||||
from .macros import macros, document, output_class # noqa: F401
|
||||
from . import log
|
||||
|
||||
logger = log.get_logger()
|
||||
|
||||
|
||||
def debug_output(res):
|
||||
if res.stdout:
|
||||
logger.debug('- Output from command: '+res.stdout.decode())
|
||||
|
||||
|
||||
def run_command(command, change_to=None):
|
||||
logger.debug('Executing: '+str(command))
|
||||
try:
|
||||
res = run(command, check=True, stdout=PIPE, stderr=STDOUT, cwd=change_to)
|
||||
except CalledProcessError as e:
|
||||
logger.error('Running {} returned {}'.format(e.cmd, e.returncode))
|
||||
debug_output(e)
|
||||
exit(FAILED_EXECUTE)
|
||||
debug_output(res)
|
||||
return res.stdout.decode().rstrip()
|
||||
|
||||
|
||||
class DiffOptions(BaseOptions):
|
||||
def __init__(self):
|
||||
with document:
|
||||
self.output = GS.def_global_output
|
||||
""" *Filename for the output (%i=diff, %x=pdf) """
|
||||
self.pcb = True
|
||||
""" Compare the PCB, otherwise compare the schematic """
|
||||
self.old = 'HEAD'
|
||||
""" Reference file. When using git use `HEAD` to refer to the last commit.
|
||||
Use `HEAD~` to refer the previous to the last commit.
|
||||
As `HEAD` is for the whole repo you can use `KIBOT_LAST-n` to make
|
||||
reference to the changes in the PCB/SCH. The `n` value is how many
|
||||
changes in the history you want to go back. A 0 is the same as `HEAD`,
|
||||
a 1 means the last time the PCB/SCH was changed, etc """
|
||||
self.old_type = 'git'
|
||||
""" [git,file] How to interpret the `old` name. Use `git` for a git hash, branch, etc.
|
||||
Use `file` for a file name """
|
||||
self.new = ''
|
||||
""" The file you want to compare. Leave it blank for the current PCB/SCH """
|
||||
self.new_type = 'file'
|
||||
""" [git,file] How to interpret the `new` name. Use `git` for a git hash, branch, etc.
|
||||
Use `file` for a file name """
|
||||
self.cache_dir = ''
|
||||
""" Directory to cache the intermediate files. Leave it blank to disable the cache """
|
||||
self.diff_mode = 'red_green'
|
||||
""" [red_green,stats] In the `red_green` mode added stuff is green and red when removed.
|
||||
The `stats` mode is used to meassure the amount of difference. In this mode all
|
||||
changes are red, but you can abort if the difference is bigger than certain threshold """
|
||||
self.fuzz = 5
|
||||
""" [0,100] Color tolerance (fuzzyness) for the `stats` mode """
|
||||
self.threshold = 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 """
|
||||
super().__init__()
|
||||
self._expand_id = 'diff'
|
||||
self._expand_ext = 'pdf'
|
||||
|
||||
def get_targets(self, out_dir):
|
||||
return [self._parent.expand_filename(out_dir, self.output)]
|
||||
|
||||
def get_digest(self, file_path, restart=True):
|
||||
logger.debug('Hashing '+file_path)
|
||||
if restart:
|
||||
self.h = sha1()
|
||||
with open(file_path, 'rb') as file:
|
||||
while True:
|
||||
chunk = file.read(self.h.block_size)
|
||||
if not chunk:
|
||||
break
|
||||
self.h.update(chunk)
|
||||
return self.h.hexdigest()
|
||||
|
||||
def add_to_cache(self, name, hash):
|
||||
cmd = [self.command, '--only_cache', '--old_file_hash', hash, '--cache_dir', self.cache_dir]
|
||||
if self.incl_file:
|
||||
cmd.extend(['--layers', self.incl_file])
|
||||
if GS.debug_enabled:
|
||||
cmd.insert(1, '-'+'v'*GS.debug_level)
|
||||
cmd.extend([name, name])
|
||||
run_command(cmd)
|
||||
|
||||
def cache_pcb(self, name):
|
||||
if not name:
|
||||
GS.check_pcb()
|
||||
name = GS.pcb_file
|
||||
hash = self.get_digest(name)
|
||||
self.add_to_cache(name, hash)
|
||||
return hash
|
||||
|
||||
def cache_sch(self, name):
|
||||
if not name:
|
||||
GS.check_sch()
|
||||
name = GS.sch_file
|
||||
# Schematics can have sub-sheets
|
||||
sch = load_any_sch(name, os.path.splitext(os.path.basename(name))[0])
|
||||
files = sch.get_files()
|
||||
hash = self.get_digest(files[0])
|
||||
if len(files) > 1:
|
||||
for f in files[1:]:
|
||||
hash = self.get_digest(f, restart=False)
|
||||
hash = 'sch'+hash
|
||||
self.add_to_cache(name, hash)
|
||||
return hash
|
||||
|
||||
def cache_file(self, name=None):
|
||||
return self.cache_pcb(name) if self.pcb else self.cache_sch(name)
|
||||
|
||||
def run_git(self, cmd):
|
||||
return run_command([self.git_command]+cmd, change_to=self.repo_dir)
|
||||
|
||||
def undo_git(self):
|
||||
if self.checkedout:
|
||||
logger.debug('Restoring point '+self.branch)
|
||||
self.run_git(['checkout', self.branch])
|
||||
if self.stashed:
|
||||
logger.debug('Restoring changes')
|
||||
self.run_git(['stash', 'pop'])
|
||||
|
||||
def solve_git_name(self, name):
|
||||
ori = name
|
||||
if not name.startswith('KIBOT_LAST'):
|
||||
return name
|
||||
logger.debug('Finding '+name)
|
||||
# The magic KIBOT_LAST
|
||||
malformed = 'Malformed `KIBOT_LAST` value, must be `KIBOT_LAST-n`, not: '+ori
|
||||
name = name[10:]
|
||||
# How many changes?
|
||||
num = 0
|
||||
if name[0] != '-':
|
||||
raise KiPlotConfigurationError(malformed)
|
||||
try:
|
||||
num = int(name[1:])
|
||||
except ValueError:
|
||||
raise KiPlotConfigurationError(malformed)
|
||||
num = str(num)
|
||||
# Return its hash
|
||||
res = self.run_git(['log', '--pretty=format:%H', '--skip='+num, '-n', '1', '--', self.file])
|
||||
logger.debug('- '+res)
|
||||
return res
|
||||
|
||||
def cache_git(self, name):
|
||||
self.stashed = False
|
||||
self.checkedout = False
|
||||
# Which file
|
||||
if self.pcb:
|
||||
GS.check_pcb()
|
||||
self.file = GS.pcb_file
|
||||
else:
|
||||
GS.check_sch()
|
||||
self.file = GS.sch_file
|
||||
# Place where we know we have a repo
|
||||
self.repo_dir = os.path.dirname(os.path.abspath(self.file))
|
||||
try:
|
||||
# Save current changes
|
||||
logger.debug('Saving current changes')
|
||||
self.run_git(['stash', 'push'])
|
||||
self.stashed = True
|
||||
# Find the current branch
|
||||
self.branch = self.run_git(['rev-parse', '--abbrev-ref', 'HEAD'])
|
||||
if self.branch == 'HEAD':
|
||||
# Detached
|
||||
self.branch = self.run_git(['rev-parse', 'HEAD'])
|
||||
logger.debug('Current branch is '+self.branch)
|
||||
# Checkout the target
|
||||
name = self.solve_git_name(name)
|
||||
logger.debug('Changing to '+name)
|
||||
self.run_git(['checkout', name])
|
||||
self.checkedout = True
|
||||
# Populate the cache
|
||||
hash = self.cache_file()
|
||||
finally:
|
||||
self.undo_git()
|
||||
return hash
|
||||
|
||||
def cache_obj(self, name, type):
|
||||
return self.cache_git(name) if type == 'git' else self.cache_file(name)
|
||||
|
||||
def create_layers_incl(self, layers):
|
||||
incl_file = None
|
||||
if self.pcb and not isinstance(layers, type):
|
||||
layers = Layer.solve(layers)
|
||||
logger.debug('Including layers:')
|
||||
with NamedTemporaryFile(mode='w', suffix='.lst', delete=False) as f:
|
||||
incl_file = f.name
|
||||
for la in layers:
|
||||
logger.debug('- {} ({})'.format(la.layer, la.id))
|
||||
f.write(str(la.id)+'\n')
|
||||
return incl_file
|
||||
|
||||
def run(self, name):
|
||||
self.command = self.ensure_tool('KiDiff')
|
||||
if self.old_type == 'git' or self.new_type == 'git':
|
||||
self.git_command = self.ensure_tool('Git')
|
||||
# Solve the cache dir
|
||||
remove_cache = False
|
||||
if not self.cache_dir:
|
||||
self.cache_dir = mkdtemp()
|
||||
remove_cache = True
|
||||
# A valid name, not really used
|
||||
file = GS.pcb_file or GS.sch_file
|
||||
dir_name = os.path.dirname(name)
|
||||
file_name = os.path.basename(name)
|
||||
self.incl_file = None
|
||||
try:
|
||||
# List of layers
|
||||
self.incl_file = self.create_layers_incl(self.layers)
|
||||
# Populate the cache
|
||||
old_hash = self.cache_obj(self.old, self.old_type)
|
||||
new_hash = self.cache_obj(self.new, self.new_type)
|
||||
# Compute the diff using the cache
|
||||
cmd = [self.command, '--no_reader', '--new_file_hash', new_hash, '--old_file_hash', old_hash,
|
||||
'--cache_dir', self.cache_dir, '--output_dir', dir_name, '--output_name', file_name,
|
||||
'--diff_mode', self.diff_mode, '--fuzz', str(self.fuzz)]
|
||||
if self.incl_file:
|
||||
cmd.extend(['--layers', self.incl_file])
|
||||
if self.threshold:
|
||||
cmd.extend(['--threshold', str(self.threshold)])
|
||||
cmd.extend([file, file])
|
||||
if GS.debug_enabled:
|
||||
cmd.insert(1, '-'+'v'*GS.debug_level)
|
||||
run_command(cmd)
|
||||
finally:
|
||||
# Clean-up
|
||||
if remove_cache:
|
||||
rmtree(self.cache_dir)
|
||||
if self.incl_file:
|
||||
os.remove(self.incl_file)
|
||||
|
||||
|
||||
@output_class
|
||||
class Diff(BaseOutput): # noqa: F821
|
||||
""" Diff
|
||||
Generates a PDF with the differences between two PCBs or schematics """
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._category = ['PCB/docs', 'Schematic/docs']
|
||||
self._both_related = True
|
||||
with document:
|
||||
self.options = DiffOptions
|
||||
""" *[dict] Options for the `diff` output """
|
||||
self.layers = Layer
|
||||
""" *[list(dict)|list(string)|string] [all,selected,copper,technical,user]
|
||||
List of PCB layers to use. When empty all available layers are used.
|
||||
Note that if you want to support adding/removing layers you should specify a list here """
|
||||
|
||||
def run(self, name):
|
||||
self.options.layers = self.layers
|
||||
super().run(name)
|
||||
|
|
@ -91,17 +91,23 @@ deps = '{\
|
|||
"downloader_str": "git",\
|
||||
"extra_deb": null,\
|
||||
"help_option": "--version",\
|
||||
"importance": 3,\
|
||||
"importance": 4,\
|
||||
"in_debian": true,\
|
||||
"is_kicad_plugin": false,\
|
||||
"is_python": false,\
|
||||
"name": "Git",\
|
||||
"no_cmd_line_version": false,\
|
||||
"no_cmd_line_version_old": false,\
|
||||
"output": "pcb_replace",\
|
||||
"output": "diff",\
|
||||
"plugin_dirs": null,\
|
||||
"pypi_name": "Git",\
|
||||
"roles": [\
|
||||
{\
|
||||
"desc": "Compare with files in the repo",\
|
||||
"mandatory": false,\
|
||||
"output": "diff",\
|
||||
"version": null\
|
||||
},\
|
||||
{\
|
||||
"desc": "Find commit hash and/or date",\
|
||||
"mandatory": false,\
|
||||
|
|
@ -380,6 +386,35 @@ deps = '{\
|
|||
"url": "https://github.com/INTI-CMNB/KiAuto",\
|
||||
"url_down": "https://github.com/INTI-CMNB/KiAuto/releases"\
|
||||
},\
|
||||
"KiCad PCB/SCH Diff": {\
|
||||
"command": "kicad-diff.py",\
|
||||
"comments": [],\
|
||||
"deb_package": "kicad pcb/sch diff",\
|
||||
"downloader": {},\
|
||||
"downloader_str": "pytool",\
|
||||
"extra_deb": null,\
|
||||
"help_option": "--version",\
|
||||
"importance": 10000,\
|
||||
"in_debian": false,\
|
||||
"is_kicad_plugin": false,\
|
||||
"is_python": false,\
|
||||
"name": "KiCad PCB/SCH Diff",\
|
||||
"no_cmd_line_version": false,\
|
||||
"no_cmd_line_version_old": false,\
|
||||
"output": "diff",\
|
||||
"plugin_dirs": null,\
|
||||
"pypi_name": "kidiff",\
|
||||
"roles": [\
|
||||
{\
|
||||
"desc": null,\
|
||||
"mandatory": true,\
|
||||
"output": "diff",\
|
||||
"version": null\
|
||||
}\
|
||||
],\
|
||||
"url": "https://github.com/INTI-CMNB/KiDiff",\
|
||||
"url_down": "https://github.com/INTI-CMNB/KiDiff/releases"\
|
||||
},\
|
||||
"KiCost": {\
|
||||
"command": "kicost",\
|
||||
"comments": [],\
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1 @@
|
|||
light_control.sch
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1 @@
|
|||
light_control.kicad_sch
|
||||
Binary file not shown.
|
|
@ -0,0 +1 @@
|
|||
../5_1_6/light_control-diff.pdf
|
||||
Binary file not shown.
|
|
@ -1266,3 +1266,72 @@ def test_dont_stop_1(test_dir):
|
|||
ctx.expect_out_file(pos_bot)
|
||||
ctx.search_err('ERROR:Failed to create BoM')
|
||||
ctx.clean_up(keep_project=True)
|
||||
|
||||
|
||||
def test_diff_file_1(test_dir):
|
||||
""" Difference between the current PCB and a reference file """
|
||||
prj = 'light_control_diff'
|
||||
yaml = 'diff_file_'+('k5' if context.ki5() else 'k6')
|
||||
ctx = context.TestContext(test_dir, prj, yaml)
|
||||
ctx.run()
|
||||
ctx.compare_pdf(prj+'-diff.pdf', reference='light_control-diff.pdf')
|
||||
ctx.clean_up(keep_project=True)
|
||||
|
||||
|
||||
def test_diff_git_1(test_dir):
|
||||
""" Difference between the current PCB and the git HEAD """
|
||||
prj = 'light_control'
|
||||
yaml = 'diff_git_1'
|
||||
ctx = context.TestContext(test_dir, prj, yaml)
|
||||
# Create a git repo
|
||||
ctx.run_command(['git', 'init', '.'], chdir_out=True)
|
||||
# Copy the "old" file
|
||||
pcb = prj+'.kicad_pcb'
|
||||
file = ctx.get_out_path(pcb)
|
||||
shutil.copy2(ctx.board_file, file)
|
||||
shutil.copy2(ctx.board_file.replace('.kicad_pcb', context.KICAD_SCH_EXT),
|
||||
file.replace('.kicad_pcb', context.KICAD_SCH_EXT))
|
||||
# Add it to the repo
|
||||
ctx.run_command(['git', 'add', pcb], chdir_out=True)
|
||||
ctx.run_command(['git', 'commit', '-m', 'Reference'], chdir_out=True)
|
||||
# Copy the "new" file
|
||||
shutil.copy2(ctx.board_file.replace(prj, prj+'_diff'), file)
|
||||
# Run the test
|
||||
ctx.run(extra=['-b', file], no_board_file=True)
|
||||
ctx.compare_pdf(prj+'-diff.pdf')
|
||||
ctx.clean_up(keep_project=True)
|
||||
|
||||
|
||||
def test_diff_git_2(test_dir):
|
||||
""" Difference between the two repo points """
|
||||
prj = 'light_control'
|
||||
yaml = 'diff_git_2'
|
||||
ctx = context.TestContext(test_dir, prj, yaml)
|
||||
# Create a git repo
|
||||
ctx.run_command(['git', 'init', '.'], chdir_out=True)
|
||||
# Copy the "old" file
|
||||
pcb = prj+'.kicad_pcb'
|
||||
file = ctx.get_out_path(pcb)
|
||||
shutil.copy2(ctx.board_file, file)
|
||||
shutil.copy2(ctx.board_file.replace('.kicad_pcb', context.KICAD_SCH_EXT),
|
||||
file.replace('.kicad_pcb', context.KICAD_SCH_EXT))
|
||||
# Add it to the repo
|
||||
ctx.run_command(['git', 'add', pcb], chdir_out=True)
|
||||
ctx.run_command(['git', 'commit', '-m', 'Reference'], chdir_out=True)
|
||||
# Add an extra commit
|
||||
dummy = ctx.get_out_path('dummy')
|
||||
with open(dummy, 'wt') as f:
|
||||
f.write('dummy\n')
|
||||
ctx.run_command(['git', 'add', 'dummy'], chdir_out=True)
|
||||
ctx.run_command(['git', 'commit', '-m', 'Dummy noise'], chdir_out=True)
|
||||
# Copy the "new" file
|
||||
shutil.copy2(ctx.board_file.replace(prj, prj+'_diff'), file)
|
||||
# Add it to the repo
|
||||
ctx.run_command(['git', 'add', pcb], chdir_out=True)
|
||||
ctx.run_command(['git', 'commit', '-m', 'New version'], chdir_out=True)
|
||||
# Now just wipe the current file
|
||||
shutil.copy2(ctx.board_file.replace(prj, '3Rs'), file)
|
||||
# Run the test
|
||||
ctx.run(extra=['-b', file], no_board_file=True, extra_debug=True)
|
||||
ctx.compare_pdf(prj+'-diff.pdf')
|
||||
ctx.clean_up(keep_project=True)
|
||||
|
|
|
|||
|
|
@ -329,6 +329,24 @@ class TestContext(object):
|
|||
os.close(f_err)
|
||||
self.err = self.err.decode()
|
||||
|
||||
def run_command(self, cmd, chdir_out=False):
|
||||
if chdir_out:
|
||||
cwd = os.getcwd()
|
||||
os.chdir(self.output_dir)
|
||||
logging.debug('Executing: '+str(cmd))
|
||||
try:
|
||||
res = subprocess.run(cmd, check=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
except subprocess.CalledProcessError as e:
|
||||
logging.error('Running {} returned {}'.format(e.cmd, e.returncode))
|
||||
if e.stdout:
|
||||
logging.debug('- Output from command: '+e.stdout.decode())
|
||||
raise
|
||||
if res.stdout:
|
||||
logging.debug('- Output from command: '+res.stdout.decode())
|
||||
if chdir_out:
|
||||
os.chdir(cwd)
|
||||
return res.stdout.decode().rstrip()
|
||||
|
||||
def run(self, ret_val=None, extra=None, use_a_tty=False, filename=None, no_out_dir=False, no_board_file=False,
|
||||
no_yaml_file=False, chdir_out=False, no_verbose=False, extra_debug=False, do_locale=False, kicost=False):
|
||||
logging.debug('Running '+self.test_name)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
kibot:
|
||||
version: 1
|
||||
|
||||
outputs:
|
||||
- name: 'diff_pcb'
|
||||
comment: "PCB difference with reference file (KiCad 5)"
|
||||
type: diff
|
||||
layers: ['F.Cu', 'In1.Cu']
|
||||
options:
|
||||
old: tests/board_samples/kicad_5/light_control.kicad_pcb
|
||||
old_type: file
|
||||
cache_dir: .cache
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
kibot:
|
||||
version: 1
|
||||
|
||||
outputs:
|
||||
- name: 'diff_pcb'
|
||||
comment: "PCB difference with reference file (KiCad 6)"
|
||||
type: diff
|
||||
layers: ['F.Cu', 'In1.Cu']
|
||||
options:
|
||||
old: tests/board_samples/kicad_6/light_control.kicad_pcb
|
||||
old_type: file
|
||||
cache_dir: .cache
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
kibot:
|
||||
version: 1
|
||||
|
||||
outputs:
|
||||
- name: 'diff_pcb'
|
||||
comment: "PCB difference with git HEAD"
|
||||
type: diff
|
||||
layers: ['F.Cu', 'In1.Cu']
|
||||
options:
|
||||
cache_dir: .cache
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
kibot:
|
||||
version: 1
|
||||
|
||||
outputs:
|
||||
- name: 'diff_pcb'
|
||||
comment: "PCB difference with git HEAD"
|
||||
type: diff
|
||||
layers: ['F.Cu', 'In1.Cu']
|
||||
options:
|
||||
old: KIBOT_LAST-1
|
||||
old_type: git
|
||||
new: HEAD
|
||||
new_type: git
|
||||
cache_dir: .cache
|
||||
Loading…
Reference in New Issue