Skip to content

PlanarTemplate

src.templates.planar.PlanarTemplate

Bases: StarterTemplate

Planar template for Planar and Phenomenon cards introduced in the Planechase block.

Todo

Needs a complete rework and a 'Modifier' class.

Source code in src\templates\planar.py
class PlanarTemplate(StarterTemplate):
    """Planar template for Planar and Phenomenon cards introduced in the Planechase block.

    Todo:
        Needs a complete rework and a 'Modifier' class.
    """

    def __init__(self, layout: CardLayout, **kwargs):
        CFG.exit_early = True
        super().__init__(layout, **kwargs)

    @cached_property
    def text_layer_static_ability(self) -> ArtLayer:
        return psd.getLayer(LAYERS.STATIC_ABILITY, self.text_group)

    @cached_property
    def text_layer_chaos_ability(self) -> ArtLayer:
        return psd.getLayer(LAYERS.CHAOS_ABILITY, self.text_group)

    def basic_text_layers(self):
        """No mana cost, don't scale name layer."""
        self.text.extend([
            text_classes.TextField(
                layer=self.text_layer_name,
                contents=self.layout.name
            ),
            text_classes.ScaledTextField(
                layer=self.text_layer_type,
                contents=self.layout.type_line,
                reference=self.type_reference
            )
        ])

    def rules_text_and_pt_layers(self):

        # Phenomenon card?
        if self.layout.type_line == LAYERS.PHENOMENON:

            # Insert oracle text into static ability layer and disable chaos ability & layer mask on textbox
            self.text.append(
                text_classes.FormattedTextField(
                    layer=self.text_layer_static_ability,
                    contents=self.layout.oracle_text
                )
            )
            psd.enable_mask(psd.getLayerSet(LAYERS.TEXTBOX))
            psd.getLayer(LAYERS.CHAOS_SYMBOL, self.text_group).visible = False
            self.text_layer_chaos_ability.visible = False

        else:

            # Split oracle text on last line break, insert everything before into static, the rest into chaos
            linebreak_index = self.layout.oracle_text.rindex("\n")
            self.text.extend([
                text_classes.FormattedTextField(
                    layer=self.text_layer_static_ability,
                    contents=self.layout.oracle_text[0:linebreak_index]
                ),
                text_classes.FormattedTextField(
                    layer=self.text_layer_chaos_ability,
                    contents=self.layout.oracle_text[linebreak_index + 1:]
                ),
            ])

    def paste_scryfall_scan(self, rotate: bool = False, visible: bool = False) -> Optional[ArtLayer]:
        """Ensure we rotate the scan for Planar cards."""
        return super().paste_scryfall_scan(rotate=True, visible=visible)

Attributes

RGB_BLACK: SolidColor

RGB_WHITE: SolidColor

active_layer: Union[ArtLayer, LayerSet]

Union[ArtLayer, LayerSet]: Get the currently active layer in the Photoshop document.

app: PhotoshopHandler

art_action: Optional[Callable]

Function that is called to perform an action on the imported art.

art_action_args: Optional[dict]

Args to pass to art_action.

art_frame: str

art_frame_vertical: str

art_layer: ArtLayer

art_reference: ReferenceLayer

background: str

background_layer: Optional[ArtLayer]

Background texture layer.

basic_watermark_color: SolidColor

Color to use for the Basic Land Watermark.

basic_watermark_color_map: dict

Maps color values for Basic Land Watermark.

basic_watermark_fx: list[LayerEffects]

Defines the layer effects used on the Basic Land Watermark.

border_color: str

Use 'black' unless an alternate color and a valid border group is provided.

border_group: Optional[Union[LayerSet, ArtLayer]]

Optional[Union[LayerSet, ArtLayer]]: Group, or sometimes a layer, containing the card border.

color_indicator_layer: Optional[ArtLayer]

Color indicator icon layer.

color_limit: int

console: type[CONSOLE]

type[CONSOLE]: Console output object used to communicate with the user.

crown_layer: Optional[ArtLayer]

Legendary crown layer.

crown_shadow_layer: Union[ArtLayer, LayerSet, None]

Legendary crown hollow shadow layer.

dfc_group: Optional[LayerSet]

Optional[LayerSet]: Group containing double face elements.

divider_layer: Optional[ArtLayer]

Optional[ArtLayer]: Divider layer between rules text and flavor text.

doc_selection: Selection

docref: Optional[Document]

Optional[Document]: This template's document open in Photoshop.

event: Event

expansion_reference: Optional[ArtLayer]

Expansion symbol reference layer

expansion_symbol_alignments: list[Dimensions]

Alignments used for positioning the expansion symbol

expansion_symbol_layer: Optional[ArtLayer]

Expansion symbol layer, value set during the load_expansion_symbol method.

frame_layer_methods: list[Callable]

list[Callable]: Methods called to insert and enable frame layers.

Functions:

Name Description
`color_border`

