renderbuilder fixes

This commit is contained in:
jan 2023-04-14 00:01:04 -07:00
parent d14d5438a4
commit 7e190bf8fc
3 changed files with 90 additions and 74 deletions

View File

@ -121,13 +121,6 @@ class Pather(Builder):
self.pattern.ports.update(copy.deepcopy(dict(ports))) self.pattern.ports.update(copy.deepcopy(dict(ports)))
if tools is None:
self.tools = {}
elif isinstance(tools, Tool):
self.tools = {None: tools}
else:
self.tools = dict(tools)
if name is not None: if name is not None:
library[name] = self.pattern library[name] = self.pattern

View File

@ -1,4 +1,4 @@
from typing import Self, Sequence, Mapping from typing import Self, Sequence, Mapping, MutableMapping
import copy import copy
import logging import logging
from collections import defaultdict from collections import defaultdict
@ -57,6 +57,7 @@ class RenderPather(PortList):
*, *,
pattern: Pattern | None = None, pattern: Pattern | None = None,
ports: str | Mapping[str, Port] | None = None, ports: str | Mapping[str, Port] | None = None,
tools: Tool | MutableMapping[str | None, Tool] | None = None,
name: str | None = None, name: str | None = None,
) -> None: ) -> None:
""" """
@ -64,6 +65,7 @@ class RenderPather(PortList):
""" """
self._dead = False self._dead = False
self.paths = defaultdict(list)
self.library = library self.library = library
if pattern is not None: if pattern is not None:
self.pattern = pattern self.pattern = pattern
@ -85,7 +87,13 @@ class RenderPather(PortList):
raise BuildError('Name was supplied, but no library was given!') raise BuildError('Name was supplied, but no library was given!')
library[name] = self.pattern library[name] = self.pattern
self.paths = defaultdict(list) if tools is None:
self.tools = {}
elif isinstance(tools, Tool):
self.tools = {None: tools}
else:
self.tools = dict(tools)
@classmethod @classmethod
def interface( def interface(
@ -228,7 +236,7 @@ class RenderPather(PortList):
# get rid of plugged ports # get rid of plugged ports
for ki, vi in map_in.items(): for ki, vi in map_in.items():
if ki in self.paths: if ki in self.paths:
self.paths[ki].append(RenderStep('P', None, self.ports[ki].copy(), None)) self.paths[ki].append(RenderStep('P', None, self.ports[ki].copy(), self.ports[ki].copy(), None))
del self.ports[ki] del self.ports[ki]
map_out[vi] = None map_out[vi] = None
self.place(other, offset=translation, rotation=rotation, pivot=pivot, self.place(other, offset=translation, rotation=rotation, pivot=pivot,
@ -268,7 +276,7 @@ class RenderPather(PortList):
continue continue
ports[new_name] = port ports[new_name] = port
if new_name in self.paths: if new_name in self.paths:
self.paths[new_name].append(RenderStep('P', None, port.copy(), None)) self.paths[new_name].append(RenderStep('P', None, port.copy(), port.copy(), None))
for name, port in ports.items(): for name, port in ports.items():
p = port.deepcopy() p = port.deepcopy()
@ -301,27 +309,19 @@ class RenderPather(PortList):
tool = self.tools.get(portspec, self.tools[None]) tool = self.tools.get(portspec, self.tools[None])
# ask the tool for bend size (fill missing dx or dy), check feasibility, and get out_ptype # ask the tool for bend size (fill missing dx or dy), check feasibility, and get out_ptype
data = tool.planL(ccw, length, in_ptype=in_ptype, **kwargs) out_port, data = tool.planL(ccw, length, in_ptype=in_ptype, **kwargs)
step = RenderStep('L', tool, port.copy(), data)
self.paths[portspec].append(step)
# Update port # Update port
port.offset += (dx, dy) out_port.rotate_around((0, 0), pi + port_rot)
if ccw is not None: out_port.translate(port.offset)
port.rotate((-1 if ccw else 1) * pi / 2)
port.ptype = out_ptype step = RenderStep('L', tool, port.copy(), out_port.copy(), data)
self.paths[portspec].append(step)
self.pattern.ports[portspec] = out_port.copy()
return self return self
'''
- record ('path', port, dx, dy, out_ptype, tool)
- to render, ccw = {0: None, 1: True, -1: False}[numpy.sign(dx) * numpy.sign(dy) * (-1 if x_start else 1)
- length is just dx or dy
- in_ptype and out_ptype are taken directly
- for sbend: dx and dy are maybe reordered (length and jog)
'''
def path_to( def path_to(
self, self,
portspec: str, portspec: str,
@ -396,31 +396,37 @@ class RenderPather(PortList):
self.path(port_name, ccw, length) self.path(port_name, ccw, length)
return self return self
def render(self, lib: ILibrary | None = None) -> Self: def render(
self,
lib: ILibrary | None = None,
append: bool = True,
) -> Self:
lib = lib if lib is not None else self.library lib = lib if lib is not None else self.library
assert lib is not None assert lib is not None
tool_port_names = ('A', 'B') tool_port_names = ('A', 'B')
bb = Builder(lib) bb = Builder(lib)
def render_batch(lib: ILibrary, portspec: str, batch: list[RenderStep], append: bool) -> None:
assert batch[0].tool is not None
name = lib << batch[0].tool.render(batch, port_names=tool_port_names)
bb.ports[portspec] = batch[0].start_port.copy()
bb.plug(name, {portspec: tool_port_names[0]}, append=append)
if append:
del lib[name]
for portspec, steps in self.paths.items(): for portspec, steps in self.paths.items():
batch: list[RenderStep] = [] batch: list[RenderStep] = []
tool0 = batch[0].tool
port0 = batch[0].start_port
assert tool0 is not None
for step in steps: for step in steps:
appendable_op = step.opcode in ('L', 'S', 'U') appendable_op = step.opcode in ('L', 'S', 'U')
same_tool = batch and step.tool == tool0 same_tool = batch and step.tool == batch[0].tool
# If we can't continue a batch, render it
if batch and (not appendable_op or not same_tool): if batch and (not appendable_op or not same_tool):
# If we can't continue a batch, render it render_batch(lib, portspec, batch, append)
assert batch[0].tool is not None
name = lib << batch[0].tool.render(batch, port_names=tool_port_names)
bb.ports[portspec] = port0.copy()
bb.plug(name, {portspec: tool_port_names[0]})
batch = [] batch = []
# batch is emptied already if we couldn't # batch is emptied already if we couldn't continue it
if appendable_op: if appendable_op:
batch.append(step) batch.append(step)
@ -428,13 +434,11 @@ class RenderPather(PortList):
if not appendable_op: if not appendable_op:
del bb.ports[portspec] del bb.ports[portspec]
#If the last batch didn't end yet
if batch: if batch:
# A batch didn't end yet render_batch(lib, portspec, batch, append)
assert batch[0].tool is not None
name = lib << batch[0].tool.render(batch, port_names=tool_port_names)
bb.ports[portspec] = batch[0].start_port.copy()
bb.plug(name, {portspec: tool_port_names[0]})
self.paths.clear()
bb.ports.clear() bb.ports.clear()
self.pattern.append(bb.pattern) self.pattern.append(bb.pattern)

View File

@ -6,6 +6,7 @@ from abc import ABCMeta, abstractmethod # TODO any way to make Tool ok with
from dataclasses import dataclass from dataclasses import dataclass
import numpy import numpy
from numpy.typing import NDArray
from numpy import pi from numpy import pi
from ..utils import SupportsBool, rotation_matrix_2d, layer_t from ..utils import SupportsBool, rotation_matrix_2d, layer_t
@ -20,8 +21,9 @@ from .builder import Builder
@dataclass(frozen=True, slots=True) @dataclass(frozen=True, slots=True)
class RenderStep: class RenderStep:
opcode: Literal['L', 'S', 'U', 'P'] opcode: Literal['L', 'S', 'U', 'P']
tool: 'Tool' | None tool: 'Tool | None'
start_port: Port start_port: Port
end_port: Port
data: Any data: Any
def __post_init__(self) -> None: def __post_init__(self) -> None:
@ -50,7 +52,7 @@ class Tool:
in_ptype: str | None = None, in_ptype: str | None = None,
out_ptype: str | None = None, out_ptype: str | None = None,
**kwargs, **kwargs,
) -> Any: ) -> tuple[Port, Any]:
raise NotImplementedError(f'planL() not implemented for {type(self)}') raise NotImplementedError(f'planL() not implemented for {type(self)}')
def planS( def planS(
@ -62,7 +64,7 @@ class Tool:
in_ptype: str | None = None, in_ptype: str | None = None,
out_ptype: str | None = None, out_ptype: str | None = None,
**kwargs, **kwargs,
) -> Any: ) -> tuple[Port, Any]:
raise NotImplementedError(f'planS() not implemented for {type(self)}') raise NotImplementedError(f'planS() not implemented for {type(self)}')
def render( def render(
@ -81,12 +83,20 @@ class Tool:
abstract_tuple_t = tuple[Abstract, str, str] abstract_tuple_t = tuple[Abstract, str, str]
@dataclass
class BasicTool(Tool, metaclass=ABCMeta): class BasicTool(Tool, metaclass=ABCMeta):
straight: tuple[Callable[[float], Pattern], str, str] straight: tuple[Callable[[float], Pattern], str, str]
bend: abstract_tuple_t # Assumed to be clockwise bend: abstract_tuple_t # Assumed to be clockwise
transitions: dict[str, abstract_tuple_t] transitions: dict[str, abstract_tuple_t]
default_out_ptype: str default_out_ptype: str
@dataclass(frozen=True, slots=True)
class LData:
straight_length: float
ccw: SupportsBool | None
in_transition: abstract_tuple_t | None
out_transition: abstract_tuple_t | None
def path( def path(
self, self,
ccw: SupportsBool | None, ccw: SupportsBool | None,
@ -97,7 +107,7 @@ class BasicTool(Tool, metaclass=ABCMeta):
port_names: tuple[str, str] = ('A', 'B'), port_names: tuple[str, str] = ('A', 'B'),
**kwargs, **kwargs,
) -> Pattern: ) -> Pattern:
straight_length, _ccw, in_transition, out_transition = self.planL( _out_port, data = self.planL(
ccw, ccw,
length, length,
in_ptype=in_ptype, in_ptype=in_ptype,
@ -107,17 +117,17 @@ class BasicTool(Tool, metaclass=ABCMeta):
gen_straight, sport_in, sport_out = self.straight gen_straight, sport_in, sport_out = self.straight
tree = Library() tree = Library()
bb = Builder(library=tree, name='_path').add_port_pair(names=port_names) bb = Builder(library=tree, name='_path').add_port_pair(names=port_names)
if in_transition: if data.in_transition:
ipat, iport_theirs, _iport_ours = in_transition ipat, iport_theirs, _iport_ours = data.in_transition
bb.plug(ipat, {port_names[1]: iport_theirs}) bb.plug(ipat, {port_names[1]: iport_theirs})
if not numpy.isclose(straight_length, 0): if not numpy.isclose(data.straight_length, 0):
straight = tree << {'_straight': gen_straight(straight_length)} straight = tree << {'_straight': gen_straight(data.straight_length)}
bb.plug(straight, {port_names[1]: sport_in}) bb.plug(straight, {port_names[1]: sport_in})
if ccw is not None: if data.ccw is not None:
bend, bport_in, bport_out = self.bend bend, bport_in, bport_out = self.bend
bb.plug(bend, {port_names[1]: bport_in}, mirrored=(False, bool(ccw))) bb.plug(bend, {port_names[1]: bport_in}, mirrored=(False, bool(ccw)))
if out_transition: if data.out_transition:
opat, oport_theirs, oport_ours = out_transition opat, oport_theirs, oport_ours = data.out_transition
bb.plug(opat, {port_names[1]: oport_ours}) bb.plug(opat, {port_names[1]: oport_ours})
return bb.pattern return bb.pattern
@ -130,7 +140,7 @@ class BasicTool(Tool, metaclass=ABCMeta):
in_ptype: str | None = None, in_ptype: str | None = None,
out_ptype: str | None = None, out_ptype: str | None = None,
**kwargs, **kwargs,
) -> tuple[float, SupportsBool | None, abstract_tuple_t | None, abstract_tuple_t | None]: ) -> tuple[Port, LData]:
# TODO check all the math for L-shaped bends # TODO check all the math for L-shaped bends
if ccw is not None: if ccw is not None:
bend, bport_in, bport_out = self.bend bend, bport_in, bport_out = self.bend
@ -195,7 +205,9 @@ class BasicTool(Tool, metaclass=ABCMeta):
f'bend: {bend_dxy[0]:,g} in_trans: {itrans_dxy[0]:,g} out_trans: {otrans_dxy[0]:,g}' f'bend: {bend_dxy[0]:,g} in_trans: {itrans_dxy[0]:,g} out_trans: {otrans_dxy[0]:,g}'
) )
return float(straight_length), ccw, in_transition, out_transition data = self.LData(straight_length, ccw, in_transition, out_transition)
out_port = Port((length, bend_run), rotation=bend_angle, ptype=out_ptype_actual)
return out_port, data
def render( def render(
self, self,
@ -234,20 +246,22 @@ class BasicTool(Tool, metaclass=ABCMeta):
return tree return tree
@dataclass
class PathTool(Tool, metaclass=ABCMeta): class PathTool(Tool, metaclass=ABCMeta):
straight: tuple[Callable[[float], Pattern], str, str]
bend: abstract_tuple_t # Assumed to be clockwise
transitions: dict[str, abstract_tuple_t]
ptype: str
width: float
layer: layer_t layer: layer_t
width: float
ptype: str = 'unk'
def __init__(self, layer: layer_t, width: float, ptype: str = 'unk') -> None: #@dataclass(frozen=True, slots=True)
Tool.__init__(self) #class LData:
self.layer = layer # dxy: NDArray[numpy.float64]
self.width = width
self.ptype: str
#def __init__(self, layer: layer_t, width: float, ptype: str = 'unk') -> None:
# Tool.__init__(self)
# self.layer = layer
# self.width = width
# self.ptype: str
def path( def path(
self, self,
@ -259,7 +273,7 @@ class PathTool(Tool, metaclass=ABCMeta):
port_names: tuple[str, str] = ('A', 'B'), port_names: tuple[str, str] = ('A', 'B'),
**kwargs, **kwargs,
) -> Pattern: ) -> Pattern:
dxy = self.planL( out_port, dxy = self.planL(
ccw, ccw,
length, length,
in_ptype=in_ptype, in_ptype=in_ptype,
@ -291,7 +305,7 @@ class PathTool(Tool, metaclass=ABCMeta):
in_ptype: str | None = None, in_ptype: str | None = None,
out_ptype: str | None = None, out_ptype: str | None = None,
**kwargs, **kwargs,
) -> tuple[float, float]: ) -> tuple[Port, NDArray[numpy.float64]]:
# TODO check all the math for L-shaped bends # TODO check all the math for L-shaped bends
if out_ptype and out_ptype != self.ptype: if out_ptype and out_ptype != self.ptype:
@ -306,7 +320,7 @@ class PathTool(Tool, metaclass=ABCMeta):
bend_angle *= -1 bend_angle *= -1
else: else:
bend_dxy = numpy.zeros(2) bend_dxy = numpy.zeros(2)
bend_angle = 0 bend_angle = pi
straight_length = length - bend_dxy[0] straight_length = length - bend_dxy[0]
bend_run = bend_dxy[1] bend_run = bend_dxy[1]
@ -315,8 +329,9 @@ class PathTool(Tool, metaclass=ABCMeta):
raise BuildError( raise BuildError(
f'Asked to draw path with total length {length:,g}, shorter than required bend: {bend_dxy[0]:,g}' f'Asked to draw path with total length {length:,g}, shorter than required bend: {bend_dxy[0]:,g}'
) )
data = numpy.array((length, bend_run))
return length, bend_run out_port = Port(data, rotation=bend_angle, ptype=self.ptype)
return out_port, data
def render( def render(
self, self,
@ -335,12 +350,16 @@ class PathTool(Tool, metaclass=ABCMeta):
if step.opcode == 'L': if step.opcode == 'L':
length, bend_run = step.data length, bend_run = step.data
dxy = rotation_matrix_2d(port_rot + pi) @ (length, bend_run) dxy = rotation_matrix_2d(port_rot + pi) @ (length, 0)
#path_vertices.append(step.start_port.offset)
path_vertices.append(step.start_port.offset + dxy) path_vertices.append(step.start_port.offset + dxy)
else: else:
raise BuildError(f'Unrecognized opcode "{step.opcode}"') raise BuildError(f'Unrecognized opcode "{step.opcode}"')
tree, pat = Library.mktree('_path') tree, pat = Library.mktree('_path')
pat.path(layer=self.layer, width=self.width, vertices=path_vertices) pat.path(layer=self.layer, width=self.width, vertices=path_vertices)
pat.ports = {
port_names[0]: batch[0].start_port.copy().rotate(pi),
port_names[1]: batch[-1].end_port.copy().rotate(pi),
}
return tree return tree