PenpotUtils.shapeStructure: Add essential information on layouts #26

This commit is contained in:
Dominik Jain 2026-01-14 21:49:01 +01:00
parent 4353f67322
commit 3c1bd875d8
2 changed files with 42 additions and 17 deletions

View File

@ -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. 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`. Actual low-level shape types are `Rectangle`, `Path`, `Text`, `Ellipse`, `Image`, `Boolean`, and `SvgRaw`.
`ShapeBase` is a base type most shapes build upon. `ShapeBase` is a base type most shapes build upon.
## Core Shape Properties ## Core Shape Properties
**Type**: **Type**:
Any given shape contains information on the concrete type via its `type` field. Any given shape contains information on the concrete type via its `type` field.
**Position and Dimensions**: **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. * 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. 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. 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. * `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. * `bounds` is a READ-ONLY property. Use `x`, `y` with `resize()` to modify shape bounds.
**Other Writable Properties**: **Other Writable Properties**:
* `name` - Shape name * `name` - Shape name
* `fills`, `strokes` - Styling properties * `fills`, `strokes` - Styling properties
* `rotation`, `opacity`, `blocked`, `hidden`, `visible` * `rotation`, `opacity`, `blocked`, `hidden`, `visible`
**Z-Order**: **Z-Order**:
* The z-order of shapes is determined by the order in the `children` array of the parent shape. * 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 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). (i.e. add background shapes first, then foreground shapes later).
* To modify z-order after creation, use these methods: `bringToFront()`, `sendToBack()`, `bringForward()`, `sendBackward()`, * To modify z-order after creation, use these methods: `bringToFront()`, `sendToBack()`, `bringForward()`, `sendBackward()`,
and, for precise control, `setParentIndex(index)` (0-based). and, for precise control, `setParentIndex(index)` (0-based).
**Modification Methods**: **Modification Methods**:
* `resize(width, height)` - Change dimensions (required for width/height since they're read-only) * `resize(width, height)` - Change dimensions (required for width/height since they're read-only)
* `rotate(angle, center?)` - Rotate shape * `rotate(angle, center?)` - Rotate shape
* `remove()` - Permanently destroy the shape (use only for deletion, NOT for reparenting) * `remove()` - Permanently destroy the shape (use only for deletion, NOT for reparenting)
**Reparenting (Moving Shapes Between Parents)**: **Reparenting (Moving Shapes Between Parents)**:
* `newParent.appendChild(shape)` or `newParent.insertChild(index, shape)` - Move shape to new parent * `newParent.appendChild(shape)` or `newParent.insertChild(index, shape)` - Move shape to new parent
- Automatically removes the shape from its old 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 - You may need to adjust absolute x/y after reparenting to achieve desired visual layout
# Images # Images
The `Image` type is a legacy type. Images are now typically mostly embedded in a `Fill` with `fillImage` set to an 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. `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 # 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! For many tasks, it can be critical to visually inspect the design. Remember to use the `export_shape` tool for this purpose!
# Layout Systems # Layout Systems
Boards can have layout systems that automatically control the positioning and spacing of their children: Boards can have layout systems that automatically control the positioning and spacing of their children:
* **Flex Layout**: `board.flex` - A flexbox-style layout system * **Flex Layout**: `board.flex` - A flexbox-style layout system
- Properties: `dir` (direction), `rowGap`, `columnGap`, `alignItems`, `justifyContent` - Properties: `dir` (direction), `rowGap`, `columnGap`, `alignItems`, `justifyContent`
- Padding: `topPadding`, `rightPadding`, `bottomPadding`, `leftPadding`, or combined `verticalPadding`, `horizontalPadding` - Padding: `topPadding`, `rightPadding`, `bottomPadding`, `leftPadding`, or combined `verticalPadding`, `horizontalPadding`
@ -102,7 +102,7 @@ initial_instructions: |
# Semantic Containment vs Hierarchical Nesting # Semantic Containment vs Hierarchical Nesting
Understanding the difference between hierarchical structure and visual containment is critical: 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** * **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 * Visual containment means a child shape is fully within the visual bounds of its parent
* Common design patterns require semantic containment: * Common design patterns require semantic containment:
@ -147,9 +147,10 @@ initial_instructions: |
* getPages(): { id: string; name: string }[] * getPages(): { id: string; name: string }[]
* getPageById(id: string): Page | null * getPageById(id: string): Page | null
* getPageByName(name: string): Page | null * getPageByName(name: string): Page | null
* shapeStructure(shape: Shape, maxDepth: number | undefined = undefined): object * shapeStructure(shape: Shape, maxDepth: number | undefined = undefined): { id, name, type, children?, layout? }
Generates an overview structure of the given shape, Generates an overview structure of the given shape.
providing the shape's id, name and type, and recursively the children's structure. - children: recursive, limited by maxDepth
- layout: present if shape has flex/grid layout, contains { type: "flex" | "grid", ... }
* findShapeById(id: string): Shape | null * findShapeById(id: string): Shape | null
* findShape(predicate: (shape: Shape) => boolean, root: Shape | null = null): Shape | null * findShape(predicate: (shape: Shape) => boolean, root: Shape | null = null): Shape | null
If no root is provided, search globally (in all pages). If no root is provided, search globally (in all pages).

View File

@ -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 { export class PenpotUtils {
/** /**
* Generates an overview structure of the given shape, * Generates an overview structure of the given shape,
* providing its id, name and type, and recursively its children's attributes. * providing its id, name and type, and recursively its children's attributes.
* The `type` field indicates the type in the Penpot API. * 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 shape - The root shape to generate the structure from
* @param maxDepth - Optional maximum depth to traverse (leave undefined for unlimited) * @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, id: shape.id,
name: shape.name, name: shape.name,
type: shape.type, type: shape.type,
children: children, 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;
} }
/** /**