Changes the border color if required and supported by the template.

`enable_frame_layers`

hooks: list[Callable]

list[Callable]: List of methods that will be called during the hooks execution step

identity: str

is_art_vertical: bool

is_artifact: bool

is_basic_land: bool

is_centered: bool

is_collector_promo: bool

is_colorless: bool

is_companion: bool

is_content_aware_enabled: bool

is_creature: bool

is_emblem: bool

is_flipside_creature: bool

is_front: bool

is_fullart: bool

is_hollow_crown: bool

is_hybrid: bool

is_land: bool

is_legendary: bool

is_mdfc: bool

is_miracle: bool

is_name_shifted: bool

is_nyx: bool

is_snow: bool

is_token: bool

is_transform: bool

is_type_shifted: bool

is_vehicle: bool

legal_group: LayerSet

mask_group: Optional[LayerSet]

mask_layers: list[ArtLayer]

List of layers containing masks used to blend multicolored layers.

name_reference: Optional[ArtLayer]

output_directory: Path

PathL Directory to save the rendered image.

output_file_name: Path

pinlines: str

pinlines_layer: Optional[ArtLayer]

Pinlines (and textbox) layer.

post_save_methods: list[Callable]

list[Callable]: Methods called after the rendered image is saved.

post_text_methods: list[Callable]

list[Callable]: Methods called after text is inserted and formatted.

pre_render_methods: list[Callable]

list[Callable]: Methods called before rendering begins.

Functions:

Name Description
`process_layout_data`

Processes layout data before it is used to generate the card.

pt_layer: Optional[ArtLayer]

Power and toughness box layer.

pt_reference: Optional[ReferenceLayer]

save_mode: Callable

text: list[FormattedTextLayer]

List of text layer objects to execute.

text_group: Optional[LayerSet]

Optional[LayerSet]: Text and icon group, contains rules text and necessary symbols.

text_layer_creator: Optional[ArtLayer]

Optional[ArtLayer]: Proxy creator name text layer.

text_layer_mana: Optional[ArtLayer]

Optional[ArtLayer]: Card mana cost text layer.

text_layer_methods: list[Callable]

list[Callable]: Methods called to insert and format text layers.

text_layer_name: Optional[ArtLayer]

Optional[ArtLayer]: Card name text layer.

text_layer_pt: Optional[ArtLayer]

Optional[ArtLayer]: Card power and toughness text layer.

text_layer_rules: Optional[ArtLayer]

Optional[ArtLayer]: Card rules text layer.

text_layer_type: Optional[ArtLayer]

Optional[ArtLayer]: Card typeline text layer.

textbox_reference: ReferenceLayer

transform_icon_layer: Optional[ArtLayer]

Optional[ArtLayer]: Transform icon layer.

twins: str

twins_layer: Optional[ArtLayer]

Name and title boxes layer.

type_reference: Optional[ArtLayer]

otherwise fallback to the expansion symbols reference layer.

watermark_blend_mode: BlendMode

Blend mode to use on the Watermark layer.

watermark_color_map: dict

Maps color values for Watermark.

watermark_colors: list[SolidColor]

Colors to use for the Watermark.

watermark_fx: list[LayerEffects]

Defines the layer effects to use for the Watermark.

Functions

add_basic_watermark_snow_effects(wm: ArtLayer)

Adds optional snow effects for 'Snow' Basic Land watermarks.

Parameters:

Name Type Description Default
wm ArtLayer

ArtLayer containing the Basic Land Watermark.

required
Source code in src\templates\_core.py
def add_basic_watermark_snow_effects(self, wm: ArtLayer):
    """Adds optional snow effects for 'Snow' Basic Land watermarks.

    Args:
        wm: ArtLayer containing the Basic Land Watermark.
    """
    pass

basic_text_layers()

No mana cost, don't scale name layer.

Source code in src\templates\planar.py
def basic_text_layers(self):
    """No mana cost, don't scale name layer."""
    self.text.extend([
        text_classes.TextField(
            layer=self.text_layer_name,
            contents=self.layout.name
        ),
        text_classes.ScaledTextField(
            layer=self.text_layer_type,
            contents=self.layout.type_line,
            reference=self.type_reference
        )
    ])

check_photoshop() -> None

Check if Photoshop is responsive to automation.

Source code in src\templates\_core.py
def check_photoshop(self) -> None:
    """Check if Photoshop is responsive to automation."""
    # Ensure the Photoshop Application is responsive
    check = self.app.refresh_app()
    if not isinstance(check, OSError):
        return

    # Connection with Photoshop couldn't be established, try again?
    if not self.console.await_choice(
            self.event, get_photoshop_error_message(check),
            end="Hit Continue to try again, or Cancel to end the operation.\n\n"
    ):
        # Cancel the operation
        raise OSError(check)
    self.check_photoshop()

collector_info() -> None

Format and add the collector info at the bottom.

