figma-mcp

by JayZeeDesign
Verified
  • figma_mcp
import json import hashlib def rgba_to_hex(rgba): """ Convert an RGBA dict into a #RRGGBB hex string (ignoring alpha in this demo). """ r = int(rgba["r"] * 255) g = int(rgba["g"] * 255) b = int(rgba["b"] * 255) return f"#{r:02x}{g:02x}{b:02x}" def style_hash(style_data): """ Create a hash from style data to use as a unique identifier. """ style_str = json.dumps(style_data, sort_keys=True) return hashlib.md5(style_str.encode()).hexdigest() def figma_align_to_flex(figma_val): """ Convert Figma alignment values to CSS flex values. """ align_map = { "MIN": "flex-start", "CENTER": "center", "MAX": "flex-end", "SPACE_BETWEEN": "space-between" } return align_map.get(figma_val, "flex-start") def get_fill_style_id(fills, styles): """ Process fill styles and add them to the styles dictionary. """ if not fills or len(fills) == 0 or fills[0].get("visible", True) == False: return None fill = fills[0] if fill["type"] == "SOLID": color = fill["color"] opacity = fill.get("opacity", 1) # Create a style object style_data = { "backgroundColor": rgba_to_hex(color), "opacity": opacity } # Generate a unique ID for this style style_id = style_hash(style_data) # Add to styles dictionary if not already present if style_id not in styles: styles[style_id] = style_data return style_id return None def get_layout_style_id(raw_node, styles): """ Process layout styles and add them to the styles dictionary. """ layout_style = {} # Extract layout properties if "layoutMode" in raw_node: layout_mode = raw_node["layoutMode"] if layout_mode == "HORIZONTAL": layout_style["display"] = "flex" layout_style["flexDirection"] = "row" elif layout_mode == "VERTICAL": layout_style["display"] = "flex" layout_style["flexDirection"] = "column" # Process padding if "paddingLeft" in raw_node: layout_style["paddingLeft"] = f"{raw_node['paddingLeft']}px" if "paddingRight" in raw_node: layout_style["paddingRight"] = f"{raw_node['paddingRight']}px" if "paddingTop" in raw_node: layout_style["paddingTop"] = f"{raw_node['paddingTop']}px" if "paddingBottom" in raw_node: layout_style["paddingBottom"] = f"{raw_node['paddingBottom']}px" # Process spacing if "itemSpacing" in raw_node: layout_style["gap"] = f"{raw_node['itemSpacing']}px" # Process alignment if "primaryAxisAlignItems" in raw_node: primary_align = raw_node["primaryAxisAlignItems"] counter_align = raw_node.get("counterAxisAlignItems", "MIN") def align_map_primary(val): if layout_mode == "HORIZONTAL": return figma_align_to_flex(val) return figma_align_to_flex(val) def align_map_counter(val): if layout_mode == "HORIZONTAL": return figma_align_to_flex(val) return figma_align_to_flex(val) layout_style["justifyContent"] = align_map_primary(primary_align) layout_style["alignItems"] = align_map_counter(counter_align) # Process size constraints if "absoluteBoundingBox" in raw_node: bbox = raw_node["absoluteBoundingBox"] layout_style["width"] = f"{bbox['width']}px" layout_style["height"] = f"{bbox['height']}px" # Process border radius border_radius = get_border_radius(raw_node) if border_radius: layout_style["borderRadius"] = border_radius # Process corner smoothing corner_smoothing = get_corner_smoothing(raw_node) if corner_smoothing: layout_style["borderRadius"] = corner_smoothing # Generate a unique ID for this style if it's not empty if layout_style: style_id = style_hash(layout_style) # Add to styles dictionary if not already present if style_id not in styles: styles[style_id] = layout_style return style_id return None def get_border_radius(raw_node): """ Extract border radius from a node. """ if "cornerRadius" in raw_node: return f"{raw_node['cornerRadius']}px" return None def get_corner_smoothing(raw_node): """ Extract corner smoothing from a node. """ if "cornerSmoothing" in raw_node and raw_node["cornerSmoothing"] > 0: return f"{raw_node['cornerSmoothing']}px" return None def get_text_style_id(raw_node, styles): """ Process text styles and add them to the styles dictionary. """ if raw_node["type"] != "TEXT" or "style" not in raw_node: return None text_style = {} node_style = raw_node["style"] # Extract text properties if "fontFamily" in node_style: text_style["fontFamily"] = node_style["fontFamily"] if "fontWeight" in node_style: text_style["fontWeight"] = node_style["fontWeight"] if "fontSize" in node_style: text_style["fontSize"] = f"{node_style['fontSize']}px" if "lineHeightPx" in node_style: text_style["lineHeight"] = f"{node_style['lineHeightPx']}px" if "letterSpacing" in node_style: text_style["letterSpacing"] = f"{node_style['letterSpacing']}px" if "textAlignHorizontal" in node_style: align_map = {"LEFT": "left", "CENTER": "center", "RIGHT": "right", "JUSTIFIED": "justify"} text_style["textAlign"] = align_map.get(node_style["textAlignHorizontal"], "left") # Process fill color for text if "fills" in raw_node and raw_node["fills"]: fill = raw_node["fills"][0] if fill["type"] == "SOLID": text_style["color"] = rgba_to_hex(fill["color"]) # Generate a unique ID for this style if it's not empty if text_style: style_id = style_hash(text_style) # Add to styles dictionary if not already present if style_id not in styles: styles[style_id] = text_style return style_id return None def transform_node(raw_node, styles): """ Transform a Figma node into a simplified structure. """ node_type = raw_node["type"] # Basic node properties node = { "id": raw_node["id"], "name": raw_node["name"], "type": node_type, "visible": raw_node.get("visible", True) } # Process styles fill_style_id = get_fill_style_id(raw_node.get("fills", []), styles) if fill_style_id: node["fillStyleId"] = fill_style_id layout_style_id = get_layout_style_id(raw_node, styles) if layout_style_id: node["layoutStyleId"] = layout_style_id text_style_id = get_text_style_id(raw_node, styles) if text_style_id: node["textStyleId"] = text_style_id # Process text content if node_type == "TEXT" and "characters" in raw_node: node["characters"] = raw_node["characters"] # Process image content if node_type == "RECTANGLE" and "fills" in raw_node: for fill in raw_node.get("fills", []): if fill["type"] == "IMAGE" and "imageRef" in fill: node["imageRef"] = fill["imageRef"] # Process children if "children" in raw_node: node["children"] = [] for child in raw_node["children"]: # Skip invisible nodes if child.get("visible", True) == False: continue transformed_child = transform_node(child, styles) node["children"].append(transformed_child) # Process component properties if "componentPropertyReferences" in raw_node: node["componentPropertyReferences"] = raw_node["componentPropertyReferences"] # Process component properties if "componentProperties" in raw_node: node["componentProperties"] = raw_node["componentProperties"] # Process component property definitions if "componentPropertyDefinitions" in raw_node: node["componentPropertyDefinitions"] = raw_node["componentPropertyDefinitions"] # Process variants if "variantProperties" in raw_node: node["variantProperties"] = raw_node["variantProperties"] # Process component set if "componentSetId" in raw_node: node["componentSetId"] = raw_node["componentSetId"] # Process component if "componentId" in raw_node: node["componentId"] = raw_node["componentId"] return node def transform_figma_json(raw_data): """ Transform Figma JSON data into a simplified structure. """ # Initialize styles dictionary styles = {} # Transform the document if isinstance(raw_data, dict) and "document" in raw_data: document = transform_node(raw_data["document"], styles) else: document = transform_node(raw_data, styles) # Return the transformed data return { "document": document, "styles": styles }