Spaces:
Sleeping
Sleeping
File size: 6,075 Bytes
cfec14d |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 |
"""
Figma API Client
Handles communication with Figma API to extract design screenshots.
"""
import requests
from typing import Dict, List, Tuple, Optional
from pathlib import Path
class FigmaClient:
"""Client for interacting with Figma API."""
BASE_URL = "https://api.figma.com/v1"
def __init__(self, access_token: str):
self.access_token = access_token
self.headers = {
"X-Figma-Token": access_token
}
def get_file(self, file_key: str) -> Dict:
"""Get file metadata from Figma."""
url = f"{self.BASE_URL}/files/{file_key}"
response = requests.get(url, headers=self.headers)
response.raise_for_status()
return response.json()
def get_frame_nodes(self, file_key: str) -> List[Dict]:
"""
Get all top-level frames from the Figma file.
Returns frames that match viewport patterns (Desktop/Mobile).
"""
file_data = self.get_file(file_key)
frames = []
# Navigate through document -> pages -> frames
document = file_data.get("document", {})
for page in document.get("children", []):
if page.get("type") == "CANVAS":
for child in page.get("children", []):
if child.get("type") == "FRAME":
frame_name = child.get("name", "")
frame_id = child.get("id", "")
bounds = child.get("absoluteBoundingBox", {})
# Determine viewport type from name
viewport = None
if "desktop" in frame_name.lower() or bounds.get("width", 0) >= 1000:
viewport = "desktop"
elif "mobile" in frame_name.lower() or bounds.get("width", 0) <= 500:
viewport = "mobile"
if viewport:
frames.append({
"id": frame_id,
"name": frame_name,
"viewport": viewport,
"width": bounds.get("width", 0),
"height": bounds.get("height", 0)
})
return frames
def export_frame(
self,
file_key: str,
frame_id: str,
output_path: str,
scale: float = 1.0,
format: str = "png"
) -> Tuple[str, Dict[str, int]]:
"""
Export a frame as an image.
Args:
file_key: Figma file key
frame_id: Node ID of the frame to export
output_path: Where to save the image
scale: Export scale (1.0 = actual size, 0.5 = half size)
format: Image format (png, jpg, svg, pdf)
Returns:
Tuple of (saved_path, dimensions_dict)
"""
# Get export URL
url = f"{self.BASE_URL}/images/{file_key}"
params = {
"ids": frame_id,
"scale": scale,
"format": format
}
response = requests.get(url, headers=self.headers, params=params)
response.raise_for_status()
data = response.json()
image_url = data.get("images", {}).get(frame_id)
if not image_url:
raise ValueError(f"Could not get export URL for frame {frame_id}")
# Download the image
img_response = requests.get(image_url)
img_response.raise_for_status()
# Save to file
Path(output_path).parent.mkdir(parents=True, exist_ok=True)
with open(output_path, "wb") as f:
f.write(img_response.content)
# Get image dimensions
from PIL import Image
with Image.open(output_path) as img:
width, height = img.size
return output_path, {"width": width, "height": height}
def export_frames_for_comparison(
self,
file_key: str,
output_dir: str,
execution_id: str
) -> Tuple[Dict[str, str], Dict[str, Dict[str, int]]]:
"""
Export all relevant frames for UI comparison.
Automatically finds Desktop and Mobile frames and exports them
at 1x scale (not 2x) for proper comparison with website screenshots.
Returns:
Tuple of (screenshot_paths, dimensions)
"""
frames = self.get_frame_nodes(file_key)
screenshots = {}
dimensions = {}
# Group by viewport, prefer larger frames
viewport_frames = {}
for frame in frames:
viewport = frame["viewport"]
if viewport not in viewport_frames:
viewport_frames[viewport] = frame
elif frame["width"] * frame["height"] > viewport_frames[viewport]["width"] * viewport_frames[viewport]["height"]:
viewport_frames[viewport] = frame
# Export each viewport
for viewport, frame in viewport_frames.items():
print(f" 📥 Exporting frame: {frame['name']} ({frame['width']}px width)")
print(f" Frame ID: {frame['id']}")
print(f" Dimensions: {frame['width']}x{frame['height']}")
output_path = f"{output_dir}/{viewport}_{execution_id}.png"
# Export at scale 1.0 (actual design size)
# Note: Figma often has designs at 2x, we'll handle normalization in comparison
saved_path, dims = self.export_frame(
file_key,
frame["id"],
output_path,
scale=1.0 # Export at 1x to get actual design dimensions
)
screenshots[viewport] = saved_path
dimensions[viewport] = dims
print(f" ✓ Exported: {saved_path}")
return screenshots, dimensions
|