[KiKit Present] Implemented various modes

This commit is contained in:
Salvador E. Tropea 2022-12-04 19:50:29 -03:00
parent cc53d655c0
commit 509f74fd31
26 changed files with 3029 additions and 61 deletions

View File

@ -13,3 +13,4 @@ include kibot/resources/images/*.ico
include kibot/resources/pcbdraw/styles/*.json
include kibot/resources/pcbdraw/templates/*.handlebars
recursive-include kibot/resources/pcbdraw/footprints/ *.svg LICENCE *.md *.py *.pl *.png
recursive-include kibot/resources/pcbdraw/present/ *.css *.html *.json *.js

View File

@ -172,6 +172,9 @@ Notes:
[**KiCad PCB/SCH Diff**](https://github.com/INTI-CMNB/KiDiff) v2.4.3 [![Tool](https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/llave-inglesa-22x22.png)](https://github.com/INTI-CMNB/KiDiff) ![Auto-download](https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/auto_download-22x22.png)
- Mandatory for `diff`
[**markdown2**](https://pypi.org/project/markdown2/) [![Python module](https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/Python-logo-notext-22x22.png)](https://pypi.org/project/markdown2/) [![Debian](https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/debian-openlogo-22x22.png)](https://packages.debian.org/bullseye/python3-markdown2)
- Mandatory for `kikit_present`
[**mistune**](https://pypi.org/project/mistune/) [![Python module](https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/Python-logo-notext-22x22.png)](https://pypi.org/project/mistune/) [![Debian](https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/debian-openlogo-22x22.png)](https://packages.debian.org/bullseye/python3-mistune)
- Mandatory for `populate`
@ -2408,6 +2411,80 @@ 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.
* KiKit's Present - Project Presentation
* Type: `kikit_present`
* Description: Creates an HTML file showing your project.
It can contain one or more PCBs, showing their top and bottom sides.
Also includes a download link and the gerbers.
* 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.
- **`name`**: [string=''] Used to identify this particular output definition.
- **`options`**: [dict] Options for the `kikit_present` output.
* Valid keys:
- **`description`**: [string=''] Name for a markdown file containing the main part of the page to be generated.
This is mandatory and is the description of your project.
- `boards`: [dict|list(dict)] One or more boards that compose your project.
When empty we will use only the main PCB for the current project.
* Valid keys:
- **`back_image`**: [string=''] local*: the name of an output that renders the back.
If empty we use the first renderer for the back.
*file*: the name of the rendered image.
*external*: ignored, we `extrenal_config`.
- **`front_image`**: [string=''] local*: the name of an output that renders the front.
If empty we use the first renderer for the front.
*file*: the name of the rendered image.
*external*: ignored, we `extrenal_config`.
- **`gerbers`**: [string=''] local*: the name of a compress output.
If empty we use the first compress output.
*file*: the name of a compressed archive.
*external*: ignored, we `extrenal_config`.
- **`name`**: [string=''] Name for this board. If empty we use the name of the PCB.
Applies to all modes.
- `comment`: [string=''] A comment or description for this board.
Applies to all modes.
- `external_config`: [string=''] Name of an external KiBot configuration.
Only used in the *external* mode.
- `mode`: [string='auto'] [local,file,external] How images and gerbers are obtained.
*local*: Only applies to the currently selected PCB.
You must provide the names of the outputs used to render
the images and compress the gerbers.
When empty KiBot will use the first render/compress output
it finds.
To apply variants use `pcb_from_output` and a `pcb_variant`
output.
*file*: You must specify the file names used for the images and
the gerbers.
*external*: You must specify an external KiBot configuration.
It will be applied to the selected PCB to create the images and
the gerbers. The front image must be generated in a dir called
*front*, the back image in a dir called *back* and the gerbers
in a dir called *gerbers*.
- `pcb_file`: [string=''] Name of the KiCad PCB file. When empty we use the current PCB.
Is ignored for the *local* mode.
- `pcb_from_output`: [string=''] Use the PCB generated by another output.
Is ignored for the *file* mode.
- `name`: [string=''] Name of the project. Will be passed to the template.
If empty we use the name of the KiCad project.
The default template uses it for things like the page title.
- `repository`: [string=''] URL of the repository. Will be passed to the template.
- `resources`: [string|list(string)=''] A list of file name patterns for additional resources to be included.
I.e. images referenced in description.
They will be copied relative to the output dir.
- `template`: [string='default'] Path to a template directory or a name of built-in one.
See KiKit's doc/present.md for template specification.
- `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.
* Navigate Results
* Type: `navigate_results`
* Description: Generates a web page to navigate the generated outputs

2
debian/control vendored
View File

@ -11,7 +11,7 @@ Package: kibot
Architecture: all
Multi-Arch: foreign
Depends: ${misc:Depends}, ${python3:Depends}, python3-yaml, kicad (>= 5.1.6), python3-wxgtk4.0, python3-xvfb
Recommends: kibom.inti-cmnb (>= 1.8.0), kicost (>= 1.1.8), interactivehtmlbom.inti-cmnb (>= 2.4.1), imagemagick, librsvg2-bin, python3-xlsxwriter, rar, ghostscript, python3-lxml, python3-mistune, kikit
Recommends: kibom.inti-cmnb (>= 1.8.0), kicost (>= 1.1.8), interactivehtmlbom.inti-cmnb (>= 2.4.1), imagemagick, librsvg2-bin, python3-xlsxwriter, rar, ghostscript, python3-lxml, python3-mistune, kikit, python3-markdown2
Suggests: pandoc, texlive-latex-base, texlive-latex-recommended, git, poppler-utils, kidiff, python3-numpy
Description: KiCad Bot
KiBot is a program which helps you to automate the generation of KiCad

View File

@ -1182,6 +1182,79 @@ outputs:
# [string=''] Board variant to apply.
# Don't use the `kicost_variant` when using internal variants/filters
variant: ''
# KiKit's Present - Project Presentation:
# It can contain one or more PCBs, showing their top and bottom sides.
# Also includes a download link and the gerbers.
- name: 'kikit_present_example'
comment: 'Creates an HTML file showing your project.'
type: 'kikit_present'
dir: 'Example/kikit_present_dir'
options:
# [dict|list(dict)] One or more boards that compose your project.
# When empty we will use only the main PCB for the current project
boards:
# [string=''] local*: the name of an output that renders the back.
# If empty we use the first renderer for the back.
# *file*: the name of the rendered image.
# *external*: ignored, we `extrenal_config`
- back_image: ''
# [string=''] A comment or description for this board.
# Applies to all modes
comment: ''
# [string=''] Name of an external KiBot configuration.
# Only used in the *external* mode
external_config: ''
# [string=''] local*: the name of an output that renders the front.
# If empty we use the first renderer for the front.
# *file*: the name of the rendered image.
# *external*: ignored, we `extrenal_config`
front_image: ''
# [string=''] local*: the name of a compress output.
# If empty we use the first compress output.
# *file*: the name of a compressed archive.
# *external*: ignored, we `extrenal_config`
gerbers: ''
# [string='auto'] [local,file,external] How images and gerbers are obtained.
# *local*: Only applies to the currently selected PCB.
# You must provide the names of the outputs used to render
# the images and compress the gerbers.
# When empty KiBot will use the first render/compress output
# it finds.
# To apply variants use `pcb_from_output` and a `pcb_variant`
# output.
# *file*: You must specify the file names used for the images and
# the gerbers.
# *external*: You must specify an external KiBot configuration.
# It will be applied to the selected PCB to create the images and
# the gerbers. The front image must be generated in a dir called
# *front*, the back image in a dir called *back* and the gerbers
# in a dir called *gerbers*
mode: 'auto'
# [string=''] Name for this board. If empty we use the name of the PCB.
# Applies to all modes
name: ''
# [string=''] Name of the KiCad PCB file. When empty we use the current PCB.
# Is ignored for the *local* mode
pcb_file: ''
# [string=''] Use the PCB generated by another output.
# Is ignored for the *file* mode
pcb_from_output: ''
# [string=''] Name for a markdown file containing the main part of the page to be generated.
# This is mandatory and is the description of your project
description: ''
# [string=''] Name of the project. Will be passed to the template.
# If empty we use the name of the KiCad project.
# The default template uses it for things like the page title
name: ''
# [string=''] URL of the repository. Will be passed to the template
repository: ''
# [string|list(string)=''] A list of file name patterns for additional resources to be included.
# I.e. images referenced in description.
# They will be copied relative to the output dir
resources: ''
# [string='default'] Path to a template directory or a name of built-in one.
# See KiKit's doc/present.md for template specification
template: 'default'
# Navigate Results:
- name: 'navigate_results_example'
comment: 'Generates a web page to navigate the generated outputs'

View File

@ -201,3 +201,7 @@ This file comes from KiKit, but it has too much in common with `populate.py`.
- Removed the try in boardpage, too broad, doesn't help
- Removed `kikit` import, _renderBoards must be implemented in a different way
- Imported local pybars
- Added source_front, source_back and source_gerbers to the boards.
So now the images and gerbers come from outside.
- Adapted Template._renderBoards to just copy the files (keeping the extensions)

View File

@ -26,7 +26,7 @@ def resolveTemplatePath(path):
TEMPLATES = os.path.join(PKG_BASE, "resources/present/templates")
if os.path.exists(os.path.join(TEMPLATES, path, "template.json")):
return os.path.join(TEMPLATES, path)
raise RuntimeError("'{}' is not a name or a path for existing template. Perhaps you miss template.json in the template?")
raise RuntimeError("'{}' is not a name or a path for existing template. Perhaps you miss template.json in the template?".format(path))
def readTemplate(path):
"""
@ -85,14 +85,17 @@ class Template:
"""
self.extraResources.append(resource)
def addBoard(self, name, comment, boardfile):
def addBoard(self, name, comment, boardfile, front, back, gerbers):
"""
Add board
"""
self.boards.append({
"name": name,
"comment": comment,
"source": boardfile
"source": boardfile,
"source_front": front,
"source_back": back,
"source_gerbers": gerbers
})
def _renderBoards(self, outputDirectory):
@ -100,31 +103,19 @@ class Template:
Convert all boards to images and gerber exports. Enrich self.boards
with paths of generated files
"""
pcbdraw = shutil.which("pcbdraw")
if not pcbdraw:
raise RuntimeError("PcbDraw needs to be installed in order to render boards")
dirPrefix = "boards"
boardDir = os.path.join(outputDirectory, dirPrefix)
Path(boardDir).mkdir(parents=True, exist_ok=True)
for boardDesc in self.boards:
boardName = os.path.basename(boardDesc["source"]).replace(".kicad_pcb", "")
boardDesc["front"] = os.path.join(dirPrefix, boardName + "-front.png")
boardDesc["back"] = os.path.join(dirPrefix, boardName + "-back.png")
boardDesc["gerbers"] = os.path.join(dirPrefix, boardName + "-gerbers.zip")
boardDesc["front"] = os.path.join(dirPrefix, boardName + "-front"+os.path.splitext(boardDesc["source_front"])[1])
boardDesc["back"] = os.path.join(dirPrefix, boardName + "-back"+os.path.splitext(boardDesc["source_back"])[1])
boardDesc["gerbers"] = os.path.join(dirPrefix, boardName + "-gerbers"+os.path.splitext(boardDesc["source_gerbers"])[1])
boardDesc["file"] = os.path.join(dirPrefix, boardName + ".kicad_pcb")
subprocess.check_call([pcbdraw, "plot", "--vcuts=Cmts.User", "--silent", "--side=front", boardDesc["source"],
os.path.join(outputDirectory, boardDesc["front"])])
subprocess.check_call([pcbdraw, "plot", "--vcuts=Cmts.User", "--silent", "--side=back", boardDesc["source"],
os.path.join(outputDirectory, boardDesc["back"])])
tmp = tempfile.mkdtemp()
export.gerberImpl(boardDesc["source"], tmp)
shutil.make_archive(os.path.join(outputDirectory, boardDesc["gerbers"])[:-4], "zip", tmp)
shutil.rmtree(tmp)
shutil.copy(boardDesc["source"], os.path.join(outputDirectory, boardDesc["file"]))
shutil.copy(boardDesc["source_front"], os.path.join(outputDirectory, boardDesc["front"]))
shutil.copy(boardDesc["source_back"], os.path.join(outputDirectory, boardDesc["back"]))
shutil.copy(boardDesc["source_gerbers"], os.path.join(outputDirectory, boardDesc["gerbers"]))
def render(self, outputDirectory):
self._copyResources(outputDirectory)
@ -183,6 +174,6 @@ def boardpage(outdir, description, board, resource, template, repository, name):
template.setName(name)
for r in resource:
template.addResource(r)
for name, comment, file in board:
template.addBoard(name, comment, file)
for name, comment, file, front, back, gerbers in board:
template.addBoard(name, comment, file, front, back, gerbers)
template.render(outdir)

View File

@ -265,6 +265,8 @@ KICAD5_SVG_SCALE = 116930/297002200
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:102.0) Gecko/20100101 Firefox/102.0'
# Text used to disable 3D models
DISABLE_3D_MODEL_TEXT = '_Disabled_by_KiBot'
RENDERERS = ['pcbdraw', 'render_3d']
PCB_GENERATORS = ['pcb_variant', 'panelize']
class Rect(object):

View File

@ -436,6 +436,38 @@ class Optionable(object):
def color_str_to_rgb(self, color):
return self.color_to_rgb(self.parse_one_color(color))
def save_renderer_options(self):
""" Save the current renderer settings """
options = self._renderer.options
self.old_filters_to_expand = options._filters_to_expand
self.old_show_components = options.show_components
self.old_highlight = options.highlight
self.old_output = options.output
self.old_dir = self._renderer.dir
self.old_done = self._renderer._done
if self._renderer_is_pcbdraw:
self.old_bottom = options.bottom
self.old_add_to_variant = options.add_to_variant
else: # render_3D
self.old_view = options.view
self.old_show_all_components = options._show_all_components
def restore_renderer_options(self):
""" Restore the renderer settings """
options = self._renderer.options
options._filters_to_expand = self.old_filters_to_expand
options.show_components = self.old_show_components
options.highlight = self.old_highlight
options.output = self.old_output
self._renderer.dir = self.old_dir
self._renderer._done = self.old_done
if self._renderer_is_pcbdraw:
options.bottom = self.old_bottom
options.add_to_variant = self.old_add_to_variant
else: # render_3D
options.view = self.old_view
options._show_all_components = self.old_show_all_components
class BaseOptions(Optionable):
""" A class to validate and hold output options.

View File

@ -118,6 +118,9 @@ class BaseOutput(RegOutput):
return [GS.sch_file]
return [GS.pcb_file]
def get_extension(self):
return self.options._expand_ext
def config(self, parent):
if self._tree and not self._configured and isinstance(self.extends, str) and self.extends:
logger.debug("Extending `{}` from `{}`".format(self.name, self.extends))

344
kibot/out_kikit_present.py Normal file
View File

@ -0,0 +1,344 @@
# -*- 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: markdown2
python_module: true
debian: python3-markdown2
arch: python-markdown2
role: mandatory
"""
import os
from tempfile import NamedTemporaryFile, TemporaryDirectory
from .error import KiPlotConfigurationError
from .misc import W_PCBDRAW, RENDERERS, PCB_GENERATORS
from .gs import GS
from .kiplot import config_output, run_output, get_output_dir, load_board
from .optionable import BaseOptions, Optionable
from .registrable import RegOutput
from .PcbDraw.present import boardpage
from .macros import macros, document, output_class # noqa: F401
from . import log
logger = log.get_logger()
def pcbdraw_warnings(tag, msg):
logger.warning('{}({}) {}'.format(W_PCBDRAW, tag, msg))
def _get_tmp_name(ext):
with NamedTemporaryFile(mode='w', suffix='.'+ext, delete=False) as f:
f.close()
return f.name
class PresentBoards(Optionable):
def __init__(self):
super().__init__()
with document:
self.mode = 'auto'
""" [local,file,external] How images and gerbers are obtained.
*local*: Only applies to the currently selected PCB.
You must provide the names of the outputs used to render
the images and compress the gerbers.
When empty KiBot will use the first render/compress output
it finds.
To apply variants use `pcb_from_output` and a `pcb_variant`
output.
*file*: You must specify the file names used for the images and
the gerbers.
*external*: You must specify an external KiBot configuration.
It will be applied to the selected PCB to create the images and
the gerbers. The front image must be generated in a dir called
*front*, the back image in a dir called *back* and the gerbers
in a dir called *gerbers* """
self.name = ''
""" *Name for this board. If empty we use the name of the PCB.
Applies to all modes """
self.comment = ''
""" A comment or description for this board.
Applies to all modes """
self.pcb_file = ''
""" Name of the KiCad PCB file. When empty we use the current PCB.
Is ignored for the *local* mode """
self.pcb_from_output = ''
""" Use the PCB generated by another output.
Is ignored for the *file* mode """
self.front_image = ''
""" *local*: the name of an output that renders the front.
If empty we use the first renderer for the front.
*file*: the name of the rendered image.
*external*: ignored, we `extrenal_config` """
self.back_image = ''
""" *local*: the name of an output that renders the back.
If empty we use the first renderer for the back.
*file*: the name of the rendered image.
*external*: ignored, we `extrenal_config` """
self.gerbers = ''
""" *local*: the name of a compress output.
If empty we use the first compress output.
*file*: the name of a compressed archive.
*external*: ignored, we `extrenal_config` """
self.external_config = ''
""" Name of an external KiBot configuration.
Only used in the *external* mode """
def config(self, parent):
super().config(parent)
if not self.name:
self.name = GS.pcb_basename
if not self.pcb_file:
self.pcb_file = GS.pcb_file
if self.mode == 'file':
files = [self.front_image, self.back_image, self.gerbers]
for f in files:
if not os.path.isfile(f):
raise KiPlotConfigurationError('Missing file: `{}`'.format(f))
elif self.mode == 'external':
if not self.external_config:
raise KiPlotConfigurationError('`external_config` must be specified for the external mode')
if not os.path.isfile(self.external_config):
raise KiPlotConfigurationError('Missing external config: `{}`'.format(self.external_config))
if self.pcb_from_output and self.mode != 'file':
out = RegOutput.get_output(self.pcb_from_output)
self._pcb_from_output = out
if out is None:
raise KiPlotConfigurationError('Unknown output `{}` selected in board {}'.
format(self.pcb_from_output, self.name))
def solve_file(self):
return self.name, self.comment, self.pcb_file, self.front_image, self.back_image, self.gerbers
def generate_image(self, back, tmp_name):
self.save_renderer_options()
options = self._renderer.options
logger.debug('Starting renderer with back: {}, name: {}'.format(back, tmp_name))
# Configure it according to our needs
options._filters_to_expand = False
options.show_components = None if self._renderer_is_pcbdraw else []
options.highlight = []
options.output = tmp_name
self._renderer._done = False
if self._renderer_is_pcbdraw:
options.add_to_variant = False
options.bottom = back
else: # render_3D
options.view = 'Z' if back else 'z'
options._show_all_components = False
run_output(self._renderer)
self.restore_renderer_options()
def do_compress(self, tmp_name, out):
tree = {'name': '_temporal_compress_gerbers',
'type': 'compress',
'comment': 'Internally created to compress gerbers',
'options': {'output': tmp_name, 'files': [{'from_output': out.name, 'dest': '/'}]}}
out = RegOutput.get_class_for('compress')()
out.set_tree(tree)
config_output(out)
logger.debug('Creating gerbers archive ...')
out.options.run(tmp_name)
def generate_archive(self, out, tmp_name):
out.options
logger.debug('Starting gerber name: {}'.format(out.name))
# Save options
old_dir = out.dir
old_done = out._done
old_variant = out.options.variant
# Configure it according to our needs
with TemporaryDirectory() as tmp_dir:
logger.debug('Generating the gerbers at '+tmp_dir)
out.done = False
out.dir = tmp_dir
out.options.variant = None
run_output(out)
self.do_compress(tmp_name, out)
# Restore options
out.dir = old_dir
out._done = old_done
out.options.variant = old_variant
def solve_pcb(self):
if not self.pcb_from_output:
return False
out_name = self.pcb_from_output
out = RegOutput.get_output(out_name)
if out is None:
raise KiPlotConfigurationError('Unknown output `{}` selected in board {}'. format(out_name, self.name))
if out.type not in PCB_GENERATORS:
raise KiPlotConfigurationError("Output `{}` can't be used to render the PCB, must be {}".
format(out, PCB_GENERATORS))
config_output(out)
run_output(out)
new_pcb = out.get_targets(get_output_dir(out.dir, out))[0]
GS.board = None
self.old_pcb = GS.pcb_file
GS.set_pcb(new_pcb)
load_board()
self.new_pcb = new_pcb
return True
def solve_local_image(self, out_name, back=False):
if not out_name:
out = next(filter(lambda x: x.type in RENDERERS, RegOutput.get_outputs()), None)
if not out:
raise KiPlotConfigurationError('No renderer output found, must be {}'.format(RENDERERS))
else:
out = RegOutput.get_output(out_name)
if out is None:
raise KiPlotConfigurationError('Unknown output `{}` selected in board {}'. format(out_name, self.name))
if out.type not in RENDERERS:
raise KiPlotConfigurationError("Output `{}` can't be used to render the PCB, must be {}".
format(out, RENDERERS))
config_output(out)
self._renderer = out
self._renderer_is_pcbdraw = out.type == 'pcbdraw'
tmp_name = _get_tmp_name(out.get_extension())
self.temporals.append(tmp_name)
self.generate_image(back, tmp_name)
return tmp_name
def solve_local_gerbers(self, out_name):
if not out_name:
out = next(filter(lambda x: x.type == 'gerber', RegOutput.get_outputs()), None)
if not out:
raise KiPlotConfigurationError('No gerber output found')
else:
out = RegOutput.get_output(out_name)
if out is None:
raise KiPlotConfigurationError('Unknown output `{}` selected in board {}'. format(out_name, self.name))
if out.type != 'gerber':
raise KiPlotConfigurationError("Output `{}` must be `gerber` type, not {}". format(out, out.type))
config_output(out)
tmp_name = _get_tmp_name('zip')
self.temporals.append(tmp_name)
# Generate the archive
self.generate_archive(out, tmp_name)
return tmp_name
def solve_local(self):
fname = GS.pcb_file
pcb_changed = self.solve_pcb()
front_image = self.solve_local_image(self.front_image)
back_image = self.solve_local_image(self.back_image, back=True)
gerbers = self.solve_local_gerbers(self.gerbers)
if pcb_changed:
fname = self.new_pcb
GS.set_pcb(self.old_pcb)
GS.reload_project(GS.pro_file)
return self.name, self.comment, fname, front_image, back_image, gerbers
def solve(self, temporals):
self.temporals = temporals
if self.mode == 'file':
return self.solve_file()
elif self.mode == 'local':
return self.solve_local()
class KiKit_PresentOptions(BaseOptions):
def __init__(self):
with document:
self.description = ''
""" *Name for a markdown file containing the main part of the page to be generated.
This is mandatory and is the description of your project """
self.boards = PresentBoards
""" [dict|list(dict)] One or more boards that compose your project.
When empty we will use only the main PCB for the current project """
self.resources = Optionable
""" [string|list(string)=''] A list of file name patterns for additional resources to be included.
I.e. images referenced in description.
They will be copied relative to the output dir """
self.template = 'default'
""" Path to a template directory or a name of built-in one.
See KiKit's doc/present.md for template specification """
self.repository = ''
""" URL of the repository. Will be passed to the template """
self.name = ''
""" Name of the project. Will be passed to the template.
If empty we use the name of the KiCad project.
The default template uses it for things like the page title """
super().__init__()
def config(self, parent):
super().config(parent)
# Validate the input file name
if not self.description:
raise KiPlotConfigurationError('You must specify an input markdown file using `description`')
if not os.path.isfile(self.description):
raise KiPlotConfigurationError('Missing description file `{}`'.format(self.description))
# List of boards
if isinstance(self.boards, type):
a_board = PresentBoards()
a_board.fill_empty_values()
self.boards = [a_board]
elif isinstance(self.boards, PresentBoards):
self.boards = [self.boards]
# else ... we have a list of boards
self.resources = self.force_list(self.resources, comma_sep=False)
if not self.name:
self.name = GS.pcb_basename
# Make sure the template exists
if not os.path.exists(os.path.join(self.template, "template.json")):
try:
self.template = GS.get_resource_path(os.path.join('pcbdraw', 'present', 'templates', self.template))
except SystemExit:
raise KiPlotConfigurationError('Missing template `{}`'.format(self.template))
# def get_targets(self, out_dir):
# img_dir = os.path.dirname(self._parent.expand_filename(out_dir, self.imgname))
# return [self._parent.expand_filename(out_dir, self.get_out_file_name()), img_dir]
def generate_images(self, dir_name, content):
# Memorize the current options
self.save_options()
dir = os.path.dirname(os.path.join(dir_name, self.imgname))
if not os.path.exists(dir):
os.makedirs(dir)
counter = 0
for item in content:
if item["type"] != "steps":
continue
for x in item["steps"]:
counter += 1
filename = self.imgname.replace('%d', str(counter))
x["img"] = self.generate_image(x["side"], x["components"], x["active_components"], filename)
# Restore the options
self.restore_options()
return content
def run(self, dir_name):
# Generate missing images
board = []
temporals = []
try:
for brd in self.boards:
board.append(brd.solve(temporals))
try:
boardpage(dir_name, self.description, board, self.resources, self.template, self.repository, self.name)
except RuntimeError as e:
raise KiPlotConfigurationError('KiKit present error: '+str(e))
finally:
for f in temporals:
if os.path.isfile(f):
os.remove(f)
@output_class
class KiKit_Present(BaseOutput): # noqa: F821
""" KiKit's Present - Project Presentation
Creates an HTML file showing your project.
It can contain one or more PCBs, showing their top and bottom sides.
Also includes a download link and the gerbers. """
def __init__(self):
super().__init__()
with document:
self.options = KiKit_PresentOptions
""" *[dict] Options for the `kikit_present` output """
self._category = 'PCB/docs'

View File

@ -15,7 +15,7 @@ import os
from tempfile import NamedTemporaryFile
# Here we import the whole module to make monkeypatch work
from .error import KiPlotConfigurationError
from .misc import W_PCBDRAW
from .misc import W_PCBDRAW, RENDERERS
from .gs import GS
from .kiplot import config_output, run_output
from .optionable import Optionable
@ -106,41 +106,9 @@ class PopulateOptions(VariantOptions):
run_output(self._renderer)
return options.expand_filename_both(name, is_sch=False)
def save_options(self):
""" Save the current renderer settings """
options = self._renderer.options
self.old_filters_to_expand = options._filters_to_expand
self.old_show_components = options.show_components
self.old_highlight = options.highlight
self.old_output = options.output
self.old_dir = self._renderer.dir
self.old_done = self._renderer._done
if self._renderer_is_pcbdraw:
self.old_bottom = options.bottom
self.old_add_to_variant = options.add_to_variant
else: # render_3D
self.old_view = options.view
self.old_show_all_components = options._show_all_components
def restore_options(self):
""" Restore the renderer settings """
options = self._renderer.options
options._filters_to_expand = self.old_filters_to_expand
options.show_components = self.old_show_components
options.highlight = self.old_highlight
options.output = self.old_output
self._renderer.dir = self.old_dir
self._renderer._done = self.old_done
if self._renderer_is_pcbdraw:
options.bottom = self.old_bottom
options.add_to_variant = self.old_add_to_variant
else: # render_3D
options.view = self.old_view
options._show_all_components = self.old_show_all_components
def generate_images(self, dir_name, content):
# Memorize the current options
self.save_options()
self.save_renderer_options()
dir = os.path.dirname(os.path.join(dir_name, self.imgname))
if not os.path.exists(dir):
os.makedirs(dir)
@ -153,7 +121,7 @@ class PopulateOptions(VariantOptions):
filename = self.imgname.replace('%d', str(counter))
x["img"] = self.generate_image(x["side"], x["components"], x["active_components"], filename)
# Restore the options
self.restore_options()
self.restore_renderer_options()
return content
def run(self, dir_name):
@ -169,8 +137,8 @@ class PopulateOptions(VariantOptions):
if out is None:
raise KiPlotConfigurationError('Unknown output `{}` selected in {}'.format(self.renderer, self._parent))
config_output(out)
if out.type not in ['pcbdraw', 'render_3d']:
raise KiPlotConfigurationError('The `renderer` must be `pcbdraw` or `render_3d` type, not {}'.format(out.type))
if out.type not in RENDERERS:
raise KiPlotConfigurationError('The `renderer` must be {} type, not {}'.format(RENDERERS, out.type))
self._renderer = out
self._renderer_is_pcbdraw = out.type == 'pcbdraw'
# Load the input content

View File

@ -0,0 +1 @@
node_modules

View File

@ -0,0 +1 @@
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}h1{font-size:2em;margin:.67em 0}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}b,strong{font-weight:bolder}code{font-family:monospace,monospace;font-size:1em}small{font-size:80%}img{border-style:none}input{font-family:inherit;font-size:100%;line-height:1.15;margin:0}input{overflow:visible}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}[hidden]{display:none}h1,h2,h3,h4,h5,h6,p,pre{margin:0}ul{margin:0;padding:0}ul{list-style:none}html{font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;line-height:1.5}*{box-sizing:border-box;border:0 solid #e2e8f0}img{border-style:solid}input::-webkit-input-placeholder{color:#a0aec0}input::-moz-placeholder{color:#a0aec0}input:-ms-input-placeholder{color:#a0aec0}input::-ms-input-placeholder{color:#a0aec0}input::placeholder{color:#a0aec0}table{border-collapse:collapse}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}input{padding:0;line-height:inherit;color:inherit}code,pre{font-family:Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}img,object{display:block;vertical-align:middle}img{max-width:100%;height:auto}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}.bg-gray-200{background-color:#edf2f7}.rounded{border-radius:.25rem}.block{display:block}.inline{display:inline}.flex{display:flex}.table{display:table}.hidden{display:none}.flex-wrap{flex-wrap:wrap}.flex-auto{flex:1 1 auto}.clearfix:after{content:"";display:table;clear:both}.my-2{margin-top:.5rem;margin-bottom:.5rem}.my-3{margin-top:.75rem;margin-bottom:.75rem}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mt-16{margin-top:4rem}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.p-2{padding:.5rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.top-0{top:0}.right-0{right:0}.bottom-0{bottom:0}.left-0{left:0}.text-center{text-align:center}.text-right{text-align:right}.text-sm{font-size:.875rem}.w-full{width:100%}body{font-size:1rem;font-weight:400;color:#718096}h1{font-size:1.875rem}h1,h2{font-weight:700;font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;overflow-wrap:normal;word-break:normal;color:#1a202c;padding-top:1.5rem;padding-bottom:.5rem}h2{font-size:1.5rem}h3{font-weight:700;font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;overflow-wrap:normal;word-break:normal;color:#1a202c;padding-top:1.5rem;padding-bottom:.5rem;font-size:1.25rem}p{text-align:justify}a{color:#63b3ed}a:hover{color:#2b6cb0}ul{list-style-type:disc;list-style-position:inside;margin-top:.25rem;margin-bottom:.25rem}code{padding:.25rem}code,pre code{background-color:#edf2f7}pre code{display:block;margin-top:1rem;margin-bottom:1rem;padding:1rem .75rem;width:100%}img{max-width:1024px;margin:.5rem auto}.boardPreview{display:block;max-width:100%;max-height:400px;width:auto;height:auto;margin-left:auto;margin-right:auto}@media (min-width:768px){.md\:w-1\/3{width:33.333333%}}

View File

@ -0,0 +1,55 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="css/styles.css">
<title>{{name}}</title>
</head>
<body>
<div class="container mx-auto px-3">
<div class="w-full my-3 text-sm text-right bg-gray-200 px-3 py-1">
Last update: {{datetime}}
{{#if gitRev}}(git revision
{{#if repo}}<a href="{{repo}}/tree/{{gitRev}}">{{/if}}
{{gitRevShort}}
{{#if repo}}</a>{{/if}})
{{/if}}
</div>
{{{description}}}
<h1>Available files</h1>
{{#each boards}}
<div class="w-full flex flex-wrap bg-gray-200 py-3 px-3 my-2 rounded">
<div class="w-full md:w-1/3">
<h3>Front</h3>
<img src="{{this.front}}" class="boardPreview">
</div>
<div class="w-full md:w-1/3">
<h3>Back</h3>
<img src="{{this.back}}" class="boardPreview">
</div>
<div class="w-full md:w-1/3 px-4">
<h3>{{this.name}}</h3>
<p>{{this.comment}}</p>
<p>
<ul>
<li><a href="{{this.file}}">Download source board</a></li>
<li><a href="{{this.gerbers}}">Download gerbers</a></li>
</ul>
</p>
</div>
</div>
{{/each}}
<div class="w-full mt-16 mb-1 p-2 bg-gray-200 text-center txt-xs rounded">
Generated by <a href="https://github.com/yaqwsx/KiKit">KiKit</a> and <a href="https://github.com/INTI-CMNB/KiBot">KiBot</a>
</div>
</div>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,20 @@
{
"name": "boardoverview",
"version": "1.0.0",
"main": "postcss.config.js",
"dependencies": {},
"devDependencies": {
"@fullhuman/postcss-purgecss": "^2.1.0",
"cssnano": "^4.1.10",
"postcss": "^7.0.36",
"postcss-purgecss": "^2.0.3",
"tailwindcss": "^1.2.0"
},
"scripts": {
"build": "postcss src/styles.css -o css/styles.css"
},
"author": "",
"license": "ISC",
"keywords": [],
"description": ""
}

View File

@ -0,0 +1,14 @@
const purgecss = require('@fullhuman/postcss-purgecss')({
content: ['./**/*.html'],
defaultExtractor: content => content.match(/[\w-/:]+(?<!:)/g) || []
})
const cssnano = require('cssnano')
module.exports = {
plugins: [
require('tailwindcss'),
require('autoprefixer'),
cssnano({ preset: 'default' }),
purgecss
]
}

