Add Mims-style graph paper background to schematics
Sage-green grid (minor 10pt, major 50pt) on warm off-white canvas, injected as nested SVG patterns behind all schematic content. Works across all three renderers (loop, connected, grid).
This commit is contained in:
parent
c120a179c8
commit
4f174e9d0f
@ -990,7 +990,7 @@ def _render_connected(parsed: ParsedNetlist, layout: ActiveLayout) -> str:
|
||||
return d.get_imagedata("svg").decode()
|
||||
|
||||
|
||||
# ── SVG Annotation ────────────────────────────────────────────
|
||||
# ── SVG Post-Processing ───────────────────────────────────────
|
||||
|
||||
# Prefixes whose values are numeric/SI-unit and make sense to edit inline
|
||||
_EDITABLE_PREFIXES = {"R", "C", "L", "V", "I", "E", "G", "F", "H", "B", "S"}
|
||||
@ -998,6 +998,94 @@ _EDITABLE_PREFIXES = {"R", "C", "L", "V", "I", "E", "G", "F", "H", "B", "S"}
|
||||
# SVG namespace
|
||||
_SVG_NS = "http://www.w3.org/2000/svg"
|
||||
|
||||
# Graph paper grid constants (fixed spacing like real engineering paper)
|
||||
_GRID_MINOR = 10 # minor grid cell size in SVG user units (pt)
|
||||
_GRID_MAJOR = 50 # major grid cell = 5× minor
|
||||
_GRID_BG = "#f8faf6" # warm off-white canvas
|
||||
_GRID_MINOR_CLR = "#d4e4d4" # light sage green
|
||||
_GRID_MAJOR_CLR = "#b0ccb0" # medium sage green
|
||||
|
||||
|
||||
def _add_graph_paper_bg(svg_str: str, pad: float = 15.0) -> str:
|
||||
"""Add Mims-style graph paper background behind schematic content.
|
||||
|
||||
Injects a subtle green grid pattern (minor 10pt, major 50pt) on a
|
||||
warm off-white canvas — evoking Forrest Mims' hand-drawn engineering
|
||||
notebook aesthetic. Adds *pad* units of margin around the existing
|
||||
viewBox so the paper extends beyond the circuit.
|
||||
"""
|
||||
ET.register_namespace("", _SVG_NS)
|
||||
try:
|
||||
root = ET.fromstring(svg_str)
|
||||
except ET.ParseError:
|
||||
logger.warning("Failed to parse SVG for graph paper background")
|
||||
return svg_str
|
||||
|
||||
# ── Determine canvas bounds ──────────────────────────────
|
||||
viewbox = root.get("viewBox")
|
||||
if viewbox:
|
||||
vb_x, vb_y, vb_w, vb_h = (float(v) for v in viewbox.split())
|
||||
else:
|
||||
vb_x, vb_y = 0.0, 0.0
|
||||
vb_w = float(re.sub(r"[a-z]+$", "", root.get("width", "600")))
|
||||
vb_h = float(re.sub(r"[a-z]+$", "", root.get("height", "400")))
|
||||
|
||||
# Expand viewBox by padding for a "drawn on paper" margin
|
||||
vb_x -= pad
|
||||
vb_y -= pad
|
||||
vb_w += 2 * pad
|
||||
vb_h += 2 * pad
|
||||
root.set("viewBox", f"{vb_x} {vb_y} {vb_w} {vb_h}")
|
||||
|
||||
# ── Build <defs> with nested grid patterns ───────────────
|
||||
ns = f"{{{_SVG_NS}}}"
|
||||
defs = ET.Element(f"{ns}defs")
|
||||
|
||||
# Minor grid: thin sage lines every 10 units
|
||||
minor_pat = ET.SubElement(defs, f"{ns}pattern", {
|
||||
"id": "sb-grid-minor",
|
||||
"width": str(_GRID_MINOR), "height": str(_GRID_MINOR),
|
||||
"patternUnits": "userSpaceOnUse",
|
||||
})
|
||||
ET.SubElement(minor_pat, f"{ns}path", {
|
||||
"d": f"M {_GRID_MINOR} 0 L 0 0 0 {_GRID_MINOR}",
|
||||
"fill": "none", "stroke": _GRID_MINOR_CLR, "stroke-width": "0.3",
|
||||
})
|
||||
|
||||
# Major grid: contains minor grid + thicker lines every 50 units
|
||||
major_pat = ET.SubElement(defs, f"{ns}pattern", {
|
||||
"id": "sb-grid-major",
|
||||
"width": str(_GRID_MAJOR), "height": str(_GRID_MAJOR),
|
||||
"patternUnits": "userSpaceOnUse",
|
||||
})
|
||||
ET.SubElement(major_pat, f"{ns}rect", {
|
||||
"width": str(_GRID_MAJOR), "height": str(_GRID_MAJOR),
|
||||
"fill": "url(#sb-grid-minor)",
|
||||
})
|
||||
ET.SubElement(major_pat, f"{ns}path", {
|
||||
"d": f"M {_GRID_MAJOR} 0 L 0 0 0 {_GRID_MAJOR}",
|
||||
"fill": "none", "stroke": _GRID_MAJOR_CLR, "stroke-width": "0.6",
|
||||
})
|
||||
|
||||
# ── Background rects (behind all schematic content) ──────
|
||||
bg_rect = ET.Element(f"{ns}rect", {
|
||||
"x": str(vb_x), "y": str(vb_y),
|
||||
"width": str(vb_w), "height": str(vb_h),
|
||||
"fill": _GRID_BG,
|
||||
})
|
||||
grid_rect = ET.Element(f"{ns}rect", {
|
||||
"x": str(vb_x), "y": str(vb_y),
|
||||
"width": str(vb_w), "height": str(vb_h),
|
||||
"fill": "url(#sb-grid-major)",
|
||||
})
|
||||
|
||||
# Insert at front: defs first, then bg, then grid overlay
|
||||
root.insert(0, grid_rect)
|
||||
root.insert(0, bg_rect)
|
||||
root.insert(0, defs)
|
||||
|
||||
return ET.tostring(root, encoding="unicode")
|
||||
|
||||
|
||||
def annotate_svg(svg_str: str, parsed: ParsedNetlist) -> str:
|
||||
"""Add data attributes to SVG text elements for interactive editing.
|
||||
@ -1124,7 +1212,8 @@ def netlist_to_svg(netlist_text: str) -> SchematicResult:
|
||||
if svg is None:
|
||||
svg = _render_grid(parsed)
|
||||
|
||||
# Post-process: annotate SVG with data attributes for interactivity
|
||||
# Post-process: graph paper background, then data attributes
|
||||
svg = _add_graph_paper_bg(svg)
|
||||
svg = annotate_svg(svg, parsed)
|
||||
component_map = build_component_map(parsed)
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user