feat: use new design

This commit is contained in:
2026-08-20 19:15:27 +08:00
parent 9f79c48c0e
commit 86b19d8d64
10 changed files with 133 additions and 100 deletions
+9 -9
View File
@@ -14,17 +14,17 @@ class Context:
self.__output_dir = output_dir self.__output_dir = output_dir
self.__temp_dir = tempfile.TemporaryDirectory() self.__temp_dir = tempfile.TemporaryDirectory()
@property def input_artifact(self, *args: str) -> Path:
def input_directory(self) -> Path: return self.__input_dir / Path(*args)
return self.__input_dir
@property def output_artifact(self, *args: str) -> Path:
def output_directory(self) -> Path: return self.__output_dir / Path(*args)
return self.__output_dir
@property def temporary_artifact(self, *args: str) -> Path:
def temporary_directory(self) -> Path: return Path(self.__temp_dir.name) / "user" / Path(*args)
return Path(self.__temp_dir.name)
def _intern_temporary_artifact(self, *args: str) -> Path:
return Path(self.__temp_dir.name) / "assemblicon" / Path(*args)
def __enter__(self) -> "Context": def __enter__(self) -> "Context":
return self return self
+121
View File
@@ -0,0 +1,121 @@
import logging
import os
import subprocess
from pathlib import Path
from dataclasses import dataclass
from .context import Context
@dataclass
class InkscapeConfig:
width: int
height: int
def __post_init__(self) -> None:
if self.width <= 0:
raise ValueError(
f"The image width in Inkscape config must be a positive integer"
)
if self.height <= 0:
raise ValueError(
f"The image height in Inkscape config must be a positive integer"
)
def inkscape_render(src: Path, dst: Path, cfg: InkscapeConfig) -> None:
logging.info("Rendering Inkscape artifact: %s -> %s", src, dst)
inkscape_bin = os.getenv("ASSEMBLICON_INKSCAPE", "inkscape")
cmd = [
inkscape_bin,
"--export-area-page",
"--export-width",
str(cfg.width),
"--export-height",
str(cfg.height),
"--export-type",
"png",
"--export-png-color-mode",
"RGBA_8",
"--export-background-opacity",
"0.0",
"--export-png-use-dithering",
"false",
"--export-filename",
str(dst),
str(src),
]
proc = subprocess.run(cmd, capture_output=False)
if proc.returncode != 0:
raise RuntimeError(
f"Fail to execute Inkscape. Return code is {proc.returncode}"
)
@dataclass
class BlenderConfig:
width: int
height: int
def __post_init__(self) -> None:
if self.width <= 0:
raise ValueError(
f"The image width in Blender config must be a positive integer"
)
if self.height <= 0:
raise ValueError(
f"The image height in Blender config must be a positive integer"
)
_BLENDER_RENDER_SCRIPT = """\
import sys
import traceback
try:
import bpy
render = bpy.context.scene.render
render.resolution_x = {width}
render.resolution_y = {height}
render.resolution_percentage = 100
render.image_settings.file_format = "PNG"
render.image_settings.color_mode = "RGBA"
render.image_settings.color_depth = "8"
render.film_transparent = True
render.use_file_extension = False
render.filepath = {dst}
bpy.ops.render.render(write_still=True)
except BaseException:
traceback.print_exc()
sys.exit(1)
"""
def blender_render(ctx: Context, src: Path, dst: Path, cfg: BlenderConfig) -> None:
logging.info("Rendering Blender artifact: %s -> %s", src, dst)
script = _BLENDER_RENDER_SCRIPT.format(
width=cfg.width, height=cfg.height, dst=repr(str(dst))
)
script_path = ctx._intern_temporary_artifact("blender_render.py")
script_path.write_text(script, encoding="utf-8")
blender_bin = os.getenv("ASSEMBLICON_BLENDER", "blender")
cmd = [
blender_bin,
"--background",
str(src),
"--python",
str(script_path),
]
proc = subprocess.run(cmd, capture_output=False)
if proc.returncode != 0:
raise RuntimeError(
f"Fail to execute Blender. Return code is {proc.returncode}"
)
if not dst.exists():
raise RuntimeError(
f"Blender exited successfully but output file {dst} is missing"
)
-7
View File
@@ -1,7 +0,0 @@
from .common import Sink
from .temp_sink import TempSink
__all__ = [
"Sink",
"TempSink"
]
-12
View File
@@ -1,12 +0,0 @@
from abc import ABC, abstractmethod
from ..context import Context
from ..utils import Artifact
class Sink(ABC):
"""
Abstract base class for all sinks.
"""
@abstractmethod
def push_artifact(self, ctx: Context, art: Artifact) -> None:
pass
-17
View File
@@ -1,17 +0,0 @@
import logging
from pathlib import Path
from ..context import Context
from ..utils import Artifact
from .common import Sink
class TempSink(Sink):
__relative_path: Path
def __init__(self, *args: str) -> None:
self.__relative_path = Path(*args)
def push_artifact(self, ctx: Context, art: Artifact) -> None:
fp = ctx.temporary_directory / self.__relative_path
logging.info('Saving temporary artifact: %s', fp)
art.save(fp)
-7
View File
@@ -1,7 +0,0 @@
from .common import Source
from .temp_source import TempSource
__all__ = [
"Source",
"TempSource"
]
-12
View File
@@ -1,12 +0,0 @@
from abc import ABC, abstractmethod
from ..context import Context
from ..utils import Artifact
class Source(ABC):
"""
Abstract base class for all sources.
"""
@abstractmethod
def pull_artifact(self, ctx: Context) -> Artifact:
pass
@@ -1,16 +0,0 @@
import logging
from pathlib import Path
import PIL.Image
from ..context import Context
from ..utils import Artifact
from .common import Source
class InkscapeSource(Source):
__relative_path: Path
def __init__(self, *args: str) -> None:
self.__relative_path = Path(*args)
def pull_artifact(self, ctx: Context) -> Artifact:
pass
-18
View File
@@ -1,18 +0,0 @@
import logging
from pathlib import Path
import PIL.Image
from ..context import Context
from ..utils import Artifact
from .common import Source
class TempSource(Source):
__relative_path: Path
def __init__(self, *args: str) -> None:
self.__relative_path = Path(*args)
def pull_artifact(self, ctx: Context) -> Artifact:
fp = ctx.temporary_directory / self.__relative_path
logging.info('Loading temporary artifact: %s', fp)
return PIL.Image.open(fp)
+3 -2
View File
@@ -1,3 +1,4 @@
from PIL.Image import Image import logging
type Artifact = Image def setup_logging() -> None:
logging.basicConfig(format="[%(levelname)s] %(message)s", level=logging.INFO)