Skip to content

Core: Patcher, Box, Patchline

py2max.core.Patcher

Bases: BoxFactoryMixin, SerializationMixin, AbstractPatcher

Core class for creating and managing Max/MSP patches.

The Patcher class provides a high-level interface for creating Max/MSP patches programmatically. It handles object positioning, connection validation, and automatic layout management.

Features
  • Automatic object positioning with multiple layout managers
  • Connection validation using Max object metadata
  • Support for all major Max object types
  • Hierarchical patch organization with subpatchers
  • Export to .maxpat file format

Parameters:

Name Type Description Default
path Optional[Union[str, Path]]

Output file path for the patch.

None
title Optional[str]

Optional title for the patch.

None
parent Optional[AbstractPatcher]

Parent patcher for hierarchical organization.

None
classnamespace Optional[str]

Namespace for object classes (e.g., 'rnbo').

None
reset_on_render bool

Whether to reset layout on render.

True
layout str

Layout manager type ('horizontal', 'vertical', 'grid', 'flow', 'matrix', 'columnar').

'horizontal'
auto_hints bool

Whether to automatically generate object hints.

False
openinpresentation int

Presentation mode setting.

0
validate_connections bool

Whether to validate patchline connections.

False
validate_attrs bool

Whether to warn (UserWarning) when an object is given a keyword that is not a known attribute for its Max class -- catches typos like inital= for initial=. Off by default.

False
flow_direction str

Direction for flow-based layouts ('horizontal', 'vertical').

'horizontal'
cluster_connected bool

Whether to cluster connected objects in grid layout.

False
num_dimensions int

Number of rows used by the matrix layout (also treated as column count when flow_direction='column').

4
dimension_spacing float

Spacing between rows/columns for matrix layout variants.

100.0
semantic_ids bool

Whether to generate semantic IDs based on object names (e.g., 'cycle_1') instead of numeric IDs (e.g., 'obj-1'). Enables more readable debugging.

False
Example

p = Patcher('my-patch.maxpat', layout='grid') osc = p.add_textbox('cycle~ 440') gain = p.add_textbox('gain~') p.add_line(osc, gain) p.save()

With semantic IDs

p = Patcher('my-patch.maxpat', semantic_ids=True) osc1 = p.add_textbox('cycle~ 440') # ID: 'cycle_1' osc2 = p.add_textbox('cycle~ 220') # ID: 'cycle_2' gain = p.add_textbox('gain~') # ID: 'gain_1'

filepath property

filepath: Union[str, Path]

Path the patcher was created with, or "" if none was set.

width property

width: float

width of patcher window.

height property

height: float

height of patcher windows.

to_dict

to_dict() -> Dict[str, Any]

Return the patcher as a .maxpat dictionary.

Renders first, so the result is the patcher as it stands rather than as it stood after whatever last happened to render it. It did not, and the failure was silent and order-dependent: to_dict() returned a patcher with no boxes until something else called render(), and the same call on the same object then started returning them. A test asserting over to_dict() examined an empty patcher and passed for the wrong reason, which is how this was found.

Rendering here also removes the asymmetry with Box.to_dict(), which has always returned a populated box with no preparation required.

to_json

to_json() -> str

cascade convert to json

save_as

save_as(path: Union[str, Path]) -> None

Save the patch to a specified file path.

Renders all objects and connections, then saves the patch as a .maxpat JSON file (or a binary .amxd) that can be opened in Max/MSP.

Parameters:

Name Type Description Default
path Union[str, Path]

File path where the patch should be saved.

required

Raises:

Type Description
PatcherIOError

If file cannot be written or path is invalid.

save

save() -> None

Save the patch to the default file path.

Uses the path specified during Patcher creation. If no path was specified, this method will do nothing. Pending associated comments are flushed during render (called by save_as), so every serialization path emits them.

add_box

add_box(
    box: "Box",
    comment: Optional[str] = None,
    comment_pos: Optional[str] = None,
) -> "Box"

registers the box and adds it to the patcher

add_associated_comment

add_associated_comment(
    box: "Box",
    comment: str,
    comment_pos: Optional[str] = None,
) -> None

Store a comment association to be processed later during layout optimization or save.

This defers the actual comment positioning until after layout optimization, ensuring comments stay properly positioned relative to their associated boxes.