Source code in src\templates\_core.py
def collector_info(self) -> None:
    """Format and add the collector info at the bottom."""

    # Ignore this step if legal layer not present
    if not self.legal_group:
        return

    # If creator is specified add the text
    if self.layout.creator and self.text_layer_creator:
        self.text_layer_creator.textItem.contents = self.layout.creator

    # Which collector info mode?
    if CFG.collector_mode in [
        CollectorMode.Default, CollectorMode.Modern
    ] and self.layout.collector_data:
        return self.collector_info_authentic()
    elif CFG.collector_mode == CollectorMode.ArtistOnly:
        return self.collector_info_artist_only()
    return self.collector_info_basic()

collector_info_artist_only() -> None

Called to generate 'Artist Only' collector info.

Source code in src\templates\_core.py
def collector_info_artist_only(self) -> None:
    """Called to generate 'Artist Only' collector info."""

    # Collector layers
    artist_layer = psd.getLayer(LAYERS.ARTIST, self.legal_group)
    psd.getLayer(LAYERS.SET, self.legal_group).visible = False

    # Correct color for non-black border
    if self.border_color != BorderColor.Black:
        artist_layer.textItem.color = self.RGB_BLACK

    # Insert artist name
    psd.replace_text(artist_layer, "Artist", self.layout.artist)

collector_info_authentic() -> None

Called to generate realistic collector info.

Source code in src\templates\_core.py
def collector_info_authentic(self) -> None:
    """Called to generate realistic collector info."""

    # Hide basic layers
    psd.getLayer(LAYERS.ARTIST, self.legal_group).visible = False
    psd.getLayer(LAYERS.SET, self.legal_group).visible = False

    # Get the collector layers
    group = psd.getLayerSet(LAYERS.COLLECTOR, self.legal_group)
    top = psd.getLayer(LAYERS.TOP, group).textItem
    bottom = psd.getLayer(LAYERS.BOTTOM, group)
    group.visible = True

    # Correct color for non-black border
    if self.border_color != 'black':
        top.color = self.RGB_BLACK
        bottom.textItem.color = self.RGB_BLACK

    # Fill in language if needed
    if self.layout.lang != "en":
        psd.replace_text(bottom, "EN", self.layout.lang.upper())

    # Fill optional collector star
    if self.is_collector_promo:
        psd.replace_text(bottom, "•", MagicIcons.COLLECTOR_STAR)

    # Apply the collector info
    top.contents = self.layout.collector_data
    psd.replace_text(bottom, "SET", self.layout.set)
    psd.replace_text(bottom, "Artist", self.layout.artist)

collector_info_basic() -> None

Called to generate basic collector info.

Source code in src\templates\_core.py
def collector_info_basic(self) -> None:
    """Called to generate basic collector info."""

    # Collector layers
    artist_layer = psd.getLayer(LAYERS.ARTIST, self.legal_group)
    set_layer = psd.getLayer(LAYERS.SET, self.legal_group)
    set_TI = set_layer.textItem

    # Correct color for non-black border
    if self.border_color != BorderColor.Black:
        set_TI.color = self.RGB_BLACK
        artist_layer.textItem.color = self.RGB_BLACK

    # Fill optional collector star
    if self.is_collector_promo:
        psd.replace_text(set_layer, "•", MagicIcons.COLLECTOR_STAR)

    # Fill language, artist, and set
    if self.layout.lang != "en":
        psd.replace_text(set_layer, "EN", self.layout.lang.upper())
    psd.replace_text(artist_layer, "Artist", self.layout.artist)
    set_TI.contents = self.layout.set + set_TI.contents

color_border() -> None

Color this card's border based on given setting.

Source code in src\templates\_core.py
@try_photoshop
def color_border(self) -> None:
    """Color this card's border based on given setting."""
    if self.border_color != BorderColor.Black:
        psd.apply_fx(self.border_group, [EffectColorOverlay(color=psd.get_color(self.border_color))])

create_basic_watermark() -> None

Builds a basic land watermark.

Source code in src\templates\_core.py
def create_basic_watermark(self) -> None:
    """Builds a basic land watermark."""

    # Generate the watermark
    wm = psd.import_svg(
        path=self.layout.watermark_basic,
        ref=self.text_group,
        placement=ElementPlacement.PlaceAfter,
        docref=self.docref)
    psd.frame_layer_by_height(
        layer=wm,
        ref=self.textbox_reference.dims,
        scale=75)

    # Add effects
    psd.apply_fx(wm, self.basic_watermark_fx)

    # Add snow effects
    if self.is_snow:
        self.add_basic_watermark_snow_effects(wm)

    # Remove rules text step
    self.rules_text_and_pt_layers = lambda: None
    self.layout.oracle_text = ''
    self.layout.flavor_text = ''

create_blended_layer(group: LayerSet, colors: Union[None, str, list[str]] = None, masks: Optional[list[ArtLayer]] = None, **kwargs)

