# -*- 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. """ 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')])