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 import logging
from pathlib import Path from pathlib import Path
from .assemblicon.context import Context from .assemblicon.context import Prerequisite, Context, AdvancedContext
from .assemblicon.utils import setup_logging from .assemblicon.utils import setup_logging
from .std_icons import build_standard_icons 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: def main() -> None:
opts = parse()
setup_logging() setup_logging()
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"
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: try:
_build_icons(ctx) _build_icons(advctx)
except BaseException as e: except BaseException as e:
logging.fatal("Runtime error: %s", e) logging.fatal("Runtime error: %s", e)
def _build_icons(ctx: Context) -> None: def _build_icons(ctx: AdvancedContext) -> None:
build_standard_icons(ctx) build_standard_icons(ctx)
#build_ext_icons(ctx) build_non_standard_icons(ctx)
@@ -1,4 +1,5 @@
import logging import logging
import shutil
from pathlib import Path from pathlib import Path
import PIL.Image import PIL.Image
from .utils import Artifact, ArtifactFile from .utils import Artifact, ArtifactFile
@@ -23,3 +24,7 @@ def load_artifact(p: Path) -> ArtifactFile:
def save_artifact(art: Artifact, p: Path) -> None: def save_artifact(art: Artifact, p: Path) -> None:
logging.info("Saving artifact: %s", p) logging.info("Saving artifact: %s", p)
art.save(p, format="PNG") 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_SVG_CONFIG = artrender.SvgConfig(VANILLA_ARTIFACT_HW, VANILLA_ARTIFACT_HW)
_DEFAULT_BLD_CONFIG = artrender.BlenderConfig(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. # 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) # _FD_THUMBNAIL_HWS: tuple[int, ...] = (16, 32, 48, 64, 128, 256)
@@ -112,7 +112,30 @@ class AdvancedContext:
def __init__(self, ctx: Context) -> None: def __init__(self, ctx: Context) -> None:
self.__context = ctx 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": def __enter__(self) -> "AdvancedContext":
return self return self
@@ -207,7 +230,10 @@ class AdvancedContext:
self.__intern_render_svg(src, dst, cfg) self.__intern_render_svg(src, dst, cfg)
def render_svg_then_load( 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: ) -> Artifact:
""" """
Render given SVG to PNG file, then load it directly. Render given SVG to PNG file, then load it directly.
@@ -248,7 +274,10 @@ class AdvancedContext:
self.__intern_render_blender(src, dst, cfg) self.__intern_render_blender(src, dst, cfg)
def render_blender_then_load( 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: ) -> Artifact:
""" """
Render given Blender composition to PNG file, then load it directly. Render given Blender composition to PNG file, then load it directly.
@@ -285,6 +314,15 @@ class AdvancedContext:
dst = self.__fd_artifact(hw, kind, name) dst = self.__fd_artifact(hw, kind, name)
artio.save_artifact(thumbnail, dst) 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: def __win_artifact(self, kind: WinCategory, name: str) -> Path:
return self.__context.output_artifact(_ART_WIN, str(kind), f"{name}.ico") 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)) 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: def __mac_artifact(self, kind: MacCategory, name: str) -> Path:
return self.__context.output_artifact(_ART_MAC, str(kind), f"{name}.icns") 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) logging.info("Saving macOS-only artifact with kind %s and name %s.", kind, name)
artrender.render_icns(art, self.__mac_artifact(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 # 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: def build_non_standard_icons(ctx: AdvancedContext) -> None:
pass build_mimetype_icons(ctx)
@@ -1,7 +1,6 @@
import PIL.Image import PIL.Image
import PIL.ImageEnhance import PIL.ImageEnhance
from ..assemblicon.artrender import render_svg, render_ico, SvgConfig from ..assemblicon.context import AdvancedContext, FdContext, WinCategory, MacCategory
from ..assemblicon.context import Context
from ..assemblicon.utils import Artifact from ..assemblicon.utils import Artifact
from ..assemblicon.geometry import MinMaxRect, PosSizeRect from ..assemblicon.geometry import MinMaxRect, PosSizeRect
from ..assemblicon.artio import load_input_artifact, save_artifact from ..assemblicon.artio import load_input_artifact, save_artifact
@@ -11,23 +10,26 @@ from ..assemblicon.artproc import (
HorizontalAnchor, 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_MINMAX = MinMaxRect(20 * 4, 52 * 4, 236 * 4, 204 * 4)
IMAGE_INNER_RECT_POSSIZE = IMAGE_INNER_RECT_MINMAX.to_pos_size_rect() 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 # build image base
image_base = render_svg( image_base = ctx.render_svg_then_load(
ctx.input_artifact("component", "image-base.svg"), ctx.input_artifact("component", "image-base.svg"),
ctx.temporary_artifact("component", "image-base.png"),
SvgConfig(1024, 1024),
) )
# load image base # load image base
with image_base: with image_base:
_build_jpg_icon(ctx, 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 = base.copy()
jpg_image_inner = load_input_artifact( jpg_image_inner = load_input_artifact(
@@ -58,10 +60,12 @@ def _build_jpg_icon(ctx: Context, base: Artifact) -> None:
jpg_image.paste( jpg_image.paste(
bestfit_jpg_image_inner, IMAGE_INNER_RECT_POSSIZE.to_pillow_tuple() bestfit_jpg_image_inner, IMAGE_INNER_RECT_POSSIZE.to_pillow_tuple()
) )
# build artifacts # build artifacts
save_artifact(jpg_image, ctx.temporary_artifact("ext", "jpg-image.png")) # save for FD
render_ico( ctx.save_fd_artifact(jpg_image, FdContext.MimeTypes, "image-jpeg")
ctx, # use image/jpeg as the default image icon
jpg_image, ctx.dup_fd_artifact(FdContext.MimeTypes, "image-jpeg", FdContext.MimeTypes, "image-x-generic")
ctx.output_artifact("ext", "jpg-image.ico"), # 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 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) build_action_icons(ctx)
@@ -1,33 +1,23 @@
from pathlib import Path
import PIL.Image import PIL.Image
from ..assemblicon.artrender import render_svg, SvgConfig from ..assemblicon.context import AdvancedContext, FdContext
from ..assemblicon.context import Context
from ..assemblicon.utils import Artifact
from ..assemblicon.artio import save_artifact
def build_action_icons(ctx: Context) -> None: def build_action_icons(ctx: AdvancedContext) -> None:
_build_go_icons(ctx) _build_go_icons(ctx)
def _build_go_icons(ctx: Context) -> None: def _build_go_icons(ctx: AdvancedContext) -> None:
# build go-* base # build go-* base
go_base = render_svg( go_base = ctx.render_svg_then_load(
ctx.input_artifact("component", "go-base.svg"), ctx.input_artifact("component", "go-base.svg")
ctx.temporary_artifact("component", "go-base.png"),
SvgConfig(1024, 1024),
) )
# build go-* shining # 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.input_artifact("component", "go-inner-shining.svg"),
ctx.temporary_artifact("component", "go-inner-shining.png"),
SvgConfig(1024, 1024),
) )
# build go-* inner # build go-* inner
go_inner = render_svg( go_inner = ctx.render_svg_then_load(
ctx.input_artifact("component", "go-inner.svg"), 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: with go_base, go_inner_shining, go_inner:
@@ -36,8 +26,9 @@ def _build_go_icons(ctx: Context) -> None:
go = go_base.copy() go = go_base.copy()
# render shining with inner mask # render shining with inner mask
go.paste(go_inner_shining, (0, 0), go_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 # rotate it for next calling
go_mask = go_mask.transpose(PIL.Image.Transpose.ROTATE_90) go_mask = go_mask.transpose(PIL.Image.Transpose.ROTATE_90)