[Pather] move render check from __del__ to __exit__ and use render= enum instead of auto_render
This commit is contained in:
parent
84664303f1
commit
9a39a436b2
13 changed files with 277 additions and 126 deletions
|
|
@ -29,7 +29,7 @@ Contents
|
||||||
* Define a custom `Tool` that exposes primitive routing offers
|
* Define a custom `Tool` that exposes primitive routing offers
|
||||||
* Use primitive offers to automatically transition between path types
|
* Use primitive offers to automatically transition between path types
|
||||||
- [renderpather](renderpather.py)
|
- [renderpather](renderpather.py)
|
||||||
* Use `Pather(auto_render=False)` and `PathTool` to build a layout similar to the one in [pather](pather.py),
|
* Use `Pather(render='deferred')` and `PathTool` to build a layout similar to the one in [pather](pather.py),
|
||||||
but using `Path` shapes instead of `Polygon`s.
|
but using `Path` shapes instead of `Polygon`s.
|
||||||
- [port_pather](port_pather.py)
|
- [port_pather](port_pather.py)
|
||||||
* Use `PortPather` and the `.at()` syntax for more concise routing
|
* Use `PortPather` and the `.at()` syntax for more concise routing
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ def main() -> None:
|
||||||
library, M1_tool, M2_tool = prepare_tools()
|
library, M1_tool, M2_tool = prepare_tools()
|
||||||
|
|
||||||
# Create a deferred Pather and place some initial pads (same as Pather tutorial)
|
# Create a deferred Pather and place some initial pads (same as Pather tutorial)
|
||||||
rpather = Pather(library, tools=M2_tool, auto_render=False)
|
rpather = Pather(library, tools=M2_tool, render='deferred')
|
||||||
|
|
||||||
rpather.place('pad', offset=(18_000, 30_000), port_map={'wire_port': 'VCC'})
|
rpather.place('pad', offset=(18_000, 30_000), port_map={'wire_port': 'VCC'})
|
||||||
rpather.place('pad', offset=(18_000, 60_000), port_map={'wire_port': 'GND'})
|
rpather.place('pad', offset=(18_000, 60_000), port_map={'wire_port': 'GND'})
|
||||||
|
|
@ -157,7 +157,7 @@ def main() -> None:
|
||||||
#
|
#
|
||||||
# Rendering and Saving
|
# Rendering and Saving
|
||||||
#
|
#
|
||||||
# Since we deferred auto-rendering, we must call .render() to generate the geometry.
|
# Since routing is deferred, we must call .render() to generate the geometry.
|
||||||
rpather.render()
|
rpather.render()
|
||||||
|
|
||||||
library['PortPather_Tutorial'] = rpather.pattern
|
library['PortPather_Tutorial'] = rpather.pattern
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ def main() -> None:
|
||||||
#
|
#
|
||||||
# To illustrate deferred routing with `Pather`, we use `PathTool` instead
|
# To illustrate deferred routing with `Pather`, we use `PathTool` instead
|
||||||
# of `AutoTool`. `PathTool` lacks some sophistication (e.g. no automatic transitions)
|
# of `AutoTool`. `PathTool` lacks some sophistication (e.g. no automatic transitions)
|
||||||
# but when used with `Pather(auto_render=False)`, it can consolidate multiple routing steps into
|
# but when used with `Pather(render='deferred')`, it can consolidate multiple routing steps into
|
||||||
# a single `Path` shape.
|
# a single `Path` shape.
|
||||||
#
|
#
|
||||||
# We'll try to nearly replicate the layout from the `Pather` tutorial; see `pather.py`
|
# We'll try to nearly replicate the layout from the `Pather` tutorial; see `pather.py`
|
||||||
|
|
@ -39,7 +39,7 @@ def main() -> None:
|
||||||
# and what port type to present.
|
# and what port type to present.
|
||||||
M1_ptool = PathTool(layer='M1', width=M1_WIDTH, ptype='m1wire')
|
M1_ptool = PathTool(layer='M1', width=M1_WIDTH, ptype='m1wire')
|
||||||
M2_ptool = PathTool(layer='M2', width=M2_WIDTH, ptype='m2wire')
|
M2_ptool = PathTool(layer='M2', width=M2_WIDTH, ptype='m2wire')
|
||||||
rpather = Pather(tools=M2_ptool, library=library, auto_render=False)
|
rpather = Pather(tools=M2_ptool, library=library, render='deferred')
|
||||||
|
|
||||||
# As in the pather tutorial, we make some pads and labels...
|
# As in the pather tutorial, we make some pads and labels...
|
||||||
rpather.place('pad', offset=(18_000, 30_000), port_map={'wire_port': 'VCC'})
|
rpather.place('pad', offset=(18_000, 30_000), port_map={'wire_port': 'VCC'})
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ Unified Pattern assembly and routing (`Pather`).
|
||||||
|
|
||||||
`Pather` is the public object that owns layout state. It holds the working
|
`Pather` is the public object that owns layout state. It holds the working
|
||||||
Pattern, Library, per-port Tool assignments, pending `RenderStep`s, and routing
|
Pattern, Library, per-port Tool assignments, pending `RenderStep`s, and routing
|
||||||
side effects such as plug consumption, port renames, and auto-render. The
|
side effects such as plug consumption, port renames, and automatic rendering. The
|
||||||
planner package is intentionally internal: custom route generators should
|
planner package is intentionally internal: custom route generators should
|
||||||
extend `Tool.primitive_offers()` and `Tool.render()` rather than depending on
|
extend `Tool.primitive_offers()` and `Tool.render()` rather than depending on
|
||||||
planner classes or search details.
|
planner classes or search details.
|
||||||
|
|
@ -16,7 +16,7 @@ Routing is split into four ownership phases:
|
||||||
`RenderStep.data`, and returns prepared actions,
|
`RenderStep.data`, and returns prepared actions,
|
||||||
- application: `Pather` appends the prepared steps to its pending queue,
|
- application: `Pather` appends the prepared steps to its pending queue,
|
||||||
replaces live output ports, consumes plug destinations, applies deferred
|
replaces live output ports, consumes plug destinations, applies deferred
|
||||||
trace-thru renames, and batches auto-render around the whole route,
|
trace-thru renames, and batches immediate rendering around the whole route,
|
||||||
- rendering: pending steps are grouped by live port, Tool, and continuity before
|
- rendering: pending steps are grouped by live port, Tool, and continuity before
|
||||||
`Tool.render()` builds geometry for each compatible batch.
|
`Tool.render()` builds geometry for each compatible batch.
|
||||||
|
|
||||||
|
|
@ -27,7 +27,7 @@ prepared actions are applied, later commit, plug, rename, render, or insertion
|
||||||
failures may leave partial output; that mutation boundary belongs to Pather,
|
failures may leave partial output; that mutation boundary belongs to Pather,
|
||||||
not to Tool implementations or the route solver.
|
not to Tool implementations or the route solver.
|
||||||
"""
|
"""
|
||||||
from typing import Self, Any, overload
|
from typing import Self, Any, Literal, overload
|
||||||
from collections.abc import Iterator, Iterable, Mapping, MutableMapping, Sequence
|
from collections.abc import Iterator, Iterable, Mapping, MutableMapping, Sequence
|
||||||
import copy
|
import copy
|
||||||
import logging
|
import logging
|
||||||
|
|
@ -36,6 +36,7 @@ from functools import wraps
|
||||||
from pprint import pformat
|
from pprint import pformat
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
|
from types import TracebackType
|
||||||
|
|
||||||
import numpy
|
import numpy
|
||||||
from numpy import pi
|
from numpy import pi
|
||||||
|
|
@ -64,6 +65,8 @@ from .logging import PatherLogger
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
RenderPolicy = Literal['auto', 'immediate', 'deferred', 'warn', 'error', 'ignore']
|
||||||
|
RENDER_POLICIES: tuple[RenderPolicy, ...] = ('auto', 'immediate', 'deferred', 'warn', 'error', 'ignore')
|
||||||
|
|
||||||
|
|
||||||
class Pather(PortList):
|
class Pather(PortList):
|
||||||
|
|
@ -76,9 +79,9 @@ class Pather(PortList):
|
||||||
pattern, and a set of `Tool`s for generating routing segments.
|
pattern, and a set of `Tool`s for generating routing segments.
|
||||||
|
|
||||||
Routing operations (`trace`, `jog`, `uturn`, etc.) select primitive offers
|
Routing operations (`trace`, `jog`, `uturn`, etc.) select primitive offers
|
||||||
from the active `Tool` and compose them into deferred `RenderStep`s.
|
from the active `Tool` and compose them into `RenderStep`s. By default,
|
||||||
Set `auto_render=False` to defer geometry generation until an explicit call
|
geometry is rendered after each route, unless the `Pather` is used as a
|
||||||
to `render()`.
|
context manager.
|
||||||
|
|
||||||
Examples: Creating a Pather
|
Examples: Creating a Pather
|
||||||
===========================
|
===========================
|
||||||
|
|
@ -94,12 +97,12 @@ class Pather(PortList):
|
||||||
connects port 'A' of the current pattern to port 'C' of `subdevice`.
|
connects port 'A' of the current pattern to port 'C' of `subdevice`.
|
||||||
|
|
||||||
- `pather.trace('my_port', ccw=True, length=100)` plans a 100-unit bend
|
- `pather.trace('my_port', ccw=True, length=100)` plans a 100-unit bend
|
||||||
starting at 'my_port'. Geometry is added immediately by default.
|
starting at 'my_port'. If the `Pather` is used as a context manager,
|
||||||
Set `auto_render=False` to defer and call `pather.render()` later.
|
geometry is generated on clean context exit.
|
||||||
"""
|
"""
|
||||||
__slots__ = (
|
__slots__ = (
|
||||||
'pattern', 'library', 'tools', 'planner', '_paths',
|
'pattern', 'library', 'tools', 'planner', '_paths',
|
||||||
'_dead', '_logger', '_auto_render', '_auto_render_append'
|
'_dead', '_logger', '_render_policy', '_render_append', '_context_depth'
|
||||||
)
|
)
|
||||||
|
|
||||||
pattern: Pattern
|
pattern: Pattern
|
||||||
|
|
@ -128,8 +131,14 @@ class Pather(PortList):
|
||||||
_logger: PatherLogger
|
_logger: PatherLogger
|
||||||
""" Handles diagnostic logging of operations """
|
""" Handles diagnostic logging of operations """
|
||||||
|
|
||||||
_auto_render: bool
|
_render_policy: RenderPolicy
|
||||||
""" If True, routing operations call render() immediately """
|
""" Routing render behavior """
|
||||||
|
|
||||||
|
_render_append: bool
|
||||||
|
""" If True, automatic render calls append geometry instead of adding references """
|
||||||
|
|
||||||
|
_context_depth: int
|
||||||
|
""" Number of active context-manager entries """
|
||||||
|
|
||||||
_paths: defaultdict[str, list[RenderStep]]
|
_paths: defaultdict[str, list[RenderStep]]
|
||||||
""" Per-port pending render steps, consumed by `render()` """
|
""" Per-port pending render steps, consumed by `render()` """
|
||||||
|
|
@ -168,8 +177,8 @@ class Pather(PortList):
|
||||||
tools: Tool | MutableMapping[str | None, Tool] | None = None,
|
tools: Tool | MutableMapping[str | None, Tool] | None = None,
|
||||||
name: str | None = None,
|
name: str | None = None,
|
||||||
debug: bool = False,
|
debug: bool = False,
|
||||||
auto_render: bool = True,
|
render: RenderPolicy = 'auto',
|
||||||
auto_render_append: bool = True,
|
render_append: bool = True,
|
||||||
planner: RoutingPlanner | None = None,
|
planner: RoutingPlanner | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
|
|
@ -181,16 +190,26 @@ class Pather(PortList):
|
||||||
tools: Tool(s) to use for routing segments.
|
tools: Tool(s) to use for routing segments.
|
||||||
name: If specified, `library[name]` is set to `self.pattern`.
|
name: If specified, `library[name]` is set to `self.pattern`.
|
||||||
debug: If True, enables detailed logging.
|
debug: If True, enables detailed logging.
|
||||||
auto_render: If True, enables immediate rendering of routing steps.
|
render: Routing render policy. `'auto'` renders after each route
|
||||||
auto_render_append: If `auto_render` is True, determines whether
|
outside a context manager and defers until clean context exit
|
||||||
to append geometry or add a reference.
|
inside one. Use `'immediate'` to always render after each route,
|
||||||
|
`'deferred'` to keep paths pending until `render()` or clean
|
||||||
|
context exit, `'warn'` to log pending paths on clean context
|
||||||
|
exit, `'error'` to reject pending paths on clean context exit,
|
||||||
|
or `'ignore'` to leave pending paths silent.
|
||||||
|
render_append: If an automatic render is triggered, determines
|
||||||
|
whether to append geometry or add a reference.
|
||||||
planner: Optional stateless route-selection planner. If omitted,
|
planner: Optional stateless route-selection planner. If omitted,
|
||||||
a new `RoutingPlanner` is used.
|
a new `RoutingPlanner` is used.
|
||||||
"""
|
"""
|
||||||
|
if render not in RENDER_POLICIES:
|
||||||
|
raise BuildError(f'Invalid render policy {render!r}; expected one of {RENDER_POLICIES}')
|
||||||
|
|
||||||
self._dead = False
|
self._dead = False
|
||||||
self._logger = PatherLogger(debug=debug)
|
self._logger = PatherLogger(debug=debug)
|
||||||
self._auto_render = auto_render
|
self._render_policy = render
|
||||||
self._auto_render_append = auto_render_append
|
self._render_append = render_append
|
||||||
|
self._context_depth = 0
|
||||||
self.library = library
|
self.library = library
|
||||||
self.pattern = pattern if pattern is not None else Pattern()
|
self.pattern = pattern if pattern is not None else Pattern()
|
||||||
self.planner = RoutingPlanner() if planner is None else planner
|
self.planner = RoutingPlanner() if planner is None else planner
|
||||||
|
|
@ -213,9 +232,40 @@ class Pather(PortList):
|
||||||
if name is not None:
|
if name is not None:
|
||||||
library[name] = self.pattern
|
library[name] = self.pattern
|
||||||
|
|
||||||
def __del__(self) -> None:
|
def __enter__(self) -> Self:
|
||||||
if any(self._paths.values()):
|
self._context_depth += 1
|
||||||
logger.warning(f'Pather {self} had pending render steps', stack_info=True)
|
return self
|
||||||
|
|
||||||
|
def __exit__(
|
||||||
|
self,
|
||||||
|
exc_type: type[BaseException] | None,
|
||||||
|
exc_value: BaseException | None,
|
||||||
|
traceback: TracebackType | None,
|
||||||
|
) -> bool:
|
||||||
|
_ = exc_value, traceback
|
||||||
|
self._context_depth -= 1
|
||||||
|
if exc_type is not None or self._context_depth != 0 or not any(self._paths.values()):
|
||||||
|
return False
|
||||||
|
|
||||||
|
if self._render_policy in ('auto', 'deferred'):
|
||||||
|
self.render(append=self._render_append)
|
||||||
|
elif self._render_policy == 'warn':
|
||||||
|
logger.warning(
|
||||||
|
'Pather context exited with %s; call render() or use render="deferred"',
|
||||||
|
self._pending_render_summary(),
|
||||||
|
)
|
||||||
|
elif self._render_policy == 'error':
|
||||||
|
raise BuildError(f'Pather context exited with {self._pending_render_summary()}')
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _pending_render_summary(self) -> str:
|
||||||
|
ports = [(portspec, len(steps)) for portspec, steps in self._paths.items() if steps]
|
||||||
|
port_count = len(ports)
|
||||||
|
step_count = sum(count for _portspec, count in ports)
|
||||||
|
return (
|
||||||
|
f'{step_count} pending render step{"s" if step_count != 1 else ""} '
|
||||||
|
f'on {port_count} port{"s" if port_count != 1 else ""}'
|
||||||
|
)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
s = f'<Pather {self.pattern} L({len(self.library)}) {pformat(self.tools)}>'
|
s = f'<Pather {self.pattern} L({len(self.library)}) {pformat(self.tools)}>'
|
||||||
|
|
@ -397,12 +447,9 @@ class Pather(PortList):
|
||||||
Apply every action and deferred rename in a prepared route result.
|
Apply every action and deferred rename in a prepared route result.
|
||||||
|
|
||||||
Route actions may contain several primitive render steps and port
|
Route actions may contain several primitive render steps and port
|
||||||
mutations. Temporarily disabling auto-render prevents each step from
|
mutations. Immediate rendering happens once after the whole prepared
|
||||||
becoming its own rendered cell.
|
result has been applied.
|
||||||
"""
|
"""
|
||||||
saved_auto_render = self._auto_render
|
|
||||||
self._auto_render = False
|
|
||||||
try:
|
|
||||||
for action in result.actions:
|
for action in result.actions:
|
||||||
if not action.render_steps:
|
if not action.render_steps:
|
||||||
raise BuildError('Prepared route action has no render steps')
|
raise BuildError('Prepared route action has no render steps')
|
||||||
|
|
@ -416,11 +463,12 @@ class Pather(PortList):
|
||||||
self.plugged({action.portspec: action.plug_into})
|
self.plugged({action.portspec: action.plug_into})
|
||||||
for old_name, new_name in result.renames:
|
for old_name, new_name in result.renames:
|
||||||
self.rename_ports({old_name: new_name})
|
self.rename_ports({old_name: new_name})
|
||||||
self._auto_render = saved_auto_render
|
render_immediately = (
|
||||||
if saved_auto_render and any(self._paths.values()):
|
self._render_policy == 'immediate'
|
||||||
self.render(append = self._auto_render_append)
|
or (self._render_policy == 'auto' and self._context_depth == 0)
|
||||||
finally:
|
)
|
||||||
self._auto_render = saved_auto_render
|
if render_immediately and any(self._paths.values()):
|
||||||
|
self.render(append=self._render_append)
|
||||||
|
|
||||||
def _apply_dead_fallback(
|
def _apply_dead_fallback(
|
||||||
self,
|
self,
|
||||||
|
|
|
||||||
|
|
@ -560,7 +560,7 @@ class RenderStep:
|
||||||
"""
|
"""
|
||||||
A single deferred routing operation.
|
A single deferred routing operation.
|
||||||
|
|
||||||
`Pather(auto_render=False)` stores these records while routing and later
|
`Pather(render='deferred')` stores these records while routing and later
|
||||||
passes batches of compatible steps to `Tool.render()` when `Pather.render()`
|
passes batches of compatible steps to `Tool.render()` when `Pather.render()`
|
||||||
is called.
|
is called.
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -122,7 +122,7 @@ def test_autotool_straight_offer_supports_requested_output_transition(
|
||||||
) -> None:
|
) -> None:
|
||||||
_p, tool, lib = autotool_setup
|
_p, tool, lib = autotool_setup
|
||||||
|
|
||||||
p = Pather(lib, tools=tool, auto_render=False)
|
p = Pather(lib, tools=tool, render='deferred')
|
||||||
p.ports["start"] = Port((0, 0), pi, ptype="wire_m1")
|
p.ports["start"] = Port((0, 0), pi, ptype="wire_m1")
|
||||||
p.straight("start", 10, out_ptype="wire_m2")
|
p.straight("start", 10, out_ptype="wire_m2")
|
||||||
|
|
||||||
|
|
@ -158,7 +158,7 @@ def test_pather_straight_topology_allows_transition_offset_cancellation() -> Non
|
||||||
.add_transition(lib.abstract("trans_out"), "EXT", "CORE")
|
.add_transition(lib.abstract("trans_out"), "EXT", "CORE")
|
||||||
)
|
)
|
||||||
|
|
||||||
p = Pather(lib, tools=tool, auto_render=False)
|
p = Pather(lib, tools=tool, render='deferred')
|
||||||
p.ports["A"] = Port((0, 0), pi, ptype="ext_in")
|
p.ports["A"] = Port((0, 0), pi, ptype="ext_in")
|
||||||
p.trace("A", None, length=10, out_ptype="ext_out")
|
p.trace("A", None, length=10, out_ptype="ext_out")
|
||||||
|
|
||||||
|
|
@ -550,7 +550,7 @@ def test_autotool_s_offer_bbox_matches_rendered_sbend() -> None:
|
||||||
|
|
||||||
assert_offer_bbox_matches_rendered_offer(tool, offer, 4, lib)
|
assert_offer_bbox_matches_rendered_offer(tool, offer, 4, lib)
|
||||||
|
|
||||||
pather = Pather(lib, tools=tool, auto_render=False)
|
pather = Pather(lib, tools=tool, render='deferred')
|
||||||
pather.ports["A"] = Port((0, 0), 0, ptype="ext")
|
pather.ports["A"] = Port((0, 0), 0, ptype="ext")
|
||||||
pather.jog("A", 4)
|
pather.jog("A", 4)
|
||||||
|
|
||||||
|
|
@ -581,7 +581,7 @@ def test_autotool_s_offer_uses_absolute_jog_range_for_both_signs() -> None:
|
||||||
assert valid_positive
|
assert valid_positive
|
||||||
assert valid_negative
|
assert valid_negative
|
||||||
|
|
||||||
pather = Pather(Library(), tools=tool, auto_render=False)
|
pather = Pather(Library(), tools=tool, render='deferred')
|
||||||
pather.ports["A"] = Port((0, 0), 0, ptype="core")
|
pather.ports["A"] = Port((0, 0), 0, ptype="core")
|
||||||
pather.jog("A", -4)
|
pather.jog("A", -4)
|
||||||
assert_allclose(pather.ports["A"].offset, [-10, 4])
|
assert_allclose(pather.ports["A"].offset, [-10, 4])
|
||||||
|
|
@ -623,7 +623,7 @@ def test_autotool_s_offer_singleton_jog_range_includes_both_signs() -> None:
|
||||||
assert [offer.endpoint_at(-4).y for offer in offers if offer.jog_domain == (-4.0, -4.0)] == [-4]
|
assert [offer.endpoint_at(-4).y for offer in offers if offer.jog_domain == (-4.0, -4.0)] == [-4]
|
||||||
|
|
||||||
for jog, expected_y in ((4, -4), (-4, 4)):
|
for jog, expected_y in ((4, -4), (-4, 4)):
|
||||||
pather = Pather(Library(), tools=tool, auto_render=False)
|
pather = Pather(Library(), tools=tool)
|
||||||
pather.ports["A"] = Port((0, 0), 0, ptype="core")
|
pather.ports["A"] = Port((0, 0), 0, ptype="core")
|
||||||
pather.jog("A", jog)
|
pather.jog("A", jog)
|
||||||
assert_allclose(pather.ports["A"].offset, [-10, expected_y])
|
assert_allclose(pather.ports["A"].offset, [-10, expected_y])
|
||||||
|
|
@ -669,7 +669,7 @@ def test_autotool_uturn_offer_mirror_exposes_both_signs() -> None:
|
||||||
|
|
||||||
def test_pather_autotool_uses_prebaked_uturn_offer() -> None:
|
def test_pather_autotool_uses_prebaked_uturn_offer() -> None:
|
||||||
tool, lib = make_uturn_tool()
|
tool, lib = make_uturn_tool()
|
||||||
p = Pather(lib, tools=tool, auto_render=False)
|
p = Pather(lib, tools=tool, render='deferred')
|
||||||
p.ports["A"] = Port((0, 0), 0, ptype="core")
|
p.ports["A"] = Port((0, 0), 0, ptype="core")
|
||||||
|
|
||||||
p.uturn("A", 4, length=10)
|
p.uturn("A", 4, length=10)
|
||||||
|
|
@ -695,7 +695,7 @@ def test_pather_autotool_omitted_uturn_composes_l_offers(
|
||||||
multi_bend_tool: tuple[AutoTool, Library],
|
multi_bend_tool: tuple[AutoTool, Library],
|
||||||
) -> None:
|
) -> None:
|
||||||
tool, lib = multi_bend_tool
|
tool, lib = multi_bend_tool
|
||||||
p = Pather(lib, tools=tool, auto_render=False)
|
p = Pather(lib, tools=tool, render='deferred')
|
||||||
p.ports["A"] = Port((0, 0), 0, ptype="wire")
|
p.ports["A"] = Port((0, 0), 0, ptype="wire")
|
||||||
|
|
||||||
p.uturn("A", 20)
|
p.uturn("A", 20)
|
||||||
|
|
@ -714,7 +714,7 @@ def test_pather_autotool_explicit_uturn_composes_l_offers(
|
||||||
multi_bend_tool: tuple[AutoTool, Library],
|
multi_bend_tool: tuple[AutoTool, Library],
|
||||||
) -> None:
|
) -> None:
|
||||||
tool, lib = multi_bend_tool
|
tool, lib = multi_bend_tool
|
||||||
p = Pather(lib, tools=tool, auto_render=False)
|
p = Pather(lib, tools=tool, render='deferred')
|
||||||
p.ports["A"] = Port((0, 0), 0, ptype="wire")
|
p.ports["A"] = Port((0, 0), 0, ptype="wire")
|
||||||
|
|
||||||
p.uturn("A", 20, length=10)
|
p.uturn("A", 20, length=10)
|
||||||
|
|
@ -739,7 +739,7 @@ def test_autotool_offer_bbox_rejects_invalid_parameter(multi_bend_tool: tuple[Au
|
||||||
|
|
||||||
def test_pather_autotool_uses_l_offer_domains(multi_bend_tool: tuple[AutoTool, Library]) -> None:
|
def test_pather_autotool_uses_l_offer_domains(multi_bend_tool: tuple[AutoTool, Library]) -> None:
|
||||||
tool, lib = multi_bend_tool
|
tool, lib = multi_bend_tool
|
||||||
p = Pather(lib, tools=tool, auto_render=False)
|
p = Pather(lib, tools=tool, render='deferred')
|
||||||
p.ports["A"] = Port((0, 0), 0, ptype="wire")
|
p.ports["A"] = Port((0, 0), 0, ptype="wire")
|
||||||
|
|
||||||
p.trace("A", True, length=15)
|
p.trace("A", True, length=15)
|
||||||
|
|
@ -760,7 +760,7 @@ def test_autotool_generated_primitives_do_not_capture_route_kwargs() -> None:
|
||||||
return make_straight(length, ptype="wire")
|
return make_straight(length, ptype="wire")
|
||||||
|
|
||||||
tool = AutoTool().add_straight("wire", make_marked_straight, "A", length_range=(0, 1e8))
|
tool = AutoTool().add_straight("wire", make_marked_straight, "A", length_range=(0, 1e8))
|
||||||
p = Pather(Library(), tools=tool, auto_render=False)
|
p = Pather(Library(), tools=tool, render='deferred')
|
||||||
p.ports["A"] = Port((0, 0), 0, ptype="wire")
|
p.ports["A"] = Port((0, 0), 0, ptype="wire")
|
||||||
|
|
||||||
p.straight("A", 5, marker="route")
|
p.straight("A", 5, marker="route")
|
||||||
|
|
@ -790,7 +790,7 @@ def test_autotool_bend_offer_supports_requested_output_transition(ccw: bool) ->
|
||||||
.add_transition(lib.abstract("out_trans"), "EXT", "CORE")
|
.add_transition(lib.abstract("out_trans"), "EXT", "CORE")
|
||||||
)
|
)
|
||||||
|
|
||||||
p = Pather(lib, tools=tool, auto_render=False)
|
p = Pather(lib, tools=tool, render='deferred')
|
||||||
p.ports["A"] = Port((0, 0), 0, ptype="core")
|
p.ports["A"] = Port((0, 0), 0, ptype="core")
|
||||||
p.trace("A", ccw, length=10, out_ptype="ext")
|
p.trace("A", ccw, length=10, out_ptype="ext")
|
||||||
|
|
||||||
|
|
@ -824,7 +824,7 @@ def test_autotool_bend_offer_supports_bend_input_transition(ccw: bool) -> None:
|
||||||
.add_transition(lib.abstract("bend_trans"), "MID", "CORE")
|
.add_transition(lib.abstract("bend_trans"), "MID", "CORE")
|
||||||
)
|
)
|
||||||
|
|
||||||
p = Pather(lib, tools=tool, auto_render=False)
|
p = Pather(lib, tools=tool, render='deferred')
|
||||||
p.ports["A"] = Port((0, 0), 0, ptype="core")
|
p.ports["A"] = Port((0, 0), 0, ptype="core")
|
||||||
p.trace("A", ccw, length=10, out_ptype="mid")
|
p.trace("A", ccw, length=10, out_ptype="mid")
|
||||||
|
|
||||||
|
|
@ -857,7 +857,7 @@ def test_pather_accepts_bend_offer_with_zero_lateral_endpoint() -> None:
|
||||||
.add_transition(lib.abstract("bend_trans"), "MID", "CORE")
|
.add_transition(lib.abstract("bend_trans"), "MID", "CORE")
|
||||||
)
|
)
|
||||||
|
|
||||||
p = Pather(lib, tools=tool, auto_render=False)
|
p = Pather(lib, tools=tool, render='deferred')
|
||||||
p.ports["A"] = Port((0, 0), 0, ptype="core")
|
p.ports["A"] = Port((0, 0), 0, ptype="core")
|
||||||
p.trace("A", True, length=10, out_ptype="mid")
|
p.trace("A", True, length=10, out_ptype="mid")
|
||||||
|
|
||||||
|
|
@ -897,7 +897,7 @@ def test_autotool_bend_offer_supports_bend_and_output_transitions(ccw: bool) ->
|
||||||
.add_transition(lib.abstract("out_trans"), "EXT", "MID")
|
.add_transition(lib.abstract("out_trans"), "EXT", "MID")
|
||||||
)
|
)
|
||||||
|
|
||||||
p = Pather(lib, tools=tool, auto_render=False)
|
p = Pather(lib, tools=tool, render='deferred')
|
||||||
p.ports["A"] = Port((0, 0), 0, ptype="core")
|
p.ports["A"] = Port((0, 0), 0, ptype="core")
|
||||||
p.trace("A", ccw, length=12, out_ptype="ext")
|
p.trace("A", ccw, length=12, out_ptype="ext")
|
||||||
|
|
||||||
|
|
@ -955,7 +955,7 @@ def test_pather_autotool_pure_sbend_with_transition_dx() -> None:
|
||||||
assert isinstance(s_data, AutoTool.GeneratedData)
|
assert isinstance(s_data, AutoTool.GeneratedData)
|
||||||
assert s_offer.out_ptype == "core"
|
assert s_offer.out_ptype == "core"
|
||||||
|
|
||||||
p = Pather(lib, tools=tool, auto_render=False)
|
p = Pather(lib, tools=tool, render='deferred')
|
||||||
p.ports["A"] = Port((0, 0), 0, ptype="ext")
|
p.ports["A"] = Port((0, 0), 0, ptype="ext")
|
||||||
p.jog("A", 4)
|
p.jog("A", 4)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ def test_autotool_uturn() -> None:
|
||||||
.add_bend(lib.abstract('bend'), 'in', 'out', clockwise=True)
|
.add_bend(lib.abstract('bend'), 'in', 'out', clockwise=True)
|
||||||
)
|
)
|
||||||
|
|
||||||
p = Pather(lib, tools=tool, auto_render=False)
|
p = Pather(lib, tools=tool)
|
||||||
p.pattern.ports['A'] = Port((0, 0), 0)
|
p.pattern.ports['A'] = Port((0, 0), 0)
|
||||||
|
|
||||||
p.at('A').uturn(offset=-2000, length=1000)
|
p.at('A').uturn(offset=-2000, length=1000)
|
||||||
|
|
@ -82,7 +82,7 @@ def test_autotool_uturn() -> None:
|
||||||
|
|
||||||
def test_deferred_render_autotool_double_L(multi_bend_tool: tuple[AutoTool, Library]) -> None:
|
def test_deferred_render_autotool_double_L(multi_bend_tool: tuple[AutoTool, Library]) -> None:
|
||||||
tool, lib = multi_bend_tool
|
tool, lib = multi_bend_tool
|
||||||
rp = Pather(lib, tools=tool, auto_render=False)
|
rp = Pather(lib, tools=tool)
|
||||||
rp.ports["A"] = Port((0,0), 0, ptype="wire")
|
rp.ports["A"] = Port((0,0), 0, ptype="wire")
|
||||||
|
|
||||||
rp.jog("A", 10, length=20)
|
rp.jog("A", 10, length=20)
|
||||||
|
|
|
||||||
|
|
@ -117,7 +117,7 @@ class CountingPathTool(PathTool):
|
||||||
def test_pather_jog_failed_two_bend_route_is_atomic() -> None:
|
def test_pather_jog_failed_two_bend_route_is_atomic() -> None:
|
||||||
lib = Library()
|
lib = Library()
|
||||||
tool = PathTool(layer='M1', width=2, ptype='wire')
|
tool = PathTool(layer='M1', width=2, ptype='wire')
|
||||||
p = Pather(lib, tools=tool)
|
p = Pather(lib, tools=tool, render='immediate')
|
||||||
p.pattern.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
p.pattern.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
||||||
|
|
||||||
with pytest.raises(BuildError, match='S-bend'):
|
with pytest.raises(BuildError, match='S-bend'):
|
||||||
|
|
@ -130,7 +130,7 @@ def test_pather_jog_failed_two_bend_route_is_atomic() -> None:
|
||||||
def test_pather_jog_accepts_sub_width_offset_when_length_is_sufficient() -> None:
|
def test_pather_jog_accepts_sub_width_offset_when_length_is_sufficient() -> None:
|
||||||
lib = Library()
|
lib = Library()
|
||||||
tool = PathTool(layer='M1', width=2, ptype='wire')
|
tool = PathTool(layer='M1', width=2, ptype='wire')
|
||||||
p = Pather(lib, tools=tool)
|
p = Pather(lib, tools=tool, render='immediate')
|
||||||
p.pattern.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
p.pattern.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
||||||
|
|
||||||
p.jog('A', 1.5, length=5)
|
p.jog('A', 1.5, length=5)
|
||||||
|
|
@ -139,10 +139,10 @@ def test_pather_jog_accepts_sub_width_offset_when_length_is_sufficient() -> None
|
||||||
assert p.pattern.ports['A'].rotation == 0
|
assert p.pattern.ports['A'].rotation == 0
|
||||||
assert len(p._paths['A']) == 0
|
assert len(p._paths['A']) == 0
|
||||||
|
|
||||||
def test_pather_auto_render_batches_multi_step_selected_route_once() -> None:
|
def test_pather_immediate_render_batches_multi_step_selected_route_once() -> None:
|
||||||
lib = Library()
|
lib = Library()
|
||||||
tool = CountingPathTool(layer='M1', width=2, ptype='wire')
|
tool = CountingPathTool(layer='M1', width=2, ptype='wire')
|
||||||
p = Pather(lib, tools=tool, auto_render=True)
|
p = Pather(lib, tools=tool, render='immediate')
|
||||||
p.pattern.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
p.pattern.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
||||||
|
|
||||||
p.jog('A', 4, length=10)
|
p.jog('A', 4, length=10)
|
||||||
|
|
@ -169,7 +169,7 @@ def test_pather_jog_length_solved_from_single_position_bound() -> None:
|
||||||
|
|
||||||
|
|
||||||
def test_pather_positional_bound_requires_port_rotation() -> None:
|
def test_pather_positional_bound_requires_port_rotation() -> None:
|
||||||
p = Pather(Library(), tools=PathTool(layer='M1', width=1, ptype='wire'), auto_render=False)
|
p = Pather(Library(), tools=PathTool(layer='M1', width=1, ptype='wire'), render='deferred')
|
||||||
p.pattern.ports['A'] = Port((0, 0), rotation=None, ptype='wire')
|
p.pattern.ports['A'] = Port((0, 0), rotation=None, ptype='wire')
|
||||||
|
|
||||||
with pytest.raises(BuildError, match='Ports must have rotation'):
|
with pytest.raises(BuildError, match='Ports must have rotation'):
|
||||||
|
|
@ -179,7 +179,7 @@ def test_pather_positional_bound_requires_port_rotation() -> None:
|
||||||
def test_pather_jog_omitted_length_uses_minimum_length_route() -> None:
|
def test_pather_jog_omitted_length_uses_minimum_length_route() -> None:
|
||||||
lib = Library()
|
lib = Library()
|
||||||
tool = PathTool(layer='M1', width=1, ptype='wire')
|
tool = PathTool(layer='M1', width=1, ptype='wire')
|
||||||
p = Pather(lib, tools=tool, auto_render=False)
|
p = Pather(lib, tools=tool, render='deferred')
|
||||||
p.pattern.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
p.pattern.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
||||||
|
|
||||||
p.jog('A', 2)
|
p.jog('A', 2)
|
||||||
|
|
@ -195,7 +195,7 @@ def test_pather_jog_omitted_length_uses_minimum_length_route() -> None:
|
||||||
def test_pather_trace_omitted_length_uses_minimum_offer() -> None:
|
def test_pather_trace_omitted_length_uses_minimum_offer() -> None:
|
||||||
lib = Library()
|
lib = Library()
|
||||||
tool = PathTool(layer='M1', width=2, ptype='wire')
|
tool = PathTool(layer='M1', width=2, ptype='wire')
|
||||||
p = Pather(lib, tools=tool, auto_render=False)
|
p = Pather(lib, tools=tool, render='deferred')
|
||||||
p.pattern.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
p.pattern.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
||||||
|
|
||||||
p.trace('A', None)
|
p.trace('A', None)
|
||||||
|
|
@ -211,7 +211,7 @@ def test_pather_trace_omitted_length_uses_minimum_offer() -> None:
|
||||||
def test_pather_trace_to_without_bound_uses_single_port_trace_minimum() -> None:
|
def test_pather_trace_to_without_bound_uses_single_port_trace_minimum() -> None:
|
||||||
lib = Library()
|
lib = Library()
|
||||||
tool = PathTool(layer='M1', width=2, ptype='wire')
|
tool = PathTool(layer='M1', width=2, ptype='wire')
|
||||||
p = Pather(lib, tools=tool, auto_render=False)
|
p = Pather(lib, tools=tool, render='deferred')
|
||||||
p.pattern.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
p.pattern.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
||||||
|
|
||||||
p.trace_to('A', False)
|
p.trace_to('A', False)
|
||||||
|
|
@ -229,7 +229,7 @@ def test_pather_trace_to_rejects_conflicting_position_bounds() -> None:
|
||||||
with pytest.raises(BuildError, match='exactly one positional bound'):
|
with pytest.raises(BuildError, match='exactly one positional bound'):
|
||||||
p.trace_to('A', None, **kwargs)
|
p.trace_to('A', None, **kwargs)
|
||||||
|
|
||||||
p = Pather(Library(), tools=tool)
|
p = Pather(Library(), tools=tool, render='deferred')
|
||||||
p.pattern.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
p.pattern.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
||||||
with pytest.raises(BuildError, match='length cannot be combined'):
|
with pytest.raises(BuildError, match='length cannot be combined'):
|
||||||
p.trace_to('A', None, x=-5, length=3)
|
p.trace_to('A', None, x=-5, length=3)
|
||||||
|
|
@ -261,7 +261,7 @@ def test_planner_constrained_bend_requires_jog() -> None:
|
||||||
|
|
||||||
def test_pather_trace_each_plans_all_ports_before_mutation() -> None:
|
def test_pather_trace_each_plans_all_ports_before_mutation() -> None:
|
||||||
tool = FirstPortOnlyTraceTool()
|
tool = FirstPortOnlyTraceTool()
|
||||||
p = Pather(Library(), tools=tool, auto_render=False)
|
p = Pather(Library(), tools=tool)
|
||||||
p.pattern.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
p.pattern.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
||||||
p.pattern.ports['B'] = Port((-2, 5), rotation=0, ptype='blocked')
|
p.pattern.ports['B'] = Port((-2, 5), rotation=0, ptype='blocked')
|
||||||
|
|
||||||
|
|
@ -277,7 +277,7 @@ def test_pather_trace_each_plans_all_ports_before_mutation() -> None:
|
||||||
|
|
||||||
def test_pather_bundle_trace_plans_all_ports_before_mutation_or_render() -> None:
|
def test_pather_bundle_trace_plans_all_ports_before_mutation_or_render() -> None:
|
||||||
tool = FirstPortOnlyTraceTool()
|
tool = FirstPortOnlyTraceTool()
|
||||||
p = Pather(Library(), tools=tool, auto_render=True)
|
p = Pather(Library(), tools=tool, render='immediate')
|
||||||
p.pattern.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
p.pattern.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
||||||
p.pattern.ports['B'] = Port((0, 4), rotation=0, ptype='blocked')
|
p.pattern.ports['B'] = Port((0, 4), rotation=0, ptype='blocked')
|
||||||
|
|
||||||
|
|
@ -343,7 +343,7 @@ def test_pather_route_commit_failure_is_atomic_for_multi_port_trace() -> None:
|
||||||
return tree
|
return tree
|
||||||
|
|
||||||
tool = CommitFailureTool()
|
tool = CommitFailureTool()
|
||||||
p = Pather(Library(), tools=tool, auto_render=True)
|
p = Pather(Library(), tools=tool, render='immediate')
|
||||||
p.pattern.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
p.pattern.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
||||||
p.pattern.ports['B'] = Port((0, 4), rotation=0, ptype='bad')
|
p.pattern.ports['B'] = Port((0, 4), rotation=0, ptype='bad')
|
||||||
|
|
||||||
|
|
@ -433,7 +433,7 @@ def test_pather_uturn_does_not_use_direct_planl_fallback() -> None:
|
||||||
jog = -1
|
jog = -1
|
||||||
return Port((length, jog), rotation=rotation, ptype=in_ptype or 'wire'), {'ccw': ccw, 'length': length}
|
return Port((length, jog), rotation=rotation, ptype=in_ptype or 'wire'), {'ccw': ccw, 'length': length}
|
||||||
|
|
||||||
p = Pather(Library(), tools=PlanLOnlyTool(), auto_render=False)
|
p = Pather(Library(), tools=PlanLOnlyTool(), render='deferred')
|
||||||
p.pattern.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
p.pattern.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
||||||
|
|
||||||
with pytest.raises(BuildError, match='No legal primitive offer for omitted-length U-turn'):
|
with pytest.raises(BuildError, match='No legal primitive offer for omitted-length U-turn'):
|
||||||
|
|
@ -476,7 +476,7 @@ def test_pather_su_topology_rejects_out_ptype_sensitive_planl_jog() -> None:
|
||||||
ptype = out_ptype or in_ptype or 'wire'
|
ptype = out_ptype or in_ptype or 'wire'
|
||||||
return Port((length, jog), rotation=rotation, ptype=ptype), {'ccw': ccw, 'length': length}
|
return Port((length, jog), rotation=rotation, ptype=ptype), {'ccw': ccw, 'length': length}
|
||||||
|
|
||||||
p = Pather(Library(), tools=OutPtypeSensitiveTool(), auto_render=False)
|
p = Pather(Library(), tools=OutPtypeSensitiveTool(), render='deferred')
|
||||||
p.pattern.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
p.pattern.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
||||||
|
|
||||||
with pytest.raises((BuildError, NotImplementedError)):
|
with pytest.raises((BuildError, NotImplementedError)):
|
||||||
|
|
@ -509,7 +509,7 @@ def test_pather_two_l_planl_only_uturn_is_not_supported() -> None:
|
||||||
jog = -1
|
jog = -1
|
||||||
return Port((length, jog), rotation=rotation, ptype=in_ptype or 'wire'), {'ccw': ccw, 'length': length}
|
return Port((length, jog), rotation=rotation, ptype=in_ptype or 'wire'), {'ccw': ccw, 'length': length}
|
||||||
|
|
||||||
p = Pather(Library(), tools=PlanLOnlyTool(), auto_render=False)
|
p = Pather(Library(), tools=PlanLOnlyTool(), render='deferred')
|
||||||
p.pattern.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
p.pattern.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
||||||
|
|
||||||
with pytest.raises((BuildError, NotImplementedError)):
|
with pytest.raises((BuildError, NotImplementedError)):
|
||||||
|
|
@ -543,7 +543,7 @@ def test_pather_two_l_planl_only_jog_is_not_supported() -> None:
|
||||||
jog = -1
|
jog = -1
|
||||||
return Port((length, jog), rotation=rotation, ptype=in_ptype or 'wire'), {'ccw': ccw, 'length': length}
|
return Port((length, jog), rotation=rotation, ptype=in_ptype or 'wire'), {'ccw': ccw, 'length': length}
|
||||||
|
|
||||||
p = Pather(Library(), tools=PlanLOnlyTool(), auto_render=False)
|
p = Pather(Library(), tools=PlanLOnlyTool(), render='deferred')
|
||||||
p.pattern.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
p.pattern.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
||||||
|
|
||||||
with pytest.raises((BuildError, NotImplementedError)):
|
with pytest.raises((BuildError, NotImplementedError)):
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ def test_builder_tool_symbol_exports() -> None:
|
||||||
|
|
||||||
|
|
||||||
def test_pather_pending_render_steps_are_private() -> None:
|
def test_pather_pending_render_steps_are_private() -> None:
|
||||||
p = Pather(Library(), tools=PathTool(layer=(1, 0), width=1), auto_render=False)
|
p = Pather(Library(), tools=PathTool(layer=(1, 0), width=1))
|
||||||
|
|
||||||
assert not hasattr(p, 'paths')
|
assert not hasattr(p, 'paths')
|
||||||
assert hasattr(p, '_paths')
|
assert hasattr(p, '_paths')
|
||||||
|
|
@ -81,7 +81,7 @@ def test_pather_accepts_and_reuses_planner_instance() -> None:
|
||||||
p = Pather(
|
p = Pather(
|
||||||
Library(),
|
Library(),
|
||||||
tools=PathTool(layer=(1, 0), width=1),
|
tools=PathTool(layer=(1, 0), width=1),
|
||||||
auto_render=False,
|
render='deferred',
|
||||||
planner=planner,
|
planner=planner,
|
||||||
)
|
)
|
||||||
p.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
p.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
||||||
|
|
@ -148,7 +148,7 @@ def test_pather_dead_ports() -> None:
|
||||||
def test_pather_trace_basic() -> None:
|
def test_pather_trace_basic() -> None:
|
||||||
lib = Library()
|
lib = Library()
|
||||||
tool = PathTool(layer='M1', width=1000)
|
tool = PathTool(layer='M1', width=1000)
|
||||||
p = Pather(lib, tools=tool, auto_render=False)
|
p = Pather(lib, tools=tool)
|
||||||
|
|
||||||
# Routing extends opposite the port's inward-facing rotation.
|
# Routing extends opposite the port's inward-facing rotation.
|
||||||
p.pattern.ports['A'] = Port((0, 0), rotation=0)
|
p.pattern.ports['A'] = Port((0, 0), rotation=0)
|
||||||
|
|
@ -164,7 +164,7 @@ def test_pather_trace_basic() -> None:
|
||||||
def test_pather_trace_to() -> None:
|
def test_pather_trace_to() -> None:
|
||||||
lib = Library()
|
lib = Library()
|
||||||
tool = PathTool(layer='M1', width=1000)
|
tool = PathTool(layer='M1', width=1000)
|
||||||
p = Pather(lib, tools=tool, auto_render=False)
|
p = Pather(lib, tools=tool)
|
||||||
|
|
||||||
p.pattern.ports['A'] = Port((0, 0), rotation=0)
|
p.pattern.ports['A'] = Port((0, 0), rotation=0)
|
||||||
|
|
||||||
|
|
@ -177,7 +177,7 @@ def test_pather_trace_to() -> None:
|
||||||
def test_pather_bundle_trace() -> None:
|
def test_pather_bundle_trace() -> None:
|
||||||
lib = Library()
|
lib = Library()
|
||||||
tool = PathTool(layer='M1', width=1000)
|
tool = PathTool(layer='M1', width=1000)
|
||||||
p = Pather(lib, tools=tool, auto_render=False)
|
p = Pather(lib, tools=tool)
|
||||||
|
|
||||||
p.pattern.ports['A'] = Port((0, 0), rotation=0)
|
p.pattern.ports['A'] = Port((0, 0), rotation=0)
|
||||||
p.pattern.ports['B'] = Port((0, 2000), rotation=0)
|
p.pattern.ports['B'] = Port((0, 2000), rotation=0)
|
||||||
|
|
@ -194,13 +194,13 @@ def test_pather_bundle_trace() -> None:
|
||||||
def test_portpather_default_spacing_matches_explicit_spacing() -> None:
|
def test_portpather_default_spacing_matches_explicit_spacing() -> None:
|
||||||
lib_default = Library()
|
lib_default = Library()
|
||||||
tool_default = PathTool(layer='M1', width=1000)
|
tool_default = PathTool(layer='M1', width=1000)
|
||||||
p_default = Pather(lib_default, tools=tool_default, auto_render=False)
|
p_default = Pather(lib_default, tools=tool_default)
|
||||||
p_default.pattern.ports['A'] = Port((0, 0), rotation=0)
|
p_default.pattern.ports['A'] = Port((0, 0), rotation=0)
|
||||||
p_default.pattern.ports['B'] = Port((0, 2000), rotation=0)
|
p_default.pattern.ports['B'] = Port((0, 2000), rotation=0)
|
||||||
|
|
||||||
lib_explicit = Library()
|
lib_explicit = Library()
|
||||||
tool_explicit = PathTool(layer='M1', width=1000)
|
tool_explicit = PathTool(layer='M1', width=1000)
|
||||||
p_explicit = Pather(lib_explicit, tools=tool_explicit, auto_render=False)
|
p_explicit = Pather(lib_explicit, tools=tool_explicit)
|
||||||
p_explicit.pattern.ports['A'] = Port((0, 0), rotation=0)
|
p_explicit.pattern.ports['A'] = Port((0, 0), rotation=0)
|
||||||
p_explicit.pattern.ports['B'] = Port((0, 2000), rotation=0)
|
p_explicit.pattern.ports['B'] = Port((0, 2000), rotation=0)
|
||||||
|
|
||||||
|
|
@ -211,11 +211,11 @@ def test_portpather_default_spacing_matches_explicit_spacing() -> None:
|
||||||
assert_allclose(p_default.pattern.ports['B'].offset, p_explicit.pattern.ports['B'].offset)
|
assert_allclose(p_default.pattern.ports['B'].offset, p_explicit.pattern.ports['B'].offset)
|
||||||
|
|
||||||
def test_portpather_default_spacing_reused_and_overridden() -> None:
|
def test_portpather_default_spacing_reused_and_overridden() -> None:
|
||||||
p_default = Pather(Library(), tools=PathTool(layer='M1', width=1000), auto_render=False)
|
p_default = Pather(Library(), tools=PathTool(layer='M1', width=1000))
|
||||||
p_default.pattern.ports['A'] = Port((0, 0), rotation=0)
|
p_default.pattern.ports['A'] = Port((0, 0), rotation=0)
|
||||||
p_default.pattern.ports['B'] = Port((0, 2000), rotation=0)
|
p_default.pattern.ports['B'] = Port((0, 2000), rotation=0)
|
||||||
|
|
||||||
p_explicit = Pather(Library(), tools=PathTool(layer='M1', width=1000), auto_render=False)
|
p_explicit = Pather(Library(), tools=PathTool(layer='M1', width=1000))
|
||||||
p_explicit.pattern.ports['A'] = Port((0, 0), rotation=0)
|
p_explicit.pattern.ports['A'] = Port((0, 0), rotation=0)
|
||||||
p_explicit.pattern.ports['B'] = Port((0, 2000), rotation=0)
|
p_explicit.pattern.ports['B'] = Port((0, 2000), rotation=0)
|
||||||
|
|
||||||
|
|
@ -227,11 +227,11 @@ def test_portpather_default_spacing_reused_and_overridden() -> None:
|
||||||
assert_allclose(p_default.pattern.ports['A'].offset, p_explicit.pattern.ports['A'].offset)
|
assert_allclose(p_default.pattern.ports['A'].offset, p_explicit.pattern.ports['A'].offset)
|
||||||
assert_allclose(p_default.pattern.ports['B'].offset, p_explicit.pattern.ports['B'].offset)
|
assert_allclose(p_default.pattern.ports['B'].offset, p_explicit.pattern.ports['B'].offset)
|
||||||
|
|
||||||
p_override = Pather(Library(), tools=PathTool(layer='M1', width=1000), auto_render=False)
|
p_override = Pather(Library(), tools=PathTool(layer='M1', width=1000))
|
||||||
p_override.pattern.ports['A'] = Port((0, 0), rotation=0)
|
p_override.pattern.ports['A'] = Port((0, 0), rotation=0)
|
||||||
p_override.pattern.ports['B'] = Port((0, 2000), rotation=0)
|
p_override.pattern.ports['B'] = Port((0, 2000), rotation=0)
|
||||||
|
|
||||||
p_override_explicit = Pather(Library(), tools=PathTool(layer='M1', width=1000), auto_render=False)
|
p_override_explicit = Pather(Library(), tools=PathTool(layer='M1', width=1000))
|
||||||
p_override_explicit.pattern.ports['A'] = Port((0, 0), rotation=0)
|
p_override_explicit.pattern.ports['A'] = Port((0, 0), rotation=0)
|
||||||
p_override_explicit.pattern.ports['B'] = Port((0, 2000), rotation=0)
|
p_override_explicit.pattern.ports['B'] = Port((0, 2000), rotation=0)
|
||||||
|
|
||||||
|
|
@ -244,7 +244,7 @@ def test_portpather_default_spacing_reused_and_overridden() -> None:
|
||||||
def test_portpather_default_spacing_not_injected_for_straight_bundle() -> None:
|
def test_portpather_default_spacing_not_injected_for_straight_bundle() -> None:
|
||||||
lib = Library()
|
lib = Library()
|
||||||
tool = PathTool(layer='M1', width=1000)
|
tool = PathTool(layer='M1', width=1000)
|
||||||
p = Pather(lib, tools=tool, auto_render=False)
|
p = Pather(lib, tools=tool)
|
||||||
p.pattern.ports['A'] = Port((0, 0), rotation=0)
|
p.pattern.ports['A'] = Port((0, 0), rotation=0)
|
||||||
p.pattern.ports['B'] = Port((0, 2000), rotation=0)
|
p.pattern.ports['B'] = Port((0, 2000), rotation=0)
|
||||||
|
|
||||||
|
|
@ -256,7 +256,7 @@ def test_portpather_default_spacing_not_injected_for_straight_bundle() -> None:
|
||||||
def test_portpather_default_spacing_vector_revalidated_after_selection_change() -> None:
|
def test_portpather_default_spacing_vector_revalidated_after_selection_change() -> None:
|
||||||
lib = Library()
|
lib = Library()
|
||||||
tool = PathTool(layer='M1', width=1000)
|
tool = PathTool(layer='M1', width=1000)
|
||||||
p = Pather(lib, tools=tool, auto_render=False)
|
p = Pather(lib, tools=tool)
|
||||||
p.pattern.ports['A'] = Port((0, 0), rotation=0)
|
p.pattern.ports['A'] = Port((0, 0), rotation=0)
|
||||||
p.pattern.ports['B'] = Port((0, 2000), rotation=0)
|
p.pattern.ports['B'] = Port((0, 2000), rotation=0)
|
||||||
p.pattern.ports['C'] = Port((0, 4000), rotation=0)
|
p.pattern.ports['C'] = Port((0, 4000), rotation=0)
|
||||||
|
|
@ -270,7 +270,7 @@ def test_portpather_default_spacing_vector_revalidated_after_selection_change()
|
||||||
def test_pather_each_bound() -> None:
|
def test_pather_each_bound() -> None:
|
||||||
lib = Library()
|
lib = Library()
|
||||||
tool = PathTool(layer='M1', width=1000)
|
tool = PathTool(layer='M1', width=1000)
|
||||||
p = Pather(lib, tools=tool, auto_render=False)
|
p = Pather(lib, tools=tool)
|
||||||
|
|
||||||
p.pattern.ports['A'] = Port((0, 0), rotation=0)
|
p.pattern.ports['A'] = Port((0, 0), rotation=0)
|
||||||
p.pattern.ports['B'] = Port((-1000, 2000), rotation=0)
|
p.pattern.ports['B'] = Port((-1000, 2000), rotation=0)
|
||||||
|
|
@ -390,7 +390,7 @@ def test_rename() -> None:
|
||||||
def test_pather_dead_fallback_preserves_out_ptype() -> None:
|
def test_pather_dead_fallback_preserves_out_ptype() -> None:
|
||||||
lib = Library()
|
lib = Library()
|
||||||
tool = PathTool(layer='M1', width=1000, ptype='wire')
|
tool = PathTool(layer='M1', width=1000, ptype='wire')
|
||||||
p = Pather(lib, tools=tool, auto_render=False)
|
p = Pather(lib, tools=tool)
|
||||||
p.pattern.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
p.pattern.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
||||||
p.set_dead()
|
p.set_dead()
|
||||||
|
|
||||||
|
|
@ -424,7 +424,7 @@ def test_pather_dead_fallback_does_not_hide_fatal_route_errors() -> None:
|
||||||
commit_planner=lambda length: {'length': length},
|
commit_planner=lambda length: {'length': length},
|
||||||
),)
|
),)
|
||||||
|
|
||||||
p = Pather(Library(), tools=MismatchedEndpointTool(layer='M1', width=1000, ptype='wire'), auto_render=False)
|
p = Pather(Library(), tools=MismatchedEndpointTool(layer='M1', width=1000, ptype='wire'))
|
||||||
p.pattern.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
p.pattern.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
||||||
p.set_dead()
|
p.set_dead()
|
||||||
|
|
||||||
|
|
@ -472,7 +472,7 @@ def test_pather_valid_candidate_does_not_hide_fatal_route_errors() -> None:
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
p = Pather(Library(), tools=PartlyMismatchedEndpointTool(layer='M1', width=1000, ptype='wire'), auto_render=False)
|
p = Pather(Library(), tools=PartlyMismatchedEndpointTool(layer='M1', width=1000, ptype='wire'))
|
||||||
p.pattern.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
p.pattern.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
||||||
|
|
||||||
with pytest.raises(BuildError, match='does not match declared offer out_ptype'):
|
with pytest.raises(BuildError, match='does not match declared offer out_ptype'):
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ from masque.error import PortError, PatternError
|
||||||
def test_pather_place_treeview_resolves_once() -> None:
|
def test_pather_place_treeview_resolves_once() -> None:
|
||||||
lib = Library()
|
lib = Library()
|
||||||
tool = PathTool(layer='M1', width=1000)
|
tool = PathTool(layer='M1', width=1000)
|
||||||
p = Pather(lib, tools=tool)
|
p = Pather(lib, tools=tool, render='deferred')
|
||||||
|
|
||||||
tree = {'child': Pattern(ports={'B': Port((1, 0), pi)})}
|
tree = {'child': Pattern(ports={'B': Port((1, 0), pi)})}
|
||||||
|
|
||||||
|
|
@ -24,7 +24,7 @@ def test_pather_place_treeview_resolves_once() -> None:
|
||||||
def test_pather_plug_treeview_resolves_once() -> None:
|
def test_pather_plug_treeview_resolves_once() -> None:
|
||||||
lib = Library()
|
lib = Library()
|
||||||
tool = PathTool(layer='M1', width=1000)
|
tool = PathTool(layer='M1', width=1000)
|
||||||
p = Pather(lib, tools=tool)
|
p = Pather(lib, tools=tool, render='deferred')
|
||||||
p.pattern.ports['A'] = Port((0, 0), rotation=0)
|
p.pattern.ports['A'] = Port((0, 0), rotation=0)
|
||||||
|
|
||||||
tree = {'child': Pattern(ports={'B': Port((0, 0), pi)})}
|
tree = {'child': Pattern(ports={'B': Port((0, 0), pi)})}
|
||||||
|
|
@ -39,7 +39,7 @@ def test_pather_plug_treeview_resolves_once() -> None:
|
||||||
def test_pather_failed_plug_does_not_add_break_marker() -> None:
|
def test_pather_failed_plug_does_not_add_break_marker() -> None:
|
||||||
lib = Library()
|
lib = Library()
|
||||||
tool = PathTool(layer='M1', width=1000)
|
tool = PathTool(layer='M1', width=1000)
|
||||||
p = Pather(lib, tools=tool, auto_render=False)
|
p = Pather(lib, tools=tool, render='deferred')
|
||||||
p.pattern.annotations = {'k': [1]}
|
p.pattern.annotations = {'k': [1]}
|
||||||
p.pattern.ports['A'] = Port((0, 0), rotation=0)
|
p.pattern.ports['A'] = Port((0, 0), rotation=0)
|
||||||
|
|
||||||
|
|
@ -60,7 +60,7 @@ def test_pather_failed_plug_does_not_add_break_marker() -> None:
|
||||||
def test_pather_place_reused_deleted_name_keeps_break_marker() -> None:
|
def test_pather_place_reused_deleted_name_keeps_break_marker() -> None:
|
||||||
lib = Library()
|
lib = Library()
|
||||||
tool = PathTool(layer='M1', width=1000)
|
tool = PathTool(layer='M1', width=1000)
|
||||||
p = Pather(lib, tools=tool, auto_render=False)
|
p = Pather(lib, tools=tool, render='deferred')
|
||||||
p.pattern.ports['A'] = Port((0, 0), rotation=0)
|
p.pattern.ports['A'] = Port((0, 0), rotation=0)
|
||||||
|
|
||||||
p.at('A').straight(5000)
|
p.at('A').straight(5000)
|
||||||
|
|
@ -80,7 +80,7 @@ def test_pather_place_reused_deleted_name_keeps_break_marker() -> None:
|
||||||
def test_pather_plug_reused_deleted_name_keeps_break_marker() -> None:
|
def test_pather_plug_reused_deleted_name_keeps_break_marker() -> None:
|
||||||
lib = Library()
|
lib = Library()
|
||||||
tool = PathTool(layer='M1', width=1000)
|
tool = PathTool(layer='M1', width=1000)
|
||||||
p = Pather(lib, tools=tool, auto_render=False)
|
p = Pather(lib, tools=tool, render='deferred')
|
||||||
p.pattern.ports['A'] = Port((0, 0), rotation=0)
|
p.pattern.ports['A'] = Port((0, 0), rotation=0)
|
||||||
p.pattern.ports['B'] = Port((0, 0), rotation=0)
|
p.pattern.ports['B'] = Port((0, 0), rotation=0)
|
||||||
|
|
||||||
|
|
@ -107,7 +107,7 @@ def test_pather_plug_reused_deleted_name_keeps_break_marker() -> None:
|
||||||
def test_pather_failed_plugged_does_not_add_break_marker() -> None:
|
def test_pather_failed_plugged_does_not_add_break_marker() -> None:
|
||||||
lib = Library()
|
lib = Library()
|
||||||
tool = PathTool(layer='M1', width=1000)
|
tool = PathTool(layer='M1', width=1000)
|
||||||
p = Pather(lib, tools=tool, auto_render=False)
|
p = Pather(lib, tools=tool, render='deferred')
|
||||||
p.pattern.ports['A'] = Port((0, 0), rotation=0)
|
p.pattern.ports['A'] = Port((0, 0), rotation=0)
|
||||||
|
|
||||||
p.at('A').straight(5000)
|
p.at('A').straight(5000)
|
||||||
|
|
|
||||||
|
|
@ -360,7 +360,7 @@ def test_pather_treats_notimplemented_offer_query_as_no_offers() -> None:
|
||||||
_ = kind, in_ptype, out_ptype, kwargs
|
_ = kind, in_ptype, out_ptype, kwargs
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
p = Pather(Library(), tools=NoOfferTool(), auto_render=False)
|
p = Pather(Library(), tools=NoOfferTool())
|
||||||
p.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
p.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
||||||
|
|
||||||
with pytest.raises(BuildError, match='No legal primitive offer for trace'):
|
with pytest.raises(BuildError, match='No legal primitive offer for trace'):
|
||||||
|
|
@ -381,7 +381,7 @@ def test_pather_propagates_offer_query_errors(err_type: type[Exception]) -> None
|
||||||
_ = kind, in_ptype, out_ptype, kwargs
|
_ = kind, in_ptype, out_ptype, kwargs
|
||||||
raise err_type('offer query failed')
|
raise err_type('offer query failed')
|
||||||
|
|
||||||
p = Pather(Library(), tools=BrokenTool(), auto_render=False)
|
p = Pather(Library(), tools=BrokenTool())
|
||||||
p.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
p.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
||||||
|
|
||||||
with pytest.raises(err_type):
|
with pytest.raises(err_type):
|
||||||
|
|
@ -413,7 +413,7 @@ def test_pather_selects_lowest_cost_offer() -> None:
|
||||||
StraightOffer(in_ptype=in_ptype, out_ptype=out_ptype, priority_bias=0, **offer_callbacks(low)),
|
StraightOffer(in_ptype=in_ptype, out_ptype=out_ptype, priority_bias=0, **offer_callbacks(low)),
|
||||||
)
|
)
|
||||||
|
|
||||||
p = Pather(Library(), tools=MultiOfferTool(), auto_render=False)
|
p = Pather(Library(), tools=MultiOfferTool(), render='deferred')
|
||||||
p.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
p.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
||||||
|
|
||||||
p.straight('A', 7)
|
p.straight('A', 7)
|
||||||
|
|
@ -460,7 +460,7 @@ def test_solver_rejects_rotation_impossible_candidates_before_parameter_solving(
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
p = Pather(Library(), tools=RotationOfferTool(), auto_render=False)
|
p = Pather(Library(), tools=RotationOfferTool(), render='deferred')
|
||||||
p.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
p.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
||||||
|
|
||||||
p.straight('A', 7)
|
p.straight('A', 7)
|
||||||
|
|
@ -503,7 +503,7 @@ def test_pather_commits_only_selected_offer() -> None:
|
||||||
|
|
||||||
return (make('expensive', 100), make('cheap', 0))
|
return (make('expensive', 100), make('cheap', 0))
|
||||||
|
|
||||||
p = Pather(Library(), tools=RecordingTool(), auto_render=False)
|
p = Pather(Library(), tools=RecordingTool(), render='deferred')
|
||||||
p.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
p.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
||||||
|
|
||||||
p.straight('A', 5)
|
p.straight('A', 5)
|
||||||
|
|
@ -553,7 +553,7 @@ def test_pather_routes_with_offer_only_s_and_u_tool() -> None:
|
||||||
),)
|
),)
|
||||||
return ()
|
return ()
|
||||||
|
|
||||||
p = Pather(Library(), tools=OfferOnlyTool(), auto_render=False)
|
p = Pather(Library(), tools=OfferOnlyTool(), render='deferred')
|
||||||
p.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
p.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
||||||
p.jog('A', 3)
|
p.jog('A', 3)
|
||||||
p.uturn('A', 4)
|
p.uturn('A', 4)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
from typing import TYPE_CHECKING, cast
|
from typing import TYPE_CHECKING, cast
|
||||||
|
import logging
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import numpy
|
import numpy
|
||||||
|
|
@ -20,10 +21,27 @@ if TYPE_CHECKING:
|
||||||
def deferred_render_setup() -> tuple[Pather, PathTool, Library]:
|
def deferred_render_setup() -> tuple[Pather, PathTool, Library]:
|
||||||
lib = Library()
|
lib = Library()
|
||||||
tool = PathTool(layer=(1, 0), width=2, ptype="wire")
|
tool = PathTool(layer=(1, 0), width=2, ptype="wire")
|
||||||
rp = Pather(lib, tools=tool, auto_render=False)
|
rp = Pather(lib, tools=tool, render='deferred')
|
||||||
rp.ports["start"] = Port((0, 0), pi / 2, ptype="wire")
|
rp.ports["start"] = Port((0, 0), pi / 2, ptype="wire")
|
||||||
return rp, tool, lib
|
return rp, tool, lib
|
||||||
|
|
||||||
|
|
||||||
|
def add_pending_straight(pather: Pather) -> None:
|
||||||
|
pather.ports["start"] = Port((0, 0), pi / 2, ptype="wire")
|
||||||
|
pather.straight("start", 10)
|
||||||
|
|
||||||
|
|
||||||
|
def route_in_context(pather: Pather) -> None:
|
||||||
|
with pather:
|
||||||
|
add_pending_straight(pather)
|
||||||
|
|
||||||
|
|
||||||
|
def fail_in_context(pather: Pather) -> None:
|
||||||
|
with pather:
|
||||||
|
add_pending_straight(pather)
|
||||||
|
raise RuntimeError('body failed')
|
||||||
|
|
||||||
|
|
||||||
def test_deferred_render_stores_pending_paths_until_render(deferred_render_setup: tuple[Pather, PathTool, Library]) -> None:
|
def test_deferred_render_stores_pending_paths_until_render(deferred_render_setup: tuple[Pather, PathTool, Library]) -> None:
|
||||||
rp, tool, lib = deferred_render_setup
|
rp, tool, lib = deferred_render_setup
|
||||||
rp.at("start").straight(10).straight(10)
|
rp.at("start").straight(10).straight(10)
|
||||||
|
|
@ -101,7 +119,7 @@ def test_portpather_translate_only_affects_future_steps(deferred_render_setup: t
|
||||||
def test_deferred_render_dead_ports() -> None:
|
def test_deferred_render_dead_ports() -> None:
|
||||||
lib = Library()
|
lib = Library()
|
||||||
tool = PathTool(layer=(1, 0), width=1)
|
tool = PathTool(layer=(1, 0), width=1)
|
||||||
rp = Pather(lib, ports={"in": Port((0, 0), 0)}, tools=tool, auto_render=False)
|
rp = Pather(lib, ports={"in": Port((0, 0), 0)}, tools=tool, render='deferred')
|
||||||
rp.set_dead()
|
rp.set_dead()
|
||||||
|
|
||||||
rp.straight("in", -10)
|
rp.straight("in", -10)
|
||||||
|
|
@ -113,6 +131,91 @@ def test_deferred_render_dead_ports() -> None:
|
||||||
rp.render()
|
rp.render()
|
||||||
assert not rp.pattern.has_shapes()
|
assert not rp.pattern.has_shapes()
|
||||||
|
|
||||||
|
|
||||||
|
def test_pather_default_auto_policy_renders_immediately_outside_context() -> None:
|
||||||
|
lib = Library()
|
||||||
|
tool = PathTool(layer=(1, 0), width=2, ptype="wire")
|
||||||
|
p = Pather(lib, tools=tool)
|
||||||
|
p.ports["start"] = Port((0, 0), pi / 2, ptype="wire")
|
||||||
|
|
||||||
|
p.straight("start", 10)
|
||||||
|
|
||||||
|
assert p.pattern.has_shapes()
|
||||||
|
assert not any(p._paths.values())
|
||||||
|
|
||||||
|
|
||||||
|
def test_pather_default_auto_policy_defers_until_clean_context_exit() -> None:
|
||||||
|
lib = Library()
|
||||||
|
tool = PathTool(layer=(1, 0), width=2, ptype="wire")
|
||||||
|
|
||||||
|
with Pather(lib, tools=tool) as p:
|
||||||
|
add_pending_straight(p)
|
||||||
|
assert not p.pattern.has_shapes()
|
||||||
|
assert len(p._paths["start"]) == 1
|
||||||
|
|
||||||
|
assert p.pattern.has_shapes()
|
||||||
|
assert not any(p._paths.values())
|
||||||
|
|
||||||
|
|
||||||
|
def test_pather_context_warn_policy_logs_pending_paths(caplog: pytest.LogCaptureFixture) -> None:
|
||||||
|
lib = Library()
|
||||||
|
tool = PathTool(layer=(1, 0), width=2, ptype="wire")
|
||||||
|
|
||||||
|
with caplog.at_level(logging.WARNING, logger="masque.builder.pather"), Pather(lib, tools=tool, render='warn') as p:
|
||||||
|
add_pending_straight(p)
|
||||||
|
|
||||||
|
records = [
|
||||||
|
record
|
||||||
|
for record in caplog.records
|
||||||
|
if 'Pather context exited with 1 pending render step on 1 port' in record.getMessage()
|
||||||
|
]
|
||||||
|
assert len(records) == 1
|
||||||
|
assert records[0].stack_info is None
|
||||||
|
assert len(p._paths["start"]) == 1
|
||||||
|
assert not p.pattern.has_shapes()
|
||||||
|
|
||||||
|
|
||||||
|
def test_pather_context_error_policy_rejects_pending_paths() -> None:
|
||||||
|
lib = Library()
|
||||||
|
tool = PathTool(layer=(1, 0), width=2, ptype="wire")
|
||||||
|
p = Pather(lib, tools=tool, render='error')
|
||||||
|
|
||||||
|
with pytest.raises(BuildError, match='1 pending render step on 1 port'):
|
||||||
|
route_in_context(p)
|
||||||
|
|
||||||
|
assert len(p._paths["start"]) == 1
|
||||||
|
assert not p.pattern.has_shapes()
|
||||||
|
|
||||||
|
|
||||||
|
def test_pather_context_ignore_policy_leaves_pending_paths_silent(caplog: pytest.LogCaptureFixture) -> None:
|
||||||
|
lib = Library()
|
||||||
|
tool = PathTool(layer=(1, 0), width=2, ptype="wire")
|
||||||
|
|
||||||
|
with caplog.at_level(logging.WARNING, logger="masque.builder.pather"), Pather(lib, tools=tool, render='ignore') as p:
|
||||||
|
add_pending_straight(p)
|
||||||
|
|
||||||
|
assert not caplog.records
|
||||||
|
assert len(p._paths["start"]) == 1
|
||||||
|
assert not p.pattern.has_shapes()
|
||||||
|
|
||||||
|
|
||||||
|
def test_pather_context_policy_does_not_mask_body_exception() -> None:
|
||||||
|
lib = Library()
|
||||||
|
tool = PathTool(layer=(1, 0), width=2, ptype="wire")
|
||||||
|
p = Pather(lib, tools=tool, render='error')
|
||||||
|
|
||||||
|
with pytest.raises(RuntimeError, match='body failed'):
|
||||||
|
fail_in_context(p)
|
||||||
|
|
||||||
|
assert len(p._paths["start"]) == 1
|
||||||
|
assert not p.pattern.has_shapes()
|
||||||
|
|
||||||
|
|
||||||
|
def test_pather_rejects_invalid_render_policy() -> None:
|
||||||
|
with pytest.raises(BuildError, match='Invalid render policy'):
|
||||||
|
Pather(Library(), tools=PathTool(layer=(1, 0), width=2), render='later') # type: ignore[arg-type]
|
||||||
|
|
||||||
|
|
||||||
def test_deferred_render_rename_port(deferred_render_setup: tuple[Pather, PathTool, Library]) -> None:
|
def test_deferred_render_rename_port(deferred_render_setup: tuple[Pather, PathTool, Library]) -> None:
|
||||||
rp, tool, lib = deferred_render_setup
|
rp, tool, lib = deferred_render_setup
|
||||||
rp.at("start").straight(10)
|
rp.at("start").straight(10)
|
||||||
|
|
@ -174,7 +277,7 @@ def test_pathtool_s_offer_render_geometry_matches_ports() -> None:
|
||||||
def test_deferred_render_uturn_fallback() -> None:
|
def test_deferred_render_uturn_fallback() -> None:
|
||||||
lib = Library()
|
lib = Library()
|
||||||
tool = PathTool(layer='M1', width=1000)
|
tool = PathTool(layer='M1', width=1000)
|
||||||
rp = Pather(lib, tools=tool, auto_render=False)
|
rp = Pather(lib, tools=tool, render='deferred')
|
||||||
rp.pattern.ports['A'] = Port((0, 0), rotation=0)
|
rp.pattern.ports['A'] = Port((0, 0), rotation=0)
|
||||||
|
|
||||||
rp.at('A').uturn(offset=10000, length=5000)
|
rp.at('A').uturn(offset=10000, length=5000)
|
||||||
|
|
@ -213,7 +316,7 @@ def test_pather_render_auto_renames_single_use_tool_children() -> None:
|
||||||
return tree
|
return tree
|
||||||
|
|
||||||
lib = Library()
|
lib = Library()
|
||||||
p = Pather(lib, tools=FullTreeTool(), auto_render=False)
|
p = Pather(lib, tools=FullTreeTool())
|
||||||
p.pattern.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
p.pattern.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
||||||
|
|
||||||
p.straight('A', 10)
|
p.straight('A', 10)
|
||||||
|
|
@ -258,7 +361,7 @@ def test_custom_tool_render_preserves_segment_subtrees() -> None:
|
||||||
return batch[0].data
|
return batch[0].data
|
||||||
|
|
||||||
lib = Library()
|
lib = Library()
|
||||||
p = Pather(lib, tools=TraceTreeTool(), auto_render=False)
|
p = Pather(lib, tools=TraceTreeTool())
|
||||||
p.pattern.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
p.pattern.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
||||||
|
|
||||||
p.straight('A', 10)
|
p.straight('A', 10)
|
||||||
|
|
@ -293,7 +396,7 @@ def test_pather_render_rejects_missing_single_use_tool_refs() -> None:
|
||||||
|
|
||||||
lib = Library()
|
lib = Library()
|
||||||
lib['_seg'] = Pattern(annotations={'stale': [1]})
|
lib['_seg'] = Pattern(annotations={'stale': [1]})
|
||||||
p = Pather(lib, tools=MissingSingleUseTool(), auto_render=False)
|
p = Pather(lib, tools=MissingSingleUseTool(), render='deferred')
|
||||||
p.pattern.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
p.pattern.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
||||||
p.straight('A', 10)
|
p.straight('A', 10)
|
||||||
|
|
||||||
|
|
@ -329,7 +432,7 @@ def test_pather_render_allows_missing_non_single_use_tool_refs() -> None:
|
||||||
|
|
||||||
lib = Library()
|
lib = Library()
|
||||||
lib['shared'] = Pattern(annotations={'shared': [1]})
|
lib['shared'] = Pattern(annotations={'shared': [1]})
|
||||||
p = Pather(lib, tools=SharedRefTool(), auto_render=False)
|
p = Pather(lib, tools=SharedRefTool())
|
||||||
p.pattern.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
p.pattern.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
||||||
|
|
||||||
p.straight('A', 10)
|
p.straight('A', 10)
|
||||||
|
|
@ -362,7 +465,7 @@ def test_pather_render_rejects_output_port_that_misses_planned_endpoint(append:
|
||||||
return tree
|
return tree
|
||||||
|
|
||||||
lib = Library()
|
lib = Library()
|
||||||
p = Pather(lib, tools=WrongEndpointTool(), auto_render=False)
|
p = Pather(lib, tools=WrongEndpointTool(), render='deferred')
|
||||||
p.pattern.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
p.pattern.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
||||||
p.straight('A', 10)
|
p.straight('A', 10)
|
||||||
|
|
||||||
|
|
@ -396,7 +499,7 @@ def test_pather_render_rejects_output_port_with_wrong_ptype(append: bool) -> Non
|
||||||
return tree
|
return tree
|
||||||
|
|
||||||
lib = Library()
|
lib = Library()
|
||||||
p = Pather(lib, tools=WrongPtypeTool(), auto_render=False)
|
p = Pather(lib, tools=WrongPtypeTool(), render='deferred')
|
||||||
p.pattern.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
p.pattern.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
||||||
p.straight('A', 10)
|
p.straight('A', 10)
|
||||||
|
|
||||||
|
|
@ -409,7 +512,7 @@ def test_pather_render_rejects_output_port_with_wrong_ptype(append: bool) -> Non
|
||||||
def test_deferred_render_rename_to_none_keeps_pending_geometry_without_port() -> None:
|
def test_deferred_render_rename_to_none_keeps_pending_geometry_without_port() -> None:
|
||||||
lib = Library()
|
lib = Library()
|
||||||
tool = PathTool(layer='M1', width=1000)
|
tool = PathTool(layer='M1', width=1000)
|
||||||
rp = Pather(lib, tools=tool, auto_render=False)
|
rp = Pather(lib, tools=tool, render='deferred')
|
||||||
rp.pattern.ports['A'] = Port((0, 0), rotation=0)
|
rp.pattern.ports['A'] = Port((0, 0), rotation=0)
|
||||||
|
|
||||||
rp.at('A').straight(5000)
|
rp.at('A').straight(5000)
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ from masque.error import BuildError, PortError
|
||||||
def trace_into_setup() -> tuple[Pather, PathTool, Library]:
|
def trace_into_setup() -> tuple[Pather, PathTool, Library]:
|
||||||
lib = Library()
|
lib = Library()
|
||||||
tool = PathTool(layer=(1, 0), width=2, ptype="wire")
|
tool = PathTool(layer=(1, 0), width=2, ptype="wire")
|
||||||
p = Pather(lib, tools=tool, auto_render=True, auto_render_append=False)
|
p = Pather(lib, tools=tool, render='immediate', render_append=False)
|
||||||
return p, tool, lib
|
return p, tool, lib
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -71,7 +71,7 @@ def test_path_into_thru(trace_into_setup: tuple[Pather, PathTool, Library]) -> N
|
||||||
def test_pather_trace_into_shapes() -> None:
|
def test_pather_trace_into_shapes() -> None:
|
||||||
lib = Library()
|
lib = Library()
|
||||||
tool = PathTool(layer='M1', width=1000)
|
tool = PathTool(layer='M1', width=1000)
|
||||||
p = Pather(lib, tools=tool, auto_render=False)
|
p = Pather(lib, tools=tool)
|
||||||
|
|
||||||
p.pattern.ports['A'] = Port((0, 0), rotation=0)
|
p.pattern.ports['A'] = Port((0, 0), rotation=0)
|
||||||
p.pattern.ports['B'] = Port((-10000, 0), rotation=pi)
|
p.pattern.ports['B'] = Port((-10000, 0), rotation=pi)
|
||||||
|
|
@ -175,7 +175,7 @@ def test_pather_trace_into_refines_output_adapter_route_before_plug() -> None:
|
||||||
pat.add_port_pair(names=port_names, ptype=batch[-1].end_port.ptype if batch else 'm1wire')
|
pat.add_port_pair(names=port_names, ptype=batch[-1].end_port.ptype if batch else 'm1wire')
|
||||||
return tree
|
return tree
|
||||||
|
|
||||||
p = Pather(Library(), tools=TransitionTool(), auto_render=False)
|
p = Pather(Library(), tools=TransitionTool(), render='deferred')
|
||||||
p.pattern.ports['src'] = Port((-65000, -11500), rotation=pi / 2, ptype='m1wire')
|
p.pattern.ports['src'] = Port((-65000, -11500), rotation=pi / 2, ptype='m1wire')
|
||||||
p.pattern.ports['dst'] = Port((-100000, -100000), rotation=pi, ptype='m2wire')
|
p.pattern.ports['dst'] = Port((-100000, -100000), rotation=pi, ptype='m2wire')
|
||||||
|
|
||||||
|
|
@ -188,7 +188,7 @@ def test_pather_trace_into_refines_output_adapter_route_before_plug() -> None:
|
||||||
def test_pather_trace_into_dead_updates_ports_without_geometry() -> None:
|
def test_pather_trace_into_dead_updates_ports_without_geometry() -> None:
|
||||||
lib = Library()
|
lib = Library()
|
||||||
tool = PathTool(layer='M1', width=1000, ptype='wire')
|
tool = PathTool(layer='M1', width=1000, ptype='wire')
|
||||||
p = Pather(lib, tools=tool, auto_render=False)
|
p = Pather(lib, tools=tool)
|
||||||
p.pattern.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
p.pattern.ports['A'] = Port((0, 0), rotation=0, ptype='wire')
|
||||||
p.pattern.ports['B'] = Port((-10000, 0), rotation=pi, ptype='wire')
|
p.pattern.ports['B'] = Port((-10000, 0), rotation=pi, ptype='wire')
|
||||||
p.set_dead()
|
p.set_dead()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue