sycan.svg_util

SVG glue used by sycan.autodraw.

This module is the only place that knows about SVG syntax: parsing res/<kind>.svg glyph files (viewBox + <circle id="port-X"> port markers) and serialising the final schematic. The autodraw layer hands a list of placed components, the routed polylines, and the loaded glyphs in, and gets a ready-to-write SVG string back.

Keeping this code in its own file means autodraw.py can stay focused on the layout algorithm.

Functions

bode_svg(omegas, mag_db, phase_deg[, title, ...])

Return an inline SVG with stacked magnitude/phase Bode panels.

emit_svg(placed, polylines, canvas_w, ...[, ...])

Serialise a routed schematic to SVG.

geometric_bbox(inner)

Compute the tight bounding box of all visible primitives plus port markers in a glyph's inner SVG content.

html_escape(s)

load_glyph(path, default_w, default_h, *[, ...])

Read a glyph SVG. Returns a dict with the keys::.

load_glyphs(res_dir, default_w, default_h)

Load every available res/<kind>.svg glyph (see load_glyph()).

parse_port_markers(inner)

Extract {port: (x, y)} from a glyph's inner SVG content.

view_glyphs([res_dir, default_w, default_h, ...])

Generate an HTML inspector for the glyphs under res_dir.

sycan.svg_util.parse_port_markers(inner)[source]

Extract {port: (x, y)} from a glyph’s inner SVG content.

Recognised forms (any tag, any glyph file):

  • <circle id="port-NAME" cx="X" cy="Y" r="0" />

  • <rect   id="port-NAME" x="X"  y="Y"  ... />

  • any element with id="port-NAME" data-x="X" data-y="Y"

Coordinates are in the glyph’s own viewBox space, with (0, 0) being the top-left of the viewBox.

Parameters:

inner (str)

Return type:

dict[str, tuple[float, float]]

sycan.svg_util.geometric_bbox(inner)[source]

Compute the tight bounding box of all visible primitives plus port markers in a glyph’s inner SVG content.

Returns (x_min, y_min, x_max, y_max) in the SVG’s own user units, or None if nothing was renderable. Port markers are included as their centre points (so wire-attachment positions always lie inside the box without inflating it by the marker radius). Curve commands and arcs are bounded by control points, which is a conservative super-set of the true extent.

Parameters:

inner (str)

Return type:

Tuple[float, float, float, float] | None

sycan.svg_util.load_glyph(path, default_w, default_h, *, snap_grid=10.0)[source]

Read a glyph SVG. Returns a dict with the keys:

{
    "viewbox":     "x y w h",         # bbox in inner-svg coords
    "svg_viewbox": "x y w h",         # original SVG viewBox attribute
    "inner":       "<svg body>",
    "bbox_w":      float,             # bbox width on the canvas
    "bbox_h":      float,             # bbox height on the canvas
    "ports":       {port: (x, y)},    # relative to the bbox origin
}

or None if the file is missing / malformed. default_w / default_h are last-resort fallbacks when neither a viewBox nor width/height attributes are present and the geometric scan finds nothing renderable.

The reported bbox starts from the tight geometric bbox of the drawing primitives + port markers — not the SVG viewBox attribute, which is frequently the editor canvas and may not match the drawing. The geometric bbox is then shifted to align with the port grid so that every port coordinate becomes a multiple of snap_grid after re-anchoring (see below).

snap_grid (default 10) is the routing-grid pitch that the autodraw layer uses. When non-zero, the bbox origin is snapped so that the topmost port (the natural “wiring terminal” for column layout) lands at a grid-aligned offset; provided the user designed every port at grid-multiple spacings from it, the rest follow. The bbox dimensions are also expanded out to grid multiples so that column edges stay on the grid.

Pass snap_grid=0 to disable snapping entirely and get the raw geometric bbox — useful for diagnosing port-placement bugs.