View File

@ -0,0 +1,58 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
body {
@apply text-base font-normal text-gray-600;
}
h1 {
@apply font-bold font-sans break-normal text-gray-900 pt-6 pb-2 text-3xl;
}
h2 {
@apply font-bold font-sans break-normal text-gray-900 pt-6 pb-2 text-2xl;
}
h3 {
@apply font-bold font-sans break-normal text-gray-900 pt-6 pb-2 text-xl;
}
p {
@apply text-justify;
}
a {
@apply text-blue-400;
}
a:hover {
@apply text-blue-700;
}
ul {
@apply list-disc list-inside my-1;
}
code {
@apply p-1 bg-gray-200;
}
pre code {
@apply block bg-gray-200 my-4 px-3 py-4 w-full;
}
img {
@apply max-w-screen-lg my-2 mx-auto;
}
.boardPreview {
display: block;
max-width: 100%;
max-height: 400px;
width: auto;
height: auto;
@apply mx-auto;
}

View File

@ -0,0 +1,7 @@
module.exports = {
theme: {
extend: {},
},
variants: {},
plugins: [],
}

View File

@ -0,0 +1,4 @@
{
"type": "HtmlTemplate",
"resources": ["css/*.css"]
}

View File

@ -1031,6 +1031,40 @@ deps = '{\
"url": null,\
"url_down": null\
},\
"markdown2": {\
"arch": "python-markdown2",\
"command": "markdown2",\
"comments": [],\
"deb_package": "python3-markdown2",\
"downloader": null,\
"downloader_str": null,\
"extra_arch": null,\
"extra_deb": null,\
"help_option": "--version",\
"importance": 10000,\
"in_debian": true,\
"is_kicad_plugin": false,\
"is_python": true,\
"module_name": "markdown2",\
"name": "markdown2",\
"no_cmd_line_version": false,\
"no_cmd_line_version_old": false,\
"output": "kikit_present",\
"plugin_dirs": null,\
"pypi_name": "markdown2",\
"roles": [\
{\
"desc": null,\
"mandatory": true,\
"max_version": null,\
"output": "kikit_present",\
"version": null\
}\
],\
"tests": [],\
"url": null,\
"url_down": null\
},\
"mistune": {\
"arch": "python-mistune",\
"command": "mistune",\

