# -*- 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('\n') f.write('\n') f.write(' \n') f.write(f' \n') # Message to inform we can't read the local files f.write(' \n') # End of message f.write(f' \n') fname = None for s in self.source: if s == 'pcb': f.write(f' \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' \n') else: f.write(f' \n') fname = GS.pro_fname f.write(' \n') # Add test to check if we can read the files f.write(' \n') # End of test f.write(' \n') f.write('\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')])