Loading files
This file is pinned to the selected release. Download the original to open it in your design tool.
357d57bfcThis file is pinned to the selected release. Download the original to open it in your design tool.
"""The op vocabulary as DATA — one source of truth for every op-shaped table.
akcli's op vocabulary is currently spelled out three times: the hand-written
tables in :mod:`akcli.ops` (required/optional/field-kind/placeholder/role/
group-coordinate), ``schemas/ops.schema.json`` (+ ``ops.capabilities.json``),
and the writer handler registry. Three spellings of one contract need three
CI drift gates and still let an op ship half-declared. This module is the
declaration those three become views of: ``tools/gen_ops_schema.py`` already
regenerates both JSON schemas from it byte-for-byte, and the follow-up wiring
batch rewrites the ``ops.py`` tables as derivations.
Deliberately inert: no imports beyond ``dataclasses``/``typing``, no I/O, no
akcli imports — anything may depend on it and it depends on nothing, so it
can never introduce an import cycle (layering rank 20).
Reading the model
-----------------
``CORE`` holds the 22 executable ops (``kind`` ``"core"``, plus the two
``"sugar"`` spellings ``place_gnd``/``place_vcc`` that share a handler with
``place_power_port``); ``MACROS`` holds the 10 compound ops expanded to core
ops before validation. Both tuples are in ``schemas/ops.schema.json`` branch
order, and each :class:`OpSpec`'s ``fields`` are in that branch's property
order — the JSON generator relies on both.
Every op additionally accepts the two UNIVERSAL fields ``group`` (functional
group tag) and ``sheet`` (multi-sheet routing tag). They are not repeated in
each spec: :data:`UNIVERSAL_FIELDS` carries them once and the generator
injects them into every branch.
``Field.template`` is the ``akcli ops template`` skeleton value; :data:`UNSET`
means "accepted but not taught by the template" (e.g. ``place_component``'s
``anchor``). ``template_omit`` marks a field that HAS a placeholder yet is
still left out of the skeleton (the anchor alternative on the single-part
macros). ``Field.coord`` classifies group-local coordinate translation:
``"x"``/``"y"`` scalar halves, ``"point"`` an ``[x, y]`` slot that shifts only
when it holds a literal point (pin/``mid()`` anchors are position-independent),
``"vertices"`` a list of those. Macro fields are never classified — macros
expand before groups resolve.
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Final
class _Unset:
"""Type of :data:`UNSET` (a distinct 'no template' marker, unlike None)."""
__slots__ = ()
def __repr__(self) -> str:
return "UNSET"
UNSET: Final[_Unset] = _Unset()
@dataclass(frozen=True)
class Constraint:
"""A cross-field rule no single field declaration can express.
``kind``:
* ``"exactly_one"`` — exactly one of the alternatives in ``groups`` may be
present, each alternative being the tuple of fields it needs
(``(("x_mil", "y_mil"), ("anchor",))`` = absolute XOR relative
placement).
* ``"at_least_one_field"`` — at least one declared field besides ``op``
(``set_title_block``); ``groups`` is empty.
``schema_enforced`` says whether the JSON schema branch can state the rule
(``delete_object``'s uuid|match becomes ``oneOf``, ``set_title_block``'s
becomes ``minProperties: 2``). ``False`` = prose in the branch description
only, enforced by the validator or at macro expansion — the placement
XOR, which JSON Schema cannot express without exploding the branch.
"""
kind: str
groups: tuple[tuple[str, ...], ...] = ()
schema_enforced: bool = True
@dataclass(frozen=True)
class Field:
"""One op field: its type contract, template value and coordinate role."""
name: str
kind: str
"""Type vocabulary shared with ``ops._OP_FIELDS`` (``str``/``num``/
``point``/``anchor``/...); ``tools/gen_ops_schema.py`` maps each kind to
the JSON-schema fragment it promises."""
item_kind: str = ""
"""Element kind for the container kinds (``vertices``: ``add_wire``
accepts pin refs, ``add_bus`` is points-only)."""
required: bool = False
template: object = UNSET
template_omit: bool = False
coord: str | None = None
doc: str = ""
@dataclass(frozen=True)
class OpSpec:
"""One entry of the op vocabulary — core, sugar or macro."""
name: str
kind: str # "core" | "sugar" | "macro"
doc: str = ""
fields: tuple[Field, ...] = ()
constraints: tuple[Constraint, ...] = ()
handler: str = ""
"""``writers.handlers`` function registered for this op (core/sugar)."""
expander: str = ""
"""``ops._MACRO_EXPANDERS`` function for this op (macros)."""
designator_field: str = ""
"""Field naming the refdes slots whose part class the macro PICKS."""
designator_roles: tuple[str, ...] = ()
"""Part class per designator slot, positionally (BOM-corruption guard)."""
kicad: bool = True
altium: bool = False
caps_note: str | None = None
"""Capability-matrix note; ``None`` = the row carries no ``notes`` key."""
def field_names(self) -> tuple[str, ...]:
return tuple(f.name for f in self.fields)
def required_names(self) -> tuple[str, ...]:
return tuple(f.name for f in self.fields if f.required)
# Accepted on EVERY op (core, sugar and macro alike) and therefore declared
# once instead of 32 times; the schema generator injects them right after the
# `op` discriminator in each branch.
UNIVERSAL_FIELDS: Final[tuple[Field, ...]] = (
Field(
"group", "str",
doc="Optional functional-group tag; coordinates become group-local "
"(see top-level groups).",
),
Field(
"sheet", "str",
doc="Optional sheet-routing tag: apply this op to the child file the "
"envelope's 'sheets' map declares under this key (0.20 "
"multi-sheet envelope; absent = the draw target itself).",
),
)
# Enumerated value vocabularies, keyed by the ``Field.kind`` (or nested slot)
# that uses them. ORDER IS LOAD-BEARING: ``tools/gen_ops_schema.py`` renders
# each tuple straight into a JSON-schema ``enum`` array, so a reordering is a
# schema byte change; ``akcli.ops.vocab`` derives the validator's frozensets
# from the same tuples, which is why neither side can drift.
ENUMS: Final[dict[str, tuple[object, ...]]] = {
"rotation": (0, 90, 180, 270),
"mirror": ("none", "x", "y"),
"scope": ("local", "global", "hierarchical"),
"fill": ("none", "outline", "background"),
"route_style": ("auto", "hv", "vh", "z"),
"array_direction": ("right", "down", "left", "up"),
# envelope-level, not a field kind: the op-list's ``target_format``.
"target_format": ("kicad", "altium"),
# nested slots of the composite kinds
"match_kind": ("wire", "bus", "label", "global_label",
"hierarchical_label", "junction", "no_connect", "text",
"bus_entry", "rectangle", "text_box"),
"sheet_pin_type": ("input", "output", "bidirectional", "tri_state",
"passive"),
"sheet_pin_side": ("left", "right", "top", "bottom"),
}
def enum(name: str) -> list[object]:
"""One :data:`ENUMS` vocabulary as a fresh JSON-schema ``enum`` list."""
return list(ENUMS[name])
# Refdes prefixes each designator ROLE conventionally uses (IEEE 315 / KiCad
# defaults) — the lookup behind ``OpSpec.designator_roles``.
ROLE_PREFIXES: Final[dict[str, frozenset[str]]] = {
"resistor": frozenset({"R"}),
"capacitor": frozenset({"C"}),
"LED": frozenset({"D", "LED"}),
"crystal": frozenset({"X", "Y"}),
}
CORE: Final[tuple[OpSpec, ...]] = (
OpSpec(
name="place_component",
kind="core",
doc=(
"Position is exactly one of x_mil+y_mil (absolute) or anchor "
"(+offset_mil, relative to a placed pin/component)."
),
fields=(
Field(
"lib_id", "str",
required=True,
template="<Lib:Symbol>",
),
Field(
"designator", "str",
required=True,
template="<REF>",
),
Field(
"x_mil", "num",
template=0,
coord="x",
),
Field(
"y_mil", "num",
template=0,
coord="y",
),
Field(
"anchor", "anchor_ref",
),
Field(
"offset_mil", "point",
),
Field(
"rotation", "rotation",
template=0,
),
Field(
"mirror", "mirror",
template="none",
),
Field(
"unit", "posint",
template=1,
doc=(
"Unit of a multi-unit part to place (default 1); each "
"unit is its own instance sharing the designator."
),
),
Field(
"value", "str",
template="<value>",
),
Field(
"footprint", "str",
template="<Lib:Footprint>",
),
Field(
"symbol_source", "str",
template="<extra.kicad_sym>",
),
Field(
"instance_designators", "dict",
template={"<instance path>": "<REF>"},
doc=(
"Per-instance references for a sheet file instantiated "
"more than once: {\"<instance path>\": \"<REF>\"} — "
"pairwise-distinct values; 'designator' must be one of "
"them (it seeds the uuid and is the displayed "
"Reference). Required (INSTANCE_REFS_REQUIRED) when the "
"target sheet has several instance paths."
),
),
),
constraints=(
Constraint(
"exactly_one", (("x_mil", "y_mil"), ("anchor",),),
schema_enforced=False,
),
),
handler="_op_place_component",
altium=True,
caps_note="",
),
OpSpec(
name="delete_component",
kind="core",
doc="",
fields=(
Field(
"designator", "str",
required=True,
template="<REF>",
),
Field(
"cascade", "bool",
template=False,
doc=(
"Also delete wires ending on a deleted pin plus "
"labels/no_connects/junctions anchored there (cascaded "
"uuids are reported)."
),
),
),
handler="_op_delete_component",
),
OpSpec(
name="delete_object",
kind="core",
doc="",
fields=(
Field(
"uuid", "str",
template="<object-uuid>",
),
Field(
"match", "match",
doc=(
"Selector alternative to uuid; must match exactly one "
"object (0 = replay-safe note, >1 = error listing "
"candidates)."
),
),
),
constraints=(
Constraint("exactly_one", (("uuid",), ("match",),)),
),
handler="_op_delete_object",
),
OpSpec(
name="move_component",
kind="core",
doc=(
"Destination is exactly one of x_mil+y_mil (absolute) or anchor "
"(+offset_mil)."
),
fields=(
Field(
"designator", "str",
required=True,
template="<REF>",
),
Field(
"x_mil", "num",
template=0,
coord="x",
),
Field(
"y_mil", "num",
template=0,
coord="y",
),
Field(
"anchor", "anchor_ref",
),
Field(
"offset_mil", "point",
),
Field(
"unit", "posint",
template=1,
),
Field(
"carry_labels", "bool",
template=False,
),
Field(
"carry_wires", "bool",
template=False,
),
),
constraints=(
Constraint(
"exactly_one", (("x_mil", "y_mil"), ("anchor",),),
schema_enforced=False,
),
),
handler="_op_move_component",
),
OpSpec(
name="set_component_transform",
kind="core",
doc="",
fields=(
Field(
"designator", "str",
required=True,
template="<REF>",
),
Field(
"rotation", "rotation",
template=0,
),
Field(
"mirror", "mirror",
template="none",
),
),
handler="_op_set_component_transform",
altium=True,
caps_note="rotate/mirror existing component",
),
OpSpec(
name="set_component_parameters",
kind="core",
doc="",
fields=(
Field(
"designator", "str",
required=True,
template="<REF>",
),
Field(
"new_designator", "str",
template="<REF>",
doc="Rename the component: the NEW reference designator.",
),
Field(
"value", "str",
template="<value>",
),
Field(
"footprint", "str",
template="<Lib:Footprint>",
),
Field(
"parameters", "dict",
template={"<KEY>": "<VALUE>"},
),
Field(
"instance_designators", "dict",
template={"<instance path>": "<REF>"},
doc=(
"Per-instance rename for a symbol on a "
"multiply-instantiated sheet: {\"<instance path>\": "
"\"<REF>\"} (a scalar 'new_designator' on such a symbol "
"refuses with INSTANCE_REFS_REQUIRED — it would smash "
"the per-path references into one duplicate refdes)."
),
),
),
handler="_op_set_component_parameters",
altium=True,
caps_note="ref/value/footprint/params",
),
OpSpec(
name="add_wire",
kind="core",
doc="",
fields=(
Field(
"vertices", "vertices",
item_kind="endpoint",
required=True,
template=["<REF.PIN>", [0, 0]],
coord="vertices",
),
),
handler="_op_add_wire",
altium=True,
caps_note="orthogonal; may emit many segments",
),
OpSpec(
name="route_net",
kind="core",
doc=(
"Deterministic orthogonal auto-route between two endpoints: "
"coaxial -> straight wire; otherwise an L/Z whose corner "
"provably avoids every placed pin tip (a coincident corner would "
"silently merge nets)."
),
fields=(
Field(
"from", "endpoint",
required=True,
template="<REF.PIN>",
coord="point",
),
Field(
"to", "endpoint",
required=True,
template="<REF.PIN>",
coord="point",
),
Field(
"style", "route_style",
template="auto",
doc=(
"Corner style: hv = horizontal-first L, vh = "
"vertical-first L, z = 3 segments; auto (default) picks "
"hv unless its corner lands on a pin tip, then vh, then "
"z."
),
),
Field(
"label", "str",
template="<NET_NAME>",
doc=(
"Optional net name placed ONCE at the longest segment's "
"midpoint (the wire_net pattern for non-coaxial pins)."
),
),
Field(
"scope", "scope",
template="local",
),
),
handler="_op_route_net",
caps_note=(
"deterministic L/Z auto-route, corner avoids pin tips; optional "
"mid-wire label"
),
),
OpSpec(
name="add_junction",
kind="core",
doc="",
fields=(
Field(
"at", "point",
required=True,
template=[0, 0],
coord="point",
),
),
handler="_op_add_junction",
altium=True,
caps_note="",
),
OpSpec(
name="add_no_connect",
kind="core",
doc="",
fields=(
Field(
"pin", "endpoint",
required=True,
template="<REF.PIN>",
coord="point",
),
),
handler="_op_add_no_connect",
altium=True,
caps_note="",
),
OpSpec(
name="add_net_label",
kind="core",
doc="",
fields=(
Field(
"name", "str",
required=True,
template="<NET_NAME>",
),
Field(
"at", "anchor",
required=True,
template=[0, 0],
coord="point",
),
Field(
"scope", "scope",
template="local",
),
Field(
"rotation", "rotation",
template=0,
),
),
handler="_op_add_net_label",
altium=True,
caps_note="scope local|global|hierarchical",
),
OpSpec(
name="place_power_port",
kind="core",
doc="",
fields=(
Field(
"lib_id", "str",
required=True,
template="<Lib:Symbol>",
),
Field(
"net_name", "str",
required=True,
template="<NET_NAME>",
),
Field(
"at", "anchor",
required=True,
template=[0, 0],
coord="point",
),
Field(
"rotation", "rotation",
template=0,
),
),
handler="_op_place_power_port",
altium=True,
caps_note="",
),
OpSpec(
name="place_gnd",
kind="sugar",
doc="",
fields=(
Field(
"lib_id", "str",
template="power:GND",
),
Field(
"net_name", "str",
template="GND",
),
Field(
"at", "anchor",
required=True,
template=[0, 0],
coord="point",
),
Field(
"rotation", "rotation",
template=0,
),
),
handler="_op_place_power_port",
altium=True,
caps_note="sugar over place_power_port",
),
OpSpec(
name="place_vcc",
kind="sugar",
doc="",
fields=(
Field(
"lib_id", "str",
template="power:VCC",
),
Field(
"net_name", "str",
template="VCC",
),
Field(
"at", "anchor",
required=True,
template=[0, 0],
coord="point",
),
Field(
"rotation", "rotation",
template=0,
),
),
handler="_op_place_power_port",
altium=True,
caps_note="sugar over place_power_port",
),
OpSpec(
name="rename_net",
kind="core",
doc="",
fields=(
Field(
"old_name", "str",
required=True,
template="<OLD_NET>",
),
Field(
"new_name", "str",
required=True,
template="<NEW_NET>",
),
Field(
"scope", "scope",
template="local",
doc=(
"Restrict the rewrite to one label kind; omitted = all "
"label kinds + power-port net Values."
),
),
),
handler="_op_rename_net",
caps_note=(
"rewrites label texts + power-port net Values; 0 matches = "
"replay-safe note"
),
),
OpSpec(
name="add_bus",
kind="core",
doc="",
fields=(
Field(
"vertices", "vertices",
item_kind="point",
required=True,
template=[[0, 0], [100, 0]],
coord="vertices",
),
),
handler="_op_add_bus",
caps_note="OP_UNSUPPORTED on Altium live v1",
),
OpSpec(
name="add_bus_entry",
kind="core",
doc="",
fields=(
Field(
"at", "point",
required=True,
template=[0, 0],
coord="point",
),
Field(
"size", "point",
template=[100, 100],
),
),
handler="_op_add_bus_entry",
caps_note="OP_UNSUPPORTED on Altium live v1",
),
OpSpec(
name="add_text",
kind="core",
doc="",
fields=(
Field(
"text", "str",
required=True,
template="<free text>",
),
Field(
"at", "point",
required=True,
template=[0, 0],
coord="point",
),
Field(
"angle", "num",
template=0,
),
Field(
"key", "str",
template="<stable-key>",
doc=(
"Stable replacement handle: re-emitting the same key "
"replaces this node in place regardless of coordinates "
"or op-list position (group titles use "
"group_title:<name>)."
),
),
),
handler="_op_add_text",
altium=True,
caps_note="free angle allowed",
),
OpSpec(
name="add_rectangle",
kind="core",
doc=(
"Top-level graphic rectangle (pure annotation, "
"connectivity-neutral): module borders / group frames. Delete by "
"uuid or by kind (it has no `at` anchor, so the match `at` "
"selector never hits a rectangle)."
),
fields=(
Field(
"start", "point",
required=True,
template=[0, 0],
coord="point",
doc="One corner [x, y] mil of the box.",
),
Field(
"end", "point",
required=True,
template=[1000, 800],
coord="point",
doc="The opposite corner [x, y] mil.",
),
Field(
"stroke_width_mil", "num",
template=0,
doc="Border line width in mils (0 = KiCad default).",
),
Field(
"fill", "fill",
template="none",
),
Field(
"key", "str",
template="<stable-key>",
doc=(
"Stable replacement handle: re-emitting the same key "
"replaces this node in place regardless of coordinates "
"or op-list position (group frames use "
"group_frame:<name>)."
),
),
),
handler="_op_add_rectangle",
caps_note="top-level graphic (group frames); connectivity-neutral",
),
OpSpec(
name="add_text_box",
kind="core",
doc=(
"Bordered multi-line note box (module notes / design rationale). "
"Pure annotation, connectivity-neutral; grammar fixture-verified "
"against a real KiCad."
),
fields=(
Field(
"text", "str",
required=True,
template="<free text>",
doc="Note content; \\n for line breaks.",
),
Field(
"at", "point",
required=True,
template=[0, 0],
coord="point",
doc="TOP-LEFT corner [x, y] mil of the box.",
),
Field(
"size", "point",
required=True,
template=[1000, 600],
doc="[width, height] mil.",
),
Field(
"angle", "num",
template=0,
),
Field(
"key", "str",
template="<stable-key>",
doc="Stable replacement handle (see add_rectangle.key).",
),
),
handler="_op_add_text_box",
caps_note="bordered multi-line note box; connectivity-neutral",
),
OpSpec(
name="add_sheet",
kind="core",
doc=(
"Emit a hierarchical (sheet ...) node with Sheetname/Sheetfile, "
"sheet pins at computed edge coordinates and the (instances) "
"block; the child file must already exist unless create is true, "
"in which case the transaction writes a blank one."
),
fields=(
Field(
"name", "str",
required=True,
template="<sheet-name>",
doc="Sheetname property value.",
),
Field(
"file", "str",
required=True,
template="<child.kicad_sch>",
doc=(
"Sheetfile property value; the referenced child "
".kicad_sch must already exist unless create is true."
),
),
Field(
"at", "point",
required=True,
template=[0, 0],
coord="point",
doc="Top-left corner [x, y] mil of the sheet box.",
),
Field(
"size", "point",
required=True,
template=[1000, 800],
doc="[width, height] mil of the sheet box.",
),
Field(
"pins", "sheetpins",
template=[
{
"name": "<pin>",
"type": "input",
"side": "left",
"offset_mil": 0,
},
],
doc=(
"Sheet pins on the box edges; each pin's edge coordinate "
"is at + offset_mil along its side, wire endpoints "
"attach to that coordinate."
),
),
Field(
"create", "bool",
template=False,
doc=(
"Create the child .kicad_sch as a blank sheet when it "
"does not exist yet, inside the same transaction; an "
"existing child is never touched. Default false keeps "
"the SHEET_FILE_MISSING refusal."
),
),
),
handler="_op_add_sheet",
caps_note=(
"hierarchical (sheet ...) node + sheet pins; child file authored "
"separately (or created in-transaction with create: true). "
"OP_UNSUPPORTED on Altium live v1"
),
),
OpSpec(
name="set_title_block",
kind="core",
doc=(
"Set/replace title-block fields (at least one required). "
"Find-or-create keeps the node where eeschema expects it (after "
"paper, before lib_symbols); connectivity-neutral; unchanged "
"values replay as a no-op note."
),
fields=(
Field(
"title", "str",
template="<title>",
),
Field(
"date", "str",
template="<YYYY-MM-DD>",
),
Field(
"rev", "str",
template="<rev>",
),
Field(
"company", "str",
template="<company>",
),
Field(
"comment1", "str",
template="<comment>",
),
Field(
"comment2", "str",
),
Field(
"comment3", "str",
),
Field(
"comment4", "str",
),
Field(
"comment5", "str",
),
Field(
"comment6", "str",
),
Field(
"comment7", "str",
),
Field(
"comment8", "str",
),
Field(
"comment9", "str",
),
),
constraints=(
Constraint("at_least_one_field", ()),
),
handler="_op_set_title_block",
caps_note=(
"title/date/rev/company/comment1..9; >=1 field; "
"connectivity-neutral"
),
),
)
MACROS: Final[tuple[OpSpec, ...]] = (
OpSpec(
name="place_divider",
kind="macro",
doc="",
fields=(
Field(
"x_mil", "num",
required=True,
template=0,
),
Field(
"y_mil", "num",
required=True,
template=0,
),
Field(
"top_net", "str",
required=True,
template="<top_net>",
),
Field(
"mid_net", "str",
required=True,
template="<mid_net>",
),
Field(
"bottom_net", "str",
required=True,
template="<bottom_net>",
),
Field(
"designators", "strpair",
template=["R1", "R2"],
),
Field(
"values", "strpair",
template=["10k", "10k"],
),
Field(
"spacing_mil", "num",
template=400,
),
Field(
"lib_id", "str",
template="Device:R",
),
),
expander="_expand_divider",
designator_field="designators",
designator_roles=("resistor", "resistor"),
),
OpSpec(
name="place_decoupling",
kind="macro",
doc=(
"Macro: one bypass cap + rail/gnd labels on its pins. Position "
"is exactly one of x_mil+y_mil or anchor (+offset_mil) — e.g. "
"anchor 'U1.VCC' drops the cap next to the pin it decouples."
),
fields=(
Field(
"x_mil", "num",
template=0,
),
Field(
"y_mil", "num",
template=0,
),
Field(
"anchor", "anchor_ref",
template="<REF.PIN>",
template_omit=True,
),
Field(
"offset_mil", "point",
template=[0, 0],
template_omit=True,
),
Field(
"power_net", "str",
required=True,
template="<power_net>",
),
Field(
"designator", "str",
template="C1",
),
Field(
"value", "str",
template="100n",
),
Field(
"gnd_net", "str",
template="GND",
),
Field(
"lib_id", "str",
template="Device:C",
),
),
constraints=(
Constraint(
"exactly_one", (("x_mil", "y_mil"), ("anchor",),),
schema_enforced=False,
),
),
expander="_expand_decoupling",
designator_field="designator",
designator_roles=("capacitor",),
),
OpSpec(
name="place_pullup",
kind="macro",
doc=(
"Macro: one pull-up/-down resistor + labels. Position is exactly "
"one of x_mil+y_mil or anchor (+offset_mil)."
),
fields=(
Field(
"x_mil", "num",
template=0,
),
Field(
"y_mil", "num",
template=0,
),
Field(
"anchor", "anchor_ref",
template="<REF.PIN>",
template_omit=True,
),
Field(
"offset_mil", "point",
template=[0, 0],
template_omit=True,
),
Field(
"net", "str",
required=True,
template="<NET_NAME>",
),
Field(
"rail_net", "str",
required=True,
template="<rail_net>",
),
Field(
"designator", "str",
template="R1",
),
Field(
"value", "str",
template="10k",
),
Field(
"lib_id", "str",
template="Device:R",
),
),
constraints=(
Constraint(
"exactly_one", (("x_mil", "y_mil"), ("anchor",),),
schema_enforced=False,
),
),
expander="_expand_pullup",
designator_field="designator",
designator_roles=("resistor",),
),
OpSpec(
name="place_led_indicator",
kind="macro",
doc="",
fields=(
Field(
"x_mil", "num",
required=True,
template=0,
),
Field(
"y_mil", "num",
required=True,
template=0,
),
Field(
"net", "str",
required=True,
template="<NET_NAME>",
),
Field(
"designators", "strpair",
template=["R1", "D1"],
),
Field(
"r_value", "str",
template="330",
),
Field(
"gnd_net", "str",
template="GND",
),
Field(
"mid_net", "str",
template="",
),
Field(
"spacing_mil", "num",
template=400,
),
),
expander="_expand_led_indicator",
designator_field="designators",
designator_roles=("resistor", "LED"),
),
OpSpec(
name="place_rc_filter",
kind="macro",
doc="",
fields=(
Field(
"x_mil", "num",
required=True,
template=0,
),
Field(
"y_mil", "num",
required=True,
template=0,
),
Field(
"in_net", "str",
required=True,
template="<in_net>",
),
Field(
"out_net", "str",
required=True,
template="<out_net>",
),
Field(
"designators", "strpair",
template=["R1", "C1"],
),
Field(
"r_value", "str",
template="1k",
),
Field(
"c_value", "str",
template="100n",
),
Field(
"gnd_net", "str",
template="GND",
),
Field(
"spacing_mil", "num",
template=400,
),
),
expander="_expand_rc_filter",
designator_field="designators",
designator_roles=("resistor", "capacitor"),
),
OpSpec(
name="place_crystal",
kind="macro",
doc="",
fields=(
Field(
"x_mil", "num",
required=True,
template=0,
),
Field(
"y_mil", "num",
required=True,
template=0,
),
Field(
"in_net", "str",
required=True,
template="<in_net>",
),
Field(
"out_net", "str",
required=True,
template="<out_net>",
),
Field(
"designators", "strtriple",
template=["Y1", "C1", "C2"],
),
Field(
"value", "str",
template="",
),
Field(
"load_c", "str",
template="22p",
),
Field(
"gnd_net", "str",
template="GND",
),
Field(
"spacing_mil", "num",
template=400,
),
),
expander="_expand_crystal",
designator_field="designators",
designator_roles=("crystal", "capacitor", "capacitor"),
),
OpSpec(
name="place_array",
kind="macro",
doc=(
"Macro: N identical parts in a row/column at a fixed pitch, "
"named <prefix><start_index>.. — placement sugar; labels/wiring "
"stay explicit."
),
fields=(
Field(
"lib_id", "str",
required=True,
template="<Lib:Symbol>",
),
Field(
"designator_prefix", "str",
required=True,
template="R",
),
Field(
"count", "array_count",
required=True,
template=4,
),
Field(
"x_mil", "num",
required=True,
template=0,
),
Field(
"y_mil", "num",
required=True,
template=0,
),
Field(
"start_index", "nonneg_int",
template=1,
),
Field(
"pitch_mil", "num",
template=400,
),
Field(
"direction", "array_direction",
template="right",
),
Field(
"value", "str",
template="<value>",
),
Field(
"values", "strlist",
template=["<v1>", "<v2>"],
doc=(
"Per-element values; length must equal count (checked at "
"expansion)."
),
),
Field(
"rotation", "rotation",
template=0,
),
Field(
"footprint", "str",
template="<Lib:Footprint>",
),
),
expander="_expand_array",
),
OpSpec(
name="connect_and_label",
kind="macro",
doc=(
"Macro: pin-to-pin wire + ONE net label mid-wire (the fix for "
"facing-pin label collisions)."
),
fields=(
Field(
"from", "endpoint",
required=True,
template="<REF.PIN>",
),
Field(
"to", "endpoint",
required=True,
template="<REF.PIN>",
),
Field(
"net", "str",
required=True,
template="<NET_NAME>",
),
Field(
"orientation", "rotation",
template=0,
),
Field(
"scope", "scope",
template="local",
),
),
expander="_expand_connect_and_label",
),
OpSpec(
name="place_pwr_flag",
kind="macro",
doc=(
"Macro: power:PWR_FLAG placed MID-WIRE (on-pin placement "
"overlaps symbol bodies); default rotation 90 turns the flag "
"body off the wire axis."
),
fields=(
Field(
"at", "anchor",
required=True,
template="mid(<REF.PIN>,<REF.PIN>)",
),
Field(
"rotation", "rotation",
template=90,
),
),
expander="_expand_pwr_flag",
),
OpSpec(
name="terminate_unused_unit",
kind="macro",
doc=(
"Macro: place an unused op-amp/comparator unit and terminate it "
"(gnd on in_plus, vcc on in_minus, no-connect on out)."
),
fields=(
Field(
"designator", "str",
required=True,
template="<REF>",
),
Field(
"lib_id", "str",
required=True,
template="<Lib:Symbol>",
),
Field(
"unit", "posint",
required=True,
template=2,
),
Field(
"at", "point",
required=True,
template=[0, 0],
),
Field(
"in_plus", "str",
required=True,
template="<PIN>",
),
Field(
"in_minus", "str",
required=True,
template="<PIN>",
),
Field(
"out", "str",
required=True,
template="<PIN>",
),
Field(
"vcc", "str",
template="VCC",
),
Field(
"gnd", "str",
template="GND",
),
),
expander="_expand_terminate_unused_unit",
),
)
ALL: Final[tuple[OpSpec, ...]] = CORE + MACROS
_BY_NAME: Final[dict[str, OpSpec]] = {s.name: s for s in ALL}
def core_names() -> frozenset[str]:
"""The 20 executable core op names (sugar excluded)."""
return frozenset(s.name for s in CORE if s.kind == "core")
def sugar_names() -> frozenset[str]:
"""Sugar spellings that share a writer handler with a core op."""
return frozenset(s.name for s in CORE if s.kind == "sugar")
def macro_names() -> frozenset[str]:
"""Compound ops expanded to core ops before validation."""
return frozenset(s.name for s in MACROS)
def op_names() -> frozenset[str]:
"""Everything the writer executes: core + sugar (``ops.OP_NAMES``)."""
return core_names() | sugar_names()
def by_name(name: str) -> OpSpec:
"""The spec for one op or macro; ``KeyError`` if the name is unknown."""
return _BY_NAME[name]