parent
8d3696796b
commit
e56343d214
|
|
@ -0,0 +1,207 @@
|
|||
# coding: utf-8
|
||||
|
||||
"""
|
||||
Markdown renderer
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
This class renders parsed markdown back to markdown.
|
||||
It is useful for automatic modifications of the md contents.
|
||||
|
||||
:copyright: (c) 2015 by Jaroslav Kysela
|
||||
(c) extended by Jan Mrázek 2022
|
||||
:licence: WTFPL 2
|
||||
"""
|
||||
|
||||
# The following try-catch is used to support mistune 0.8.4 and 2.x
|
||||
try:
|
||||
from mistune.renderers import BaseRenderer # type: ignore
|
||||
except ModuleNotFoundError:
|
||||
from mistune import Renderer # type: ignore
|
||||
BaseRenderer = Renderer
|
||||
|
||||
|
||||
class MdRenderer(BaseRenderer):
|
||||
def __init__(self, **kwargs):
|
||||
super(MdRenderer, self).__init__()
|
||||
|
||||
def get_block(text):
|
||||
type = text[0]
|
||||
p = text.find(':')
|
||||
if p <= 0:
|
||||
return ('', '', '')
|
||||
l = int(text[1:p])
|
||||
t = text[p+1:p+1+l]
|
||||
return (text[p+1+l:], type, t)
|
||||
|
||||
def newline(self):
|
||||
return '\n'
|
||||
|
||||
def text(self, text):
|
||||
return text
|
||||
|
||||
def linebreak(self):
|
||||
return '\n'
|
||||
|
||||
def hrule(self):
|
||||
return '---\n'
|
||||
|
||||
def heading(self, text, level, raw=None):
|
||||
return '#'*level + " " + text + '\n\n'
|
||||
|
||||
# Mistune 0.8.4 calls it header, not heading
|
||||
header = heading
|
||||
|
||||
def paragraph(self, text):
|
||||
return text + '\n\n'
|
||||
|
||||
def list(self, text, ordered=True):
|
||||
r = ''
|
||||
while text:
|
||||
text, type, t = MdRenderer.get_block(text)
|
||||
if type == 'l':
|
||||
r += (ordered and ('# ' + t) or ('* ' + t)) + '\n'
|
||||
return r
|
||||
|
||||
def list_item(self, text):
|
||||
return 'l' + str(len(text)) + ':' + text
|
||||
|
||||
def block_text(self, text):
|
||||
return text
|
||||
|
||||
def block_code(self, code, lang=None):
|
||||
return '```\n' + code + '\n```\n'
|
||||
|
||||
def block_quote(self, text):
|
||||
r = ''
|
||||
for line in text.splitlines():
|
||||
r += (line and '> ' or '') + line + '\n'
|
||||
return r
|
||||
|
||||
def _emphasis(self, text, pref):
|
||||
return pref + text + pref + ' '
|
||||
|
||||
def emphasis(self, text):
|
||||
return self._emphasis(text, '_')
|
||||
|
||||
def double_emphasis(self, text):
|
||||
return self._emphasis(text, '__')
|
||||
|
||||
def strikethrough(self, text):
|
||||
return self._emphasis(text, '~~')
|
||||
|
||||
def codespan(self, text):
|
||||
return '`' + text + '`'
|
||||
|
||||
def autolink(self, link, is_email=False):
|
||||
return '<' + link + '>'
|
||||
|
||||
def link(self, link, title, text, image=False):
|
||||
r = (image and '!' or '') + '[' + text + '](' + link + ')'
|
||||
if title:
|
||||
r += '"' + title + '"'
|
||||
return r
|
||||
|
||||
def image(self, src, title, text):
|
||||
self.link(src, title, text, image=True)
|
||||
|
||||
def table(self, header, body):
|
||||
hrows = []
|
||||
while header:
|
||||
header, type, t = MdRenderer.get_block(header)
|
||||
if type == 'r':
|
||||
flags = {}
|
||||
cols = []
|
||||
while t:
|
||||
t, type2, t2 = MdRenderer.get_block(t)
|
||||
if type2 == 'f':
|
||||
fl, v = t2.split('=')
|
||||
flags[fl] = v
|
||||
elif type2 == 'c':
|
||||
cols.append(
|
||||
type('', (object,), {'flags': flags, 'text': t2})())
|
||||
hrows.append(cols)
|
||||
brows = []
|
||||
while body:
|
||||
body, type, t = MdRenderer.get_block(body)
|
||||
if type == 'r':
|
||||
flags = {}
|
||||
cols = []
|
||||
while t:
|
||||
t, type2, t2 = MdRenderer.get_block(t)
|
||||
if type2 == 'f':
|
||||
fl, v = t2.split('=')
|
||||
flags[fl] = v
|
||||
elif type2 == 'c':
|
||||
cols.append(
|
||||
type('', (object,), {'flags': flags, 'text': t2})())
|
||||
brows.append(cols)
|
||||
colscount = 0
|
||||
colmax = [0] * 100
|
||||
align = [''] * 100
|
||||
for row in hrows + brows:
|
||||
colscount = max(len(row), colscount)
|
||||
i = 0
|
||||
for col in row:
|
||||
colmax[i] = max(len(col.text), colmax[i])
|
||||
if 'align' in col.flags:
|
||||
align[i] = col.flags['align'][0]
|
||||
i += 1
|
||||
r = ''
|
||||
for row in hrows:
|
||||
i = 0
|
||||
for col in row:
|
||||
if i > 0:
|
||||
r += ' | '
|
||||
r += col.text.ljust(colmax[i])
|
||||
i += 1
|
||||
r += '\n'
|
||||
for i in range(colscount):
|
||||
if i > 0:
|
||||
r += ' | '
|
||||
if align[i] == 'c':
|
||||
r += ':' + '-'.ljust(colmax[i]-2, '-') + ':'
|
||||
elif align[i] == 'l':
|
||||
r += ':' + '-'.ljust(colmax[i]-1, '-')
|
||||
elif align[i] == 'r':
|
||||
r += '-'.ljust(colmax[i]-1, '-') + ':'
|
||||
else:
|
||||
r += '-'.ljust(colmax[i], '-')
|
||||
r += '\n'
|
||||
for row in brows:
|
||||
i = 0
|
||||
for col in row:
|
||||
if i > 0:
|
||||
r += ' | '
|
||||
r += col.text.ljust(colmax[i])
|
||||
i += 1
|
||||
r += '\n'
|
||||
return r
|
||||
|
||||
def table_row(self, content):
|
||||
return 'r' + str(len(content)) + ':' + content
|
||||
|
||||
def table_cell(self, content, **flags):
|
||||
content = content.replace('\n', ' ')
|
||||
r = ''
|
||||
for fl in flags:
|
||||
v = flags[fl]
|
||||
if type(v) == type(True):
|
||||
v = v and 1 or 0
|
||||
v = str(v) and str(v) or ''
|
||||
r += 'f' + str(len(fl) + 1 + len(v)) + ':' + fl + '=' + v
|
||||
return r + 'c' + str(len(content)) + ':' + content
|
||||
|
||||
def footnote_ref(self, key, index):
|
||||
return '[^' + str(index) + ']'
|
||||
|
||||
def footnote_item(self, key, text):
|
||||
r = '[^' + str(index) + ']:\n'
|
||||
for l in text.split('\n'):
|
||||
r += ' ' + l.lstrip().rstrip() + '\n'
|
||||
return r
|
||||
|
||||
def footnotes(self, text):
|
||||
return text
|
||||
|
||||
def finalize(self, data):
|
||||
return ''.join(data)
|
||||
|
|
@ -0,0 +1,350 @@
|
|||
import codecs
|
||||
import os
|
||||
import re
|
||||
import shlex
|
||||
import sys
|
||||
from copy import deepcopy
|
||||
from itertools import chain
|
||||
from typing import List, Optional, Any, Tuple, Dict
|
||||
|
||||
import mistune # type: ignore
|
||||
# The following try-catch is used to support mistune 0.8.4 and 2.x
|
||||
try:
|
||||
from mistune.plugins.table import plugin_table # type: ignore
|
||||
from mistune.plugins.footnotes import plugin_footnotes # type: ignore
|
||||
InlineParser = mistune.inline_parser.InlineParser
|
||||
HTMLRenderer = mistune.renderers.HTMLRenderer
|
||||
except ModuleNotFoundError:
|
||||
InlineParser = mistune.InlineLexer
|
||||
HTMLRenderer = mistune.Renderer
|
||||
import pybars # type: ignore
|
||||
import yaml
|
||||
|
||||
from . import mdrenderer
|
||||
from .plot import find_data_file, get_global_datapaths
|
||||
|
||||
PKG_BASE = os.path.dirname(__file__)
|
||||
|
||||
def parse_pcbdraw(lexer: Any, m: re.Match[str], state: Any=None) -> Any:
|
||||
text = m.group(1)
|
||||
side, components = text.split("|")
|
||||
components = list(map(lambda x: x.strip(), components.split(",")))
|
||||
return 'pcbdraw', side, components
|
||||
|
||||
class PcbDrawInlineLexer(InlineParser): # type: ignore
|
||||
def __init__(self, renderer: Any, **kwargs: Any) -> None:
|
||||
super(PcbDrawInlineLexer, self).__init__(renderer, **kwargs)
|
||||
self.enable_pcbdraw()
|
||||
|
||||
def enable_pcbdraw(self) -> None:
|
||||
pcbdraw_pattern = (
|
||||
r"\[\[" # [[
|
||||
r"([\s\S]+?\|[\s\S]+?)" # side| component
|
||||
r"\]\](?!\])" # ]]
|
||||
)
|
||||
if hasattr(self, 'register_rule'):
|
||||
# mistune v2 API
|
||||
self.rules.insert(3, 'pcbdraw')
|
||||
self.register_rule('pcbdraw', pcbdraw_pattern, parse_pcbdraw)
|
||||
else:
|
||||
# mistune v0.8.4
|
||||
self.rules.pcbdraw = re.compile(pcbdraw_pattern)
|
||||
self.default_rules.insert(3, 'pcbdraw')
|
||||
|
||||
# This method is invoked by the old mistune API (i.e. v0.8.4)
|
||||
# For the new API we register `parse_pcbdraw`
|
||||
def output_pcbdraw(self, m: re.Match[str]) -> Any:
|
||||
_, side, components = parse_pcbdraw(self, m)
|
||||
return self.renderer.pcbdraw(side, components)
|
||||
|
||||
|
||||
def Renderer(BaseRenderer, initial_components: List[str]): # type: ignore
|
||||
class Tmp(BaseRenderer): # type: ignore
|
||||
def __init__(self, initial_components: List[str]) -> None:
|
||||
super(Tmp, self).__init__(escape=False)
|
||||
self.items: List[Dict[str, Any]]= []
|
||||
self.current_item: Optional[Dict[str, Any]] = None
|
||||
self.active_side: str = "front"
|
||||
self.visited_components: List[str] = initial_components
|
||||
self.active_components: List[str] = []
|
||||
|
||||
def append_comment(self, html: str) -> None:
|
||||
if self.current_item is not None and self.current_item["type"] == "steps":
|
||||
self.items.append(self.current_item)
|
||||
if self.current_item is None or self.current_item["type"] == "steps":
|
||||
self.current_item = {
|
||||
"is_comment": True,
|
||||
"type": "comment",
|
||||
"content": ""
|
||||
}
|
||||
self.current_item["content"] += html
|
||||
|
||||
def append_step(self, step: Dict[str, Any]) -> None:
|
||||
if self.current_item is not None and self.current_item["type"] == "comment":
|
||||
self.items.append(self.current_item)
|
||||
if self.current_item is None or self.current_item["type"] == "comment":
|
||||
self.current_item = {
|
||||
"is_step": True,
|
||||
"type": "steps",
|
||||
"steps": []
|
||||
}
|
||||
self.current_item["steps"].append(step)
|
||||
|
||||
def output(self) -> List[Dict[str, Any]]:
|
||||
items = self.items
|
||||
if self.current_item is not None:
|
||||
items.append(self.current_item)
|
||||
return items
|
||||
|
||||
def pcbdraw(self, side: str, components: List[str]) -> str:
|
||||
self.active_side = side
|
||||
self.visited_components += components
|
||||
self.active_components = components
|
||||
return ""
|
||||
|
||||
def block_code(self, children: str, info: Optional[str]=None) -> Any:
|
||||
retval = super(Tmp, self).block_code(children, info)
|
||||
self.append_comment(retval)
|
||||
return retval
|
||||
|
||||
def block_quote(self, text: str) -> Any:
|
||||
retval = super(Tmp, self).block_quote(text)
|
||||
self.append_comment(retval)
|
||||
return retval
|
||||
|
||||
def block_html(self, html: str) -> Any:
|
||||
retval = super(Tmp, self).block_html(html)
|
||||
self.append_comment(retval)
|
||||
return retval
|
||||
|
||||
def heading(self, children: str, level: int) -> Any:
|
||||
retval = super(Tmp, self).heading(children, level)
|
||||
self.append_comment(retval)
|
||||
return retval
|
||||
|
||||
# Mistune 0.8.4 API
|
||||
def header(self, text: str, level: int, raw: Optional[str]=None) -> Any:
|
||||
retval = super(Tmp, self).header(text, level, raw)
|
||||
self.append_comment(retval)
|
||||
return retval
|
||||
|
||||
# Mistune 0.8.4 API
|
||||
def hrule(self) -> Any:
|
||||
retval = super(Tmp, self).hrule()
|
||||
self.append_comment(retval)
|
||||
return retval
|
||||
|
||||
def thematic_break(self) -> Any:
|
||||
retval = super(Tmp, self).thematic_break()
|
||||
self.append_comment(retval)
|
||||
return retval
|
||||
|
||||
def list(self, text: Any, ordered: bool, level: Any=None, start: Any=None) -> str:
|
||||
return ""
|
||||
|
||||
def list_item(self, text: str, level: Any=None) -> str:
|
||||
step = {
|
||||
"side": self.active_side,
|
||||
"components": self.visited_components,
|
||||
"active_components": self.active_components,
|
||||
"comment": text
|
||||
}
|
||||
self.append_step(deepcopy(step))
|
||||
return ""
|
||||
|
||||
def paragraph(self, text: str) -> Any:
|
||||
retval = super(Tmp, self).paragraph(text)
|
||||
self.append_comment(retval)
|
||||
return retval
|
||||
|
||||
def table(self, header: str, body: str) -> Any:
|
||||
retval = super(Tmp, self).table(header, body)
|
||||
self.append_comment(retval)
|
||||
return retval
|
||||
return Tmp(initial_components)
|
||||
|
||||
def load_content(filename: str) -> Tuple[Optional[Dict[str, Any]], str]:
|
||||
header = None
|
||||
with codecs.open(filename, encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
if content.startswith("---"):
|
||||
end = content.find("...")
|
||||
if end != -1:
|
||||
header = yaml.safe_load(content[3:end])
|
||||
content = content[end+3:]
|
||||
return header, content
|
||||
|
||||
def parse_content(renderer: Any, content: str) -> List[Dict[str, Any]]:
|
||||
lexer = PcbDrawInlineLexer(renderer)
|
||||
processor = mistune.Markdown(renderer=renderer, inline=lexer)
|
||||
try:
|
||||
plugin_table(processor)
|
||||
plugin_footnotes(processor)
|
||||
except NameError:
|
||||
# Mistune v0.8.4 doesn't define the above functions
|
||||
pass
|
||||
processor(content)
|
||||
return renderer.output() # type: ignore
|
||||
|
||||
def read_template(filename: str) -> str:
|
||||
with codecs.open(filename, encoding="utf-8") as f:
|
||||
return f.read()
|
||||
|
||||
def generate_html(template: str, input: List[Dict[str, Any]]) -> bytes:
|
||||
input_dict = {
|
||||
"items": input
|
||||
}
|
||||
template_fn = pybars.Compiler().compile(template)
|
||||
return template_fn(input_dict).encode("utf-8") # type: ignore
|
||||
|
||||
def generate_markdown(input: List[Dict[str, Any]]) -> bytes:
|
||||
output = ""
|
||||
for item in input:
|
||||
if item["type"] == "comment":
|
||||
output += item["content"] + "\n"
|
||||
else:
|
||||
for x in item["steps"]:
|
||||
output += "#### " + x["comment"] + "\n\n"
|
||||
output += "\n\n"
|
||||
return output.encode("utf-8")
|
||||
|
||||
|
||||
def generate_images(content: List[Dict[str, Any]], boardfilename: str,
|
||||
plot_args: List[str], name: str, outdir: str) -> List[Dict[str, Any]]:
|
||||
dir = os.path.dirname(os.path.join(outdir, name))
|
||||
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 = name.format(counter)
|
||||
generate_image(boardfilename, x["side"], x["components"],
|
||||
x["active_components"], plot_args, os.path.join(outdir, filename))
|
||||
x["img"] = filename
|
||||
return content
|
||||
|
||||
def generate_image(boardfilename: str, side: str, components: List[str],
|
||||
active: List[str], plot_args: List[str], outputfile: str) -> None:
|
||||
from copy import deepcopy
|
||||
|
||||
from .ui import plot
|
||||
|
||||
plot_args = deepcopy(plot_args)
|
||||
|
||||
if side.startswith("back"):
|
||||
plot_args += ["--side", "back"]
|
||||
plot_args += ["--filter", ",".join(components)]
|
||||
plot_args += ["--highlight", ",".join(active)]
|
||||
plot_args += [boardfilename, outputfile]
|
||||
try:
|
||||
plot.main(args=plot_args)
|
||||
except SystemExit as e:
|
||||
if e.code is not None and e.code != 0:
|
||||
raise e from None
|
||||
|
||||
def get_data_path() -> List[str]:
|
||||
paths: List[str] = []
|
||||
paths += filter(lambda x: len(x) > 0, os.environ.get("PCBDRAW_LIB_PATH", "").split(":"))
|
||||
paths += [os.path.join(PKG_BASE, "resources", "templates")]
|
||||
paths += get_global_datapaths()
|
||||
return paths
|
||||
|
||||
def prepare_params(params: List[str]) -> List[str]:
|
||||
p = [shlex.split(x) for x in params]
|
||||
return list(chain(*p))
|
||||
|
||||
|
||||
def create_renderer(format, initial_components):
|
||||
if format == "html":
|
||||
return Renderer(HTMLRenderer, initial_components) # type: ignore
|
||||
return Renderer(mdrenderer.MdRenderer, initial_components) # type: ignore
|
||||
|
||||
|
||||
# @click.command()
|
||||
# @click.argument("input", type=click.Path(exists=True, file_okay=True, dir_okay=False))
|
||||
# @click.argument("output", type=click.Path(file_okay=False, dir_okay=True))
|
||||
# @click.option("--board", "-b", type=click.Path(exists=True, file_okay=True, dir_okay=False),
|
||||
# default=None, help="override input board")
|
||||
# @click.option("--imgname", "-t", type=str, default=None,
|
||||
# help="overide image name template, should contain exactly one {{}}")
|
||||
# @click.option("--template", "-t", type=str, default=None,
|
||||
# help="override handlebars template for HTML output")
|
||||
# @click.option("--type", "-t", type=click.Choice(["md", "html"]), default=None,
|
||||
# help="override output type: markdown or HTML")
|
||||
# def populate(input: str, output: str, board: Optional[str], imgname: Optional[str],
|
||||
# template: Optional[str], type: Optional[str]) -> None:
|
||||
# """
|
||||
# Create assembly step-by-step guides
|
||||
# """
|
||||
#
|
||||
# app = fakeKiCADGui()
|
||||
#
|
||||
# data_path = get_data_path()
|
||||
# try:
|
||||
# header, content = load_content(input)
|
||||
# except IOError:
|
||||
# sys.exit("Cannot open source file " + input)
|
||||
#
|
||||
# # We change board and output paths to absolute; then we change working
|
||||
# # directory to the input file so we resolve everything according to it
|
||||
# if board is not None:
|
||||
# board = os.path.realpath(board)
|
||||
# outputpath = os.path.realpath(output)
|
||||
# input_dir = os.path.dirname(input)
|
||||
# if input_dir != '':
|
||||
# os.chdir(input_dir)
|
||||
#
|
||||
# # If no overriding is specified, load it from the template
|
||||
# try:
|
||||
# if board is None:
|
||||
# if header is None:
|
||||
# raise KeyError("board")
|
||||
# board = header["board"]
|
||||
# if imgname is None:
|
||||
# if header is None:
|
||||
# raise KeyError("imgname")
|
||||
# imgname = header["imgname"]
|
||||
# if type is None:
|
||||
# if header is None:
|
||||
# raise KeyError("type")
|
||||
# type = header["type"]
|
||||
# if template is None and type == "html":
|
||||
# if header is None:
|
||||
# raise KeyError("template")
|
||||
# template = header["template"]
|
||||
# except KeyError as e:
|
||||
# sys.exit(f"Missing parameter {e} either in template file or source header")
|
||||
#
|
||||
# if type == "html":
|
||||
# renderer = Renderer(HTMLRenderer, header.get("initial_components", [])) # type: ignore
|
||||
# outputfile = "index.html"
|
||||
# try:
|
||||
# assert template is not None
|
||||
# template_file = find_data_file(template, '.handlebars', data_path)
|
||||
# if template_file is None:
|
||||
# raise RuntimeError(f"Cannot find template '{template}'")
|
||||
# template = read_template(template_file)
|
||||
# except IOError:
|
||||
# sys.exit("Cannot open template file " + str(template))
|
||||
# else:
|
||||
# renderer = Renderer(pcbdraw.mdrenderer.MdRenderer, header.get("initial_components", [])) # type: ignore
|
||||
# outputfile = "index.md"
|
||||
# parsed_content = parse_content(renderer, content)
|
||||
# if header is None:
|
||||
# raise RuntimeError("Parameters were not specified in the template")
|
||||
# parsed_content = generate_images(parsed_content, board, prepare_params(header["params"]),
|
||||
# imgname, outputpath)
|
||||
# if type == "html":
|
||||
# assert template is not None
|
||||
# output_content = generate_html(template, parsed_content)
|
||||
# else:
|
||||
# output_content = generate_markdown(parsed_content)
|
||||
#
|
||||
# with open(os.path.join(outputpath, outputfile), "wb") as f:
|
||||
# f.write(output_content)
|
||||
#
|
||||
# if __name__ == '__main__':
|
||||
# populate()
|
||||
|
|
@ -160,7 +160,8 @@ class BaseOutput(RegOutput):
|
|||
|
||||
def run(self, output_dir):
|
||||
self.output_dir = output_dir
|
||||
self.options.run(self.expand_filename(output_dir, self.options.output))
|
||||
output = self.options.output if hasattr(self.options, 'output') else ''
|
||||
self.options.run(self.expand_filename(output_dir, output))
|
||||
|
||||
|
||||
class BoMRegex(Optionable):
|
||||
|
|
|
|||
|
|
@ -277,8 +277,8 @@ class PcbDrawOptions(VariantOptions):
|
|||
# Libs
|
||||
if isinstance(self.libs, type):
|
||||
self.libs = ['KiCAD-base']
|
||||
else:
|
||||
self.libs = ','.join(self.libs)
|
||||
# else:
|
||||
# self.libs = ','.join(self.libs)
|
||||
# V-CUTS layer
|
||||
self._vcuts_layer = Layer.solve(self.vcuts_layer)[0]._id if self.vcuts else 41
|
||||
# Highlight
|
||||
|
|
@ -420,6 +420,7 @@ class PcbDrawOptions(VariantOptions):
|
|||
plotter.setup_env_data_path()
|
||||
# Libs from the user HOME and the system (for pcbdraw)
|
||||
plotter.setup_global_data_path()
|
||||
logger.debugl(3, 'PcbDraw data path: {}'.format(plotter.data_path))
|
||||
plotter.yield_warning = pcbdraw_warnings
|
||||
plotter.libs = self.libs
|
||||
plotter.render_back = self.bottom
|
||||
|
|
|
|||
|
|
@ -0,0 +1,201 @@
|
|||
# -*- 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: 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
|
||||
from .gs import GS
|
||||
from .kiplot import config_output, run_output
|
||||
from .optionable import Optionable
|
||||
from .out_base import VariantOptions
|
||||
from .PcbDraw.populate import (load_content, get_data_path, read_template, create_renderer, parse_content, generate_html,
|
||||
generate_markdown)
|
||||
from .PcbDraw.plot import find_data_file
|
||||
from .registrable import RegOutput
|
||||
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` 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))
|
||||
# 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))
|
||||
# Initial components
|
||||
self.initial_components = Optionable.force_list(self.initial_components)
|
||||
# Validate the image patter name
|
||||
if '%d' not in self.imgname:
|
||||
raise KiPlotConfigurationError('The image pattern must contain `%d` `{}`'.format(self.imgname))
|
||||
|
||||
# def get_targets(self, out_dir):
|
||||
# return [self._parent.expand_filename(out_dir, self.output)]
|
||||
|
||||
def generate_image(self, side, components, active_components, name):
|
||||
options = self._renderer.options
|
||||
logger.debug('Starting renderer with side: {}, components: {}, high: {}, image: {}'.
|
||||
format(side, components, active_components, name))
|
||||
# Configure it according to our needs
|
||||
options.bottom = side.startswith("back")
|
||||
options.show_components = [c for c in components if c]
|
||||
if not options.show_components:
|
||||
options.show_components = None
|
||||
options.add_to_variant = False
|
||||
options.highlight = [c for c in active_components if c]
|
||||
options.output = name
|
||||
self._renderer.dir = self._parent.dir
|
||||
self._renderer._done = False
|
||||
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_bottom = options.bottom
|
||||
self.old_show_components = options.show_components
|
||||
self.old_add_to_variant = options.add_to_variant
|
||||
self.old_highlight = options.highlight
|
||||
self.old_output = options.output
|
||||
self.old_dir = self._renderer.dir
|
||||
self.old_done = self._renderer._done
|
||||
|
||||
def restore_options(self):
|
||||
""" Restore the renderer settings """
|
||||
options = self._renderer.options
|
||||
options.bottom = self.old_bottom
|
||||
options.show_components = self.old_show_components
|
||||
options.add_to_variant = self.old_add_to_variant
|
||||
options.highlight = self.old_highlight
|
||||
options.output = self.old_output
|
||||
self._renderer.dir = self.old_dir
|
||||
self._renderer._done = self.old_done
|
||||
|
||||
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):
|
||||
is_html = self.format == 'html'
|
||||
# Check the renderer output is valid
|
||||
out = RegOutput.get_output(self.renderer)
|
||||
if out is None:
|
||||
raise KiPlotConfigurationError('Unknown output `{}` selected in {}'.format(self.renderer, self._parent))
|
||||
config_output(out)
|
||||
self._renderer = out
|
||||
# Load the input content
|
||||
try:
|
||||
_, content = load_content(self.input)
|
||||
except IOError:
|
||||
raise KiPlotConfigurationError('Failed to load `{}`'.format(self.input))
|
||||
# Initialize the output file renderer
|
||||
renderer = create_renderer(self.format, self.initial_components)
|
||||
outputfile = 'index.html' if is_html else 'index.md'
|
||||
# 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 PcbDraw project """
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
with document:
|
||||
self.options = PopulateOptions
|
||||
""" *[dict] Options for the `populate` output """
|
||||
self._category = 'PCB/docs'
|
||||
|
||||
# def get_dependencies(self):
|
||||
|
||||
# @staticmethod
|
||||
# def get_conf_examples(name, layers, templates):
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
|
||||
<title>Populating manual</title>
|
||||
|
||||
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" rel="stylesheet">
|
||||
<style>
|
||||
figure {
|
||||
padding: 1em;
|
||||
background: rgb(218, 241, 255);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
body {
|
||||
padding-bottom: 3em;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
{{#each items}}
|
||||
<div class="row">
|
||||
{{#if this.is_comment}}
|
||||
<div class="col-md-12">
|
||||
{{{this.content}}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if this.is_step}}
|
||||
{{#each this.steps}}
|
||||
<div class="col-lg-6">
|
||||
<figure class="figure">
|
||||
<img src="{{this.img}}" class="figure-img img-fluid rounded">
|
||||
<figcaption class="figure-caption">{{{this.comment}}}</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
<scrip src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.bundle.min.js">
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,36 @@
|
|||
---
|
||||
params:
|
||||
- --remap remap.json
|
||||
- --libs KiCAD-base
|
||||
imgname: img/populating_{}.png
|
||||
template: simple
|
||||
type: html
|
||||
board: ../resources/ArduinoLearningKitStarter.kicad_pcb
|
||||
initial_components:
|
||||
- C1
|
||||
- R13
|
||||
...
|
||||
|
||||
# Demo population manual
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Itaque earum rerum hic
|
||||
tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias
|
||||
consequatur aut perferendis doloribus asperiores repellat. Vestibulum fermentum
|
||||
tortor id mi. Nulla turpis magna, cursus sit amet, suscipit a, interdum id,
|
||||
felis.
|
||||
|
||||
- [[front | ]] This is the front side of the board we are populating
|
||||
- [[back | ]] This is the back side of the board we are populating
|
||||
- [[front | RV1, RV2 ]] First, populate RV1 and RV2. Basically, any description
|
||||
could be here.
|
||||
- [[front | U2 ]] Let's populate U2!
|
||||
|
||||
You can put a paragraph of text between the population steps. Lorem ipsum dolor
|
||||
sit amet, consectetuer adipiscing elit. Itaque earum rerum hic tenetur a
|
||||
sapiente.
|
||||
|
||||
- [[back | R24 ]] We can also populate a component on the other side
|
||||
|
||||
## Conclusion
|
||||
|
||||
This is the end of the demo.
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
kiplot:
|
||||
version: 1
|
||||
|
||||
outputs:
|
||||
- name: PcbDraw
|
||||
comment: "PcbDraw test top"
|
||||
type: pcbdraw
|
||||
dir: PcbDraw
|
||||
run_by_default: false
|
||||
options: &pcb_draw_ops
|
||||
format: png
|
||||
style:
|
||||
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
|
||||
#libs:
|
||||
# - default
|
||||
# - eagle-default
|
||||
remap:
|
||||
L_G1: "LEDs:LED-5MM_green"
|
||||
L_B1: "LEDs:LED-5MM_blue"
|
||||
L_Y1: "LEDs:LED-5MM_yellow"
|
||||
'REF**': "dummy:dummy"
|
||||
G***: "dummy:dummy"
|
||||
svg2mod: "dummy:dummy"
|
||||
JP1: "dummy:dummy"
|
||||
JP2: "dummy:dummy"
|
||||
JP3: "dummy:dummy"
|
||||
JP4: "dummy:dummy"
|
||||
remap_components:
|
||||
- ref: PHOTO1
|
||||
lib: yaqwsx
|
||||
comp: R_PHOTO_7mm
|
||||
- reference: J8
|
||||
library: yaqwsx
|
||||
component: Pin_Header_Straight_1x02_circle
|
||||
no_drillholes: False
|
||||
mirror: False
|
||||
highlight:
|
||||
- L_G1
|
||||
- L_B1
|
||||
- R10
|
||||
- RV1
|
||||
show_components: all
|
||||
vcuts: True
|
||||
warnings: visible
|
||||
dpi: 600
|
||||
# margin:
|
||||
# left: 5
|
||||
# right: 1
|
||||
# top: 0
|
||||
# bottom: 6
|
||||
# outline_width: 3
|
||||
# show_solderpaste: false
|
||||
resistor_remap:
|
||||
- ref: R1
|
||||
val: 10K
|
||||
- ref: R2
|
||||
val: 4k7
|
||||
resistor_flip: "R2"
|
||||
size_detection: svg_paths
|
||||
# size_detection: kicad_all
|
||||
# size_detection: kicad_edge
|
||||
|
||||
- name: Populate
|
||||
comment: "Populate example"
|
||||
type: populate
|
||||
dir: Populate
|
||||
options:
|
||||
renderer: PcbDraw
|
||||
input: tests/data/source_html.md
|
||||
Loading…
Reference in New Issue