From 3c1bd875d82fc18e333ddc441686fe492ccb7a8e Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Wed, 14 Jan 2026 21:49:01 +0100 Subject: [PATCH] PenpotUtils.shapeStructure: Add essential information on layouts #26 --- mcp-server/data/prompts.yml | 31 ++++++++++++++++--------------- penpot-plugin/src/PenpotUtils.ts | 28 ++++++++++++++++++++++++++-- 2 files changed, 42 insertions(+), 17 deletions(-) diff --git a/mcp-server/data/prompts.yml b/mcp-server/data/prompts.yml index addfbea..9083c50 100644 --- a/mcp-server/data/prompts.yml +++ b/mcp-server/data/prompts.yml @@ -32,12 +32,12 @@ initial_instructions: | A `Group` is a more low-level grouping element used to organize low-level shapes into a logical unit. Actual low-level shape types are `Rectangle`, `Path`, `Text`, `Ellipse`, `Image`, `Boolean`, and `SvgRaw`. `ShapeBase` is a base type most shapes build upon. - + ## Core Shape Properties - + **Type**: Any given shape contains information on the concrete type via its `type` field. - + **Position and Dimensions**: * The location properties `x` and `y` refer to the top left corner of a shape's bounding box in the absolute (Page) coordinate system. These are writable - set them directly to position shapes. @@ -45,24 +45,24 @@ initial_instructions: | To position a shape within its parent, set the absolute `x` and `y` properties accordingly. * `width` and `height` are READ-ONLY. Use `resize(width, height)` method to change dimensions. * `bounds` is a READ-ONLY property. Use `x`, `y` with `resize()` to modify shape bounds. - + **Other Writable Properties**: * `name` - Shape name * `fills`, `strokes` - Styling properties * `rotation`, `opacity`, `blocked`, `hidden`, `visible` - + **Z-Order**: * The z-order of shapes is determined by the order in the `children` array of the parent shape. Therefore, when creating shapes that should be on top of each other, add them to the parent in the correct order (i.e. add background shapes first, then foreground shapes later). * To modify z-order after creation, use these methods: `bringToFront()`, `sendToBack()`, `bringForward()`, `sendBackward()`, and, for precise control, `setParentIndex(index)` (0-based). - + **Modification Methods**: * `resize(width, height)` - Change dimensions (required for width/height since they're read-only) * `rotate(angle, center?)` - Rotate shape * `remove()` - Permanently destroy the shape (use only for deletion, NOT for reparenting) - + **Reparenting (Moving Shapes Between Parents)**: * `newParent.appendChild(shape)` or `newParent.insertChild(index, shape)` - Move shape to new parent - Automatically removes the shape from its old parent @@ -70,18 +70,18 @@ initial_instructions: | - You may need to adjust absolute x/y after reparenting to achieve desired visual layout # Images - + The `Image` type is a legacy type. Images are now typically mostly embedded in a `Fill` with `fillImage` set to an `ImageData` object, i.e. the `fills` property of of a shape (e.g. a `Rectangle`) will contain a fill where `fillImage` is set. - + # Visual Inspection of Designs For many tasks, it can be critical to visually inspect the design. Remember to use the `export_shape` tool for this purpose! - + # Layout Systems Boards can have layout systems that automatically control the positioning and spacing of their children: - + * **Flex Layout**: `board.flex` - A flexbox-style layout system - Properties: `dir` (direction), `rowGap`, `columnGap`, `alignItems`, `justifyContent` - Padding: `topPadding`, `rightPadding`, `bottomPadding`, `leftPadding`, or combined `verticalPadding`, `horizontalPadding` @@ -102,7 +102,7 @@ initial_instructions: | # Semantic Containment vs Hierarchical Nesting Understanding the difference between hierarchical structure and visual containment is critical: - + * **Hierarchical nesting** (parent/child in the shape tree) does NOT always equal **visual containment** * Visual containment means a child shape is fully within the visual bounds of its parent * Common design patterns require semantic containment: @@ -147,9 +147,10 @@ initial_instructions: | * getPages(): { id: string; name: string }[] * getPageById(id: string): Page | null * getPageByName(name: string): Page | null - * shapeStructure(shape: Shape, maxDepth: number | undefined = undefined): object - Generates an overview structure of the given shape, - providing the shape's id, name and type, and recursively the children's structure. + * shapeStructure(shape: Shape, maxDepth: number | undefined = undefined): { id, name, type, children?, layout? } + Generates an overview structure of the given shape. + - children: recursive, limited by maxDepth + - layout: present if shape has flex/grid layout, contains { type: "flex" | "grid", ... } * findShapeById(id: string): Shape | null * findShape(predicate: (shape: Shape) => boolean, root: Shape | null = null): Shape | null If no root is provided, search globally (in all pages). diff --git a/penpot-plugin/src/PenpotUtils.ts b/penpot-plugin/src/PenpotUtils.ts index c5d9e97..0869dbb 100644 --- a/penpot-plugin/src/PenpotUtils.ts +++ b/penpot-plugin/src/PenpotUtils.ts @@ -1,10 +1,11 @@ -import { Fill, Page, Rectangle, Shape } from "@penpot/plugin-types"; +import { Fill, FlexLayout, GridLayout, Page, Rectangle, Shape } from "@penpot/plugin-types"; export class PenpotUtils { /** * Generates an overview structure of the given shape, * providing its id, name and type, and recursively its children's attributes. * The `type` field indicates the type in the Penpot API. + * If the shape has a layout system (flex or grid), includes layout information. * * @param shape - The root shape to generate the structure from * @param maxDepth - Optional maximum depth to traverse (leave undefined for unlimited) @@ -19,12 +20,35 @@ export class PenpotUtils { ); } } - return { + + const result: any = { id: shape.id, name: shape.name, type: shape.type, children: children, }; + + // add layout information if present + if ("flex" in shape && shape.flex) { + const flex: FlexLayout = shape.flex; + result.layout = { + type: "flex", + dir: flex.dir, + rowGap: flex.rowGap, + columnGap: flex.columnGap, + }; + } else if ("grid" in shape && shape.grid) { + const grid: GridLayout = shape.grid; + result.layout = { + type: "grid", + rows: grid.rows, + columns: grid.columns, + rowGap: grid.rowGap, + columnGap: grid.columnGap, + }; + } + + return result; } /**