How to Implement Pinned Constraints in ppf-contact-solver: Vertex Groups and Animation Methods
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.
PinHolder and PinData
The PinHolder class (lines 35–46 in 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
Operationobjects - Optional metadata including
unpin_time,pull_strength, andpin_group_id
Operation Classes
All transformations inherit from the abstract Operation class (lines 40–62). Concrete implementations include:
MoveByOperationMoveToOperationSpinOperationScaleOperationTorqueOperationTransformKeyframeOperation
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:
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:
# 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.
# 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:
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 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.
# 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):
group.create_pin("MyCurve", "left", indices=[0, 7, 14])
# Creates custom property "_pin_left" containing JSON array
Method B: Pre-populate the custom property:
# Ensure obj["_pin_left"] exists with JSON array of integers
group.create_pin("MyCurve", "left")
Complete Implementation Examples
Python-Only Script
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
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
PinHolderinfrontend/_scene_pin_.pyis 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 orGroup.create_pin()(defined inblender_addon/ops/api/group.pylines 47–78) for Blender integration. - Serialization converts the Python
PinDatastructure 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) 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. 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →