# How to Implement Pinned Constraints in ppf-contact-solver: Vertex Groups and Animation Methods

> Implement pinned constraints in ppf-contact-solver using PinHolder and transformation operations. Learn about supported vertex groups and animation methods for precise control.

- Repository: [ZOZO, Inc./ppf-contact-solver](https://github.com/st-tech/ppf-contact-solver)
- Tags: how-to-guide
- Published: 2026-05-27

---

**You implement pinned constraints in ppf-contact-solver by instantiating a `PinHolder`—via `scene.add().pin()` or `Group.create_pin()`—chaining transformation operations like `move_by()` or `spin()`, and targeting either mesh vertex groups or curve control-point indices, which the Rust kernel then validates and serializes to TOML.**

The st-tech/ppf-contact-solver provides a robust Python front-end and Blender integration for creating pinned constraints that animate specific vertices during physics simulation. Understanding how to implement pinned constraints requires knowledge of the `PinHolder` builder pattern and the specific vertex group types supported by the solver's Rust back-end.

## Understanding the Pinned Constraint Architecture

The constraint system revolves around three core abstractions defined in [`frontend/_scene_pin_.py`](https://github.com/st-tech/ppf-contact-solver/blob/main/frontend/_scene_pin_.py).

### PinHolder and PinData

The **`PinHolder`** class (lines 35–46 in [`frontend/_scene_pin_.py`](https://github.com/st-tech/ppf-contact-solver/blob/main/frontend/_scene_pin_.py)) acts as the high-level builder that records pinning operations on a set of vertex indices. It mirrors a Rust validator and emits a TOML description for the solver during scene compilation.

The **`PinData`** dataclass stores:
- The target vertex indices
- A list of `Operation` objects
- Optional metadata including `unpin_time`, `pull_strength`, and `pin_group_id`

### Operation Classes

All transformations inherit from the abstract **`Operation`** class (lines 40–62). Concrete implementations include:

- **`MoveByOperation`**
- **`MoveToOperation`**
- **`SpinOperation`**
- **`ScaleOperation`**
- **`TorqueOperation`**
- **`TransformKeyframeOperation`**

Each operation contains parameters for the transformation and forwards work to the Rust kernel via `_rust.scene_…_apply` (lines 64–226).

## How to Implement Pinned Constraints

### Step 1: Create a Pin Holder

You can obtain a `PinHolder` through two primary APIs according to your environment.

**From the pure-Python front-end:**

```python
from ppf_contact_solver.frontend import Scene

scene = Scene()
pin = scene.add("sphere").pin()  # Pins all vertices of the object

```

**From the Blender add-on:**

```python

# From blender_addon/ops/api/group.py lines 47-78

group = solver.create_group("Cloth", type="SHELL")
pin = group.create_pin("ClothMesh", "collar")  # Pins specific vertex group

```

### Step 2: Chain Animation Operations

Add concrete operations to `PinData.operations` using fluent method calls. Each call invokes the same operation on the Rust mirror (`self._rust`) for validation.

```python

# Move pinned vertices +8 units on X between t=0 and t=5

pin.move_by([8, 0, 0], t_start=0.0, t_end=5.0)

# Spin around Y-axis at 180°/s from t=5 onward

pin.spin(
    center=None,
    axis=[0, 1, 0],
    angular_velocity=180.0,
    t_start=5.0,
    t_end=float("inf"),
)

```

### Step 3: Configure Metadata

Set optional constraint properties before serialization:

```python
pin.pull(strength=2.0)        # Increase pull force

pin.unpin(time=12.0)         # Release at t=12s

pin.interp("bezier")         # Set easing for subsequent ops

```

### Step 4: Serialization

When the scene compiles, `_pin_to_toml_dict` walks the `PinData` objects, converts operations via `_pin_op_to_toml_dict`, and passes the dictionary to `_rust.scene_format_pin_toml` for solver ingestion.

## Supported Vertex Groups

The `Group.create_pin` method in [`blender_addon/ops/api/group.py`](https://github.com/st-tech/ppf-contact-solver/blob/main/blender_addon/ops/api/group.py) accepts two distinct target types.

### Mesh Vertex Groups

For **mesh objects**, provide the name of an existing vertex group from the mesh's `vertex_groups` collection.

```python

# The "Collar" group must exist on the mesh before calling

group.create_pin("MyMesh", "Collar")

```

### Curve Control-Point Groups

For **curve objects**, the system uses custom properties to store pinning indices.

**Method A:** Supply indices directly (creates the property automatically):

```python
group.create_pin("MyCurve", "left", indices=[0, 7, 14])

# Creates custom property "_pin_left" containing JSON array

```

**Method B:** Pre-populate the custom property:

```python

# Ensure obj["_pin_left"] exists with JSON array of integers

group.create_pin("MyCurve", "left")

```

## Complete Implementation Examples

### Python-Only Script

```python
from ppf_contact_solver.frontend import Scene

scene = Scene()
obj = scene.add("cube")
pin = obj.pin()

# Animate: translate 4m on Z, then scale to 0.5

pin.move_by([0, 0, 4], t_start=0.0, t_end=2.0) \
   .scale(0.5, t_start=2.0, t_end=5.0)

scene.save("my_simulation.toml")

```

### Blender Operator

```python
import bpy

solver = bpy.context.scene.zozo_contact_solver

# Create dynamics group

cloth_group = solver.create_group("Cloth", type="SHELL")

# Pin "collar" vertex group and animate

cloth_group.add("ClothMesh") \
           .create_pin("ClothMesh", "collar") \
           .move_by([0, 0, 0.2], t_start=1, t_end=60) \
           .spin(axis=[0, 0, 1], angular_velocity=90, t_start=10, t_end=30)

```

## Summary

- **`PinHolder`** in [`frontend/_scene_pin_.py`](https://github.com/st-tech/ppf-contact-solver/blob/main/frontend/_scene_pin_.py) is the primary interface for building pinned constraints through method chaining.
- **Vertex group support** includes existing mesh vertex groups and curve control-point indices stored as custom properties `"_pin_<name>"`.
- **Available operations** cover linear motion, rotation, scaling, torque, and keyframe-based transformations, each implemented as a concrete subclass of `Operation`.
- **Entry points** differ by environment: use `scene.add().pin()` for pure Python or `Group.create_pin()` (defined in [`blender_addon/ops/api/group.py`](https://github.com/st-tech/ppf-contact-solver/blob/main/blender_addon/ops/api/group.py) lines 47–78) for Blender integration.
- **Serialization** converts the Python `PinData` structure to TOML via Rust validation functions for solver consumption.

## Frequently Asked Questions

### What is the difference between PinHolder and PinData?

**`PinHolder`** is the high-level builder class that provides the fluent API (`move_by`, `spin`, etc.) and manages the Rust mirror instance. **`PinData`** is the underlying dataclass (lines 35–46 in [`frontend/_scene_pin_.py`](https://github.com/st-tech/ppf-contact-solver/blob/main/frontend/_scene_pin_.py)) that actually stores the vertex indices, operations list, and metadata like `unpin_time`. The holder manipulates the data class during scene construction.

### Can I pin individual vertices without creating a vertex group?

Yes, but only when using the curve control-point workflow or the pure Python front-end. For curves, you can pass a specific `indices` list to `Group.create_pin`, which stores them in a custom property. For meshes via the Blender API, you must reference an existing vertex group; however, the underlying `PinHolder` itself works with raw vertex indices, so custom Python scripts using the front-end `Scene` API can pin arbitrary index arrays.

### How do I release pinned vertices during the simulation?

Call the `.unpin(time)` method on your pin instance, which sets the `unpin_time` metadata in `PinData`. When the scene serializes to TOML, this value instructs the Rust solver to release the constraint at the specified simulation time, allowing the vertices to respond to physics freely thereafter.

### Which source file handles the TOML serialization for pins?

The conversion logic resides in [`frontend/_scene_pin_.py`](https://github.com/st-tech/ppf-contact-solver/blob/main/frontend/_scene_pin_.py). Specifically, `_pin_to_toml_dict` iterates over `PinData` objects, while `_pin_op_to_toml_dict` converts each operation instance to its dictionary representation. The final TOML string generation is delegated to `_rust.scene_format_pin_toml` in the Rust kernel.