# -*- coding: utf-8 -*-
from __future__ import annotations
import os
from pathlib import Path
from typing import Deque, List, Tuple
from pygerber.constants import Interpolation
from pygerber.drawing_state import DrawingState
from pygerber.exceptions import EndOfStream
from pygerber.mathclasses import BoundingBox, Vector2D
from pygerber.tokens.token import Token
from .aperture import RegionApertureManager
from .aperture_manager import ApertureManager
from .apertureset import ApertureSet
from .spec import ArcSpec, FlashSpec, LineSpec, Spec
DEBUG = False
if DEBUG:
TEMP_PATH = Path(os.getcwd()) / ".temp"
TEMP_PATH.mkdir(parents=True, exist_ok=True)
[docs]class Renderer:
current_point: Vector2D
region_bounds: List[Spec]
apertures: ApertureManager
state: DrawingState
def __init__(self, apertureSet: ApertureSet) -> None:
self.apertures = ApertureManager(apertureSet, self)
self.state = DrawingState()
self.set_defaults()
[docs] def set_defaults(self):
self.apertures.set_defaults()
self.state.set_defaults()
self.current_point = Vector2D(0, 0)
self.region_bounds = []
if DEBUG:
def render(self, token_stack: Deque[Token]) -> None:
from PyR3.shortcut.io import export_to
total = len(token_stack)
current = 0
try:
for token in token_stack:
token.alter_state(self.state)
token.pre_render(self)
token.render(self)
token.post_render(self)
current += 1
if current % 10 == 0:
print(f"Rendered {current} / {total}")
if (current > 500 and current % 20 == 0) or current % 100 == 0:
export_to(TEMP_PATH / f"render_{current}_{total}.blend")
except EndOfStream:
return
else:
[docs] def render(self, token_stack: Deque[Token]) -> None:
try:
for token in token_stack:
token.alter_state(self.state)
token.pre_render(self)
token.render(self)
token.post_render(self)
except EndOfStream:
return
[docs] def define_aperture(self, *args, **kwargs):
self.apertures.define_aperture(*args, **kwargs)
[docs] def select_aperture(self, id: int):
self.apertures.select_aperture(id)
[docs] def total_bounding_box(self, token_stack: Deque[Token]):
total_bbox: BoundingBox = None
try:
for token in token_stack:
total_bbox = self.__update_bounding_box(token, total_bbox)
except EndOfStream:
pass
self.set_defaults()
if total_bbox is None:
return BoundingBox(0, 0, 0, 0)
else:
return total_bbox
def __update_bounding_box(self, token, total_bbox):
token.alter_state(self.state)
token.pre_render(self)
bbox = token.bbox(self)
if bbox is not None:
if total_bbox is None:
total_bbox = bbox
else:
total_bbox = total_bbox + bbox
token.post_render(self)
return total_bbox
[docs] def draw_interpolated(self, end: Vector2D, offset: Vector2D) -> None:
if self.state.interpolation == Interpolation.Linear:
self.draw_line(end)
else:
self.draw_arc(end, offset)
[docs] def bbox_interpolated(self, end: Vector2D, offset: Vector2D) -> BoundingBox:
if self.state.interpolation == Interpolation.Linear:
return self.bbox_line(end)
else:
return self.bbox_arc(end, offset)
[docs] def draw_line(self, end: Vector2D) -> None:
spec = self.__get_line_spec(end)
if self.state.is_regionmode:
self.__push_region_step(spec)
else:
self.apertures.get_current_aperture().line(spec)
[docs] def bbox_line(self, end: Vector2D) -> None:
spec = self.__get_line_spec(end)
if self.state.is_regionmode:
self.__push_region_step(spec)
else:
return self.apertures.get_current_aperture().line_bbox(spec)
def __get_line_spec(self, end: Vector2D) -> LineSpec:
return LineSpec(
self.current_point,
self.replace_none_with_current(end),
self.state.is_regionmode,
)
[docs] def draw_arc(self, end: Vector2D, offset: Vector2D) -> None:
spec = self.__get_arc_spec(end, offset)
if self.state.is_regionmode:
self.__push_region_step(spec)
else:
self.apertures.get_current_aperture().arc(spec)
[docs] def bbox_arc(self, end: Vector2D, offset: Vector2D) -> None:
spec = self.__get_arc_spec(end, offset)
if self.state.is_regionmode:
self.__push_region_step(spec)
else:
return self.apertures.get_current_aperture().arc_bbox(spec)
def __get_arc_spec(self, end: Vector2D, offset: Vector2D) -> ArcSpec:
return ArcSpec(
self.current_point,
self.replace_none_with_current(end),
self.current_point + self.replace_none_with_0(offset),
self.state.is_regionmode,
)
[docs] def draw_flash(self, point: Vector2D) -> None:
if self.state.is_regionmode:
raise RuntimeError("Flashes can't be used in region mode.")
else:
aperture = self.apertures.get_current_aperture()
spec = self.__get_flash_spec(point)
aperture.flash(spec)
[docs] def bbox_flash(self, point: Vector2D) -> BoundingBox:
self.move_pointer(point)
aperture = self.apertures.get_current_aperture()
spec = self.__get_flash_spec(point)
return aperture.flash_bbox(spec)
def __get_flash_spec(self, point: Vector2D) -> FlashSpec:
spec = FlashSpec(
self.replace_none_with_current(point),
self.state.is_regionmode,
)
return spec
def __push_region_step(self, spec: Spec):
self.region_bounds.append(spec)
self.move_pointer(spec.end)
[docs] def end_region(self):
self.state.end_region()
[docs] def finish_drawing_region(self) -> Tuple[RegionApertureManager, List[Spec]]:
bounds = self.__get_and_clean_region_bounds()
apertureClass = self.apertures.getApertureClass(None, True)
return apertureClass(self), bounds
def __get_and_clean_region_bounds(self):
bounds = self.region_bounds
self.region_bounds = []
return bounds
[docs] def move_pointer(self, location: Vector2D) -> None:
self.current_point = Vector2D(
location.x if location.x is not None else self.current_point.x,
location.y if location.y is not None else self.current_point.y,
)
[docs] def isCCW(self):
return self.state.interpolation == Interpolation.CounterclockwiseCircular
[docs] def replace_none_with_current(self, vector: Vector2D):
if vector.x is None:
vector.x = self.current_point.x
if vector.y is None:
vector.y = self.current_point.y
return vector
[docs] def replace_none_with_0(self, vector: Vector2D):
if vector.x is None:
vector.x = 0.0
if vector.y is None:
vector.y = 0.0
return vector