KiBot/kibot/out_populate.py

174 lines
7.4 KiB
Python

# -*- coding: utf-8 -*-
# Copyright (c) 2022-2023 Salvador E. Tropea
# Copyright (c) 2022-2023 Instituto Nacional de Tecnología Industrial
# License: GPL-3.0
# Project: KiBot (formerly KiPlot)
"""
Dependencies:
- name: mistune
python_module: true
debian: python3-mistune
arch: python-mistune
role: mandatory
"""
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, RENDERERS
from .gs import GS
from .kiplot import run_output, look_for_output
from .optionable import Optionable
from .out_base import VariantOptions
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 PopulateOptions(VariantOptions):
def __init__(self):
with document:
self.renderer = ''
""" *Name of the output used to render the PCB steps.
Currently this must be a `pcbdraw` or `render_3d` output """
self.template = 'simple'
""" [string] The name of the handlebars template used for the HTML output.
The extension must be `.handlebars`, it will be added when missing.
The `simple.handlebars` template is a built-in template """
self.format = 'html'
""" *[html,md] Format for the generated output """
self.initial_components = Optionable
""" [string|list(string)=''] List of components soldered before the first step """
self.input = ''
""" *Name of the input file describing the assembly. Must be a markdown file.
Note that the YAML section of the file will be skipped, all the needed information
comes from this output and the `renderer` output """
self.imgname = 'img/populating_%d.%x'
""" Pattern used for the image names. The `%d` is replaced by the image number.
The `%x` is replaced by the extension. Note that the format is selected by the
`renderer` """
super().__init__()
def config(self, parent):
super().config(parent)
# Validate the input file name
if not self.input:
raise KiPlotConfigurationError('You must specify an input markdown file')
if not os.path.isfile(self.input):
raise KiPlotConfigurationError('Missing input file `{}`'.format(self.input))
# Initial components
self.initial_components = Optionable.force_list(self.initial_components)
# Validate the image pattern name
if '%d' not in self.imgname:
raise KiPlotConfigurationError('The image pattern must contain `%d` `{}`'.format(self.imgname))
def get_out_file_name(self):
return 'index.html' if self.format == 'html' else 'index.md'
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_image(self, side, components, active_components, name, options):
logger.debug('Starting renderer with side: {}, components: {}, high: {}, image: {}'.
format(side, components, active_components, name))
# Configure it according to our needs
o_name = options.setup_renderer(components, active_components, side.startswith("back"), name)
self._renderer.dir = self._parent.dir
self._renderer._done = False
run_output(self._renderer)
return o_name
def generate_images(self, dir_name, content):
options = self._renderer.get_renderer_options()
if options is None:
raise KiPlotConfigurationError('No suitable renderer ({})'.format(self._renderer))
# Memorize the current options
options.save_renderer_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, options)
# Restore the options
options.restore_renderer_options()
return content
def run(self, dir_name):
# Ensure we have mistune
self.ensure_tool('mistune')
# Now we can use populate
from .PcbDraw.populate import (load_content, get_data_path, read_template, create_renderer, parse_content,
generate_html, generate_markdown, find_data_file)
is_html = self.format == 'html'
# Check the renderer output is valid
self._renderer = look_for_output(self.renderer, 'renderer', self._parent, RENDERERS)
# Load the input content
try:
_, content = load_content(self.input)
except IOError:
raise KiPlotConfigurationError('Failed to load `{}`'.format(self.input))
# Load the template
if self.format == 'html':
data_path = get_data_path()
data_path.insert(0, os.path.join(GS.get_resource_path('pcbdraw'), 'templates'))
template_file = find_data_file(self.template, '.handlebars', data_path)
if not template_file:
raise KiPlotConfigurationError('Unable to find template file `{}`'.format(self.template))
try:
self._template = read_template(template_file)
except IOError:
raise KiPlotConfigurationError('Failed to load file `{}`'.format(template_file))
# Initialize the output file renderer
renderer = create_renderer(self.format, self.initial_components)
outputfile = self.get_out_file_name()
# Parse the input markdown
parsed_content = parse_content(renderer, content)
logger.debugl(3, parsed_content)
# Generate the images
self.generate_images(dir_name, parsed_content)
# Generate the output file content
if is_html:
output_content = generate_html(self._template, parsed_content)
else:
output_content = generate_markdown(parsed_content)
logger.debugl(3, output_content)
# Write it to the output file
with open(os.path.join(dir_name, outputfile), "wb") as f:
f.write(output_content)
@output_class
class Populate(BaseOutput): # noqa: F821
""" Populate - Assembly instructions builder
Creates a markdown and/or HTML file explaining how to assembly a PCB.
Each step shows the already soldered components and the ones to add highlighted.
This is equivalent to the PcbDraw's Populate command, but integrated to KiBot.
For more information about the input markdown file please consult the
[documentation](docs/populate.md) """
def __init__(self):
super().__init__()
with document:
self.options = PopulateOptions
""" *[dict] Options for the `populate` output """
self._category = 'PCB/docs'