API Reference¶
This is the comprehensive API reference for PyFreeform. Every class, method, property, and concept is documented here on a single page, organized to follow the natural discovery flow -- from creating your first Scene to building complex generative art.
How to use this reference
This page is designed as a lookup reference. For learning-oriented walkthroughs with visual examples, see the Guide section. For copy-paste starting points, see Recipes.
1. The Entry Point: Scene¶
Everything starts with a Scene. It is the canvas -- it holds your artwork and renders it to SVG.
See also
For a hands-on walkthrough of creating scenes, see Scenes and Grids.
Three Ways to Create a Scene¶
| Constructor | Use Case | Returns |
|---|---|---|
Scene(width, height, background=None) |
Manual canvas, no grid | Scene |
Scene.from_image(source, *, grid_size=40, cell_size=10, ...) |
Image-based art | Scene with grid |
Scene.with_grid(*, cols=30, rows=None, cell_size=10, ...) |
Grid-based art, no image | Scene with grid |
Scene.from_image() is the flagship -- load a photo, get a grid where every cell knows the color and brightness of the pixel it overlays.
Scene.with_grid() gives you the same grid structure but with no image data (cells default to brightness 0.5, color "#808080").
Scene(w, h) is for freeform art -- you place entities at absolute positions, no grid involved.
Scene Properties¶
| Property | Type | Description |
|---|---|---|
scene.width |
int |
Canvas width in pixels |
scene.height |
int |
Canvas height in pixels |
scene.background |
str \| None |
Background color (default: "#1a1a2e" midnight blue) |
scene.grid |
Grid |
The primary grid (raises ValueError if none) |
scene.grids |
list[Grid] |
All grids in the scene |
scene.entities |
list[Entity] |
All entities (including those inside grid cells) |
scene.connections |
list[Connection] |
All connections |
Scene Methods¶
| Method | Description |
|---|---|
scene.add(entity, at=) |
Add an entity with relative positioning (inherited from Surface). |
scene.place(entity) |
Add an entity at its current pixel position (inherited from Surface). |
scene.add_connection(conn) |
Add a connection to the scene. |
scene.add_grid(grid) |
Add a grid to the scene. |
scene.remove(entity) |
Remove an entity. Returns True if found. |
scene.remove_connection(conn) |
Remove a connection. Returns True if found. |
scene.remove_grid(grid) |
Remove a grid. Returns True if found. |
scene.clear() |
Remove everything. |
scene.to_svg() |
Render to SVG string. |
scene.save(path) |
Save to .svg file (adds extension if missing). |
scene.crop(padding=0) |
Crop viewBox to fit content bounds. Great for transparent exports. |
scene.trim(top=0, right=0, bottom=0, left=0) |
Remove pixels from edges of the scene. Chainable with crop(). |
from_image() Full Signature¶
Expand full signature
Scene.from_image(
source: str | Path | Image, # File path or Image object
*,
grid_size: int | None = 40, # Columns (rows auto from aspect ratio)
cell_size: int = 10, # Base cell size in pixels
cell_ratio: float = 1.0, # Width-to-height ratio (2.0 = domino)
cell_width: float | None, # Explicit cell width (overrides cell_size)
cell_height: float | None, # Explicit cell height (overrides cell_size)
background: str | None, # Background color (default "#1a1a2e")
) -> Scene
Two modes:
grid_size=N(default): N columns, rows calculated from image aspect ratio. Scene dimensions = grid * cell_size.grid_size=None: Grid fits the image dimensions. Columns/rows derived fromimage.width / cell_size.
with_grid() Full Signature¶
Expand full signature
Scene.with_grid(
*,
cols: int = 30, # Columns
rows: int | None = None, # Rows (defaults to cols for square)
cell_size: int = 10, # Base cell size in pixels
cell_width: float | None, # Explicit cell width
cell_height: float | None, # Explicit cell height
background: str | None, # Background color (default "#1a1a2e")
) -> Scene
2. The Grid: Structure and Selection¶
A Grid divides the scene into rows and columns of Cell objects. It provides powerful selection methods for targeting specific cells.
See also
For grid creation patterns and cell access techniques, see Scenes and Grids.
Grid Construction¶
| Constructor | Description |
|---|---|
Grid(cols, rows, cell_size=None, cell_width=None, cell_height=None) |
Manual grid |
Grid.from_image(image, cols=None, rows=None, cell_size=10, ...) |
Grid sized to image |
Grid Properties¶
| Property | Type | Description |
|---|---|---|
grid.cols |
int |
Number of columns |
grid.rows |
int |
Number of rows |
grid.cell_width |
float |
Cell width in pixels |
grid.cell_height |
float |
Cell height in pixels |
grid.cell_size |
(float, float) |
(cell_width, cell_height) tuple |
grid.pixel_width |
float |
Total width = cols * cell_width |
grid.pixel_height |
float |
Total height = rows * cell_height |
grid.origin |
Coord |
Top-left corner position |
grid.source_image |
Image \| None |
Original source image (if from_image) |
Cell Access¶
| Operation | Description |
|---|---|
grid[row, col] |
Access by (row, col) index |
for cell in grid: |
Iterate row-by-row, left-to-right |
len(grid) |
Total number of cells |
grid.cell_at(x, y) |
Get cell at pixel position (or None) |
Row & Column Access¶
| Method | Returns | Description |
|---|---|---|
grid.row(i) |
list[Cell] |
All cells in row i |
grid.column(i) |
list[Cell] |
All cells in column i |
grid.all_rows |
Iterator[list[Cell]] |
Iterate over all rows |
grid.all_columns |
Iterator[list[Cell]] |
Iterate over all columns |
Region Selection¶
| Method | Returns | Description |
|---|---|---|
grid.region(row_start, row_end, col_start, col_end) |
Iterator[Cell] |
Rectangular region |
grid.border(thickness=1) |
Iterator[Cell] |
Cells on the grid border |
Cell Merging (CellGroup)¶
| Method | Returns | Description |
|---|---|---|
grid.merge(start, end) |
CellGroup |
Merge region into single surface. Both args are (row, col) tuples, both inclusive. Default: start=(0, 0), end=(rows-1, cols-1). Example: merge((0, 0), (2, 2)) selects a 3x3 block. |
grid.merge_row(i) |
CellGroup |
Merge full row |
grid.merge_col(i) |
CellGroup |
Merge full column |
A CellGroup is a virtual surface -- it has all the same add_* builder methods as a Cell, and averaged data properties from its constituent cells.
Pattern Selection¶
| Method | Description |
|---|---|
grid.every(n, offset=0) |
Every Nth cell (linear count) |
grid.checkerboard("black" \| "white") |
Checkerboard pattern |
grid.where(predicate) |
Filter by lambda: grid.where(lambda c: c.brightness > 0.5) |
grid.diagonal(direction="down", offset=0) |
Main or offset diagonals |
Data Loading¶
Modes: "value" (raw), "normalized" (0-1), "hex" (color string). Normally handled automatically by Scene.from_image().
3. The Cell: Your Creative Unit¶
Cell extends Surface -- it inherits all 12+ builder methods plus has image data, position helpers, and neighbor access.
See also
For practical cell usage patterns, see Working with Cells.
Typed Data Properties (from loaded image)¶
| Property | Type | Default | Description |
|---|---|---|---|
cell.brightness |
float |
0.5 |
0.0 (black) to 1.0 (white) |
cell.color |
str |
"#808080" |
Hex color string |
cell.rgb |
(int, int, int) |
(128, 128, 128) |
RGB tuple (0-255 each) |
cell.alpha |
float |
1.0 |
0.0 (transparent) to 1.0 (opaque) |
cell.data |
dict |
{} |
Raw data dict for custom layers |
Position Properties (inherited from Surface)¶
| Property | Type | Description |
|---|---|---|
cell.x, cell.y |
float |
Top-left corner |
cell.width, cell.height |
float |
Cell dimensions |
cell.bounds |
(x, y, w, h) |
Bounding tuple |
cell.center |
Coord |
Center position |
cell.top_left |
Coord |
Top-left corner |
cell.top_right |
Coord |
Top-right corner |
cell.bottom_left |
Coord |
Bottom-left corner |
cell.bottom_right |
Coord |
Bottom-right corner |
Grid Position¶
| Property | Type | Description |
|---|---|---|
cell.row |
int |
Row index (0-based) |
cell.col |
int |
Column index (0-based) |
cell.grid |
Grid |
Parent grid |
cell.normalized_position |
RelCoord |
RelCoord(rx, ry) normalized to 0.0-1.0 within grid |
Neighbors¶
| Property | Returns | Direction |
|---|---|---|
cell.above |
Cell \| None |
North |
cell.below |
Cell \| None |
South |
cell.left |
Cell \| None |
West |
cell.right |
Cell \| None |
East |
cell.above_left |
Cell \| None |
Northwest |
cell.above_right |
Cell \| None |
Northeast |
cell.below_left |
Cell \| None |
Southwest |
cell.below_right |
Cell \| None |
Southeast |
cell.neighbors |
dict[str, Cell \| None] |
4 cardinal directions |
cell.neighbors_all |
dict[str, Cell \| None] |
All 8 directions |
Neighbor properties return Cells, not positions
cell.left, cell.right, cell.above, cell.below return Cell | None, not position coordinates. Use cell.center, cell.top_left, etc. for positions.
Sub-Cell Image Sampling¶
For finer-grained access to the original source image within a cell's area:
| Method | Returns | Description |
|---|---|---|
cell.sample_image(rx, ry) |
(int, int, int) |
RGB at relative position within cell |
cell.sample_brightness(rx, ry) |
float |
Brightness at relative position |
cell.sample_hex(rx, ry) |
str |
Hex color at relative position |
Where rx and ry are 0.0-1.0 within the cell (0.5, 0.5 = center).
Utility¶
| Method | Returns | Description |
|---|---|---|
cell.distance_to(other) |
float |
Pixel distance to Cell, Coord, or tuple |
4. The Surface Protocol: Builder Methods¶
Surface is the base class for Cell, Scene, and CellGroup. It provides 12 builder methods that all work identically across these three surfaces.
See also
For creative examples of all builder methods, see Drawing with Entities.
Named Positions¶
All at parameters accept named positions or (rx, ry) relative coordinates:
| Name | Relative | Description |
|---|---|---|
"center" |
(0.5, 0.5) |
Center of surface |
"top_left" |
(0.0, 0.0) |
Top-left corner |
"top_right" |
(1.0, 0.0) |
Top-right corner |
"bottom_left" |
(0.0, 1.0) |
Bottom-left corner |
"bottom_right" |
(1.0, 1.0) |
Bottom-right corner |
"top" |
(0.5, 0.0) |
Top center |
"bottom" |
(0.5, 1.0) |
Bottom center |
"left" |
(0.0, 0.5) |
Left center |
"right" |
(1.0, 0.5) |
Right center |
Parametric Positioning: along / t / align¶
All builder methods (except add_fill, add_border) support parametric positioning:
along: AnyPathableobject (Line, Curve, Ellipse, Path, Connection, or custom)t: Parameter 0.0 (start) to 1.0 (end) along the pathalign: IfTrue, rotate the entity to follow the path's tangent direction
Killer feature
This is PyFreeform's most powerful concept -- position any element along any path:
See Paths and Parametric Positioning for in-depth examples.Entity-Relative Positioning: within¶
All builder methods (except add_fill, add_border, add_path) support within=:
rect = cell.add_rect(fill="blue", width=0.5, height=0.5)
dot = cell.add_dot(within=rect, at="center", color="red")
When within= is set, all relative coordinates (at, start/end, radius, rx/ry, width/height) are resolved against the referenced entity's bounding box instead of the cell. This is reactive -- if the reference entity moves, dependent entities follow automatically.
The .at Property¶
Every entity has a read/write .at property that returns a RelCoord:
dot = cell.add_dot(at=(0.25, 0.75), color="red")
print(dot.at) # RelCoord(0.25, 0.75)
print(dot.at.rx) # 0.25
dot.at = (0.5, 0.5) # Reposition to center (plain tuples still accepted)
Returns None if the entity was created with pixel coordinates (via place() or direct constructor). See The RelCoord Type for details.
The Binding Dataclass¶
Every entity's full positioning configuration is accessible as a Binding -- a frozen dataclass from pyfreeform.core.binding:
from pyfreeform.core.binding import Binding
# Relative position within the cell
entity.binding = Binding(at=RelCoord(0.25, 0.75))
# Position relative to another entity
entity.binding = Binding(at=RelCoord(0.5, 0.5), reference=rect)
# Position along a path at t=0.3
entity.binding = Binding(along=line, t=0.3)
| Field | Type | Default | Description |
|---|---|---|---|
at |
RelCoord \| None |
None |
Relative position within reference frame |
reference |
Surface \| Entity \| None |
None |
Override reference frame (default: containing cell) |
along |
Pathable \| None |
None |
Path to follow |
t |
float |
0.5 |
Parameter along path (0.0--1.0) |
Modes are mutually exclusive: use at for relative positioning, or along+t for path positioning. The reference field optionally overrides the cell as the frame of reference (this is what within= sets in builder methods).
Relative Sizing Properties¶
Builder methods store sizing as fractions of the reference frame. These are accessible as read/write properties on each entity:
| Entity | Property | Builder default | Description |
|---|---|---|---|
| Dot | relative_radius |
0.05 |
Fraction of min(width, height) |
| Line | relative_start, relative_end |
varies | Start/end as RelCoord fractions |
| Curve | relative_start, relative_end |
varies | Start/end as RelCoord fractions |
| Ellipse | relative_rx, relative_ry |
0.4 |
Fraction of surface width/height |
| Rect | relative_width, relative_height |
0.6 |
Fraction of surface width/height |
| Text | relative_font_size |
0.25 |
Fraction of surface height |
| Polygon | relative_vertices |
varies | List of RelCoord vertex positions |
These return None when the entity is in absolute mode (constructed directly or after a transform resolves them). Setting them switches the entity back to relative mode for that dimension.
Sizing vs geometry
Relative sizing properties (radius, rx/ry, width/height, font_size) are independent of transforms -- rotation doesn't affect how big something is relative to its cell. Relative geometry properties (vertices, start/end) encode positions that transforms resolve to absolute values. Builder methods use entity.is_resolved to guard against setting geometry after a transform.
Complete Builder Reference¶
add_dot¶
add_dot(*, at, within, along, t, radius=0.05, color="black", z_index=0, opacity=1.0, style=DotStyle)
Creates a filled circle. radius is a fraction of the cell's smaller dimension (0.05 = 5%). Default position: center.
add_line¶
add_line(*, start, end, within, along, t, align, width=1, color="black", z_index=0,
cap="round", start_cap, end_cap, opacity=1.0, style=LineStyle)
Creates a line segment. Default: center to center (zero-length).
add_diagonal¶
add_diagonal(*, start="bottom_left", end="top_right", within, along, t, align, width=1,
color="black", z_index=0, cap="round", start_cap, end_cap,
opacity=1.0, style=LineStyle)
Convenience for corner-to-corner lines. Delegates to add_line().
add_curve¶
add_curve(*, start="bottom_left", end="top_right", curvature=0.5, within, along, t, align,
width=1, color="black", z_index=0, cap="round", start_cap, end_cap,
opacity=1.0, style=LineStyle)
Creates a smooth curve between two points. curvature controls how much it bows: 0 = straight, positive = bows left, negative = bows right (relative to the direction from start to end).
add_path¶
Expand full signature
Renders any Pathable as a smooth SVG <path> using cubic Bezier approximation. Supports arcs via start_t/end_t, closed paths with fill, and dual opacity.
add_ellipse¶
Expand full signature
Creates an ellipse. Default radii: 40% of surface dimensions. The ellipse itself is a Pathable -- you can position other elements along it.
add_polygon¶
Expand full signature
Creates a polygon from relative-coordinate vertices (0-1). Use Polygon.hexagon(), Polygon.star(), etc. for common shapes. See Shapes and Polygons for shape classmethods.
add_rect¶
Expand full signature
Creates a rectangle. at specifies the center position. Default size: 60% of surface.
add_text¶
Expand full signature
Creates text. font_size is a fraction of the surface height (0.25 = 25% of cell height). Default: 0.25.
fit=True: Shrink font_size so the rendered text fits within the cell width. Never upsizes — font_size acts as a ceiling. Ignored in path modes (along=).
Two modes with along:
along+t: Position text at a point on the path, optionally align to tangent.alongwithoutt: Warp text along the path using SVG<textPath>(auto-sizes font to fill path).
See Text and Typography for text layout techniques.
add_fill¶
Fill the entire surface with a solid color.
add_border¶
Add a stroke-only border around the surface.
add_point¶
Creates an invisible positional anchor. Points render nothing -- use them as reactive Polygon vertices, connection endpoints, or within= reference positions.
add¶
Add an existing entity to this surface with relative positioning. The entity is moved to the resolved at position. Works for any entity type including EntityGroup.
place¶
Place an entity at its current absolute pixel position (escape hatch). Unlike add(), this does NOT reposition the entity -- it is registered exactly where it already is.
5. Entities: The Drawing Primitives¶
All entities inherit from Entity and share these common capabilities.
See also
For creative examples of each entity type, see Drawing with Entities.
Entity Base Class¶
| Property/Method | Description |
|---|---|
entity.position |
Current position (Coord) -- computed from relative coords if set |
entity.x, entity.y |
Position coordinates (lazily resolved) |
entity.at |
Read/write relative position as RelCoord(rx, ry), or None if in absolute mode |
entity.binding |
Read/write positioning config as a Binding dataclass (see Binding below) |
entity.rotation |
Accumulated rotation in degrees (default 0.0). Non-destructive -- stored, not baked. |
entity.scale_factor |
Accumulated scale multiplier (default 1.0). Non-destructive -- stored, not baked. |
entity.rotation_center |
Coord -- the natural pivot for rotation/scale. Default: self.position. Overridden by Rect (center), Polygon (centroid), Line/Curve (chord midpoint), Path (Bezier midpoint). |
entity.is_resolved |
Read-only bool -- True after a transform with origin resolves all relative properties (position, sizing, geometry) to absolute values. Builder methods use this to decide whether to store relative properties. |
entity.z_index |
Layer ordering (higher = on top) |
entity.cell |
Containing Surface (if placed) |
entity.connections |
Set of connections involving this entity |
entity.data |
Custom data dictionary |
entity.ref_frame() |
Returns (x, y, width, height) -- bounding box as a reference frame. Unified interface used by both Entity and Surface for resolving relative coordinates. |
entity.offset_from(anchor, dx, dy) |
Returns Coord at the named anchor position offset by (dx, dy) pixels. |
Movement¶
| Method | Description |
|---|---|
entity.move_to_cell(cell, at="center") |
Move to position within a cell |
Transforms¶
| Method | Description |
|---|---|
entity.rotate(angle, origin=None) |
Rotate in degrees (counterclockwise) |
entity.scale(factor, origin=None) |
Scale (2.0 = double size) |
entity.fit_to_cell(scale=1.0, recenter=True, *, at=None, visual=True, rotate=False, match_aspect=False) |
Auto-scale to fit within containing cell. rotate=True finds optimal rotation for maximum fill. match_aspect=True rotates to match the cell's aspect ratio. |
entity.fit_within(target, scale=1.0, recenter=True, *, at=None, visual=True, rotate=False, match_aspect=False) |
Auto-scale to fit within another entity's inner bounds. Same rotate and match_aspect options. |
See Transforms and Layout for detailed transform examples.
Relationships¶
| Method | Description |
|---|---|
entity.connect(other, style, start_anchor, end_anchor) |
Create a Connection to another entity |
entity.anchor(name) |
Get named anchor point |
entity.anchor_names |
List of available anchor names |
entity.place_beside(other, side="right", gap=0) |
Position beside another entity using bounding boxes |
Abstract Methods (implemented by each entity type)¶
| Method | Description |
|---|---|
entity.to_svg() |
Render to SVG element string |
entity.bounds(*, visual=False) |
Bounding box: (min_x, min_y, max_x, max_y). Pass visual=True to include stroke width in the bounds. |
entity.inner_bounds() |
Inscribed rectangle (default: same as bounds) |
entity.rotated_bounds(angle, *, visual=False) |
Tight AABB of this entity rotated by angle degrees around the origin. Default: rotates 4 bounds() corners. Override with exact analytical formulas (Bezier extrema, ellipse extents, etc.) for tighter bounds. Used by EntityGroup.bounds() to compose tight group bounds. |
5a. Dot¶
A filled circle. The simplest entity.
- Anchors:
"center" color=parameter (notfill=)inner_bounds()returns inscribed square
5b. Line¶
A line segment between two points. Implements the Pathable protocol.
Line(x1, y1, x2, y2, width=1, color="black", z_index=0, cap="round",
start_cap=None, end_cap=None, opacity=1.0)
Line.from_points(start, end, ...)
- Anchors:
"start","center","end" - Pathable:
line.point_at(t)returns a point along the line - Properties:
line.start,line.end,line.length - Methods:
line.set_endpoints(start, end),line.arc_length(),line.angle_at(t),line.to_svg_path_d() - Cap values:
"round","square","butt","arrow","arrow_in"
5c. Curve¶
A smooth curve between two points. Implements the Pathable protocol.
Curve(x1, y1, x2, y2, curvature=0.5, width=1, color="black", z_index=0,
cap="round", start_cap=None, end_cap=None, opacity=1.0)
Curve.from_points(start, end, curvature=0.5, ...)
- Anchors:
"start","center","end","control" - Pathable:
curve.point_at(t)returns a point along the Bezier curve - Curvature: 0 = straight, positive = bows left, negative = bows right, typical range -1 to 1
- Properties:
curve.start,curve.end,curve.curvature,curve.control - Methods:
curve.arc_length(),curve.angle_at(t),curve.to_svg_path_d()
See Paths and Parametric Positioning for Bezier curve techniques.
5d. Ellipse¶
An ellipse (oval). Implements the Pathable protocol.
Ellipse(x, y, rx=10, ry=10, rotation=0, fill="black", stroke=None,
stroke_width=1, z_index=0, opacity=1.0, fill_opacity=None, stroke_opacity=None)
Ellipse.at_center(center, rx, ry, ...)
- Anchors:
"center","right","top","left","bottom" - Pathable:
ellipse.point_at(t)-- t=0 rightmost, t=0.25 top, t=0.5 left, t=0.75 bottom fill=parameter (notcolor=)- Dual opacity:
fill_opacityandstroke_opacityoverrideopacity - Methods:
point_at_angle(degrees),arc_length(),angle_at(t),to_svg_path_d(),inner_bounds()
5e. Polygon¶
A closed polygon from vertices. Includes shape classmethods for common shapes.
Polygon(vertices, fill="black", stroke=None, stroke_width=1, z_index=0,
opacity=1.0, fill_opacity=None, stroke_opacity=None)
- Anchors:
"center"+"v0","v1","v2", ... fill=parameter (notcolor=)- Dual opacity:
fill_opacityandstroke_opacity - Position is the center of the shape (average of all vertices)
Entity-Reference Vertices¶
Vertices can be static coordinates or entity references:
| Vertex type | Example | Behavior |
|---|---|---|
(x, y) tuple or Coord |
(50, 100) |
Static — moves with polygon transforms |
Entity |
Point(50, 100) |
Reactive — tracks entity's .position |
(Entity, "anchor") |
(rect, "top_right") |
Reactive — tracks entity's named anchor |
Entity-reference vertices are resolved at render time. When the referenced entity moves, the polygon deforms automatically.
Transforms and entity vertices
polygon.rotate() and polygon.scale() only affect static (Coord) vertices. Entity-reference vertices follow their entity, not polygon transforms.
See also
For all shape classmethods and polygon techniques, see Shapes and Polygons.
Shape Classmethods¶
All return list[tuple[float, float]] in relative coordinates (0-1), ready for add_polygon():
| Method | Description |
|---|---|
Polygon.triangle(size=1.0, center=(0.5, 0.5)) |
Equilateral triangle (pointing up) |
Polygon.square(size=0.8, center=(0.5, 0.5)) |
Axis-aligned square |
Polygon.diamond(size=0.8, center=(0.5, 0.5)) |
Rotated square (45 degrees) |
Polygon.hexagon(size=0.8, center=(0.5, 0.5)) |
Regular hexagon |
Polygon.star(points=5, size=0.8, inner_ratio=0.4, center=(0.5, 0.5)) |
Star with N points |
Polygon.regular_polygon(sides, size=0.8, center=(0.5, 0.5)) |
Regular N-gon |
Polygon.squircle(size=0.8, center=(0.5, 0.5), n=4, points=32) |
Superellipse (n=2 circle, n=4 squircle) |
Polygon.rounded_rect(size=0.8, center=(0.5, 0.5), corner_radius=0.2, points_per_corner=8) |
Rectangle with rounded corners |
5f. Rect¶
A rectangle with optional rotation.
Rect(x, y, width, height, fill="black", stroke=None, stroke_width=1,
rotation=0, z_index=0, opacity=1.0, fill_opacity=None, stroke_opacity=None)
Rect.at_center(center, width, height, rotation=0, ...)
- Anchors:
"center","top_left","top_right","bottom_left","bottom_right","top","bottom","left","right" fill=parameter (notcolor=)- Dual opacity:
fill_opacityandstroke_opacity x, yis top-left corner;Rect.at_center()positions by center- Rotation: stored as
rotationattribute, emits SVGtransform="rotate()" - Rotation-aware anchors (anchors account for rotation angle)
5g. Text¶
A text label with rich formatting.
Expand full constructor
font_sizein the constructor is pixels (16 = 16px). Inadd_text(), it's a fraction of surface height (0.25 = 25%).- Anchors:
"center" color=parameter- Sugar:
bold=Truesetsfont_weight="bold",italic=Truesetsfont_style="italic" - Text alignment:
text_anchor="start"/"middle"/"end",baseline="auto"/"middle"/"hanging"/ etc. - Rotation:
rotationattribute, SVGtransform="rotate()" - Bounds: Uses Pillow for accurate font measurement; heuristic fallback
fit_to_cell(fraction): Scales font up or down so text fills the cell atfraction(likeEntityGroup.fit_to_cell)text.has_textpath: Read-onlybool--Trueif the text renders along a path (textPath mode)- TextPath:
text.set_textpath(path_id, path_d, start_offset, text_length)for warping along paths
See Text and Typography for text layout and textpath examples.
5h. Path¶
Renders any Pathable as a smooth SVG path. Implements the Pathable protocol itself.
Expand full constructor
Path(
pathable, # Any object with point_at(t)
*,
segments=64, # Number of cubic Bezier segments
closed=False, # Close path smoothly (enables fill)
start_t=0.0, # Start parameter (for arcs/sub-paths)
end_t=1.0, # End parameter (for arcs/sub-paths)
width=1, # Stroke width
color="black", # Stroke color
fill=None, # Fill color (only if closed)
z_index=0,
cap="round",
start_cap=None,
end_cap=None,
opacity=1.0,
fill_opacity=None,
stroke_opacity=None,
)
- Anchors:
"start","center","end" - Pathable:
path.point_at(t)evaluates the stored Bezier segments - Algorithm: Smooth curve fitting — no sharp corners between segments (Hermite-to-cubic-Bezier with C1 continuity)
- Sub-paths: Use
start_t/end_tto render a portion of any path (e.g., quarter of an ellipse) - Methods:
arc_length(),angle_at(t),to_svg_path_d()
See Paths and Parametric Positioning for path rendering techniques.
5i. EntityGroup¶
A reusable composite entity. Children positioned relative to (0,0), rendered as SVG <g>.
group.add(entity): Add child (positioned relative to local origin)group.children: List of children (copy)group.rotate(angle, origin=None): Accumulate rotation (degrees). Withorigin, also orbits position.group.scale(factor, origin=None): Accumulate scale factor.group.rotation: Read-onlyfloat-- current accumulated rotation angle in degrees.group.scale_factor: Read-onlyfloat-- current cumulative scale factor.group.opacity: Group-level opacity (applies to entire<g>element)- Placement:
cell.add(group)-- centers in cell - Fitting:
group.fit_to_cell(fraction)-- auto-scales to fit cell bounds - SVG:
<g transform="translate(x,y) rotate(r) scale(s)" opacity="o">-- children never mutated - Reuse: Wrap creation in a factory function; each call returns new instance
EntityGroup vs CellGroup
EntityGroup inherits Entity and is used for reusable composite shapes. CellGroup inherits Surface and represents merged multi-cell regions. They serve different purposes.
See Transforms and Layout for EntityGroup composition patterns.
5j. Point¶
An invisible positional entity. Renders no SVG — used as a movable anchor for reactive polygon vertices or connection endpoints.
# Direct constructor (pixel coordinates)
Point(x=0, y=0, z_index=0)
# Builder method (relative coordinates — preferred)
point = cell.add_point(at=(0.25, 0.75))
- Anchors:
"center" - SVG output: None (empty string)
- Bounds: Zero-size at position
- Key use: Pass to
Polygon()as a vertex — the polygon tracks the Point's position at render time
# Reactive polygon with add_point
a = cell.add_point(at=(0.5, 0.1))
b = cell.add_point(at=(0.9, 0.9))
c = cell.add_point(at=(0.1, 0.9))
tri = Polygon([a, b, c], fill="coral")
b.at = (0.8, 0.3) # triangle vertex moves with it
See Reactive Polygons for shared vertices and anchor tracking examples.
6. The Pathable Protocol¶
The Pathable protocol defines a single method:
Where t ranges from 0.0 (start) to 1.0 (end). This enables the along/t parametric positioning system.
See also
For a deep dive into parametric positioning, see Paths and Parametric Positioning.
Built-in Pathables¶
| Entity | Description |
|---|---|
Line |
Linear interpolation from start to end |
Curve |
Smooth curve with adjustable bow |
Ellipse |
Parametric ellipse (t=0 right, t=0.25 top, t=0.5 left, t=0.75 bottom) |
Path |
Evaluates stored cubic Bezier segments |
Connection |
Dynamic path between entities |
Optional Pathable Methods¶
These methods enable additional features when present:
| Method | Used By | Description |
|---|---|---|
arc_length() |
add_text(along=) |
Total path length for text sizing |
angle_at(t) |
get_angle_at() |
Tangent angle for alignment |
to_svg_path_d() |
add_text(along=) |
SVG path for <textPath> warping |
Built-in Path Shapes¶
Ready-to-use pathable classes, accessible as nested classes on Path. All four inherit from PathShape (pyfreeform.paths.base), which provides shared arc_length() and to_svg_path_d() implementations — subclasses only need to implement point_at(t) and angle_at(t).
| Shape | Description | Parameters |
|---|---|---|
Path.Wave(start, end, amplitude, frequency) |
Sinusoidal wave | Defaults to (0,0)->(1,0), amplitude=0.15, frequency=2 |
Path.Spiral(center, start_radius, end_radius, turns) |
Archimedean spiral | Defaults to center (0,0), start_radius=0, end_radius=50, turns=3 |
Path.Lissajous(center, a, b, delta, size) |
Lissajous curve | Defaults to center (0,0), a=3, b=2, delta=pi/2, size=50 |
Path.Zigzag(start, end, teeth, amplitude) |
Triangle wave | Defaults to (0,0)->(1,0), teeth=5, amplitude=0.12 |
All four implement the full Pathable interface: point_at(t), angle_at(t), arc_length(), and to_svg_path_d().
# As a standalone path
spiral = Path.Spiral(center=cell.center, end_radius=40, turns=3)
cell.add_path(spiral, width=1.5, color="coral")
# As a connection shape
wave = Path.Wave(amplitude=0.15, frequency=3)
conn = dot_a.connect(dot_b, shape=Path(wave), style=style)
Custom Path Shapes¶
To create a new path shape, inherit from PathShape (pyfreeform.paths.base) and implement point_at(t) and angle_at(t). The base class provides arc_length() and to_svg_path_d() automatically. See Creating Custom Entities for a walkthrough.
Custom Pathables¶
Any object with point_at(t: float) -> Coord works as a path. See the Pathable Protocol for a full walkthrough.
7. Connections¶
Links between entities that auto-update when entities move. By default, connections are invisible — they encode a relationship without rendering anything. Pass a shape to give them visual form.
Connection(start, end, start_anchor="center", end_anchor="center",
style=None, shape=None, segments=32)
Or via the entity method:
connection = entity1.connect(
entity2,
style=ConnectionStyle(...),
start_anchor="center",
end_anchor="center",
shape=Line(), # or Curve(), Path(pathable), or None
segments=32,
)
Shape Options¶
| Shape | SVG Output | Notes |
|---|---|---|
None (default) |
Nothing (to_svg() returns "") |
Pure relationship — supports point_at(t) |
Line() |
<line> element |
Straight connection |
Curve(curvature=0.3) |
Single cubic Bezier <path> |
Arc; curvature controls bow direction and amount |
Path(pathable) |
Fitted Bezier <path> |
Any Pathable — wave, spiral, custom shape |
Shape coordinates are auto-mapped
Shape objects define a template curve (e.g. Line() defaults to (0,0)→(1,0)). The shape is automatically stretched and rotated to connect the actual anchor positions at render time (affine transform).
styleacceptsConnectionStyleordictwithwidth,color,z_index,capkeysshape=None(default) = invisible —point_at(t)still works (linear interpolation between anchors)segmentscontrols Bezier fitting resolution forPathshapes (default 32; ignored for Line/Curve)- Added to scene via
scene.add_connection(connection)(entity.connect()does not auto-add) - Supports cap system (arrow, arrow_in, custom) on all visible shapes
- Closed paths (
Path(pathable, closed=True)) cannot be used as shapes — raisesValueError
Connections must be added to the scene
Calling entity.connect(other) creates a Connection object but does not add it to the scene. You must call scene.add_connection(connection) separately.
8. Transforms and Fitting¶
See also
For hands-on transform examples including fit_to_cell and fit_within, see Transforms and Layout.
Entity Transforms¶
All entities support non-destructive transforms -- rotation and scale are stored as numbers and applied at render time via SVG transform, not baked into geometry:
rotate(angle, origin)-- Accumulatesrotation(degrees). Withoutorigin: stores angle only, relative properties preserved. Withorigin: resolves all relative properties (position, sizing, geometry) to absolute values, then orbits position around origin.scale(factor, origin)-- Accumulatesscale_factor(multiplier). Withoutorigin: stores factor only, relative properties preserved. Withorigin: resolves all relative properties to absolute values, then scales position distance from origin.
Properties after transforms:
- Model-space properties (
.radius,.width,.end,.vertices) are unchanged by transforms - World-space methods (
anchor(),bounds(),point_at()) apply rotation and scale automatically to_svg()emits model-space coordinates +transform="translate(cx,cy) rotate(R) scale(S) translate(-cx,-cy)"
Per-entity pivot points (rotation_center):
- Dot, Ellipse, Text: center (position)
- Rect: center of rectangle
- Line, Curve: chord midpoint (start↔end)
- Polygon: centroid of vertices
- Path: Bezier midpoint at t=0.5
- EntityGroup: accumulates in
<g>transform (children unchanged)
fit_within(target, scale=1.0, recenter=True, *, at=None, visual=True, rotate=False, match_aspect=False)¶
The core fitting method. Scales and positions any entity to fit within a target region.
target: AnEntity(usesinner_bounds()) or a raw(min_x, min_y, max_x, max_y)tuple.scale: Fraction of target to fill (0.0-1.0). Default 1.0 fills entire area.recenter: If True, centers entity within target after scaling.at=(rx, ry): Position-aware mode. Entity is placed at relative position within target, with available space constrained by nearest edges.visual: If True (default), includes stroke width when measuring bounds.rotate: If True, finds the rotation angle that maximizes fill before scaling. Uses a closed-form O(1) solution (3 candidate angles). Works on any entity type.match_aspect: If True, rotates the entity so its bounding box aspect ratio matches the target's. Mutually exclusive withrotate.
fit_to_cell(scale=1.0, recenter=True, *, at=None, visual=True, rotate=False, match_aspect=False)¶
Convenience wrapper around fit_within() that uses the containing cell's bounds as the target. All parameters are forwarded directly.
Raises ValueError if the entity has no cell. Raises TypeError if at is a string (named positions sit on cell edges where available space is 0).
For Text entities, fit_to_cell(fraction) adjusts font size (up or down) to fill the cell at fraction. Compare with add_text(fit=True) which only shrinks — never upsizes.
9. Color and Styling¶
See also
For creative styling techniques and palette usage, see Colors, Styles, and Palettes.
The Color Parameter Split¶
Critical API distinction: fill= vs color=
This is the most common source of errors. Using the wrong parameter will raise a TypeError.
| Parameter | Used by | Entity property |
|---|---|---|
color= |
Dot, Line, Curve, Text, add_dot, add_line, add_curve, add_text, add_fill, add_border, all style classes | .color |
fill= |
Rect, Ellipse, Polygon, add_rect, add_ellipse, add_polygon, Path (for closed paths) | .fill |
ShapeStyle.color maps to fill= when applied to shapes.
Color Formats¶
The Color utility accepts:
- Named colors:
"red","coral","navy", etc. - Hex:
"#ff0000","#f00","#FF0000" - RGB tuple:
(255, 0, 0)
Opacity System¶
opacityon every entity and style: 0.0 (transparent) to 1.0 (opaque). Default 1.0 emits no SVG attribute.fill_opacity/stroke_opacityon shapes (Rect, Ellipse, Polygon, Path, ShapeStyle): optional overrides for independent control.- Simple entities (Dot, Line, Curve, Text, Connection): SVG
opacityattribute. - Shapes: SVG
fill-opacity+stroke-opacityattributes.
Style Classes¶
7 dataclasses with .with_*() builder methods (each is a one-liner using dataclasses.replace()):
| Class | For | Key Fields |
|---|---|---|
DotStyle |
add_dot() |
color, z_index, opacity |
LineStyle |
add_line(), add_diagonal(), add_curve(), add_path() |
width, color, z_index, cap, start_cap, end_cap, opacity |
FillStyle |
add_fill() |
color, opacity, z_index |
BorderStyle |
add_border() |
width, color, z_index, opacity |
ShapeStyle |
add_ellipse(), add_polygon(), add_rect() |
color, stroke, stroke_width, z_index, opacity, fill_opacity, stroke_opacity |
TextStyle |
add_text() |
color, font_family, bold, italic, text_anchor, baseline, rotation, z_index, opacity |
ConnectionStyle |
Connection, entity.connect() |
width, color, z_index, cap, start_cap, end_cap, opacity |
Example builder pattern:
base_style = LineStyle(width=2, color="coral")
thick_style = base_style.with_width(4)
arrow_style = base_style.with_end_cap("arrow")
Palettes¶
8 pre-built color palettes with 6 named colors each:
| Palette | Background | Vibe |
|---|---|---|
Palette.midnight() |
#1a1a2e |
Dark blue with coral accent |
Palette.sunset() |
#2d1b4e |
Warm oranges and purples |
Palette.ocean() |
#0a1628 |
Cool blues and teals |
Palette.forest() |
#1a2e1a |
Natural greens and earth |
Palette.monochrome() |
#0a0a0a |
Black, white, grays |
Palette.paper() |
#fafafa |
Light, clean, minimalist |
Palette.neon() |
#0d0d0d |
Vibrant neon electric |
Palette.pastel() |
#fef6e4 |
Soft, gentle pastels |
Named colors: background, primary, secondary, accent, line, grid.
Methods: with_background(color), inverted(), all_colors(), iteration.
10. Cap System¶
Line/Curve/Connection endpoints support custom caps:
| Built-in Cap | Description |
|---|---|
"round" |
Standard SVG round cap |
"square" |
SVG square cap |
"butt" |
SVG butt cap (flat) |
"arrow" |
Arrowhead marker at endpoint |
"arrow_in" |
Inward-pointing arrowhead |
Per-end caps: start_cap and end_cap override the base cap:
Custom caps via registry:
11. The Coord Type¶
Coord is a NamedTuple with x and y fields. Subscriptable: point[0], point[1].
| Method | Description |
|---|---|
Coord(x, y) |
Create a point |
point.x, point.y |
Access coordinates |
point[0], point[1] |
Subscript access |
point.distance_to(other) |
Euclidean distance |
point.midpoint(other) |
Midpoint between two points |
point.lerp(other, t) |
Linear interpolation |
point.normalized() |
Unit vector in same direction (zero vector if length is 0) |
point.dot(other) |
Dot product with another coord (as 2D vectors) |
point.rotated(angle, origin=None) |
Rotate around origin. angle is in radians. Default origin: (0, 0) |
point.clamped(min_x, min_y, max_x, max_y) |
Return coord clamped to bounds |
point.rounded(decimals=0) |
Round coordinates to N decimal places |
point.as_tuple() |
Return as plain (float, float) |
Coord.coerce(value) |
Convert (x, y) tuple or Coord-like to Coord |
point + Coord(dx, dy) |
Addition |
point - Coord(dx, dy) |
Subtraction |
12. The RelCoord Type¶
RelCoord is a NamedTuple with rx and ry fields representing relative fractions (0.0--1.0) within a surface. It is the type returned by .at and used throughout the relative-positioning system.
Like Coord, it is subscriptable (rc[0], rc[1]) and destructurable:
rc = RelCoord(0.25, 0.75)
rx, ry = rc # destructure
print(rc.rx, rc.ry) # named access
print(rc[0], rc[1]) # index access
Arithmetic¶
RelCoord supports the same arithmetic operators as Coord:
a = RelCoord(0.2, 0.3)
b = RelCoord(0.1, 0.1)
a + b # RelCoord(0.3, 0.4)
a - b # RelCoord(0.1, 0.2)
a * 2 # RelCoord(0.4, 0.6)
a / 2 # RelCoord(0.1, 0.15)
-a # RelCoord(-0.2, -0.3)
Methods¶
| Method | Description |
|---|---|
RelCoord(rx, ry) |
Create a relative coordinate |
rc.rx, rc.ry |
Access fractions |
rc[0], rc[1] |
Subscript access |
rc.lerp(other, t) |
Linear interpolation |
rc.clamped(min_rx=0, min_ry=0, max_rx=1, max_ry=1) |
Clamp to valid range (default 0.0--1.0) |
rc.as_tuple() |
Return as plain (float, float) |
RelCoord.coerce(value) |
Convert (rx, ry) tuple or RelCoord-like to RelCoord |
Where RelCoord Appears¶
| API | Usage |
|---|---|
entity.at |
Returns RelCoord (or None if in absolute mode) |
add_*(..., at=) |
Accepts RelCoord, plain tuple, or named position string |
cell.normalized_position |
Returns RelCoord (cell position within grid, 0.0--1.0) |
surface.absolute_to_relative(point) |
Returns RelCoord |
Backward compatible
All APIs that accept RelCoord also accept plain (rx, ry) tuples -- existing code continues to work unchanged. The difference is that returned values are now RelCoord instances with named fields and helper methods.
13. Image Processing¶
Image Class¶
| Method/Property | Description |
|---|---|
Image.load(path) |
Load from file |
Image.from_pil(pil_image) |
Create from PIL Image |
image.width, image.height |
Dimensions |
image.has_alpha |
Whether image has alpha channel |
image["brightness"] |
Get brightness Layer |
image["red"], ["green"], ["blue"], ["alpha"] |
Channel layers |
image.rgb_at(x, y) |
RGB at pixel position |
image.hex_at(x, y) |
Hex color at pixel position |
image.resize(width, height) |
Resize image |
image.fit(max_dim) |
Fit within max dimension |
image.quantize(levels) |
Reduce to N levels |
image.downscale(factor) |
Downscale by factor |
See also
For image-to-art workflows, see Image to Art.
Layer Class¶
A single-channel grayscale array (used for brightness, individual color channels, etc.):
| Property/Method | Description |
|---|---|
layer.width, layer.height |
Dimensions |
layer[x, y] |
Value at position (0-255) |
14. Utility Functions¶
map_range(value, in_min=0, in_max=1, out_min=0, out_max=1, clamp=False)¶
Convert a value from one range to another — like converting between units. If brightness goes from 0 to 1 but you want a radius between 2 and 10:
from pyfreeform import map_range
radius = map_range(cell.brightness, 0, 1, 2, 10)
# brightness 0.0 → radius 2
# brightness 0.5 → radius 6
# brightness 1.0 → radius 10
# Swap the output range to reverse the direction
radius = map_range(cell.brightness, 0, 1, 10, 2) # dark = big, bright = small
# Works with any range — here, pixel position (0-800) to rotation (0-360)
rotation = map_range(cell.center.x, 0, 800, 0, 360)
Set clamp=True to keep the result within the output range even if the input is outside its range.
get_angle_at(pathable, t)¶
Compute the tangent angle at parameter t on a Pathable. Used internally by the align system.
display(scene_or_svg)¶
Display an SVG in the current environment (Jupyter notebook, etc.).
15. Relationship Map¶
Key Design Principles¶
- Surface protocol: Cell, Scene, CellGroup all share identical
add_*methods - Pathable protocol: Position anything along anything with
along/t fill=vscolor=: Shapes usefill, everything else usescolor- Immutable styles: Style classes with
.with_*()builder methods - z_index layering: Higher values render on top, same values preserve add-order
- Everything returns self: Transform methods chain:
entity.rotate(45).scale(0.5)