[Blender Export] Now can be used as renderer

- Tested with kikit_present and populate
- Also added `auto_crop`
This commit is contained in:
Salvador E. Tropea 2023-01-26 18:13:38 -03:00
parent fce9a2d581
commit e16086ca70
12 changed files with 209 additions and 76 deletions

View File

@ -201,6 +201,7 @@ Notes:
[**ImageMagick**](https://imagemagick.org/) [![Tool](https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/llave-inglesa-22x22.png)](https://imagemagick.org/) [![Debian](https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/debian-openlogo-22x22.png)](https://packages.debian.org/bullseye/imagemagick) ![Auto-download](https://raw.githubusercontent.com/INTI-CMNB/KiBot/master/docs/images/auto_download-22x22.png)
- Optional to:
- Automatically crop images for `blender_export`
- Create outputs preview for `navigate_results`
- Create monochrome prints and scaled PNG files for `pcb_print`
- Create JPG and BMP images for `pcbdraw`
@ -1608,10 +1609,14 @@ Notes:
- **`samples`**: [number=10] How many samples we create. Each sample is a raytracing render.
Use 1 for a raw preview, 10 for a draft and 100 or more for the final render.
- **`transparent_background`**: [boolean=false] Make the background transparent.
- `auto_crop`: [boolean=false] When enabled the image will be post-processed to remove the empty space around the image.
In this mode the `background2` is changed to be the same as `background1`.
- `background1`: [string='#66667F'] First color for the background gradient.
- `background2`: [string='#CCCCE5'] Second color for the background gradient.
- *height*: Alias for resolution_y.
- `resolution_x`: [number=1280] Width of the image.
- `resolution_y`: [number=720] Height of the image.
- *width*: Alias for resolution_x.
- `add_default_light`: [boolean=true] Add a default light when none specified.
The default light is located at (-size*3.33, size*3.33, size*5) where size is max(width, height) of the PCB.
- `camera`: [dict] Options for the camera.

View File

@ -209,10 +209,14 @@ outputs:
view: 'top'
# [dict] Controls how the render is done for the `render` output type
render_options:
# [boolean=false] When enabled the image will be post-processed to remove the empty space around the image.
# In this mode the `background2` is changed to be the same as `background1`
auto_crop: false
# [string='#66667F'] First color for the background gradient
background1: '#66667F'
# [string='#CCCCE5'] Second color for the background gradient
background2: '#CCCCE5'
# `height` is an alias for `resolution_y`
# [number=1280] Width of the image
resolution_x: 1280
# [number=720] Height of the image
@ -222,6 +226,7 @@ outputs:
samples: 10
# [boolean=false] Make the background transparent
transparent_background: false
# `width` is an alias for `resolution_x`
# BoardView:
# This format allows simple pads and connections navigation, mainly for circuit debug.
# The output can be loaded using Open Board View (https://openboardview.org/)

View File

@ -271,7 +271,7 @@ KICAD5_SVG_SCALE = 116930/297002200
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:102.0) Gecko/20100101 Firefox/102.0'
# Text used to disable 3D models
DISABLE_3D_MODEL_TEXT = '_Disabled_by_KiBot'
RENDERERS = ['pcbdraw', 'render_3d']
RENDERERS = ['pcbdraw', 'render_3d', 'blender_export']
PCB_GENERATORS = ['pcb_variant', 'panelize']
KIKIT_UNIT_ALIASES = {'millimeters': 'mm', 'inches': 'inch', 'mils': 'mil'}

View File

