🐍 Blender Python API Reference¢

Every public class and method listed here is reachable after from zozo_contact_solver import solver. See 🐍 Blender Python API for a narrative walkthrough of the same surface.

Classes:

class SolverΒΆ

Top-level entry point for the ZOZO Contact Solver.

Available as solver when imported via:

from zozo_contact_solver import solver

Scene parameters are accessed via param (a SceneParam proxy). Groups, pins, and invisible colliders are created via the methods below.

Unrecognized attribute access falls through to bpy.ops.zozo_contact_solver.<name>(), so every operator registered under that namespace (including every MCP handler) can be called as a method on solver.

Example:

solver.param.gravity = (0, 0, -9.8)
group = solver.create_group("Sphere", type="SOLID")
group.add("Sphere")
group.param.solid_density = 1000
create_group(name: str = '', type: str = 'SOLID') GroupΒΆ

Create a new dynamics group.

Parameters:
  • name – Display name for the group. Empty string leaves the auto-generated name in place.

  • type – One of "SOLID", "SHELL", "ROD", "STATIC".

Returns:

A Group proxy for the newly created group.

Example:

group = solver.create_group("Shirt", type="SHELL")
group.add("Shirt")
get_group(group_uuid: str) GroupΒΆ

Look up a group by UUID.

Parameters:

group_uuid – UUID string of the group.

Returns:

A Group proxy.

Raises:

KeyError – If the group does not exist.

Example:

uuid = solver.get_groups()[0].uuid
group = solver.get_group(uuid)
get_groups() list[Group]ΒΆ

Return Group proxies for every active group.

Example:

for group in solver.get_groups():
    print(group.uuid)
delete_all_groups() SolverΒΆ

Delete every active group and the pins they own.

Returns:

self for chaining.

Example:

solver.delete_all_groups()
clear() SolverΒΆ

Reset the entire solver state to defaults.

Deletes every active group, resets scene parameters to their property defaults, clears merge pairs, invisible colliders, dynamic parameters, previously fetched frames, saved pin keyframes, and any residual MESH_CACHE modifiers on mesh objects. Call this at the top of any script that needs a clean slate.

Returns:

self for chaining.

Example:

solver.clear()
solver.param.gravity = (0, 0, -9.8)
snap(object_a: str, object_b: str) SolverΒΆ

Translate object_a so its nearest vertex lands on object_b.

Parameters:
  • object_a – Name of the mesh that moves.

  • object_b – Name of the mesh that stays in place.

Returns:

self for chaining.

Raises:

ValueError – If either object is missing, not a mesh, or validation in the underlying mutation service fails.

Example:

solver.snap("Shirt", "Mannequin")
add_merge_pair(object_a: str, object_b: str) SolverΒΆ

Mark two objects to be merged at their shared contact.

Parameters:
  • object_a – Name of the first mesh.

  • object_b – Name of the second mesh.

Returns:

self for chaining.

Raises:

ValueError – If either object is missing, not a mesh, or the pair is invalid.

Example:

solver.add_merge_pair("SleeveLeft", "BodyLeft")
remove_merge_pair(object_a: str, object_b: str) SolverΒΆ

Remove a previously added merge pair.

The ordering of object_a and object_b does not matter; the pair is matched by UUID in either direction.

Parameters:
  • object_a – Name of the first mesh.

  • object_b – Name of the second mesh.

Returns:

self for chaining.

Raises:

ValueError – If validation fails for the given pair.

Example:

solver.remove_merge_pair("SleeveLeft", "BodyLeft")
get_merge_pairs() list[tuple[str, str]]ΒΆ

Return every merge pair as a list of (object_a, object_b) tuples.

Example:

for a, b in solver.get_merge_pairs():
    print(f"{a} <-> {b}")
clear_merge_pairs() SolverΒΆ

Remove every merge pair.

Returns:

self for chaining.

Example:

solver.clear_merge_pairs()
add_wall(position, normal) WallΒΆ

Add an invisible infinite-plane wall collider.

Parameters:
  • position – (x, y, z) world-space point on the plane.

  • normal – (nx, ny, nz) outward-facing plane normal. Need not be unit-length.

Returns:

A chainable Wall builder bound to the newly added collider.

