feat: use new design
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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