From 9bc8d29b85805875b8f4a2a1362f8d2afe236c72 Mon Sep 17 00:00:00 2001 From: jan Date: Fri, 14 Apr 2023 00:01:04 -0700 Subject: [PATCH] renderbuilder fixes --- masque/builder/pather.py | 7 --- masque/builder/renderpather.py | 76 ++++++++++++++++--------------- masque/builder/tools.py | 81 +++++++++++++++++++++------------- 3 files changed, 90 insertions(+), 74 deletions(-) diff --git a/masque/builder/pather.py b/masque/builder/pather.py index 5ad8dc4..3783de9 100644 --- a/masque/builder/pather.py +++ b/masque/builder/pather.py @@ -121,13 +121,6 @@ class Pather(Builder): 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: library[name] = self.pattern diff --git a/masque/builder/renderpather.py b/masque/builder/renderpather.py index 60219a4..9194653 100644 --- a/masque/builder/renderpather.py +++ b/masque/builder/renderpather.py @@ -1,4 +1,4 @@ -from typing import Self, Sequence, Mapping +from typing import Self, Sequence, Mapping, MutableMapping import copy import logging from collections import defaultdict @@ -57,6 +57,7 @@ class RenderPather(PortList): *, pattern: Pattern | None = None, ports: str | Mapping[str, Port] | None = None, + tools: Tool | MutableMapping[str | None, Tool] | None = None, name: str | None = None, ) -> None: """ @@ -64,6 +65,7 @@ class RenderPather(PortList): """ self._dead = False + self.paths = defaultdict(list) self.library = library if pattern is not None: self.pattern = pattern @@ -85,7 +87,13 @@ class RenderPather(PortList): raise BuildError('Name was supplied, but no library was given!') 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 def interface( @@ -228,7 +236,7 @@ class RenderPather(PortList): # get rid of plugged ports for ki, vi in map_in.items(): 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] map_out[vi] = None self.place(other, offset=translation, rotation=rotation, pivot=pivot, @@ -268,7 +276,7 @@ class RenderPather(PortList): continue ports[new_name] = port 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(): p = port.deepcopy() @@ -301,27 +309,19 @@ class RenderPather(PortList): 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 - data = tool.planL(ccw, length, in_ptype=in_ptype, **kwargs) - - step = RenderStep('L', tool, port.copy(), data) - self.paths[portspec].append(step) + out_port, data = tool.planL(ccw, length, in_ptype=in_ptype, **kwargs) # Update port - port.offset += (dx, dy) - if ccw is not None: - port.rotate((-1 if ccw else 1) * pi / 2) - port.ptype = out_ptype + out_port.rotate_around((0, 0), pi + port_rot) + out_port.translate(port.offset) + + step = RenderStep('L', tool, port.copy(), out_port.copy(), data) + self.paths[portspec].append(step) + + self.pattern.ports[portspec] = out_port.copy() 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( self, portspec: str, @@ -396,31 +396,37 @@ class RenderPather(PortList): self.path(port_name, ccw, length) 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 assert lib is not None tool_port_names = ('A', 'B') 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(): batch: list[RenderStep] = [] - tool0 = batch[0].tool - port0 = batch[0].start_port - assert tool0 is not None for step in steps: 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 we can't continue a batch, render it - 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]}) + render_batch(lib, portspec, batch, append) batch = [] - # batch is emptied already if we couldn't + # batch is emptied already if we couldn't continue it if appendable_op: batch.append(step) @@ -428,13 +434,11 @@ class RenderPather(PortList): if not appendable_op: del bb.ports[portspec] + #If the last batch didn't end yet if batch: - # A batch didn't end yet - 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]}) + render_batch(lib, portspec, batch, append) + self.paths.clear() bb.ports.clear() self.pattern.append(bb.pattern) diff --git a/masque/builder/tools.py b/masque/builder/tools.py index 006f3d2..2aadc6f 100644 --- a/masque/builder/tools.py +++ b/masque/builder/tools.py @@ -6,6 +6,7 @@ from abc import ABCMeta, abstractmethod # TODO any way to make Tool ok with from dataclasses import dataclass import numpy +from numpy.typing import NDArray from numpy import pi from ..utils import SupportsBool, rotation_matrix_2d, layer_t @@ -20,8 +21,9 @@ from .builder import Builder @dataclass(frozen=True, slots=True) class RenderStep: opcode: Literal['L', 'S', 'U', 'P'] - tool: 'Tool' | None + tool: 'Tool | None' start_port: Port + end_port: Port data: Any def __post_init__(self) -> None: @@ -50,7 +52,7 @@ class Tool: in_ptype: str | None = None, out_ptype: str | None = None, **kwargs, - ) -> Any: + ) -> tuple[Port, Any]: raise NotImplementedError(f'planL() not implemented for {type(self)}') def planS( @@ -62,7 +64,7 @@ class Tool: in_ptype: str | None = None, out_ptype: str | None = None, **kwargs, - ) -> Any: + ) -> tuple[Port, Any]: raise NotImplementedError(f'planS() not implemented for {type(self)}') def render( @@ -81,12 +83,20 @@ class Tool: abstract_tuple_t = tuple[Abstract, str, str] +@dataclass class BasicTool(Tool, metaclass=ABCMeta): straight: tuple[Callable[[float], Pattern], str, str] bend: abstract_tuple_t # Assumed to be clockwise transitions: dict[str, abstract_tuple_t] 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( self, ccw: SupportsBool | None, @@ -97,7 +107,7 @@ class BasicTool(Tool, metaclass=ABCMeta): port_names: tuple[str, str] = ('A', 'B'), **kwargs, ) -> Pattern: - straight_length, _ccw, in_transition, out_transition = self.planL( + _out_port, data = self.planL( ccw, length, in_ptype=in_ptype, @@ -107,17 +117,17 @@ class BasicTool(Tool, metaclass=ABCMeta): gen_straight, sport_in, sport_out = self.straight tree = Library() bb = Builder(library=tree, name='_path').add_port_pair(names=port_names) - if in_transition: - ipat, iport_theirs, _iport_ours = in_transition + if data.in_transition: + ipat, iport_theirs, _iport_ours = data.in_transition bb.plug(ipat, {port_names[1]: iport_theirs}) - if not numpy.isclose(straight_length, 0): - straight = tree << {'_straight': gen_straight(straight_length)} + if not numpy.isclose(data.straight_length, 0): + straight = tree << {'_straight': gen_straight(data.straight_length)} 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 bb.plug(bend, {port_names[1]: bport_in}, mirrored=(False, bool(ccw))) - if out_transition: - opat, oport_theirs, oport_ours = out_transition + if data.out_transition: + opat, oport_theirs, oport_ours = data.out_transition bb.plug(opat, {port_names[1]: oport_ours}) return bb.pattern @@ -130,7 +140,7 @@ class BasicTool(Tool, metaclass=ABCMeta): in_ptype: str | None = None, out_ptype: str | None = None, **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 if ccw is not None: 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}' ) - 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( self, @@ -234,20 +246,22 @@ class BasicTool(Tool, metaclass=ABCMeta): return tree - +@dataclass 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 + width: float + ptype: str = 'unk' - def __init__(self, layer: layer_t, width: float, ptype: str = 'unk') -> None: - Tool.__init__(self) - self.layer = layer - self.width = width - self.ptype: str + #@dataclass(frozen=True, slots=True) + #class LData: + # dxy: NDArray[numpy.float64] + + + #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( self, @@ -259,7 +273,7 @@ class PathTool(Tool, metaclass=ABCMeta): port_names: tuple[str, str] = ('A', 'B'), **kwargs, ) -> Pattern: - dxy = self.planL( + out_port, dxy = self.planL( ccw, length, in_ptype=in_ptype, @@ -291,7 +305,7 @@ class PathTool(Tool, metaclass=ABCMeta): in_ptype: str | None = None, out_ptype: str | None = None, **kwargs, - ) -> tuple[float, float]: + ) -> tuple[Port, NDArray[numpy.float64]]: # TODO check all the math for L-shaped bends if out_ptype and out_ptype != self.ptype: @@ -306,7 +320,7 @@ class PathTool(Tool, metaclass=ABCMeta): bend_angle *= -1 else: bend_dxy = numpy.zeros(2) - bend_angle = 0 + bend_angle = pi straight_length = length - bend_dxy[0] bend_run = bend_dxy[1] @@ -315,8 +329,9 @@ class PathTool(Tool, metaclass=ABCMeta): raise BuildError( f'Asked to draw path with total length {length:,g}, shorter than required bend: {bend_dxy[0]:,g}' ) - - return length, bend_run + data = numpy.array((length, bend_run)) + out_port = Port(data, rotation=bend_angle, ptype=self.ptype) + return out_port, data def render( self, @@ -335,12 +350,16 @@ class PathTool(Tool, metaclass=ABCMeta): if step.opcode == 'L': 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) else: raise BuildError(f'Unrecognized opcode "{step.opcode}"') tree, pat = Library.mktree('_path') 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