251 lines
9.7 KiB
Python
251 lines
9.7 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Copyright (c) 2024 Salvador E. Tropea
|
|
# Copyright (c) 2024 Instituto Nacional de Tecnología Industrial
|
|
# License: AGPL-3.0
|
|
# Project: KiBot (formerly KiPlot)
|
|
import requests
|
|
import os
|
|
from .error import KiPlotConfigurationError
|
|
from .gs import GS
|
|
from .kiplot import load_sch, load_board
|
|
from .out_base import VariantOptions
|
|
from .macros import macros, document, output_class # noqa: F401
|
|
from . import log
|
|
|
|
logger = log.get_logger()
|
|
VALID_SOURCE = {'schematic', 'pcb', 'project'}
|
|
URL_SCRIPT = 'https://kicanvas.org/kicanvas/kicanvas.js'
|
|
SCRIPT_NAME = 'kicanvas.js'
|
|
JS_TEST = """
|
|
function ready()
|
|
{
|
|
try
|
|
{
|
|
var xmlhttp = new XMLHttpRequest();
|
|
xmlhttp.open('GET', 'fname', false);
|
|
xmlhttp.send();
|
|
}
|
|
catch (error)
|
|
{
|
|
if (window.location.protocol === 'file:')
|
|
{
|
|
document.getElementById('no_file_access').style.display = 'block';
|
|
}
|
|
throw(error);
|
|
}
|
|
}
|
|
|
|
window.addEventListener('DOMContentLoaded', ready);
|
|
"""
|
|
|
|
|
|
class KiCanvasOptions(VariantOptions):
|
|
def __init__(self):
|
|
with document:
|
|
self.source = 'schematic'
|
|
""" *[string|list(string)] [schematic,pcb,project] Source to display """
|
|
self.local_script = True
|
|
""" *Download the script and use a copy """
|
|
self.title = ''
|
|
""" Text used to replace the sheet title. %VALUE expansions are allowed.
|
|
If it starts with `+` the text is concatenated """
|
|
self.url_script = URL_SCRIPT
|
|
""" URL for the KiCanvas script """
|
|
self.controls = 'full'
|
|
""" [full,basic,none] Which controls are displayed """
|
|
self.download = True
|
|
""" Show the download button """
|
|
self.overlay = True
|
|
""" Show the overlay asking to click """
|
|
super().__init__()
|
|
self._expand_ext = 'html'
|
|
self._expand_id = 'kicanvas'
|
|
|
|
def config(self, parent):
|
|
super().config(parent)
|
|
self.source = self.force_list(self.source, lower_case=True)
|
|
for s in self.source:
|
|
if s not in VALID_SOURCE:
|
|
raise KiPlotConfigurationError(f'Invalid source `{s}` must be any of: {", ".join(VALID_SOURCE)}')
|
|
|
|
def get_html_name(self, out_dir):
|
|
return os.path.join(out_dir, self.expand_filename_sch(self._parent.output))
|
|
|
|
def _get_targets(self, out_dir, only_index=False):
|
|
files = [self.get_html_name(out_dir)]
|
|
if only_index:
|
|
return files
|
|
if self.local_script:
|
|
files.append(os.path.join(out_dir, SCRIPT_NAME))
|
|
for s in self.source:
|
|
if s == 'pcb' and GS.pcb_fname:
|
|
files.append(os.path.join(out_dir, GS.pcb_fname))
|
|
elif s == 'schematic' and GS.sch_file:
|
|
files.extend(GS.sch.file_names_variant(out_dir))
|
|
elif s == 'project' and (GS.sch_file or GS.pcb_file):
|
|
files.extend(GS.copy_project_names(GS.sch_file or GS.pcb_file, ref_dir=out_dir))
|
|
return files
|
|
|
|
def get_targets(self, out_dir):
|
|
return self._get_targets(out_dir)
|
|
|
|
def get_navigate_targets(self, out_dir):
|
|
""" Targets for the navigate results, just the index """
|
|
return self._get_targets(out_dir, True)
|
|
|
|
def save_pcb(self, out_dir):
|
|
out_pcb = os.path.join(out_dir, GS.pcb_fname)
|
|
if self._pcb_saved:
|
|
return out_pcb
|
|
self._pcb_saved = True
|
|
self.set_title(self.title)
|
|
self.filter_pcb_components(do_3D=True)
|
|
logger.debug('Saving PCB to '+out_pcb)
|
|
GS.board.Save(out_pcb)
|
|
self.unfilter_pcb_components(do_3D=True)
|
|
self.restore_title()
|
|
return out_pcb
|
|
|
|
def save_sch(self, out_dir):
|
|
if self._sch_saved:
|
|
return
|
|
self._sch_saved = True
|
|
self.set_title(self.title, sch=True)
|
|
logger.debug('Saving Schematic to '+out_dir)
|
|
GS.sch.save_variant(out_dir)
|
|
self.restore_title(sch=True)
|
|
|
|
def run(self, out_dir):
|
|
# We declare _any_related=True because we don't know if the PCB/SCH are needed until the output is configured.
|
|
# Now that we know it we can load the PCB and/or SCH.
|
|
for s in self.source:
|
|
if s == 'pcb':
|
|
load_board()
|
|
elif s == 'schematic':
|
|
load_sch()
|
|
else:
|
|
load_sch()
|
|
load_board()
|
|
GS.check_pro()
|
|
# Now that we loaded the needed stuff we can do the parent run
|
|
super().run(out_dir)
|
|
# Download KiCanvas
|
|
if self.local_script:
|
|
logger.debug(f'Downloading the script from `{self.url_script}`')
|
|
try:
|
|
r = requests.get(self.url_script, allow_redirects=True)
|
|
except Exception as e:
|
|
raise KiPlotConfigurationError(f'Failed to download the KiCanvas script from `{self.url_script}`: '+str(e))
|
|
dest = os.path.join(out_dir, SCRIPT_NAME)
|
|
logger.debug(f'Saving the script to `{dest}`')
|
|
GS.write_to_file(r.content, dest)
|
|
script_src = SCRIPT_NAME
|
|
else:
|
|
script_src = self.url_script
|
|
# Generate all pages
|
|
self._sch_saved = self._pcb_saved = False
|
|
for s in self.source:
|
|
# Save the PCB/SCH/Project
|
|
if s == 'pcb':
|
|
self.save_pcb(out_dir)
|
|
elif s == 'schematic':
|
|
self.save_sch(out_dir)
|
|
else:
|
|
self.save_sch(out_dir)
|
|
GS.copy_project(self.save_pcb(out_dir))
|
|
# Create the HTML file
|
|
full_name = self.get_html_name(out_dir)
|
|
logger.debug(f'Creating KiCanvas HTML: {full_name}')
|
|
controlslist = []
|
|
if not self.download:
|
|
controlslist.append('nodownload')
|
|
if not self.overlay:
|
|
controlslist.append('nooverlay')
|
|
controlslist = f' controlslist="{",".join(controlslist)}"' if controlslist else ''
|
|
with GS.create_file(full_name) as f:
|
|
f.write('<!DOCTYPE HTML>\n')
|
|
f.write('<html lang="en">\n')
|
|
f.write(' <body>\n')
|
|
f.write(f' <script type="module" src="{script_src}"></script>\n')
|
|
# Message to inform we can't read the local files
|
|
f.write(' <div id="no_file_access" style="display: none; ">\n')
|
|
f.write(" <span>The browser can't read local files. Enable it to continue."
|
|
" I.e. use <i>--allow-file-access-from-files</i> on Chrome</span>\n")
|
|
f.write(' </div>\n')
|
|
# End of message
|
|
f.write(f' <kicanvas-embed controls="{self.controls}"{controlslist}>\n')
|
|
fname = None
|
|
for s in self.source:
|
|
if s == 'pcb':
|
|
f.write(f' <kicanvas-source src="{GS.pcb_fname}"></kicanvas-source>\n')
|
|
fname = GS.pcb_fname
|
|
elif s == 'schematic':
|
|
GS.sch_dir
|
|
for s in sorted(GS.sch.all_sheets, key=lambda x: x.sheet_path_h):
|
|
fname = os.path.relpath(s.fname, GS.sch_dir)
|
|
f.write(f' <kicanvas-source src="{fname}"></kicanvas-source>\n')
|
|
else:
|
|
f.write(f' <kicanvas-source src="{GS.pro_fname}"></kicanvas-source>\n')
|
|
fname = GS.pro_fname
|
|
f.write(' </kicanvas-embed>\n')
|
|
# Add test to check if we can read the files
|
|
f.write(' <script>\n')
|
|
f.write(JS_TEST.replace('fname', fname))
|
|
f.write(' </script>\n')
|
|
# End of test
|
|
f.write(' </body>\n')
|
|
f.write('</html>\n')
|
|
|
|
|
|
@output_class
|
|
class KiCanvas(BaseOutput): # noqa: F821
|
|
""" KiCanvas
|
|
Generates an interactive web page to browse the schematic and/or PCB.
|
|
Note that this tool is in alpha stage, so be ready to face some issues.
|
|
Also note that most browsers won't allow Java Script to read local files,
|
|
needed to load the SCH/PCB. So you must use a web server or enable the
|
|
access to local files. In the case of Google Chrome you can use the
|
|
`--allow-file-access-from-files` command line option.
|
|
For more information visit the [KiCanvas web](https://github.com/theacodes/kicanvas)
|
|
"""
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._category = ['PCB/docs', 'Schematic/docs']
|
|
self._any_related = True
|
|
with document:
|
|
self.output = GS.def_global_output
|
|
""" *Filename for the output (%i=kicanvas, %x=html) """
|
|
self.options = KiCanvasOptions
|
|
""" *[dict] Options for the KiCanvas output """
|
|
|
|
def config(self, parent):
|
|
super().config(parent)
|
|
if self.get_user_defined('category'):
|
|
# The user specified a category, don't change it
|
|
return
|
|
# Adjust the category according to the selected output/s
|
|
cat = set()
|
|
for s in self.options.source:
|
|
if s == 'pcb':
|
|
cat.add('PCB/docs')
|
|
elif s == 'schematic':
|
|
cat.add('Schematic/docs')
|
|
else:
|
|
cat.add('PCB/docs')
|
|
cat.add('Schematic/docs')
|
|
self.category = list(cat)
|
|
|
|
@staticmethod
|
|
def get_conf_examples(name, layers):
|
|
outs = BaseOutput.simple_conf_examples(name, 'Web page to browse the schematic and/or PCB', 'Browse') # noqa: F821
|
|
sources = []
|
|
if GS.sch_file:
|
|
sources.append('schematic')
|
|
if GS.pcb_file:
|
|
sources.append('pcb')
|
|
outs[0]['options'] = {'source': sources}
|
|
return outs
|
|
|
|
def get_navigate_targets(self, out_dir):
|
|
return (self.options.get_navigate_targets(out_dir), [os.path.join(GS.get_resource_path('kicanvas'), 'kicanvas.svg')])
|