Skip to content

Commit

Permalink
Add color maps guide with examples
Browse files Browse the repository at this point in the history
  • Loading branch information
Argmaster committed Jan 31, 2025
1 parent f498963 commit defddf9
Show file tree
Hide file tree
Showing 6 changed files with 152 additions and 7 deletions.
43 changes: 41 additions & 2 deletions docs/40_gerber/20_quick_start/10_custom_color_maps.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,43 @@
# Custom color maps

Unfortunately, this documentation is yet to be written. I am doing my best to provide it
before release 3.0.0 so stay tuned!
## Introduction

In PyGerber Gerber API (`pygerber.gerber.api` module) term color maps refers to
dictionaries used to convert type of layer (specified manually or determined
automatically) to style determining colors which should be used for rendering of that
layer.

Layer (file) type is expressed as `FileTypeEnum` value while style is expressed as
`Style` instance. Both classes are defined in `pygerber.gerber.api` module.

There are two predefined color maps in `pygerber.gerber.api`: `DEFAULT_COLOR_MAP` and
`DEFAULT_ALPHA_COLOR_MAP`. By default second one is used in `GerberFile` class during
rendering.

To create custom color map you need to create dictionary with keys being `FileTypeEnum`
values and values being `Style` instances. Then to change color map used by `GerberFile`
you need to call `set_color_map` method with your custom color map as argument.

## Single file example

{{ include_code("test/examples/gerberx3/api/_90_custom_color_map.example.py", "docspygerberlexer", title="custom_color_map.py", linenums="1") }}

## Multi file example

The reason for using dictionaries instead of just a parameter for render function is
that color map approach scales well for rendering multiple files from a project at once.
In such case colors will be automatically determined based on file types which can be
easily inferred from either file extension or file attributes.

See an example below:

{{ include_code("test/examples/gerberx3/api/_91_custom_color_map_project.example.py", "docspygerberlexer", title="custom_color_map_multi_file.py", linenums="1") }}

## Partial override

It might be tricky to guess all file types when rendering multiple files, especially if
we want to change colors only for single file type. In such case we can just copy
default color map which covers all file types and then override only colors for specific
file type.

{{ include_code("test/examples/gerberx3/api/_92_custom_color_map_partial_override.example.py", "docspygerberlexer", title="custom_color_map_multi_file.py", linenums="1") }}
12 changes: 11 additions & 1 deletion src/pygerber/gerber/api/_composite_view.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
from __future__ import annotations

from typing import Iterable, Sequence
from typing import TYPE_CHECKING, Iterable, Sequence

from PIL import Image

from pygerber.gerber.api._enums import COLOR_MAP_T
from pygerber.gerber.api._gerber_file import GerberFile, PillowImage

if TYPE_CHECKING:
from typing_extensions import Self


class CompositeImage:
"""Image composed of multiple sub-images."""
Expand Down Expand Up @@ -43,6 +47,12 @@ class CompositeView:
def __init__(self, files: Iterable[GerberFile]) -> None:
self._files = tuple(files)

def set_color_map(self, color_map: COLOR_MAP_T) -> Self:
"""Set color map for all files."""
for file in self._files:
file.set_color_map(color_map)
return self

@property
def files(self) -> tuple[GerberFile, ...]:
"""Get sequence of Gerber files."""
Expand Down
4 changes: 0 additions & 4 deletions src/pygerber/gerber/api/_gerber_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,10 +242,6 @@ def save_bmp(
`str` and `Path` objects are interpreted as file paths and opened with
truncation. `BinaryIO`-like (files, BytesIO) objects are written to
directly.
color : Style, optional
Color to use for SVG, background is ignored as it is always rendered as
empty space, so only foreground applies, by default
Style.presets.COPPER_ALPHA
kwargs : Any
Additional keyword arguments to pass to `PIL.Image.save`.
Expand Down
37 changes: 37 additions & 0 deletions test/examples/gerberx3/api/_90_custom_color_map.example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from pygerber.gerber.api import GerberFile

from pygerber.examples import ExamplesEnum, load_example
from pygerber.gerber.api import FileTypeEnum, Color, Style


# Define a custom color map as a dictionary. We dont have to map all file types, only
# the ones we need. There is no fallback mechanism though, so if you miss a file type
# that you need during rendering, KeyError will be raised.
my_color_map = {
FileTypeEnum.COPPER: Style(
background=Color.from_rgba(0, 0, 0, 255),
foreground=Color.from_rgba(200, 20, 20, 255),
),
FileTypeEnum.MASK: Style(
background=Color.from_rgba(0, 0, 0, 255),
foreground=Color.from_rgba(117, 117, 117, 255),
),
FileTypeEnum.PASTE: Style(
background=Color.from_rgba(0, 0, 0, 255),
foreground=Color.from_rgba(117, 117, 117, 255),
),
FileTypeEnum.SILK: Style(
background=Color.from_hex("#000000"),
foreground=Color.from_hex("#FFFFFF"),
),
}

gerber_source_code = load_example(ExamplesEnum.carte_test_F_Cu)

file = GerberFile.from_str(gerber_source_code)
# Set the custom color map with the set_color_map method. If you don't do that,
# nothing will change.
file.set_color_map(my_color_map)

image = file.render_with_pillow()
image.get_image().save("output.png")
38 changes: 38 additions & 0 deletions test/examples/gerberx3/api/_91_custom_color_map_project.example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from pygerber.examples import ExamplesEnum, load_example
from pygerber.gerber.api import CompositeView, GerberFile, FileTypeEnum, Color, Style


my_color_map = {
FileTypeEnum.COPPER: Style(
background=Color.from_rgba(0, 0, 0, 0),
foreground=Color.from_rgba(200, 20, 20, 255),
),
FileTypeEnum.SOLDERMASK: Style(
background=Color.from_rgba(0, 0, 0, 0),
foreground=Color.from_rgba(117, 117, 117, 255),
),
FileTypeEnum.PASTE: Style(
background=Color.from_rgba(0, 0, 0, 0),
foreground=Color.from_rgba(117, 117, 117, 255),
),
FileTypeEnum.LEGEND: Style(
background=Color.from_hex("#00000000"),
foreground=Color.from_hex("#FFFFFFFF"),
),
}


project = CompositeView(
[
GerberFile.from_str(load_example(ExamplesEnum.simple_2layer_F_Cu)),
GerberFile.from_str(load_example(ExamplesEnum.simple_2layer_F_Mask)),
GerberFile.from_str(load_example(ExamplesEnum.simple_2layer_F_Paste)),
GerberFile.from_str(load_example(ExamplesEnum.simple_2layer_F_Silkscreen)),
],
)
# Calling `set_color_map` on `CompositeView` will propagate the color map to all
# `GerberFile` objects inside it.
project.set_color_map(my_color_map)

image = project.render_with_pillow()
image.get_image().save("output.png")
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from pygerber.gerber.api import GerberFile

from pygerber.examples import ExamplesEnum, load_example
from pygerber.gerber.api import FileTypeEnum, Color, Style, DEFAULT_ALPHA_COLOR_MAP


# We can copy DEFAULT_ALPHA_COLOR_MAP and update it with our custom values to avoid
# missing any file type. The ones we don't override will keep the default values.
my_color_map = DEFAULT_ALPHA_COLOR_MAP.copy()
my_color_map.update(
{
FileTypeEnum.COPPER: Style(
background=Color.from_rgba(0, 0, 0, 255),
foreground=Color.from_rgba(20, 20, 200, 255),
),
}
)

gerber_source_code = load_example(ExamplesEnum.carte_test_F_Cu)

file = GerberFile.from_str(gerber_source_code)
file.set_color_map(my_color_map)

image = file.render_with_pillow()
image.get_image().save("output.png")

0 comments on commit defddf9

Please sign in to comment.