add_patchline_by_index

add_patchline_by_index(
    src_id: str,
    dst_id: str,
    dst_inlet: int = 0,
    src_outlet: int = 0,
) -> "Patchline"

Patchline creation between two objects using stored indexes

add_patchline

add_patchline(
    src_id: str,
    src_outlet: int,
    dst_id: str,
    dst_inlet: int,
) -> "Patchline"

Primary patchline creation method with validation and logging.

Parameters:

Name Type Description Default
src_id str

Source object ID.

required
src_outlet int

Source outlet index.

required
dst_id str

Destination object ID.

required
dst_inlet int

Destination inlet index.

required

Returns:

Type Description
'Patchline'

Created Patchline object.

Raises:

Type Description
InvalidConnectionError

If connection validation fails.

add_line

add_line(
    src_obj: "Box",
    dst_obj: "Box",
    inlet: int = 0,
    outlet: int = 0,
) -> "Patchline"

Create a connection between two objects.

Connects an outlet of the source object to an inlet of the destination object. Validates the connection if validation is enabled.

Parameters:

Name Type Description Default
src_obj 'Box'

Source object to connect from.

required
dst_obj 'Box'

Destination object to connect to.

required
inlet int

Destination inlet index (default: 0).

0
outlet int

Source outlet index (default: 0).

0

Returns:

Type Description
'Patchline'

The created Patchline object.

Raises:

Type Description
InvalidConnectionError

If connection validation fails.

Example

osc = p.add_textbox('cycle~ 440') gain = p.add_textbox('gain~') p.add_line(osc, gain) # Connect outlet 0 to inlet 0

add_textbox

add_textbox(
    text: str,
    maxclass: Optional[str] = None,
    numinlets: Optional[int] = None,
    numoutlets: Optional[int] = None,
    outlettype: Optional[List[str]] = None,
    patching_rect: Optional[Rect] = None,
    id: Optional[str] = None,
    comment: Optional[str] = None,
    comment_pos: Optional[str] = None,
    **kwds: "Unpack[TextboxProps]",
) -> "Box"

Add a text-based Max object to the patch.

Creates a Max object from a text specification (e.g., 'cycle~ 440'). Automatically looks up default attributes and applies appropriate maxclass based on the object type.

Parameters:

Name Type Description Default
text str

Max object specification (e.g., 'cycle~ 440', 'gain~').

required
maxclass Optional[str]

Override the automatically determined maxclass.

None
numinlets Optional[int]

Number of input connections.

None
numoutlets Optional[int]

Number of output connections.

None
outlettype Optional[List[str]]

Types of outputs (e.g., ['signal', 'int']).

None
patching_rect Optional[Rect]

Position and size rectangle.

None
id Optional[str]

Unique identifier for the object.

None
comment Optional[str]

Optional comment text.

None
comment_pos Optional[str]

Comment position ('above', 'below', etc.).

None
**kwds 'Unpack[TextboxProps]'

Additional Max object properties.

{}

Returns:

Type Description
'Box'

The created Box object.

Example

osc = p.add_textbox('cycle~ 440') gain = p.add_textbox('gain~') metro = p.add_textbox('metro 500')

add

add(value: Any, *args: Any, **kwds: Any) -> 'Box'

generic adder: value can be a number or a list or text for an object.

add_codebox

add_codebox(
    code: str,
    patching_rect: Optional[Rect] = None,
    id: Optional[str] = None,
    comment: Optional[str] = None,
    comment_pos: Optional[str] = None,
    tilde: bool = False,
    **kwds: Any,
) -> "Box"

Add a codebox.

add_codebox_tilde

add_codebox_tilde(
    code: str,
    patching_rect: Optional[Rect] = None,
    id: Optional[str] = None,
    comment: Optional[str] = None,
    comment_pos: Optional[str] = None,
    **kwds: Any,
) -> "Box"

Add a codebox_tilde

add_gen_codebox

add_gen_codebox(
    code: str,
    patching_rect: Optional[Rect] = None,
    id: Optional[str] = None,
    numinlets: Optional[int] = None,
    numoutlets: Optional[int] = None,
    outlettype: Optional[List[str]] = None,
    comment: Optional[str] = None,
    comment_pos: Optional[str] = None,
    **kwds: Any,
) -> "Box"

