803 lines
31 KiB
Markdown
803 lines
31 KiB
Markdown
# Migration Guide
|
|
|
|
This guide covers changes between the `master` branch and the current tree.
|
|
Both `master` and the current tree report `masque.__version__ == '3.4'`; the
|
|
version string has not yet been bumped for these changes.
|
|
|
|
Most downstream changes are in `masque/builder/*`, but there are a few other
|
|
API changes that may require code updates.
|
|
|
|
## Routing API: renamed and consolidated
|
|
|
|
The routing helpers were consolidated into a single implementation in
|
|
`masque/builder/pather.py`.
|
|
|
|
The biggest migration point is that the old routing verbs were renamed:
|
|
|
|
| Old API | New API |
|
|
| --- | --- |
|
|
| `Pather.path(...)` | `Pather.trace(...)` |
|
|
| `Pather.path_to(...)` | `Pather.trace_to(...)` |
|
|
| `Pather.mpath(...)` | `Pather.trace(...)` / `Pather.trace_to(...)` with multiple ports |
|
|
| `Pather.pathS(...)` | `Pather.jog(...)` |
|
|
| `Pather.pathU(...)` | `Pather.uturn(...)` |
|
|
| `Pather.path_into(...)` | `Pather.trace_into(...)` |
|
|
| `Pather.path_from(src, dst)` | `Pather.at(src).trace_into(dst)` |
|
|
| `RenderPather.path(...)` | `Pather(..., render='deferred').trace(...)` |
|
|
| `RenderPather.path_to(...)` | `Pather(..., render='deferred').trace_to(...)` |
|
|
| `RenderPather.mpath(...)` | `Pather(..., render='deferred').trace(...)` / `Pather(..., render='deferred').trace_to(...)` |
|
|
| `RenderPather.pathS(...)` | `Pather(..., render='deferred').jog(...)` |
|
|
| `RenderPather.pathU(...)` | `Pather(..., render='deferred').uturn(...)` |
|
|
| `RenderPather.path_into(...)` | `Pather(..., render='deferred').trace_into(...)` |
|
|
| `RenderPather.path_from(src, dst)` | `Pather(..., render='deferred').at(src).trace_into(dst)` |
|
|
|
|
There are also new convenience wrappers:
|
|
|
|
- `straight(...)` for `trace_to(..., ccw=None, ...)`
|
|
- `ccw(...)` for `trace_to(..., ccw=True, ...)`
|
|
- `cw(...)` for `trace_to(..., ccw=False, ...)`
|
|
- `jog(...)` for S-bends
|
|
- `uturn(...)` for U-bends
|
|
|
|
Important: `Pather.path()` is no longer the routing API. It now forwards to
|
|
`Pattern.path()` and creates a geometric `Path` element. Any old routing code
|
|
that still calls `pather.path(...)` must be renamed.
|
|
|
|
### Common rewrites
|
|
|
|
```python
|
|
# old
|
|
pather.path('VCC', False, 6_000)
|
|
pather.path_to('VCC', None, x=0)
|
|
pather.mpath(['GND', 'VCC'], True, xmax=-10_000, spacing=5_000)
|
|
pather.pathS('VCC', offset=-2_000, length=8_000)
|
|
pather.pathU('VCC', offset=4_000, length=5_000)
|
|
pather.path_into('src', 'dst')
|
|
pather.path_from('src', 'dst')
|
|
|
|
# new
|
|
pather.cw('VCC', 6_000)
|
|
pather.straight('VCC', x=0)
|
|
pather.ccw(['GND', 'VCC'], xmax=-10_000, spacing=5_000)
|
|
pather.jog('VCC', offset=-2_000, length=8_000)
|
|
pather.uturn('VCC', offset=4_000, length=5_000)
|
|
pather.trace_into('src', 'dst')
|
|
pather.at('src').trace_into('dst')
|
|
```
|
|
|
|
If you prefer the more explicit spelling, `trace(...)` and `trace_to(...)`
|
|
remain the underlying primitives:
|
|
|
|
```python
|
|
pather.trace('VCC', False, 6_000)
|
|
pather.trace_to('VCC', None, x=0)
|
|
```
|
|
|
|
## `PortPather` and `.at(...)`
|
|
|
|
Routing can now be written in a fluent style via `.at(...)`, which returns a
|
|
`PortPather`.
|
|
|
|
```python
|
|
(rpather.at('VCC')
|
|
.trace(False, length=6_000)
|
|
.trace_to(None, x=0)
|
|
)
|
|
```
|
|
|
|
This is additive, not required for migration. Existing code can stay with the
|
|
non-fluent `Pather` methods after renaming the verbs above.
|
|
|
|
Old `PortPather` helper names were also cleaned up:
|
|
|
|
| Old API | New API |
|
|
| --- | --- |
|
|
| `save_copy(...)` | `mark(...)` |
|
|
| `rename_to(...)` | `rename(...)` |
|
|
|
|
Example:
|
|
|
|
```python
|
|
# old
|
|
pp.save_copy('branch')
|
|
pp.rename_to('feed')
|
|
|
|
# new
|
|
pp.mark('branch')
|
|
pp.rename('feed')
|
|
```
|
|
|
|
## Imports and module layout
|
|
|
|
`Pather` now provides the remaining builder/routing surface in
|
|
`masque/builder/pather.py`. The old module files
|
|
`masque/builder/builder.py` and `masque/builder/renderpather.py` were removed.
|
|
|
|
Update imports like this:
|
|
|
|
```python
|
|
# old
|
|
from masque.builder.builder import Builder
|
|
from masque.builder.renderpather import RenderPather
|
|
|
|
# new
|
|
from masque.builder import Pather
|
|
|
|
builder = Pather(...)
|
|
deferred = Pather(..., render='deferred')
|
|
```
|
|
|
|
The new `Pather` remains importable from both `masque` and `masque.builder`.
|
|
The removed `Builder` and `RenderPather` names are no longer exported from
|
|
either location.
|
|
|
|
`Pather` now defaults to `render='auto'`, so plain construction replaces the
|
|
old `Builder` behavior. Use `Pather(..., render='deferred')` where you
|
|
previously used `RenderPather`.
|
|
|
|
## `SimpleTool` was removed and `AutoTool` registration changed
|
|
|
|
`SimpleTool` is no longer exported. `AutoTool` remains, but its old public
|
|
descriptor classes and constructor-oriented configuration were replaced by
|
|
registration methods. Use it for generated straights and S-bends and reusable
|
|
bends, U-turns, and transitions.
|
|
|
|
### Old `AutoTool`
|
|
|
|
```python
|
|
from masque.builder import AutoTool
|
|
|
|
tool = AutoTool(
|
|
straights=[
|
|
AutoTool.Straight('m1wire', make_straight, 'input', 'output'),
|
|
],
|
|
bends=[
|
|
AutoTool.Bend(lib.abstract('bend'), 'input', 'output'),
|
|
],
|
|
sbends=[],
|
|
transitions={
|
|
('m2wire', 'm1wire'): AutoTool.Transition(
|
|
lib.abstract('via'), 'top', 'bottom'
|
|
),
|
|
},
|
|
default_out_ptype='m1wire',
|
|
)
|
|
```
|
|
|
|
### New `AutoTool`
|
|
|
|
```python
|
|
from masque.builder import AutoTool
|
|
|
|
tool = (
|
|
AutoTool()
|
|
.add_straight(make_straight, 'm1wire', 'input')
|
|
.add_bend(lib.abstract('bend'), 'input', 'output', clockwise=True)
|
|
.add_transition(lib.abstract('via'), 'top', 'bottom')
|
|
)
|
|
```
|
|
|
|
The key differences are:
|
|
|
|
- `SimpleTool` was removed; use `AutoTool` or implement the new `Tool`
|
|
primitive-offer interface
|
|
- `AutoTool.Straight(...)` -> `add_straight(fn, ptype, in_name)`
|
|
- `AutoTool.Bend(...)` -> `add_bend(abstract, in_name, out_name)`
|
|
- `AutoTool.SBend(...)` -> `add_sbend(fn, ptype, in_name, out_name)`
|
|
- reusable native U-turns can be registered with `add_uturn(...)`
|
|
- transitions are registered with `add_transition(abstract, external_port, internal_port)`
|
|
- transitions are bidirectional by default; pass `one_way=True` to inhibit the reverse adapter
|
|
|
|
Primitive costs are now explicit and independent of `AutoTool` registration
|
|
order. Every `add_*()` method accepts `cost=`, as do the concrete offer
|
|
factories. A numeric value scales the default geometric cost; for example,
|
|
`cost=2` makes a primitive twice as expensive. A callable receives the
|
|
canonical primitive parameter and local endpoint and returns the complete
|
|
cost:
|
|
|
|
```python
|
|
tool.add_straight(make_straight, 'm1wire', 'input', cost=1.5)
|
|
tool.add_sbend(
|
|
make_sbend,
|
|
'm1wire',
|
|
'input',
|
|
'output',
|
|
cost=lambda jog, endpoint: abs(jog) + 2 * abs(endpoint.x),
|
|
)
|
|
```
|
|
|
|
`PrimitiveOffer.priority_bias` was removed; custom offers should use `cost`
|
|
instead. Exact equal-cost candidates still use deterministic discovery order
|
|
as the final tie-break, but registration order no longer changes their
|
|
reported cost.
|
|
|
|
For two-port primitives, `AutoTool` can infer omitted port names and, for
|
|
generated straight/S-bend primitives, omitted ptype metadata by sampling an
|
|
in-domain example. Supply those values explicitly when generation requires
|
|
route-specific keyword arguments, because metadata inference does not receive
|
|
`tool_options`.
|
|
|
|
## Custom `Tool` subclasses
|
|
|
|
If you maintain your own `Tool` subclass, the interface changed:
|
|
|
|
- `primitive_offers()` is now the planning boundary
|
|
- `render()` consumes committed primitive render tokens
|
|
- `Tool.path(...)`, `traceL()`, `traceS()`, `traceU()`, `planL()`,
|
|
`planS()`, and `planU()` are no longer part of the public `Tool` API
|
|
|
|
In practice, a minimal old implementation like:
|
|
|
|
```python
|
|
class MyTool(Tool):
|
|
def path(self, ccw, length, **kwargs):
|
|
...
|
|
```
|
|
|
|
should now become:
|
|
|
|
```python
|
|
from collections.abc import Sequence
|
|
from typing import Any
|
|
|
|
from masque import Port
|
|
from masque.builder import RenderStep, StraightOffer, Tool
|
|
|
|
|
|
class MyTool(Tool):
|
|
def primitive_offers(self, kind, *, in_ptype=None, out_ptype=None, **kwargs):
|
|
if kind != 'straight':
|
|
return ()
|
|
|
|
def endpoint(length):
|
|
ptype = out_ptype or in_ptype
|
|
return Port((length, 0), rotation=3.141592653589793, ptype=ptype)
|
|
|
|
def commit(length):
|
|
return {'length': length}
|
|
|
|
return (StraightOffer(
|
|
in_ptype=in_ptype,
|
|
out_ptype=out_ptype or in_ptype,
|
|
endpoint_planner=endpoint,
|
|
commit_planner=commit,
|
|
),)
|
|
|
|
def render(self, batch: Sequence[RenderStep]):
|
|
...
|
|
```
|
|
|
|
If a tool does not provide a primitive kind, return `()` for that kind. `Pather`
|
|
will compose available primitive offers where the route family allows it.
|
|
|
|
### Primitive offers
|
|
|
|
Tools describe legal routing primitives through `Tool.primitive_offers()`.
|
|
`Pather` composes those primitive offers to implement `trace()`, `jog()`,
|
|
`uturn()`, and `trace_into()`.
|
|
|
|
For custom tools, construct the concrete offer class that matches the primitive
|
|
you are exposing:
|
|
|
|
- `StraightOffer` for non-turning length-parameterized primitives
|
|
- `BendOffer` for single-turn length-parameterized primitives
|
|
- `SOffer` for S-like jog-parameterized primitives
|
|
- `UOffer` for U-like jog-parameterized primitives
|
|
|
|
`PrimitiveOffer` is the shared base type used for generic annotations and
|
|
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. 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:
|
|
|
|
```python
|
|
from collections.abc import Sequence
|
|
from typing import Literal
|
|
|
|
from masque import Port
|
|
from masque.builder import RenderStep, StraightOffer, Tool
|
|
|
|
|
|
class MyTool(Tool):
|
|
def primitive_offers(
|
|
self,
|
|
kind: Literal['straight', 'bend', 's', 'u'],
|
|
*,
|
|
in_ptype=None,
|
|
out_ptype=None,
|
|
**kwargs,
|
|
):
|
|
if kind != 'straight':
|
|
return ()
|
|
|
|
def endpoint(length):
|
|
ptype = out_ptype or in_ptype
|
|
return Port((length, 0), rotation=3.141592653589793, ptype=ptype)
|
|
|
|
def commit(length):
|
|
return {'length': length}
|
|
|
|
return (StraightOffer(
|
|
in_ptype=in_ptype,
|
|
out_ptype=out_ptype or in_ptype,
|
|
endpoint_planner=endpoint,
|
|
commit_planner=commit,
|
|
),)
|
|
|
|
def render(self, batch: Sequence[RenderStep]):
|
|
...
|
|
```
|
|
|
|
Routing entry points now name every supported route argument explicitly.
|
|
Custom per-route planning values must be placed under `tool_options`:
|
|
|
|
```python
|
|
pather.jog('A', 4, length=10, tool_options={'process_corner': 'slow'})
|
|
```
|
|
|
|
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
|
|
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.
|
|
|
|
AutoTool does not validate generator keyword signatures during planning. A bad
|
|
keyword therefore raises when the generator runs, normally during rendering.
|
|
Generators that require route options must provide explicit port metadata at
|
|
registration; S-bend generators must also provide an explicit `endpoint`.
|
|
|
|
Primitive offers are local planning objects:
|
|
|
|
- `endpoint_at(parameter)` returns the local output `Port`
|
|
- `cost_at(parameter)` returns an additive scalar route-selection cost
|
|
- `bbox_at(parameter)` returns local primitive bounds when a footprint hook is supplied
|
|
- `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.
|
|
|
|
`Tool` subclasses must override `primitive_offers()` and return `()` themselves
|
|
for recognized unsupported kinds. There is no route-level `plan*()` fallback.
|
|
Omitted-length S/U behavior comes from direct `SOffer` and `UOffer` endpoint
|
|
domains or from composed straight/bend primitives.
|
|
|
|
Offer constructors accept split `endpoint_planner` and `commit_planner`
|
|
callbacks. Provide both callbacks or override the offer methods in a subclass;
|
|
partial callback configurations are rejected during offer construction.
|
|
|
|
When writing direct primitive offers, declare the actual endpoint ptype
|
|
produced by the offer if it can differ from the requested value; `Pather`
|
|
validates evaluated endpoints against the declared offer ptype.
|
|
|
|
Stable imports for custom tool authors live in `masque.builder`. The
|
|
`masque.builder.planner` module is an internal planner implementation; do not
|
|
import it from user code.
|
|
|
|
`trace_into()` uses the same primitive-offer route selection and now searches
|
|
bounded route topologies with up to four bend roles. This preserves the common
|
|
straight, bend, S-like, U-like, and dogleg cases while allowing routes that
|
|
need an additional bounded bend pair. Among legal bounded candidates,
|
|
`trace_into()` selects the lowest total primitive-offer cost; bend count and
|
|
step count are used only to break exact cost ties.
|
|
|
|
Explicit-length `jog()` routes may also be satisfied by composing a straight
|
|
primitive before or after an omitted-length native S primitive. `uturn()` routes
|
|
may compose a straight primitive before an omitted-length native U primitive.
|
|
These compositions are used when they are the lowest-cost legal route for the
|
|
explicit request.
|
|
|
|
`AutoTool` can attach `bbox_at()` hooks to its primitive offers by rendering the
|
|
selected primitive into a temporary pattern and measuring it. If the rendered
|
|
primitive contains reusable refs, pass the source library as `bbox_library=...`;
|
|
normal routing does not require this.
|
|
|
|
### Omitted-length routing
|
|
|
|
Single-port omitted-length calls now evaluate legal primitive routes at their
|
|
minimum legal length-like parameter, or at their intrinsic endpoint length when
|
|
the requested offset fixes the primitive geometry. Cost then selects among
|
|
those minimum-length candidates:
|
|
|
|
```python
|
|
pather.trace('A', None) # minimum straight-like route
|
|
pather.jog('A', offset=2) # minimum S-like route for that offset
|
|
pather.uturn('A', offset=4) # minimum U-like route for that offset
|
|
```
|
|
|
|
For U-turns, use explicit `length=0` to request the old zero-public-length
|
|
shape:
|
|
|
|
```python
|
|
pather.uturn('A', offset=4, length=0)
|
|
```
|
|
|
|
## Transform semantics changed
|
|
|
|
The other major user-visible change is that `mirror()` and `rotate()` are now
|
|
treated more consistently as intrinsic transforms on low-level objects.
|
|
|
|
The practical migration rule is:
|
|
|
|
- use `mirror()` / `rotate()` when you want to change the object relative to its
|
|
own origin
|
|
- use `flip_across(...)`, `rotate_around(...)`, or container-level transforms
|
|
when you want to move the object in its parent coordinate system
|
|
|
|
### Example: `Port`
|
|
|
|
Old behavior:
|
|
|
|
```python
|
|
port.mirror(0) # changed both offset and orientation
|
|
```
|
|
|
|
New behavior:
|
|
|
|
```python
|
|
port.mirror(0) # changes orientation only
|
|
port.flip_across(axis=0) # old "mirror in the parent pattern" behavior
|
|
```
|
|
|
|
### What to audit
|
|
|
|
Check code that calls:
|
|
|
|
- `Port.mirror(...)`
|
|
- `Ref.rotate(...)`
|
|
- `Ref.mirror(...)`
|
|
- `Label.rotate_around(...)` / `Label.mirror(...)`
|
|
- `Abstract.mirror_port_offsets(...)` / `Abstract.mirror_ports(...)`
|
|
|
|
If that code expected offsets or repetition grids to move automatically, it
|
|
needs updating. For whole-pattern transforms, prefer calling `Pattern.mirror()`
|
|
or `Pattern.rotate_around(...)` at the container level.
|
|
|
|
`Abstract.mirror_port_offsets()` and `Abstract.mirror_ports()` were removed.
|
|
Use `Abstract.mirror(axis)` to mirror both port locations and orientations. If
|
|
you intentionally need only one half of that operation, update the individual
|
|
ports explicitly with `Port.flip_across(...)` or `Port.mirror(...)`.
|
|
|
|
## Library hierarchy and graph behavior
|
|
|
|
`masque/library.py` was split into the `masque.library` package. Imports from
|
|
the public module remain stable:
|
|
|
|
```python
|
|
from masque import Library, LazyLibrary
|
|
# or
|
|
from masque.library import Library, LazyLibrary
|
|
```
|
|
|
|
Code importing the implementation file itself must move to the public package;
|
|
do not depend on the new internal `base`, `mapping`, or `lazy` module paths.
|
|
|
|
Hierarchy helpers now handle dangling references explicitly. The following
|
|
methods accept `dangling='error' | 'ignore' | 'include'` and default to
|
|
`'error'`:
|
|
|
|
- `child_graph()`
|
|
- `parent_graph()`
|
|
- `child_order()`
|
|
- `find_refs_local()`
|
|
- `find_refs_global()`
|
|
- `prune_empty()`
|
|
|
|
On `master`, graph construction could expose missing targets implicitly or
|
|
fail later with a `KeyError`. If dangling refs are intentional, choose the
|
|
behavior explicitly, for example:
|
|
|
|
```python
|
|
graph = library.child_graph(dangling='include')
|
|
order = library.child_order(dangling='ignore')
|
|
```
|
|
|
|
Graph cycles and invalid hierarchy states are now reported as `LibraryError`
|
|
with context. Audit code that caught `KeyError` or `graphlib.CycleError` from
|
|
these helpers.
|
|
|
|
Invalid `dangling=` strings now raise `ValueError`; they no longer fall through
|
|
to the `include` behavior. Empty lists stored in `Pattern.refs` are consistently
|
|
treated as absent references by hierarchy and geometry traversal. Code that
|
|
uses a `defaultdict` lookup such as `pattern.refs[name]` without appending a
|
|
`Ref` will therefore not create an edge or force that target to be loaded.
|
|
|
|
`Library.add()` now resolves the full name plan and remaps references before it
|
|
starts inserting cells. Name-resolution and preparation failures no longer
|
|
leave partially-added cells behind. A `rename_theirs` callback now receives an
|
|
`INameView` containing both existing names and names reserved earlier in the
|
|
same addition. Use membership, iteration, `len()`, or `get_name()`; the callback
|
|
argument is not the destination object and does not support pattern lookup or
|
|
mapping helpers such as `keys()` and `items()`. Update callback annotations from
|
|
`ILibraryView` to `INameView`. Custom `_merge()` implementations remain
|
|
responsible for their own rollback if they fail during the final commit.
|
|
|
|
Reference transforms returned by `find_refs_local()` and
|
|
`find_refs_global()` are now Nx5 arrays. The fifth column is the cumulative
|
|
scale, so rows have the form `(x, y, rotation, mirrored, scale)` rather than the
|
|
old Nx4 form.
|
|
|
|
`BuildReport` mapping fields are now defensively copied and read-only. Copy a
|
|
field to a new `dict` before adding or removing report entries.
|
|
|
|
`PortsLibraryView`, `OverlayLibrary`, and source-backed outputs returned by
|
|
`LibraryBuilder.build()` borrow their sources. They do not close source resources, and
|
|
`PortsLibraryView` is not a context manager. Keep each lazy source open and
|
|
unchanged until every borrowing view or overlay is finished, then close the
|
|
owning source explicitly. An eager `build(output='library')` result is detached
|
|
and does not need its sources afterward.
|
|
|
|
Read-only `subtree()` results are now borrowed lazy views rather than eager
|
|
`LibraryView` snapshots. Creating one no longer loads its reachable patterns,
|
|
and the view preserves source ordering, hierarchy metadata, and lazy GDS
|
|
copy-through. Mutable `Library`, `LazyLibrary`, and `OverlayLibrary` subtrees
|
|
return the same writable type as their source. Overlay subtrees retain their
|
|
source layers and lazy GDS capabilities. Mutable subtree containers are
|
|
structurally independent, but already-materialized patterns remain shared.
|
|
Keep borrowed sources open and structurally unchanged for the subtree's
|
|
lifetime.
|
|
|
|
`ILibrary` no longer inherits `collections.abc.MutableMapping`. It remains a
|
|
readable `Mapping` with explicit insert-only item assignment and deletion, but
|
|
generic mutation helpers such as `update()`, `setdefault()`, `pop()`,
|
|
`popitem()`, and `clear()` are no longer supplied. Use `add()`, `rename()`,
|
|
`delete()`, item insertion, and item deletion so library name and reference
|
|
invariants remain explicit.
|
|
|
|
Library-level `referenced_patterns()` and `dangling_refs()` now report only
|
|
named cell targets and return `set[str]`. Populated refs whose target is `None`
|
|
are ignored, matching `child_graph()` and recursive geometry behavior;
|
|
`Pattern.referenced_patterns()` continues to report `None` locally.
|
|
|
|
Port-importing views now always process detached patterns, including when the
|
|
raw source cell was already cached. Code can safely retain and compare raw and
|
|
processed views without port overrides leaking back into the raw pattern.
|
|
Once a processed cell is persistently materialized, lazy GDS writers no longer
|
|
copy the raw source structure for that cell, so later mutations to the returned
|
|
`Pattern` are serialized. Non-persistent materialization does not mark the cell
|
|
as changed.
|
|
|
|
Underscore-prefixed declarations work through the attribute authoring surface:
|
|
`builder.cells._helper = pattern` now declares `_helper`. Only the view's exact
|
|
internal `_library` attribute is reserved.
|
|
|
|
Recursive geometry operations now reject cyclic reference hierarchies with a
|
|
contextual `PatternError` instead of eventually leaking `RecursionError`. This
|
|
applies to bounds calculation, flattened layer polygon extraction, and
|
|
visualization as well as the existing flattening checks.
|
|
|
|
`LibraryBuilder.validate(names=...)` now accepts either one string or a sequence
|
|
of strings. Non-string roots raise `TypeError`, and duplicate roots are reduced
|
|
to their first occurrence. A recipe may build a different `LibraryBuilder`, but
|
|
calling `build()` or `validate()` recursively on its own active builder now
|
|
raises `BuildError` before starting another session.
|
|
|
|
`LibraryBuilder` is an authoring registry, not an `ILibraryView` or mapping.
|
|
Use membership, iteration, `keys()`, `get_name()`, assignment, and deletion to
|
|
manage declarations, then use the library returned by `build()` for reads and
|
|
hierarchy operations. Recipes that need an active library must receive the
|
|
builder-owned placeholder as a direct argument:
|
|
|
|
```python
|
|
def make_top(lib: ILibrary) -> Pattern:
|
|
return Pather(library=lib, ports='device').pattern
|
|
|
|
builder.cells.top = cell(make_top)(builder.library)
|
|
builder.cells.device = cell(factory)(hole_lib=builder.library)
|
|
```
|
|
|
|
The builder-owned `builder.library` placeholder is read-only. Only direct
|
|
positional and keyword values equal to it are substituted; placeholders nested
|
|
inside containers are not interpreted. A placeholder from another builder is
|
|
rejected when the recipe is assigned.
|
|
|
|
`IMaterializable` now identifies libraries which support explicit
|
|
`materialize()` and `materialize_many()` operations. `LibraryBuilder.add()`
|
|
borrows these marked inputs, while ordinary mappings and `ILibraryView`
|
|
instances are copied eagerly. Use `add_source()` to force borrowing of an
|
|
unmarked view. Wrapping a materializable library in `LibraryView` intentionally
|
|
erases the marker.
|
|
|
|
`IBorrowing` separately identifies composite views which retain direct source
|
|
libraries and expose them through `borrowed_sources()`. Keep those sources open
|
|
and unchanged for the lifetime of the borrowing view. Owner libraries such as
|
|
`LazyLibrary` and the lazy GDS readers are materializable but do not implement
|
|
`IBorrowing`. GDS raw-structure copy-through remains a separate,
|
|
format-specific capability.
|
|
|
|
`LibraryBuilder`, `OverlayLibrary`, and `PortsLibraryView` are new additive
|
|
library implementations. `LibraryBuilder` supports declarative `@cell` recipes
|
|
and dependency-aware builds; `OverlayLibrary` composes source libraries without
|
|
eagerly copying all patterns; `PortsLibraryView` overlays port metadata on a
|
|
read-only source.
|
|
|
|
Flattening with `flatten_ports=True` now rejects repeated refs whose target has
|
|
ports, because expanding them would create duplicate port names. Resolve the
|
|
repetition and assign unique port names before flattening, or use
|
|
`flatten_ports=False`.
|
|
|
|
## GDSII module and lazy-loading changes
|
|
|
|
`masque.file.gdsii` changed from a module into a package. The eager klamath
|
|
API remains available at the old import path, so ordinary `read`, `readfile`,
|
|
`write`, and `writefile` calls do not need to change:
|
|
|
|
```python
|
|
from masque.file import gdsii
|
|
library, info = gdsii.readfile('layout.gds')
|
|
```
|
|
|
|
The old `gdsii.load_library()` and `gdsii.load_libraryfile()` entry points were
|
|
removed. Use the source-backed lazy reader instead:
|
|
|
|
```python
|
|
# old
|
|
library, info = gdsii.load_libraryfile('layout.gds')
|
|
|
|
# new
|
|
from masque.file.gdsii import lazy
|
|
|
|
library, info = lazy.readfile('layout.gds')
|
|
try:
|
|
pattern = library['TOP']
|
|
finally:
|
|
library.close()
|
|
```
|
|
|
|
`lazy.read(stream)` and `lazy.readfile(path, use_mmap=...)` return a read-only
|
|
`GdsLibrarySource`. It owns file resources when it opens them and also supports
|
|
the context-manager protocol. The old `full_load` and `postprocess` arguments
|
|
are gone; materialize/copy the desired cells and post-process them explicitly.
|
|
|
|
An optional Arrow/native backend is available through
|
|
`masque.file.gdsii.arrow` and `masque.file.gdsii.lazy_arrow`; install the new
|
|
`arrow` extra to use it. These modules are additive and are not a transparent
|
|
replacement unless their additional dependencies and native library are
|
|
available.
|
|
|
|
## Shape construction and geometry additions
|
|
|
|
The public `raw=True` constructor shortcut was removed from `Arc`, `Circle`,
|
|
`Ellipse`, `Path`, `Polygon`, `PolyCollection`, and `Text`. Call their normal
|
|
constructors without `raw`; `_from_raw()` is an internal fast path and is not a
|
|
compatibility API.
|
|
|
|
```python
|
|
# old
|
|
polygon = Polygon(vertices, raw=True)
|
|
|
|
# new
|
|
polygon = Polygon(vertices)
|
|
```
|
|
|
|
`Arc` radii must now be strictly positive rather than merely non-negative.
|
|
`Arc.angle_ref` is additive and defaults to `Arc.AngleRef.Center`, preserving
|
|
the previous center-referenced angle interpretation.
|
|
|
|
`RectCollection` is a new shape for batches of axis-aligned rectangles and is
|
|
exported from both `masque` and `masque.shapes`. `Polygon.boolean()` and the
|
|
top-level `masque.boolean()` helper are also new; install the `boolean` extra
|
|
for their `pyclipper` dependency.
|
|
|
|
## Other user-facing changes
|
|
|
|
### File writers
|
|
|
|
SVG writing no longer polygonizes or flattens caller-owned patterns in place;
|
|
it works from detached copies. `svg.writefile(..., annotate_ports=True)` can
|
|
add port arrows. DXF writing now expands shape repetitions into individual DXF
|
|
entities, so callers no longer need to wrap repeated shapes solely for DXF
|
|
output.
|
|
|
|
### DXF environments
|
|
|
|
If you install the DXF extra, the supported `ezdxf` baseline moved from
|
|
`~=1.0.2` to `~=1.4`. Any pinned environments should be updated accordingly.
|
|
|
|
### Optional dependency names
|
|
|
|
The misspelled `manhatanize_slow` extra was corrected to
|
|
`manhattanize_slow`. A separate `manhattanize` extra now installs the
|
|
scikit-image implementation. The `arrow` and `boolean` extras are also new.
|
|
|
|
### New exports
|
|
|
|
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`, `RenderStepKind`, the concrete
|
|
primitive-offer classes, structured route error/status types,
|
|
`ToolContractCase`, and `validate_tool_contract`
|
|
|
|
## Minimal migration checklist
|
|
|
|
If your code uses the routing stack, do these first:
|
|
|
|
1. Replace `path`/`path_to`/`mpath`/`path_into` calls with
|
|
`trace`/`trace_to`/multi-port `trace`/`trace_into`.
|
|
2. Replace `SimpleTool` and old `AutoTool` descriptor construction with the
|
|
new `AutoTool.add_*()` methods.
|
|
3. Fix imports that still reference `masque.builder.builder` or
|
|
`masque.builder.renderpather`.
|
|
4. Audit any low-level `mirror()` usage, especially on `Port` and `Ref`.
|
|
5. Move lazy GDS calls from `gdsii.load_library*()` to
|
|
`masque.file.gdsii.lazy`.
|
|
6. Remove `raw=True` from public shape constructors.
|
|
|
|
If your code only uses `Pattern`, `Library`, `place()`, and `plug()` without the
|
|
routing helpers, audit transforms, dangling-reference graph calls, raw shape
|
|
construction, and any stale imports.
|