From 3df394f0817553ad4bb3ad46dc743e5790b5bd99 Mon Sep 17 00:00:00 2001 From: Peter Corke Date: Sun, 4 Oct 2026 09:28:07 -0400 Subject: [PATCH 1/2] docs: add pgraph logo to README --- README.md | 2 + docs/figs/pgraph_logo.png | Bin 0 -> 42491 bytes docs/figs/pgraph_logo.py | 161 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 163 insertions(+) create mode 100644 docs/figs/pgraph_logo.png create mode 100644 docs/figs/pgraph_logo.py diff --git a/README.md b/README.md index f983cbed..c676c5ee 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # PGraph: graphs for Python
+ pgraph logo +
Mathematical graphs for Python

diff --git a/docs/figs/pgraph_logo.png b/docs/figs/pgraph_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..4ffc3d852b32675d9a668f416c6a244eab46d1e2 GIT binary patch literal 42491 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 H#999V*PRH& literal 0 HcmV?d00001 diff --git a/docs/figs/pgraph_logo.py b/docs/figs/pgraph_logo.py new file mode 100644 index 00000000..781900dc --- /dev/null +++ b/docs/figs/pgraph_logo.py @@ -0,0 +1,161 @@ +""" +Generate the pgraph logo. + +A snake follows a path through a small graph, its head acting as the arrowhead +pointing at the goal vertex, beside the "pgraph" wordmark. The style matches +the snake logos used across the RVC toolboxes (RTB, MVTB, bdsim, SMTB): a +grey-to-teal body, yellow eyes and a magenta forked tongue. + +Renders ``pgraph_logo.png`` (2x scale, white background) next to this +script, using ``rsvg-convert`` (from librsvg) to rasterize the SVG. + +The wordmark is SVG text in Roboto Medium, so Roboto must be installed for +the PNG to render correctly. The PNG is the committed artifact used by the +README. + +Usage:: + + python docs/figs/pgraph_logo.py +""" + +import math +import shutil +import subprocess +from pathlib import Path + +# palette +TEAL = "#2bb39a" # snake head, end of body gradient +GREY_TAIL = "#5c5c5c" # start of body gradient +GREY_TEXT = "#4d4d4d" +GREY_EDGE = "#9a9a9a" +GREY_NODE = "#555555" +EYE = "#ffee00" +TONGUE = "#d14fc4" + +WIDTH, HEIGHT = 580, 210 +NODE_RADIUS = 11 +BODY_WIDTH = 14 +HEAD_SCALE = 1.3 + +# graph: vertex coordinates, edges, and the path the snake follows +VERTICES: dict[int, tuple[float, float]] = { + 1: (35, 165), + 2: (85, 55), + 3: (125, 150), + 4: (180, 90), + 5: (225, 175), + 6: (255, 40), +} +EDGES: list[tuple[int, int]] = [ + (1, 2), (1, 3), (2, 3), (2, 4), (3, 4), + (3, 5), (4, 5), (4, 6), (2, 6), (5, 6), +] # fmt: skip +PATH: list[int] = [1, 3, 4, 6] + + +def snake_head(x: float, y: float, angle: float, scale: float = 1.0) -> str: + """ + Snake head as an SVG group. + + :param x: x-coordinate of the neck + :param y: y-coordinate of the neck + :param angle: heading of the head, in radians + :param scale: scale factor applied to the head + :returns: SVG ```` element + + The head is defined with its neck at the origin and nose pointing along +x, + then translated and rotated into place. + """ + return f""" + + + + +""" + + +def logo_svg(font_weight: int = 500) -> str: + """ + Build the logo as an SVG document. + + :param font_weight: CSS font weight of the "pgraph" wordmark + :returns: SVG document + """ + pts = [VERTICES[v] for v in PATH] + (x0, y0), (xN, yN) = pts[0], pts[-1] + + # body gradient runs from the tail to near the neck, so the neck + # blends into the solid-colored head + gx = x0 + 0.8 * (xN - x0) + gy = y0 + 0.8 * (yN - y0) + out = [ + f'', + f'' + f'' + "", + ] + + # graph edges not on the path, drawn thin + on_path = {frozenset(e) for e in zip(PATH, PATH[1:])} + for a, b in EDGES: + if frozenset((a, b)) in on_path: + continue + (xa, ya), (xb, yb) = VERTICES[a], VERTICES[b] + out.append( + f'' + ) + + # the body stops short of the goal vertex, leaving room for head and tongue + (xa, ya), (xb, yb) = pts[-2], pts[-1] + heading = math.atan2(yb - ya, xb - xa) + tongue_len, head_len = 15, 30 + back = NODE_RADIUS + tongue_len + head_len + 2 + hx, hy = xb - back * math.cos(heading), yb - back * math.sin(heading) + + # thin tail poking out behind the start vertex + x1, y1 = pts[1] + t = math.atan2(y0 - y1, x0 - x1) + tx, ty = x0 + 22 * math.cos(t), y0 + 22 * math.sin(t) + out.append( + f'' + ) + + body = f"M {x0} {y0} " + " ".join(f"L {x} {y}" for x, y in pts[1:-1]) + body += f" L {hx:.1f} {hy:.1f}" + out.append( + f'' + ) + out.append(snake_head(hx, hy, heading, HEAD_SCALE)) + + # vertices drawn last, over the edges and the snake's body + for x, y in VERTICES.values(): + out.append( + f'' + ) + + out.append( + f'pgraph' + ) + out.append("") + return "\n".join(out) + + +def main() -> None: + if shutil.which("rsvg-convert") is None: + raise SystemExit("rsvg-convert not found (brew install librsvg)") + png = Path(__file__).parent / "pgraph_logo.png" + subprocess.run( + ["rsvg-convert", "-z", "2", "-b", "white", "-o", str(png)], + input=logo_svg().encode(), + check=True, + ) + print(f"wrote {png}") + + +if __name__ == "__main__": + main() From de5b06dac09c444211c50c74ffdc699aeee9febb Mon Sep 17 00:00:00 2001 From: Peter Corke Date: Sun, 4 Oct 2026 09:36:05 -0400 Subject: [PATCH 2/2] chore: exclude docs/figs asset scripts from Codacy analysis The logo generator shells out to rsvg-convert with fixed arguments, which Bandit flags (B404/B603/B607). It's a developer-only script, not package code, so exclude the folder rather than scattering nosec pragmas. Co-Authored-By: Claude Opus 5.5 --- .codacy.yml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .codacy.yml diff --git a/.codacy.yml b/.codacy.yml new file mode 100644 index 00000000..e6569a25 --- /dev/null +++ b/.codacy.yml @@ -0,0 +1,7 @@ +--- +exclude_paths: + # Developer scripts that regenerate documentation assets (e.g. the README + # logo), not part of the installed package. They shell out to fixed + # commands like rsvg-convert, which Bandit flags (B404/B603/B607) even + # though no untrusted input is involved. + - "docs/figs/**"