Add a standalone gen.codebox~ object.

Unlike :meth:add_codebox (which emits the codebox~ object meant to live inside a gen~ or rnbo~ subpatcher), this creates the self-contained gen.codebox~ object that lives directly in a regular Max patcher -- a complete gen patch in a single box, with no subpatcher wrapper. This is the form emitted by gen transpilers.

A gen codebox's inlet/outlet counts are dynamic: they are determined by the highest inN / outN references in the code. They are derived automatically when numinlets / numoutlets are not given (each defaults to at least 1).

add_v8_bridge

add_v8_bridge(
    bundle: Optional[str] = None,
    patching_rect: Optional[Rect] = None,
    id: Optional[str] = None,
    comment: Optional[str] = None,
    comment_pos: Optional[str] = None,
    **kwds: "Unpack[TextboxProps]",
) -> "Box"

Add a [v8] box running the js2max bridge, and ship it with the patch.

js2max is the JavaScript counterpart to this package: it runs inside an open patcher and can build objects into it from a patch description, or serialize the patcher back out to a .maxpat. Adding this box is what makes a generated patch able to do either.

The runtime is written next to the patch when it is saved -- Max resolves a bare filename through the folder holding the patch, so the two sitting together need no configuration. Nothing is written for a patcher that never called this, so an ordinary save() cannot leave a stray .js file behind.

Parameters:

Name Type Description Default
bundle Optional[str]

Filename of the runtime to load. Defaults to the shipped drop-in build; pass another name only if you are installing the runtime yourself under a different name.

None
patching_rect Optional[Rect]

Position and size of the box.

None
id Optional[str]

Explicit object id.

None
comment Optional[str]

Associated comment text.

None
comment_pos Optional[str]

Where to place the associated comment.

None
**kwds 'Unpack[TextboxProps]'

Further box properties.

{}

Returns:

Type Description
'Box'

The [v8] box, so it can be wired up like any other.

Example

p = Patcher('builder.maxpat') bridge = p.add_v8_bridge() p.save() # writes builder.maxpat and js2max.v8.js beside it

add_message

add_message(
    text: Optional[str] = None,
    patching_rect: Optional[Rect] = None,
    id: Optional[str] = None,
    comment: Optional[str] = None,
    comment_pos: Optional[str] = None,
    **kwds: "Unpack[TextboxProps]",
) -> "Box"

Add a max message.

add_comment

add_comment(
    text: str,
    patching_rect: Optional[Rect] = None,
    id: Optional[str] = None,
    justify: Optional[str] = None,
    **kwds: "Unpack[TextboxProps]",
) -> "Box"

Add a basic comment object.

add_intbox

add_intbox(
    comment: Optional[str] = None,
    comment_pos: Optional[str] = None,
    patching_rect: Optional[Rect] = None,
    id: Optional[str] = None,
    **kwds: Any,
) -> "Box"

Add an int box object.

add_floatbox

add_floatbox(
    comment: Optional[str] = None,
    comment_pos: Optional[str] = None,
    patching_rect: Optional[Rect] = None,
    id: Optional[str] = None,
    **kwds: Any,
) -> "Box"

Add an float box object.

add_floatparam

add_floatparam(
    longname: str,
    initial: Optional[float] = None,
    minimum: Optional[float] = None,
    maximum: Optional[float] = None,
    shortname: Optional[str] = None,
    id: Optional[str] = None,
    rect: Optional[Rect] = None,
    hint: Optional[str] = None,
    comment: Optional[str] = None,
    comment_pos: Optional[str] = None,
    **kwds: Any,
) -> "Box"

Add a float parameter object.

add_intparam

add_intparam(
    longname: str,
    initial: Optional[int] = None,
    minimum: Optional[int] = None,
    maximum: Optional[int] = None,
    shortname: Optional[str] = None,
    id: Optional[str] = None,
    rect: Optional[Rect] = None,
    hint: Optional[str] = None,
    comment: Optional[str] = None,
    comment_pos: Optional[str] = None,
    **kwds: Any,
) -> "Box"

Add an int parameter object.

add_pattrstorage

add_pattrstorage(
    name: str = "presets", **kwds: Any
) -> "Box"

Add a pattrstorage object for saving and recalling named presets.

