feat: use new design
This commit is contained in:
@@ -14,17 +14,17 @@ class Context:
|
||||
self.__output_dir = output_dir
|
||||
self.__temp_dir = tempfile.TemporaryDirectory()
|
||||
|
||||
@property
|
||||
def input_directory(self) -> Path:
|
||||
return self.__input_dir
|
||||
def input_artifact(self, *args: str) -> Path:
|
||||
return self.__input_dir / Path(*args)
|
||||
|
||||
@property
|
||||
def output_directory(self) -> Path:
|
||||
return self.__output_dir
|
||||
def output_artifact(self, *args: str) -> Path:
|
||||
return self.__output_dir / Path(*args)
|
||||
|
||||
@property
|
||||
def temporary_directory(self) -> Path:
|
||||
return Path(self.__temp_dir.name)
|
||||
def temporary_artifact(self, *args: str) -> Path:
|
||||
return Path(self.__temp_dir.name) / "user" / Path(*args)
|
||||
|
||||
def _intern_temporary_artifact(self, *args: str) -> Path:
|
||||
return Path(self.__temp_dir.name) / "assemblicon" / Path(*args)
|
||||
|
||||
def __enter__(self) -> "Context":
|
||||
return self
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
@@ -1,7 +0,0 @@
|
||||
from .common import Sink
|
||||
from .temp_sink import TempSink
|
||||
|
||||
__all__ = [
|
||||
"Sink",
|
||||
"TempSink"
|
||||
]
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -1,7 +0,0 @@
|
||||
from .common import Source
|
||||
from .temp_source import TempSource
|
||||
|
||||
__all__ = [
|
||||
"Source",
|
||||
"TempSource"
|
||||
]
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user