Either enable a single frame layer or create a multicolor layer using a gradient mask.

Parameters:

Name Type Description Default
group LayerSet

Group to look for the color layers within.

required
colors None | str | list[str]

Color layers to look for.

None
masks list[ArtLayer] | None

Masks to use for blending the layers.

None
Source code in src\templates\_core.py
def create_blended_layer(
        self,
        group: LayerSet,
        colors: Union[None, str, list[str]] = None,
        masks: Optional[list[ArtLayer]] = None,
        **kwargs
):
    """Either enable a single frame layer or create a multicolor layer using a gradient mask.

    Args:
        group: Group to look for the color layers within.
        colors: Color layers to look for.
        masks: Masks to use for blending the layers.
    """
    # Ensure masks is a list
    masks = masks or []

    # Establish our colors
    colors = colors or self.identity or self.pinlines
    if isinstance(colors, str) and not is_multicolor_string(colors):
        # Received a color string that isn't a frame color combination
        colors = [colors]
    elif len(colors) >= self.color_limit:
        # Received too big a color combination, revert to pinlines
        colors = [self.pinlines]
    elif isinstance(colors, str):
        # Convert string of colors to list
        colors = list(colors)

    # Single layer
    if len(colors) == 1:
        layer = psd.getLayer(colors[0], group)
        layer.visible = True
        return

    # Enable each layer color
    layers: list[ArtLayer] = []
    for i, color in enumerate(colors):

        # Make layer visible
        layer = psd.getLayer(color, group)
        if 'blend_mode' in kwargs:
            layer.blendMode = kwargs['blend_mode']
        layer.visible = True

        # Position the new layer and add a mask to previous, if previous layer exists
        if layers and len(masks) >= i:
            layer.move(layers[i - 1], ElementPlacement.PlaceAfter)
            psd.copy_layer_mask(masks[i - 1], layers[i - 1])

        # Add to the layer list
        layers.append(layer)

create_blended_solid_color(group: LayerSet, colors: list[ColorObject], masks: Optional[list[Union[ArtLayer, LayerSet]]] = None, **kwargs) -> None

Either enable a single frame layer or create a multicolor layer using a gradient mask.

Parameters:

Name Type Description Default
group LayerSet

Group to look for the color layers within.

required
colors list[ColorObject]

Color layers to look for.

required
masks list[ArtLayer | LayerSet] | None

Masks to use for blending the layers.

None
Source code in src\templates\_core.py
@staticmethod
def create_blended_solid_color(
        group: LayerSet,
        colors: list[ColorObject],
        masks: Optional[list[Union[ArtLayer, LayerSet]]] = None,
        **kwargs
) -> None:
    """Either enable a single frame layer or create a multicolor layer using a gradient mask.

    Args:
        group: Group to look for the color layers within.
        colors: Color layers to look for.
        masks: Masks to use for blending the layers.

    Keyword Args:
        blend_mode (BlendMode): Sets the blend mode of the generated solid color layers.
    """
    # Ensure masks is a list
    masks = masks or []

    # Enable each layer color
    layers: list[ArtLayer] = []
    for i, color in enumerate(colors):
        layer = psd.smart_layer(psd.create_color_layer(color, group))
        if 'blend_mode' in kwargs:
            layer.blendMode = kwargs['blend_mode']

        # Position the new layer and add a mask to previous, if previous layer exists
        if layers and len(masks) >= i:
            layer.move(layers[i - 1], ElementPlacement.PlaceAfter)
            psd.copy_layer_mask(masks[i - 1], layers[i - 1])
        layers.append(layer)

create_watermark() -> None

Builds the watermark.

Source code in src\templates\_core.py
def create_watermark(self) -> None:
    """Builds the watermark."""
    # Required values to generate a Watermark
    if not all([
        self.layout.watermark_svg,
        self.layout.watermark,
        self.textbox_reference,
        self.watermark_colors,
        self.text_group
    ]):
        return

    # Get watermark custom settings if available
    wm_details = CON.watermarks.get(self.layout.watermark, {})

    # Import and frame the watermark
    wm = psd.import_svg(
        path=self.layout.watermark_svg,
        ref=self.text_group,
        placement=ElementPlacement.PlaceAfter,
        docref=self.docref)
    psd.frame_layer(
        layer=wm,
        ref=self.textbox_reference.dims,
        smallest=True,
        scale=wm_details.get('scale', 80))

    # Apply opacity, blending, and effects
    wm.opacity = wm_details.get('opacity', CFG.watermark_opacity)
    wm.blendMode = self.watermark_blend_mode
    psd.apply_fx(wm, self.watermark_fx)

enable_crown() -> None

Enable layers required by the Legendary Crown.

Source code in src\templates\_core.py
def enable_crown(self) -> None:
    """Enable layers required by the Legendary Crown."""
    pass

enable_frame_layers() -> None

Enable the correct layers for this card's frame.

