[Blender export][Added] Support for pcb2blender 2.7

- Solder mask and silk screen color
- PCB finish
- PCB thickness
This commit is contained in:
Salvador E. Tropea 2023-10-11 13:08:10 -03:00
parent 299e06ae3e
commit 3cd644d19d
10 changed files with 186 additions and 45 deletions

View File

@ -25,7 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `forced_name` option to force the name displayed at the top left corner - `forced_name` option to force the name displayed at the top left corner
(#470) (#470)
- Blender export: - Blender export:
- Support for pcb2blender v2.6 (Blender 3.5.1) - Support for pcb2blender v2.6/2.7 (Blender 3.5.1/3.6)
- `auto_camera_z_axis_factor`: used to control the default camera distance - `auto_camera_z_axis_factor`: used to control the default camera distance
- Options to create simple animations: - Options to create simple animations:
- PoV `steps`: to create rotation angle increments - PoV `steps`: to create rotation angle increments

View File

@ -169,7 +169,7 @@ outputs:
type: 'render' type: 'render'
# [string|dict] Options to export the PCB to Blender. # [string|dict] Options to export the PCB to Blender.
# You can also specify the name of the output that generates the PCB3D file. # You can also specify the name of the output that generates the PCB3D file.
# See the `PCB2Blender_2_1` and `PCB2Blender_2_1_haschtl` templates # See the `PCB2Blender_2_1`, `PCB2Blender_2_7` and `PCB2Blender_2_1_haschtl` templates
pcb3d: pcb3d:
# [string|list(string)='_none'] Name of the filter to mark components as not fitted. # [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 # A short-cut to use for simple cases where a variant is an overkill
@ -211,8 +211,8 @@ outputs:
solder_paste_for_populated: true solder_paste_for_populated: true
# [string=''] Board variant to apply # [string=''] Board variant to apply
variant: '' variant: ''
# [string='2.1'] [2.1,2.1_haschtl] Variant of the format used # [string='2.7'] [2.1,2.1_haschtl,2.7] Variant of the format used
version: '2.1' version: '2.7'
# Options to configure how Blender imports the PCB. # Options to configure how Blender imports the PCB.
# The default values are good for most cases # The default values are good for most cases
pcb_import: pcb_import:
@ -2176,12 +2176,14 @@ outputs:
# [list(string)|string=all] [none,all] List of components to include in the pads list, # [list(string)|string=all] [none,all] List of components to include in the pads list,
# can be also a string for `none` or `all`. The default is `all` # can be also a string for `none` or `all`. The default is `all`
show_components: all show_components: all
# [boolean=false] Create a JSON file containing the board stackup # [boolean=false] Create a file containing the board stackup
stackup_create: false stackup_create: false
# [string='.'] Directory for the stackup file # [string='.'] Directory for the stackup file. Use 'layers' for 2.7+
stackup_dir: '.' stackup_dir: '.'
# [string='board.yaml'] Name for the stackup file # [string='board.yaml'] Name for the stackup file. Use 'stackup' for 2.7+
stackup_file: 'board.yaml' stackup_file: 'board.yaml'
# [string='JSON'] [JSON,BIN] Format for the stackup file. Use 'BIN' for 2.7+
stackup_format: 'JSON'
# [string='bounds'] File name for the sub-PCBs bounds # [string='bounds'] File name for the sub-PCBs bounds
sub_boards_bounds_file: 'bounds' sub_boards_bounds_file: 'bounds'
# [boolean=true] Extract sub-PCBs and their Z axis position # [boolean=true] Extract sub-PCBs and their Z axis position

View File

@ -272,7 +272,8 @@ Here is a list of currently defined templates and their outputs / groups:
- `P-Ban_stencil <https://www.p-ban.com/>`__: same as **P-Ban**, but - `P-Ban_stencil <https://www.p-ban.com/>`__: same as **P-Ban**, but
also generates gerbers for *F.Paste* and *B.Paste* layers. also generates gerbers for *F.Paste* and *B.Paste* layers.
- `PCB2Blender_2_1 <https://github.com/30350n/pcb2blender>`__ - `PCB2Blender_2_1 <https://github.com/30350n/pcb2blender>`__: Exports the PCB in a format that can be imported by the
pcb2blender tool v2.1 or newer.
- **_PCB2Blender_layers_2_1**: The layers in SVG format. Disabled by default. - **_PCB2Blender_layers_2_1**: The layers in SVG format. Disabled by default.
- **_PCB2Blender_vrml_2_1**: The VRML for the board. Disabled by default. - **_PCB2Blender_vrml_2_1**: The VRML for the board. Disabled by default.
@ -280,7 +281,11 @@ Here is a list of currently defined templates and their outputs / groups:
- **_PCB2Blender_2_1**: The PCB3D file. Is enabled and creates the other files. - **_PCB2Blender_2_1**: The PCB3D file. Is enabled and creates the other files.
- **_PCB2Blender_elements_2_1**: **_PCB2Blender_tools_2_1** + **_PCB2Blender_layers_2_1** + **_PCB2Blender_vrml_2_1** - **_PCB2Blender_elements_2_1**: **_PCB2Blender_tools_2_1** + **_PCB2Blender_layers_2_1** + **_PCB2Blender_vrml_2_1**
- `PCB2Blender_2_1_haschtl <https://github.com/haschtl/pcb2blender>`__ - `PCB2Blender_2_7 <https://github.com/30350n/pcb2blender>`__: Similar to **PCB2Blender_2_1**, but for v2.7 or newer.
The content is the same, just using *2_1* instead of *2_7*
- `PCB2Blender_2_1_haschtl <https://github.com/haschtl/pcb2blender>`__: Similar to **PCB2Blender_2_1**, but for the
experimental **haschtl** fork.
- Imports **PCB2Blender_2_1** and disables **_PCB2Blender_2_1** - Imports **PCB2Blender_2_1** and disables **_PCB2Blender_2_1**
- **_PCB2Blender_tools_2_1_haschtl**: Pads, bounds and stack-up information. Disabled by default. - **_PCB2Blender_tools_2_1_haschtl**: Pads, bounds and stack-up information. Disabled by default.

View File

@ -33,7 +33,7 @@ Parameters:
- **pcb3d** :index:`: <pair: output - blender_export - options; pcb3d>` [string|dict] Options to export the PCB to Blender. - **pcb3d** :index:`: <pair: output - blender_export - options; pcb3d>` [string|dict] Options to export the PCB to Blender.
You can also specify the name of the output that generates the PCB3D file. You can also specify the name of the output that generates the PCB3D file.
See the `PCB2Blender_2_1` and `PCB2Blender_2_1_haschtl` templates. See the `PCB2Blender_2_1`, `PCB2Blender_2_7` and `PCB2Blender_2_1_haschtl` templates.
- Valid keys: - Valid keys:
@ -67,7 +67,7 @@ Parameters:
- ``solder_paste_for_populated`` :index:`: <pair: output - blender_export - options - pcb3d; solder_paste_for_populated>` [boolean=true] Add solder paste only for the populated components. - ``solder_paste_for_populated`` :index:`: <pair: output - blender_export - options - pcb3d; solder_paste_for_populated>` [boolean=true] Add solder paste only for the populated components.
Populated components are the ones listed in `show_components`. Populated components are the ones listed in `show_components`.
- ``variant`` :index:`: <pair: output - blender_export - options - pcb3d; variant>` [string=''] Board variant to apply. - ``variant`` :index:`: <pair: output - blender_export - options - pcb3d; variant>` [string=''] Board variant to apply.
- ``version`` :index:`: <pair: output - blender_export - options - pcb3d; version>` [string='2.1'] [2.1,2.1_haschtl] Variant of the format used. - ``version`` :index:`: <pair: output - blender_export - options - pcb3d; version>` [string='2.7'] [2.1,2.1_haschtl,2.7] Variant of the format used.
- **point_of_view** :index:`: <pair: output - blender_export - options; point_of_view>` [dict|list(dict)] How the object is viewed by the camera. - **point_of_view** :index:`: <pair: output - blender_export - options; point_of_view>` [dict|list(dict)] How the object is viewed by the camera.

View File

@ -45,9 +45,10 @@ Parameters:
- ``pre_transform`` :index:`: <pair: output - pcb2blender_tools - options; pre_transform>` [string|list(string)='_none'] Name of the filter to transform fields before applying other filters. - ``pre_transform`` :index:`: <pair: output - pcb2blender_tools - 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. A short-cut to use for simple cases where a variant is an overkill.
- ``stackup_create`` :index:`: <pair: output - pcb2blender_tools - options; stackup_create>` [boolean=false] Create a JSON file containing the board stackup. - ``stackup_create`` :index:`: <pair: output - pcb2blender_tools - options; stackup_create>` [boolean=false] Create a file containing the board stackup.
- ``stackup_dir`` :index:`: <pair: output - pcb2blender_tools - options; stackup_dir>` [string='.'] Directory for the stackup file. - ``stackup_dir`` :index:`: <pair: output - pcb2blender_tools - options; stackup_dir>` [string='.'] Directory for the stackup file. Use 'layers' for 2.7+.
- ``stackup_file`` :index:`: <pair: output - pcb2blender_tools - options; stackup_file>` [string='board.yaml'] Name for the stackup file. - ``stackup_file`` :index:`: <pair: output - pcb2blender_tools - options; stackup_file>` [string='board.yaml'] Name for the stackup file. Use 'stackup' for 2.7+.
- ``stackup_format`` :index:`: <pair: output - pcb2blender_tools - options; stackup_format>` [string='JSON'] [JSON,BIN] Format for the stackup file. Use 'BIN' for 2.7+.
- ``sub_boards_bounds_file`` :index:`: <pair: output - pcb2blender_tools - options; sub_boards_bounds_file>` [string='bounds'] File name for the sub-PCBs bounds. - ``sub_boards_bounds_file`` :index:`: <pair: output - pcb2blender_tools - options; sub_boards_bounds_file>` [string='bounds'] File name for the sub-PCBs bounds.
- ``sub_boards_create`` :index:`: <pair: output - pcb2blender_tools - options; sub_boards_create>` [boolean=true] Extract sub-PCBs and their Z axis position. - ``sub_boards_create`` :index:`: <pair: output - pcb2blender_tools - options; sub_boards_create>` [boolean=true] Extract sub-PCBs and their Z axis position.
- ``sub_boards_dir`` :index:`: <pair: output - pcb2blender_tools - options; sub_boards_dir>` [string='boards'] Directory for the boards definitions. - ``sub_boards_dir`` :index:`: <pair: output - pcb2blender_tools - options; sub_boards_dir>` [string='boards'] Directory for the boards definitions.

View File

@ -451,12 +451,12 @@ class Optionable(object):
for color in names: for color in names:
self.validate_color(color) self.validate_color(color)
def parse_one_color(self, color): def parse_one_color(self, color, scale=1/255.0):
res = self._color_re_component.findall(color) res = self._color_re_component.findall(color)
alpha = 1.0 alpha = 1.0
if len(res) > 3: if len(res) > 3:
alpha = int(res[3], 16)/255.0 alpha = int(res[3], 16)*scale
return (int(res[0], 16)/255.0, int(res[1], 16)/255.0, int(res[2], 16)/255.0, alpha) return (int(res[0], 16)*scale, int(res[1], 16)*scale, int(res[2], 16)*scale, alpha)
def color_to_rgb(self, color): def color_to_rgb(self, color):
index = 4 if len(color) > 4 else 0 index = 4 if len(color) > 4 else 0

View File

@ -248,8 +248,8 @@ class PCB3DExportOptions(Base3DOptionsWithHL):
with document: with document:
self.output = GS.def_global_output self.output = GS.def_global_output
""" Name for the generated PCB3D file (%i='blender_export' %x='pcb3d') """ """ Name for the generated PCB3D file (%i='blender_export' %x='pcb3d') """
self.version = '2.1' self.version = '2.7'
""" [2.1,2.1_haschtl] Variant of the format used """ """ [2.1,2.1_haschtl,2.7] Variant of the format used """
self.solder_paste_for_populated = True self.solder_paste_for_populated = True
""" Add solder paste only for the populated components. """ Add solder paste only for the populated components.
Populated components are the ones listed in `show_components` """ Populated components are the ones listed in `show_components` """
@ -307,7 +307,7 @@ class Blender_ExportOptions(BaseOptions):
self.pcb3d = PCB3DExportOptions self.pcb3d = PCB3DExportOptions
""" *[string|dict] Options to export the PCB to Blender. """ *[string|dict] Options to export the PCB to Blender.
You can also specify the name of the output that generates the PCB3D file. You can also specify the name of the output that generates the PCB3D file.
See the `PCB2Blender_2_1` and `PCB2Blender_2_1_haschtl` templates """ See the `PCB2Blender_2_1`, `PCB2Blender_2_7` and `PCB2Blender_2_1_haschtl` templates """
self.pcb_import = PCB2BlenderOptions self.pcb_import = PCB2BlenderOptions
""" Options to configure how Blender imports the PCB. """ Options to configure how Blender imports the PCB.
The default values are good for most cases """ The default values are good for most cases """
@ -461,11 +461,19 @@ class Blender_ExportOptions(BaseOptions):
configure_and_run(tree, out_dir, ' - Creating SVG for layers ...') configure_and_run(tree, out_dir, ' - Creating SVG for layers ...')
def create_pads(self, dest_dir): def create_pads(self, dest_dir):
options = {'stackup_create': False}
if self.pcb3d.version == '2.1_haschtl':
options['stackup_create'] = True
elif self.pcb3d.version == '2.7':
options['stackup_create'] = True
options['stackup_file'] = 'stackup'
options['stackup_dir'] = 'layers'
options['stackup_format'] = 'BIN'
tree = {'name': '_temporal_pcb3d_tools', tree = {'name': '_temporal_pcb3d_tools',
'type': 'pcb2blender_tools', 'type': 'pcb2blender_tools',
'comment': 'Internally created for the PCB3D', 'comment': 'Internally created for the PCB3D',
'dir': dest_dir, 'dir': dest_dir,
'options': {'stackup_create': self.pcb3d.version == '2.1_haschtl'}} 'options': options}
if self.pcb3d.solder_paste_for_populated: if self.pcb3d.solder_paste_for_populated:
sc = 'all' sc = 'all'
if not self.pcb3d._show_all_components: if not self.pcb3d._show_all_components:

View File

@ -5,6 +5,7 @@
# Project: KiBot (formerly KiPlot) # Project: KiBot (formerly KiPlot)
# Some code is adapted from: https://github.com/30350n/pcb2blender # Some code is adapted from: https://github.com/30350n/pcb2blender
from dataclasses import dataclass, field from dataclasses import dataclass, field
from enum import IntEnum
import json import json
import os import os
import re import re
@ -37,6 +38,34 @@ class BoardDef:
stacked_boards: List[StackedBoard] = field(default_factory=list) stacked_boards: List[StackedBoard] = field(default_factory=list)
class KiCadColor(IntEnum):
CUSTOM = 0
GREEN = 1
RED = 2
BLUE = 3
PURPLE = 4
BLACK = 5
WHITE = 6
YELLOW = 7
class SurfaceFinish(IntEnum):
HASL = 0
ENIG = 1
NONE = 2
SURFACE_FINISH_MAP = {
"ENIG": SurfaceFinish.ENIG,
"ENEPIG": SurfaceFinish.ENIG,
"Hard gold": SurfaceFinish.ENIG,
"ImAu": SurfaceFinish.ENIG,
"Immersion Gold": SurfaceFinish.ENIG,
"Immersion Au": SurfaceFinish.ENIG,
"HT_OSP": SurfaceFinish.NONE,
"OSP": SurfaceFinish.NONE}
def sanitized(name): def sanitized(name):
""" Replace character that aren't alphabetic by _ """ """ Replace character that aren't alphabetic by _ """
return re.sub(r"[\W]+", "_", name) return re.sub(r"[\W]+", "_", name)
@ -59,11 +88,13 @@ class PCB2Blender_ToolsOptions(VariantOptions):
self.pads_info_dir = 'pads' self.pads_info_dir = 'pads'
""" Sub-directory where the pads info files are stored """ """ Sub-directory where the pads info files are stored """
self.stackup_create = False self.stackup_create = False
""" Create a JSON file containing the board stackup """ """ Create a file containing the board stackup """
self.stackup_file = 'board.yaml' self.stackup_file = 'board.yaml'
""" Name for the stackup file """ """ Name for the stackup file. Use 'stackup' for 2.7+ """
self.stackup_dir = '.' self.stackup_dir = '.'
""" Directory for the stackup file """ """ Directory for the stackup file. Use 'layers' for 2.7+ """
self.stackup_format = 'JSON'
""" [JSON,BIN] Format for the stackup file. Use 'BIN' for 2.7+ """
self.sub_boards_create = True self.sub_boards_create = True
""" Extract sub-PCBs and their Z axis position """ """ Extract sub-PCBs and their Z axis position """
self.sub_boards_dir = 'boards' self.sub_boards_dir = 'boards'
@ -146,30 +177,53 @@ class PCB2Blender_ToolsOptions(VariantOptions):
pad.GetDrillShape(), pad.GetDrillShape(),
*map(GS.to_mm, pad.GetDrillSize()))) *map(GS.to_mm, pad.GetDrillSize())))
def parse_kicad_color(self, string):
if string[0] == "#":
return KiCadColor.CUSTOM, self.parse_one_color(string, scale=1)[:3]
else:
return KiCadColor[string.upper()], (0, 0, 0)
def do_stackup(self, dir_name): def do_stackup(self, dir_name):
if not self.stackup_create or (not GS.global_pcb_finish and not GS.stackup): if not self.stackup_create or (not GS.global_pcb_finish and not GS.stackup):
return return
dir_name = os.path.join(dir_name, self.stackup_dir) dir_name = os.path.join(dir_name, self.stackup_dir)
os.makedirs(dir_name, exist_ok=True) os.makedirs(dir_name, exist_ok=True)
fname = os.path.join(dir_name, self.stackup_file) fname = os.path.join(dir_name, self.stackup_file)
# Create the board_info if self.stackup_format == 'JSON':
board_info = {} # This is for the experimental "haschtl" fork
if GS.global_pcb_finish: # Create the board_info
board_info['copper_finish'] = GS.global_pcb_finish board_info = {}
if GS.stackup: if GS.global_pcb_finish:
layers_parsed = [] board_info['copper_finish'] = GS.global_pcb_finish
for la in GS.stackup: if GS.stackup:
parsed_layer = {'name': la.name, 'type': la.type} layers_parsed = []
if la.color is not None: for la in GS.stackup:
parsed_layer['color'] = la.color parsed_layer = {'name': la.name, 'type': la.type}
if la.thickness is not None: if la.color is not None:
parsed_layer['thickness'] = la.thickness/1000 parsed_layer['color'] = la.color
layers_parsed.append(parsed_layer) if la.thickness is not None:
board_info['stackup'] = layers_parsed parsed_layer['thickness'] = la.thickness/1000
data = json.dumps(board_info, indent=3) layers_parsed.append(parsed_layer)
logger.debug('Stackup: '+str(data)) board_info['stackup'] = layers_parsed
with open(fname, 'wt') as f: data = json.dumps(board_info, indent=3)
f.write(data) logger.debug('Stackup: '+str(data))
with open(fname, 'wt') as f:
f.write(data)
else: # self.stackup_format == 'BIN':
# This is for 2.7+
# Map the surface finish
if GS.global_pcb_finish:
surface_finish = SURFACE_FINISH_MAP.get(GS.global_pcb_finish, SurfaceFinish.HASL)
else:
surface_finish = SurfaceFinish.NONE
ds = GS.board.GetDesignSettings()
thickness_mm = GS.to_mm(ds.GetBoardThickness())
mask_color, mask_color_custom = self.parse_kicad_color(GS.global_solder_mask_color.upper())
silks_color, silks_color_custom = self.parse_kicad_color(GS.global_silk_screen_color.upper())
logger.error(f"{mask_color} {mask_color_custom}")
with open(fname, 'wb') as f:
f.write(struct.pack("!fbBBBbBBBb", thickness_mm, mask_color, *mask_color_custom, silks_color,
*silks_color_custom, surface_finish))
def get_boarddefs(self): def get_boarddefs(self):
""" Extract the sub-PCBs and their positions using texts. """ Extract the sub-PCBs and their positions using texts.

View File

@ -0,0 +1,71 @@
# PCB2Blender (PCB3D) format for plug-in v2.7
# URL: https://github.com/30350n/pcb2blender
kibot:
version: 1
groups:
- name: _PCB2Blender_elements_2_7
outputs:
- _PCB2Blender_tools_2_7
- _PCB2Blender_layers_2_7
- _PCB2Blender_vrml_2_7
outputs:
- name: _PCB2Blender_tools_2_7
comment: Pads information and board bounds for PCB3D 2.7
type: pcb2blender_tools
dir: '%f%v_pcb3d'
run_by_default: false
options:
stackup_create: true
stackup_file: stackup
stackup_dir: layers
stackup_format: BIN
- name: _PCB2Blender_layers_2_7
comment: SVG files for the layers for PCB3D 2.7
type: svg
dir: '%f%v_pcb3d/layers'
run_by_default: false
options:
output: "%i.%x"
margin: 1
limit_viewbox: true
svg_precision: 6
drill_marks: none
layers:
- F.Cu
- B.Cu
- F.Paste
- B.Paste
- layer: F.SilkS
suffix: F_SilkS
- layer: B.SilkS
suffix: B_SilkS
- F.Mask
- B.Mask
- name: _PCB2Blender_vrml_2_7
comment: VRML model for PCB3D 2.7
type: vrml
dir: '%f%v_pcb3d'
run_by_default: false
options:
output: pcb.wrl
dir_models: components
use_pcb_center_as_ref: false
model_units: meters
- name: _PCB2Blender_2_7
comment: PCB3D model for pcb2blender plug-in
type: compress
options:
output: '%f%v.pcb3d'
format: ZIP
files:
- from_output: _PCB2Blender_tools_2_7
from_output_dir: true
- from_output: _PCB2Blender_layers_2_7
dest: layers
- from_output: _PCB2Blender_vrml_2_7
from_output_dir: true

View File

@ -3,15 +3,15 @@ kibot:
version: 1 version: 1
import: import:
- file: PCB2Blender_2_1 - file: PCB2Blender_2_7
outputs: outputs:
- name: '3d_export' - name: '3d_export'
comment: "Exports the PCB in blender format" comment: "Exports the PCB in blender format"
type: blender_export type: blender_export
disable_run_by_default: _PCB2Blender_2_1 disable_run_by_default: _PCB2Blender_2_7
options: options:
pcb3d: _PCB2Blender_2_1 pcb3d: _PCB2Blender_2_7
# camera: # camera:
# name: MyCamera # name: MyCamera
# pos_x: 0.3 # pos_x: 0.3