From 3cd644d19db009160b9433f8b14e22efce145099 Mon Sep 17 00:00:00 2001 From: "Salvador E. Tropea" Date: Wed, 11 Oct 2023 13:08:10 -0300 Subject: [PATCH] [Blender export][Added] Support for pcb2blender 2.7 - Solder mask and silk screen color - PCB finish - PCB thickness --- CHANGELOG.md | 2 +- docs/samples/generic_plot.kibot.yaml | 14 +-- docs/source/configuration/imports.rst | 9 +- .../configuration/outputs/blender_export.rst | 4 +- .../outputs/pcb2blender_tools.rst | 7 +- kibot/optionable.py | 6 +- kibot/out_blender_export.py | 16 +++- kibot/out_pcb2blender_tools.py | 96 +++++++++++++++---- .../PCB2Blender_2_7.kibot.yaml | 71 ++++++++++++++ .../yaml_samples/blender_export_1.kibot.yaml | 6 +- 10 files changed, 186 insertions(+), 45 deletions(-) create mode 100644 kibot/resources/config_templates/PCB2Blender_2_7.kibot.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index 67eb2a81..87b7d0dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 (#470) - 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 - Options to create simple animations: - PoV `steps`: to create rotation angle increments diff --git a/docs/samples/generic_plot.kibot.yaml b/docs/samples/generic_plot.kibot.yaml index aaceb736..209fd8d3 100644 --- a/docs/samples/generic_plot.kibot.yaml +++ b/docs/samples/generic_plot.kibot.yaml @@ -169,7 +169,7 @@ outputs: type: 'render' # [string|dict] Options to export the PCB to Blender. # 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: # [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 @@ -211,8 +211,8 @@ outputs: solder_paste_for_populated: true # [string=''] Board variant to apply variant: '' - # [string='2.1'] [2.1,2.1_haschtl] Variant of the format used - version: '2.1' + # [string='2.7'] [2.1,2.1_haschtl,2.7] Variant of the format used + version: '2.7' # Options to configure how Blender imports the PCB. # The default values are good for most cases pcb_import: @@ -2176,12 +2176,14 @@ outputs: # [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` 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 - # [string='.'] Directory for the stackup file + # [string='.'] Directory for the stackup file. Use 'layers' for 2.7+ 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' + # [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 sub_boards_bounds_file: 'bounds' # [boolean=true] Extract sub-PCBs and their Z axis position diff --git a/docs/source/configuration/imports.rst b/docs/source/configuration/imports.rst index a20bf916..1af64e41 100644 --- a/docs/source/configuration/imports.rst +++ b/docs/source/configuration/imports.rst @@ -272,7 +272,8 @@ Here is a list of currently defined templates and their outputs / groups: - `P-Ban_stencil `__: same as **P-Ban**, but also generates gerbers for *F.Paste* and *B.Paste* layers. -- `PCB2Blender_2_1 `__ +- `PCB2Blender_2_1 `__: 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_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_elements_2_1**: **_PCB2Blender_tools_2_1** + **_PCB2Blender_layers_2_1** + **_PCB2Blender_vrml_2_1** -- `PCB2Blender_2_1_haschtl `__ +- `PCB2Blender_2_7 `__: 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 `__: Similar to **PCB2Blender_2_1**, but for the + experimental **haschtl** fork. - Imports **PCB2Blender_2_1** and disables **_PCB2Blender_2_1** - **_PCB2Blender_tools_2_1_haschtl**: Pads, bounds and stack-up information. Disabled by default. diff --git a/docs/source/configuration/outputs/blender_export.rst b/docs/source/configuration/outputs/blender_export.rst index 3f9ec340..c38fbe12 100644 --- a/docs/source/configuration/outputs/blender_export.rst +++ b/docs/source/configuration/outputs/blender_export.rst @@ -33,7 +33,7 @@ Parameters: - **pcb3d** :index:`: ` [string|dict] Options to export the PCB to Blender. 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: @@ -67,7 +67,7 @@ Parameters: - ``solder_paste_for_populated`` :index:`: ` [boolean=true] Add solder paste only for the populated components. Populated components are the ones listed in `show_components`. - ``variant`` :index:`: ` [string=''] Board variant to apply. - - ``version`` :index:`: ` [string='2.1'] [2.1,2.1_haschtl] Variant of the format used. + - ``version`` :index:`: ` [string='2.7'] [2.1,2.1_haschtl,2.7] Variant of the format used. - **point_of_view** :index:`: ` [dict|list(dict)] How the object is viewed by the camera. diff --git a/docs/source/configuration/outputs/pcb2blender_tools.rst b/docs/source/configuration/outputs/pcb2blender_tools.rst index 1ff581dd..f1107bb9 100644 --- a/docs/source/configuration/outputs/pcb2blender_tools.rst +++ b/docs/source/configuration/outputs/pcb2blender_tools.rst @@ -45,9 +45,10 @@ Parameters: - ``pre_transform`` :index:`: ` [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. - - ``stackup_create`` :index:`: ` [boolean=false] Create a JSON file containing the board stackup. - - ``stackup_dir`` :index:`: ` [string='.'] Directory for the stackup file. - - ``stackup_file`` :index:`: ` [string='board.yaml'] Name for the stackup file. + - ``stackup_create`` :index:`: ` [boolean=false] Create a file containing the board stackup. + - ``stackup_dir`` :index:`: ` [string='.'] Directory for the stackup file. Use 'layers' for 2.7+. + - ``stackup_file`` :index:`: ` [string='board.yaml'] Name for the stackup file. Use 'stackup' for 2.7+. + - ``stackup_format`` :index:`: ` [string='JSON'] [JSON,BIN] Format for the stackup file. Use 'BIN' for 2.7+. - ``sub_boards_bounds_file`` :index:`: ` [string='bounds'] File name for the sub-PCBs bounds. - ``sub_boards_create`` :index:`: ` [boolean=true] Extract sub-PCBs and their Z axis position. - ``sub_boards_dir`` :index:`: ` [string='boards'] Directory for the boards definitions. diff --git a/kibot/optionable.py b/kibot/optionable.py index 5bbb3dc1..4f69b9e1 100644 --- a/kibot/optionable.py +++ b/kibot/optionable.py @@ -451,12 +451,12 @@ class Optionable(object): for color in names: 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) alpha = 1.0 if len(res) > 3: - alpha = int(res[3], 16)/255.0 - return (int(res[0], 16)/255.0, int(res[1], 16)/255.0, int(res[2], 16)/255.0, alpha) + alpha = int(res[3], 16)*scale + return (int(res[0], 16)*scale, int(res[1], 16)*scale, int(res[2], 16)*scale, alpha) def color_to_rgb(self, color): index = 4 if len(color) > 4 else 0 diff --git a/kibot/out_blender_export.py b/kibot/out_blender_export.py index 7b87e893..21e40f4d 100644 --- a/kibot/out_blender_export.py +++ b/kibot/out_blender_export.py @@ -248,8 +248,8 @@ class PCB3DExportOptions(Base3DOptionsWithHL): with document: self.output = GS.def_global_output """ Name for the generated PCB3D file (%i='blender_export' %x='pcb3d') """ - self.version = '2.1' - """ [2.1,2.1_haschtl] Variant of the format used """ + self.version = '2.7' + """ [2.1,2.1_haschtl,2.7] Variant of the format used """ self.solder_paste_for_populated = True """ Add solder paste only for the populated components. Populated components are the ones listed in `show_components` """ @@ -307,7 +307,7 @@ class Blender_ExportOptions(BaseOptions): self.pcb3d = PCB3DExportOptions """ *[string|dict] Options to export the PCB to Blender. 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 """ Options to configure how Blender imports the PCB. 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 ...') 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', 'type': 'pcb2blender_tools', 'comment': 'Internally created for the PCB3D', 'dir': dest_dir, - 'options': {'stackup_create': self.pcb3d.version == '2.1_haschtl'}} + 'options': options} if self.pcb3d.solder_paste_for_populated: sc = 'all' if not self.pcb3d._show_all_components: diff --git a/kibot/out_pcb2blender_tools.py b/kibot/out_pcb2blender_tools.py index a0f0f81b..2218bb34 100644 --- a/kibot/out_pcb2blender_tools.py +++ b/kibot/out_pcb2blender_tools.py @@ -5,6 +5,7 @@ # Project: KiBot (formerly KiPlot) # Some code is adapted from: https://github.com/30350n/pcb2blender from dataclasses import dataclass, field +from enum import IntEnum import json import os import re @@ -37,6 +38,34 @@ class BoardDef: 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): """ Replace character that aren't alphabetic by _ """ return re.sub(r"[\W]+", "_", name) @@ -59,11 +88,13 @@ class PCB2Blender_ToolsOptions(VariantOptions): self.pads_info_dir = 'pads' """ Sub-directory where the pads info files are stored """ self.stackup_create = False - """ Create a JSON file containing the board stackup """ + """ Create a file containing the board stackup """ self.stackup_file = 'board.yaml' - """ Name for the stackup file """ + """ Name for the stackup file. Use 'stackup' for 2.7+ """ 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 """ Extract sub-PCBs and their Z axis position """ self.sub_boards_dir = 'boards' @@ -146,30 +177,53 @@ class PCB2Blender_ToolsOptions(VariantOptions): pad.GetDrillShape(), *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): if not self.stackup_create or (not GS.global_pcb_finish and not GS.stackup): return dir_name = os.path.join(dir_name, self.stackup_dir) os.makedirs(dir_name, exist_ok=True) fname = os.path.join(dir_name, self.stackup_file) - # Create the board_info - board_info = {} - if GS.global_pcb_finish: - board_info['copper_finish'] = GS.global_pcb_finish - if GS.stackup: - layers_parsed = [] - for la in GS.stackup: - parsed_layer = {'name': la.name, 'type': la.type} - if la.color is not None: - parsed_layer['color'] = la.color - if la.thickness is not None: - parsed_layer['thickness'] = la.thickness/1000 - layers_parsed.append(parsed_layer) - board_info['stackup'] = layers_parsed - data = json.dumps(board_info, indent=3) - logger.debug('Stackup: '+str(data)) - with open(fname, 'wt') as f: - f.write(data) + if self.stackup_format == 'JSON': + # This is for the experimental "haschtl" fork + # Create the board_info + board_info = {} + if GS.global_pcb_finish: + board_info['copper_finish'] = GS.global_pcb_finish + if GS.stackup: + layers_parsed = [] + for la in GS.stackup: + parsed_layer = {'name': la.name, 'type': la.type} + if la.color is not None: + parsed_layer['color'] = la.color + if la.thickness is not None: + parsed_layer['thickness'] = la.thickness/1000 + layers_parsed.append(parsed_layer) + board_info['stackup'] = layers_parsed + data = json.dumps(board_info, indent=3) + 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): """ Extract the sub-PCBs and their positions using texts. diff --git a/kibot/resources/config_templates/PCB2Blender_2_7.kibot.yaml b/kibot/resources/config_templates/PCB2Blender_2_7.kibot.yaml new file mode 100644 index 00000000..29862938 --- /dev/null +++ b/kibot/resources/config_templates/PCB2Blender_2_7.kibot.yaml @@ -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 diff --git a/tests/yaml_samples/blender_export_1.kibot.yaml b/tests/yaml_samples/blender_export_1.kibot.yaml index 47eb5fee..e638864f 100644 --- a/tests/yaml_samples/blender_export_1.kibot.yaml +++ b/tests/yaml_samples/blender_export_1.kibot.yaml @@ -3,15 +3,15 @@ kibot: version: 1 import: - - file: PCB2Blender_2_1 + - file: PCB2Blender_2_7 outputs: - name: '3d_export' comment: "Exports the PCB in blender format" type: blender_export - disable_run_by_default: _PCB2Blender_2_1 + disable_run_by_default: _PCB2Blender_2_7 options: - pcb3d: _PCB2Blender_2_1 + pcb3d: _PCB2Blender_2_7 # camera: # name: MyCamera # pos_x: 0.3