Source code in src\templates\_core.py
def enable_frame_layers(self) -> None:
    """Enable the correct layers for this card's frame."""
    pass

enable_hollow_crown() -> None

Enable layers required by the Hollow Legendary Crown modification

Source code in src\templates\_core.py
def enable_hollow_crown(self) -> None:
    """Enable layers required by the Hollow Legendary Crown modification"""
    pass

execute() -> bool

Perform actions to render the card using this template.

Notes
  • Each action is wrapped in an exception check and breakpoint to cancel the thread if a cancellation signal was sent by the user.
  • Never override this method!
Source code in src\templates\_core.py
def execute(self) -> bool:
    """Perform actions to render the card using this template.

    Notes:
        - Each action is wrapped in an exception check and breakpoint to cancel the thread
            if a cancellation signal was sent by the user.
        - Never override this method!
    """
    # Preliminary Photoshop check
    if not self.run_tasks(
        funcs=[self.check_photoshop],
        message="Unable to reach Photoshop!"
    ):
        return False

    # Pre-process layout data
    if not self.run_tasks(
        funcs=self.pre_render_methods,
        message="Pre-processing layout data failed!"
    ):
        return False

    # Load in the PSD template
    if not self.run_tasks(
        funcs=[self.app.load],
        message="PSD template failed to load!",
        args=[str(self.layout.template_file)]
    ):
        return False

    # Load in artwork and frame it
    if not self.run_tasks(
        funcs=[self.load_artwork],
        message="Unable to load artwork!"
    ):
        return False

    # Load in Scryfall scan and frame it
    if CFG.import_scryfall_scan:
        self.run_tasks(
            funcs=[self.paste_scryfall_scan],
            message="Couldn't import Scryfall scan, continuing without it!",
            warning=True)

    # Add expansion symbol
    self.run_tasks(
        funcs=[self.load_expansion_symbol],
        message="Unable to generate expansion symbol!",
        warning=True)

    # Add watermark
    if CFG.enable_basic_watermark and self.is_basic_land:
        # Basic land watermark
        if not self.run_tasks(
            funcs=[self.create_basic_watermark],
            message="Unable to generate basic land watermark!"
        ):
            return False
    elif CFG.watermark_mode is not WatermarkMode.Disabled:
        # Normal watermark
        if not self.run_tasks(
            funcs=[self.create_watermark],
            message="Unable to generate watermark!"
        ):
            return False

    # Enable layers to build our frame
    if not self.run_tasks(
        funcs=self.frame_layer_methods,
        message="Enabling layers failed!"
    ):
        return False

    # Format text layers
    if not self.run_tasks(
        funcs=[
            *self.text_layer_methods,
            self.format_text_layers,
            *self.post_text_methods
        ],
        message="Formatting text layers failed!"
    ):
        return False

    # Specific hooks
    if not self.run_tasks(
        funcs=self.hooks,
        message="Encountered an error during triggered hooks step!"
    ):
        return False

    # Manual edit step?
    if CFG.exit_early and not ENV.TEST_MODE:
        self.console.await_choice(self.event)

    # Save the document
    if not self.run_tasks(
        funcs=[self.save_mode],
        message="Error during file save process!",
        kwargs={'path': self.output_file_name, 'docref': self.docref}
    ):
        return False

    # Post save methods
    if not self.run_tasks(
        funcs=self.post_save_methods,
        message="Image saved, but an error was encountered during the post-save step!"
    ):
        return False

    # Reset document, return success
    if not ENV.TEST_MODE:
        self.console.update(f"[b]{self.output_file_name.stem}[/b] rendered successfully!")
    self.reset()
    return True

format_text_layers() -> None

Validate and execute each formatted text layer.

Source code in src\templates\_core.py
def format_text_layers(self) -> None:
    """Validate and execute each formatted text layer."""
    for t in self.text:
        # Check for cancelled thread each iteration
        if self.event.is_set():
            return
        # Validate and execute
        if t and t.validate():
            t.execute()

generate_layer(group: Union[ArtLayer, LayerSet], colors: Union[str, ColorObject, list[ColorObject], list[dict]], masks: Optional[list[ArtLayer]] = None, **kwargs) -> Optional[ArtLayer]

Takes information about a frame layer group and routes it to the correct generation function which blends rasterized layers, blends solid color layers, or generates a solid color/gradient adjustment layer.

Notes

The result for a given 'colors' schema: - str: Enable and/or blend one or more texture layers, unless string is a hex color, in which case create a solid color adjustment layer. - list[str]: Blend multiple texture layers. - list[int]: Create a solid color adjustment layer. - list[dict]: Create a gradient adjustment layer. - list[list[int]]: Blend multiple solid color adjustment layers. - list[SolidColor]: Blend multiple solid color adjustment layers.

Parameters:

Name Type Description Default
group ArtLayer | LayerSet

Layer or group containing layers.

