14 KiB
Migration Guide
This guide covers changes between the git tag release and the current tree.
At release, masque.__version__ was 3.3; the current tree reports 3.4.
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(..., auto_render=False).trace(...) |
RenderPather.path_to(...) |
Pather(..., auto_render=False).trace_to(...) |
RenderPather.mpath(...) |
Pather(..., auto_render=False).trace(...) / Pather(..., auto_render=False).trace_to(...) |
RenderPather.pathS(...) |
Pather(..., auto_render=False).jog(...) |
RenderPather.pathU(...) |
Pather(..., auto_render=False).uturn(...) |
RenderPather.path_into(...) |
Pather(..., auto_render=False).trace_into(...) |
RenderPather.path_from(src, dst) |
Pather(..., auto_render=False).at(src).trace_into(dst) |
There are also new convenience wrappers:
straight(...)fortrace_to(..., ccw=None, ...)ccw(...)fortrace_to(..., ccw=True, ...)cw(...)fortrace_to(..., ccw=False, ...)jog(...)for S-bendsuturn(...)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
# 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:
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.
(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:
# 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:
# old
from masque.builder.builder import Builder
from masque.builder.renderpather import RenderPather
# new
from masque.builder import Pather
builder = Pather(...)
deferred = Pather(..., auto_render=False)
Top-level imports from masque also continue to work.
Pather now defaults to auto_render=True, so plain construction replaces the
old Builder behavior. Use Pather(..., auto_render=False) where you
previously used RenderPather.
BasicTool was replaced
BasicTool is no longer exported. Use AutoTool for reusable straight, bend,
transition, and S-bend primitives.
Old BasicTool
from masque.builder.tools import BasicTool
tool = BasicTool(
straight=(make_straight, 'input', 'output'),
bend=(lib.abstract('bend'), 'input', 'output'),
transitions={
'm2wire': (lib.abstract('via'), 'top', 'bottom'),
},
)
New AutoTool
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:
BasicTool->AutoToolstraight=(fn, in_name, out_name)->add_straight(fn, ptype, in_name)bend=(abstract, in_name, out_name)->add_bend(abstract, in_name, out_name)- transitions are registered with
add_transition(abstract, external_port, internal_port) - transitions are bidirectional by default; pass
one_way=Trueto inhibit the reverse adapter
Custom Tool subclasses
If you maintain your own Tool subclass, the interface changed:
primitive_offers()is now the planning boundaryrender()consumes committed primitive render tokensTool.path(...),traceL(),traceS(),traceU(),planL(),planS(), andplanU()are no longer part of the publicToolAPI
In practice, a minimal old implementation like:
class MyTool(Tool):
def path(self, ccw, length, **kwargs):
...
should now become:
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], **kwargs: Any):
...
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:
StraightOfferfor non-turning length-parameterized primitivesBendOfferfor single-turn length-parameterized primitivesSOfferfor S-like jog-parameterized primitivesUOfferfor 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.
Minimal straight-only example:
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], **kwargs):
...
Primitive offers are local planning objects:
endpoint_at(parameter)returns the local outputPortcost_at(parameter)returns an additive scalar route-selection costbbox_at(parameter)returns local primitive bounds when a footprint hook is suppliedparameterized_bboxmay carry opaque future-router footprint metadatacommit(parameter)returns opaque render data consumed later byrender()(min, max)parameter domains are half-open;(value, value)is a fixed singleton- selected parameter values must be finite; domains may use infinite open bounds but not
NaN Noneand"unk"ptypes are wildcards; concrete ptype mismatches reject an offer
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:
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:
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:
port.mirror(0) # changed both offset and orientation
New behavior:
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(...)
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.
Other user-facing changes
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.
New exports
These are additive, but available now from masque and masque.builder:
PortPatherAutoToolboolean
Minimal migration checklist
If your code uses the routing stack, do these first:
- Replace
path/path_to/mpath/path_intocalls withtrace/trace_to/multi-porttrace/trace_into. - Replace
BasicToolwithAutoTool. - Fix imports that still reference
masque.builder.builderormasque.builder.renderpather. - Audit any low-level
mirror()usage, especially onPortandRef.
If your code only uses Pattern, Library, place(), and plug() without the
routing helpers, you may not need any changes beyond the transform audit and any
stale imports.