refactor: basically finish core refactor

This commit is contained in:
2026-09-02 13:36:41 +08:00
parent bcac59ceb1
commit 63ba6d44f7
11 changed files with 244 additions and 117 deletions
+37 -14
View File
@@ -1,11 +1,39 @@
import logging
from pathlib import Path from pathlib import Path
from dataclasses import dataclass from dataclasses import dataclass
from .assemblicon.cli import ArgParserSetup, create_argparser, parse_standard_opts from typing import Optional
from .assemblicon.cli import (
SvgRenderKind,
StdOptsSetup,
create_argparser,
parse_standard_opts,
)
from .assemblicon.highlevel.environment import Prologue, Environment from .assemblicon.highlevel.environment import Prologue, Environment
from .assemblicon.highlevel.context import Context from .assemblicon.highlevel.context import Context
from .std_icons import build_standard_icons from .assemblicon.logger import APP_LOGGER as LOGGER
from .nostd_icons import build_non_standard_icons from . import std_icons, nostd_icons
@dataclass(frozen=True)
class AuroraOpts:
selection: Optional[list[str]]
"""The list holding all name of jobs for building."""
svg_render: SvgRenderKind
"""The render kind for SVG file."""
def parse_opts() -> AuroraOpts:
setup = StdOptsSetup(require_input=False, require_output=False)
parser = create_argparser(setup)
parser.prog = "Aurora Icon Set Builder"
parser.description = "The Programmatic Icon Assembly Workflow for Aurora Icon Set."
ns = parser.parse_args()
opts = parse_standard_opts(setup, ns)
return AuroraOpts(
selection=opts.selection, svg_render=opts.svg_render.to_assemblicon_type()
)
@dataclass(frozen=True) @dataclass(frozen=True)
@@ -16,17 +44,12 @@ class AuroraPrologue(Prologue):
def main() -> None: def main() -> None:
parser_cfg = ArgParserSetup(require_input=False, require_output=False) opts = parse_opts()
parser = create_argparser(parser_cfg)
ns = parser.parse_args()
opts = parse_standard_opts(parser_cfg, ns)
repository_root = Path(__file__).resolve().parent.parent.parent.parent repository_root = Path(__file__).resolve().parent.parent.parent.parent
iconset_input = repository_root / "src" iconset_input = repository_root / "src"
iconset_output = repository_root / "artifact" iconset_output = repository_root / "artifact"
prologue = AuroraPrologue( prologue = AuroraPrologue(iconset_input, iconset_output, opts.svg_render)
iconset_input, iconset_output, opts.svg_render.to_assemblicon_type()
)
env = Environment(prologue) env = Environment(prologue)
ctx = Context(env) ctx = Context(env)
@@ -34,9 +57,9 @@ def main() -> None:
register(ctx) register(ctx)
ctx.run(opts.selection) ctx.run(opts.selection)
except BaseException as e: except BaseException as e:
logging.fatal("Runtime error: %s", e) LOGGER.fatal("Runtime error: %s", e)
def register(ctx: Context) -> None: def register(ctx: Context) -> None:
build_standard_icons(ctx) std_icons.register(ctx)
build_non_standard_icons(ctx) nostd_icons.register(ctx)
@@ -33,14 +33,14 @@ class StdOpts:
@dataclass(frozen=True) @dataclass(frozen=True)
class ArgParserSetup: class StdOptsSetup:
require_input: bool require_input: bool
"""Whether add input path option in argument parser.""" """Whether add input path option in argument parser."""
require_output: bool require_output: bool
"""Whether add outputput path option in argument parser.""" """Whether add outputput path option in argument parser."""
def create_argparser(cfg: ArgParserSetup) -> ArgumentParser: def create_argparser(setup: StdOptsSetup) -> ArgumentParser:
""" """
Create Assemblicon standard argument parser. Create Assemblicon standard argument parser.
@@ -54,7 +54,7 @@ def create_argparser(cfg: ArgParserSetup) -> ArgumentParser:
prog="Assemblicon", prog="Assemblicon",
description="The Programmatic Icon Assembly Workflow.", description="The Programmatic Icon Assembly Workflow.",
) )
if cfg.require_input: if setup.require_input:
parser.add_argument( parser.add_argument(
"-i", "-i",
"--input", "--input",
@@ -65,7 +65,7 @@ def create_argparser(cfg: ArgParserSetup) -> ArgumentParser:
help="The path to input directory.", help="The path to input directory.",
metavar="DIR", metavar="DIR",
) )
if cfg.require_output: if setup.require_output:
parser.add_argument( parser.add_argument(
"-o", "-o",
"--output", "--output",
@@ -99,7 +99,7 @@ def create_argparser(cfg: ArgParserSetup) -> ArgumentParser:
return parser return parser
def parse_standard_opts(cfg: ArgParserSetup, ns: Namespace) -> StdOpts: def parse_standard_opts(cfg: StdOptsSetup, ns: Namespace) -> StdOpts:
return StdOpts( return StdOpts(
input_directory=ns.input if cfg.require_input else None, input_directory=ns.input if cfg.require_input else None,
output_directory=ns.output if cfg.require_output else None, output_directory=ns.output if cfg.require_output else None,
@@ -1,60 +1,121 @@
import math
from dataclasses import dataclass from dataclasses import dataclass
@dataclass @dataclass
class MinMaxRect: class Point:
x1: int """A point (or vector) on 2D plane with integer coordinates."""
y1: int
x2: int
y2: int
def to_pos_size_rect(self) -> "PosSizeRect":
return PosSizeRect(
x=self.x1, y=self.y1, width=self.x2 - self.x1, height=self.y2 - self.y1
)
def to_pillow_tuple(self) -> tuple[int, int, int, int]:
return (self.x1, self.y1, self.x2, self.y2)
@dataclass
class PosSizeRect:
x: int x: int
y: int y: int
width: int
height: int
def to_min_max_rect(self) -> MinMaxRect: @staticmethod
return MinMaxRect( def from_tuple(t: tuple[int, int]) -> "Point":
x1=self.x, y1=self.y, x2=self.x + self.width, y2=self.y + self.height return Point(t[0], t[1])
)
def to_pillow_tuple(self) -> tuple[int, int]: def __add__(self, other: "Point") -> "Point":
if not isinstance(other, Point):
return NotImplemented
return Point(self.x + other.x, self.y + other.y)
def __sub__(self, other: "Point") -> "Point":
if not isinstance(other, Point):
return NotImplemented
return Point(self.x - other.x, self.y - other.y)
def __neg__(self) -> "Point":
return Point(-self.x, -self.y)
def __mul__(self, other: int | float) -> "Point":
if not isinstance(other, (int, float)):
return NotImplemented
return Point(math.floor(self.x * other), math.floor(self.y * other))
def __rmul__(self, other: int | float) -> "Point":
return self.__mul__(other)
def __floordiv__(self, other: int | float) -> "Point":
if not isinstance(other, (int, float)):
return NotImplemented
return Point(math.floor(self.x / other), math.floor(self.y / other))
def to_tuple(self) -> tuple[int, int]:
"""
Create the tuple representing this point by ``(x, y)``
which can be used by Pillow.
"""
return (self.x, self.y) return (self.x, self.y)
@dataclass
class MarginRect:
top: int
right: int
bottom: int
left: int
def to_min_max_rect(self, width: int, height: int) -> MinMaxRect:
return MinMaxRect(
x1=self.left,
y1=self.top,
x2=width - self.right,
y2=height - self.bottom,
)
@dataclass @dataclass
class PaddingRect: class Rectangle:
top: int """
right: int A rectangle framed by two corner points.
bottom: int
left: int
def to_min_max_rect(self, x: int, y: int) -> MinMaxRect: pos1 is always assumed to be the top-left corner and pos2 the
return MinMaxRect( bottom-right one. This relation is a pure convention and is never
x1=x - self.left, y1=y - self.top, x2=x + self.right, y2=y + self.bottom validated: constructing a Rectangle with swapped or inverted corners
) produces a meaningless-but-silent result.
"""
pos1: Point
pos2: Point
@staticmethod
def from_tuple(t: tuple[int, int, int, int]) -> "Rectangle":
return Rectangle(Point(t[0], t[1]), Point(t[2], t[3]))
@staticmethod
def from_min_max_rect(pos1: Point, pos2: Point) -> "Rectangle":
return Rectangle(pos1, pos2)
@staticmethod
def from_pos_size_rect(pos: Point, width: int, height: int) -> "Rectangle":
return Rectangle(pos, pos + Point(width, height))
@staticmethod
def from_margin_rect(
width: int, height: int, top: int, right: int, bottom: int, left: int
) -> "Rectangle":
"""
TODO
:param width: The width of parent.
:param height: The height of parent.
:param top: The distance from this rectangle to the top border of parent.
:param right: The distance from this rectangle to the right border of parent.
:param bottom: The distance from this rectangle to the bottom border of parent.
:param left: The distance from this rectangle to the top border of parent.
"""
return Rectangle(Point(left, top), Point(width - right, height - bottom))
@staticmethod
def from_padding_rect(
x: int, y: int, top: int, right: int, bottom: int, left: int
) -> "Rectangle":
"""
TODO
:param x: The X factor of reference point.
:param y: The Y factor of reference point.
:param top: The distance from reference point to this rectangle's top border.
:param right: The distance from reference point to this rectangle's right border.
:param bottom: The distance from reference point to this rectangle's bottom border.
:param left: The distance from reference point to this rectangle's left border.
"""
return Rectangle(Point(x - left, y - top), Point(x + right, y + bottom))
@property
def width(self) -> int:
return self.pos2.x - self.pos1.x
@property
def height(self) -> int:
return self.pos2.y - self.pos1.y
def to_tuple(self) -> tuple[int, int, int, int]:
"""
Create the tuple representing this rectangle by ``(x1, y2 x2, y2)``
which can be used by Pillow.
"""
return (self.pos1.x, self.pos1.y, self.pos2.x, self.pos2.y)
@@ -1,29 +1,32 @@
from collections import deque from collections import deque
from dataclasses import dataclass from dataclasses import dataclass
from typing import Callable, Iterable from typing import Callable, Iterable, TypeVar, Generic
from .environment import Environment from .environment import Environment, Prologue
from ..logger import HIGHLEVEL_LOGGER as LOGGER from ..logger import HIGHLEVEL_LOGGER as LOGGER
P = TypeVar("P", bound=Prologue)
@dataclass(frozen=True) @dataclass(frozen=True)
class Job: class Job(Generic[P]):
"""The class represents a job.""" """The class represents a job."""
name: str name: str
"""The name of this job.""" """The name of this job."""
dependencies: set[str] dependencies: set[str]
"""The dependencies of this job. Each item is a job name. Empty for no dependencies.""" """The dependencies of this job. Each item is a job name. Empty for no dependencies."""
executor: Callable[[Environment], None] executor: Callable[[Environment[P]], None]
"""The executor of this job.""" """The executor of this job."""
class Context: class Context(Generic[P]):
__jobs: dict[str, Job] __jobs: dict[str, Job[P]]
"""The dict storing all jobs. Key is job name and value is job payload.""" """The dict storing all jobs. Key is job name and value is job payload."""
__env: Environment __env: Environment[P]
"""The shared environment passed to every job executor.""" """The shared environment passed to every job executor."""
def __init__(self, env: Environment) -> None: def __init__(self, env: Environment[P]) -> None:
self.__jobs = {} self.__jobs = {}
self.__env = env self.__env = env
@@ -92,7 +95,7 @@ class Context:
self, self,
name: str, name: str,
deps: Iterable[str], deps: Iterable[str],
exec: Callable[[Environment], None], exec: Callable[[Environment[P]], None],
) -> None: ) -> None:
""" """
Register a new job with given name, dependencies and executor. Register a new job with given name, dependencies and executor.
@@ -6,9 +6,8 @@ from types import TracebackType
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path from pathlib import Path
from typing import TypeVar, Generic from typing import TypeVar, Generic
from .utils import Artifact, ArtifactFile, VART_HW from .utils import Artifact, ArtifactFile, VART_HW, check_vart
from ..logger import HIGHLEVEL_LOGGER as LOGGER from ..logger import HIGHLEVEL_LOGGER as LOGGER
from ..lowlevel.artrdr import SvgRenderKind
from ..lowlevel.artrdr import SvgRenderKind, SvgConfig, BlenderConfig from ..lowlevel.artrdr import SvgRenderKind, SvgConfig, BlenderConfig
from ..lowlevel import artio, artrdr from ..lowlevel import artio, artrdr
@@ -120,6 +119,14 @@ class Environment(Generic[P]):
# endregion # endregion
# region: Prologue Visitor
@property
def prologue(self) -> P:
return self.__prologue
# endregion
# region: New Artifact # region: New Artifact
def new_artifact(self) -> Artifact: def new_artifact(self) -> Artifact:
@@ -309,6 +316,8 @@ class Environment(Generic[P]):
'Saving FreeDesktop artifact with kind "%s" and name "%s".', kind, name 'Saving FreeDesktop artifact with kind "%s" and name "%s".', kind, name
) )
check_vart(art)
for hw, thumbnail in zip( for hw, thumbnail in zip(
_FD_THUMBNAIL_HWS, artrdr.render_thumbnail(art, _FD_THUMBNAIL_HWS) _FD_THUMBNAIL_HWS, artrdr.render_thumbnail(art, _FD_THUMBNAIL_HWS)
): ):
@@ -343,6 +352,7 @@ class Environment(Generic[P]):
LOGGER.info( LOGGER.info(
'Saving Windows-only artifact with kind "%s" and name "%s".', kind, name 'Saving Windows-only artifact with kind "%s" and name "%s".', kind, name
) )
check_vart(art)
artrdr.render_ico(art, self.__win_artifact(kind, name)) artrdr.render_ico(art, self.__win_artifact(kind, name))
def dup_win_artifact( def dup_win_artifact(
@@ -372,6 +382,7 @@ class Environment(Generic[P]):
LOGGER.info( LOGGER.info(
'Saving macOS-only artifact with kind "%s" and name "%s".', kind, name 'Saving macOS-only artifact with kind "%s" and name "%s".', kind, name
) )
check_vart(art)
artrdr.render_icns(art, self.__mac_artifact(kind, name)) artrdr.render_icns(art, self.__mac_artifact(kind, name))
def dup_mac_artifact( def dup_mac_artifact(
@@ -4,7 +4,7 @@ VART_HW: int = 1024
"""The height or width of Vanilla Artifact.""" """The height or width of Vanilla Artifact."""
def check_vanilla_artifact(art: Artifact) -> None: def check_vart(art: Artifact) -> None:
if art.size != (VART_HW, VART_HW): if art.size != (VART_HW, VART_HW):
raise ValueError( raise ValueError(
f"The size of general highlevel used artifact must be {VART_HW}x{VART_HW}, got {art.width}x{art.height} instead" f"The size of general highlevel used artifact must be {VART_HW}x{VART_HW}, got {art.width}x{art.height} instead"
@@ -8,6 +8,9 @@ class LoggerFactory:
format="[%(levelname)s][%(name)s] %(message)s", level=logging.INFO format="[%(levelname)s][%(name)s] %(message)s", level=logging.INFO
) )
def get_app_logger(self) -> logging.Logger:
return logging.getLogger("App")
def get_lowlevel_logger(self) -> logging.Logger: def get_lowlevel_logger(self) -> logging.Logger:
return logging.getLogger("Lowlevel") return logging.getLogger("Lowlevel")
@@ -18,3 +21,4 @@ class LoggerFactory:
LOGGER_FACTORY = LoggerFactory() LOGGER_FACTORY = LoggerFactory()
LOWLEVEL_LOGGER = LOGGER_FACTORY.get_lowlevel_logger() LOWLEVEL_LOGGER = LOGGER_FACTORY.get_lowlevel_logger()
HIGHLEVEL_LOGGER = LOGGER_FACTORY.get_highlevel_logger() HIGHLEVEL_LOGGER = LOGGER_FACTORY.get_highlevel_logger()
APP_LOGGER = LOGGER_FACTORY.get_app_logger()
@@ -1,6 +1,7 @@
import enum import enum
import math import math
from .utils import Artifact from .utils import Artifact
from ..geometry import Point
class HorizontalAnchor(enum.IntEnum): class HorizontalAnchor(enum.IntEnum):
@@ -15,14 +16,32 @@ class VerticalAnchor(enum.IntEnum):
Bottom = enum.auto() Bottom = enum.auto()
def _anchor_offset(oversize: int, anchor: HorizontalAnchor | VerticalAnchor) -> int: def anchor_to_offset(
match anchor: oversize: Point,
case HorizontalAnchor.Left | VerticalAnchor.Top: horizontal_anchor: HorizontalAnchor,
return 0 vertical_anchor: VerticalAnchor,
case HorizontalAnchor.Center | VerticalAnchor.Center: ) -> Point:
return oversize // 2 half_oversize = oversize // 2
case HorizontalAnchor.Right | VerticalAnchor.Bottom:
return oversize x: int
match horizontal_anchor:
case HorizontalAnchor.Left:
x = 0
case HorizontalAnchor.Center:
x = half_oversize.x
case HorizontalAnchor.Right:
x = oversize.x
y: int
match vertical_anchor:
case VerticalAnchor.Top:
y = 0
case VerticalAnchor.Center:
y = half_oversize.y
case VerticalAnchor.Bottom:
y = oversize.y
return Point(x, y)
def aspect_ratio_scale_and_crop( def aspect_ratio_scale_and_crop(
@@ -45,16 +64,20 @@ def aspect_ratio_scale_and_crop(
scaled_height = math.ceil(art.height * scale) scaled_height = math.ceil(art.height * scale)
scaled = art.resize((scaled_width, scaled_height), scale_resample) scaled = art.resize((scaled_width, scaled_height), scale_resample)
crop_x = _anchor_offset(scaled_width - target_width, crop_horizontal_anchor) crop = anchor_to_offset(
crop_y = _anchor_offset(scaled_height - target_height, crop_vertical_anchor) Point(scaled_width - target_width, scaled_height - target_height),
crop_horizontal_anchor,
crop_vertical_anchor,
)
(crop_x, crop_y) = crop.to_tuple()
return scaled.crop((crop_x, crop_y, crop_x + target_width, crop_y + target_height)) return scaled.crop((crop_x, crop_y, crop_x + target_width, crop_y + target_height))
def anchor_to_pos( def art_anchor_to_offset(
art: Artifact, art: Artifact,
horizontal_anchor: HorizontalAnchor, horizontal_anchor: HorizontalAnchor,
vertical_anchor: VerticalAnchor, vertical_anchor: VerticalAnchor,
) -> tuple[int, int]: ) -> Point:
""" """
Resolve the anchor point position inside given artifact. Resolve the anchor point position inside given artifact.
@@ -65,17 +88,16 @@ def anchor_to_pos(
anchors this is the corresponding edge; for Right/Bottom anchors anchors this is the corresponding edge; for Right/Bottom anchors
this is just outside the artifact (the exact corner coordinate). this is just outside the artifact (the exact corner coordinate).
""" """
return ( return anchor_to_offset(
_anchor_offset(art.width, horizontal_anchor), Point.from_tuple(art.size), horizontal_anchor, vertical_anchor
_anchor_offset(art.height, vertical_anchor),
) )
def align_paste( def align_paste(
art: Artifact, art: Artifact,
clipboard: Artifact, clipboard: Artifact,
art_pos: tuple[int, int], art_pos: Point,
clipboard_pos: tuple[int, int], clipboard_pos: Point,
) -> None: ) -> None:
""" """
Paste clipboard onto given artifact with two anchor points aligned. Paste clipboard onto given artifact with two anchor points aligned.
@@ -93,21 +115,16 @@ def align_paste(
:param clipboard_pos: The (x, y) point on the clipboard to be :param clipboard_pos: The (x, y) point on the clipboard to be
aligned, usually resolved by anchor_to_pos. aligned, usually resolved by anchor_to_pos.
""" """
art.paste( art.paste(clipboard, (art_pos - clipboard_pos).to_tuple())
clipboard,
(art_pos[0] - clipboard_pos[0], art_pos[1] - clipboard_pos[1]),
)
def align_alpha_composite( def align_alpha_composite(
art: Artifact, art: Artifact,
clipboard: Artifact, clipboard: Artifact,
art_pos: tuple[int, int], art_pos: Point,
clipboard_pos: tuple[int, int], clipboard_pos: Point,
) -> None: ) -> None:
""" """
Alpha-supported ``align_paste``. Alpha-supported ``align_paste``.
""" """
art.alpha_composite( art.alpha_composite(clipboard, (art_pos - clipboard_pos).to_tuple())
clipboard, (art_pos[0] - clipboard_pos[0], art_pos[1] - clipboard_pos[1])
)
@@ -1,4 +1,3 @@
import logging
import os import os
import enum import enum
import subprocess import subprocess
@@ -6,7 +5,8 @@ from typing import Iterable, Iterator
from pathlib import Path from pathlib import Path
from dataclasses import dataclass from dataclasses import dataclass
import PIL.Image import PIL.Image
from .utils import Artifact, check_vanilla_artifact from .utils import Artifact
from ..logger import LOWLEVEL_LOGGER as LOGGER
class SvgRenderKind(enum.IntEnum): class SvgRenderKind(enum.IntEnum):
@@ -39,7 +39,7 @@ def render_svg(src: Path, dst: Path, kind: SvgRenderKind, cfg: SvgConfig) -> Non
:param kind: The selected SVG render kind. :param kind: The selected SVG render kind.
:param cfg: The configuration when rendering. :param cfg: The configuration when rendering.
""" """
logging.debug( LOGGER.debug(
"Rendering SVG artifact with render %s: %s -> %s", kind.name, src, dst "Rendering SVG artifact with render %s: %s -> %s", kind.name, src, dst
) )
@@ -141,7 +141,7 @@ def render_blender(src: Path, dst: Path, temp_script: Path, cfg: BlenderConfig)
The caller must make sure that this file is immutable during calling this function. The caller must make sure that this file is immutable during calling this function.
:param cfg: The configuration when rendering. :param cfg: The configuration when rendering.
""" """
logging.debug("Rendering Blender artifact: %s -> %s", src, dst) LOGGER.debug("Rendering Blender artifact: %s -> %s", src, dst)
if not src.is_file(): if not src.is_file():
raise ValueError( raise ValueError(
@@ -178,9 +178,19 @@ def render_thumbnail(art: Artifact, hws: Iterable[int]) -> Iterator[Artifact]:
:param hws: The int iterable holding all resolution of thumbnails (square thumbnail only) :param hws: The int iterable holding all resolution of thumbnails (square thumbnail only)
:return: A generator outputing thumbnail one by one with the order of given resolution. :return: A generator outputing thumbnail one by one with the order of given resolution.
""" """
check_vanilla_artifact(art) LOGGER.debug("Rendering thumbnail...")
# check whether given artifact is square rectangle.
if art.width != art.height:
raise ValueError(f'Thumbnail render only accept square rectangle.')
# scale artifact up to the maximum resolution hws if it is too small.
max_hws = max(hws)
if art.width < max_hws:
art = art.resize((max_hws, max_hws), PIL.Image.Resampling.LANCZOS)
# render thumbnail
for hw in hws: for hw in hws:
logging.debug("Rendering thumbnail %dx%d.", hw, hw) LOGGER.debug("Rendering thumbnail %dx%d.", hw, hw)
thumbnail = art.copy() thumbnail = art.copy()
thumbnail.thumbnail((hw, hw), PIL.Image.Resampling.LANCZOS) thumbnail.thumbnail((hw, hw), PIL.Image.Resampling.LANCZOS)
yield thumbnail yield thumbnail
@@ -193,9 +203,8 @@ def render_ico(art: Artifact, dst: Path) -> None:
:param art: The artifact to be rendered. :param art: The artifact to be rendered.
:param dst: The path to rendered result. :param dst: The path to rendered result.
""" """
logging.debug("Rendering .ICO artifact: %s", dst) LOGGER.debug("Rendering .ICO artifact: %s", dst)
check_vanilla_artifact(art)
sizes = [16, 32, 48, 64, 128, 256] sizes = [16, 32, 48, 64, 128, 256]
# provide every frame explicitly so that we never depend on # provide every frame explicitly so that we never depend on
# undocumented plugin-side resizing behavior # undocumented plugin-side resizing behavior
@@ -215,9 +224,8 @@ def render_icns(art: Artifact, dst: Path) -> None:
:param art: The artifact to be rendered. :param art: The artifact to be rendered.
:param dst: The path to rendered result. :param dst: The path to rendered result.
""" """
logging.debug("Rendering .ICNS artifact: %s", dst) LOGGER.debug("Rendering .ICNS artifact: %s", dst)
check_vanilla_artifact(art)
# provide every smaller frame explicitly; the 1024x1024 entry is # provide every smaller frame explicitly; the 1024x1024 entry is
# covered by the art itself # covered by the art itself
frames = list(render_thumbnail(art, (512, 256, 128, 64, 32))) frames = list(render_thumbnail(art, (512, 256, 128, 64, 32)))
@@ -1,10 +1,10 @@
from ..assemblicon.context import AdvancedContext from ..assemblicon.highlevel.context import Context
from .action_icons import build_action_icons from .action_icons import build_action_icons
from .mimetype_icons import build_mimetype_icons from .mimetype_icons import build_mimetype_icons
from .application_icons import build_application_icons from .application_icons import build_application_icons
def build_non_standard_icons(ctx: AdvancedContext) -> None: def register(ctx: Context) -> None:
build_mimetype_icons(ctx) build_mimetype_icons(ctx)
build_application_icons(ctx) build_application_icons(ctx)
build_action_icons(ctx) build_action_icons(ctx)
@@ -1,8 +1,8 @@
from ..assemblicon.context import AdvancedContext from ..assemblicon.highlevel.context import Context
from .action_icons import build_action_icons from .action_icons import build_action_icons
from .status_icons import build_status_icons from .status_icons import build_status_icons
def build_standard_icons(ctx: AdvancedContext) -> None: def register(ctx: Context) -> None:
build_action_icons(ctx) build_action_icons(ctx)
build_status_icons(ctx) build_status_icons(ctx)