parent
c14019cc25
commit
de1fc05298
|
|
@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
## [1.6.4] - UNRELEASED
|
||||
### Added
|
||||
- New outputs:
|
||||
- KiRi: interactive diff
|
||||
- KiCanvas: on-line schematic/PCB browser
|
||||
- General:
|
||||
- Operations that copies the project now also copies the PRL and the DRU
|
||||
- Files named *.kibot.yml are also detected as configuration files
|
||||
|
|
|
|||
|
|
@ -1517,6 +1517,36 @@ outputs:
|
|||
# variants with the ';' (semicolon) character.
|
||||
# This isn't related to the KiBot concept of variants
|
||||
variant: ''
|
||||
# KiCanvas:
|
||||
#
|
||||
- name: 'kicanvas_example'
|
||||
comment: 'Generates an interactive web page to browse the schematic and/or PCB.'
|
||||
type: 'kicanvas'
|
||||
dir: 'Example/kicanvas_dir'
|
||||
options:
|
||||
# [string='full'] [full,basic,none] Which controls are displayed
|
||||
controls: 'full'
|
||||
# [string|list(string)='_none'] Name of the filter to mark components as not fitted.
|
||||
# A short-cut to use for simple cases where a variant is an overkill
|
||||
dnf_filter: '_none'
|
||||
# [boolean=true] Show the download button
|
||||
download: true
|
||||
# [boolean=true] Download the script and use a copy
|
||||
local_script: true
|
||||
# [boolean=true] Show the overlay asking to click
|
||||
overlay: true
|
||||
# [string|list(string)='_none'] Name of the filter to transform fields before applying other filters.
|
||||
# A short-cut to use for simple cases where a variant is an overkill
|
||||
pre_transform: '_none'
|
||||
# [string|list(string)] [schematic,pcb,project] Source to display
|
||||
source: 'schematic'
|
||||
# [string=''] Text used to replace the sheet title. %VALUE expansions are allowed.
|
||||
# If it starts with `+` the text is concatenated
|
||||
title: ''
|
||||
# URL for the KiCanvas script
|
||||
url_script: 'https://kicanvas.org/kicanvas/kicanvas.js'
|
||||
# [string=''] Board variant to apply
|
||||
variant: ''
|
||||
# KiCost (KiCad Cost calculator):
|
||||
# For more information: https://github.com/INTI-CMNB/KiCost
|
||||
# This output is what you get from the KiCost plug-in (eeschema).
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@ The available values for *type* are:
|
|||
- ``report`` generates a report about the PDF. Can include images
|
||||
from the above outputs.
|
||||
- ``diff`` creates PDF files showing schematic or PCB changes.
|
||||
- ``kiri`` creates an interactive web page showing schematic or PCB changes.
|
||||
|
||||
.. index::
|
||||
pair: supported; Bill of Materials
|
||||
|
|
@ -128,6 +129,7 @@ The available values for *type* are:
|
|||
- ``kikit_present`` To create a project presentation web page.
|
||||
- ``navigate_results`` generates web pages to navigate the generated
|
||||
outputs.
|
||||
- ``kicanvas`` creates a web page to display the schematic and/or PCB
|
||||
|
||||
.. index::
|
||||
pair: supported; fabrication helpers
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
.. Automatically generated by KiBot, please don't edit this file
|
||||
|
||||
.. index::
|
||||
pair: KiCanvas; kicanvas
|
||||
|
||||
KiCanvas
|
||||
~~~~~~~~
|
||||
|
||||
Generates an interactive web page to browse the schematic and/or PCB.
|
||||
|
||||
|
||||
Type: ``kicanvas``
|
||||
|
||||
Categories: **PCB/docs**, **Schematic/docs**
|
||||
|
||||
Parameters:
|
||||
|
||||
- **comment** :index:`: <pair: output - kicanvas; comment>` [string=''] A comment for documentation purposes. It helps to identify the output.
|
||||
- **dir** :index:`: <pair: output - kicanvas; dir>` [string='./'] Output directory for the generated files.
|
||||
If it starts with `+` the rest is concatenated to the default dir.
|
||||
- **name** :index:`: <pair: output - kicanvas; name>` [string=''] Used to identify this particular output definition.
|
||||
Avoid using `_` as first character. These names are reserved for KiBot.
|
||||
- **options** :index:`: <pair: output - kicanvas; options>` [dict] Options for the KiCanvas output.
|
||||
|
||||
- Valid keys:
|
||||
|
||||
- **local_script** :index:`: <pair: output - kicanvas - options; local_script>` [boolean=true] Download the script and use a copy.
|
||||
- **source** :index:`: <pair: output - kicanvas - options; source>` [string|list(string)] [schematic,pcb,project] Source to display.
|
||||
- ``controls`` :index:`: <pair: output - kicanvas - options; controls>` [string='full'] [full,basic,none] Which controls are displayed.
|
||||
- ``dnf_filter`` :index:`: <pair: output - kicanvas - options; dnf_filter>` [string|list(string)='_none'] Name of the filter to mark components as not fitted.
|
||||
A short-cut to use for simple cases where a variant is an overkill.
|
||||
|
||||
- ``download`` :index:`: <pair: output - kicanvas - options; download>` [boolean=true] Show the download button.
|
||||
- ``overlay`` :index:`: <pair: output - kicanvas - options; overlay>` [boolean=true] Show the overlay asking to click.
|
||||
- ``pre_transform`` :index:`: <pair: output - kicanvas - options; pre_transform>` [string|list(string)='_none'] Name of the filter to transform fields before applying other filters.
|
||||
A short-cut to use for simple cases where a variant is an overkill.
|
||||
|
||||
- ``title`` :index:`: <pair: output - kicanvas - options; title>` [string=''] Text used to replace the sheet title. %VALUE expansions are allowed.
|
||||
If it starts with `+` the text is concatenated.
|
||||
- ``url_script`` :index:`: <pair: output - kicanvas - options; url_script>` URL for the KiCanvas script.
|
||||
- ``variant`` :index:`: <pair: output - kicanvas - options; variant>` [string=''] Board variant to apply.
|
||||
|
||||
- **output** :index:`: <pair: output - kicanvas; output>` [string='%f-%i%I%v.%x'] Filename for the output (%i=kicanvas, %x=html). Affected by global options.
|
||||
- **type** :index:`: <pair: output - kicanvas; type>` 'kicanvas'
|
||||
- ``category`` :index:`: <pair: output - kicanvas; category>` [string|list(string)=''] The category for this output. If not specified an internally defined category is used.
|
||||
Categories looks like file system paths, i.e. **PCB/fabrication/gerber**.
|
||||
The categories are currently used for `navigate_results`.
|
||||
|
||||
- ``disable_run_by_default`` :index:`: <pair: output - kicanvas; disable_run_by_default>` [string|boolean] Use it to disable the `run_by_default` status of other output.
|
||||
Useful when this output extends another and you don't want to generate the original.
|
||||
Use the boolean true value to disable the output you are extending.
|
||||
- ``extends`` :index:`: <pair: output - kicanvas; extends>` [string=''] Copy the `options` section from the indicated output.
|
||||
Used to inherit options from another output of the same type.
|
||||
- ``groups`` :index:`: <pair: output - kicanvas; groups>` [string|list(string)=''] One or more groups to add this output. In order to catch typos
|
||||
we recommend to add outputs only to existing groups. You can create an empty group if
|
||||
needed.
|
||||
|
||||
- ``output_id`` :index:`: <pair: output - kicanvas; output_id>` [string=''] Text to use for the %I expansion content. To differentiate variations of this output.
|
||||
- ``priority`` :index:`: <pair: output - kicanvas; priority>` [number=50] [0,100] Priority for this output. High priority outputs are created first.
|
||||
Internally we use 10 for low priority, 90 for high priority and 50 for most outputs.
|
||||
- ``run_by_default`` :index:`: <pair: output - kicanvas; run_by_default>` [boolean=true] When enabled this output will be created when no specific outputs are requested.
|
||||
|
||||
|
|
@ -29,6 +29,7 @@ Notes:
|
|||
outputs/ibom
|
||||
outputs/info
|
||||
outputs/kibom
|
||||
outputs/kicanvas
|
||||
outputs/kicost
|
||||
outputs/kikit_present
|
||||
outputs/kiri
|
||||
|
|
|
|||
46
kibot/gs.py
46
kibot/gs.py
|
|
@ -1,8 +1,9 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020-2023 Salvador E. Tropea
|
||||
# Copyright (c) 2020-2023 Instituto Nacional de Tecnología Industrial
|
||||
# Copyright (c) 2020-2024 Salvador E. Tropea
|
||||
# Copyright (c) 2020-2024 Instituto Nacional de Tecnología Industrial
|
||||
# License: GPL-3.0
|
||||
# Project: KiBot (formerly KiPlot)
|
||||
from contextlib import contextmanager
|
||||
import os
|
||||
import re
|
||||
import json
|
||||
|
|
@ -52,6 +53,7 @@ class GS(object):
|
|||
pcb_no_ext = None # /.../dir/pcb
|
||||
pcb_dir = None # /.../dir
|
||||
pcb_basename = None # pcb
|
||||
pcb_fname = None # pcb.kicad_pcb
|
||||
pcb_last_dir = None # dir
|
||||
# SCH name and useful parts
|
||||
sch_file = None # /.../dir/file.sch
|
||||
|
|
@ -59,12 +61,14 @@ class GS(object):
|
|||
sch_dir = None # /.../dir
|
||||
sch_last_dir = None # dir
|
||||
sch_basename = None # file
|
||||
sch_fname = None # file.kicad_sch
|
||||
# Project and useful parts
|
||||
pro_file = None # /.../dir/file.kicad_pro (or .pro)
|
||||
pro_no_ext = None # /.../dir/file
|
||||
pro_dir = None # /.../dir
|
||||
pro_last_dir = None # dir
|
||||
pro_basename = None # file
|
||||
pro_fname = None # file.kicad_pro (or .pro)
|
||||
pro_ext = '.pro'
|
||||
pro_variables = None # KiCad 6 text variables defined in the project
|
||||
vars_regex = re.compile(r'\$\{([^\}]+)\}')
|
||||
|
|
@ -199,7 +203,8 @@ class GS(object):
|
|||
if name:
|
||||
name = os.path.abspath(name)
|
||||
GS.sch_file = name
|
||||
GS.sch_basename = os.path.splitext(os.path.basename(name))[0]
|
||||
GS.sch_fname = os.path.basename(name)
|
||||
GS.sch_basename = os.path.splitext(GS.sch_fname)[0]
|
||||
GS.sch_no_ext = os.path.splitext(name)[0]
|
||||
GS.sch_dir = os.path.dirname(name)
|
||||
GS.sch_last_dir = os.path.basename(GS.sch_dir)
|
||||
|
|
@ -209,7 +214,8 @@ class GS(object):
|
|||
if name:
|
||||
name = os.path.abspath(name)
|
||||
GS.pcb_file = name
|
||||
GS.pcb_basename = os.path.splitext(os.path.basename(name))[0]
|
||||
GS.pcb_fname = os.path.basename(name)
|
||||
GS.pcb_basename = os.path.splitext(GS.pcb_fname)[0]
|
||||
GS.pcb_no_ext = os.path.splitext(name)[0]
|
||||
GS.pcb_dir = os.path.dirname(name)
|
||||
GS.pcb_last_dir = os.path.basename(GS.pcb_dir)
|
||||
|
|
@ -219,7 +225,8 @@ class GS(object):
|
|||
if name:
|
||||
name = os.path.abspath(name)
|
||||
GS.pro_file = name
|
||||
GS.pro_basename = os.path.splitext(os.path.basename(name))[0]
|
||||
GS.pro_fname = os.path.basename(name)
|
||||
GS.pro_basename = os.path.splitext(GS.pro_fname)[0]
|
||||
GS.pro_no_ext = os.path.splitext(name)[0]
|
||||
GS.pro_dir = os.path.dirname(name)
|
||||
GS.pro_last_dir = os.path.basename(GS.pro_dir)
|
||||
|
|
@ -483,6 +490,11 @@ class GS(object):
|
|||
if not GS.sch_file:
|
||||
GS.exit_with_error('No SCH file found (*.sch), use -e to specify one.', EXIT_BAD_ARGS)
|
||||
|
||||
@staticmethod
|
||||
def check_pro():
|
||||
if not GS.pro_file:
|
||||
GS.exit_with_error('No project file found (*.kicad_pro/*.pro).', EXIT_BAD_ARGS)
|
||||
|
||||
@staticmethod
|
||||
def copy_project(new_pcb_name, dry=False):
|
||||
pro_name = GS.pro_file
|
||||
|
|
@ -510,6 +522,18 @@ class GS(object):
|
|||
copy2(dru_name, dru_copy)
|
||||
return pro_copy, prl_copy, dru_copy
|
||||
|
||||
@staticmethod
|
||||
def copy_project_names(pcb_name):
|
||||
pro_copy, prl_copy, dru_copy = GS.copy_project(pcb_name, dry=True)
|
||||
files = []
|
||||
if pro_copy:
|
||||
files.append(pro_copy)
|
||||
if prl_copy:
|
||||
files.append(prl_copy)
|
||||
if dru_copy:
|
||||
files.append(dru_copy)
|
||||
return files
|
||||
|
||||
@staticmethod
|
||||
def copy_project_sch(sch_dir):
|
||||
""" Copy the project file to the temporal dir """
|
||||
|
|
@ -819,3 +843,15 @@ class GS(object):
|
|||
# This is why we just use os.makedirs
|
||||
os.makedirs(lib_name, exist_ok=True)
|
||||
return lib_name
|
||||
|
||||
@staticmethod
|
||||
@contextmanager
|
||||
def create_file(name, bin=False):
|
||||
os.makedirs(os.path.dirname(name), exist_ok=True)
|
||||
with open(name, 'wb' if bin else 'w') as f:
|
||||
yield f
|
||||
|
||||
@staticmethod
|
||||
def write_to_file(content, name):
|
||||
with GS.create_file(name, bin=isinstance(content, bytes)) as f:
|
||||
f.write(content)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,177 @@
|
|||
# -*- 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 .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'
|
||||
|
||||
|
||||
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':
|
||||
files.append(os.path.join(out_dir, GS.pcb_fname))
|
||||
elif s == 'schematic':
|
||||
files.extend(GS.sch.file_names_variant(out_dir))
|
||||
else:
|
||||
files.extend(GS.copy_project_names(GS.pcb_file))
|
||||
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):
|
||||
# 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':
|
||||
GS.check_pcb()
|
||||
self.save_pcb(out_dir)
|
||||
elif s == 'schematic':
|
||||
GS.check_sch()
|
||||
self.save_sch(out_dir)
|
||||
else:
|
||||
GS.check_sch()
|
||||
GS.check_pcb()
|
||||
GS.check_pro()
|
||||
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')
|
||||
f.write(f' <kicanvas-embed controls="{self.controls}"{controlslist}>\n')
|
||||
for s in self.source:
|
||||
if s == 'pcb':
|
||||
source = GS.pcb_fname
|
||||
elif s == 'schematic':
|
||||
source = GS.sch_fname
|
||||
else:
|
||||
source = GS.pro_fname
|
||||
f.write(f' <kicanvas-source src="{source}"></kicanvas-source>\n')
|
||||
f.write(' </kicanvas-embed>\n')
|
||||
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.
|
||||
"""
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._category = ['PCB/docs', 'Schematic/docs']
|
||||
self._both_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 """
|
||||
|
||||
@staticmethod
|
||||
def get_conf_examples(name, layers):
|
||||
# TODO: implement
|
||||
outs = []
|
||||
return outs
|
||||
|
||||
def get_navigate_targets(self, out_dir):
|
||||
return (self.options.get_navigate_targets(out_dir), None)
|
||||
# [os.path.join(GS.get_resource_path('kicanvas'), 'images', 'icon.svg')]
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
# Example KiBot config file
|
||||
kibot:
|
||||
version: 1
|
||||
|
||||
outputs:
|
||||
- name: 'KiCanvas'
|
||||
comment: "Example of KiCanvas export"
|
||||
type: kicanvas
|
||||
dir: KiCanvas
|
||||
options:
|
||||
source: ['schematic', 'pcb', 'project']
|
||||
overlay: false
|
||||
# local_script: false
|
||||
# source: ['schematic', 'pcb', 'project', 'pp']
|
||||
# source: pp
|
||||
Loading…
Reference in New Issue