[Pather / Tool] more unification work and fixes

This commit is contained in:
Jan Petykiewicz 2026-07-13 14:44:39 -07:00
commit f9611933ac
19 changed files with 1022 additions and 137 deletions

View file

@ -288,7 +288,25 @@ you are exposing:
common callback behavior. It is not the normal class users should instantiate.
The concrete offer classes carry the semantic fields (`length_domain`,
`jog_domain`, `ccw`) so tools do not need to encode primitive identity in
strings.
strings. Each concrete offer now also exposes a class-level canonical `kind`.
Tools must return the matching offer kind for the discovery query; for example,
`primitive_offers('s', ...)` must return only `SOffer` instances. Mismatches are
reported as fatal `ToolContractError`s rather than silently ignored.
`RenderStep` now stores that same canonical kind. Code that constructs render
steps directly must use the kind rather than a legacy opcode:
```python
# old
RenderStep('L', tool, start, end, data)
# new
RenderStep('straight', tool, start, end, data)
```
Use `'bend'`, `'s'`, `'u'`, or `'plug'` for the other step types. The read-only
`RenderStep.opcode` and `PrimitiveOffer.opcode` properties remain available for
code that only consumes steps, but opcodes are now derived rather than stored.
Minimal straight-only example:
@ -341,9 +359,11 @@ The mapping is unpacked only for `Tool.primitive_offers()`. Route arguments do
not leak into that namespace, and tool options are not forwarded to
`Tool.render()`. An offer that needs route-specific render behavior must capture
the selected value in its `commit()` result (`RenderStep.data`). `AutoTool`
does this automatically for generated straight and S-bend primitives: the flat
mapping is snapshotted per selected primitive and passed to its generator as
keyword arguments during rendering. AutoTool options must not change generated
does this automatically for generated straight and S-bend primitives: the
mapping is deep-copied per offer discovery and passed to its generator as
keyword arguments during rendering. Consequently every AutoTool option value
must be deep-copyable, and later mutation of nested caller-owned values does
not affect a pending route. AutoTool options must not change generated
ports or endpoint geometry. `PathTool` defines no tool options and rejects
nonempty mappings.
@ -360,9 +380,51 @@ Primitive offers are local planning objects:
- `parameterized_bbox` may carry opaque future-router footprint metadata
- `commit(parameter)` returns opaque render data consumed later by `render()`
- `(min, max)` parameter domains are half-open; `(value, value)` is a fixed singleton
- domains are validated when an offer is constructed, not when it is first evaluated
- selected parameter values must be finite; domains may use infinite open bounds but not `NaN`
- straight/bend domains require a finite nonnegative minimum; fixed singleton domains must be finite
- `None` and `"unk"` ptypes are wildcards; concrete ptype mismatches reject an offer
`AutoTool.add_straight(length_range=...)` and
`AutoTool.add_sbend(jog_range=...)` now validate their ranges before metadata
inference. `jog_range` is an absolute-magnitude range and must have a finite,
nonnegative lower bound; AutoTool creates the corresponding positive and
negative S offers itself. Invalid ranges now raise immediately instead of
registering no offers.
`ToolContractError` is exported from `masque` and `masque.builder`. It marks a
broken Tool contract—such as an endpoint ptype that disagrees with its offer,
an invalid evaluated cost, or rendered output that disagrees with the planned
endpoint—and is fatal to route fallback. Ordinary `BuildError` raised by a
planning callback remains a recoverable candidate rejection. Rendered output
rotations are compared modulo one full turn; a port facing exactly backward is
no longer accepted as equivalent. A `None` rotation remains an explicit
wildcard.
Custom Tool authors can exercise discovery, offer callbacks, committed data,
and one-step rendering without depending on pytest:
```python
from masque.builder import ToolContractCase, validate_tool_contract
validate_tool_contract(my_tool, (
ToolContractCase('straight', in_ptype='wire', probe_parameters=(10,)),
ToolContractCase('bend', in_ptype='wire', ccw=False),
ToolContractCase('bend', in_ptype='wire', ccw=True),
ToolContractCase('s', in_ptype='wire', require_offers=False),
))
```
Cases derive representative parameters from each returned offer domain and
may add explicit probes. Empty discovery is an error unless
`require_offers=False`; bbox support is required only with `check_bbox=True`.
Validation returns normally on success and otherwise raises an
`ExceptionGroup` of contextual `ToolContractError`s.
Positional routing bounds (`p`, `pos`, `position`, `x`, and `y`) now require a
nearly Manhattan input-port direction. Arbitrarily angled ports remain valid
for non-positional/extension routing.
Heterogeneous `StraightOffer` and `SOffer` objects may be used as ptype
adapters. Requested `out_ptype` constrains only the final route endpoint; any
intermediate ptypes are chosen by the route solver.
@ -717,8 +779,9 @@ These are additive, but available now from `masque` and `masque.builder`:
- from `masque`: `RectCollection`, `boolean`, `OverlayLibrary`,
`PortsLibraryView`, `IMaterializable`, `IBorrowing`, `LibraryBuilder`,
`BuildReport`, `CellProvenance`, and `cell`
- from `masque.builder`: `CostCallable`, the concrete primitive-offer classes,
and structured route error/status types
- from `masque.builder`: `CostCallable`, `RenderStepKind`, the concrete
primitive-offer classes, structured route error/status types,
`ToolContractCase`, and `validate_tool_contract`
## Minimal migration checklist