[PcbDraw] Added present.py
- This is from KiKit, but is very similar to populate.py
This commit is contained in:
parent
5ad49e435d
commit
5cd508ac3b
|
|
@ -193,3 +193,8 @@ No current changes
|
|||
- Made `mdrenderer` import relative. So we get the mdrenderer from the same dir, not the system
|
||||
- Replicated find_data_file (from plot.py) to avoid cross dependencies
|
||||
|
||||
### present.py
|
||||
|
||||
This file comes from KiKit, but it has too much in common with `populate.py`.
|
||||
|
||||
- Removed click import (unused)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,197 @@
|
|||
import click
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
import glob
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import markdown2
|
||||
import pybars
|
||||
from datetime import datetime
|
||||
from kikit import export
|
||||
|
||||
def resolveTemplatePath(path):
|
||||
"""
|
||||
Return a correct template path:
|
||||
- if the path matches a directory relative to working directory and the
|
||||
directory contains template.json, return that
|
||||
- otherwise treat the path as a name into the default template library.
|
||||
If none of those are template directories, raise exception.
|
||||
"""
|
||||
if os.path.exists(os.path.join(path, "template.json")):
|
||||
return path
|
||||
PKG_BASE = os.path.dirname(__file__)
|
||||
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?")
|
||||
|
||||
def readTemplate(path):
|
||||
"""
|
||||
Resolve template path, read the property file and return a subclass of
|
||||
Template which can render the template.
|
||||
"""
|
||||
templateClasses = {
|
||||
"HtmlTemplate": HtmlTemplate
|
||||
}
|
||||
path = resolveTemplatePath(path)
|
||||
with open(os.path.join(path, "template.json")) as jsonFile:
|
||||
parameters = json.load(jsonFile)
|
||||
try:
|
||||
tType = parameters["type"]
|
||||
except KeyError:
|
||||
raise RuntimeError("Invalid template.json - missing 'type'")
|
||||
try:
|
||||
return templateClasses[tType](path)
|
||||
except KeyError:
|
||||
raise RuntimeError("Unknown template type '{}'".format(tType))
|
||||
|
||||
def copyRelativeTo(sourceTree, sourceFile, outputDir):
|
||||
sourceTree = os.path.abspath(sourceTree)
|
||||
sourceFile = os.path.abspath(sourceFile)
|
||||
relPath = os.path.relpath(sourceFile, sourceTree)
|
||||
outputDir = os.path.join(outputDir, os.path.dirname(relPath))
|
||||
Path(outputDir).mkdir(parents=True, exist_ok=True)
|
||||
shutil.copy(sourceFile, outputDir)
|
||||
|
||||
class Template:
|
||||
def __init__(self, directory):
|
||||
self.directory = directory
|
||||
with open(os.path.join(directory, "template.json")) as jsonFile:
|
||||
self.parameters = json.load(jsonFile)
|
||||
self.extraResources = []
|
||||
self.boards = []
|
||||
self.name = None
|
||||
self.repository = None
|
||||
|
||||
def _copyResources(self, outputDirectory):
|
||||
"""
|
||||
Copy all resource files specified by template.json and further specified
|
||||
by addResource to the output directory.
|
||||
"""
|
||||
for pattern in self.parameters["resources"]:
|
||||
for path in glob.glob(os.path.join(self.directory, pattern), recursive=True):
|
||||
copyRelativeTo(self.directory, path, outputDirectory)
|
||||
for pattern in self.extraResources:
|
||||
for path in glob.glob(pattern, recursive=True):
|
||||
copyRelativeTo(".", path, outputDirectory)
|
||||
|
||||
def addResource(self, resource):
|
||||
"""
|
||||
Add a resources. Resource can be specified by a glob pattern. The files
|
||||
are treated relative to current working directory.
|
||||
"""
|
||||
self.extraResources.append(resource)
|
||||
|
||||
def addBoard(self, name, comment, boardfile):
|
||||
"""
|
||||
Add board
|
||||
"""
|
||||
self.boards.append({
|
||||
"name": name,
|
||||
"comment": comment,
|
||||
"source": boardfile
|
||||
})
|
||||
|
||||
def _renderBoards(self, outputDirectory):
|
||||
"""
|
||||
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["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"]))
|
||||
|
||||
def render(self, outputDirectory):
|
||||
self._copyResources(outputDirectory)
|
||||
self._renderBoards(outputDirectory)
|
||||
self._renderPage(outputDirectory)
|
||||
|
||||
def gitRevision(self):
|
||||
"""
|
||||
Return a git revision string if in git repo, None otherwise
|
||||
"""
|
||||
proc = subprocess.run(["git", "rev-parse", "HEAD"], capture_output=True)
|
||||
if proc.returncode:
|
||||
return None
|
||||
return proc.stdout.decode("utf-8")
|
||||
|
||||
def currentDateTime(self):
|
||||
return datetime.now().strftime("%d. %m. %Y %H:%M")
|
||||
|
||||
def setName(self, name):
|
||||
self.name = name
|
||||
|
||||
def setRepository(self, rep):
|
||||
self.repository = rep
|
||||
|
||||
|
||||
class HtmlTemplate(Template):
|
||||
def __init__(self, path):
|
||||
super().__init__(path)
|
||||
|
||||
def addDescriptionFile(self, description):
|
||||
if not description.endswith(".md"):
|
||||
raise RuntimeError("Only markdown descriptions are supported for now")
|
||||
self.description = markdown2.markdown_path(description, extras=["fenced-code-blocks"])
|
||||
|
||||
def _renderPage(self, outputDirectory):
|
||||
with open(os.path.join(self.directory, "index.html")) as templateFile:
|
||||
template = pybars.Compiler().compile(templateFile.read())
|
||||
gitRev = self.gitRevision()
|
||||
content = template({
|
||||
"repo": self.repository,
|
||||
"gitRev": gitRev,
|
||||
"gitRevShort": gitRev[:7] if gitRev else None,
|
||||
"datetime": self.currentDateTime(),
|
||||
"name": self.name,
|
||||
"boards": self.boards,
|
||||
"description": self.description
|
||||
})
|
||||
with open(os.path.join(outputDirectory, "index.html"),"w") as outFile:
|
||||
outFile.write(content)
|
||||
|
||||
def boardpage(outdir, description, board, resource, template, repository, name):
|
||||
try:
|
||||
Path(outdir).mkdir(parents=True, exist_ok=True)
|
||||
template = readTemplate(template)
|
||||
template.addDescriptionFile(description)
|
||||
template.setRepository(repository)
|
||||
template.setName(name)
|
||||
for r in resource:
|
||||
template.addResource(r)
|
||||
for name, comment, file in board:
|
||||
template.addBoard(name, comment, file)
|
||||
template.render(outdir)
|
||||
except Exception as e:
|
||||
sys.stderr.write("An error occurred: " + str(e) + "\n")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Loading…
Reference in New Issue