Raises:

ValueError – If the position or normal fails vec3 validation.

Example:

solver.add_wall(position=(0, 0, 0), normal=(0, 0, 1))
add_sphere(position, radius) SphereΒΆ

Add an invisible sphere collider.

Parameters:
  • position – (x, y, z) world-space center.

  • radius – Sphere radius.

Returns:

A chainable Sphere builder bound to the newly added collider.

Raises:

ValueError – If the position or radius fails validation.

Example:

solver.add_sphere(position=(0, 0, 1.0), radius=0.25)
get_invisible_colliders() listΒΆ

Return every invisible collider as a list of (type, name) tuples.

type is one of "WALL" or "SPHERE".

Example:

for kind, name in solver.get_invisible_colliders():
    print(kind, name)
clear_invisible_colliders() SolverΒΆ

Remove every invisible collider.

Returns:

self for chaining.

Example:

solver.clear_invisible_colliders()
class SceneParamΒΆ

Attribute proxy for scene and SSH/connection parameters.

Accessed as Solver.param. Supports both get and set via attribute access. Writes go through the zozo_contact_solver.set operator (with auto type coercion), reads fall through to the scene’s addon state or SSH state.

gravity is an alias for gravity_3d.

Example:

solver.param.step_size = 0.004
print(solver.param.gravity)

Dynamic (keyframed) parameters are accessed via dyn():

solver.param.dyn("gravity").time(60).hold().time(61).change((0, 0, 9.8))
dyn(key: str) DynParamΒΆ

Select a parameter for dynamic keyframing.

Parameters:

key – One of "gravity", "wind", "air_density", "air_friction", "vertex_air_damp".

Returns:

A chainable DynParam builder.

Raises:

ValueError – If key is not one of the valid dynamic keys.

Example:

solver.param.dyn("gravity").time(60).hold().time(61).change((0, 0, 9.8))
class DynParamΒΆ

Fluent builder for dynamic scene parameter keyframes.

Mirrors the frontend session.param.dyn() API but uses frames instead of seconds. Obtained from SceneParam.dyn().

Valid parameter keys: "gravity", "wind", "air_density", "air_friction", "vertex_air_damp".

Frames must be strictly increasing within a chain. Every mutating method returns self so operations chain.

Example:

solver.param.dyn("gravity").time(60).hold().time(61).change((0, 0, 9.8))
solver.param.dyn("wind").time(30).hold().time(31).change((0, 1, 0), strength=5.0)
time(frame: int) DynParamΒΆ

Advance the frame cursor.

Parameters:

frame – Target frame (must be strictly greater than the current cursor position).

Returns:

self for chaining.

Raises:

ValueError – If frame is not strictly increasing.

Example:

solver.param.dyn("gravity").time(60).hold().time(61).change((0, 0, 9.8))
hold() DynParamΒΆ

Hold the previous value at the current cursor frame (step function).

Returns:

self for chaining.

Example:

solver.param.dyn("gravity").time(60).hold().time(61).change((0, 0, 9.8))
change(value, strength=None) DynParamΒΆ

Set a new value at the current cursor frame.

Parameters:
  • value – For "gravity", an (x, y, z) tuple. For "wind", an (x, y, z) direction tuple. For scalar keys ("air_density", "air_friction", "vertex_air_damp"), a float.

  • strength – Wind strength (only for "wind").

Returns:

self for chaining.

Example:

solver.param.dyn("wind").time(30).hold().time(31).change((0, 1, 0), strength=5.0)
clear() DynParamΒΆ

Remove this dynamic parameter entirely.

Returns:

self for chaining (though no further method on this builder will do anything meaningful after clear()).

Example:

solver.param.dyn("wind").clear()
class GroupΒΆ

A dynamics group proxy.

Created via Solver.create_group(). Material parameters are accessed via param. Every mutating method returns self so operations chain.

Example:

group = solver.create_group("Shirt", type="SHELL")
group.add("Shirt").set_overlay_color(0.9, 0.2, 0.1)
group.param.friction = 0.5
group.param.shell_density = 1.0
property uuidΒΆ
Type:

str

The UUID of this group. Stable across renames.

Example:

group = solver.create_group("Shirt", type="SHELL")
same_group = solver.get_group(group.uuid)
property paramΒΆ
Type:

GroupParam

Material and simulation parameter proxy. See GroupParam.

Example:

group.param.friction = 0.5
group.param.shell_density = 1.0
set_overlay_color(r: float, g: float, b: float, a: float = 1.0) GroupΒΆ

Set the viewport overlay color for this group and enable it.

Parameters:
  • r – Red channel in [0, 1].

  • g – Green channel in [0, 1].

  • b – Blue channel in [0, 1].

  • a – Alpha in [0, 1] (default 1.0).

Returns:

self for chaining.

Example:

group.set_overlay_color(0.9, 0.2, 0.1)  # red overlay
add(*object_names: str) GroupΒΆ

Add mesh objects to this group by name.

Parameters:

*object_names – One or more Blender object names.

Returns:

self for chaining.

Example:

group.add("Shirt", "Skirt", "Sleeve")
remove(object_name: str) GroupΒΆ

Remove an object from this group.

Parameters:

object_name – Name of the object to remove.

Returns:

self for chaining.

Example:

group.remove("Sleeve")
create_pin(object_name: str, vertex_group_name: str) PinΒΆ

Pin a vertex group so its vertices stay fixed during simulation.

Parameters:
  • object_name – Name of the mesh object.

  • vertex_group_name – Name of the vertex group on that object.

Returns:

A Pin proxy for the newly created pin.

Raises:

ValueError – If the object is missing, not a mesh, or the vertex group does not exist on it.

Example:

pin = group.create_pin("Cloth", "collar")
pin.move(delta=(0, 0, 0.2), frame=60)
get_pins() list[Pin]ΒΆ

Return all pins in this group as Pin proxies.

Example:

for pin in group.get_pins():
    pin.clear_keyframes()
clear_keyframes() GroupΒΆ

Delete all keyframes for all pins in this group.

Convenience method that calls Pin.clear_keyframes() on every pin returned by get_pins().

Returns:

self for chaining.

Example:

group.clear_keyframes()
delete() NoneΒΆ

Delete this group and every pin it owns.

Example:

group.delete()
class GroupParamΒΆ

Proxy for material and simulation parameters on a group.

Accessed via Group.param. Attribute access is whitelisted: reading or writing a name outside the whitelist raises AttributeError.

Whitelisted attributes:

  • Solver model: solid_model, shell_model

  • Density: solid_density, shell_density, rod_density

  • Young’s modulus: solid_young_modulus, shell_young_modulus, rod_young_modulus

  • Poisson ratio: solid_poisson_ratio, shell_poisson_ratio

  • Contact: friction, contact_gap, contact_gap_rat, contact_offset, contact_offset_rat

  • Strain limit: enable_strain_limit, strain_limit

  • Inflation: enable_inflate, inflate_pressure

  • Plasticity: enable_plasticity, plasticity, plasticity_threshold

  • Bend plasticity: enable_bend_plasticity, bend_plasticity, bend_plasticity_threshold, bend_rest_angle_source

  • Shell-specific: bend, shrink, shrink_x, shrink_y, stitch_stiffness

Example:

# Solver model
group.param.solid_model = "ARAP"
group.param.shell_model = "ARAP"

# Density (kg/m^3 for solid/shell, kg/m for rod)
group.param.solid_density = 1000.0
group.param.shell_density = 0.3
group.param.rod_density = 0.05

# Young's modulus (Pa) and Poisson ratio
group.param.solid_young_modulus = 1.0e6
group.param.shell_young_modulus = 5.0e5
group.param.rod_young_modulus = 1.0e7
group.param.solid_poisson_ratio = 0.45
group.param.shell_poisson_ratio = 0.30

# Contact
group.param.friction = 0.5
group.param.contact_gap = 0.001
group.param.contact_gap_rat = 0.1
group.param.contact_offset = 0.002
group.param.contact_offset_rat = 0.2

# Strain limit
group.param.enable_strain_limit = True
group.param.strain_limit = 1.05

# Inflation
group.param.enable_inflate = True
group.param.inflate_pressure = 100.0

