V0.10.0: the "Shape" update
# Changelog
## Shapes are now iterable objects with a structured “elements” list
- **`shape.elements` – new way to inspect or edit a drawing**
- Every `Shape` now owns a list called `elements`;
- Each item is a **`ShapeElement`** object holding **one drawing command** (`m`, `l`, `b`, …) plus its coordinates;
- This replaces the old “raw string hacking”: you can iterate the list, index it, or modify it in-place while keeping everything in sync.
- **`for elem in shape:` shape iteration**
- `Shape.__iter__` returns an iterator over the elements in a shape, yielding the *individual segments* of the outline in the same order they appear in the ASS string;
- Implicit continuations after an `l` or `b` are split automatically, so every segment is explicit and self-contained.
- **`drawing_cmds` is still there – it’s just smarter**
- It became a *computed* read-only property generated on-the-fly from the `elements` list.
### Examples
```python
shape = Shape("m 0 0 l 10 0 10 10")
for part in shape:
print(part)
# → ShapeElement('m', [Point(0, 0)])
# → ShapeElement('l', [Point(10, 0)])
# → ShapeElement('l', [Point(10, 10)])
```
## Native Shapely bridges – `to_multipolygon()` ↔ `from_multipolygon()`
- `shape.to_multipolygon()` converts your ASS outline into a Shapely MultiPolygon (curves will be flattened);
- `Shape.from_multipolygon(mp)` travels the other way, rebuilding a Shape from any MultiPolygon you give it.
- This is a huge step for PyonFX, since Shapely is the de-facto Python toolkit for planar geometry.
## Counterparts for ASS tags now available for Shape objects
- `align(an=5, anchor=None)` (contributed by @Funami580): mirrors \an;
- `scale(fscx=100, fscy=100, origin=(0, 0))`: mirrors \fscx/\fscy;
- `rotate(frx=0, fry=0, frz=0, origin=(0, 0))`: mirrors \frx, \fry, \frz;
- `shear(fax=0, fay=0, origin=(0, 0))`: mirrors \fax/\fay;
## New geometry operations: `boolean()` and `buffer()`
- `boolean(other, op, tolerance=1, min_point_spacing=0.5)`: combines this shape with another via the classic set operators you already know from vector tools:
- *union* → merge both outlines;
- *intersection* → keep only the overlap;
- *difference* → subtract other from this;
- *xor* → areas that belong to one or the other, but not both.
- `buffer(dist_xy, dist_y=None, kind='border'|'fill', join='round'|'bevel'|'mitre')`: recreates \bord and “fake-fill” tricks at outline level:
- grow (`dist_xy` > 0) or shrink (`dist_xy` < 0) the outline.
- `kind='border'` (default) returns only the stroke ring, while `kind='fill'` returns the full, bloated outline.
## Smooth shape transitions with `morph()`
- `shape.morph(target, t)`: returns a new Shape that is an intermediate form between shape (when t=0) and target (when t=1). Any t in the 0 – 1 range is allowed, so you can animate the morph over time by stepping t frame-by-frame.
- Under the hood every outline is flattened, decomposed into compound loops (shells & holes) and then paired by similarity (distance + area + overlap heuristics).

## `Shape.polygon(edges, side_length)`: one shape to rule them all
- A single factory covers every regular shape: pass the number of edges (≥ 3) and a side_length, get a perfectly centred, unit-thick outline;
- The specialised helpers `triangle()` and `rectangle()`/`square()` have been retired.
## Other improvements / changes
- `bounding(exact=True)` (contributed by @Funami580): passing the new *exact=True* can be used to obtain boxes that hugs the curve with sub-pixel accuracy. Not passing *exact* or passing *exact=False* still returns the VsFilter/Libass bounding results;
- Curve flattening (`.flatten()`) and segmentation (`.split()`) were re-implemented for numerical stability and speed;
- `.map()` was re-implemented to use the new iterator of `ShapeElement`;
合并状态:未合并 0 条评论