pattrstorage stores presets of every parameter-enabled and pattr-bound object in the patcher. Pair it with :meth:add_autopattr (or use :meth:add_preset_system) so named objects are exposed automatically.

Parameters:

Name Type Description Default
name str

the storage name (the pattrstorage argument).

'presets'
**kwds Any

extra attributes forwarded to the box.

{}

add_autopattr

add_autopattr(**kwds: Any) -> 'Box'

Add an autopattr object exposing named objects to the pattr system.

Any object with a scripting name (varname) is bound automatically, so it participates in pattrstorage presets without a per-object pattr.

add_preset_system

add_preset_system(
    name: str = "presets", **kwds: Any
) -> Tuple["Box", "Box"]

Add a standard preset system: autopattr + pattrstorage, wired.

Connects autopattr to pattrstorage so that any object with a scripting name (varname) or parameter_enable=1 participates in presets. Returns (autopattr_box, pattrstorage_box).

enable_parameter

enable_parameter(
    box: "Box",
    longname: str,
    shortname: str = "",
    ptype: int = 0,
    initial: Optional[float] = None,
) -> "Box"

Mark an existing box as a Max parameter.

Parameterized objects participate in pattrstorage presets and, in a Max for Live device, appear as automatable parameters. Use this to turn a plain UI object (toggle, dial, slider, ...) into a parameter without rebuilding it.

Parameters:

Name Type Description Default
box 'Box'

the box to parameterize.

required
longname str

parameter long name (its preset/automation name).

required
shortname str

optional short name.

''
ptype int

parameter type (0=float, 1=int, 2=enum, 3=blob).

0
initial Optional[float]

optional initial value.

None

Returns:

Type Description
'Box'

The same box, for chaining.

add_attr

add_attr(
    name: str,
    value: float,
    shortname: Optional[str] = None,
    id: Optional[str] = None,
    rect: Optional[Rect] = None,
    hint: Optional[str] = None,
    comment: Optional[str] = None,
    comment_pos: Optional[str] = None,
    autovar: bool = True,
    show_label: bool = False,
    **kwds: Any,
) -> "Box"

create a param-linke attrui entry

add_subpatcher

add_subpatcher(
    text: str,
    maxclass: Optional[str] = None,
    numinlets: Optional[int] = None,
    numoutlets: Optional[int] = None,
    outlettype: Optional[List[str]] = None,
    patching_rect: Optional[Rect] = None,
    id: Optional[str] = None,
    patcher: Optional["Patcher"] = None,
    **kwds: Any,
) -> "Box"

Add a subpatcher object.

remove_line

remove_line(line: 'Patchline') -> None

Remove a specific patchline connection from the patcher.

disconnect

disconnect(
    src: "Union[Box, str]",
    dst: "Union[Box, str]",
    outlet: int = 0,
    inlet: int = 0,
) -> int

Remove connection(s) between src and dst.

Removes every patchline from src[outlet] to dst[inlet] and returns how many were removed (0 if none matched). src/dst may be Box objects or their ids.

Example

p.add_line(osc, gain) p.disconnect(osc, gain) 1

remove_box

remove_box(box: 'Union[Box, str]') -> Optional['Box']

Remove a box and every patchline connected to it.

Accepts a Box or its id. Drops all incident patchlines, the box's entry in the object map / node index, and any pending associated comment for it. Returns the removed Box, or None if it was not present.

Example

osc = p.add_textbox('cycle~ 440') p.remove_box(osc)

replace

replace(
    old: "Union[Box, str]",
    new: "Union[Box, str]",
    **kwds: Any,
) -> "Box"

Replace a box in place, preserving its position and connections.

old (a Box or its id) is swapped for new -- either the text for a replacement object (created with its Max-class defaults) or an already-built Box. The replacement takes old's slot and window position, and every patchline incident to old is rewired to it, keeping the same outlet/inlet indices and connection order. Any pending associated comment for old transfers to the replacement. Returns the new Box.

Ports are preserved by index: if the replacement has fewer inlets/outlets than the original, wires to the now-missing ports are kept as-is rather than silently dropped here (Max drops them on load).

Example

osc = p.add('cycle~ 440') p.add_line(osc, gain) saw = p.replace(osc, 'saw~ 220') # saw~ inherits osc's wiring

encapsulate

encapsulate(
    boxes: "Iterable[Box]",
    text: str = "p subpatch",
    **kwds: Any,
) -> "Box"

Wrap the given boxes into a subpatcher, auto-generating inlet/outlet objects for connections that cross the selection boundary.

  • Connections wholly inside the selection move into the subpatcher.
  • Connections crossing in/out are rewired through generated inlet / outlet objects and the new subpatcher box's ports.
  • Connections wholly outside the selection are left untouched.

Inlets/outlets are de-duplicated by source port, so multiple wires from the same outlet share a single port, matching how patches are usually built by hand.

Parameters:

Name Type Description Default
boxes 'Iterable[Box]'

boxes belonging to this patcher to move into a subpatcher.

required
text str

the subpatcher box text (e.g. "p voice").

'p subpatch'
**kwds Any

forwarded to add_subpatcher (e.g. patching_rect).

{}

Returns:

Type Description
'Box'

The new subpatcher Box added to this patcher.

add_gen

add_gen(
    text: Optional[str] = None,
    tilde: bool = False,
    **kwds: Any,
) -> "Box"

Add a gen object.

add_gen_tilde

add_gen_tilde(
    text: Optional[str] = None, **kwds: Any
) -> "Box"

Add a gen~ object.

add_rnbo

add_rnbo(text: str = 'rnbo~', **kwds: Any) -> 'Box'

Add an rnbo~ object.

add_mc

add_mc(
    text: str, chans: Optional[int] = None, **kwds: Any
) -> "Box"

Add a multichannel (mc.) object.

Prefixes mc. to the object name if not already present, and appends an @chans attribute when chans is given. A single patchline between two mc. objects carries all channels.

Example

p.add_mc("cycle~ 440", chans=4) # -> "mc.cycle~ 440 @chans 4"

add_poly

add_poly(
    target: str, voices: int = 1, **kwds: Any
) -> "Box"

Add a poly~ object hosting voices instances of target.

target is the patch (or subpatcher) name loaded into each voice.

Example

p.add_poly("mysynth", 8) # -> "poly~ mysynth 8"

add_coll

add_coll(
    name: Optional[str] = None,
    dictionary: Optional[Dict[Any, Any]] = None,
    embed: int = 1,
    patching_rect: Optional[Rect] = None,
    text: Optional[str] = None,
    id: Optional[str] = None,
    comment: Optional[str] = None,
    comment_pos: Optional[str] = None,
    **kwds: Any,
) -> "Box"

Add a coll object with option to pre-populate from a py dictionary.

add_dict

add_dict(
    name: Optional[str] = None,
    dictionary: Optional[Dict[Any, Any]] = None,
    embed: int = 1,
    patching_rect: Optional[Rect] = None,
    text: Optional[str] = None,
    id: Optional[str] = None,
    comment: Optional[str] = None,
    comment_pos: Optional[str] = None,
    **kwds: Any,
) -> "Box"

Add a dict object with option to pre-populate from a py dictionary.

add_table

add_table(
    name: Optional[str] = None,
    array: Optional[List[Union[int, float]]] = None,
    embed: int = 1,
    patching_rect: Optional[Rect] = None,
    text: Optional[str] = None,
    id: Optional[str] = None,
    comment: Optional[str] = None,
    comment_pos: Optional[str] = None,
    tilde: bool = False,
    **kwds: Any,
) -> "Box"

Add a table object with option to pre-populate from a py list.

add_table_tilde

add_table_tilde(
    name: Optional[str] = None,
    array: Optional[List[Union[int, float]]] = None,
    embed: int = 1,
    patching_rect: Optional[Rect] = None,
    text: Optional[str] = None,
    id: Optional[str] = None,
    comment: Optional[str] = None,
    comment_pos: Optional[str] = None,
    **kwds: Any,
) -> "Box"

Add a table~ object with option to pre-populate from a py list.

add_itable

add_itable(
    name: Optional[str] = None,
    array: Optional[List[Union[int, float]]] = None,
    patching_rect: Optional[Rect] = None,
    text: Optional[str] = None,
    id: Optional[str] = None,
    comment: Optional[str] = None,
    comment_pos: Optional[str] = None,
    **kwds: Any,
) -> "Box"

Add a itable object with option to pre-populate from a py list.

add_umenu

add_umenu(
    prefix: Optional[str] = None,
    autopopulate: int = 1,
    items: Optional[List[str]] = None,
    patching_rect: Optional[Rect] = None,
    depth: Optional[int] = None,
    id: Optional[str] = None,
    comment: Optional[str] = None,
    comment_pos: Optional[str] = None,
    **kwds: Any,
) -> "Box"

Add a umenu object with option to pre-populate items from a py list.

add_bpatcher

add_bpatcher(
    name: str,
    numinlets: int = 1,
    numoutlets: int = 1,
    outlettype: Optional[List[str]] = None,
    bgmode: int = 0,
    border: int = 0,
    clickthrough: int = 0,
    enablehscroll: int = 0,
    enablevscroll: int = 0,
    lockeddragscroll: int = 0,
    offset: Optional[List[float]] = None,
    viewvisibility: int = 1,
    patching_rect: Optional[Rect] = None,
    id: Optional[str] = None,
    comment: Optional[str] = None,
    comment_pos: Optional[str] = None,
    **kwds: Any,
) -> "Box"

Add a bpatcher object -- name or patch of bpatcher .maxpat is required.

add_beap

add_beap(name: str, **kwds: Any) -> 'Box'

Add a beap bpatcher object.

find_by_id

find_by_id(box_id: str) -> Optional[Box]

Find a box by its ID.

Parameters:

Name Type Description Default
box_id str

The ID of the box to find.

required

Returns:

Type Description
Optional[Box]

The Box object if found, None otherwise.

Example

p = Patcher('patch.maxpat') osc = p.add_textbox('cycle~ 440') found = p.find_by_id(osc.id) assert found is osc

find_by_type

find_by_type(maxclass: str) -> List[Box]

Find all boxes of a specific Max object type.

Parameters:

Name Type Description Default
maxclass str

The Max object class name (e.g., 'newobj', 'message', 'comment').

required

Returns:

Type Description
List[Box]

List of Box objects matching the type.

Example

p = Patcher('patch.maxpat') p.add_textbox('cycle~ 440') p.add_textbox('saw~ 220') p.add_message('bang') oscillators = [b for b in p.find_by_type('newobj') ... if 'cycle~' in b.text or 'saw~' in b.text] assert len(oscillators) == 2

find_by_text

find_by_text(
    pattern: str, case_sensitive: bool = False
) -> List[Box]

Find all boxes whose text matches a pattern.

Parameters:

Name Type Description Default
pattern str

The text pattern to search for (substring match).

required
case_sensitive bool

Whether the search should be case-sensitive (default: False).

False

Returns:

Type Description
List[Box]

List of Box objects whose text contains the pattern.

Example

p = Patcher('patch.maxpat') p.add_textbox('cycle~ 440') p.add_textbox('saw~ 220') p.add_textbox('gain~ 0.5') oscillators = p.find_by_text('~') assert len(oscillators) == 3 just_cycle = p.find_by_text('cycle') assert len(just_cycle) == 1

apply_theme

apply_theme(
    theme: Union[str, Dict[str, ColorLike]],
) -> Patcher

Apply a color theme to every box in this patcher (and subpatchers).

theme is a named theme ("light", "dark", "blue", "high-contrast") or a dict with "bg" / "text" / "border" color values (each a name, hex string, or float sequence). Returns self for chaining.

Example

Patcher('p.maxpat').apply_theme('dark')

from_dict classmethod

from_dict(
    patcher_dict: Dict[str, Any],
    save_to: Optional[str] = None,
) -> Patcher

create a patcher instance from a dict

from_file classmethod

from_file(
    path: Union[str, Path], save_to: Optional[str] = None
) -> Patcher

create a patcher instance from a .maxpat or .amxd file

find

find(text: str) -> Optional[Box]

Find box object by maxclass or text pattern.

Recursively searches through all objects in the patch (including subpatchers) to find one matching the specified maxclass or text prefix.

Parameters:

Name Type Description Default
text str

The maxclass name or text pattern to search for.

required

Returns:

Type Description
Optional[Box]

The first matching Box object, or None if not found.

find_box

find_box(text: str) -> Optional[Box]

Find a box in this patcher (non-recursive) by maxclass or text prefix.

returns box if found else None

find_box_with_index

find_box_with_index(text: str) -> Optional[Tuple[int, Box]]

Find a box and its index by maxclass or text prefix (non-recursive).

returns (index, box) if found

render

render(reset: bool = False) -> None

cascade convert py2max objects to dicts.

Idempotent: rendering twice produces the same patcher, not two copies of it. That matters because to_dict() renders on its own behalf, so a save_as() renders once for its own log line and again underneath -- and because a caller has no way to know whether a render already happened.

self.boxes used to be appended to while self.lines was rebuilt, so a second render duplicated every box and no line. Nothing in the repository passed reset_on_render=False, which is the only way to reach that path, so the asymmetry had never bitten -- but it made rendering order-dependent in exactly the way to_dict() was.

enable_presentation

enable_presentation(
    devicewidth: Optional[int] = None,
) -> Patcher

Configure this patcher to open as a Max for Live device.

Sets openinpresentation=1 so Ableton Live renders the device strip instead of the patcher view, and optionally sets devicewidth. Ableton's device strip height is fixed at ~170 px; only width is author-controlled.

enforce_integer_coords

enforce_integer_coords() -> int

Round all rect coordinates in this patcher tree to integers.

Ableton renders fractional device-strip coordinates blurry on non-retina displays. Returns the number of rects that were non-integer and got rounded. Recurses into subpatchers.

to_svg

to_svg(
    output_path: Union[str, Path],
    show_ports: bool = True,
    title: Optional[str] = None,
) -> None

Export this patcher to SVG format.

Parameters:

Name Type Description Default
output_path Union[str, Path]

Output file path for the SVG.

required
show_ports bool

Whether to show inlet/outlet ports on boxes.

True
title Optional[str]

Optional title to display at top of SVG.

None
Example

p = Patcher('my-patch.maxpat') osc = p.add_textbox('cycle~ 440') dac = p.add_textbox('ezdac~') p.add_line(osc, dac) p.to_svg('/tmp/my-patch.svg')

to_svg_string

to_svg_string(
    show_ports: bool = True, title: Optional[str] = None
) -> str

Export this patcher to SVG format as a string.

Parameters:

Name Type Description Default
show_ports bool

Whether to show inlet/outlet ports on boxes.

True
title Optional[str]

Optional title to display at top of SVG.

None

Returns:

Type Description
str

SVG content as a string.

Example

p = Patcher('my-patch.maxpat') osc = p.add_textbox('cycle~ 440') svg_content = p.to_svg_string()

get_id

get_id(object_name: Optional[str] = None) -> str

Generate object ID, optionally semantic based on object name.

Parameters:

Name Type Description Default
object_name Optional[str]

Optional Max object name (e.g., 'cycle~', 'gain~'). Used to generate semantic IDs like 'cycle_1' when semantic_ids mode is enabled.

None

Returns:

Type Description
str

Object ID string (e.g., 'obj-5' or 'cycle_1').

set_layout_mgr

set_layout_mgr(name: str) -> layout_module.LayoutManager

takes a name and returns an instance of a layout manager

get_pos

get_pos(maxclass: Optional[str] = None) -> Rect

get box rect (position) via maxclass or layout_manager

optimize_layout

optimize_layout() -> None

Arrange the whole patch based on the active layout manager.

Calls the layout manager to lay out every object, then repositions any associated comments based on the new box positions. The effect depends on the layout manager:

  • FlowLayoutManager: Arranges objects by signal flow topology
  • GridLayoutManager: Clusters connected objects together
  • Other managers: May have limited or no effect

This is a batch, whole-patch operation intended to be called once after all objects and connections have been added. Interactive, per-edit relayout is out of scope for py2max (see docs/auto-layout.md in py2max-server).

lint

lint() -> List[Finding]

Return patch-level lint findings (errors and warnings), errors first.

Checks connection validity, out-of-range ports, orphaned patchlines, duplicate IDs, overlapping objects, off-canvas objects, and unknown object classes. See :mod:py2max.lint.

py2max.core.Box

Bases: AbstractBox

Represents a Max object in a patch.

The Box class encapsulates a single Max object with its properties, position, and connections. It provides methods for introspection and help information.

Parameters:

Name Type Description Default
maxclass Optional[str]

Max object class name (e.g., 'newobj', 'flonum').

None
numinlets Optional[int]

Number of input connections.

None
numoutlets Optional[int]

Number of output connections.

None
id Optional[str]

Unique identifier for the object.

None
patching_rect Optional[Rect]

Position and size rectangle.

None
**kwds 'Unpack[BoxProps]'

Additional Max object properties.

{}

Attributes:

Name Type Description
id

Unique identifier for the object.

maxclass

Max object class name.

numinlets

Number of input connections.

numoutlets

Number of output connections.

patching_rect

Position and size as Rect.

oid property

oid: Optional[int]

Trailing numeric part of the object id, or None if it has none.

Works for numeric ids (obj-5 -> 5) and semantic ids (cycle_1 -> 1).

subpatcher property

subpatcher: Optional['Patcher']

synonym for parent patcher object

text property

text: Any

Get the text content of the box.

render

render() -> None

convert self and children to dictionary.

to_dict

to_dict() -> Dict[str, Any]

create dict from object with extra kwds included

from_dict classmethod

from_dict(obj_dict: Dict[str, Any]) -> 'Box'

create instance from dict

set_color

set_color(
    bg: Optional["ColorLike"] = None,
    text: Optional["ColorLike"] = None,
    border: Optional["ColorLike"] = None,
) -> "Box"

Set this box's colors, returning self for chaining.

Each argument accepts a named color (e.g. "red"), a hex string ("#ff8800"), or an [r, g, b(, a)] float sequence (0..1). Sets the bgcolor / textcolor / bordercolor attributes respectively.

Example

p.add_textbox("toggle").set_color(bg="blue", text="white")

add_to_presentation

add_to_presentation(
    rect: Any, *, strict: bool = False
) -> "Box"

Mark this box as a presentation-mode UI element (Max for Live).

Sets presentation=1 and presentation_rect on the box. Rounds fractional coordinates to integers with a warning. Raises if the box is M4L infrastructure (live.remote~, live.map, etc.) that must stay hidden from the device strip.

Parameters:

Name Type Description Default
rect Any

[x, y, width, height] in device-strip coordinates.

required
strict bool

if True, warn when this isn't a known UI class.

False

help_text

help_text() -> str

Get formatted help documentation for this Max object.

Returns:

Type Description
str

Formatted help string with object documentation from .maxref.xml files.

help

help() -> None

Print formatted help documentation for this Max object.

get_info

get_info() -> Optional[Dict[str, Any]]

Get complete object information from .maxref.xml files.

Returns:

Type Description
Optional[Dict[str, Any]]

Dictionary with complete object information or None if not found.

get_inlet_count

get_inlet_count() -> Optional[int]

Get the number of inlets for this object from maxref data.

Returns:

Type Description
Optional[int]

Number of inlets or None if unknown.

get_outlet_count

get_outlet_count() -> Optional[int]

Get the number of outlets for this object from maxref data.

Returns:

Type Description
Optional[int]

Number of outlets or None if unknown.

get_inlet_types

get_inlet_types() -> List[str]

Get the inlet types for this object from maxref data

Returns:

Type Description
List[str]

List of inlet type strings

get_outlet_types

get_outlet_types() -> List[str]

Get the outlet types for this object from maxref data

Returns:

Type Description
List[str]

List of outlet type strings

py2max.core.Patchline

Bases: AbstractPatchline

Represents a connection between two Max objects.

A Patchline connects an outlet of one object to an inlet of another, enabling signal or message flow between objects in a patch.

Parameters:

Name Type Description Default
source Optional[List[Any]]

Source connection as [object_id, outlet_index].

None
destination Optional[List[Any]]

Destination connection as [object_id, inlet_index].

None
**kwds Any

Additional patchline properties.

{}

Attributes:

Name Type Description
source

Source object ID and outlet index.

destination

Destination object ID and inlet index.

src property

src: str

first object from source list

dst property

dst: str

first object from destination list

to_tuple

to_tuple() -> Tuple[str, str, str, str, Union[str, int]]

Return a tuple describing the patchline.

to_dict

to_dict() -> Dict[str, Any]

create dict from object with extra kwds included

from_dict classmethod

from_dict(obj_dict: Dict[str, Any]) -> Patchline

convert toPatchline object from dict