required
colors str | ColorObject | list[ColorObject] | list[dict]

Color definition for this frame layer generation.

required
masks list[ArtLayer] | None

Masks used to blend this generated layer.

None
Source code in src\templates\_core.py
def generate_layer(
        self, group: Union[ArtLayer, LayerSet],
        colors: Union[str, ColorObject, list[ColorObject], list[dict]],
        masks: Optional[list[ArtLayer]] = None,
        **kwargs
) -> Optional[ArtLayer]:
    """Takes information about a frame layer group and routes it to the correct
    generation function which blends rasterized layers, blends solid color layers, or
    generates a solid color/gradient adjustment layer.

    Notes:
        The result for a given 'colors' schema:
        - str: Enable and/or blend one or more texture layers, unless string is a hex color, in which
            case create a solid color adjustment layer.
        - list[str]: Blend multiple texture layers.
        - list[int]: Create a solid color adjustment layer.
        - list[dict]: Create a gradient adjustment layer.
        - list[list[int]]: Blend multiple solid color adjustment layers.
        - list[SolidColor]: Blend multiple solid color adjustment layers.

    Args:
        group: Layer or group containing layers.
        colors: Color definition for this frame layer generation.
        masks: Masks used to blend this generated layer.
    """
    # Assign a generator task based on colors value
    if isinstance(colors, str):
        # Example: '#FFFFFF'
        # Single adjustment layer
        if colors.startswith('#'):
            return psd.create_color_layer(
                color=colors,
                layer=group,
                docref=self.docref,
                **kwargs)
        # Example: 'Land'
        # Single or blended texture layers
        return self.create_blended_layer(
            group=group,
            colors=colors,
            masks=masks,
            **kwargs)
    elif isinstance(colors, SolidColor):
        # Example: SolidColor
        # Single adjustment layer
        return psd.create_color_layer(
            color=colors,
            layer=group,
            docref=self.docref,
            **kwargs
        )
    elif isinstance(colors, list):
        if all(isinstance(c, str) for c in colors):
            # Example: ['#000000', '#FFFFFF', ...]
            # Blended RGB/CMYK adjustment layers
            if colors[0].startswith('#'):  # noqa
                return self.create_blended_solid_color(
                    group=group,
                    colors=colors,
                    masks=masks,
                    **kwargs)
            # Example: ['W', 'U']
            # Blended texture layers
            return self.create_blended_layer(
                group=group,
                colors=colors,
                masks=masks,
                **kwargs)
        elif all(isinstance(c, int) for c in colors):
            # Example: [r, g, b]
            # RGB/CMYK adjustment layer
            return psd.create_color_layer(
                color=colors,
                layer=group,
                docref=self.docref,
                **kwargs)
        elif all(isinstance(c, dict) for c in colors):
            # Example: [GradientColor, GradientColor, ...]
            # Gradient adjustment layer
            return psd.create_gradient_layer(
                colors=colors,
                layer=group,
                docref=self.docref,
                **kwargs)
        elif all(isinstance(c, (list, SolidColor)) for c in colors):
            # Example 1: [[r, g, b], [r, g, b], ...]
            # Example 2: [SolidColor, SolidColor, ...]
            # Blended RGB/CMYK adjustment layers
            return self.create_blended_solid_color(
                group=group,
                colors=colors,
                masks=masks,
                **kwargs)

    # Failed to match a recognized color notation
    if group:
        self.log(f"Couldn't generate frame element: '{group.name}'")

get_template_route(layout, **kwargs) -> BaseTemplate

Overwrite this method to reroute a template class to another class under a set of conditions. See the 'IxalanTemplate' class for an example.

Parameters:

Name Type Description Default
layout

The card layout object.

required

Returns:

Type Description
BaseTemplate

Initialized template class object.

Source code in src\templates\_core.py
@classmethod
def get_template_route(cls, layout, **kwargs) -> 'BaseTemplate':
    """Overwrite this method to reroute a template class to another class under a set of
    conditions. See the 'IxalanTemplate' class for an example.

    Args:
        layout: The card layout object.

    Returns:
        Initialized template class object.
    """
    return super().__new__(cls)

hook_creature() -> None

Run this if card is a creature.

Source code in src\templates\_core.py
def hook_creature(self) -> None:
    """Run this if card is a creature."""
    pass

hook_large_mana() -> None

Run this if card has a large mana symbol.

Source code in src\templates\_core.py
def hook_large_mana(self) -> None:
    """Run this if card has a large mana symbol."""
    pass

load_artwork(art_file: Optional[str | Path] = None, art_layer: Optional[ArtLayer] = None, art_reference: Optional[ReferenceLayer] = None) -> None

Loads the specified art file into the specified layer.

Parameters:

Name Type Description Default
art_file str | Path | None

Optional path (as str or Path) to art file. Will use self.layout.art_file if not provided.

None
art_layer ArtLayer | None