View File

@ -0,0 +1,3 @@
# Silly project
This is a silly project.

View File

@ -0,0 +1,37 @@
kiplot:
version: 1
outputs:
- name: KiKit_present_files
comment: "Present test using full auto mode"
type: kikit_present
dir: Present/Auto
options:
description: 'tests/data/silly_project.md'
boards:
mode: local
comment: This is a comment
repository: 'https://github.com/INTI-CMNB/KiBot/'
- name: PcbDraw
comment: "PcbDraw test top"
type: pcbdraw
dir: PcbDraw
options:
format: svg
vcuts: true
warnings: visible
dpi: 600
- name: 'gerbers'
comment: "Gerbers for the Gerber god"
type: gerber
dir: gerberdir
layers: copper
- name: result
comment: Compress the gerbers
type: compress
options:
files:
- from_output: gerbers

View File

@ -0,0 +1,20 @@
kiplot:
version: 1
outputs:
- name: KiKit_present_files
comment: "Present test using files"
type: kikit_present
dir: Present/Files
options:
description: 'tests/data/silly_project.md'
boards:
mode: file
comment: This is a comment
front_image: pp/front.png
back_image: pp/back.png
gerbers: pp/gerbers.zip
resources: 'tests/data/silly_project.md'
# template: 'default'
repository: 'https://github.com/INTI-CMNB/KiBot/'
# name: ''