# Plasticity (shells and tets only)
group.param.enable_plasticity = True
group.param.plasticity = 0.3
group.param.plasticity_threshold = 0.2
group.param.enable_bend_plasticity = True
group.param.bend_plasticity = 0.5
group.param.bend_plasticity_threshold = 0.1
group.param.bend_rest_angle_source = "REST"

# Shell-specific
group.param.bend = 1.0e-4
group.param.shrink = 0.98
group.param.shrink_x = 0.99
group.param.shrink_y = 0.97
group.param.stitch_stiffness = 5.0e4
class PinΒΆ

A pinned vertex group bound to a dynamics group.

Created via Group.create_pin(object_name, vertex_group_name). Every mutating method returns self so operations chain.

Example:

pin = group.create_pin("Cloth", "hem")
pin.move(delta=(0, 0, 1.0), frame=60)  # lift hem over 60 frames
pin.unpin(frame=120)                   # release at frame 120
property object_nameΒΆ
Type:

str

Name of the mesh object this pin belongs to.

Example:

pin = group.create_pin("Cloth", "hem")
print(pin.object_name)  # "Cloth"
property vertex_group_nameΒΆ
Type:

str

Name of the vertex group this pin targets.

Example:

pin = group.create_pin("Cloth", "hem")
print(pin.vertex_group_name)  # "hem"
pull(strength: float = 1.0) PinΒΆ

Use pull force instead of hard pin constraint.

Pull allows the vertices to move but applies a restoring force toward their target position.

Parameters:

strength – Pull force strength (default 1.0).

Returns:

self for chaining.

Example:

group.create_pin("Cloth", "shoulder").pull(strength=2.5)
spin(axis: tuple[float, float, float] = (1, 0, 0), angular_velocity: float = 360.0, flip: bool = False, center: tuple[float, float, float] | None = None, center_mode: str | None = None, center_direction: tuple[float, float, float] | None = None, center_vertex: int | None = None, frame_start: int = 1, frame_end: int = 60, transition: str = 'LINEAR') PinΒΆ

Add a spin operation to this pin.

Parameters:
  • axis – Rotation axis vector.

  • angular_velocity – Degrees per second.

  • flip – Reverse spin direction.

  • center – Center of rotation (for ABSOLUTE mode).

  • center_mode – "CENTROID", "ABSOLUTE", "MAX_TOWARDS", or "VERTEX". If None, inferred from other args (None center β†’ "CENTROID").

  • center_direction – Direction for MAX_TOWARDS mode.

  • center_vertex – Vertex index for VERTEX mode.

  • frame_start – Start frame.

  • frame_end – End frame.

  • transition – "LINEAR" or "SMOOTH".

Returns:

self for chaining.

Example:

# Spin about the centroid at 180 deg/s for frames 1-60
pin.spin(axis=(0, 0, 1), angular_velocity=180.0)
# Spin about an absolute world-space pivot
pin.spin(axis=(0, 1, 0), center=(0, 0, 1),
         frame_start=30, frame_end=90)
scale(factor: float = 1.0, center: tuple[float, float, float] | None = None, center_mode: str | None = None, center_direction: tuple[float, float, float] | None = None, center_vertex: int | None = None, frame_start: int = 1, frame_end: int = 60, transition: str = 'LINEAR') PinΒΆ

Add a scale operation to this pin.

Parameters:
  • factor – Scale factor.

  • center – Center point (for ABSOLUTE mode).

  • center_mode – "CENTROID", "ABSOLUTE", "MAX_TOWARDS", or "VERTEX". If None, inferred from other args (None center β†’ "CENTROID").

  • center_direction – Direction for MAX_TOWARDS mode.

  • center_vertex – Vertex index for VERTEX mode.

  • frame_start – Start frame.

  • frame_end – End frame.

  • transition – "LINEAR" or "SMOOTH".

Returns:

self for chaining.

Example:

# Shrink to 50% over frames 1-60 about the centroid
pin.scale(factor=0.5, transition="SMOOTH")
torque(magnitude: float = 1.0, axis_component: str = 'PC3', flip: bool = False, frame_start: int = 1, frame_end: int = 60) PinΒΆ

Add a torque operation to this pin.

Applies a rotational force around a PCA-computed axis.

