renderbuilder fixes
This commit is contained in:
parent
d14d5438a4
commit
7e190bf8fc
@ -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
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
|
||||||
for portspec, steps in self.paths.items():
|
def render_batch(lib: ILibrary, portspec: str, batch: list[RenderStep], append: bool) -> None:
|
||||||
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
|
|
||||||
|
|
||||||
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
|
assert batch[0].tool is not None
|
||||||
name = lib << batch[0].tool.render(batch, port_names=tool_port_names)
|
name = lib << batch[0].tool.render(batch, port_names=tool_port_names)
|
||||||
bb.ports[portspec] = port0.copy()
|
bb.ports[portspec] = batch[0].start_port.copy()
|
||||||
bb.plug(name, {portspec: tool_port_names[0]})
|
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] = []
|
||||||
|
for step in steps:
|
||||||
|
appendable_op = step.opcode in ('L', 'S', 'U')
|
||||||
|
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):
|
||||||
|
render_batch(lib, portspec, batch, append)
|
||||||
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)
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user