Parameters:
  • path (Path)

  • default_w (float)

  • default_h (float)

  • snap_grid (float)

Return type:

dict | None

sycan.svg_util.load_glyphs(res_dir, default_w, default_h)[source]

Load every available res/<kind>.svg glyph (see load_glyph()).

Returns {kind: glyph_info}. Missing kinds are absent; those components fall back to the default rect with canonical pin positions in the autodraw layer.

Parameters:
  • res_dir (str | Path | None)

  • default_w (float)

  • default_h (float)

Return type:

dict[str, dict]

sycan.svg_util.html_escape(s)[source]
Parameters:

s (str)

Return type:

str

sycan.svg_util.emit_svg(placed, polylines, canvas_w, canvas_h, rail_top_y, rail_bot_y, *, label_fs=11, port_fs=9, glyphs=None, short_port=None, solder_dots=None, back_annotation=None, top_rail=None, bot_rail=None, group_boxes=None)[source]

Serialise a routed schematic to SVG.

placed is an iterable of _Placed-like objects (anything with .cx, .cy, .pin_pos, .pin_side, and a .desc that carries label, kind, bbox_w, bbox_h, mirror, flip). polylines is the list of routed wires/rails as (net_class, [(x, y), ...]) pairs (a class starting with "rail" is drawn in the rail style).

glyphs maps a kind to the dict produced by load_glyph(); components whose kind is in glyphs render as <use> references, the rest fall back to a labelled <rect>.

short_port is an optional callable mapping a port name to its short label glyph (e.g., "drain" "D"); defaults to using the first two characters of the port name.

Parameters:
  • placed (Sequence)

  • polylines (Sequence[tuple[str, list[tuple[float, float]]]])

  • canvas_w (float)

  • canvas_h (float)

  • rail_top_y (float)

  • rail_bot_y (float)

  • label_fs (int)

  • port_fs (int)

  • glyphs (dict[str, dict] | None)

  • short_port (callable | None)

  • solder_dots (Sequence[tuple[float, float]] | None)

  • back_annotation (dict[str, Sequence[str]] | None)

  • top_rail (str | None)

  • bot_rail (str | None)

  • group_boxes (Sequence[tuple[str, float, float, float, float]] | None)

Return type:

str

sycan.svg_util.view_glyphs(res_dir=None, *, default_w=70.0, default_h=60.0, output=None, open_browser=True)[source]

Generate an HTML inspector for the glyphs under res_dir.

Each card shows the glyph rendered in a 0..bbox_w / 0..bbox_h frame, the parsed bounding box (translucent blue rectangle), and a labelled dot for every port marker that load_glyph() found. This is exactly the data that load_glyphs() returns to autodraw, so the page is the visual ground truth for what the placer is using.

Parameters:
  • res_dir (str | Path | None) – Directory of glyph SVGs. Defaults to <repo>/res/ relative to this file.

  • default_w (float) – Fallback dimensions for SVGs with neither viewBox nor explicit width/height attributes (mirrors load_glyph()).

  • default_h (float) – Fallback dimensions for SVGs with neither viewBox nor explicit width/height attributes (mirrors load_glyph()).

  • output (str | Path | None) – Path to write the HTML to. If None (default), a temp file is created.

  • open_browser (bool) – Launch the system default browser on the generated file.

Return type:

The path to the HTML file that was written.

sycan.svg_util.bode_svg(omegas, mag_db, phase_deg, title='', *, width=720, height=460, mag_range=(-80.0, 10.0))[source]

Return an inline SVG with stacked magnitude/phase Bode panels.

omegas is the angular-frequency axis (assumed log-spaced and monotonically increasing). mag_db and phase_deg must be the same length as omegas. The phase y-axis auto-ranges to the nearest 90° and a dashed -3 dB reference line is overlaid on the magnitude panel.

Parameters:
  • title (str)

  • width (int)

  • height (int)

  • mag_range (tuple[float, float])

Return type:

str