Parameters:
  • magnitude – Torque in NΒ·m.

  • axis_component – "PC1" (major), "PC2" (middle), or "PC3" (minor).

  • flip – Reverse torque direction.

  • frame_start – Start frame.

  • frame_end – End frame.

Returns:

self for chaining.

Example:

pin.torque(magnitude=2.0, axis_component="PC1",
           frame_start=1, frame_end=30)
move_by(delta: tuple[float, float, float] = (0, 0, 0), frame_start: int = 1, frame_end: int = 60, transition: str = 'LINEAR') PinΒΆ

Ramp a translation of the pinned vertices over a frame range.

Parameters:
  • delta – (dx, dy, dz) offset.

  • frame_start – Start frame.

  • frame_end – End frame.

  • transition – "LINEAR" or "SMOOTH".

Returns:

self for chaining.

Example:

# Lift 1.0m along +Z between frames 10 and 90
pin.move_by(delta=(0, 0, 1.0),
            frame_start=10, frame_end=90,
            transition="SMOOTH")
unpin(frame: int) PinΒΆ

Mark this pin to be released at the given frame.

Sets the duration on the underlying UI property so the encoder and clear logic are aware. Also prevents future move(frame=N) calls where N >= frame.

Parameters:

frame – Frame number at which the pin is released.

Returns:

self for chaining.

Example:

pin.move(delta=(0, 0, 1), frame=60).unpin(frame=120)
move(delta: tuple[float, float, float] = (0, 0, 0), frame: int | None = None) PinΒΆ

Move pin vertices by delta and optionally keyframe at frame.

On the first call with frame, the current vertex positions are automatically keyframed at the current scene frame before any movement is applied. Ignored if frame >= the unpin frame.

Parameters:
  • delta – (dx, dy, dz) offset to apply (default no movement).

  • frame – Frame number to keyframe at. None means no keyframe.

Returns:

self for chaining.

Raises:

ValueError – If the target object is missing, not a mesh, or the vertex group does not exist on it.

Example:

pin = group.create_pin("Cloth", "hem")
pin.move(delta=(0, 0, 1.0), frame=60)   # auto-keyframes start
pin.move(delta=(0.5, 0, 0), frame=120)  # adds another keyframe
clear_keyframes() PinΒΆ

Delete all positional keyframes for this pin’s vertices.

Returns:

self for chaining.

Example:

pin = group.create_pin("Cloth", "hem")
pin.move(delta=(0, 0, 1.0), frame=60)
pin.clear_keyframes()  # wipe the animation, keep the pin
delete() NoneΒΆ

Remove this pin from its group.

Raises:

ValueError – If the owning group or pin item can no longer be found (for example, after solver.clear()).

Example:

pin = group.create_pin("Cloth", "hem")
pin.delete()  # remove the pin entry from the group
class WallΒΆ

Chainable builder for invisible wall colliders.

Returned by Solver.add_wall(). Keyframe frames must be strictly increasing. Every mutating method returns self.

Example:

solver.add_wall((0, 0, 0), (0, 0, 1)).param.friction = 0.5
(solver.add_wall((0, 0, 0), (0, 1, 0))
       .time(60).hold().time(61).move_to((0, 1, 0)))
property paramΒΆ
Type:

ColliderParam

Collider parameter proxy. See ColliderParam.

Example:

wall = solver.add_wall((0, 0, 0), (0, 0, 1))
wall.param.friction = 0.5
time(frame: int) WallΒΆ

Advance the keyframe cursor.

Parameters:

frame – Target frame (must be strictly greater than the current cursor position).

Returns:

self for chaining.

Raises:

ValueError – If frame is not strictly increasing.

Example:

(solver.add_wall((0, 0, 0), (0, 0, 1))
       .time(60).move_to((0, 0, 0.5)))
hold() WallΒΆ

Hold the previous position at the current cursor frame.

Returns:

self for chaining.

Example:

(solver.add_wall((0, 0, 0), (0, 0, 1))
       .time(60).hold().time(90).move_to((0, 0, 0.5)))
move_to(position) WallΒΆ

Keyframe a new absolute position at the current cursor frame.

Parameters:

position – (x, y, z) world-space position.

Returns:

