581 lines
24 KiB
Python
581 lines
24 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Copyright (c) 2020-2023 Salvador E. Tropea
|
|
# Copyright (c) 2020-2023 Instituto Nacional de Tecnología Industrial
|
|
# License: GPL-3.0
|
|
# Project: KiBot (formerly KiPlot)
|
|
"""
|
|
Dependencies:
|
|
- from: RSVG
|
|
role: Create PNG, JPG and BMP images
|
|
- from: ImageMagick
|
|
role: Create JPG and BMP images
|
|
- from: LXML
|
|
role: mandatory
|
|
- name: numpy
|
|
python_module: true
|
|
debian: python3-numpy
|
|
arch: python-numpy
|
|
downloader: python
|
|
role: Automatically adjust SVG margin
|
|
"""
|
|
import os
|
|
import subprocess
|
|
from tempfile import NamedTemporaryFile
|
|
# Here we import the whole module to make monkeypatch work
|
|
from .error import KiPlotConfigurationError
|
|
from .kiplot import load_sch, get_board_comps_data
|
|
from .misc import (PCBDRAW_ERR, PCB_MAT_COLORS, PCB_FINISH_COLORS, SOLDER_COLORS, SILK_COLORS, W_PCBDRAW)
|
|
from .gs import GS
|
|
from .layer import Layer
|
|
from .optionable import Optionable
|
|
from .out_base import VariantOptions, PcbMargin
|
|
from .macros import macros, document, output_class # noqa: F401
|
|
from . import log
|
|
|
|
|
|
logger = log.get_logger()
|
|
|
|
|
|
def mm2ki(val: float) -> int:
|
|
return int(val * 1000000)
|
|
|
|
|
|
def pcbdraw_warnings(tag, msg):
|
|
logger.warning('{}({}) {}'.format(W_PCBDRAW, tag, msg))
|
|
|
|
|
|
def _get_tmp_name(ext):
|
|
with NamedTemporaryFile(mode='w', suffix=ext, delete=False) as f:
|
|
f.close()
|
|
return f.name
|
|
|
|
|
|
def _run_command(cmd):
|
|
logger.debug('Executing: '+GS.pasteable_cmd(cmd))
|
|
try:
|
|
cmd_output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
|
|
except subprocess.CalledProcessError as e:
|
|
logger.error('Failed to run %s, error %d', cmd[0], e.returncode)
|
|
if e.output:
|
|
logger.debug('Output from command: '+e.output.decode())
|
|
exit(PCBDRAW_ERR)
|
|
out = cmd_output.decode()
|
|
if out.strip():
|
|
logger.debug('Output from command:\n'+out)
|
|
|
|
|
|
class PcbDrawStyle(Optionable):
|
|
def __init__(self):
|
|
super().__init__()
|
|
with document:
|
|
self.copper = "#285e3a"
|
|
""" *Color for the copper zones (covered by solder mask) """
|
|
self.board = "#208b47"
|
|
""" *Color for the board without copper (covered by solder mask) """
|
|
self.silk = "#d5dce4"
|
|
""" *Color for the silk screen """
|
|
self.pads = "#8b898c"
|
|
""" *Color for the exposed pads (metal finish) """
|
|
self.outline = "#000000"
|
|
""" *Color for the outline """
|
|
self.clad = "#cabb3e"
|
|
""" *Color for the PCB core (not covered by solder mask) """
|
|
self.vcut = "#bf2600"
|
|
""" Color for the V-CUTS """
|
|
self.highlight_on_top = False
|
|
""" Highlight over the component (not under) """
|
|
self.highlight_style = "stroke:none;fill:#ff0000;opacity:0.5;"
|
|
""" SVG code for the highlight style """
|
|
self.highlight_padding = 1.5
|
|
""" [0,1000] How much the highlight extends around the component [mm] """
|
|
|
|
def config(self, parent):
|
|
# Apply global defaults
|
|
# PCB Material
|
|
if GS.global_pcb_material is not None:
|
|
material = GS.global_pcb_material.lower()
|
|
for mat, color in PCB_MAT_COLORS.items():
|
|
if mat in material:
|
|
self.clad = "#"+color
|
|
break
|
|
# Solder mask
|
|
if parent.bottom:
|
|
name = GS.global_solder_mask_color_bottom or GS.global_solder_mask_color
|
|
else:
|
|
name = GS.global_solder_mask_color_top or GS.global_solder_mask_color
|
|
if name and name.lower() in SOLDER_COLORS:
|
|
(self.copper, self.board) = SOLDER_COLORS[name.lower()]
|
|
# Silk screen
|
|
if parent.bottom:
|
|
name = GS.global_silk_screen_color_bottom or GS.global_silk_screen_color
|
|
else:
|
|
name = GS.global_silk_screen_color_top or GS.global_silk_screen_color
|
|
if name and name.lower() in SILK_COLORS:
|
|
self.silk = "#"+SILK_COLORS[name.lower()]
|
|
# PCB Finish
|
|
if GS.global_pcb_finish is not None:
|
|
name = GS.global_pcb_finish.lower()
|
|
for nm, color in PCB_FINISH_COLORS.items():
|
|
if nm in name:
|
|
self.pads = "#"+color
|
|
break
|
|
super().config(parent)
|
|
self.validate_colors(['board', 'copper', 'board', 'silk', 'pads', 'outline', 'clad', 'vcut'])
|
|
# Not implemented but required
|
|
self.highlight_offset = 0
|
|
|
|
def to_dict(self):
|
|
return {k.replace('_', '-'): v for k, v in self.get_attrs_gen()}
|
|
|
|
|
|
class PcbDrawRemap(Optionable):
|
|
""" This class accepts a free form dict.
|
|
No validation is done. """
|
|
def __init__(self):
|
|
super().__init__()
|
|
|
|
def config(self, parent):
|
|
pass
|
|
|
|
|
|
class PcbDrawResistorRemap(Optionable):
|
|
""" Reference -> New value """
|
|
def __init__(self):
|
|
super().__init__()
|
|
with document:
|
|
self.ref = ''
|
|
""" *Reference for the resistor to change """
|
|
self.reference = None
|
|
""" {ref} """
|
|
self.val = ''
|
|
""" *Value to use for `ref` """
|
|
self.value = None
|
|
""" {val} """
|
|
|
|
def config(self, parent):
|
|
super().config(parent)
|
|
if not self.ref or not self.val:
|
|
raise KiPlotConfigurationError("The resistors remapping must specify a `ref` and a `val`")
|
|
|
|
|
|
class PcbDrawRemapComponents(Optionable):
|
|
""" Reference -> Library + Footprint """
|
|
def __init__(self):
|
|
super().__init__()
|
|
with document:
|
|
self.ref = ''
|
|
""" *Reference for the component to change """
|
|
self.reference = None
|
|
""" {ref} """
|
|
self.lib = ''
|
|
""" *Library to use """
|
|
self.library = None
|
|
""" {lib} """
|
|
self.comp = ''
|
|
""" *Component to use (from `lib`) """
|
|
self.component = None
|
|
""" {comp} """
|
|
|
|
def config(self, parent):
|
|
super().config(parent)
|
|
if not self.ref or not self.lib or not self.comp:
|
|
raise KiPlotConfigurationError("The component remapping must specify a `ref`, a `lib` and a `comp`")
|
|
|
|
|
|
class PcbDrawOptions(VariantOptions):
|
|
def __init__(self):
|
|
with document:
|
|
self.style = PcbDrawStyle
|
|
""" *[string|dict] PCB style (colors). An internal name, the name of a JSON file or the style options """
|
|
self.libs = Optionable
|
|
""" [list(string)=[]] List of libraries """
|
|
self.placeholder = False
|
|
""" Show placeholder for missing components """
|
|
self.remap = PcbDrawRemap
|
|
""" [dict|None] (DEPRECATED) Replacements for PCB references using specified components (lib:component).
|
|
Use `remap_components` instead """
|
|
self.remap_components = PcbDrawRemapComponents
|
|
""" [list(dict)] Replacements for PCB references using specified components.
|
|
Replaces `remap` with type check """
|
|
self.no_drillholes = False
|
|
""" Do not make holes transparent """
|
|
self.bottom = False
|
|
""" *Render the bottom side of the board (default is top side) """
|
|
self.mirror = False
|
|
""" *Mirror the board """
|
|
self.highlight = Optionable
|
|
""" [list(string)=[]] List of components to highlight. Filter expansion is also allowed here,
|
|
see `show_components` """
|
|
self.show_components = Optionable
|
|
""" *[list(string)|string=none] [none,all] List of components to draw, can be also a string for none or all.
|
|
The default is none.
|
|
There two ways of using this option, please consult the `add_to_variant` option.
|
|
You can use `_kf(FILTER)` as an element in the list to get all the components that pass the filter.
|
|
You can even use `_kf(FILTER1;FILTER2)` to concatenate filters """
|
|
self.add_to_variant = True
|
|
""" The `show_components` list is added to the list of components indicated by the variant (fitted and not
|
|
excluded).
|
|
This is the old behavior, but isn't intuitive because the `show_components` meaning changes when a variant
|
|
is used. In this mode you should avoid using `show_components` and variants.
|
|
To get a more coherent behavior disable this option, and `none` will always be `none`.
|
|
Also `all` will be what the variant says """
|
|
self.vcuts = False
|
|
""" Render V-CUTS on the `vcuts_layer` layer """
|
|
self.vcuts_layer = 'Cmts.User'
|
|
""" Layer to render the V-CUTS, only used when `vcuts` is enabled.
|
|
Note that any other content from this layer will be included """
|
|
self.warnings = 'visible'
|
|
""" [visible,all,none] Using visible only the warnings about components in the visible side are generated """
|
|
self.dpi = 300
|
|
""" [10,1200] Dots per inch (resolution) of the generated image """
|
|
self.format = 'svg'
|
|
""" *[svg,png,jpg,bmp] Output format. Only used if no `output` is specified """
|
|
self.output = GS.def_global_output
|
|
""" *Name for the generated file """
|
|
self.margin = PcbMargin
|
|
""" [number|dict] Margin around the generated image [mm].
|
|
Using a number the margin is the same in the four directions """
|
|
self.outline_width = 0.15
|
|
""" [0,10] Width of the trace to draw the PCB border [mm].
|
|
Note this also affects the drill holes """
|
|
self.show_solderpaste = True
|
|
""" Show the solder paste layers """
|
|
self.resistor_remap = PcbDrawResistorRemap
|
|
""" [list(dict)] List of resistors to be remapped. You can change the value of the resistors here """
|
|
self.resistor_flip = Optionable
|
|
""" [string|list(string)=''] List of resistors to flip its bands """
|
|
self.size_detection = 'kicad_edge'
|
|
""" [kicad_edge,kicad_all,svg_paths] Method used to detect the size of the resulting image.
|
|
The `kicad_edge` method uses the size of the board as reported by KiCad,
|
|
components that extend beyond the PCB limit will be cropped. You can manually
|
|
adjust the margins to make them visible.
|
|
The `kicad_all` method uses the whole size reported by KiCad. Usually includes extra space.
|
|
The `svg_paths` uses all visible drawings in the image. To use this method you
|
|
must install the `numpy` Python module (may not be available in docker images) """
|
|
self.svg_precision = 4
|
|
""" [3,6] Scale factor used to represent 1 mm in the SVG (KiCad 6).
|
|
The value is how much zeros has the multiplier (1 mm = 10 power `svg_precision` units).
|
|
Note that for an A4 paper Firefox 91 and Chrome 105 can't handle more than 5 """
|
|
super().__init__()
|
|
|
|
def config(self, parent):
|
|
self._filters_to_expand = False
|
|
# Pre-parse the bottom option
|
|
if 'bottom' in self._tree:
|
|
bot = self._tree['bottom']
|
|
if isinstance(bot, bool):
|
|
self.bottom = bot
|
|
super().config(parent)
|
|
# Libs
|
|
if isinstance(self.libs, type):
|
|
self.libs = ['KiCAD-base']
|
|
# else:
|
|
# self.libs = ','.join(self.libs)
|
|
# V-CUTS layer
|
|
self._vcuts_layer = Layer.solve(self.vcuts_layer)[0]._id if self.vcuts else 41
|
|
# Highlight
|
|
if isinstance(self.highlight, type):
|
|
self.highlight = None
|
|
else:
|
|
self.highlight = self.solve_kf_filters(self.highlight)
|
|
# Margin
|
|
self.margin = PcbMargin.solve(self.margin)
|
|
# Filter
|
|
if isinstance(self.show_components, type):
|
|
# Default option is 'none'
|
|
self.show_components = None
|
|
elif isinstance(self.show_components, str):
|
|
if self.show_components == 'none':
|
|
self.show_components = None
|
|
else: # self.show_components == 'all'
|
|
# Empty list: means we don't filter
|
|
self.show_components = []
|
|
else: # A list
|
|
self.show_components = self.solve_kf_filters(self.show_components)
|
|
# Resistors remap/flip
|
|
if isinstance(self.resistor_remap, type):
|
|
self.resistor_remap = []
|
|
self.resistor_flip = Optionable.force_list(self.resistor_flip)
|
|
# Remap
|
|
self._remap = {}
|
|
if isinstance(self.remap, PcbDrawRemap):
|
|
for ref, v in self.remap._tree.items():
|
|
if not isinstance(v, str):
|
|
raise KiPlotConfigurationError("Wrong PcbDraw remap, must be `ref: lib:component` ({}: {})".format(ref, v))
|
|
lib_comp = v.split(':')
|
|
if len(lib_comp) == 2:
|
|
self._remap[ref] = tuple(lib_comp)
|
|
else:
|
|
raise KiPlotConfigurationError("Wrong PcbDraw remap, must be `ref: lib:component` ({}: {})".format(ref, v))
|
|
if isinstance(self.remap_components, list):
|
|
for mapping in self.remap_components:
|
|
self._remap[mapping.ref] = (mapping.lib, mapping.comp)
|
|
# Style
|
|
if isinstance(self.style, type):
|
|
# Apply the global defaults
|
|
style = PcbDrawStyle()
|
|
style.config(self)
|
|
self.style = style.to_dict()
|
|
elif isinstance(self.style, PcbDrawStyle):
|
|
self.style = self.style.to_dict()
|
|
self._expand_id = 'bottom' if self.bottom else 'top'
|
|
self._expand_ext = self.format
|
|
|
|
def setup_renderer(self, components, active_components, bottom, name):
|
|
super().setup_renderer(components, active_components)
|
|
self.add_to_variant = False
|
|
self.bottom = bottom
|
|
self.output = name
|
|
if not self.show_components:
|
|
self.show_components = None
|
|
return self.expand_filename_both(name, is_sch=False)
|
|
|
|
def save_renderer_options(self):
|
|
""" Save the current renderer settings """
|
|
super().save_renderer_options()
|
|
self.old_bottom = self.bottom
|
|
self.old_add_to_variant = self.add_to_variant
|
|
self.old_output = self.output
|
|
|
|
def restore_renderer_options(self):
|
|
""" Restore the renderer settings """
|
|
super().restore_renderer_options()
|
|
self.bottom = self.old_bottom
|
|
self.add_to_variant = self.old_add_to_variant
|
|
self.output = self.old_output
|
|
|
|
def expand_filtered_components(self, components):
|
|
""" Expands references to filters in show_components """
|
|
if not components or not self._filters_to_expand:
|
|
return components
|
|
new_list = []
|
|
if self._comps:
|
|
all_comps = self._comps
|
|
else:
|
|
load_sch()
|
|
all_comps = GS.sch.get_components()
|
|
get_board_comps_data(all_comps)
|
|
# Scan the list to show
|
|
for c in components:
|
|
if isinstance(c, str):
|
|
# A reference, just add it
|
|
new_list.append(c)
|
|
continue
|
|
# A filter, add its results
|
|
ext_list = []
|
|
for ac in all_comps:
|
|
if c.filter(ac):
|
|
ext_list.append(ac.ref)
|
|
new_list += ext_list
|
|
return new_list
|
|
|
|
def get_targets(self, out_dir):
|
|
return [self._parent.expand_filename(out_dir, self.output)]
|
|
|
|
def build_plot_components(self):
|
|
from .PcbDraw.plot import PlotComponents, ResistorValue
|
|
remapping = self._remap
|
|
|
|
def remapping_fun(ref, lib, name):
|
|
if ref in remapping:
|
|
remapped_lib, remapped_name = remapping[ref]
|
|
if name.endswith('.back'):
|
|
return remapped_lib, remapped_name + '.back'
|
|
else:
|
|
return remapped_lib, remapped_name
|
|
return lib, name
|
|
|
|
resistor_values = {}
|
|
for mapping in self.resistor_remap:
|
|
resistor_values[mapping.ref] = ResistorValue(value=mapping.val)
|
|
for ref in self.resistor_flip:
|
|
field = resistor_values.get(ref, ResistorValue())
|
|
field.flip_bands = True
|
|
resistor_values[ref] = field
|
|
|
|
plot_components = PlotComponents(remapping=remapping_fun,
|
|
resistor_values=resistor_values,
|
|
no_warn_back=self.warnings == 'visible')
|
|
|
|
filter_set = None
|
|
show_components = self.expand_filtered_components(self.show_components)
|
|
if self._comps:
|
|
# A variant is applied, filter the DNF components
|
|
all_comps = set(self.get_fitted_refs())
|
|
if self.add_to_variant:
|
|
# Old behavior: components from the variant + show_components
|
|
all_comps.update(show_components)
|
|
filter_set = all_comps
|
|
else:
|
|
# Something more coherent
|
|
if show_components:
|
|
# The user supplied a list of components
|
|
# Use only the valid ones, but only if fitted
|
|
filter_set = set(show_components).intersection(all_comps)
|
|
else:
|
|
# Empty list means all, but here is all fitted
|
|
filter_set = all_comps
|
|
else:
|
|
# No variant applied
|
|
if show_components:
|
|
# The user supplied a list of components
|
|
# Note: if the list is empty this means we don't filter
|
|
filter_set = set(show_components)
|
|
if filter_set is not None:
|
|
logger.debug('List of filtered components: '+str(filter_set))
|
|
plot_components.filter = lambda ref: ref in filter_set
|
|
else:
|
|
logger.debug('Using all components')
|
|
|
|
if self.highlight is not None:
|
|
highlight_set = set(self.expand_filtered_components(self.highlight))
|
|
plot_components.highlight = lambda ref: ref in highlight_set
|
|
return plot_components
|
|
|
|
def create_image(self, name, board):
|
|
self.ensure_tool('LXML')
|
|
from .PcbDraw.plot import PcbPlotter, PlotPaste, PlotPlaceholders, PlotSubstrate, PlotVCuts
|
|
# Select a name and format that PcbDraw can handle
|
|
svg_save_output_name = save_output_name = name
|
|
self.rsvg_command = None
|
|
self.convert_command = None
|
|
# Check we have the tools needed for the output format
|
|
if self.format != 'svg':
|
|
# We need RSVG for anything other than SVG
|
|
self.rsvg_command = self.ensure_tool('RSVG')
|
|
svg_save_output_name = _get_tmp_name('.svg')
|
|
# We need ImageMagick for anything other than SVG and PNG
|
|
if self.format != 'png':
|
|
self.convert_command = self.ensure_tool('ImageMagick')
|
|
save_output_name = _get_tmp_name('.png')
|
|
|
|
try:
|
|
plotter = PcbPlotter(board)
|
|
# Read libs from KiBot resources
|
|
plotter.setup_arbitrary_data_path(GS.get_resource_path('pcbdraw'))
|
|
# Libs indicated by PCBDRAW_LIB_PATH
|
|
plotter.setup_env_data_path()
|
|
# Libs from the user HOME and the system (for pcbdraw)
|
|
plotter.setup_global_data_path()
|
|
logger.debugl(3, 'PcbDraw data path: {}'.format(plotter.data_path))
|
|
plotter.yield_warning = pcbdraw_warnings
|
|
plotter.libs = self.libs
|
|
plotter.render_back = self.bottom
|
|
plotter.mirror = self.mirror
|
|
plotter.margin = self.margin
|
|
plotter.svg_precision = self.svg_precision
|
|
if self.style:
|
|
if isinstance(self.style, str):
|
|
plotter.resolve_style(self.style)
|
|
else:
|
|
plotter.style = self.style
|
|
plotter.plot_plan = [PlotSubstrate(drill_holes=not self.no_drillholes, outline_width=mm2ki(self.outline_width))]
|
|
if self.show_solderpaste:
|
|
plotter.plot_plan.append(PlotPaste())
|
|
if self.vcuts:
|
|
plotter.plot_plan.append(PlotVCuts(layer=self._vcuts_layer))
|
|
# Adjust the show_components option if needed
|
|
if self.add_to_variant:
|
|
# Enable the old behavior
|
|
if self._comps and self.show_components is None:
|
|
# A variant automatically adds their components
|
|
# So `none` becomes `all`
|
|
self.show_components = []
|
|
if self.show_components is not None:
|
|
plotter.plot_plan.append(self.build_plot_components())
|
|
if self.placeholder:
|
|
plotter.plot_plan.append(PlotPlaceholders())
|
|
plotter.compute_bbox = self.size_detection == 'svg_paths'
|
|
# Make sure we can use svgpathtools
|
|
if plotter.compute_bbox:
|
|
self.ensure_tool('numpy')
|
|
plotter.kicad_bb_only_edge = self.size_detection == 'kicad_edge'
|
|
|
|
image = plotter.plot()
|
|
# Most errors are reported as RuntimeError
|
|
# When the PCB can't be loaded we get IOError
|
|
# When the SVG contains errors we get SyntaxError
|
|
except (RuntimeError, SyntaxError, IOError) as e:
|
|
GS.exit_with_error('PcbDraw error: '+str(e), PCBDRAW_ERR)
|
|
|
|
# Save the result
|
|
logger.debug('Saving output to '+svg_save_output_name)
|
|
image.write(svg_save_output_name)
|
|
# Do we need to convert to PNG?
|
|
if self.rsvg_command:
|
|
logger.debug('Converting {} -> {}'.format(svg_save_output_name, save_output_name))
|
|
cmd = [self.rsvg_command, '--dpi-x', str(self.dpi), '--dpi-y', str(self.dpi),
|
|
'--output', save_output_name, '--format', 'png', svg_save_output_name]
|
|
_run_command(cmd)
|
|
os.remove(svg_save_output_name)
|
|
# Do we need to convert the saved file? (JPG/BMP)
|
|
if self.convert_command is not None:
|
|
logger.debug('Converting {} -> {}'.format(save_output_name, name))
|
|
cmd = [self.convert_command, save_output_name]
|
|
if self.format == 'jpg':
|
|
cmd += ['-quality', '85%']
|
|
cmd.append(name)
|
|
_run_command(cmd)
|
|
os.remove(save_output_name)
|
|
|
|
def run(self, name):
|
|
super().run(name)
|
|
# Apply any variant
|
|
self.filter_pcb_components(do_3D=True)
|
|
# Create the image
|
|
self.create_image(name, GS.board)
|
|
# Undo the variant
|
|
self.unfilter_pcb_components(do_3D=True)
|
|
|
|
|
|
@output_class
|
|
class PcbDraw(BaseOutput): # noqa: F821
|
|
""" PcbDraw - Beautiful 2D PCB render
|
|
Exports the PCB as a 2D model (SVG, PNG or JPG).
|
|
Uses configurable colors.
|
|
Can also render the components if the 2D models are available.
|
|
Note that this output is fast for simple PCBs, but becomes useless for huge ones.
|
|
You can easily create very complex PCBs using the `panelize` output.
|
|
In this case you can use other outputs, like `render_3d`, which are slow for small
|
|
PCBs but can handle big ones """
|
|
def __init__(self):
|
|
super().__init__()
|
|
with document:
|
|
self.options = PcbDrawOptions
|
|
""" *[dict] Options for the `pcbdraw` output """
|
|
self._category = 'PCB/docs'
|
|
|
|
def get_dependencies(self):
|
|
files = super().get_dependencies()
|
|
if isinstance(self.options.style, str) and os.path.isfile(self.options.style):
|
|
files.append(self.options.style)
|
|
return files
|
|
|
|
def get_renderer_options(self):
|
|
""" Where are the options for this output when used as a 'renderer' """
|
|
return self.options
|
|
|
|
@staticmethod
|
|
def get_conf_examples(name, layers):
|
|
outs = []
|
|
for la in layers:
|
|
is_top = la.is_top()
|
|
is_bottom = la.is_bottom()
|
|
if not is_top and not is_bottom:
|
|
continue
|
|
id = 'top' if is_top else 'bottom'
|
|
for style in ['jlcpcb-green-enig', 'set-blue-enig', 'set-red-hasl']:
|
|
style_2 = style.replace('-', '_')
|
|
for fmt in ['svg', 'png', 'jpg']:
|
|
gb = {}
|
|
gb['name'] = 'basic_{}_{}_{}_{}'.format(name, fmt, style_2, id)
|
|
gb['comment'] = 'PCB 2D render in {} format, using {} style'.format(fmt.upper(), style)
|
|
gb['type'] = name
|
|
gb['dir'] = os.path.join('PCB', '2D_render', style_2)
|
|
ops = {'style': style, 'format': fmt}
|
|
if is_bottom:
|
|
ops['bottom'] = True
|
|
gb['options'] = ops
|
|
outs.append(gb)
|
|
return outs
|