View File

@ -0,0 +1,52 @@
kiplot:
version: 1
outputs:
- name: KiKit_present_files
comment: "Present test using local mode"
type: kikit_present
dir: Present/Local_1
options:
description: 'tests/data/silly_project.md'
boards:
mode: local
comment: This is a comment
name: Light control
back_image: Raytraced
repository: 'https://github.com/INTI-CMNB/KiBot/'
- name: PcbDraw
comment: "PcbDraw test top"
type: pcbdraw
dir: PcbDraw
run_by_default: false
options:
format: svg
vcuts: true
warnings: visible
dpi: 600
- name: Raytraced
type: render_3d
run_by_default: false
options:
width: 640
height: 480
rotate_x: 3
rotate_z: -2
ray_tracing: true
- name: 'gerbers'
comment: "Gerbers for the Gerber god"
type: gerber
dir: gerberdir
run_by_default: false
layers: copper
- name: result
comment: Compress the gerbers
type: compress
run_by_default: false
options:
files:
- from_output: gerbers

View File

@ -0,0 +1,76 @@
kiplot:
version: 1
outputs:
- name: KiKit_present_files
comment: "Present test using local mode"
type: kikit_present
dir: Present/Local_2
options:
description: 'tests/data/silly_project.md'
boards:
mode: local
comment: This is a comment
name: Light control
back_image: Raytraced
pcb_from_output: panel
repository: 'https://github.com/INTI-CMNB/KiBot/'
- name: panel
comment: "Simple panel"
type: panelize
run_by_default: false
options:
configs:
- name: basic
layout:
rows: 2
cols: 2
space: 2
tabs:
type: fixed
width: 5
cuts:
type: mousebites
drill: 0.5mm
spacing: 1mm
offset: 0.2mm
- name: mill_radius_1
post:
mill_radius: 1
- name: PcbDraw
comment: "PcbDraw test top"
type: pcbdraw
dir: PcbDraw
run_by_default: false
options:
format: svg
vcuts: true
warnings: visible
dpi: 600
- name: Raytraced
type: render_3d
run_by_default: false
options:
width: 640
height: 480
rotate_x: 3
rotate_z: -2
ray_tracing: true
- name: 'gerbers'
comment: "Gerbers for the Gerber god"
type: gerber
dir: gerberdir
run_by_default: false
layers: copper
- name: result
comment: Compress the gerbers
type: compress
run_by_default: false
options:
files:
- from_output: gerbers