@ -468,38 +468,6 @@ class Optionable(object):
def color_str_to_rgb(self, color):
return self.color_to_rgb(self.parse_one_color(color))
def save_renderer_options(self):
""" Save the current renderer settings """
options = self._renderer.options
self.old_filters_to_expand = options._filters_to_expand
self.old_show_components = options.show_components
self.old_highlight = options.highlight
self.old_output = options.output
self.old_dir = self._renderer.dir
self.old_done = self._renderer._done
if self._renderer_is_pcbdraw:
self.old_bottom = options.bottom
self.old_add_to_variant = options.add_to_variant
else: # render_3D
self.old_view = options.view
self.old_show_all_components = options._show_all_components
def restore_renderer_options(self):
""" Restore the renderer settings """
options = self._renderer.options
options._filters_to_expand = self.old_filters_to_expand
options.show_components = self.old_show_components
options.highlight = self.old_highlight
options.output = self.old_output
self._renderer.dir = self.old_dir
self._renderer._done = self.old_done
if self._renderer_is_pcbdraw:
options.bottom = self.old_bottom
options.add_to_variant = self.old_add_to_variant
else: # render_3D
options.view = self.old_view
options._show_all_components = self.old_show_all_components
class BaseOptions(Optionable):
""" A class to validate and hold output options.

View File

@ -990,6 +990,32 @@ class VariantOptions(BaseOptions):
self._sub_pcb = self.variant._sub_pcb
self._comps = comps
# The following 3 members are used by 2D and 3D renderers
def setup_renderer(self, components, active_components):
""" Setup the options to use it as a renderer """
self._show_all_components = False
self._filters_to_expand = False
self.highlight = self.solve_kf_filters([c for c in active_components if c])
self.show_components = [c for c in components if c]
if self.show_components:
self.show_components = self.solve_kf_filters(self.show_components)
def save_renderer_options(self):
""" Save the current renderer settings """
self.old_filters_to_expand = self._filters_to_expand
self.old_show_components = self.show_components
self.old_highlight = self.highlight
self.old_dir = self._parent.dir
self.old_done = self._parent._done
def restore_renderer_options(self):
""" Restore the renderer settings """
self._filters_to_expand = self.old_filters_to_expand
self.show_components = self.old_show_components
self.highlight = self.old_highlight
self._parent.dir = self.old_dir
self._parent._done = self.old_done
class PcbMargin(Optionable):
""" To adjust each margin """

View File

@ -8,6 +8,8 @@ Dependencies:
- from: Blender
role: mandatory
version: 3.4.0
- from: ImageMagick
role: Automatically crop images
"""
import json
import os
@ -122,14 +124,21 @@ class BlenderRenderOptions(Optionable):
Use 1 for a raw preview, 10 for a draft and 100 or more for the final render """
self.resolution_x = 1280
""" Width of the image """
self.width = None
""" {resolution_x} """
self.resolution_y = 720
""" Height of the image """
self.height = None
""" {resolution_y} """
self.transparent_background = False
""" *Make the background transparent """
self.background1 = "#66667F"
""" First color for the background gradient """
self.background2 = "#CCCCE5"
""" Second color for the background gradient """
self.auto_crop = False
""" When enabled the image will be post-processed to remove the empty space around the image.
In this mode the `background2` is changed to be the same as `background1` """
self._unkown_is_error = True
@ -154,6 +163,7 @@ class BlenderPointOfViewOptions(Optionable):
""" String to diferentiate the name of this view.
When empty we use the `view` """
self._unkown_is_error = True
self._file_id = ''
def config(self, parent):
super().config(parent)
@ -190,6 +200,38 @@ class PCB3DExportOptions(Base3DOptionsWithHL):
p._expand_ext = cur_ext
return out_name
def setup_renderer(self, components, active_components, bottom, name):
super().setup_renderer(components, active_components)
self._pov.view = 'Z' if bottom else 'z'
# Expand the name using .PNG
cur_ext = self._expand_ext
self._expand_ext = 'png'
o_name = self.expand_filename_both(name, is_sch=False)
self._expand_ext = cur_ext
self._out.output = o_name
return o_name
def save_renderer_options(self):
""" Save the current renderer settings """
p = self._parent
# We are an option inside another option
self._parent = self._parent._parent
super().save_renderer_options()
self._parent = p
self.old_show_all_components = self._show_all_components
self.old_view = self._pov.view
self.old_output = self._out.output
def restore_renderer_options(self):
""" Restore the renderer settings """
p = self._parent
self._parent = self._parent._parent
super().restore_renderer_options()
self._parent = p
self._show_all_components = self.old_show_all_components
self._pov.view = self.old_view
self._out.output = self.old_output
class Blender_ExportOptions(BaseOptions):
def __init__(self):
@ -222,9 +264,12 @@ class Blender_ExportOptions(BaseOptions):
def config(self, parent):
super().config(parent)
# Check we at least have a name for the source output
if isinstance(self.pcb3d, type) or (isinstance(self.pcb3d, str) and not self.pcb3d):
if isinstance(self.pcb3d, str) and not self.pcb3d:
raise KiPlotConfigurationError('You must specify the name of the output that'
' generates the PCB3D file or its options')
if isinstance(self.pcb3d, type):
self.pcb3d = PCB3DExportOptions()
self.pcb3d.config(self)
# Do we have outputs?
if isinstance(self.outputs, type):
self.outputs = []
@ -409,6 +454,10 @@ class Blender_ExportOptions(BaseOptions):
return
# Make sure Blender is available
command = self._pcb3d.ensure_tool('Blender')
if self.render_options.auto_crop:
# Avoid a gradient
self.render_options.background2 = self.render_options.background1
convert_command = self.ensure_tool('ImageMagick')
# Create a JSON with the scene information
with NamedTemporaryFile(mode='w', suffix='.json') as f:
scene = {}
@ -473,6 +522,13 @@ class Blender_ExportOptions(BaseOptions):
cmd.append(pcb3d_file)
# Execute the command
run_command(cmd)
if self.render_options.auto_crop:
for pov in self.point_of_view:
for o in self.outputs:
if o.type != 'render':
continue
name = self.get_output_filename(o, self._parent.output_dir, pov)
run_command([convert_command, name, '-trim', '+repage', '-trim', '+repage', name])
@output_class
@ -501,6 +557,25 @@ class Blender_Export(Base3D):
files.extend(self.options.pcb3d.list_models())
return files
def get_renderer_options(self):
""" Where are the options for this output when used as a 'renderer' """
ops = self.options
out = next(filter(lambda x: x.type == 'render', ops.outputs), None)
res = None
if out is not None:
if isinstance(ops.pcb3d, str):
# We can't configure it
out = None
else:
res = ops.pcb3d
res._pov = ops.point_of_view[0]
res._out = out
return res if out is not None else None
def get_extension(self):
# Used when we are a renderer
return 'png'
@staticmethod
def get_conf_examples(name, layers, templates):
if not GS.check_tool(name, 'Blender'):

View File

@ -122,23 +122,19 @@ class PresentBoards(Optionable):
return self.name, self.comment, self.pcb_file, self.front_image, self.back_image, self.gerbers
def generate_image(self, back, tmp_name):
self.save_renderer_options()
options = self._renderer.options
options = self._renderer.get_renderer_options()
if options is None:
raise KiPlotConfigurationError('No suitable renderer ({})'.format(self._renderer))
# Memorize the current options
options.save_renderer_options()
logger.debug('Starting renderer with back: {}, name: {}'.format(back, tmp_name))
# Configure it according to our needs
options._filters_to_expand = False
options.show_components = None if self._renderer_is_pcbdraw else []
options.highlight = []
options.output = tmp_name
options.setup_renderer([], [], back, tmp_name)
self._renderer.dir = self._parent._parent.dir
self._renderer._done = False
if self._renderer_is_pcbdraw:
options.add_to_variant = False
options.bottom = back
else: # render_3D
options.view = 'Z' if back else 'z'
options._show_all_components = False
run_output(self._renderer)
self.restore_renderer_options()
# Restore the options
options.restore_renderer_options()
def do_compress(self, tmp_name, out):
tree = {'name': '_temporal_compress_gerbers',
@ -202,7 +198,6 @@ class PresentBoards(Optionable):
format(out, RENDERERS))
config_output(out)
self._renderer = out
self._renderer_is_pcbdraw = out.type == 'pcbdraw'
tmp_name = _get_tmp_name(out.get_extension())
self.temporals.append(tmp_name)
self.generate_image(back, tmp_name)

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2020-2022 Salvador E. Tropea
# Copyright (c) 2020-2022 Instituto Nacional de Tecnología Industrial
# 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)
"""
@ -322,6 +322,29 @@ class PcbDrawOptions(VariantOptions):
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:
@ -530,6 +553,10 @@ class PcbDraw(BaseOutput): # noqa: F821
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, templates):
outs = []

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2022 Salvador E. Tropea
# Copyright (c) 2022 Instituto Nacional de Tecnología Industrial
# Copyright (c) 2022-2023 Salvador E. Tropea
# Copyright (c) 2022-2023 Instituto Nacional de Tecnología Industrial
# License: GPL-3.0
# Project: KiBot (formerly KiPlot)
"""
@ -82,33 +82,22 @@ class PopulateOptions(VariantOptions):
img_dir = os.path.dirname(self._parent.expand_filename(out_dir, self.imgname))
return [self._parent.expand_filename(out_dir, self.get_out_file_name()), img_dir]
def generate_image(self, side, components, active_components, name):
options = self._renderer.options
def generate_image(self, side, components, active_components, name, options):
logger.debug('Starting renderer with side: {}, components: {}, high: {}, image: {}'.
format(side, components, active_components, name))
# Configure it according to our needs
options._filters_to_expand = False
options.show_components = [c for c in components if c]
if not options.show_components:
options.show_components = None if self._renderer_is_pcbdraw else []
else:
options.show_components = options.solve_kf_filters(options.show_components)
options.highlight = options.solve_kf_filters([c for c in active_components if c])
options.output = name
o_name = options.setup_renderer(components, active_components, side.startswith("back"), name)
self._renderer.dir = self._parent.dir
self._renderer._done = False
if self._renderer_is_pcbdraw:
options.add_to_variant = False
options.bottom = side.startswith("back")
else: # render_3D
options.view = 'Z' if side.startswith("back") else 'z'
options._show_all_components = False
run_output(self._renderer)
return options.expand_filename_both(name, is_sch=False)
return o_name
def generate_images(self, dir_name, content):
options = self._renderer.get_renderer_options()
if options is None:
raise KiPlotConfigurationError('No suitable renderer ({})'.format(self._renderer))
# Memorize the current options
self.save_renderer_options()
options.save_renderer_options()
dir = os.path.dirname(os.path.join(dir_name, self.imgname))
if not os.path.exists(dir):
os.makedirs(dir)
@ -119,9 +108,9 @@ class PopulateOptions(VariantOptions):
for x in item["steps"]:
counter += 1
filename = self.imgname.replace('%d', str(counter))
x["img"] = self.generate_image(x["side"], x["components"], x["active_components"], filename)
x["img"] = self.generate_image(x["side"], x["components"], x["active_components"], filename, options)
# Restore the options
self.restore_renderer_options()
options.restore_renderer_options()
return content
def run(self, dir_name):
@ -140,7 +129,6 @@ class PopulateOptions(VariantOptions):
if out.type not in RENDERERS:
raise KiPlotConfigurationError('The `renderer` must be {} type, not {}'.format(RENDERERS, out.type))
self._renderer = out
self._renderer_is_pcbdraw = out.type == 'pcbdraw'
# Load the input content
try:
_, content = load_content(self.input)

View File

@ -176,6 +176,26 @@ class Render3DOptions(Base3DOptionsWithHL):
self.view = view
self._expand_id += '_'+self._rviews.get(self.view)
def setup_renderer(self, components, active_components, bottom, name):
super().setup_renderer(components, active_components)
self.view = 'Z' if bottom else 'z'
self.output = name
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_show_all_components = self._show_all_components
self.old_view = self.view
self.old_output = self.output
def restore_renderer_options(self):
""" Restore the renderer settings """
super().restore_renderer_options()
self._show_all_components = self.old_show_all_components
self.view = self.old_view
self.output = self.old_output
def add_step(self, cmd, steps, ops):
if steps:
cmd.extend([ops, str(steps)])
@ -265,6 +285,10 @@ class Render_3D(Base3D): # noqa: F821
""" *[dict] Options for the `render_3d` output """
self._category = 'PCB/3D'
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, templates):
outs = []

View File

@ -250,17 +250,24 @@ deps = '{\
],\
"extra_deb": null,\
"help_option": "--version",\
"importance": 4,\
"importance": 5,\
"in_debian": true,\
"is_kicad_plugin": false,\
"is_python": false,\
"name": "ImageMagick",\
"no_cmd_line_version": false,\
"no_cmd_line_version_old": false,\
"output": "navigate_results",\
"output": "blender_export",\
"plugin_dirs": null,\
"pypi_name": "ImageMagick",\
"roles": [\
{\
"desc": "Automatically crop images",\
"mandatory": false,\
"max_version": null,\
"output": "blender_export",\
"version": null\
},\
{\
"desc": "Create outputs preview",\
"mandatory": false,\

View File

@ -13,7 +13,8 @@ outputs:
mode: local
comment: This is a comment
name: Light control
back_image: Raytraced
# back_image: Raytraced
back_image: Blender
repository: 'https://github.com/INTI-CMNB/KiBot/'
- name: PcbDraw
@ -37,6 +38,18 @@ outputs:
rotate_z: -2
ray_tracing: true
- name: Blender
type: blender_export
run_by_default: false
options:
outputs:
- type: render
render_options:
transparent_background: true
auto_crop: true
# width: 640
# height: 480
- name: 'gerbers'
comment: "Gerbers for the Gerber god"
type: gerber