Examples¶
Open a document and inspect it¶
from vexy_lines_api import MCPClient
with MCPClient() as vl:
vl.open_document("artwork.lines")
info = vl.get_document_info()
print(f"{info.width_mm:.0f} x {info.height_mm:.0f} mm")
print(f"Resolution: {info.resolution} dpi")
print(f"Unsaved changes: {info.has_changes}")
Walk the layer tree¶
from vexy_lines_api import MCPClient
with MCPClient() as vl:
vl.open_document("artwork.lines")
root = vl.get_layer_tree()
def print_tree(node, depth=0):
indent = " " * depth
label = f"{node.type}: {node.caption}"
if node.fill_type:
label += f" [{node.fill_type}]"
if not node.visible:
label += " [hidden]"
print(f"{indent}{label} (id={node.id})")
for child in node.children:
print_tree(child, depth + 1)
print_tree(root)
Change fill colours and re-export¶
from vexy_lines_api import MCPClient
with MCPClient() as vl:
vl.open_document("artwork.lines")
root = vl.get_layer_tree()
# Find all fill nodes and set them to blue
def set_all_fills_blue(node):
if node.type == "fill":
vl.set_fill_params(node.id, color="#2563eb")
for child in node.children:
set_all_fills_blue(child)
set_all_fills_blue(root)
vl.render()
vl.export_png("blue_version.png", dpi=150)
Create a document from scratch¶
Build a complete document programmatically. No .lines template needed.
from vexy_lines_api import MCPClient
with MCPClient() as vl:
doc = vl.new_document(source_image="photo.jpg", dpi=150)
# Add a group, layer, and fill
group = vl.add_group(parent_id=doc.root_id, caption="My Group")
layer = vl.add_layer(group_id=group["id"])
fill = vl.add_fill(
layer_id=layer["id"],
fill_type="linear",
color="#333333",
params={"interval": 2.0, "angle": 45},
)
vl.render()
vl.export_svg("from_scratch.svg")
Build a multi-layer composition¶
Combine multiple fill algorithms on separate layers with different colours.
from vexy_lines_api import MCPClient
with MCPClient() as vl:
doc = vl.new_document(source_image="portrait.jpg", dpi=150)
group = vl.add_group(parent_id=doc.root_id, caption="Composition")
# Layer 1: coarse linear fill for shadows
layer1 = vl.add_layer(group_id=group["id"])
vl.add_fill(
layer_id=layer1["id"],
fill_type="linear",
color="#1a1a2e",
params={"interval": 8.0, "angle": 45, "thickness": 2.0, "contrast": 0.9},
)
# Layer 2: fine circular fill for midtones
layer2 = vl.add_layer(group_id=group["id"])
vl.add_fill(
layer_id=layer2["id"],
fill_type="circular",
color="#16213e",
params={"interval": 3.0, "thickness": 0.8, "x0": 500, "y0": 400},
)
# Layer 3: halftone overlay for highlights
layer3 = vl.add_layer(group_id=group["id"])
vl.add_fill(
layer_id=layer3["id"],
fill_type="halftone",
color="#e94560",
params={"cell_size": 12.0, "rotation": 15, "break_down": 180},
)
vl.render(timeout=60)
vl.export_svg("composition.svg")
vl.export_png("composition.png", dpi=300)
Style transfer: one style, many images¶
from pathlib import Path
from vexy_lines_api import MCPClient, extract_style, apply_style
style = extract_style("artistic.lines")
photos = sorted(Path("./photos").glob("*.jpg"))
output = Path("./output")
output.mkdir(exist_ok=True)
with MCPClient() as vl:
for photo in photos:
svg = apply_style(vl, style, photo, dpi=72)
(output / f"{photo.stem}.svg").write_text(svg)
print(f"Done: {photo.name}")
Style transfer with error handling and progress¶
from pathlib import Path
from vexy_lines_api import MCPClient, MCPError, extract_style, apply_style
style = extract_style("artistic.lines")
photos = sorted(Path("./photos").glob("*.jpg"))
output = Path("./output")
output.mkdir(exist_ok=True)
failed: list[tuple[str, str]] = []
with MCPClient(timeout=60.0) as vl:
for i, photo in enumerate(photos, 1):
print(f"[{i}/{len(photos)}] Processing {photo.name}...")
try:
svg = apply_style(
vl,
style,
photo,
dpi=72,
relative=True,
render_timeout=120.0,
save_lines_to=output / f"{photo.stem}.lines",
)
(output / f"{photo.stem}.svg").write_text(svg)
except MCPError as e:
print(f" FAILED: {e.message}")
failed.append((photo.name, e.message))
except Exception as e:
print(f" FAILED: {e}")
failed.append((photo.name, str(e)))
if failed:
print(f"\n{len(failed)} failures:")
for name, reason in failed:
print(f" {name}: {reason}")
else:
print(f"\nAll {len(photos)} images processed successfully.")
Style interpolation for animation¶
from vexy_lines_api import MCPClient, extract_style, interpolate_style, apply_style
start = extract_style("soft.lines")
end = extract_style("bold.lines")
frames = [f"frame_{i:04d}.jpg" for i in range(60)]
with MCPClient() as vl:
for i, frame in enumerate(frames):
t = i / max(len(frames) - 1, 1)
blended = interpolate_style(start, end, t)
svg = apply_style(vl, blended, frame, dpi=72)
with open(f"output/frame_{i:04d}.svg", "w") as f:
f.write(svg)
Export to multiple formats¶
from vexy_lines_api import MCPClient
with MCPClient() as vl:
vl.open_document("artwork.lines")
vl.render()
vl.export_svg("output.svg")
vl.export_pdf("output.pdf")
vl.export_png("output.png", dpi=300)
vl.export_jpeg("output.jpg", dpi=150)
vl.export_eps("output.eps")
Get SVG as a string¶
from vexy_lines_api import MCPClient
with MCPClient() as vl:
vl.open_document("artwork.lines")
vl.render()
svg_text = vl.svg()
print(f"SVG length: {len(svg_text)} chars")
# Parse with svglab (requires: pip install vexy-lines-apy[svg])
svg_obj = vl.svg_parsed()
Error handling¶
from vexy_lines_api import MCPClient, MCPError
try:
with MCPClient(auto_launch=False, timeout=5.0) as vl:
vl.get_document_info()
except MCPError as e:
print(f"Could not connect: {e.message}")
Low-level tool calls¶
Use call_tool to access any MCP tool directly, including tools not yet wrapped as typed methods.
from vexy_lines_api import MCPClient
with MCPClient() as vl:
vl.open_document("artwork.lines")
# Direct tool call -- returns parsed JSON dict or raw string
info = vl.call_tool("get_document_info")
print(info) # {'width_mm': 210.0, 'height_mm': 297.0, ...}
# Tool call with arguments
result = vl.call_tool("set_fill_params", {
"id": 42,
"params": {"color": "#ff0000", "interval": 20},
})
# List all available tools from the server
tools = vl.call_tool("tools/list")
Video processing with per-frame style transfer¶
from vexy_lines_api import extract_style, process_video_with_style
style = extract_style("engraving.lines")
info = process_video_with_style(
"input.mp4",
"output.mp4",
style=style,
start_frame=0,
end_frame=120, # first 4 seconds at 30fps
include_audio=True,
relative=True,
on_progress=lambda current, total: print(f"Frame {current}/{total}"),
)
print(f"Output: {info.width}x{info.height} @ {info.fps}fps, {info.total_frames} frames")
Video with style interpolation¶
Transition between two styles across the video duration.
from vexy_lines_api import extract_style, process_video_with_style
start_style = extract_style("watercolor.lines")
end_style = extract_style("woodcut.lines")
info = process_video_with_style(
"clip.mp4",
"transition.mp4",
style=start_style,
end_style=end_style,
include_audio=True,
relative=True,
style_mode="auto",
)
Using the JobFolder for resumable batch export¶
from pathlib import Path
from vexy_lines_api import MCPClient, extract_style, apply_style
from vexy_lines_api.export import JobFolder
style = extract_style("reference.lines")
photos = sorted(Path("./photos").glob("*.jpg"))
output_dir = Path("./output")
jf = JobFolder(output_dir)
with MCPClient() as vl:
existing = jf.existing_frames("styled", "svg")
for i, photo in enumerate(photos):
if i in existing:
print(f"Skipping frame {i} (already exists)")
continue
svg_path = jf.frame_path("styled", i, "svg", pad_width=4)
lines_path = jf.frame_path("styled", i, "lines", pad_width=4)
svg = apply_style(vl, style, photo, dpi=72, save_lines_to=lines_path)
svg_path.write_text(svg)
print(f"Frame {i}: {svg_path.name}")
# When done, optionally clean up intermediates
# jf.cleanup()
Export pipeline with callbacks¶
Use process_export for GUI/CLI integration with progress reporting.
import threading
from vexy_lines_api.export import ExportRequest, process_export
request = ExportRequest(
mode="images",
input_paths=["photo1.jpg", "photo2.jpg", "photo3.jpg"],
style_path="artistic.lines",
end_style_path=None,
output_path="./output/",
format="SVG",
size="1x",
relative_style=True,
style_mode="fast",
force=False,
cleanup=False,
)
abort = threading.Event()
process_export(
request,
abort_event=abort,
on_progress=lambda cur, total, msg: print(f"[{cur}/{total}] {msg}"),
on_complete=lambda msg: print(f"Done: {msg}"),
on_error=lambda msg: print(f"Error: {msg}"),
)
Style creation and consolidation¶
Create a styled document, make manual adjustments, then save with the consolidation pattern.
from vexy_lines_api import MCPClient, extract_style
from vexy_lines_api.style import create_styled_document, save_and_consolidate
style = extract_style("base.lines")
with MCPClient() as vl:
create_styled_document(vl, style, "photo.jpg", dpi=150, relative=True)
# Tweak individual fills after the style is applied
tree = vl.get_layer_tree()
for child in tree.children:
for layer in child.children:
for fill in layer.children:
if fill.fill_type == "linear":
vl.set_fill_params(fill.id, angle=90)
# Save -> reopen -> render -> save (ensures file integrity)
save_and_consolidate(vl, "custom.lines", render_timeout=120)
Integration with Claude Desktop¶
If you configure Vexy Lines as an MCP server in Claude Desktop via the vexy-lines-mcp bridge, Claude can call the tools directly. The Python client and the bridge share the same underlying TCP connection to the app.
To use both simultaneously, ensure only one connection is active at a time:
from vexy_lines_api import MCPClient
# Close any Claude Desktop MCP connection first (or use a separate document)
with MCPClient() as vl:
vl.open_document("artwork.lines")
# ... work with the document ...
vl.save_document()
# Connection released -- Claude Desktop can reconnect
The bridge binary location depends on your platform. See MCP Protocol for setup instructions.