Compare commits

...
4 Commits
Author SHA1 Message Date
yyc12345 80738d1254 feat: pre-create all essential directories in advanced context 2026-08-27 13:04:12 +08:00
yyc12345 c54ae438c1 feat: add cli for svg render 2026-08-27 13:03:48 +08:00
yyc12345 f33523259a fix: fix build issue 2026-08-26 20:02:59 +08:00
yyc12345 96e0eab9d2 feat: add dup feature 2026-08-26 19:48:56 +08:00
9 changed files with 152 additions and 54 deletions
+13 -6
View File
@@ -1,25 +1,32 @@
import logging
from pathlib import Path
from .assemblicon.context import Context
from .assemblicon.context import Prerequisite, Context, AdvancedContext
from .assemblicon.utils import setup_logging
from .std_icons import build_standard_icons
from .ext_icons import build_ext_icons
from .nostd_icons import build_non_standard_icons
from .cli import parse
def main() -> None:
opts = parse()
setup_logging()
repository_root = Path(__file__).resolve().parent.parent.parent.parent
iconset_input = repository_root / "src"
iconset_output = repository_root / "artifact"
ctx = Context(iconset_input, iconset_output)
prereq = Prerequisite(
iconset_input, iconset_output, opts.svg_render.to_assemblicon_type()
)
ctx = Context(prereq)
advctx = AdvancedContext(ctx)
try:
_build_icons(ctx)
_build_icons(advctx)
except BaseException as e:
logging.fatal("Runtime error: %s", e)
def _build_icons(ctx: Context) -> None:
def _build_icons(ctx: AdvancedContext) -> None:
build_standard_icons(ctx)
#build_ext_icons(ctx)
build_non_standard_icons(ctx)
@@ -1,4 +1,5 @@
import logging
import shutil
from pathlib import Path
import PIL.Image
from .utils import Artifact, ArtifactFile
@@ -23,3 +24,7 @@ def load_artifact(p: Path) -> ArtifactFile:
def save_artifact(art: Artifact, p: Path) -> None:
logging.info("Saving artifact: %s", p)
art.save(p, format="PNG")
def dup_artifact(src: Path, dst: Path) -> None:
shutil.copyfile(src, dst)
@@ -100,9 +100,9 @@ _ART_MAC: str = "macos"
_DEFAULT_SVG_CONFIG = artrender.SvgConfig(VANILLA_ARTIFACT_HW, VANILLA_ARTIFACT_HW)
_DEFAULT_BLD_CONFIG = artrender.BlenderConfig(VANILLA_ARTIFACT_HW, VANILLA_ARTIFACT_HW)
# TODO: For easy testing, we only build 48x48.
# TODO: For easy testing, we only build 32x32 and 48x48.
# Enable full generation after testing.
_FD_THUMBNAIL_HWS: tuple[int, ...] = (48,)
_FD_THUMBNAIL_HWS: tuple[int, ...] = (32, 48)
# _FD_THUMBNAIL_HWS: tuple[int, ...] = (16, 32, 48, 64, 128, 256)
@@ -112,7 +112,30 @@ class AdvancedContext:
def __init__(self, ctx: Context) -> None:
self.__context = ctx
# construct basic layout of temporary directory and output directory.
# construct basic layout of temporary directory
self.__context.temporary_artifact(_TEMP_USER_DIR).mkdir(
parents=True, exist_ok=True
)
self.__context.temporary_artifact(_TEMP_LIB_DIR, _TEMP_LIB_ALLOC_DIR).mkdir(
parents=True, exist_ok=True
)
self.__context.temporary_artifact(_TEMP_LIB_DIR, _TEMP_LIB_STABLE_DIR).mkdir(
parents=True, exist_ok=True
)
# and output directory
for hw in _FD_THUMBNAIL_HWS:
for kind in FdContext:
self.__context.output_artifact(f"{hw}x{hw}", str(kind)).mkdir(
parents=True, exist_ok=True
)
for kind in WinCategory:
self.__context.output_artifact(_ART_WIN, str(kind)).mkdir(
parents=True, exist_ok=True
)
for kind in MacCategory:
self.__context.output_artifact(_ART_MAC, str(kind)).mkdir(
parents=True, exist_ok=True
)
def __enter__(self) -> "AdvancedContext":
return self
@@ -207,7 +230,10 @@ class AdvancedContext:
self.__intern_render_svg(src, dst, cfg)
def render_svg_then_load(
self, src: Path, dst: Optional[Path], cfg: SvgConfig = _DEFAULT_SVG_CONFIG
self,
src: Path,
dst: Optional[Path] = None,
cfg: SvgConfig = _DEFAULT_SVG_CONFIG,
) -> Artifact:
"""
Render given SVG to PNG file, then load it directly.
@@ -248,7 +274,10 @@ class AdvancedContext:
self.__intern_render_blender(src, dst, cfg)
def render_blender_then_load(
self, src: Path, dst: Optional[Path], cfg: BlenderConfig = _DEFAULT_BLD_CONFIG
self,
src: Path,
dst: Optional[Path] = None,
cfg: BlenderConfig = _DEFAULT_BLD_CONFIG,
) -> Artifact:
"""
Render given Blender composition to PNG file, then load it directly.
@@ -285,6 +314,15 @@ class AdvancedContext:
dst = self.__fd_artifact(hw, kind, name)
artio.save_artifact(thumbnail, dst)
def dup_fd_artifact(
self, src_kind: FdContext, src_name: str, dst_kind: FdContext, dst_name: str
) -> None:
for hw in _FD_THUMBNAIL_HWS:
artio.dup_artifact(
self.__fd_artifact(hw, src_kind, src_name),
self.__fd_artifact(hw, dst_kind, dst_name),
)
def __win_artifact(self, kind: WinCategory, name: str) -> Path:
return self.__context.output_artifact(_ART_WIN, str(kind), f"{name}.ico")
@@ -299,6 +337,14 @@ class AdvancedContext:
)
artrender.render_ico(art, self.__win_artifact(kind, name))
def dup_win_artifact(
self, src_kind: WinCategory, src_name: str, dst_kind: WinCategory, dst_name: str
) -> None:
artio.dup_artifact(
self.__win_artifact(src_kind, src_name),
self.__win_artifact(dst_kind, dst_name),
)
def __mac_artifact(self, kind: MacCategory, name: str) -> Path:
return self.__context.output_artifact(_ART_MAC, str(kind), f"{name}.icns")
@@ -311,4 +357,12 @@ class AdvancedContext:
logging.info("Saving macOS-only artifact with kind %s and name %s.", kind, name)
artrender.render_icns(art, self.__mac_artifact(kind, name))
def dup_mac_artifact(
self, src_kind: MacCategory, src_name: str, dst_kind: MacCategory, dst_name: str
) -> None:
artio.dup_artifact(
self.__mac_artifact(src_kind, src_name),
self.__mac_artifact(dst_kind, dst_name),
)
# endregion
+42
View File
@@ -0,0 +1,42 @@
import argparse
import enum
from dataclasses import dataclass
from .assemblicon import artrender
class SvgRender(enum.StrEnum):
Inkscape = "inkscape"
ReSvg = "resvg"
def to_assemblicon_type(self) -> artrender.SvgRenderKind:
match self:
case SvgRender.Inkscape:
return artrender.SvgRenderKind.Inkscape
case SvgRender.ReSvg:
return artrender.SvgRenderKind.ReSvg
@dataclass(frozen=True)
class Cli:
svg_render: SvgRender
def parse() -> Cli:
parser = argparse.ArgumentParser(
prog="Aurora Icon Set Builder",
description="The Programmatic Icon Assembly Workflow for Aurora Icon Set.",
)
parser.add_argument(
"-s",
"--svg-render",
dest="svg_render",
action="store",
type=SvgRender,
default=SvgRender.Inkscape,
help="The render used for rendering SVG into PNG.",
metavar="RENDER",
)
args = parser.parse_args()
return Cli(
svg_render=args.svg_render,
)
@@ -1,6 +0,0 @@
from ..assemblicon.context import Context
from .image_icons import build_image_icons
def build_ext_icons(ctx: Context) -> None:
build_image_icons(ctx)
@@ -1,5 +1,6 @@
from ..assemblicon.context import Context
from ..assemblicon.context import AdvancedContext
from .mimetype_icons import build_mimetype_icons
def build_non_standard_icons(ctx: Context) -> None:
pass
def build_non_standard_icons(ctx: AdvancedContext) -> None:
build_mimetype_icons(ctx)
@@ -1,7 +1,6 @@
import PIL.Image
import PIL.ImageEnhance
from ..assemblicon.artrender import render_svg, render_ico, SvgConfig
from ..assemblicon.context import Context
from ..assemblicon.context import AdvancedContext, FdContext, WinCategory, MacCategory
from ..assemblicon.utils import Artifact
from ..assemblicon.geometry import MinMaxRect, PosSizeRect
from ..assemblicon.artio import load_input_artifact, save_artifact
@@ -11,23 +10,26 @@ from ..assemblicon.artproc import (
HorizontalAnchor,
)
def build_mimetype_icons(ctx: AdvancedContext) -> None:
_build_image_icons(ctx)
IMAGE_INNER_RECT_MINMAX = MinMaxRect(20 * 4, 52 * 4, 236 * 4, 204 * 4)
IMAGE_INNER_RECT_POSSIZE = IMAGE_INNER_RECT_MINMAX.to_pos_size_rect()
def build_image_icons(ctx: Context) -> None:
def _build_image_icons(ctx: AdvancedContext) -> None:
# build image base
image_base = render_svg(
image_base = ctx.render_svg_then_load(
ctx.input_artifact("component", "image-base.svg"),
ctx.temporary_artifact("component", "image-base.png"),
SvgConfig(1024, 1024),
)
# load image base
with image_base:
_build_jpg_icon(ctx, image_base)
def _build_jpg_icon(ctx: Context, base: Artifact) -> None:
def _build_jpg_icon(ctx: AdvancedContext, base: Artifact) -> None:
jpg_image = base.copy()
jpg_image_inner = load_input_artifact(
@@ -58,10 +60,12 @@ def _build_jpg_icon(ctx: Context, base: Artifact) -> None:
jpg_image.paste(
bestfit_jpg_image_inner, IMAGE_INNER_RECT_POSSIZE.to_pillow_tuple()
)
# build artifacts
save_artifact(jpg_image, ctx.temporary_artifact("ext", "jpg-image.png"))
render_ico(
ctx,
jpg_image,
ctx.output_artifact("ext", "jpg-image.ico"),
)
# save for FD
ctx.save_fd_artifact(jpg_image, FdContext.MimeTypes, "image-jpeg")
# use image/jpeg as the default image icon
ctx.dup_fd_artifact(FdContext.MimeTypes, "image-jpeg", FdContext.MimeTypes, "image-x-generic")
# save for windows and mac
ctx.save_win_artifact(jpg_image, WinCategory.Extension, "jpg")
ctx.save_mac_artifact(jpg_image, MacCategory.Extension, "jpg")
@@ -1,6 +1,6 @@
from ..assemblicon.context import Context
from ..assemblicon.context import AdvancedContext
from .action_icons import build_action_icons
def build_standard_icons(ctx: Context) -> None:
def build_standard_icons(ctx: AdvancedContext) -> None:
build_action_icons(ctx)
@@ -1,33 +1,23 @@
from pathlib import Path
import PIL.Image
from ..assemblicon.artrender import render_svg, SvgConfig
from ..assemblicon.context import Context
from ..assemblicon.utils import Artifact
from ..assemblicon.artio import save_artifact
from ..assemblicon.context import AdvancedContext, FdContext
def build_action_icons(ctx: Context) -> None:
def build_action_icons(ctx: AdvancedContext) -> None:
_build_go_icons(ctx)
def _build_go_icons(ctx: Context) -> None:
def _build_go_icons(ctx: AdvancedContext) -> None:
# build go-* base
go_base = render_svg(
ctx.input_artifact("component", "go-base.svg"),
ctx.temporary_artifact("component", "go-base.png"),
SvgConfig(1024, 1024),
go_base = ctx.render_svg_then_load(
ctx.input_artifact("component", "go-base.svg")
)
# build go-* shining
go_inner_shining = render_svg(
go_inner_shining = ctx.render_svg_then_load(
ctx.input_artifact("component", "go-inner-shining.svg"),
ctx.temporary_artifact("component", "go-inner-shining.png"),
SvgConfig(1024, 1024),
)
# build go-* inner
go_inner = render_svg(
go_inner = ctx.render_svg_then_load(
ctx.input_artifact("component", "go-inner.svg"),
ctx.temporary_artifact("component", "go-inner.png"),
SvgConfig(1024, 1024),
)
with go_base, go_inner_shining, go_inner:
@@ -36,8 +26,9 @@ def _build_go_icons(ctx: Context) -> None:
go = go_base.copy()
# render shining with inner mask
go.paste(go_inner_shining, (0, 0), go_mask)
# save as image
save_artifact(go, ctx.output_artifact(f"go-{part}.png"))
# save as FD action
ctx.save_fd_artifact(go, FdContext.Actions, f'go-{part}')
# rotate it for next calling
go_mask = go_mask.transpose(PIL.Image.Transpose.ROTATE_90)