parent
b4c1531e10
commit
27b26feb88
|
|
@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
- Default global `dir` option.
|
- Default global `dir` option.
|
||||||
- Pattern to expand the variant name: %V
|
- Pattern to expand the variant name: %V
|
||||||
- PCB PDF Print: mechanism to change the block title. (#102)
|
- PCB PDF Print: mechanism to change the block title. (#102)
|
||||||
|
- 3D view render
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Internal BoM: now components with different Tolerance, Voltage, Current
|
- Internal BoM: now components with different Tolerance, Voltage, Current
|
||||||
|
|
|
||||||
37
README.md
37
README.md
|
|
@ -58,6 +58,7 @@ For example, it's common that you might want for each board rev:
|
||||||
* Fab docs for the assembler, including the BoM (Bill of Materials), costs spreadsheet and board view
|
* Fab docs for the assembler, including the BoM (Bill of Materials), costs spreadsheet and board view
|
||||||
* Pick and place files
|
* Pick and place files
|
||||||
* PCB 3D model in STEP format
|
* PCB 3D model in STEP format
|
||||||
|
* PCB 3D render in PNG format
|
||||||
|
|
||||||
You want to do this in a one-touch way, and make sure everything you need to
|
You want to do this in a one-touch way, and make sure everything you need to
|
||||||
do so it securely saved in version control, not on the back of an old
|
do so it securely saved in version control, not on the back of an old
|
||||||
|
|
@ -1396,6 +1397,42 @@ Next time you need this list just use an alias, like this:
|
||||||
- `width_adjust`: [number=0] This width factor is intended to compensate PS printers/plotters that do not strictly obey line width settings.
|
- `width_adjust`: [number=0] This width factor is intended to compensate PS printers/plotters that do not strictly obey line width settings.
|
||||||
Only used to plot pads and tracks.
|
Only used to plot pads and tracks.
|
||||||
|
|
||||||
|
* 3D render of the PCB
|
||||||
|
* Type: `render_3d`
|
||||||
|
* Description: Exports the image generated by KiCad's 3D viewer.
|
||||||
|
* Valid keys:
|
||||||
|
- `comment`: [string=''] A comment for documentation purposes.
|
||||||
|
- `dir`: [string='./'] Output directory for the generated files. If it starts with `+` the rest is concatenated to the default dir.
|
||||||
|
- `name`: [string=''] Used to identify this particular output definition.
|
||||||
|
- `options`: [dict] Options for the `render_3d` output.
|
||||||
|
* Valid keys:
|
||||||
|
- `background1`: [string='#66667F'] First color for the background gradient.
|
||||||
|
- `background2`: [string='#CCCCE5'] Second color for the background gradient.
|
||||||
|
- `board`: [string='#332B16'] Color for the board without copper or solder mask.
|
||||||
|
- `copper`: [string='#B29C00'] Color for the copper.
|
||||||
|
- `dnf_filter`: [string|list(string)=''] 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`: [boolean=true] Downloads missing 3D models from KiCad git. Only applies to models in KISYS3DMOD.
|
||||||
|
- `kicad_3d_url`: [string='https://gitlab.com/kicad/libraries/kicad-packages3D/-/raw/master/'] Base URL for the KiCad 3D models.
|
||||||
|
- `move_x`: [number=0] Steps to move in the X axis, positive is to the right.
|
||||||
|
Just like pressing the right arrow in the 3D viewer.
|
||||||
|
- `move_y`: [number=0] Steps to move in the Y axis, positive is up.
|
||||||
|
Just like pressing the up arrow in the 3D viewer.
|
||||||
|
- `no_smd`: [boolean=false] Used to exclude 3D models for surface mount components.
|
||||||
|
- `no_tht`: [boolean=false] Used to exclude 3D models for through hole components.
|
||||||
|
- `no_virtual`: [boolean=false] Used to exclude 3D models for components with 'virtual' attribute.
|
||||||
|
- `output`: [string='%f-%i%v.%x'] Name for the generated image file (%i='3D_$VIEW' %x='png'). Affected by global options.
|
||||||
|
- `ray_tracing`: [boolean=false] Enable the ray tracing. Much better result, but slow, and you'll need to adjust `wait_rt`.
|
||||||
|
- `silk`: [string='#E5E5E5'] Color for the silk screen.
|
||||||
|
- `solder_mask`: [string='#143324'] Color for the solder mask.
|
||||||
|
- `solder_paste`: [string='#808080'] Color for the solder paste.
|
||||||
|
- `variant`: [string=''] Board variant to apply.
|
||||||
|
- `view`: [string='top'] [top,bottom,front,rear,right,left,z,Z,y,Y,x,X] Point of view.
|
||||||
|
- `wait_ray_tracing`: [number=5] How many seconds we must wait before capturing the ray tracing render.
|
||||||
|
Lamentably KiCad can save an unfinished image. Enlarge it if your image looks partially rendered.
|
||||||
|
- `zoom`: [number=0] Zoom steps. Use positive to enlarge, get closer, and negative to reduce.
|
||||||
|
Same result as using the mouse wheel in the 3D viewer.
|
||||||
|
|
||||||
* Schematic with variant generator
|
* Schematic with variant generator
|
||||||
* Type: `sch_variant`
|
* Type: `sch_variant`
|
||||||
* Description: Creates a copy of the schematic with all the filters and variants applied.
|
* Description: Creates a copy of the schematic with all the filters and variants applied.
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@ For example, it's common that you might want for each board rev:
|
||||||
* Fab docs for the assembler, including the BoM (Bill of Materials), costs spreadsheet and board view
|
* Fab docs for the assembler, including the BoM (Bill of Materials), costs spreadsheet and board view
|
||||||
* Pick and place files
|
* Pick and place files
|
||||||
* PCB 3D model in STEP format
|
* PCB 3D model in STEP format
|
||||||
|
* PCB 3D render in PNG format
|
||||||
|
|
||||||
You want to do this in a one-touch way, and make sure everything you need to
|
You want to do this in a one-touch way, and make sure everything you need to
|
||||||
do so it securely saved in version control, not on the back of an old
|
do so it securely saved in version control, not on the back of an old
|
||||||
|
|
|
||||||
|
|
@ -1021,6 +1021,60 @@ outputs:
|
||||||
width_adjust: 0
|
width_adjust: 0
|
||||||
layers: all
|
layers: all
|
||||||
|
|
||||||
|
# 3D render of the PCB:
|
||||||
|
- name: 'render_3d_example'
|
||||||
|
comment: 'Exports the image generated by KiCad's 3D viewer.'
|
||||||
|
type: 'render_3d'
|
||||||
|
dir: 'Example/render_3d_dir'
|
||||||
|
options:
|
||||||
|
# [string='#66667F'] First color for the background gradient
|
||||||
|
background1: '#66667F'
|
||||||
|
# [string='#CCCCE5'] Second color for the background gradient
|
||||||
|
background2: '#CCCCE5'
|
||||||
|
# [string='#332B16'] Color for the board without copper or solder mask
|
||||||
|
board: '#332B16'
|
||||||
|
# [string='#B29C00'] Color for the copper
|
||||||
|
copper: '#B29C00'
|
||||||
|
# [string|list(string)=''] 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: ''
|
||||||
|
# [boolean=true] Downloads missing 3D models from KiCad git. Only applies to models in KISYS3DMOD
|
||||||
|
download: true
|
||||||
|
# [string='https://gitlab.com/kicad/libraries/kicad-packages3D/-/raw/master/'] Base URL for the KiCad 3D models
|
||||||
|
kicad_3d_url: 'https://gitlab.com/kicad/libraries/kicad-packages3D/-/raw/master/'
|
||||||
|
# [number=0] Steps to move in the X axis, positive is to the right.
|
||||||
|
# Just like pressing the right arrow in the 3D viewer
|
||||||
|
move_x: 0
|
||||||
|
# [number=0] Steps to move in the Y axis, positive is up.
|
||||||
|
# Just like pressing the up arrow in the 3D viewer
|
||||||
|
move_y: 0
|
||||||
|
# [boolean=false] Used to exclude 3D models for surface mount components
|
||||||
|
no_smd: false
|
||||||
|
# [boolean=false] Used to exclude 3D models for through hole components
|
||||||
|
no_tht: false
|
||||||
|
# [boolean=false] Used to exclude 3D models for components with 'virtual' attribute
|
||||||
|
no_virtual: false
|
||||||
|
# [string='%f-%i%v.%x'] Name for the generated image file (%i='3D_$VIEW' %x='png'). Affected by global options
|
||||||
|
output: '%f-%i%v.%x'
|
||||||
|
# [boolean=false] Enable the ray tracing. Much better result, but slow, and you'll need to adjust `wait_rt`
|
||||||
|
ray_tracing: false
|
||||||
|
# [string='#E5E5E5'] Color for the silk screen
|
||||||
|
silk: '#E5E5E5'
|
||||||
|
# [string='#143324'] Color for the solder mask
|
||||||
|
solder_mask: '#143324'
|
||||||
|
# [string='#808080'] Color for the solder paste
|
||||||
|
solder_paste: '#808080'
|
||||||
|
# [string=''] Board variant to apply
|
||||||
|
variant: ''
|
||||||
|
# [string='top'] [top,bottom,front,rear,right,left,z,Z,y,Y,x,X] Point of view
|
||||||
|
view: 'top'
|
||||||
|
# [number=5] How many seconds we must wait before capturing the ray tracing render.
|
||||||
|
# Lamentably KiCad can save an unfinished image. Enlarge it if your image looks partially rendered
|
||||||
|
wait_ray_tracing: 5
|
||||||
|
# [number=0] Zoom steps. Use positive to enlarge, get closer, and negative to reduce.
|
||||||
|
# Same result as using the mouse wheel in the 3D viewer
|
||||||
|
zoom: 0
|
||||||
|
|
||||||
# Schematic with variant generator:
|
# Schematic with variant generator:
|
||||||
# This copy isn't intended for development.
|
# This copy isn't intended for development.
|
||||||
# Is just a tweaked version of the original where you can look at the results.
|
# Is just a tweaked version of the original where you can look at the results.
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ PCBDRAW_ERR = 20
|
||||||
SVG_SCH_PRINT = 21
|
SVG_SCH_PRINT = 21
|
||||||
CORRUPTED_SCH = 22
|
CORRUPTED_SCH = 22
|
||||||
WRONG_INSTALL = 23
|
WRONG_INSTALL = 23
|
||||||
|
RENDER_3D_ERR = 24
|
||||||
error_level_to_name = ['NONE',
|
error_level_to_name = ['NONE',
|
||||||
'INTERNAL_ERROR',
|
'INTERNAL_ERROR',
|
||||||
'WRONG_ARGUMENTS',
|
'WRONG_ARGUMENTS',
|
||||||
|
|
@ -58,13 +59,16 @@ error_level_to_name = ['NONE',
|
||||||
'SVG_SCH_PRINT',
|
'SVG_SCH_PRINT',
|
||||||
'CORRUPTED_SCH',
|
'CORRUPTED_SCH',
|
||||||
'WRONG_INSTALL',
|
'WRONG_INSTALL',
|
||||||
|
'RENDER_3D_ERR',
|
||||||
]
|
]
|
||||||
CMD_EESCHEMA_DO = 'eeschema_do'
|
CMD_EESCHEMA_DO = 'eeschema_do'
|
||||||
URL_EESCHEMA_DO = 'https://github.com/INTI-CMNB/kicad-automation-scripts'
|
URL_EESCHEMA_DO = 'https://github.com/INTI-CMNB/KiAuto'
|
||||||
CMD_PCBNEW_RUN_DRC = 'pcbnew_do'
|
CMD_PCBNEW_RUN_DRC = 'pcbnew_do'
|
||||||
URL_PCBNEW_RUN_DRC = URL_EESCHEMA_DO
|
URL_PCBNEW_RUN_DRC = URL_EESCHEMA_DO
|
||||||
CMD_PCBNEW_PRINT_LAYERS = 'pcbnew_do'
|
CMD_PCBNEW_PRINT_LAYERS = CMD_PCBNEW_RUN_DRC
|
||||||
URL_PCBNEW_PRINT_LAYERS = URL_EESCHEMA_DO
|
URL_PCBNEW_PRINT_LAYERS = URL_EESCHEMA_DO
|
||||||
|
CMD_PCBNEW_3D = CMD_PCBNEW_RUN_DRC
|
||||||
|
URL_PCBNEW_3D = URL_EESCHEMA_DO
|
||||||
CMD_KIBOM = 'KiBOM_CLI.py'
|
CMD_KIBOM = 'KiBOM_CLI.py'
|
||||||
URL_KIBOM = 'https://github.com/INTI-CMNB/KiBoM'
|
URL_KIBOM = 'https://github.com/INTI-CMNB/KiBoM'
|
||||||
CMD_IBOM = 'generate_interactive_bom.py'
|
CMD_IBOM = 'generate_interactive_bom.py'
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ class Optionable(object):
|
||||||
_str_values_re = compile(r"string=.*\] \[([^\]]+)\]")
|
_str_values_re = compile(r"string=.*\] \[([^\]]+)\]")
|
||||||
_num_range_re = compile(r"number=.*\] \[(-?\d+),(-?\d+)\]")
|
_num_range_re = compile(r"number=.*\] \[(-?\d+),(-?\d+)\]")
|
||||||
_default = None
|
_default = None
|
||||||
|
_color_re = re.compile(r"#[A-Fa-f0-9]{6}$")
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._unkown_is_error = False
|
self._unkown_is_error = False
|
||||||
|
|
@ -267,6 +268,15 @@ class Optionable(object):
|
||||||
def get_default(cls):
|
def get_default(cls):
|
||||||
return cls._default
|
return cls._default
|
||||||
|
|
||||||
|
def validate_color(self, name):
|
||||||
|
color = getattr(self, name)
|
||||||
|
if not self._color_re.match(color):
|
||||||
|
raise KiPlotConfigurationError('Invalid color for `{}` use `#rrggbb` with hex digits'.format(name))
|
||||||
|
|
||||||
|
def validate_colors(self, names):
|
||||||
|
for color in names:
|
||||||
|
self.validate_color(color)
|
||||||
|
|
||||||
|
|
||||||
class BaseOptions(Optionable):
|
class BaseOptions(Optionable):
|
||||||
""" A class to validate and hold output options.
|
""" A class to validate and hold output options.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,230 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2020-2021 Salvador E. Tropea
|
||||||
|
# Copyright (c) 2020-2021 Instituto Nacional de Tecnología Industrial
|
||||||
|
# License: GPL-3.0
|
||||||
|
# Project: KiBot (formerly KiPlot)
|
||||||
|
import os
|
||||||
|
import requests
|
||||||
|
import tempfile
|
||||||
|
from tempfile import NamedTemporaryFile
|
||||||
|
from .error import KiPlotConfigurationError
|
||||||
|
from .misc import W_MISS3D, W_FAILDL
|
||||||
|
from .gs import (GS)
|
||||||
|
from .out_base import VariantOptions, BaseOutput
|
||||||
|
from .kicad.config import KiConf
|
||||||
|
from .macros import macros, document # noqa: F401
|
||||||
|
from . import log
|
||||||
|
|
||||||
|
logger = log.get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Base3DOptions(VariantOptions):
|
||||||
|
def __init__(self):
|
||||||
|
with document:
|
||||||
|
self.no_virtual = False
|
||||||
|
""" Used to exclude 3D models for components with 'virtual' attribute """
|
||||||
|
self.download = True
|
||||||
|
""" Downloads missing 3D models from KiCad git. Only applies to models in KISYS3DMOD """
|
||||||
|
self.kicad_3d_url = 'https://gitlab.com/kicad/libraries/kicad-packages3D/-/raw/master/'
|
||||||
|
""" Base URL for the KiCad 3D models """
|
||||||
|
# Temporal dir used to store the downloaded files
|
||||||
|
self._tmp_dir = None
|
||||||
|
super().__init__()
|
||||||
|
self._expand_id = '3D'
|
||||||
|
|
||||||
|
def download_model(self, url, fname):
|
||||||
|
""" Download the 3D model from the provided URL """
|
||||||
|
logger.debug('Downloading `{}`'.format(url))
|
||||||
|
r = requests.get(url, allow_redirects=True)
|
||||||
|
if r.status_code != 200:
|
||||||
|
logger.warning(W_FAILDL+'Failed to download `{}`'.format(url))
|
||||||
|
return None
|
||||||
|
if self._tmp_dir is None:
|
||||||
|
self._tmp_dir = tempfile.mkdtemp()
|
||||||
|
logger.debug('Using `{}` as temporal dir for downloaded files'.format(self._tmp_dir))
|
||||||
|
dest = os.path.join(self._tmp_dir, fname)
|
||||||
|
os.makedirs(os.path.dirname(dest), exist_ok=True)
|
||||||
|
with open(dest, 'wb') as f:
|
||||||
|
f.write(r.content)
|
||||||
|
return dest
|
||||||
|
|
||||||
|
def undo_3d_models_rename(self):
|
||||||
|
""" Restores the file name for any renamed 3D module """
|
||||||
|
for m in GS.board.GetModules():
|
||||||
|
# Get the model references
|
||||||
|
models = m.Models()
|
||||||
|
models_l = []
|
||||||
|
while not models.empty():
|
||||||
|
models_l.append(models.pop())
|
||||||
|
# Fix any changed path
|
||||||
|
replaced = self.undo_3d_models_rep.get(m.GetReference())
|
||||||
|
for i, m3d in enumerate(models_l):
|
||||||
|
if m3d.m_Filename in self.undo_3d_models:
|
||||||
|
m3d.m_Filename = self.undo_3d_models[m3d.m_Filename]
|
||||||
|
if replaced:
|
||||||
|
m3d.m_Filename = replaced[i]
|
||||||
|
# Push the models back
|
||||||
|
for model in models_l:
|
||||||
|
models.push_front(model)
|
||||||
|
|
||||||
|
def replace_models(self, models, new_model, c):
|
||||||
|
""" Changes the 3D model using a provided model """
|
||||||
|
logger.debug('Changing 3D models for '+c.ref)
|
||||||
|
# Get the model references
|
||||||
|
models_l = []
|
||||||
|
while not models.empty():
|
||||||
|
models_l.append(models.pop())
|
||||||
|
# Check if we have more than one model
|
||||||
|
c_models = len(models_l)
|
||||||
|
if c_models > 1:
|
||||||
|
new_model = new_model.split(',')
|
||||||
|
c_replace = len(new_model)
|
||||||
|
if c_models != c_replace:
|
||||||
|
raise KiPlotConfigurationError('Found {} models in component {}, but {} replacements provided'.
|
||||||
|
format(c_models, c, c_replace))
|
||||||
|
else:
|
||||||
|
new_model = [new_model]
|
||||||
|
# Change the models
|
||||||
|
replaced = []
|
||||||
|
for i, m3d in enumerate(models_l):
|
||||||
|
replaced.append(m3d.m_Filename)
|
||||||
|
m3d.m_Filename = new_model[i]
|
||||||
|
self.undo_3d_models_rep[c.ref] = replaced
|
||||||
|
# Push the models back
|
||||||
|
for model in models_l:
|
||||||
|
models.push_front(model)
|
||||||
|
|
||||||
|
def download_models(self):
|
||||||
|
""" Check we have the 3D models.
|
||||||
|
Inform missing models.
|
||||||
|
Try to download the missing models """
|
||||||
|
models_replaced = False
|
||||||
|
# Load KiCad configuration so we can expand the 3D models path
|
||||||
|
KiConf.init(GS.pcb_file)
|
||||||
|
# List of models we already downloaded
|
||||||
|
downloaded = set()
|
||||||
|
self.undo_3d_models = {}
|
||||||
|
# Look for all the footprints
|
||||||
|
for m in GS.board.GetModules():
|
||||||
|
ref = m.GetReference()
|
||||||
|
# Extract the models (the iterator returns copies)
|
||||||
|
models = m.Models()
|
||||||
|
models_l = []
|
||||||
|
while not models.empty():
|
||||||
|
models_l.append(models.pop())
|
||||||
|
# Look for all the 3D models for this footprint
|
||||||
|
for m3d in models_l:
|
||||||
|
full_name = KiConf.expand_env(m3d.m_Filename)
|
||||||
|
if not os.path.isfile(full_name):
|
||||||
|
# Missing 3D model
|
||||||
|
if full_name not in downloaded:
|
||||||
|
logger.warning(W_MISS3D+'Missing 3D model for {}: `{}`'.format(ref, full_name))
|
||||||
|
if self.download and m3d.m_Filename.startswith('${KISYS3DMOD}/'):
|
||||||
|
# This is a model from KiCad, try to download it
|
||||||
|
fname = m3d.m_Filename[14:]
|
||||||
|
replace = None
|
||||||
|
if full_name in downloaded:
|
||||||
|
# Already downloaded
|
||||||
|
replace = os.path.join(self._tmp_dir, fname)
|
||||||
|
else:
|
||||||
|
# Download the model
|
||||||
|
url = self.kicad_3d_url+fname
|
||||||
|
replace = self.download_model(url, fname)
|
||||||
|
if replace:
|
||||||
|
# Successfully downloaded
|
||||||
|
downloaded.add(full_name)
|
||||||
|
self.undo_3d_models[replace] = m3d.m_Filename
|
||||||
|
# If this is a .wrl also download the .step
|
||||||
|
if url.endswith('.wrl'):
|
||||||
|
url = url[:-4]+'.step'
|
||||||
|
fname = fname[:-4]+'.step'
|
||||||
|
self.download_model(url, fname)
|
||||||
|
if replace:
|
||||||
|
m3d.m_Filename = replace
|
||||||
|
models_replaced = True
|
||||||
|
# Push the models back
|
||||||
|
for model in models_l:
|
||||||
|
models.push_front(model)
|
||||||
|
return models_replaced
|
||||||
|
|
||||||
|
def list_models(self):
|
||||||
|
""" Get the list of 3D models """
|
||||||
|
# Load KiCad configuration so we can expand the 3D models path
|
||||||
|
KiConf.init(GS.pcb_file)
|
||||||
|
models = set()
|
||||||
|
# Look for all the footprints
|
||||||
|
for m in GS.board.GetModules():
|
||||||
|
# Look for all the 3D models for this footprint
|
||||||
|
for m3d in m.Models():
|
||||||
|
full_name = KiConf.expand_env(m3d.m_Filename)
|
||||||
|
if os.path.isfile(full_name):
|
||||||
|
models.add(full_name)
|
||||||
|
return list(models)
|
||||||
|
|
||||||
|
def save_board(self, dir):
|
||||||
|
""" Save the PCB to a temporal file """
|
||||||
|
with NamedTemporaryFile(mode='w', suffix='.kicad_pcb', delete=False, dir=dir) as f:
|
||||||
|
fname = f.name
|
||||||
|
logger.debug('Storing modified PCB to `{}`'.format(fname))
|
||||||
|
GS.board.Save(fname)
|
||||||
|
return fname
|
||||||
|
|
||||||
|
def filter_components(self, dir):
|
||||||
|
self.undo_3d_models_rep = {}
|
||||||
|
if not self._comps:
|
||||||
|
# No variant/filter to apply
|
||||||
|
if self.download_models():
|
||||||
|
# Some missing components found and we downloaded them
|
||||||
|
# Save the fixed board
|
||||||
|
ret = self.save_board(dir)
|
||||||
|
# Undo the changes
|
||||||
|
self.undo_3d_models_rename()
|
||||||
|
return ret
|
||||||
|
return GS.pcb_file
|
||||||
|
comps_hash = self.get_refs_hash()
|
||||||
|
# Remove the 3D models for not fitted components
|
||||||
|
rem_models = []
|
||||||
|
for m in GS.board.GetModules():
|
||||||
|
ref = m.GetReference()
|
||||||
|
c = comps_hash.get(ref, None)
|
||||||
|
if c:
|
||||||
|
# The filter/variant knows about this component
|
||||||
|
models = m.Models()
|
||||||
|
if c.included and not c.fitted:
|
||||||
|
# Not fitted, remove the 3D model
|
||||||
|
rem_m_models = []
|
||||||
|
while not models.empty():
|
||||||
|
rem_m_models.append(models.pop())
|
||||||
|
rem_models.append(rem_m_models)
|
||||||
|
else:
|
||||||
|
# Fitted
|
||||||
|
new_model = c.get_field_value(GS.global_3D_model_field)
|
||||||
|
if new_model:
|
||||||
|
# We will change the 3D model
|
||||||
|
self.replace_models(models, new_model, c)
|
||||||
|
self.download_models()
|
||||||
|
fname = self.save_board(dir)
|
||||||
|
self.undo_3d_models_rename()
|
||||||
|
# Undo the removing
|
||||||
|
for m in GS.board.GetModules():
|
||||||
|
ref = m.GetReference()
|
||||||
|
c = comps_hash.get(ref, None)
|
||||||
|
if c and c.included and not c.fitted:
|
||||||
|
models = m.Models()
|
||||||
|
restore = rem_models.pop(0)
|
||||||
|
for model in restore:
|
||||||
|
models.push_front(model)
|
||||||
|
return fname
|
||||||
|
|
||||||
|
def get_targets(self, out_dir):
|
||||||
|
return [self._parent.expand_filename(out_dir, self.output)]
|
||||||
|
|
||||||
|
|
||||||
|
class Base3D(BaseOutput):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
def get_dependencies(self):
|
||||||
|
files = super().get_dependencies()
|
||||||
|
files.extend(self.options.list_models())
|
||||||
|
return files
|
||||||
|
|
@ -4,14 +4,12 @@
|
||||||
# License: GPL-3.0
|
# License: GPL-3.0
|
||||||
# Project: KiBot (formerly KiPlot)
|
# Project: KiBot (formerly KiPlot)
|
||||||
import os
|
import os
|
||||||
import re
|
|
||||||
from tempfile import (NamedTemporaryFile)
|
from tempfile import (NamedTemporaryFile)
|
||||||
# Here we import the whole module to make monkeypatch work
|
# Here we import the whole module to make monkeypatch work
|
||||||
import subprocess
|
import subprocess
|
||||||
import shutil
|
import shutil
|
||||||
from .misc import PCBDRAW, PCBDRAW_ERR, URL_PCBDRAW, W_AMBLIST, W_UNRETOOL, W_USESVG2, W_USEIMAGICK
|
from .misc import PCBDRAW, PCBDRAW_ERR, URL_PCBDRAW, W_AMBLIST, W_UNRETOOL, W_USESVG2, W_USEIMAGICK
|
||||||
from .kiplot import check_script
|
from .kiplot import check_script
|
||||||
from .error import KiPlotConfigurationError
|
|
||||||
from .gs import (GS)
|
from .gs import (GS)
|
||||||
from .optionable import Optionable
|
from .optionable import Optionable
|
||||||
from .out_base import VariantOptions
|
from .out_base import VariantOptions
|
||||||
|
|
@ -24,8 +22,6 @@ CONVERT = 'convert'
|
||||||
|
|
||||||
|
|
||||||
class PcbDrawStyle(Optionable):
|
class PcbDrawStyle(Optionable):
|
||||||
_color_re = re.compile(r"#[A-Fa-f0-9]{6}$")
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
with document:
|
with document:
|
||||||
|
|
@ -50,21 +46,9 @@ class PcbDrawStyle(Optionable):
|
||||||
self.highlight_padding = 1.5
|
self.highlight_padding = 1.5
|
||||||
""" [0,1000] how much the highlight extends around the component [mm] """
|
""" [0,1000] how much the highlight extends around the component [mm] """
|
||||||
|
|
||||||
def validate_color(self, name):
|
|
||||||
color = getattr(self, name)
|
|
||||||
if not self._color_re.match(color):
|
|
||||||
raise KiPlotConfigurationError('Invalid color for `{}` use `#rrggbb` with hex digits'.format(name))
|
|
||||||
|
|
||||||
def config(self, parent):
|
def config(self, parent):
|
||||||
super().config(parent)
|
super().config(parent)
|
||||||
self.validate_color('board')
|
self.validate_colors(['board', 'copper', 'board', 'silk', 'pads', 'outline', 'clad', 'vcut'])
|
||||||
self.validate_color('copper')
|
|
||||||
self.validate_color('board')
|
|
||||||
self.validate_color('silk')
|
|
||||||
self.validate_color('pads')
|
|
||||||
self.validate_color('outline')
|
|
||||||
self.validate_color('clad')
|
|
||||||
self.validate_color('vcut')
|
|
||||||
# Not implemented but required
|
# Not implemented but required
|
||||||
self.highlight_offset = 0
|
self.highlight_offset = 0
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,133 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright (c) 2021 Salvador E. Tropea
|
||||||
|
# Copyright (c) 2021 Instituto Nacional de Tecnología Industrial
|
||||||
|
# License: GPL-3.0
|
||||||
|
# Project: KiBot (formerly KiPlot)
|
||||||
|
import os
|
||||||
|
from shutil import rmtree
|
||||||
|
from .misc import CMD_PCBNEW_3D, URL_PCBNEW_3D, RENDER_3D_ERR
|
||||||
|
from .gs import (GS)
|
||||||
|
from .kiplot import check_script, exec_with_retry, add_extra_options
|
||||||
|
from .out_base_3d import Base3DOptions, Base3D
|
||||||
|
from .macros import macros, document, output_class # noqa: F401
|
||||||
|
from . import log
|
||||||
|
|
||||||
|
logger = log.get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Render3DOptions(Base3DOptions):
|
||||||
|
_colors = {'background1': 'bg_color_1',
|
||||||
|
'background2': 'bg_color_2',
|
||||||
|
'copper': 'copper_color',
|
||||||
|
'board': 'board_color',
|
||||||
|
'silk': 'silk_color',
|
||||||
|
'solder_mask': 'sm_color',
|
||||||
|
'solder_paste': 'sp_color'}
|
||||||
|
_views = {'top': 'z', 'bottom': 'Z', 'front': 'y', 'rear': 'Y', 'right': 'x', 'left': 'X'}
|
||||||
|
_rviews = {v: k for k, v in _views.items()}
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
with document:
|
||||||
|
self.output = GS.def_global_output
|
||||||
|
""" Name for the generated image file (%i='3D_$VIEW' %x='png') """
|
||||||
|
self.no_tht = False
|
||||||
|
""" Used to exclude 3D models for through hole components """
|
||||||
|
self.no_smd = False
|
||||||
|
""" Used to exclude 3D models for surface mount components """
|
||||||
|
self.background1 = "#66667F"
|
||||||
|
""" First color for the background gradient """
|
||||||
|
self.background2 = "#CCCCE5"
|
||||||
|
""" Second color for the background gradient """
|
||||||
|
self.board = "#332B16"
|
||||||
|
""" Color for the board without copper or solder mask """
|
||||||
|
self.copper = "#B29C00"
|
||||||
|
""" Color for the copper """
|
||||||
|
self.silk = "#E5E5E5"
|
||||||
|
""" Color for the silk screen """
|
||||||
|
self.solder_mask = "#143324"
|
||||||
|
""" Color for the solder mask """
|
||||||
|
self.solder_paste = "#808080"
|
||||||
|
""" Color for the solder paste """
|
||||||
|
self.move_x = 0
|
||||||
|
""" Steps to move in the X axis, positive is to the right.
|
||||||
|
Just like pressing the right arrow in the 3D viewer """
|
||||||
|
self.move_y = 0
|
||||||
|
""" Steps to move in the Y axis, positive is up.
|
||||||
|
Just like pressing the up arrow in the 3D viewer """
|
||||||
|
self.ray_tracing = False
|
||||||
|
""" Enable the ray tracing. Much better result, but slow, and you'll need to adjust `wait_rt` """
|
||||||
|
self.wait_ray_tracing = 5
|
||||||
|
""" How many seconds we must wait before capturing the ray tracing render.
|
||||||
|
Lamentably KiCad can save an unfinished image. Enlarge it if your image looks partially rendered """
|
||||||
|
self.view = 'top'
|
||||||
|
""" [top,bottom,front,rear,right,left,z,Z,y,Y,x,X] Point of view """
|
||||||
|
self.zoom = 0
|
||||||
|
""" Zoom steps. Use positive to enlarge, get closer, and negative to reduce.
|
||||||
|
Same result as using the mouse wheel in the 3D viewer """
|
||||||
|
super().__init__()
|
||||||
|
self._expand_ext = 'png'
|
||||||
|
|
||||||
|
def config(self, parent):
|
||||||
|
super().config(parent)
|
||||||
|
self.validate_colors(self._colors.keys())
|
||||||
|
view = self._views.get(self.view, None)
|
||||||
|
if view is not None:
|
||||||
|
self.view = view
|
||||||
|
self._expand_id += '_'+self._rviews.get(self.view)
|
||||||
|
|
||||||
|
def run(self, output):
|
||||||
|
super().run(output)
|
||||||
|
check_script(CMD_PCBNEW_3D, URL_PCBNEW_3D, '1.5.11')
|
||||||
|
# Base command with overwrite
|
||||||
|
cmd = [CMD_PCBNEW_3D, '3d_view', '--output_name', output]
|
||||||
|
# Add user options
|
||||||
|
if not self.no_virtual:
|
||||||
|
cmd.append('--virtual')
|
||||||
|
if self.no_tht:
|
||||||
|
cmd.append('--no_tht')
|
||||||
|
if self.no_smd:
|
||||||
|
cmd.append('--no_smd')
|
||||||
|
for color, option in self._colors.items():
|
||||||
|
cmd.extend(['--'+option, getattr(self, color)])
|
||||||
|
if self.move_x:
|
||||||
|
cmd.extend(['--move_x', str(self.move_x)])
|
||||||
|
if self.move_y:
|
||||||
|
cmd.extend(['--move_y', str(self.move_y)])
|
||||||
|
if self.zoom:
|
||||||
|
cmd.extend(['--zoom', str(self.zoom)])
|
||||||
|
if self.wait_ray_tracing != 5:
|
||||||
|
cmd.extend(['--wait_rt', str(self.wait_ray_tracing)])
|
||||||
|
if self.ray_tracing:
|
||||||
|
cmd.append('--ray_tracing')
|
||||||
|
if self.view != 'z':
|
||||||
|
cmd.extend(['--view', self.view])
|
||||||
|
# The board
|
||||||
|
board_name = self.filter_components(GS.pcb_dir)
|
||||||
|
cmd.extend([board_name, os.path.dirname(output)])
|
||||||
|
cmd, video_remove = add_extra_options(cmd)
|
||||||
|
# Execute it
|
||||||
|
ret = exec_with_retry(cmd)
|
||||||
|
# Remove the temporal PCB
|
||||||
|
if board_name != GS.pcb_file:
|
||||||
|
os.remove(board_name)
|
||||||
|
# Remove the downloaded 3D models
|
||||||
|
if self._tmp_dir:
|
||||||
|
rmtree(self._tmp_dir)
|
||||||
|
if ret:
|
||||||
|
logger.error(CMD_PCBNEW_3D+' returned %d', ret)
|
||||||
|
exit(RENDER_3D_ERR)
|
||||||
|
if video_remove:
|
||||||
|
video_name = os.path.join(GS.out_dir, 'pcbnew_3d_view_screencast.ogv')
|
||||||
|
if os.path.isfile(video_name):
|
||||||
|
os.remove(video_name)
|
||||||
|
|
||||||
|
|
||||||
|
@output_class
|
||||||
|
class Render_3D(Base3D): # noqa: F821
|
||||||
|
""" 3D render of the PCB
|
||||||
|
Exports the image generated by KiCad's 3D viewer. """
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
with document:
|
||||||
|
self.options = Render3DOptions
|
||||||
|
""" [dict] Options for the `render_3d` output """
|
||||||
|
|
@ -5,23 +5,19 @@
|
||||||
# Project: KiBot (formerly KiPlot)
|
# Project: KiBot (formerly KiPlot)
|
||||||
import re
|
import re
|
||||||
import os
|
import os
|
||||||
import requests
|
|
||||||
import tempfile
|
|
||||||
from subprocess import (check_output, STDOUT, CalledProcessError)
|
from subprocess import (check_output, STDOUT, CalledProcessError)
|
||||||
from tempfile import NamedTemporaryFile
|
|
||||||
from shutil import rmtree
|
from shutil import rmtree
|
||||||
from .error import KiPlotConfigurationError
|
from .error import KiPlotConfigurationError
|
||||||
from .misc import KICAD2STEP, KICAD2STEP_ERR, W_MISS3D, W_FAILDL
|
from .misc import KICAD2STEP, KICAD2STEP_ERR
|
||||||
from .gs import (GS)
|
from .gs import (GS)
|
||||||
from .out_base import VariantOptions
|
from .out_base_3d import Base3DOptions, Base3D
|
||||||
from .kicad.config import KiConf
|
|
||||||
from .macros import macros, document, output_class # noqa: F401
|
from .macros import macros, document, output_class # noqa: F401
|
||||||
from . import log
|
from . import log
|
||||||
|
|
||||||
logger = log.get_logger(__name__)
|
logger = log.get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class STEPOptions(VariantOptions):
|
class STEPOptions(Base3DOptions):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
with document:
|
with document:
|
||||||
self.metric_units = True
|
self.metric_units = True
|
||||||
|
|
@ -30,20 +26,13 @@ class STEPOptions(VariantOptions):
|
||||||
""" Determines the coordinates origin. Using grid the coordinates are the same as you have in the design sheet.
|
""" Determines the coordinates origin. Using grid the coordinates are the same as you have in the design sheet.
|
||||||
The drill option uses the auxiliary reference defined by the user.
|
The drill option uses the auxiliary reference defined by the user.
|
||||||
You can define any other origin using the format 'X,Y', i.e. '3.2,-10' """
|
You can define any other origin using the format 'X,Y', i.e. '3.2,-10' """
|
||||||
self.no_virtual = False
|
|
||||||
""" Used to exclude 3D models for components with 'virtual' attribute """
|
|
||||||
self.min_distance = -1
|
self.min_distance = -1
|
||||||
""" The minimum distance between points to treat them as separate ones (-1 is KiCad default: 0.01 mm) """
|
""" The minimum distance between points to treat them as separate ones (-1 is KiCad default: 0.01 mm) """
|
||||||
self.output = GS.def_global_output
|
self.output = GS.def_global_output
|
||||||
""" Name for the generated STEP file (%i='3D' %x='step') """
|
""" Name for the generated STEP file (%i='3D' %x='step') """
|
||||||
self.download = True
|
|
||||||
""" Downloads missing 3D models from KiCad git. Only applies to models in KISYS3DMOD """
|
|
||||||
self.kicad_3d_url = 'https://gitlab.com/kicad/libraries/kicad-packages3D/-/raw/master/'
|
|
||||||
""" Base URL for the KiCad 3D models """
|
|
||||||
# Temporal dir used to store the downloaded files
|
# Temporal dir used to store the downloaded files
|
||||||
self._tmp_dir = None
|
self._tmp_dir = None
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._expand_id = '3D'
|
|
||||||
self._expand_ext = 'step'
|
self._expand_ext = 'step'
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
@ -56,193 +45,6 @@ class STEPOptions(VariantOptions):
|
||||||
raise KiPlotConfigurationError('Origin must be `grid` or `drill` or `X,Y`')
|
raise KiPlotConfigurationError('Origin must be `grid` or `drill` or `X,Y`')
|
||||||
self._origin = val
|
self._origin = val
|
||||||
|
|
||||||
def download_model(self, url, fname):
|
|
||||||
""" Download the 3D model from the provided URL """
|
|
||||||
logger.debug('Downloading `{}`'.format(url))
|
|
||||||
r = requests.get(url, allow_redirects=True)
|
|
||||||
if r.status_code != 200:
|
|
||||||
logger.warning(W_FAILDL+'Failed to download `{}`'.format(url))
|
|
||||||
return None
|
|
||||||
if self._tmp_dir is None:
|
|
||||||
self._tmp_dir = tempfile.mkdtemp()
|
|
||||||
logger.debug('Using `{}` as temporal dir for downloaded files'.format(self._tmp_dir))
|
|
||||||
dest = os.path.join(self._tmp_dir, fname)
|
|
||||||
os.makedirs(os.path.dirname(dest), exist_ok=True)
|
|
||||||
with open(dest, 'wb') as f:
|
|
||||||
f.write(r.content)
|
|
||||||
return dest
|
|
||||||
|
|
||||||
def undo_3d_models_rename(self):
|
|
||||||
""" Restores the file name for any renamed 3D module """
|
|
||||||
for m in GS.board.GetModules():
|
|
||||||
# Get the model references
|
|
||||||
models = m.Models()
|
|
||||||
models_l = []
|
|
||||||
while not models.empty():
|
|
||||||
models_l.append(models.pop())
|
|
||||||
# Fix any changed path
|
|
||||||
replaced = self.undo_3d_models_rep.get(m.GetReference())
|
|
||||||
for i, m3d in enumerate(models_l):
|
|
||||||
if m3d.m_Filename in self.undo_3d_models:
|
|
||||||
m3d.m_Filename = self.undo_3d_models[m3d.m_Filename]
|
|
||||||
if replaced:
|
|
||||||
m3d.m_Filename = replaced[i]
|
|
||||||
# Push the models back
|
|
||||||
for model in models_l:
|
|
||||||
models.push_front(model)
|
|
||||||
|
|
||||||
def replace_models(self, models, new_model, c):
|
|
||||||
""" Changes the 3D model using a provided model """
|
|
||||||
logger.debug('Changing 3D models for '+c.ref)
|
|
||||||
# Get the model references
|
|
||||||
models_l = []
|
|
||||||
while not models.empty():
|
|
||||||
models_l.append(models.pop())
|
|
||||||
# Check if we have more than one model
|
|
||||||
c_models = len(models_l)
|
|
||||||
if c_models > 1:
|
|
||||||
new_model = new_model.split(',')
|
|
||||||
c_replace = len(new_model)
|
|
||||||
if c_models != c_replace:
|
|
||||||
raise KiPlotConfigurationError('Found {} models in component {}, but {} replacements provided'.
|
|
||||||
format(c_models, c, c_replace))
|
|
||||||
else:
|
|
||||||
new_model = [new_model]
|
|
||||||
# Change the models
|
|
||||||
replaced = []
|
|
||||||
for i, m3d in enumerate(models_l):
|
|
||||||
replaced.append(m3d.m_Filename)
|
|
||||||
m3d.m_Filename = new_model[i]
|
|
||||||
self.undo_3d_models_rep[c.ref] = replaced
|
|
||||||
# Push the models back
|
|
||||||
for model in models_l:
|
|
||||||
models.push_front(model)
|
|
||||||
|
|
||||||
def download_models(self):
|
|
||||||
""" Check we have the 3D models.
|
|
||||||
Inform missing models.
|
|
||||||
Try to download the missing models """
|
|
||||||
models_replaced = False
|
|
||||||
# Load KiCad configuration so we can expand the 3D models path
|
|
||||||
KiConf.init(GS.pcb_file)
|
|
||||||
# List of models we already downloaded
|
|
||||||
downloaded = set()
|
|
||||||
self.undo_3d_models = {}
|
|
||||||
# Look for all the footprints
|
|
||||||
for m in GS.board.GetModules():
|
|
||||||
ref = m.GetReference()
|
|
||||||
# Extract the models (the iterator returns copies)
|
|
||||||
models = m.Models()
|
|
||||||
models_l = []
|
|
||||||
while not models.empty():
|
|
||||||
models_l.append(models.pop())
|
|
||||||
# Look for all the 3D models for this footprint
|
|
||||||
for m3d in models_l:
|
|
||||||
full_name = KiConf.expand_env(m3d.m_Filename)
|
|
||||||
if not os.path.isfile(full_name):
|
|
||||||
# Missing 3D model
|
|
||||||
if full_name not in downloaded:
|
|
||||||
logger.warning(W_MISS3D+'Missing 3D model for {}: `{}`'.format(ref, full_name))
|
|
||||||
if self.download and m3d.m_Filename.startswith('${KISYS3DMOD}/'):
|
|
||||||
# This is a model from KiCad, try to download it
|
|
||||||
fname = m3d.m_Filename[14:]
|
|
||||||
replace = None
|
|
||||||
if full_name in downloaded:
|
|
||||||
# Already downloaded
|
|
||||||
replace = os.path.join(self._tmp_dir, fname)
|
|
||||||
else:
|
|
||||||
# Download the model
|
|
||||||
url = self.kicad_3d_url+fname
|
|
||||||
replace = self.download_model(url, fname)
|
|
||||||
if replace:
|
|
||||||
# Successfully downloaded
|
|
||||||
downloaded.add(full_name)
|
|
||||||
self.undo_3d_models[replace] = m3d.m_Filename
|
|
||||||
# If this is a .wrl also download the .step
|
|
||||||
if url.endswith('.wrl'):
|
|
||||||
url = url[:-4]+'.step'
|
|
||||||
fname = fname[:-4]+'.step'
|
|
||||||
self.download_model(url, fname)
|
|
||||||
if replace:
|
|
||||||
m3d.m_Filename = replace
|
|
||||||
models_replaced = True
|
|
||||||
# Push the models back
|
|
||||||
for model in models_l:
|
|
||||||
models.push_front(model)
|
|
||||||
return models_replaced
|
|
||||||
|
|
||||||
def list_models(self):
|
|
||||||
""" Get the list of 3D models """
|
|
||||||
# Load KiCad configuration so we can expand the 3D models path
|
|
||||||
KiConf.init(GS.pcb_file)
|
|
||||||
models = set()
|
|
||||||
# Look for all the footprints
|
|
||||||
for m in GS.board.GetModules():
|
|
||||||
# Look for all the 3D models for this footprint
|
|
||||||
for m3d in m.Models():
|
|
||||||
full_name = KiConf.expand_env(m3d.m_Filename)
|
|
||||||
if os.path.isfile(full_name):
|
|
||||||
models.add(full_name)
|
|
||||||
return list(models)
|
|
||||||
|
|
||||||
def save_board(self, dir):
|
|
||||||
""" Save the PCB to a temporal file """
|
|
||||||
with NamedTemporaryFile(mode='w', suffix='.kicad_pcb', delete=False, dir=dir) as f:
|
|
||||||
fname = f.name
|
|
||||||
logger.debug('Storing modified PCB to `{}`'.format(fname))
|
|
||||||
GS.board.Save(fname)
|
|
||||||
return fname
|
|
||||||
|
|
||||||
def filter_components(self, dir):
|
|
||||||
self.undo_3d_models_rep = {}
|
|
||||||
if not self._comps:
|
|
||||||
# No variant/filter to apply
|
|
||||||
if self.download_models():
|
|
||||||
# Some missing components found and we downloaded them
|
|
||||||
# Save the fixed board
|
|
||||||
ret = self.save_board(dir)
|
|
||||||
# Undo the changes
|
|
||||||
self.undo_3d_models_rename()
|
|
||||||
return ret
|
|
||||||
return GS.pcb_file
|
|
||||||
comps_hash = self.get_refs_hash()
|
|
||||||
# Remove the 3D models for not fitted components
|
|
||||||
rem_models = []
|
|
||||||
for m in GS.board.GetModules():
|
|
||||||
ref = m.GetReference()
|
|
||||||
c = comps_hash.get(ref, None)
|
|
||||||
if c:
|
|
||||||
# The filter/variant knows about this component
|
|
||||||
models = m.Models()
|
|
||||||
if c.included and not c.fitted:
|
|
||||||
# Not fitted, remove the 3D model
|
|
||||||
rem_m_models = []
|
|
||||||
while not models.empty():
|
|
||||||
rem_m_models.append(models.pop())
|
|
||||||
rem_models.append(rem_m_models)
|
|
||||||
else:
|
|
||||||
# Fitted
|
|
||||||
new_model = c.get_field_value(GS.global_3D_model_field)
|
|
||||||
if new_model:
|
|
||||||
# We will change the 3D model
|
|
||||||
self.replace_models(models, new_model, c)
|
|
||||||
self.download_models()
|
|
||||||
fname = self.save_board(dir)
|
|
||||||
self.undo_3d_models_rename()
|
|
||||||
# Undo the removing
|
|
||||||
for m in GS.board.GetModules():
|
|
||||||
ref = m.GetReference()
|
|
||||||
c = comps_hash.get(ref, None)
|
|
||||||
if c and c.included and not c.fitted:
|
|
||||||
models = m.Models()
|
|
||||||
restore = rem_models.pop(0)
|
|
||||||
for model in restore:
|
|
||||||
models.push_front(model)
|
|
||||||
return fname
|
|
||||||
|
|
||||||
def get_targets(self, out_dir):
|
|
||||||
return [self._parent.expand_filename(out_dir, self.output)]
|
|
||||||
|
|
||||||
def run(self, output):
|
def run(self, output):
|
||||||
super().run(output)
|
super().run(output)
|
||||||
# Make units explicit
|
# Make units explicit
|
||||||
|
|
@ -286,7 +88,7 @@ class STEPOptions(VariantOptions):
|
||||||
|
|
||||||
|
|
||||||
@output_class
|
@output_class
|
||||||
class STEP(BaseOutput): # noqa: F821
|
class STEP(Base3D): # noqa: F821
|
||||||
""" STEP (ISO 10303-21 Clear Text Encoding of the Exchange Structure)
|
""" STEP (ISO 10303-21 Clear Text Encoding of the Exchange Structure)
|
||||||
Exports the PCB as a 3D model.
|
Exports the PCB as a 3D model.
|
||||||
This is the most common 3D format for exchange purposes.
|
This is the most common 3D format for exchange purposes.
|
||||||
|
|
@ -296,8 +98,3 @@ class STEP(BaseOutput): # noqa: F821
|
||||||
with document:
|
with document:
|
||||||
self.options = STEPOptions
|
self.options = STEPOptions
|
||||||
""" [dict] Options for the `step` output """
|
""" [dict] Options for the `step` output """
|
||||||
|
|
||||||
def get_dependencies(self):
|
|
||||||
files = super().get_dependencies()
|
|
||||||
files.extend(self.options.list_models())
|
|
||||||
return files
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue