From dacd87bbacb05ba5908b52039015abe2731d6ba2 Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Tue, 30 Sep 2025 15:10:37 +0200 Subject: [PATCH] Add utils for Page handling, improve prompts explaining design structure --- mcp-server/data/prompts.yml | 16 ++++++++--- penpot-plugin/src/PenpotUtils.ts | 46 +++++++++++++++++++++++++++++++- 2 files changed, 58 insertions(+), 4 deletions(-) diff --git a/mcp-server/data/prompts.yml b/mcp-server/data/prompts.yml index a49046b..6a33c3b 100644 --- a/mcp-server/data/prompts.yml +++ b/mcp-server/data/prompts.yml @@ -9,7 +9,14 @@ initial_instructions: | NEVER make assumptions about missing values and don't get overly creative (e.g. don't pick your own colours and stick to non-creative defaults such as white/black if you are lacking information). - A Penpot project contains shapes and more general design elements (which we shall collectively refer to as "elements"). + A Penpot design ultimately consists of shapes. + The type `Shape` is a union type, which encompasses both containers and low-level shapes. + Shapes in a Penpot design are organized hierarchically. + At the top level, a design project contains one or more `Page` objects. + Each `Page` contains a tree of elements. For a given instance `page`, its root shape is `page.root`. + A Page is frequently structured into boards. A `Board` is a high-level grouping element. + A `Group` is a more low-level grouping element to organize low-level shapes into a logical unit. + Low-level shape types are `Rectangle`, `Path`, `Text`, `Ellipse`, `Image`, etc. One of your key tools is the `execute_code` tool, which allows you to run JavaScript code using the Penpot Plugin API directly in the connected project. @@ -17,9 +24,9 @@ initial_instructions: | the `penpot_api_info` tool. When writing code, a key object is the `penpot` object (which is of type `Penpot`): - * `penpot.selection` provides the list of elements the user has selected in the Penpot UI. + * `penpot.selection` provides the list of shapes the user has selected in the Penpot UI. If it is unclear which elements to work on, you can ask the user to select them for you. - * `penpot.root` provides the root element of the tree-structured design project, from which all shapes can be reached. + * `penpot.root` provides the root shape of the currently active page. * Generation of CSS content for elements via `penpot.generateStyle` * Generation of HTML/SVG content for elements via `penpot.generateMarkup` @@ -31,6 +38,9 @@ initial_instructions: | Any given shape contains information on the concrete type via its `type` field. Another useful object is the `penpotUtils` object (which is not part of the official API). It provides: + * 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. diff --git a/penpot-plugin/src/PenpotUtils.ts b/penpot-plugin/src/PenpotUtils.ts index 181d73e..35af48a 100644 --- a/penpot-plugin/src/PenpotUtils.ts +++ b/penpot-plugin/src/PenpotUtils.ts @@ -1,4 +1,4 @@ -import { Shape } from "@penpot/plugin-types"; +import { Page, Shape } from "@penpot/plugin-types"; export class PenpotUtils { /** @@ -27,6 +27,33 @@ export class PenpotUtils { }; } + /** + * Finds all shapes that matches the given predicate in the given shape tree. + * + * @param predicate - A function that takes a shape and returns true if it matches the criteria + * @param root - The root shape to start the search from (defaults to penpot.root) + */ + public static findShapes(predicate: (shape: Shape) => boolean, root: Shape | null = penpot.root): Shape[] { + let result = new Array(); + + let find = function (shape: Shape | null) { + if (!shape) { + return; + } + if (predicate(shape)) { + result.push(shape); + } + if ("children" in shape && shape.children) { + for (let child of shape.children) { + find(child); + } + } + }; + + find(root); + return result; + } + /** * Finds the first shape that matches the given predicate in the given shape tree. * @@ -64,4 +91,21 @@ export class PenpotUtils { public static findShapeById(id: string): Shape | null { return this.findShape((shape) => shape.id === id); } + + public static findPage(predicate: (page: Page) => boolean): Page | null { + let page = penpot.currentFile!.pages.find(predicate); + return page || null; + } + + public static getPages(): { id: string; name: string }[] { + return penpot.currentFile!.pages.map((page) => ({ id: page.id, name: page.name })); + } + + public static getPageById(id: string): Page | null { + return this.findPage((page) => page.id === id); + } + + public static getPageByName(name: string): Page | null { + return this.findPage((page) => page.name.toLowerCase() === name.toLowerCase()); + } }