self for chaining.

Example:

(solver.add_wall((0, 0, 0), (0, 0, 1))
       .time(60).move_to((0, 0, 1.0)))
move_by(delta) WallΒΆ

Keyframe a position offset from the previous keyframe.

Parameters:

delta – (dx, dy, dz) offset added to the previous keyframed position.

Returns:

self for chaining.

Example:

(solver.add_wall((0, 0, 0), (0, 0, 1))
       .time(60).move_by((0, 0, 0.25)))
delete() NoneΒΆ

Remove this wall collider from the scene.

Example:

wall = solver.add_wall((0, 0, 0), (0, 0, 1))
wall.delete()
class SphereΒΆ

Chainable builder for invisible sphere colliders.

Returned by Solver.add_sphere(). Keyframe frames must be strictly increasing. Every mutating method returns self.

Example:

solver.add_sphere((0, 0, 0), 0.98).invert().hemisphere()
(solver.add_sphere((0, 0, 0), 1.0)
       .time(60).hold().time(61).radius(0.5))
property paramΒΆ
Type:

ColliderParam

Collider parameter proxy. See ColliderParam.

Example:

sphere = solver.add_sphere((0, 0, 0), 1.0)
sphere.param.friction = 0.3
invert() SphereΒΆ

Flip the sphere inside-out so contact is on the inside surface.

Returns:

self for chaining.

Example:

solver.add_sphere((0, 0, 0), 1.0).invert()
hemisphere() SphereΒΆ

Treat this collider as a hemisphere rather than a full sphere.

Returns:

self for chaining.

Example:

solver.add_sphere((0, 0, 0), 1.0).hemisphere()
time(frame: int) SphereΒΆ

Advance the keyframe cursor.

Parameters:

frame – Target frame (must be strictly greater than the current cursor position).

Returns:

self for chaining.

Raises:

ValueError – If frame is not strictly increasing.

Example:

(solver.add_sphere((0, 0, 0), 1.0)
       .time(60).move_to((0, 0, 1.0)))
hold() SphereΒΆ

Hold the previous position and radius at the current cursor frame.

Returns:

self for chaining.

Example:

(solver.add_sphere((0, 0, 0), 1.0)
       .time(60).hold().time(90).radius(0.5))
move_to(position) SphereΒΆ

Keyframe a new absolute position at the current cursor frame.

Parameters:

position – (x, y, z) world-space position.

Returns:

self for chaining.

Example:

(solver.add_sphere((0, 0, 0), 1.0)
       .time(60).move_to((0, 0, 2.0)))
radius(r) SphereΒΆ

Keyframe a new radius at the current cursor frame.

Parameters:

r – New radius.

Returns:

self for chaining.

Example:

(solver.add_sphere((0, 0, 0), 1.0)
       .time(60).radius(0.25))  # shrink over 60 frames
transform_to(position, radius) SphereΒΆ

Keyframe both position and radius together.

Parameters:
  • position – (x, y, z) world-space position.

  • radius – New radius.

Returns:

self for chaining.

Example:

(solver.add_sphere((0, 0, 0), 1.0)
       .time(60).transform_to((0, 0, 1.0), 0.5))
delete() NoneΒΆ

Remove this sphere collider from the scene.

Example:

sphere = solver.add_sphere((0, 0, 0), 1.0)
sphere.delete()
class ColliderParamΒΆ

Attribute proxy for invisible-collider parameters.

Accessed via Wall.param or Sphere.param. Attribute access is whitelisted: reading or writing a name outside the whitelist raises AttributeError.

Whitelisted attributes:

  • friction: contact friction coefficient

  • contact_gap: contact gap thickness

  • thickness: wall/sphere shell thickness

  • enable_active_duration: True to limit collider lifetime

  • active_duration: number of frames the collider is active when enable_active_duration is set

Example:

wall = solver.add_wall((0, 0, 0), (0, 0, 1))
wall.param.friction = 0.5
wall.param.contact_gap = 0.002
wall.param.thickness = 0.01
wall.param.enable_active_duration = True
wall.param.active_duration = 60  # active for frames 1-60

sphere = solver.add_sphere((0, 0, 1), 0.5)
sphere.param.friction = 0.3