You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

132 lines
4.8 KiB

figma.showUI(__html__, { width: 320, height: 180 });
const NODE_LIMIT = 500;
async function runExport() {
const selection = figma.currentPage.selection;
if (selection.length === 0) {
figma.ui.postMessage({ type: 'error', message: 'Please select a frame or node.' });
return;
}
let nodeCount = 0;
function countNodes(node: SceneNode) {
nodeCount++;
if ("children" in node) {
for (const child of node.children) countNodes(child);
}
}
for (const node of selection) countNodes(node);
if (nodeCount > NODE_LIMIT) {
figma.ui.postMessage({ type: 'error', message: `Selection exceeds limit. Selected: ${nodeCount}. Limit: ${NODE_LIMIT}.` });
return;
}
let markdownOutput = "# AI DATA EXTRACTION GUIDE\n\n";
markdownOutput += "This archive contains structural design data and visual assets extracted from Figma specifically for machine learning interpretation.\n\n";
markdownOutput += "## Structural Architecture\n";
markdownOutput += "- data.md: This document contains the hierarchical tree of design nodes, text strings, and layout properties.\n";
markdownOutput += "- Folders: Named after the top-level selected nodes, containing the associated PNG and SVG image files.\n\n";
markdownOutput += "## Interpretation Rules for AI\n";
markdownOutput += "1. Tree Hierarchy: Indentation levels indicate parent-child node relationships.\n";
markdownOutput += "2. Asset Mapping: Visual exports (PNG and SVG formats) are located inside the corresponding top-level folder. Nodes are exported visually if they contain photographic fills or child text layers requiring structural context.\n";
markdownOutput += "3. Text Handling: Independent text nodes skip visual image export because their complete textual data is captured inline within this document.\n";
markdownOutput += "4. Property Metadata: The Raw Properties object provides precise dimensions, positions, fills, and styling definitions parsed directly from the Figma canvas API.\n\n";
markdownOutput += "--- \n\n";
markdownOutput += "## Hierarchical Tree Data\n\n";
const exportedImages: Array<{ folder: string; name: string; data: Uint8Array }> = [];
const allFolders: string[] = [];
function getSafeName(node: SceneNode): string {
let cleanName = node.name.replace(/[^a-z0-9]/gi, '_');
if (cleanName.length > 30) {
cleanName = cleanName.substring(0, 30);
}
const cleanId = node.id.replace(/[^a-z0-9]/gi, '_');
return `${cleanName}_${cleanId}`;
}
function hasText(node: SceneNode): boolean {
if (node.type === "TEXT") return true;
if ("children" in node) {
for (const child of node.children) {
if (hasText(child)) return true;
}
}
return false;
}
async function traverseNode(node: SceneNode, depth: number, currentFolderPath: string) {
allFolders.push(currentFolderPath);
const indent = " ".repeat(depth);
markdownOutput += `${indent}- Name: ${node.name} (Type: ${node.type}, Path: ${currentFolderPath})\n`;
if (node.type === "TEXT") {
markdownOutput += `${indent} - Text Content: ${node.characters}\n`;
}
const rawProps = {
id: node.id,
width: node.width,
height: node.height,
x: node.x,
y: node.y,
fills: "fills" in node ? node.fills : null,
strokes: "strokes" in node ? node.strokes : null,
effects: "effects" in node ? node.effects : null,
opacity: "opacity" in node ? node.opacity : null,
rotation: "rotation" in node ? node.rotation : null
};
markdownOutput += `${indent} - Raw Properties: ${JSON.stringify(rawProps)}\n`;
const isTextNode = node.type === "TEXT";
if (!isTextNode) {
try {
const pngBuffer = await node.exportAsync({ format: 'PNG' });
const safeNodeName = getSafeName(node);
exportedImages.push({ folder: currentFolderPath, name: `${safeNodeName}.png`, data: pngBuffer });
const nodeHasText = hasText(node);
if (!nodeHasText) {
const svgBuffer = await node.exportAsync({ format: 'SVG' });
exportedImages.push({ folder: currentFolderPath, name: `${safeNodeName}.svg`, data: svgBuffer });
}
} catch (e) {
console.error(`Failed to export ${node.name}`, e);
}
}
if ("children" in node) {
for (const child of node.children) {
const childSafeName = getSafeName(child);
const childFolderPath = `${currentFolderPath}/children/${childSafeName}`;
await traverseNode(child, depth + 1, childFolderPath);
}
}
}
for (const node of selection) {
const safeFolderName = getSafeName(node);
await traverseNode(node, 0, safeFolderName);
}
figma.ui.postMessage({
type: 'export',
markdown: markdownOutput,
images: exportedImages,
folders: allFolders
});
}
figma.ui.onmessage = (msg) => {
if (msg.type === 'run-extraction') {
runExport();
}
};