Optional ArtLayer where art image should be placed when imported. Will use self.art_layer property if not provided.

None
art_reference ReferenceLayer | None

Optional ReferenceLayer that should be used to position and scale the imported image. Will use self.art_reference property if not provided.`

None
Source code in src\templates\_core.py
def load_artwork(
    self,
    art_file: Optional[str | Path] = None,
    art_layer: Optional[ArtLayer] = None,
    art_reference: Optional[ReferenceLayer] = None
) -> None:
    """Loads the specified art file into the specified layer.

    Args:
        art_file: Optional path (as str or Path) to art file. Will use `self.layout.art_file`
            if not provided.
        art_layer: Optional `ArtLayer` where art image should be placed when imported. Will use `self.art_layer`
            property if not provided.
        art_reference: Optional `ReferenceLayer` that should be used to position and scale the imported
            image. Will use `self.art_reference` property if not provided.`
    """

    # Set default values
    art_file = art_file or self.layout.art_file
    art_layer = art_layer or self.art_layer
    art_reference = art_reference or self.art_reference

    # Check for full-art test image
    if ENV.TEST_MODE and self.is_fullart:
        art_file = PATH.SRC_IMG / "test-fa.jpg"

    # Import art file
    if self.art_action:
        # Use action pipeline
        art_layer = psd.paste_file(
            layer=art_layer,
            path=art_file,
            action=self.art_action,
            action_args=self.art_action_args,
            docref=self.docref)
    else:
        # Use traditional pipeline
        art_layer = psd.import_art(
            layer=art_layer,
            path=art_file,
            docref=self.docref)
    self.active_layer = art_layer

    # Frame the artwork
    psd.frame_layer(
        layer=art_layer,
        ref=art_reference)

    # Perform content aware fill if needed
    if self.is_content_aware_enabled:

        # Perform a generative fill
        if CFG.generative_fill:
            if _doc_generated := psd.generative_fill_edges(
                layer=art_layer,
                feather=CFG.feathered_fill,
                close_doc=bool(not CFG.select_variation),
                docref=self.docref
            ):
                # Document reference was returned, await user intervention
                self.console.await_choice(
                    self.event, msg="Select a Generative Fill variation, then click Continue ...")
                _doc_generated.close(SaveOptions.SaveChanges)
            return

        # Perform a content aware fill
        psd.content_aware_fill_edges(
            layer=art_layer,
            feather=CFG.feathered_fill)

load_expansion_symbol() -> None

Imports and positions the expansion symbol SVG image.

Source code in src\templates\_core.py
def load_expansion_symbol(self) -> None:
    """Imports and positions the expansion symbol SVG image."""

    # Check for expansion symbol disabled
    if not CFG.symbol_enabled or not self.expansion_reference:
        return
    if not self.layout.symbol_svg:
        return self.log("Expansion symbol disabled, SVG file not found.")

    # Try to import the expansion symbol
    try:

        # Import and place the symbol
        svg = psd.import_svg(
            path=str(self.layout.symbol_svg),
            ref=self.expansion_reference,
            placement=ElementPlacement.PlaceBefore,
            docref=self.docref)

        # Frame the symbol
        psd.frame_layer_by_height(
            layer=svg,
            ref=self.expansion_reference,
            alignments=self.expansion_symbol_alignments)

        # Rename and reset property
        svg.name = 'Expansion Symbol'
        self.expansion_symbol_layer = svg

    except Exception as e:
        return self.log('Expansion symbol disabled due to an error.', e)

log(text: str, e: Optional[Exception] = None) -> None

Writes a message to console if test mode isn't enabled, logs an exception if provided.

Parameters:

Name Type Description Default
text str

Message to write to console.

required
e Exception | None

Exception to log if provided.

None
Source code in src\templates\_core.py
def log(self, text: str, e: Optional[Exception] = None) -> None:
    """Writes a message to console if test mode isn't enabled, logs an exception if provided.

    Args:
        text: Message to write to console.
        e: Exception to log if provided.
    """
    if e:
        self.console.log_exception(e)
    if not ENV.TEST_MODE:
        self.console.update(text)

paste_scryfall_scan(rotate: bool = False, visible: bool = False) -> Optional[ArtLayer]

Ensure we rotate the scan for Planar cards.

Source code in src\templates\planar.py
def paste_scryfall_scan(self, rotate: bool = False, visible: bool = False) -> Optional[ArtLayer]:
    """Ensure we rotate the scan for Planar cards."""
    return super().paste_scryfall_scan(rotate=True, visible=visible)

process_layout_data() -> None

Performs any required pre-processing on the provided layout data.

Source code in src\templates\_core.py
def process_layout_data(self) -> None:
    """Performs any required pre-processing on the provided layout data."""

    # Strip flavor text, string or list
    if CFG.remove_flavor:
        self.layout.flavor_text = "" if isinstance(
            self.layout.flavor_text, str
        ) else ['' for _ in self.layout.flavor_text]

    # Strip reminder text, string or list
    if CFG.remove_reminder:
        self.layout.oracle_text = strip_reminder_text(
            self.layout.oracle_text
        ) if isinstance(
            self.layout.oracle_text, str
        ) else [strip_reminder_text(n) for n in self.layout.oracle_text]

raise_error(message: str, error: Optional[Exception] = None) -> None

Raise an error on the console display.

Parameters:

Name Type Description Default
message str

Message to be displayed

required
error Exception | None

Exception object

None
Source code in src\templates\_core.py
def raise_error(self, message: str, error: Optional[Exception] = None) -> None:
    """Raise an error on the console display.

    Args:
        message: Message to be displayed
        error: Exception object
    """
    self.console.log_error(
        thr=self.event,
        card=self.layout.name,
        template=self.layout.template_file,
        msg=f'{msg_error(message)}\n'
            f'Check [b]/logs/error.txt[/b] for details.',
        e=error)
    self.reset()

raise_warning(message: str, error: Exception = None) -> None

Raise a warning on the console display.

Parameters:

Name Type Description Default
message str

Message to be displayed.

required
error Exception

Exception object.

None
Source code in src\templates\_core.py
def raise_warning(self, message: str, error: Exception = None) -> None:
    """Raise a warning on the console display.

    Args:
        message: Message to be displayed.
        error: Exception object.
    """
    if error:
        self.console.log_exception(error)
        message += "\nCheck [b]/logs/error.txt[/b] for details."
    self.console.update(msg_warn(message), exception=error)

redirect_template(template_class: type[BaseTemplate], template_file: Union[str, Path], layout, **kwargs) -> BaseTemplate

Reroutes template initialization to another template class and PSD file.

Parameters:

Name Type Description Default
template_class type[BaseTemplate]

Template class to reroute to.

required
template_file str | Path

Filename of the PSD to load with this template class.

required
layout

The card layout object.

required

Returns:

Type Description
BaseTemplate

Initialized template class object.

Source code in src\templates\_core.py
@staticmethod
def redirect_template(
    template_class: type['BaseTemplate'],
    template_file: Union[str, Path],
    layout,
    **kwargs
) -> 'BaseTemplate':
    """Reroutes template initialization to another template class and PSD file.

    Args:
        template_class: Template class to reroute to.
        template_file: Filename of the PSD to load with this template class.
        layout: The card layout object.

    Returns:
        Initialized template class object.
    """
    if isinstance(template_file, Path):
        layout.template_file = template_file
    elif isinstance(template_file, str):
        layout.template_file = layout.template_file.with_name(template_file)
    return template_class(layout, **kwargs)

reset() -> None

Reset the document, purge the cache, end await.

Source code in src\templates\_core.py
def reset(self) -> None:
    """Reset the document, purge the cache, end await."""
    try:
        if self.docref:
            psd.reset_document(self.docref)
    except PS_EXCEPTIONS:
        pass
    self.console.end_await()

run_tasks(funcs: list[Callable], message: str, warning: bool = False, args: Union[Iterable[Any], None] = None, kwargs: Optional[dict] = None) -> bool

Run a list of functions, checking for thread cancellation and exceptions on each.

Parameters:

Name Type Description Default
funcs list[Callable]

List of functions to perform.

required
message str

Error message to raise if exception occurs.

required
warning bool

Warn the user if True, otherwise raise error.

False
args Iterable[Any] | None

Optional arguments to pass to the func. Empty tuple if not provided.

None
kwargs dict | None

Optional keyword arguments to pass to the func. Empty dict if not provided.

None

Returns:

Type Description
bool

True if tasks completed, False if exception occurs or thread is cancelled.

Source code in src\templates\_core.py
def run_tasks(
        self,
        funcs: list[Callable],
        message: str,
        warning: bool = False,
        args: Union[Iterable[Any], None] = None,
        kwargs: Optional[dict] = None,
) -> bool:
    """Run a list of functions, checking for thread cancellation and exceptions on each.

    Args:
        funcs: List of functions to perform.
        message: Error message to raise if exception occurs.
        warning: Warn the user if True, otherwise raise error.
        args: Optional arguments to pass to the func. Empty tuple if not provided.
        kwargs: Optional keyword arguments to pass to the func. Empty dict if not provided.

    Returns:
        True if tasks completed, False if exception occurs or thread is cancelled.
    """

    # Default args and kwargs
    args = args or ()
    kwargs = kwargs or {}

    # Execute each function
    for func in funcs:
        # Check if thread was cancelled
        if self.event.is_set():
            return False
        try:
            # Run the task
            func(*args, **kwargs)
        except Exception as e:
            # Raise error or warning
            if not warning:
                self.raise_error(message=message, error=e)
                return False
            self.raise_warning(message=message, error=e)
        # Once again, check if thread was cancelled
        if self.event.is_set():
            return False
    return True