Skip to main content
Glama
element-extractor.ts7.4 kB
import { FigmaService, FigmaNode, ImageResource, VectorElement, DesignElement, NodeElements } from './figma-service.js'; export class FigmaElementExtractor { constructor(private figmaService: FigmaService) {} /** * 获取节点中的所有图片资源 */ async getNodeImages(fileId: string, nodeId: string): Promise<ImageResource[]> { try { const node = await this.figmaService.getNodeDetails(fileId, nodeId); if (!node) { throw new Error(`节点 ${nodeId} 不存在`); } const images: ImageResource[] = []; // 递归收集所有包含图片的节点 this.figmaService.traverseNodeTree(node, (currentNode) => { // 检查填充中的图片 if (currentNode.fills) { for (const fill of currentNode.fills) { if (fill.type === 'IMAGE' && fill.imageRef) { images.push({ id: fill.imageRef, name: currentNode.name || `Image in ${currentNode.id}`, type: 'EMBEDDED', size: currentNode.absoluteBoundingBox ? { width: currentNode.absoluteBoundingBox.width, height: currentNode.absoluteBoundingBox.height } : undefined }); } } } // 检查节点类型是否为图片 if (currentNode.type === 'RECTANGLE' && currentNode.fills?.some(f => f.type === 'IMAGE')) { // 已在上面处理 } }); // 获取图片的实际URL if (images.length > 0) { try { const imageRefs = await this.figmaService.getFileImageReferences(fileId); for (const image of images) { if (imageRefs[image.id]) { image.url = imageRefs[image.id]; } } } catch (error) { console.error('获取图片URL失败:', error); } } return this.deduplicateImages(images); } catch (error) { throw new Error(`获取节点图片失败: ${error instanceof Error ? error.message : '未知错误'}`); } } /** * 获取节点的SVG数据 */ async getNodeAsSVG(fileId: string, nodeId: string): Promise<string> { try { return await this.figmaService.getNodeAsSVG(fileId, nodeId); } catch (error) { throw new Error(`获取SVG数据失败: ${error instanceof Error ? error.message : '未知错误'}`); } } /** * 提取节点中的所有矢量元素 */ async extractVectorElements(fileId: string, nodeId: string): Promise<VectorElement[]> { try { const node = await this.figmaService.getNodeDetails(fileId, nodeId); if (!node) { throw new Error(`节点 ${nodeId} 不存在`); } const vectors: VectorElement[] = []; // 递归收集所有矢量节点 this.figmaService.traverseNodeTree(node, (currentNode) => { if (this.isVectorNode(currentNode)) { vectors.push({ id: currentNode.id, name: currentNode.name || `Vector ${currentNode.id}`, type: currentNode.type, fills: currentNode.fills, strokes: currentNode.strokes, boundingBox: currentNode.absoluteBoundingBox ? { x: currentNode.absoluteBoundingBox.x, y: currentNode.absoluteBoundingBox.y, width: currentNode.absoluteBoundingBox.width, height: currentNode.absoluteBoundingBox.height } : undefined }); } }); return vectors; } catch (error) { throw new Error(`提取矢量元素失败: ${error instanceof Error ? error.message : '未知错误'}`); } } /** * 获取节点的所有设计元素(图片+矢量) */ async getAllNodeElements(fileId: string, nodeId: string): Promise<NodeElements> { try { const node = await this.figmaService.getNodeDetails(fileId, nodeId); if (!node) { throw new Error(`节点 ${nodeId} 不存在`); } const [images, vectors] = await Promise.all([ this.getNodeImages(fileId, nodeId), this.extractVectorElements(fileId, nodeId) ]); // 收集组件节点 const components: any[] = []; this.figmaService.traverseNodeTree(node, (currentNode) => { if (currentNode.type === 'COMPONENT' || currentNode.type === 'INSTANCE') { components.push({ id: currentNode.id, name: currentNode.name, type: currentNode.type, componentId: currentNode.componentId || undefined }); } }); return { nodeId, nodeName: node.name || 'Unnamed Node', images, vectors, components, totalElements: images.length + vectors.length + components.length }; } catch (error) { throw new Error(`获取节点元素失败: ${error instanceof Error ? error.message : '未知错误'}`); } } /** * 根据Figma URL获取设计元素 */ async getElementsFromUrl(figmaUrl: string): Promise<NodeElements> { try { const urlInfo = this.figmaService.parseUrl(figmaUrl); const { fileId, nodeId } = urlInfo; if (!nodeId) { throw new Error('URL中缺少节点ID,无法提取设计元素。请确保URL包含node-id参数'); } return await this.getAllNodeElements(fileId, nodeId); } catch (error) { throw new Error(`从URL获取设计元素失败: ${error instanceof Error ? error.message : '未知错误'}`); } } /** * 判断是否为矢量节点 */ private isVectorNode(node: FigmaNode): boolean { const vectorTypes = [ 'VECTOR', 'ELLIPSE', 'POLYGON', 'STAR', 'LINE', 'RECTANGLE' // 矩形也可能包含矢量信息 ]; return vectorTypes.includes(node.type) || Boolean(node.fills && node.fills.some(fill => fill.type !== 'IMAGE')) || Boolean(node.strokes && node.strokes.length > 0); } /** * 去重图片资源 */ private deduplicateImages(images: ImageResource[]): ImageResource[] { const seen = new Set<string>(); return images.filter(image => { const key = image.id || image.url || image.name; if (seen.has(key)) { return false; } seen.add(key); return true; }); } /** * 生成设计元素摘要 */ generateElementsSummary(elements: NodeElements): string { const summary = []; summary.push(`节点名称: ${elements.nodeName}`); summary.push(`总元素数: ${elements.totalElements}`); if (elements.images.length > 0) { summary.push(`\\n📸 图片资源 (${elements.images.length}个):`); elements.images.forEach((img, idx) => { summary.push(` ${idx + 1}. ${img.name} (${img.type})`); }); } if (elements.vectors.length > 0) { summary.push(`\\n🎨 矢量元素 (${elements.vectors.length}个):`); elements.vectors.forEach((vec, idx) => { summary.push(` ${idx + 1}. ${vec.name} (${vec.type})`); }); } if (elements.components.length > 0) { summary.push(`\\n🧩 组件 (${elements.components.length}个):`); elements.components.forEach((comp, idx) => { summary.push(` ${idx + 1}. ${comp.name} (${comp.type})`); }); } return summary.join('\\n'); } }

Implementation Reference

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Echoxiawan/figma-mcp-full-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server