8cb1f9f479
Publish SDK (PyPI) / publish (push) Has been cancelled
Publish SDK (npm) / publish (@aionui/officecli-sdk) (push) Has been cancelled
SDK smoke / smoke (windows-latest) (push) Has been cancelled
Publish SDK (npm) / publish (@officecli/officecli-sdk) (push) Has been cancelled
Publish SDK (npm) / publish (@officecli/sdk) (push) Has been cancelled
Publish SDK (npm) / publish (officecli-sdk) (push) Has been cancelled
SDK smoke / smoke (macos-latest) (push) Has been cancelled
SDK smoke / smoke (ubuntu-latest) (push) Has been cancelled
Skill parity / diff (push) Has been cancelled
225 lines
12 KiB
Python
225 lines
12 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Connectors and groups — generates shapes-connectors.pptx exercising the pptx
|
|
`connector` element (straight / elbow / curve presets, head/tail arrowheads,
|
|
lineJoin / miterLimit) and the `group` element (comma-separated shape indices).
|
|
|
|
SDK twin of shapes-connectors.sh (officecli CLI). Both produce an equivalent
|
|
shapes-connectors.pptx. This one drives the **officecli Python SDK**
|
|
(`pip install officecli-sdk`): one resident is started and every command is
|
|
shipped over the named pipe. Each item is the same
|
|
`{"command","parent","type","props"}` dict you'd put in an `officecli batch`
|
|
list.
|
|
|
|
Connectors attach to shapes via from=/to= shape *paths*, and a group is built
|
|
from a comma-separated list of shape paths — so the shapes those refer to must
|
|
be added first and their returned `@id` path captured. `add_shape` does exactly
|
|
that: it `send`s one add and parses the path out of the response envelope
|
|
("Added shape at /slide[N]/shape[@id=M]" → the last whitespace-delimited token).
|
|
|
|
Usage:
|
|
pip install officecli-sdk # plus the `officecli` binary on PATH
|
|
python3 shapes-connectors.py
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
|
|
# --- locate the SDK: prefer an installed `officecli-sdk`, else the in-repo copy
|
|
try:
|
|
import officecli # pip install officecli-sdk
|
|
except ImportError:
|
|
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
|
"..", "..", "..", "sdk", "python"))
|
|
import officecli
|
|
|
|
FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "shapes-connectors.pptx")
|
|
|
|
|
|
def _path_from_add(resp):
|
|
"""officecli prints "Added shape at /slide[N]/shape[@id=M]"; the path is the
|
|
last whitespace-delimited token of the response's data/message string."""
|
|
msg = ""
|
|
if isinstance(resp, dict):
|
|
msg = resp.get("data") or resp.get("message") or ""
|
|
else:
|
|
msg = str(resp)
|
|
return msg.split()[-1] if msg else ""
|
|
|
|
|
|
print(f"Building {FILE} ...")
|
|
|
|
with officecli.create(FILE, "--force") as doc:
|
|
|
|
def add(parent, type_, **props):
|
|
"""Send one `add` and return the parsed envelope."""
|
|
return doc.send({"command": "add", "parent": parent, "type": type_,
|
|
"props": {k: str(v) for k, v in props.items()}})
|
|
|
|
def add_shape(parent, **props):
|
|
"""Add a shape and return its captured @id path (for from=/to=/shapes=)."""
|
|
return _path_from_add(add(parent, "shape", **props))
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────
|
|
# Slide 1 — Connector geometry presets (straight / elbow / curve)
|
|
# ─────────────────────────────────────────────────────────────────────────
|
|
add("/", "slide")
|
|
add("/slide[1]", "shape", text="Connector Presets", size=28, bold="true",
|
|
x="0.5in", y="0.3in", width="12in", height="0.6in")
|
|
|
|
A1 = add_shape("/slide[1]", geometry="ellipse",
|
|
x="0.5in", y="1.5in", width="2in", height="1.2in",
|
|
fill="4472C4", color="FFFFFF", bold="true", text="A")
|
|
B1 = add_shape("/slide[1]", geometry="ellipse",
|
|
x="4.5in", y="1.5in", width="2in", height="1.2in",
|
|
fill="E63946", color="FFFFFF", bold="true", text="B")
|
|
add("/slide[1]", "connector", shape="straight", **{"from": A1}, to=B1,
|
|
color="1D3557", lineWidth="2pt", tailEnd="triangle")
|
|
|
|
add("/slide[1]", "shape", text="straight (default)", size=12,
|
|
x="0.5in", y="2.8in", width="6in", height="0.4in")
|
|
|
|
A2 = add_shape("/slide[1]", geometry="ellipse",
|
|
x="0.5in", y="3.6in", width="2in", height="1.2in",
|
|
fill="4472C4", color="FFFFFF", bold="true", text="A")
|
|
B2 = add_shape("/slide[1]", geometry="ellipse",
|
|
x="4.5in", y="5in", width="2in", height="1.2in",
|
|
fill="E63946", color="FFFFFF", bold="true", text="B")
|
|
add("/slide[1]", "connector", shape="elbow", **{"from": A2}, to=B2,
|
|
color="1D3557", lineWidth="2pt", tailEnd="triangle")
|
|
|
|
add("/slide[1]", "shape", text="elbow (bent, 90° turns)", size=12,
|
|
x="0.5in", y="6.3in", width="6in", height="0.4in")
|
|
|
|
A3 = add_shape("/slide[1]", geometry="ellipse",
|
|
x="8in", y="1.5in", width="2in", height="1.2in",
|
|
fill="4472C4", color="FFFFFF", bold="true", text="A")
|
|
B3 = add_shape("/slide[1]", geometry="ellipse",
|
|
x="11.5in", y="4.5in", width="2in", height="1.2in",
|
|
fill="E63946", color="FFFFFF", bold="true", text="B")
|
|
add("/slide[1]", "connector", shape="curve", **{"from": A3}, to=B3,
|
|
color="2A9D8F", lineWidth="3pt", tailEnd="arrow")
|
|
|
|
add("/slide[1]", "shape", text="curve (smooth Bezier)", size=12,
|
|
x="7.5in", y="6in", width="6in", height="0.4in")
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────
|
|
# Slide 2 — Mini flowchart with attached connectors
|
|
# ─────────────────────────────────────────────────────────────────────────
|
|
add("/", "slide")
|
|
add("/slide[2]", "shape", text="Flowchart with Attached Connectors",
|
|
size=28, bold="true",
|
|
x="0.5in", y="0.3in", width="12in", height="0.6in")
|
|
|
|
P1 = add_shape("/slide[2]", geometry="roundRect",
|
|
x="0.8in", y="2.5in", width="2.2in", height="1.2in",
|
|
fill="2A9D8F", color="FFFFFF", bold="true", size=16, text="Start")
|
|
P2 = add_shape("/slide[2]", geometry="diamond",
|
|
x="4.5in", y="2.3in", width="2.8in", height="1.6in",
|
|
fill="F4A261", color="000000", bold="true", size=14, text="Valid?")
|
|
P3 = add_shape("/slide[2]", geometry="roundRect",
|
|
x="9in", y="2.5in", width="2.2in", height="1.2in",
|
|
fill="E63946", color="FFFFFF", bold="true", size=16, text="End")
|
|
P4 = add_shape("/slide[2]", geometry="roundRect",
|
|
x="4.7in", y="5in", width="2.4in", height="1in",
|
|
fill="A8DADC", color="000000", bold="true", size=14, text="Retry")
|
|
|
|
# Connect Start → Valid? → End, plus the loopback Valid? → Retry → Start
|
|
add("/slide[2]", "connector", shape="straight", **{"from": P1}, to=P2,
|
|
color="1D3557", lineWidth="2pt", tailEnd="triangle")
|
|
add("/slide[2]", "connector", shape="straight", **{"from": P2}, to=P3,
|
|
color="1D3557", lineWidth="2pt", tailEnd="triangle")
|
|
add("/slide[2]", "connector", shape="elbow", **{"from": P2}, to=P4,
|
|
color="E63946", lineWidth="2pt", lineDash="dash", tailEnd="triangle")
|
|
add("/slide[2]", "connector", shape="elbow", **{"from": P4}, to=P1,
|
|
color="E63946", lineWidth="2pt", lineDash="dash", tailEnd="triangle")
|
|
|
|
# Branch labels (textbox-style; no fill, transparent outline)
|
|
add("/slide[2]", "textbox", x="7.4in", y="2.7in", width="1.3in", height="0.5in",
|
|
text="yes", size=12, bold="true", color="2A9D8F")
|
|
add("/slide[2]", "textbox", x="6in", y="4in", width="1.3in", height="0.5in",
|
|
text="no", size=12, bold="true", color="E63946")
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────
|
|
# Slide 3 — Grouping shapes
|
|
# ─────────────────────────────────────────────────────────────────────────
|
|
add("/", "slide")
|
|
add("/slide[3]", "shape", text="Grouping Shapes", size=28, bold="true",
|
|
x="0.5in", y="0.3in", width="12in", height="0.6in")
|
|
|
|
# Three logo-like shapes that we'll group together.
|
|
G1 = add_shape("/slide[3]", geometry="ellipse",
|
|
x="1.5in", y="2in", width="1.4in", height="1.4in", fill="E63946")
|
|
G2 = add_shape("/slide[3]", geometry="ellipse",
|
|
x="2.4in", y="2in", width="1.4in", height="1.4in",
|
|
fill="F4A261", opacity="0.75")
|
|
G3 = add_shape("/slide[3]", geometry="ellipse",
|
|
x="3.3in", y="2in", width="1.4in", height="1.4in",
|
|
fill="2A9D8F", opacity="0.75")
|
|
|
|
# Group them by passing the captured shape paths (comma-separated)
|
|
add("/slide[3]", "group", shapes=f"{G1},{G2},{G3}", name="Logo")
|
|
|
|
add("/slide[3]", "textbox",
|
|
text=f"Three ellipses grouped (shapes={G1},{G2},{G3}).", size=12,
|
|
x="0.5in", y="4in", width="12in", height="0.5in")
|
|
|
|
# Three independent boxes for comparison
|
|
add("/slide[3]", "shape", geometry="rect",
|
|
x="8in", y="2in", width="1.4in", height="1.4in", fill="4472C4")
|
|
add("/slide[3]", "shape", geometry="rect",
|
|
x="9.5in", y="2in", width="1.4in", height="1.4in", fill="4472C4")
|
|
add("/slide[3]", "shape", geometry="rect",
|
|
x="11in", y="2in", width="1.4in", height="1.4in", fill="4472C4")
|
|
|
|
add("/slide[3]", "textbox",
|
|
text="Three independent boxes (no group — each addressed separately).",
|
|
size=12, x="7in", y="4in", width="6in", height="0.5in")
|
|
|
|
# ─────────────────────────────────────────────────────────────────────────
|
|
# Slide 4 — headEnd / lineJoin / miterLimit on connectors
|
|
# ─────────────────────────────────────────────────────────────────────────
|
|
add("/", "slide")
|
|
add("/slide[4]", "shape",
|
|
text="headEnd / lineJoin / miterLimit on Connectors",
|
|
size=24, bold="true",
|
|
x="0.5in", y="0.3in", width="12in", height="0.6in")
|
|
|
|
# headEnd — arrowhead at the START of the connector (the 'head' = from-side)
|
|
# tailEnd — arrowhead at the END of the connector (the 'tail' = to-side)
|
|
add("/slide[4]", "textbox", text="headEnd + tailEnd arrowhead combinations:",
|
|
size=14, bold="true",
|
|
x="0.5in", y="1.1in", width="12in", height="0.4in")
|
|
|
|
y = 1.8
|
|
for hv, tv in [("triangle", "oval"), ("diamond", "arrow"), ("arrow", "arrow")]:
|
|
add("/slide[4]", "connector", shape="straight",
|
|
x="0.5in", y=f"{y}in", width="5in", height="0in",
|
|
color="1D3557", lineWidth="2pt", headEnd=hv, tailEnd=tv)
|
|
add("/slide[4]", "textbox", text=f"headEnd={hv} tailEnd={tv}", size=12,
|
|
x="5.8in", y=f"{y}in", width="6in", height="0.4in")
|
|
y += 0.8
|
|
|
|
# lineJoin — connector joins: round / bevel / miter, visible at the elbow bend.
|
|
add("/slide[4]", "textbox", text="lineJoin on elbow connectors:",
|
|
size=14, bold="true",
|
|
x="0.5in", y="4.6in", width="12in", height="0.4in")
|
|
|
|
# Three elbow connectors, one per column. The third uses the compound
|
|
# lineJoin=miter:<lim> form (limit in 1/1000ths of a percent) to also
|
|
# exercise miterLimit.
|
|
def add_elbow(x_in, color, line_join, label):
|
|
add("/slide[4]", "connector", shape="elbow",
|
|
x=f"{x_in}in", y="5.2in", width="3.4in", height="1.6in",
|
|
color=color, lineWidth="5pt", lineJoin=line_join)
|
|
add("/slide[4]", "textbox", text=label, size=12,
|
|
x=f"{x_in}in", y="7.0in", width="4in", height="0.4in")
|
|
|
|
add_elbow(0.5, "E63946", "round", "lineJoin=round")
|
|
add_elbow(4.7, "E63946", "bevel", "lineJoin=bevel")
|
|
add_elbow(8.9, "2A9D8F", "miter:800000", "lineJoin=miter:800000 (800% limit)")
|
|
|
|
doc.send({"command": "save"})
|
|
|
|
print(f"Generated: {FILE}")
|