diff --git a/mcp-server/data/prompts.yml b/mcp-server/data/prompts.yml index 9083c50..40e0472 100644 --- a/mcp-server/data/prompts.yml +++ b/mcp-server/data/prompts.yml @@ -42,7 +42,7 @@ initial_instructions: | * 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. * `parentX` and `parentY` (as well as `boardX` and `boardY`) are READ-ONLY computed properties showing position relative to parent/board. - To position a shape within its parent, set the absolute `x` and `y` properties accordingly. + To position relative to parent, use `penpotUtils.setParentXY(shape, parentX, parentY)` or manually set `shape.x = parent.x + parentX`. * `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. @@ -114,14 +114,7 @@ initial_instructions: | Example: A card board with an image, and a separate text board positioned below it as a sibling. This is incorrect if the text should overlay the image. The text should be a child of the card board. - * **Verification**: Always verify visual containment when working with nested structures: - ```javascript - const isVisuallyContained = - child.x >= parent.x && - child.y >= parent.y && - (child.x + child.width) <= (parent.x + parent.width) && - (child.y + child.height) <= (parent.y + parent.height); - ``` + * **Verification**: Use `penpotUtils.isContainedIn(child, parent)` to verify visual containment # Text Elements @@ -155,6 +148,10 @@ initial_instructions: | * findShape(predicate: (shape: Shape) => boolean, root: Shape | null = null): Shape | null If no root is provided, search globally (in all pages). * findShapes(predicate: (shape: Shape) => boolean, root: Shape | null = null): Shape[] + * isContainedIn(child: Shape, parent: Shape): boolean + Returns true if child is fully within parent's visual bounds + * setParentXY(shape: Shape, parentX: number, parentY: number): void + Sets shape position relative to its parent (since parentX/parentY are read-only) General pointers for working with Penpot designs: * Prefer `penpotUtils` helper functions — avoid reimplementing shape searching. diff --git a/penpot-plugin/src/PenpotUtils.ts b/penpot-plugin/src/PenpotUtils.ts index 0869dbb..55285a5 100644 --- a/penpot-plugin/src/PenpotUtils.ts +++ b/penpot-plugin/src/PenpotUtils.ts @@ -164,6 +164,40 @@ export class PenpotUtils { return penpot.generateStyle([shape], { type: "css", includeChildren: true }); } + /** + * Checks if a child shape is fully contained within its parent's bounds. + * Visual containment means all edges of the child are within the parent's bounding box. + * + * @param child - The child shape to check + * @param parent - The parent shape to check against + * @returns true if child is fully contained within parent bounds, false otherwise + */ + public static isContainedIn(child: Shape, parent: Shape): boolean { + return ( + child.x >= parent.x && + child.y >= parent.y && + child.x + child.width <= parent.x + parent.width && + child.y + child.height <= parent.y + parent.height + ); + } + + /** + * Sets the position of a shape relative to its parent's position. + * This is a convenience method since parentX and parentY are read-only properties. + * + * @param shape - The shape to position + * @param parentX - The desired X position relative to the parent + * @param parentY - The desired Y position relative to the parent + * @throws Error if the shape has no parent + */ + public static setParentXY(shape: Shape, parentX: number, parentY: number): void { + if (!shape.parent) { + throw new Error("Shape has no parent - cannot set parent-relative position"); + } + shape.x = shape.parent.x + parentX; + shape.y = shape.parent.y + parentY; + } + /** * Decodes a base64 string to a Uint8Array. * This is required because the Penpot plugin environment does not provide the atob function.