🧩 Module Reference¢

Every class, method, and property exported by the frontend package. Import surface:

from frontend import App, Scene, FixedSession, ParamManager, ...

Entry point for the frontend package.

Import App from this package to start the application; the other names listed in __all__ expose the scene, session, mesh, plotting, asset, and utility APIs.

class frontend.App(name: str, renew: bool, cache_dir: str = '')[source]ΒΆ

Bases: object

High-level entry point for the simulation frontend.

An App bundles together the mesh, asset, scene, and session managers used to build and run a simulation. It is the canonical starting point for notebooks and scripts, typically constructed via App.create() (new) or App.load() (resume).

Example

Build a trivial scene and run it to completion:

from frontend import App

app = App.create("intro")

V, F = app.mesh.square(res=32)
app.asset.add.tri("sheet", V, F)

scene = app.scene.create().add("sheet").build()
session = app.session.create(scene).build()
session.start(blocking=True)
property asset: AssetManagerΒΆ

Get the asset manager.

Returns:

The asset manager.

Return type:

AssetManager

Example

Use app.asset to register mesh data and list what exists:

app = App.create("demo")
V, F = app.mesh.square(res=32)
app.asset.add.tri("sheet", V, F)
print(app.asset.list())
static busy() bool[source]ΒΆ

Return whether a simulation is currently running.

Returns:

True if a simulation is running, False otherwise.

Return type:

bool

Example

Guard against starting a second simulation while one is still in progress:

from frontend import App

if App.busy():
    print("solver is still running; skipping")
else:
    print("ready to start a new session")
property cache_dir: strΒΆ

Get the path to the cache directory.

Returns:

The path to the cache directory.

Return type:

str

Example

Inspect where downloaded preset meshes and other cached files are stored on disk:

app = App.create("demo")
print(app.cache_dir)
property ci: boolΒΆ

Check if the code is running in a CI environment.

Returns:

True if the code is running in a CI environment, False otherwise.

Return type:

bool

Example

Skip long-running steps when executing in CI:

app = App.create("demo")
if not app.ci:
    session.start(blocking=True)
property ci_dir: str | NoneΒΆ

Get the path to the CI directory, if running under CI.

Returns:

The CI directory path, or None when not running in CI.

Return type:

Optional[str]

Example

Resolve a path under the CI workspace only when the app is running inside CI:

app = App.create("demo")
if app.ci_dir is not None:
    print("CI workspace:", app.ci_dir)
clear() App[source]ΒΆ

Clear the application state and return a fresh App instance.

Example

Discard existing assets, scenes, and sessions before rebuilding a workflow from scratch:

from frontend import App

app = App.create("drape")
app = app.clear()
print(app.asset.list())
clear_cache() App[source]ΒΆ

Remove all files and subdirectories under the cache directory.

Example

Force downloaded meshes and other cached artifacts to be regenerated on the next run:

from frontend import App

app = App.create("drape")
app.clear_cache()
static create(name: str, cache_dir: str = '') App[source]ΒΆ

Start a new application.

Parameters:
  • name (str) – The name of the application.

  • cache_dir (str) – The directory used to store cached files. If empty, defaults to ~/.cache/ppf-cts (or a project-relative cache/ppf-cts on Windows).

Returns:

A new instance of the App class.

Return type:

App

Example

Register a mesh as an asset, drop it into a scene, and run a short simulation:

from frontend import App

app = App.create("hello")

V, F = app.mesh.square(res=64, ex=[1, 0, 0], ey=[0, 0, 1])
app.asset.add.tri("sheet", V, F)

scene = app.scene.create()
scene.add("sheet").at(0, 0.6, 0)
scene = scene.build()

session = app.session.create(scene)
session.param.set("frames", 60).set("dt", 0.01)
session = session.build()
session.start(blocking=True)
session.export.animation().zip()
property extra: ExtraΒΆ

Get the extra manager.

Returns:

The extra manager.

Return type:

Extra

Example

Use app.extra for auxiliary helpers that do not belong to the core asset/scene/session flow:

app = App.create("demo")
helpers = app.extra
static get_data_dirpath()[source]ΒΆ
static get_default_param() ParamManager[source]ΒΆ

Get default parameters for the application.

Returns:

The default parameters.

Return type:

ParamManager

Example

Inspect the default value of a solver parameter before building a session:

from frontend import App

param = App.get_default_param()
print(param.get("gravity"))
static get_proj_root() str[source]ΒΆ

Find the root directory of the project.

Returns:

Path to the root directory of the project (parent of frontend).

Return type:

str

Example

Resolve paths relative to the project root (for example, the bundled src directory):

import os
from frontend import App

src_dir = os.path.join(App.get_proj_root(), "src")
print(os.path.isdir(src_dir))
static is_fast_check() bool[source]ΒΆ

Check if fast check mode is enabled.

Fast check mode forces simulations to run for only 1 frame, enabling quick validation of all examples.

Returns:

True if fast check mode is enabled.

Return type:

bool

Example

Branch on fast-check mode to shorten a CI run:

from frontend import App

frames = 1 if App.is_fast_check() else 200
print("frames:", frames)
static load(name: str, cache_dir: str = '') App[source]ΒΆ

Load the saved state of the application if it exists.

If no saved state is found on disk, a fresh App is initialized instead.

Parameters:
  • name (str) – The name of the application.

  • cache_dir (str) – The directory used to store cached files. If empty, defaults to ~/.cache/ppf-cts (or a project-relative cache/ppf-cts on Windows).

Returns:

A new instance of the App class.

Return type:

App

Example

Resume a named app, reusing the previously saved assets and scene when available:

from frontend import App

app = App.load("drape")
print(app.asset.list())
property mesh: MeshManagerΒΆ

Get the mesh manager.

Returns:

The mesh manager.

Return type:

MeshManager

Example

Use app.mesh to build primitives or load presets:

app = App.create("demo")
V, F = app.mesh.square(res=64)
V2, F2, T = app.mesh.preset("armadillo").tetrahedralize()
property name: strΒΆ

Get the name of the application.

Returns:

The name of the application.

Return type:

str

Example

Read back the name assigned at construction time:

app = App.create("hello")
assert app.name == "hello"
property plot: PlotManagerΒΆ

Get the plot manager.

Returns:

The plot manager.

Return type:

PlotManager

Example

Use app.plot to create interactive viewers from mesh data:

app = App.create("demo")
V, F = app.mesh.square(res=32)
plot = app.plot.create().tri(V, F).build()
static recover(name: str) FixedSession[source]ΒΆ

Recover the fixed session previously saved under name.

The session is located via a symlink in the data directory (or, on Windows, a .txt fallback file holding the target path), and otherwise via a fallback {data_dir}/{name}/session directory.

Parameters:

name (str) – The name used to identify the session.

Returns:

The recovered fixed session.

Return type:

FixedSession

Raises:

Exception – If no recoverable session can be found for name.

Example

Resume a long-running session after restarting the kernel and inspect its current status:

from frontend import App

session = App.recover("drape")
print(session.finished())
static run_tests() bool[source]ΒΆ

Run all frontend tests.

Returns:

True if all tests pass, False otherwise.

Return type:

bool

Example

Run the bundled frontend self-tests and exit non-zero on failure:

import sys
from frontend import App

if not App.run_tests():
    sys.exit(1)
save() App[source]ΒΆ

Save the application state to disk and return self.

Example

Persist the current assets and scene so a later call to App.load() can resume them:

from frontend import App

app = App.create("drape")
V, F = app.mesh.square(res=64)
app.asset.add.tri("sheet", V, F)
app.save()
property scene: SceneManagerΒΆ

Get the scene manager.

Returns:

The scene manager.

Return type:

SceneManager

Example

Use app.scene to assemble a scene from registered assets:

app = App.create("demo")
scene = app.scene.create().add("sheet").at(0, 0.5, 0).build()
property session: SessionManagerΒΆ

Get the session manager.

Returns:

The session manager.

Return type:

SessionManager

Example

Use app.session to build and start a solver run from a finalized scene:

app = App.create("demo")
scene = app.scene.create().add("sheet").build()
session = app.session.create(scene).build()
session.start(blocking=True)
static set_fast_check(enabled: bool = True)[source]ΒΆ

Set fast check mode.

When enabled, simulations are forced to run for only 1 frame, enabling quick validation of examples.

Parameters:

enabled – Whether to enable fast check mode. Defaults to True.

Example

Enable fast-check mode before building a session to run a single-frame smoke test:

from frontend import App

App.set_fast_check(True)
assert App.is_fast_check()
static terminate()[source]ΒΆ

Terminate the running simulation if one is busy.

Example

Stop a stuck solver process before starting a new run:

from frontend import App

if App.busy():
    App.terminate()
class frontend.AssetFetcher(manager: AssetManager)[source]ΒΆ

Bases: object

Fetcher used to retrieve mesh assets from an AssetManager.

Example

Access the fetcher via App.asset.fetch to pull arrays for a registered mesh:

from frontend import App

app = App.create("demo")
V, F = app.mesh.square(res=64)
app.asset.add.tri("sheet", V, F)
V2, F2 = app.asset.fetch.tri("sheet")
get(name: str) dict[str, ndarray][source]ΒΆ

Return the raw arrays stored for an asset.

The keys present in the returned dictionary depend on the asset type:

  • "tri": V, F, and optionally UV.

  • "tet": V, F, T.

  • "rod": V, E.

  • "stitch": Ind, W.

Parameters:

name (str) – The name of the asset.

Returns:

Mapping from array name to array.

Return type:

dict[str, np.ndarray]

Raises:

Exception – If no asset is registered under name.

Example

Retrieve the raw arrays of a registered mesh as a dictionary and inspect their shapes:

from frontend import App

app = App.create("demo")
V, F = app.mesh.square(res=32)
app.asset.add.tri("sheet", V, F)
data = app.asset.fetch.get("sheet")
print(data["V"].shape, data["F"].shape)
get_type(name: str) str[source]ΒΆ

Return the type tag of a registered asset.

Parameters:

name (str) – The name of the asset.

Returns:

The type of the asset: one of "tri", "tet", "rod", or "stitch".

Return type:

str

Raises:

Exception – If no asset is registered under name.

Example

Branch on the type tag before retrieving arrays of the correct shape:

from frontend import App

app = App.create("demo")
V, F = app.mesh.square(res=32)
app.asset.add.tri("sheet", V, F)
assert app.asset.fetch.get_type("sheet") == "tri"
rod(name: str) tuple[ndarray, ndarray][source]ΒΆ

Return the vertex and edge arrays of a rod mesh asset.

Parameters:

name (str) – The name of the asset.

Returns:

The vertices (#x3) and the edges (#x2) of the rod.

Return type:

tuple[np.ndarray, np.ndarray]

Raises:

Exception – If no asset is registered under name, or the asset exists but is not a rod mesh.

Example

Retrieve the vertex and edge arrays of a registered rod asset:

import numpy as np
from frontend import App

app = App.create("demo")
V = np.linspace([0, 0, 0], [1, 0, 0], 10)
E = np.array([[i, i + 1] for i in range(len(V) - 1)], dtype=np.uint32)
app.asset.add.rod("strand", V, E)
V, E = app.asset.fetch.rod("strand")
stitch(name: str) tuple[ndarray, ndarray][source]ΒΆ

Return the index and weight arrays of a stitch asset.

Parameters:

name (str) – The name of the asset.

Returns:

The index array Ind (#x4) and the weight array W (#x4) of the stitch, as uploaded via AssetUploader.stitch().

Return type:

tuple[np.ndarray, np.ndarray]

Raises:

Exception – If no asset is registered under name, or the asset exists but is not a stitch.

Example

Fetch the index and weight arrays for a registered stitch asset:

from frontend import App

app = App.create("demo")
V, F, S = app.extra.load_CIPC_stitch_mesh("dress_stage.obj")
app.asset.add.stitch("glue", S)
Ind, W = app.asset.fetch.stitch("glue")
tet(name: str) tuple[ndarray, ndarray, ndarray][source]ΒΆ

Return the arrays of a tetrahedral mesh asset.

Parameters:

name (str) – The name of the asset.

Returns:

The vertices (#x3), the surface triangle elements (#x3), and the tetrahedral elements (#x4) of the mesh.

Return type:

tuple[np.ndarray, np.ndarray, np.ndarray]

Raises:

Exception – If no asset is registered under name, or the asset exists but is not a tetrahedral mesh.

Example

Fetch a tet asset and plot its volumetric connectivity:

from frontend import App

app = App.create("demo")
V, F, T = app.mesh.icosphere(r=0.25, subdiv_count=3).tetrahedralize()
app.asset.add.tet("ball", V, F, T)
V, F, T = app.asset.fetch.tet("ball")
app.plot.create().tet(V, T)
tri(name: str) tuple[ndarray, ndarray][source]ΒΆ

Return the vertex and face arrays of a triangle mesh asset.

Parameters:

name (str) – The name of the asset.

Returns:

The vertices (#x3) and the triangle elements (#x3) of the mesh.

Return type:

tuple[np.ndarray, np.ndarray]

Raises:

Exception – If no asset is registered under name, or the asset exists but is not a triangle mesh.

Example

Plot a previously registered triangle asset after fetching its arrays:

from frontend import App

app = App.create("demo")
V, F = app.mesh.square(res=32)
app.asset.add.tri("sheet", V, F)
V, F = app.asset.fetch.tri("sheet")
app.plot.create().tri(V, F)
class frontend.AssetManager[source]ΒΆ

Bases: object

Registry for mesh data assets.

Holds uploaded meshes keyed by name and exposes an AssetUploader for registering new assets and an AssetFetcher for retrieving them.

Example

Access the manager via App.asset to register a mesh, list the currently known names, and fetch arrays back out:

from frontend import App

app = App.create("demo")
V, F = app.mesh.square(res=64)
app.asset.add.tri("sheet", V, F)
print(app.asset.list())
V2, F2 = app.asset.fetch.tri("sheet")
property add: AssetUploaderΒΆ

The uploader used to register new assets.

Example

Use app.asset.add to register triangle, tetrahedral, or rod meshes by name:

app = App.create("demo")
V, F = app.mesh.square(res=32)
app.asset.add.tri("sheet", V, F)
clear()[source]ΒΆ

Remove all assets from the manager.

Example

Reset the asset registry before reloading a different set of meshes:

from frontend import App

app = App.create("demo")
app.asset.clear()
V, F, T = app.mesh.preset("armadillo").tetrahedralize()
app.asset.add.tet("body", V, F, T)
property fetch: AssetFetcherΒΆ

The fetcher used to retrieve registered assets.

Example

Use app.asset.fetch to pull mesh arrays back out by name:

app = App.create("demo")
V, F = app.mesh.square(res=32)
app.asset.add.tri("sheet", V, F)
V2, F2 = app.asset.fetch.tri("sheet")
list() list[str][source]ΒΆ

List the names of all assets currently registered.

Returns:

The registered asset names.

Return type:

list[str]

Example

Check whether an asset has already been uploaded before re-registering it:

from frontend import App

app = App.create("demo")
V, F = app.mesh.square(res=32)
app.asset.add.tri("sheet", V, F)
assert "sheet" in app.asset.list()
property mesh: dict[str, tuple]ΒΆ

The underlying name-to-mesh-tuple dictionary.

Example

Peek at the raw registry to inspect what has been uploaded:

app = App.create("demo")
V, F = app.mesh.square(res=32)
app.asset.add.tri("sheet", V, F)
print(list(app.asset.mesh.keys()))
remove(name: str) bool[source]ΒΆ

Remove an asset from the manager.

Parameters:

name (str) – The name of the asset to remove.

Returns:

True if the asset existed and was removed, False if no asset with that name was registered.

Return type:

bool

Example

Drop an asset by name and re-upload a fresh version:

from frontend import App

app = App.create("demo")
V, F = app.mesh.square(res=32)
app.asset.add.tri("sheet", V, F)
app.asset.remove("sheet")
app.asset.add.tri("sheet", V, F)
class frontend.AssetUploader(manager: AssetManager)[source]ΒΆ

Bases: object

Uploader used to register mesh assets with an AssetManager.

Example

Access the uploader via App.asset.add and register triangle, tetrahedral, and rod assets:

from frontend import App

app = App.create("demo")
V, F = app.mesh.square(res=64)
app.asset.add.tri("sheet", V, F)
V, F, T = app.mesh.icosphere(r=0.25, subdiv_count=4).tetrahedralize()
app.asset.add.tet("ball", V, F, T)
check_bounds(V: ndarray, E: ndarray)[source]ΒΆ

Check that every index in E refers to a valid row of V.

Parameters:
  • V (np.ndarray) – Vertex array whose row count defines the upper bound.

  • E (np.ndarray) – Element/index array to validate.

Raises:

Exception – If any index in E is greater than or equal to V.shape[0].

Example

Validate a hand-built mesh before uploading it:

import numpy as np
from frontend import App

app = App.create("demo")
V = np.array([[0, 0, 0], [1, 0, 0], [0, 1, 0]], dtype=float)
F = np.array([[0, 1, 2]], dtype=np.uint32)
app.asset.add.check_bounds(V, F)
rod(name: str, V: ndarray, E: ndarray)[source]ΒΆ

Upload a rod mesh to the asset manager.

Parameters:
  • name (str) – The name of the asset. Must not already exist.

  • V (np.ndarray) – The vertices (#x3) of the rod.

  • E (np.ndarray) – The edges (#x2) of the rod.

Raises:

Exception – If name already exists or any index in E is out of bounds for V.

Example

Build a polyline from a numpy array of points and register it as a rod asset:

import numpy as np
from frontend import App

app = App.create("demo")
V = np.linspace([0, 0, 0], [1, 0, 0], 20)
E = np.array([[i, i + 1] for i in range(len(V) - 1)], dtype=np.uint32)
app.asset.add.rod("strand", V, E)
stitch(name: str, stitch: tuple[ndarray, ndarray])[source]ΒΆ

Upload a stitch asset to the asset manager.

Parameters:
  • name (str) – The name of the asset. Must not already exist.

  • stitch (tuple[np.ndarray, np.ndarray]) – A pair (Ind, W) where Ind is the index array (#x4) and W is the weight array (#x4) of the stitch.

Raises:

Exception – If Ind or W does not have 4 columns, or if name already exists.

Example

Load a CIPC-format stitch mesh and register its stitch information alongside the dress geometry:

from frontend import App

app = App.create("demo")
V, F, S = app.extra.load_CIPC_stitch_mesh("dress_stage.obj")
app.asset.add.tri("dress", V, F)
app.asset.add.stitch("glue", S)
tet(name: str, V: ndarray, F: ndarray, T: ndarray)[source]ΒΆ

Upload a tetrahedral mesh to the asset manager.

Parameters:
  • name (str) – The name of the asset. Must not already exist.

  • V (np.ndarray) – The vertices (#x3) of the mesh.

  • F (np.ndarray) – The surface triangle elements (#x3) of the mesh.

  • T (np.ndarray) – The tetrahedral elements (#x4) of the mesh.

Raises:

Exception – If the column counts of V, F, or T are wrong, name already exists, or any index in F or T is out of bounds for V.

Example

Tetrahedralize an icosphere and register it as a volumetric asset:

from frontend import App

app = App.create("demo")
V, F, T = app.mesh.icosphere(r=0.25, subdiv_count=4).tetrahedralize()
app.asset.add.tet("sphere", V, F, T)
tri(name: str, V: ndarray, F: ndarray)[source]ΒΆ

Upload a triangle mesh to the asset manager.

Parameters:
  • name (str) – The name of the asset. Must not already exist.

  • V (np.ndarray) – The vertices (#x3 or #x5) of the mesh. If #x5, columns 0-2 are xyz coordinates and columns 3-4 are UV coordinates.

  • F (np.ndarray) – The triangle elements (#x3) of the mesh.

Raises:

Exception – If V does not have 3 or 5 columns, F does not have 3 columns, name already exists, or any index in F is out of bounds for V.

Example

Register a square sheet and then an icosphere as triangle assets:

from frontend import App

app = App.create("demo")
V, F = app.mesh.square(res=128, ex=[1, 0, 0], ey=[0, 0, 1])
app.asset.add.tri("sheet", V, F)
V, F = app.mesh.icosphere(r=0.5, subdiv_count=4)
app.asset.add.tri("sphere", V, F)
class frontend.BlenderApp(name: str, verbose: bool = False, progress_callback=None)[source]ΒΆ

Bases: object

Attach to a project exported by the Blender addon and build a runnable session.

The addon writes data.pickle and param.pickle into a per-project directory under ~/.local/share/ppf-cts/git-<branch>/<name>/. open() is the canonical entry point: it decodes those pickles, populates the scene, applies parameters, and builds a FixedSession accessible via scene and session.

Example

Canonical notebook cell (as generated by the Blender addon), attach to a transferred project and run it:

from frontend import BlenderApp

app = BlenderApp.open("rod-example-3")
app.scene.report()
app.scene.preview()
app.session.run()
app.session.preview()
make() BlenderApp[source]ΒΆ

Apply param.pickle to the populated scene and build a runnable session.

Applies per-object parameters, pin configuration, cross-stitch constraints, explicit merge pairs, and invisible colliders to the scene, then builds the fixed scene and session. The resulting state is persisted to app_state.pickle on a best-effort basis.

Example

Finish the build after populate(), then run the session:

from frontend import BlenderApp

app = BlenderApp("my-project").populate().make()
app.session.run()
app.session.preview()
classmethod open(name: str, verbose: bool = False, progress_callback=None) BlenderApp[source]ΒΆ

Open a Blender project and build its scene and session.

Behavior:

  1. If the Blender addon has uploaded both data.pickle and param.pickle under the project root, populate().make() is invoked to build a fresh scene and session in this process.

  2. Otherwise, FileNotFoundError is raised β€” the user must click β€œTransfer” in the Blender addon first.

After open(), app.scene and app.session are the built FixedScene / FixedSession objects, ready for report, preview, run, and stream.

Example

Notebook cell generated by the Blender addon β€” attach to the transferred project and run it:

from frontend import BlenderApp

app = BlenderApp.open("<project>")
app.scene.preview()
app.session.run()
app.session.preview()
populate() BlenderApp[source]ΒΆ

Populate the scene with objects decoded from data.pickle.

Also peeks at param.pickle for per-group fTetWild overrides so that tetrahedralization can use them at build time; full parameter application is deferred to make().

Example

Two-stage construction (useful when you want to inspect the mutable scene before parameters are applied):

from frontend import BlenderApp

app = BlenderApp("my-project")
app.populate()
app.make()
app.session.run()
property scene: FixedSceneΒΆ

The built fixed scene β€” use this for .report(), .preview(), etc. The mutable pre-build Scene remains accessible as _scene for advanced callers.

Example

Open a Blender-authored project and inspect the built scene:

from frontend import BlenderApp

app = BlenderApp.open("rod-example-3")
app.scene.report()
app.scene.preview()
property session: FixedSessionΒΆ

The built fixed session β€” use this for .run(), .preview(), .stream(), etc.

Example

Run a Blender-authored session and stream live output:

from frontend import BlenderApp

app = BlenderApp.open("rod-example-3")
app.session.run()
app.session.preview()
app.session.stream()
class frontend.CppRustDocStringParser[source]ΒΆ

Bases: object

Parser for logging-related docstrings in the C++/Rust sources.

Example

Harvest the log-name table from the bundled C++/Rust sources so a session’s recorded log keys can be annotated:

import os
from frontend import App, CppRustDocStringParser

src_dir = os.path.join(App.get_proj_root(), "src")
entries = CppRustDocStringParser.get_logging_docstrings(src_dir)
print(sorted(entries)[:5])
static get_logging_docstrings(root: str) dict[str, dict[str, str]][source]ΒΆ

Scan root for logging docstrings in .cu and .rs files.

Walks root recursively (skipping args.rs) and parses // comment blocks describing SimpleLog, logging.push, and logging.mark call sites. Each block contributes a Label: value dictionary plus a free-form Description and a derived filename.

Parameters:

root (str) – Directory to search.

Returns:

Mapping from entry name (with underscores replaced by hyphens) to the parsed docstring fields, sorted by key.

Return type:

dict[str, dict[str, str]]

Example

Look up the metadata associated with a specific log name under the bundled solver sources:

import os
from frontend import App, CppRustDocStringParser

src_dir = os.path.join(App.get_proj_root(), "src")
entries = CppRustDocStringParser.get_logging_docstrings(src_dir)
if "time-per-frame" in entries:
    print(entries["time-per-frame"].get("Description"))
class frontend.CreateManager(cache_dir: str)[source]ΒΆ

Bases: object

A manager that provides mesh creation functions.

This manager provides a set of functions to create various types of meshes, such as rods, triangles, and tetrahedra.

Example

Wrap raw numpy arrays into the mesh types expected by the solver:

V = np.array([[0, 0, 0], [1, 0, 0], [0, 1, 0]])
E = np.array([[0, 1], [1, 2]])
rod = app.mesh.create.rod(V, E)

pts = np.array([[0, 0], [1, 0], [1, 1], [0, 1]])
tri_mesh = app.mesh.create.tri(pts).triangulate(1024)
rod(vert: ndarray, edge: ndarray) Rod[source]ΒΆ

Create a rod mesh.

Parameters:
  • vert (np.ndarray) – array of vertex positions

  • edge (np.ndarray) – array of edges

Returns:

a rod mesh, a pair of vertices and edges

Return type:

Rod

Example

Build a custom rod from hand-specified vertices and edges:

V = np.array([[0, 0, 0], [0.5, 0.2, 0], [1, 0, 0]])
E = np.array([[0, 1], [1, 2]], dtype=np.uint32)
rod = app.mesh.create.rod(V, E)
app.asset.add.rod("strand", *rod)
tet(vert: ndarray, elm: ndarray, tet: ndarray) TetMesh[source]ΒΆ

Create a tetrahedral mesh.

Parameters:
  • vert (np.ndarray) – array of vertex positions

  • elm (np.ndarray) – array of surface triangle elements

  • tet (np.ndarray) – array of tetrahedral elements

Returns:

a tetrahedral mesh containing vertices, surface triangles, and tetrahedra

Return type:

TetMesh

Example

Wrap precomputed tet arrays into a TetMesh and register it:

V, F, T = app.mesh.tet_box(1, 1, 1)
tet = app.mesh.create.tet(V, F, T)
app.asset.add.tet("block", *tet)
tri(vert: ndarray, elm: ndarray = None) TriMesh[source]ΒΆ

Create a triangle mesh.

When elm is None or empty, a closed edge loop over all vertices is auto-generated instead of triangle faces. The resulting mesh’s element array then stores edges rather than triangles.

Parameters:
  • vert (np.ndarray) – array of vertex positions

  • elm (np.ndarray) – array of elements (None or empty to auto-generate an edge loop)

Returns:

a triangle (or line-loop) mesh bound to the manager’s cache directory

Return type:

TriMesh

Example

Make a closed 2D curve and triangulate it into a filled patch:

pts = np.array(
    [[np.cos(t), np.sin(t)] for t in np.linspace(0, 2 * np.pi, 64, endpoint=False)]
)
V, F = app.mesh.create.tri(pts).triangulate(target=1024)
app.plot.create().tri(V, F)
class frontend.Extra[source]ΒΆ

Bases: object

Collection of auxiliary helpers that do not belong to any manager.

Example

Access the helpers via App.extra to sparse-clone an external dataset and load one of its stitch meshes:

import os
from frontend import App, get_cache_dir

app = App.create("fitting")
dest = os.path.join(get_cache_dir(), "Codim-IPC")
app.extra.sparse_clone(
    "https://github.com/ipc-sim/Codim-IPC",
    dest,
    ["Projects/FEMShell/input/dress_knife"],
)
stage = os.path.join(dest, "Projects/FEMShell/input/dress_knife/stage.obj")
V, F, S = app.extra.load_CIPC_stitch_mesh(stage)
load_CIPC_stitch_mesh(path: str) tuple[ndarray, ndarray, tuple[ndarray, ndarray]][source]ΒΆ

Load a stitch mesh in the format used by the CIPC paper repository.

Parameters:

path (str) – Path to the stitch mesh file.

Returns:

A tuple (vertices, faces, (stitch_index, stitch_weight)) where vertices have shape (#, 3), faces have shape (#, 3) with zero-based indices, and each stitch entry has shape (#, 4). The weight row [1.0, 1 - w, w, 0.0] encodes linear interpolation between the second and third stitch vertices.

Return type:

tuple[np.ndarray, np.ndarray, tuple[np.ndarray, np.ndarray]]

Example

Load a CIPC-format stitch mesh and register the pieces as triangle and stitch assets:

from frontend import App

app = App.create("fitting")
V, F, S = app.extra.load_CIPC_stitch_mesh("dress_stage.obj")
app.asset.add.tri("dress", V, F)
app.asset.add.stitch("glue", S)
sparse_clone(url: str, dest: str, paths: list[str], delete_exist: bool = False)[source]ΒΆ

Fetch a git repository using sparse-checkout.

Clones url into dest if needed, then adds each entry in paths to the sparse-checkout set and checks it out. Already present paths are left untouched.

Parameters:
  • url (str) – URL of the git repository.

  • dest (str) – Destination directory for the clone.

  • paths (list[str]) – Repository-relative paths to fetch.

  • delete_exist (bool) – If True, delete dest before cloning.

Raises:

FileNotFoundError – If git cannot be found on PATH.

Example

Fetch only the FEMShell input subdirectories from the upstream CIPC repository into the ppf-cts cache:

import os
from frontend import App, get_cache_dir

app = App.create("fitting")
dest = os.path.join(get_cache_dir(), "Codim-IPC")
app.extra.sparse_clone(
    "https://github.com/ipc-sim/Codim-IPC",
    dest,
    ["Projects/FEMShell/input/dress_knife"],
)
class frontend.FixedScene(plot: PlotManager | None, name: str, map_by_name: dict[str, list[int]], displacement: ndarray, vert: tuple[ndarray, ndarray], color: ndarray, dyn_face_color: list[EnumColor], dyn_face_intensity: list[float], vel: ndarray, uv: list[ndarray], rod: ndarray, tri: ndarray, tet: ndarray, rod_param: dict[str, list[Any]], tri_param: dict[str, list[Any]], tet_param: dict[str, list[Any]], wall: list[Wall], sphere: list[Sphere], rod_vert_range: tuple[int, int], shell_vert_range: tuple[int, int], rod_count: int, shell_count: int, tri_is_collider: ndarray, rod_is_collider: ndarray, pinned_vertices: set[int] | None = None, static_vert_for_check: ndarray | None = None, static_tri_for_check: ndarray | None = None, surface_map_by_name: dict[str, tuple] | None = None, concat_rest_vert: ndarray | None = None, rest_vert_mask: ndarray | None = None)[source]ΒΆ

Bases: object

A fixed scene class.

FixedScene is the immutable, validated result of Scene.build(). Hand it to SessionManager.create() to drive a solver run.

Example

Build a scene, inspect it, then pass it into a session:

scene = app.scene.create()
scene.add("sheet").at(0, 0.6, 0)
fixed = scene.build().report()
fixed.preview()
session = app.session.create(fixed).build()
bbox() tuple[ndarray, ndarray][source]ΒΆ

Compute the bounding box of the scene.

Returns:

The maximum and minimum coordinates of the bounding box.

Return type:

tuple[np.ndarray, np.ndarray]

Example

Print the extents of the built scene:

hi, lo = fixed.bbox()
print("size:", hi - lo)
center() ndarray[source]ΒΆ

Compute the area-weighted center of the scene.

Returns:

The area-weighted center of the scene.

Return type:

np.ndarray

Example

Aim a camera at the scene’s area-weighted center:

target = fixed.center()
fixed.preview(options={"lookat": target.tolist()})
color(vert: ndarray, hint: dict | None = None) ndarray[source]ΒΆ

Compute the per-vertex color for the scene given a vertex array.

Parameters:
  • vert (np.ndarray) – The current vertex positions.

  • hint (dict, optional) – Optional hints for the color computation (e.g. "max-area"). Defaults to None.

Returns:

The per-vertex colors of the scene.

Return type:

np.ndarray

Example

Compute colors for the current vertex positions before previewing:

fixed = scene.build()
V = fixed.vertex()
colors = fixed.color(V, hint={"max-area": 2.0})
fixed.preview(V)
export(vert: ndarray, color: ndarray, path: str, include_static: bool = True, args: dict | None = None, delete_exist: bool = False) FixedScene[source]ΒΆ

Export the scene to a mesh file.

The vertices and vertex colors must be supplied explicitly so callers can pass time-evaluated positions.

Parameters:
  • vert (np.ndarray) – The vertices of the scene.

  • color (np.ndarray) – The colors of the vertices.

  • path (str) – The path to the mesh file. Supported formats include .ply and .obj.

  • include_static (bool, optional) – Whether to include the static mesh. Defaults to True.

  • args (dict, optional) – Additional arguments passed to the renderer.

  • delete_exist (bool, optional) – Whether to delete any existing file at the path. Defaults to False.

Returns:

The fixed scene.

Return type:

FixedScene

Example

Write out the scene as a .ply at the initial time:

vert = fixed.vertex()
color = fixed.color(vert)
fixed.export(vert, color, "/tmp/scene.ply", delete_exist=True)
export_fixed(path: str, delete_exist: bool) FixedScene[source]ΒΆ

Export the fixed scene into a set of data files that are read by the simulator.

Parameters:
  • path (str) – The path to the output directory.

  • delete_exist (bool) – Whether to delete the existing directory.

Returns:

The fixed scene.

Return type:

FixedScene

Example

Typically invoked internally by Session.build(), but can be called directly to materialize the solver’s data directory:

fixed = scene.build()
fixed.export_fixed("/tmp/my_scene_data", delete_exist=True)
get_violation_messages() list[str][source]ΒΆ

Get a list of violation messages for the scene.

Example

Print any validation violations before running the solver:

for msg in fixed.get_violation_messages():
    print(msg)
property has_violations: boolΒΆ

Check if the scene has any violations that prevent simulation.

Example

Guard the solver launch behind a validation check:

fixed = scene.build()
if fixed.has_violations:
    print(fixed.get_violation_messages())
preview(vert: ndarray | None = None, options: dict | None = None, show_slider: bool = True, engine: str = 'threejs') Plot | None[source]ΒΆ

Preview the scene.

Parameters:
  • vert (Optional[np.ndarray], optional) – The vertices to preview. Defaults to None, in which case self.vertex() is used.

  • options (dict, optional) – The options for the plot. Defaults to None.

  • show_slider (bool, optional) – Whether to show the time slider. Defaults to True.

  • engine (str, optional) – The rendering engine. Defaults to "threejs".

Returns:

The plot object if in a Jupyter notebook, otherwise None.

Return type:

Optional[Plot]

Example

Preview the initial scene with a custom camera and no pin markers:

opts = {"eye": [0, 1.4, 2.5], "pin": False, "wireframe": True}
fixed.preview(options=opts)
report() FixedScene[source]ΒΆ

Print a summary of the scene.

Returns:

The fixed scene (for chaining).

Return type:

FixedScene

Example

Chain a summary printout into the build step:

fixed = scene.build().report()
fixed.preview()
set_pin(pin: list[PinData])[source]ΒΆ

Set the pinning data of all the objects.

Parameters:

pin (list[PinData]) – A list of pinning data.

Example

Typically invoked internally by Scene.build(), but can be called directly to inject pinning data into a fixed scene:

fixed = scene.build()
fixed.set_pin(list_of_pin_data)
set_spin(spin: list[SpinData])[source]ΒΆ

Set the spinning data of all the objects.

Parameters:

spin (list[SpinData]) – A list of spinning data.

Example

Typically invoked internally by Scene.build(), but can be called directly to inject spin data:

fixed = scene.build()
fixed.set_spin(list_of_spin_data)
set_static(vert: tuple[ndarray, ndarray], tri: ndarray, color: ndarray, param: dict[str, list[Any]], transform_animations: list[tuple[int, TransformAnimation]] | None = None)[source]ΒΆ

Set the static mesh data.

Parameters:
  • vert (tuple[np.ndarray, np.ndarray]) – The vertices of the static mesh. The first array is the displacement map reference; the second is local positions.

  • tri (np.ndarray) – The triangle elements of the static mesh.

  • color (np.ndarray) – The colors of the static mesh.

  • param (dict[str, list[Any]]) – Parameters for the static mesh elements.

  • transform_animations – Optional list of (vert_offset, TransformAnimation) for animated static objects.

Example

Typically invoked internally by Scene.build(), but can be called directly to attach a static collider mesh:

fixed = scene.build()
fixed.set_static((ref, V), F, colors, {"area": areas})
set_stitch(ind: ndarray, w: ndarray)[source]ΒΆ

Set the stitch data.

Parameters:
  • ind (np.ndarray) – The stitch indices.

  • w (np.ndarray) – The stitch weights.

Example

Typically invoked internally by Scene.build(), but can be called directly to inject stitch constraints:

fixed = scene.build()
fixed.set_stitch(stitch_indices, stitch_weights)
static_time(time: float) ndarray[source]ΒΆ

Compute animated static vertex positions at a specific time.

Parameters:

time (float) – The time to evaluate.

Returns:

The static vertex positions at the specified time.

Return type:

np.ndarray

Example

Sample the animated collider mesh halfway through the animation:

fixed = scene.build()
V_static = fixed.static_time(0.5)
print(V_static.shape)
time(time: float) ndarray[source]ΒΆ

Compute the vertex positions at a specific time.

Parameters:

time (float) – The time to compute the vertex positions.

Returns:

The vertex positions at the specified time.

Return type:

np.ndarray

Example

Evaluate the scene midway through a pin animation:

vert_mid = fixed.time(0.5)
print(vert_mid.shape)
property tri_param: dict[str, list[Any]]ΒΆ

Get the triangle parameters.

Example

Inspect per-triangle parameter arrays captured at build time:

fixed = scene.build()
for key, values in fixed.tri_param.items():
    print(key, len(values))
vertex(transform: bool = True) ndarray[source]ΒΆ

Get the vertices of the scene.

Parameters:

transform (bool, optional) – Whether to transform the vertices. Defaults to True.

Returns:

The vertices of the scene.

Return type:

np.ndarray

Example

Fetch the initial world-space vertex positions:

vert = fixed.vertex()
print(vert.shape)
class frontend.FixedSession(session: Session)[source]ΒΆ

Bases: object

Class to manage a fixed simulation session.

Returned by Session.build(). Use it to launch the solver, monitor it, pull results, and export.

Example

Build a session from a configured Session and run it to completion, then export:

fixed_session = session.build()
fixed_session.start(blocking=True)
fixed_session.export.animation().zip()
animate(options: dict | None = None, engine: str = 'threejs') FixedSession[source]ΒΆ

Show the animation inside a Jupyter notebook.

Outside Jupyter this is a no-op. Loads all available frames from disk and exposes a slider plus a reload button to pull in frames produced after the initial load.

Parameters:
  • options (dict, optional) – The render options.

  • engine (str, optional) – The rendering engine. Defaults to "threejs".

Returns:

This session.

Return type:

FixedSession

Example

Replay frames once the run has finished (or has enough frames on disk):

session.animate()
delete()[source]ΒΆ

Delete the session.

Example

Remove the on-disk session directory before a clean rerun:

session.delete()
property export: SessionExportΒΆ

Get the session export object.

Example

Export the session shell command for reproducible replays:

session = app.session.create(fixed_scene).build()
cmd_path = session.export.shell_command(session.session.param)
finished() bool[source]ΒΆ

Check if the session has finished.

Any stderr lines present are printed as a side effect.

Returns:

True if a finished.txt marker exists in the output directory, False otherwise.

Return type:

bool

Example

Assert the run completed cleanly in CI:

if app.ci:
    assert session.finished()
property fixed_scene: FixedScene | NoneΒΆ

Get the fixed scene.

Example

Retrieve the bound scene from a fixed session:

fixed = app.session.create(scene).build()
scene_ref = fixed.fixed_scene
property get: SessionGetΒΆ

Get the session get object.

Example

Retrieve solver-emitted log channel names:

session = session.build().start(blocking=True)
channels = session.get.log.names()
property info: SessionInfoΒΆ

Get the session information.

Example

Read the session name and directory path:

session = app.session.create(fixed_scene).build()
print(session.info.name, session.info.path)
initialize_finished() bool[source]ΒΆ

Check if the session initialization has finished.

Any stderr lines present are printed as a side effect.

Returns:

True if an initialize_finish.txt marker exists in the output directory, False otherwise.

Return type:

bool

Example

Wait for solver initialization to complete before continuing:

while not session.initialize_finished():
    time.sleep(1)
is_running() bool[source]ΒΆ

Check if the solver process is running.

This method first checks the stored process handle (most reliable), then falls back to Utils.busy() for broader detection.

Returns:

True if the solver is running, False otherwise.

Return type:

bool

Example

Poll the solver state after launching non-blocking:

session.start()
while session.is_running():
    time.sleep(1)
property output: SessionOutputΒΆ

Get the session output object.

Example

Locate the solver output directory:

session = app.session.create(fixed_scene).build()
print(session.output.path)
preview(options: dict | None = None, live_update: bool = True, engine: str = 'threejs') Plot | None[source]ΒΆ

Live-view the session inside a Jupyter notebook.

Outside Jupyter this is a no-op and returns None.

Parameters:
  • options (dict, optional) – The render options.

  • live_update (bool, optional) – Whether to enable live updates. Defaults to True.

  • engine (str, optional) – The rendering engine. Defaults to "threejs".

Returns:

The plot object, or None when not running inside a Jupyter notebook.

Return type:

Optional[Plot]

Example

Kick off a run and watch it in-notebook while tailing stdout:

session.start().preview()   # live frame playback
session.stream()            # tail solver stdout
print(message)[source]ΒΆ

Print a message.

Parameters:

message (str) – The message to print.

Example

Emit a status line that renders nicely in Jupyter or stdout:

session.print("Launching solver...")
resume(frame: int = -1, force: bool = True, blocking: bool | None = None) FixedSession[source]ΒΆ

Resume the solver from a saved state.

Parameters:
  • frame (int) – The saved frame to resume from. If -1, resumes from the most recent saved frame. Defaults to -1.

  • force (bool) – Forwarded to start(). Defaults to True.

  • blocking (Optional[bool]) – Forwarded to start().

Returns:

This session.

Return type:

FixedSession

Example

Pick up where a previous run left off:

session.resume()                 # latest saved frame
session.resume(frame=120)        # specific saved frame
run(blocking: bool | None = None) FixedSession[source]ΒΆ

Idempotent launcher: ensure the solver is running.

  • If the solver is already running (this process or another host process detected via Utils.busy()), return without relaunching.

  • Otherwise, start a fresh simulation from frame 0.

To pick up a previously-saved state, use resume() explicitly, since run() never auto-resumes.

Parameters:

blocking (Optional[bool]) – If True, block until the solver finishes. If None, defaults to blocking outside Jupyter and non-blocking inside Jupyter.

Returns:

This session.

Return type:

FixedSession

Example

Ensure the solver is running without restarting it if it already is:

session.run(blocking=True)
running() bool[source]ΒΆ

Alias for is_running().

Example

Shorthand form for polling solver state:

session.start()
while session.running():
    time.sleep(1)
save_and_quit()[source]ΒΆ

Save the session and quit the solver.

Example

Ask a running solver to checkpoint and exit gracefully:

session.save_and_quit()
while session.is_running():
    time.sleep(1)
save_and_quit_file_path() str[source]ΒΆ

Get the flag-file path that signals the solver to save and quit.

If this file exists, the solver will save the session and quit. After the session is saved, the file is removed.

Example

Check for the save-and-quit sentinel file:

path = session.save_and_quit_file_path()
print(os.path.exists(path))
property session: SessionΒΆ

Get the session object.

Example

Reach back to the parent Session for its parameters:

fixed = app.session.create(fixed_scene).build()
params = fixed.session.param
start(force: bool = False, blocking: bool | None = None, load: int = 0) FixedSession[source]ΒΆ

Start the session.

Inside a Jupyter notebook the function returns immediately by default and the solver runs in the background; outside Jupyter it blocks until the solver finishes. Pass blocking explicitly to override this behavior.

If saved states exist and force is False, this delegates to resume() from the latest saved frame.

Parameters:
  • force (bool, optional) – If True, terminate any running solver and skip the auto-resume branch. Defaults to False.

  • blocking (Optional[bool], optional) – Whether to block until the solver finishes. Defaults to None (auto-detect based on Jupyter).

  • load (int, optional) – The frame number to load from saved states. Defaults to 0 (start fresh).

Returns:

The started session.

Return type:

FixedSession

Example

Launch the solver and return immediately for notebook-style monitoring:

session.start().preview()
session.stream()

Or block until finished when running as a script:

session.start(blocking=True)
stream(n_lines=40) FixedSession[source]ΒΆ

Stream the tail of the session stdout log inside a Jupyter notebook.

Outside Jupyter this is a no-op.

Parameters:

n_lines (int, optional) – The number of trailing lines to display. Defaults to 40.

Returns:

This session.

Return type:

FixedSession

Example

Kick off a run and watch both the live preview and stdout tail in a notebook cell:

session.start().preview()
session.stream()
update_options(options: dict) dict[source]ΒΆ

Return a copy of options with missing defaults filled in.

Parameters:

options (dict) – User-supplied render options.

Returns:

A new dictionary combining options with the session’s default option values.

Return type:

dict

Example

Add the session’s default render flags to a user-provided options dict:

opts = session.update_options({"flat_shading": True})
class frontend.InvisibleAdder(scene: Scene)[source]ΒΆ

Bases: object

Helper for attaching invisible colliders (walls and spheres) to a scene.

Obtained via scene.add.invisible.

Example

Add a ground wall and a ball collider together:

scene.add.invisible.wall([0, 0, 0], [0, 1, 0])
scene.add.invisible.sphere([0, 0.5, 0], 0.25)
sphere(position: list[float], radius: float) Sphere[source]ΒΆ

Add an invisible sphere to the scene.

Parameters:
  • position (list[float]) – The position of the sphere.

  • radius (float) – The radius of the sphere.

Returns:

The invisible sphere.

Return type:

Sphere

Example

Place an inverted hemispherical bowl under the cloth:

scene.add.invisible.sphere([0, 1, 0], 1.0).invert().hemisphere()
wall(position: list[float], normal: list[float]) Wall[source]ΒΆ

Add an invisible wall to the scene.

Parameters:
  • position (list[float]) – The position of the wall.

  • normal (list[float]) – The outer normal of the wall.

Returns:

The invisible wall.

Return type:

Wall

Example

Seal a simulation box with four side walls:

scene.add.invisible.wall([1, 0, 0], [-1, 0, 0])
scene.add.invisible.wall([-1, 0, 0], [1, 0, 0])
scene.add.invisible.wall([0, 0, 1], [0, 0, -1])
scene.add.invisible.wall([0, 0, -1], [0, 0, 1])
class frontend.MeshManager(cache_dir: str)[source]ΒΆ

Bases: object

Mesh manager for accessing mesh creation functions.

Example

Reach into the mesh manager via the top-level app object:

app = App.create("demo")
V, F = app.mesh.square(res=64)
V, F = app.mesh.icosphere(r=0.5, subdiv_count=4)
V, E = app.mesh.line([0, 0, 0], [1, 0, 0], n=32)
box(width: float = 1, height: float = 1, depth: float = 1) TriMesh[source]ΒΆ

Create a box mesh.

Parameters:
  • width (float) – the width of the box

  • height (float) – the height of the box

  • depth (float) – the depth of the box

Returns:

a box mesh, a pair of vertices and triangles

Return type:

TriMesh

Example

Build a unit box and subdivide it once:

V, F = app.mesh.box(width=1, height=1, depth=1).subdivide(n=1)
app.asset.add.tri("box", V, F)
circle(n: int = 32, r: float = 1, ntri: int = 1024) TriMesh[source]ΒΆ

Create a circle mesh.

Parameters:
  • n (int) – number of boundary segments

  • r (float) – radius of the circle

  • ntri (int) – approximate number of triangles filling the circle

Returns:

a circle mesh, a pair of 2D vertices and triangles

Return type:

TriMesh

Example

Produce a circular patch ready for plotting:

V, F = app.mesh.circle(n=64, r=1, ntri=2048)
app.plot.create().tri(V, F)
cone(Nr: int = 16, Ny: int = 16, Nb: int = 4, radius: float = 0.5, height: float = 2, sharpen: float = 1.0) TriMesh[source]ΒΆ

Create a cone mesh with the given radial, vertical, and bottom resolution, radius, and height.

Parameters:
  • Nr (int) – radial resolution

  • Ny (int) – vertical resolution

  • Nb (int) – bottom resolution

  • radius (float) – radius of the cone

  • height (float) – height of the cone

  • sharpen (float) – exponent applied to the radial parameter, sharpening the tip

Returns:

a cone mesh, a pair of vertices and triangles

Return type:

TriMesh

Example

Generate a pointed cone and register it:

V, F = app.mesh.cone(radius=0.5, height=2, sharpen=1.5)
app.asset.add.tri("cone", V, F)
property create: CreateManagerΒΆ

Get the mesh creation manager.

Example

Access the creation manager to build a parametric mesh:

V, F = app.mesh.create.square(res=64)
app.asset.add.tri("sheet", V, F)
cylinder(r: float, min_x: float, max_x: float, n: int)[source]ΒΆ

Create a cylinder along the x-axis.

Parameters:
  • r (float) – radius of the cylinder

  • min_x (float) – minimum x coordinate

  • max_x (float) – maximum x coordinate

  • n (int) – number of divisions along the x-axis

Returns:

(V, F) where:
  • V: ndarray of shape (N, 3) containing vertex positions

  • F: ndarray of shape (M, 3) containing triangle indices

Return type:

tuple

Example

Create a cylinder and plot it directly:

V, F = app.mesh.cylinder(r=0.5, min_x=-1, max_x=1, n=32)
app.plot.create().tri(V, F)
export(V: ndarray, F: ndarray, path: str)[source]ΒΆ

Export a mesh given by vertices V and faces F to a file.

Example

Save a generated square as an OBJ on disk:

V, F = app.mesh.square(res=32)
app.mesh.export(V, F, "sheet.obj")
icosphere(r: float = 1, subdiv_count: int = 3) TriMesh[source]ΒΆ

Create an icosphere mesh with the given radius and subdivision count.

Parameters:
  • r (float) – radius of the icosphere

  • subdiv_count (int) – number of subdivision iterations applied to the base icosahedron

Returns:

an icosphere mesh, a pair of vertices and triangles

Return type:

TriMesh

Example

Create a smooth sphere asset and pin it as a static collider:

V, F = app.mesh.icosphere(r=0.5, subdiv_count=4)
app.asset.add.tri("sphere", V, F)
line(_p0: list[float], _p1: list[float], n: int) Rod[source]ΒΆ

Create a line mesh with the given start and end points and resolution.

Parameters:
  • _p0 (list[float]) – the start point of the line

  • _p1 (list[float]) – the end point of the line

  • n (int) – the number of segments; the line has n + 1 vertices

Returns:

a line mesh, a pair of vertices and edges

Return type:

Rod

Example

Create a tall vertical rod and register it as an asset:

V, E = app.mesh.line([0, 0.01, 0], [0.01, 15, 0], 960)
app.asset.add.rod("strand", V, E)
load_tri(path: str) TriMesh[source]ΒΆ

Load a triangle mesh from a file.

Parameters:

path (str) – path to the mesh file

Returns:

a triangle mesh, a pair of vertices and triangles

Return type:

TriMesh

Example

Load a ribbon mesh from disk and register it as a tri asset:

V, F = app.mesh.load_tri("fishingknot.ply")
app.asset.add.tri("ribbon", V, F)
make_cache_dir()[source]ΒΆ

Create the cache directory if it does not already exist.

Example

Ensure the mesh cache directory is present before loading presets or importing files:

app.mesh.make_cache_dir()
V, F = app.mesh.preset("armadillo")
mobius(length_split: int = 70, width_split: int = 15, twists: int = 1, r: float = 1, flatness: float = 1, width: float = 1, scale: float = 1) TriMesh[source]ΒΆ

Create a Mobius strip mesh with the given length split, width split, twists, radius, flatness, width, and scale.

Parameters:
  • length_split (int) – number of segments along the length of the strip

  • width_split (int) – number of segments across the width of the strip

  • twists (int) – number of half-twists in the strip

  • r (float) – radius of the strip (distance from center to middle of strip)

  • flatness (float) – controls the z-extent of the strip

  • width (float) – width of the strip

  • scale (float) – overall scale factor applied to the mesh

Returns:

a Mobius strip mesh, a pair of vertices and triangles

Return type:

TriMesh

Example

Create a Mobius strip and plot it:

V, F = app.mesh.mobius(length_split=120, width_split=20, twists=1)
app.plot.create().tri(V, F)
preset(name: str) TriMesh[source]ΒΆ

Load a preset mesh, downloading it from a remote source on first use and caching it locally.

Parameters:

name (str) – the name of the preset mesh. Available names are armadillo, knot, and bunny.

Returns:

a preset mesh, a pair of vertices and triangles

Return type:

TriMesh

Raises:
  • ValueError – if name is not a recognized preset.

  • Exception – if the mesh cannot be downloaded after multiple retries.

Example

Load the armadillo, decimate it, then tetrahedralize and normalize:

V, F, T = (
    app.mesh.preset("armadillo")
    .decimate(19000)
    .tetrahedralize()
    .normalize()
)
app.plot.create().tet(V, T)
rectangle(res_x: int = 32, width: float = 2, height: float = 1, ex: list[float] | None = None, ey: list[float] | None = None, gen_uv: bool = True) TriMesh[source]ΒΆ

Create a rectangle mesh with the given resolution, width, and height, spanned by the vectors ex and ey.

Parameters:
  • res_x (int) – resolution along the ex axis

  • width (float) – the width of the rectangle

  • height (float) – the height of the rectangle

  • ex (list[float] | None) – a 3D vector to span the rectangle. Defaults to [1, 0, 0].

  • ey (list[float] | None) – a 3D vector to span the rectangle. Defaults to [0, 1, 0].

  • gen_uv (bool) – if True, include UV coordinates in vertices (Nx5), otherwise Nx3

Returns:

a rectangle mesh, a pair of vertices (Nx5: x,y,z,u,v or Nx3: x,y,z) and triangles

Return type:

TriMesh

Example

Create a tall thin ribbon lying in the xz plane:

V, F = app.mesh.rectangle(
    res_x=4, width=0.15, height=12.0,
    ex=[1, 0, 0], ey=[0, 0, 1],
)
app.asset.add.tri("ribbon", V, F)
square(res: int = 32, size: float = 2, ex: list[float] | None = None, ey: list[float] | None = None, gen_uv: bool = True) TriMesh[source]ΒΆ

Create a square mesh with the given resolution and size, spanned by the vectors ex and ey.

Parameters:
  • res (int) – resolution of the mesh

  • size (float) – the side length of the square

  • ex (list[float] | None) – a 3D vector to span the square. Defaults to [1, 0, 0].

  • ey (list[float] | None) – a 3D vector to span the square. Defaults to [0, 1, 0].

  • gen_uv (bool) – if True, include UV coordinates in vertices (Nx5), otherwise Nx3

Returns:

a square mesh, a pair of vertices and triangles

Return type:

TriMesh

Example

Build a 128-resolution sheet in the xz plane and register it:

V, F = app.mesh.square(res=128, ex=[1, 0, 0], ey=[0, 0, 1])
app.asset.add.tri("sheet", V, F)
tet_box(width: float = 1, height: float = 1, depth: float = 1) TetMesh[source]ΒΆ

Create a box tetrahedral mesh directly without subdivision.

Unlike box().tetrahedralize(), this creates a minimal tetrahedral mesh with only 8 vertices and 5 tetrahedra, preserving the original box shape without any surface subdivision.

Parameters:
  • width (float) – width of the box (x-axis)

  • height (float) – height of the box (y-axis)

  • depth (float) – depth of the box (z-axis)

Returns:

a tetrahedral box mesh

Return type:

TetMesh

Example

Register a minimal tet box (8 verts, 5 tets) as a tet asset:

V, F, T = app.mesh.tet_box(width=1, height=1, depth=1)
app.asset.add.tet("block", V, F, T)
torus(r: float = 1, R: float = 0.25, n: int = 32) TriMesh[source]ΒΆ

Create a torus mesh with the given major and minor radii and resolution.

Parameters:
  • r (float) – major radius of the torus (passed as major_radius)

  • R (float) – minor radius of the torus (passed as minor_radius)

  • n (int) – number of sections used for both the major and minor loops

Returns:

a torus mesh, a pair of vertices and triangles

Return type:

TriMesh

Example

Create a torus to use as a fixed obstacle:

V, F = app.mesh.torus(r=0.5, R=0.125)
app.asset.add.tri("torus", V, F)
class frontend.Object(asset: AssetManager, name: str)[source]ΒΆ

Bases: object

The object class.

An Object is a placed instance of a registered asset. It carries a 4x4 transform, per-vertex colors, pins, stitches, and material parameters. Instances are created via scene.add("<mesh_name>") and are typically configured through chainable methods.

Example

Chain placement, material, and pinning onto a cloth sheet:

sheet = scene.add("sheet").at(0, 0.6, 0).jitter()
sheet.param.set("strain-limit", 0.05)
sheet.pin(sheet.grab([-1, 0, -1]) + sheet.grab([1, 0, -1]))
animate_rotate(axis, angle: float, center=None, t_start: float = 0.0, t_end: float = 1.0) Object[source]ΒΆ

Animate the static object by rotating around an axis over time.

Parameters:
  • axis – [ax, ay, az] rotation axis (will be normalized).

  • angle (float) – Rotation angle in degrees.

  • center – Center of rotation [cx, cy, cz]. Defaults to object centroid.

  • t_start (float) – Start time in seconds.

  • t_end (float) – End time in seconds.

Returns:

The object with the animation added.

Return type:

Object

Example

Rotate a pinned static collider 180 degrees around y over 2 seconds:

roller = scene.add("cylinder").at(0, 0.5, 0).pin()
scene.select("cylinder").animate_rotate(
    [0, 1, 0], 180.0, t_start=0.0, t_end=2.0,
)
apply_transform(x: ndarray, translate: bool) ndarray[source]ΒΆ

Apply the object’s transformation to a set of vertices.

Parameters:
  • x (np.ndarray) – The vertices to transform (N, 3).

  • translate (bool) – Whether to include the translation component.

Returns:

The transformed vertices (N, 3).

Return type:

np.ndarray

Example

Manually map a batch of points through the object’s current transform:

obj = scene.add("sheet").at(0, 0.6, 0)
world_pts = obj.apply_transform(obj.get("V"), True)
at(x: float, y: float, z: float) Object[source]ΒΆ

Set the translation component of the transform.

Parameters:
  • x (float) – The x-coordinate.

  • y (float) – The y-coordinate.

  • z (float) – The z-coordinate.

Returns:

The object with the updated position.

Return type:

Object

Example

Drop a sheet 0.6 units above the origin:

scene.add("sheet").at(0, 0.6, 0)
bbox() tuple[ndarray, ndarray][source]ΒΆ

Compute the bounding box of the object.

Returns:

The dimensions and center of the bounding box.

Return type:

tuple[np.ndarray, np.ndarray]

Example

Read a sheet’s size and center in world space:

sheet = scene.add("sheet")
size, center = sheet.bbox()
print(size, center)
clear()[source]ΒΆ

Clear the object data.

Example

Reset an object’s transform and pin state:

obj = scene.select("sheet")
obj.clear()
obj.at(0, 0.6, 0)
collision_windows(windows: list) Object[source]ΒΆ

Set collision active time windows: list of (t_start, t_end) pairs.

Example

Enable contact only during t in [0.2, 1.0] and [2.0, 3.0]:

scene.add("sheet").collision_windows([(0.2, 1.0), (2.0, 3.0)])
color(red: float, green: float, blue: float) Object[source]ΒΆ

Set the color of the object.

Parameters:
  • red (float) – The red component.

  • green (float) – The green component.

  • blue (float) – The blue component.

Returns:

The object with the updated color.

Return type:

Object

Example

Color a falling armadillo light gray:

scene.add("armadillo").color(0.75, 0.75, 0.75)
cylinder_color(center: list[float], direction: list[float], up: list[float]) Object[source]ΒΆ

Set the color along the cylinder direction.

Parameters:
  • center (list[float]) – The center of the cylinder.

  • direction (list[float]) – The direction of the cylinder.

  • up (list[float]) – The up vector of the cylinder.

Returns:

The object with the updated color.

Return type:

Object

Example

Apply a cylinder gradient used in the twist demo:

obj = scene.add("cylinder").cylinder_color(
    [0, 0, 0], [1, 0, 0], [0, 1, 0],
)
default_color(red: float, green: float, blue: float) Object[source]ΒΆ

Set the default color of the object.

Parameters:
  • red (float) – The red component.

  • green (float) – The green component.

  • blue (float) – The blue component.

Returns:

The object with the updated default color.

Return type:

Object

Example

Override the auto-assigned hue for a dynamic object:

scene.add("sheet").default_color(1.0, 0.85, 0.0)
direction(_ex: list[float], _ey: list[float]) Object[source]ΒΆ

Set two orthogonal directions of a shell required for Baraff-Witkin model.

Parameters:
  • _ex (list[float]) – The 3D x-direction vector.

  • _ey (list[float]) – The 3D y-direction vector.

Returns:

The object with the updated direction.

Return type:

Object

Example

Pin the warp and weft directions of a flat sheet in the xz plane:

sheet = scene.add("sheet")
sheet.param.set("model", "baraff-witkin")
sheet.direction([1, 0, 0], [0, 0, 1])
direction_color(x: float, y: float, z: float) Object[source]ΒΆ

Set the color along the direction of the object.

Parameters:
  • x (float) – The x-component of the direction.

  • y (float) – The y-component of the direction.

  • z (float) – The z-component of the direction.

Returns:

The object with the updated color.

Return type:

Object

Example

Shade a cylinder along its long axis:

scene.add("cylinder").direction_color(1, 0, 0)
dyn_color(color: str, intensity: float = 0.75) Object[source]ΒΆ

Set the dynamic color of the object.

Parameters:
  • color (str) – The dynamic color type. Currently only "area" is supported.

  • intensity (float, optional) – Blend intensity of the dynamic color. Defaults to 0.75.

Returns:

The object with the updated dynamic color.

Return type:

Object

Example

Highlight stretched triangles on a trampoline sheet:

scene.add("sheet").dyn_color("area", 1.0)
property dynamic_color: EnumColorΒΆ

Get the dynamic color type.

Example

Inspect the dynamic color mode previously selected:

obj = scene.add("sheet")
print(obj.dynamic_color)
property dynamic_intensity: floatΒΆ

Get the dynamic color intensity.

Example

Read the scalar intensity used by dynamic coloring:

obj = scene.add("sheet")
print(obj.dynamic_intensity)
get(key: str) ndarray | None[source]ΒΆ

Get an associated value of the object with respect to the key.

Parameters:

key (str) – The key of the value.

Returns:

The value associated with the key.

Return type:

Optional[np.ndarray]

Example

Fetch the rest-pose vertices and face list of an asset:

sheet = scene.add("sheet")
V = sheet.get("V")
F = sheet.get("F")
grab(direction: list[float], eps: float = 0.001) list[int][source]ΒΆ

Select vertices that are furthest along a specified direction.

Parameters:
  • direction (list[float]) – The direction vector.

  • eps (float, optional) – Tolerance (in dot-product units) from the maximum. Defaults to 1e-3.

Returns:

The indices of the selected vertices.

Return type:

list[int]

Example

Pin the two top corners of a sheet to hang it:

sheet = scene.add("sheet")
sheet.pin(sheet.grab([-1, 1, 0]) + sheet.grab([1, 1, 0]))
jitter(r: float = 0.01) Object[source]ΒΆ

Add random jitter to the translation.

Parameters:

r (float, optional) – The jitter magnitude.

Returns:

The object with the jittered position.

Return type:

Object

Example

Break symmetry for a falling armadillo on a trampoline:

scene.add("armadillo").at(0, 1, 0).jitter().velocity(0, -5, 0)
mat4x4(matrix: ndarray) Object[source]ΒΆ

Set the full 4x4 transformation matrix directly.

Replaces the current transform entirely. The matrix is applied as:

world_pos = matrix[:3,:3] @ local_pos + matrix[:3,3]
Parameters:

matrix (np.ndarray) – A 4x4 transformation matrix.

Returns:

The object with the updated transform.

Return type:

Object

Example

Apply a pre-computed transform imported from Blender:

import numpy as np
M = np.eye(4)
M[:3, 3] = [0, 0.6, 0]
scene.add("sheet").mat4x4(M)
max(dim: str) float[source]ΒΆ

Get the maximum coordinate value along a specified dimension.

Parameters:

dim (str) – The dimension to get the maximum value along, either β€œx”, β€œy”, or β€œz”.

Returns:

The maximum coordinate value.

Return type:

float

Example

Check that a sheet sits above y=0:

sheet = scene.add("sheet").at(0, 0.6, 0)
assert sheet.min("y") >= 0
print(sheet.max("y"))
min(dim: str) float[source]ΒΆ

Get the minimum coordinate value along a specified dimension.

Parameters:

dim (str) – The dimension to get the minimum value along, either β€œx”, β€œy”, or β€œz”.

Returns:

The minimum coordinate value.

Return type:

float

Example

Lift an object so its lowest point sits at y=0:

obj = scene.add("armadillo")
obj.at(0, -obj.min("y"), 0)
move(delta, t_start: float = 0.0, t_end: float = 1.0) Object[source]ΒΆ

Animate the static object by a translational delta over time.

Parameters:
  • delta – [dx, dy, dz] translation delta in world space.

  • t_start (float) – Start time in seconds.

  • t_end (float) – End time in seconds.

Returns:

The object with the animation added.

Return type:

Object

Example

Slide a static (fully pinned) collider along +x between t=0 and t=2:

scene.add("sphere").at(-1, 0, 0).pin()
scene.select("sphere").move([2, 0, 0], t_start=0.0, t_end=2.0)
property name: strΒΆ

Get name of the object.

Example

Read the asset reference name back from an object:

obj = scene.add("sheet")
assert obj.name == "sheet"
normalize() Object[source]ΒΆ

Normalize the object so that it fits within a unit cube.

Returns:

The normalized object.

Return type:

Object

Example

Normalize a tetrahedral asset before scaling it into place:

arm = scene.add("armadillo").normalize().scale(0.75)
arm.at(0, 1, 0)
property obj_type: strΒΆ

Get the type of the object.

Returns:

The type of the object, either β€œrod”, β€œtri”, or β€œtet”.

Return type:

str

Example

Branch on object topology when iterating the scene:

for obj in scene.object_dict.values():
    if obj.obj_type == "tet":
        obj.param.set("young-mod", 1e6)
property object_color: list[float] | NoneΒΆ

Get the object color.

Example

Read the RGB color previously assigned via color():

obj = scene.add("sheet").color(0.9, 0.3, 0.3)
print(obj.object_color)
property object_velocity: list[float] | ndarrayΒΆ

Get the object velocity.

Example

Inspect the initial velocity set via velocity():

obj = scene.add("sheet").velocity(0, 0, -1)
print(obj.object_velocity)
property param: ParamHolderΒΆ

Get the material parameters of the object.

Returns:

The material parameters of the object.

Return type:

ParamHolder

Example

Configure the Young’s modulus through the returned holder:

scene.add("sheet").param.set("young-mod", 1e5)
pin(ind: list[int] | None = None) PinHolder[source]ΒΆ

Set specified vertices as pinned.

An object with every vertex pinned is a static collider β€” its motion is prescribed, not simulated. Use the returned PinHolder to animate it via PinHolder.move_by(), PinHolder.move_to(), or PinHolder.transform_keyframes().

Parameters:
  • ind (Optional[list[int]], optional) – The indices of the vertices to pin.

  • None (If)

  • None. (all vertices are pinned. Defaults to)

Returns:

The pin holder.

Return type:

PinHolder

Example

Static sphere that slides across the scene:

(scene.add("sphere")
      .at(-1, 0, 0)
      .pin()
      .move_by([8, 0, 0], t_start=0.0, t_end=5.0))
property pin_list: list[PinHolder]ΒΆ

Get the list of pin holders.

Example

Iterate pin holders attached to an object:

obj = scene.add("sheet")
obj.pin([0, 1, 2])
for holder in obj.pin_list:
    print(len(holder.index))
property position: list[float]ΒΆ

Get the object translation from the transform matrix.

Example

Read back the translation set by at():

obj = scene.add("sheet").at(0, 0.6, 0)
assert obj.position == [0.0, 0.6, 0.0]
report()[source]ΒΆ

Report the object data.

Example

Print a summary of an object after configuring it:

obj = scene.add("sheet").at(0, 0.6, 0)
obj.pin(obj.grab([-1, 0, -1]))
obj.report()
rotate(angle: float, axis: str) Object[source]ΒΆ

Apply rotation around a specified axis to the transform.

Parameters:
  • angle (float) – The rotation angle in degrees.

  • axis (str) – The rotation axis (β€˜x’, β€˜y’, or β€˜z’).

Returns:

The object with the updated rotation.

Return type:

Object

Example

Stand a sheet upright by rotating 90 degrees around x:

scene.add("sheet").rotate(90, "x").at(0, 0.5, 0)
scale(_scale: float) Object[source]ΒΆ

Apply uniform scale to the transform.

Parameters:

_scale (float) – The scale factor.

Returns:

The object with the updated scale.

Return type:

Object

Example

Shrink an armadillo to 0.75 of its original size:

scene.add("armadillo").scale(0.75).at(0, 1, 0)
set_uv(uv: list[ndarray]) Object[source]ΒΆ

Set the UV coordinates of the object.

Parameters:

uv (list[np.ndarray]) – The UV coordinates for each face.

Returns:

The object with the updated UV coordinates.

Return type:

Object

Example

Supply per-face UVs for a triangulated sheet:

import numpy as np
sheet = scene.add("sheet")
n_faces = len(sheet.get("F"))
uv = [np.zeros((3, 2), dtype=np.float32) for _ in range(n_faces)]
sheet.set_uv(uv)
property static: boolΒΆ

Get whether the object is static.

Example

Pinning every vertex turns an object into a static collider:

obj = scene.add("sphere").pin().object
assert obj.static
static_color(red: float, green: float, blue: float) Object[source]ΒΆ

Set the static color of the object.

Parameters:
  • red (float) – The red component.

  • green (float) – The green component.

  • blue (float) – The blue component.

Returns:

The object with the updated static color.

Return type:

Object

Example

Tint a fully pinned collider a light gray:

scene.add("sphere").pin()
scene.select("sphere").static_color(0.75, 0.75, 0.75)
stitch(name: str) Object[source]ΒΆ

Apply stitch to the object.

Parameters:

name (str) – The name of stitch registered in the asset manager.

Returns:

The stitched object.

Return type:

Object

Example

Attach a glue stitch registered earlier in the asset manager:

app.asset.add.stitch("glue", stitch_data)
scene.add("dress").stitch("glue").rotate(-90, "x")
property transform_matrix: ndarrayΒΆ

Get the current 4x4 transformation matrix.

Example

Inspect the composed translation, rotation, and scale:

obj = scene.add("sheet").at(0, 0.6, 0)
print(obj.transform_matrix)
update_static()[source]ΒΆ

Recompute whether the object is static.

When every vertex is pinned and no pin carries operations, a pull strength, or an unpin time, the object is treated as static. The result is cached on self._static.

Example

Typically invoked internally by Scene.build() after pins are finalized, but can be called directly to refresh the cached flag after editing pin data in place:

obj = scene.select("sheet")
obj.pin()
obj.update_static()
assert obj.static
property uv_coords: list[ndarray] | NoneΒΆ

Get the UV coordinates.

Example

Check whether the asset carries UV data for texturing:

obj = scene.add("sheet")
if obj.uv_coords is not None:
    print(obj.uv_coords[0].shape)
velocity(u: float, v: float, w: float, t: float = 0.0) Object[source]ΒΆ

Set the velocity of the object.

Parameters:
  • u (float) – The velocity in the x-direction.

  • v (float) – The velocity in the y-direction.

  • w (float) – The velocity in the z-direction.

  • t (float) – Time in seconds. 0.0 sets initial velocity; >0 adds a timed override.

Returns:

The object with the updated velocity.

Return type:

Object

Example

Give an armadillo a downward initial velocity of 5 m/s:

scene.add("armadillo").at(0, 1, 0).velocity(0, -5, 0)
velocity_schedule(schedule: list) Object[source]ΒΆ

Set a list of (time, [vx, vy, vz]) velocity overrides.

Example

Kick an object, then stop it a second later:

obj = scene.add("armadillo").at(0, 1, 0)
obj.velocity_schedule([
    (0.0, [0, -5, 0]),
    (1.0, [0, 0, 0]),
])
vert_color(color: ndarray) Object[source]ΒΆ

Set the vertex colors of the object.

Parameters:

color (np.ndarray) – The vertex colors.

Returns:

The object with the updated vertex colors.

Return type:

Object

Example

Paint each vertex of a sheet with its own RGB:

import numpy as np
sheet = scene.add("sheet")
n = len(sheet.get("V"))
sheet.vert_color(np.random.rand(n, 3))
vertex(translate: bool) ndarray[source]ΒΆ

Get the transformed vertices of the object.

Parameters:

translate (bool) – Whether to translate the vertices.

Returns:

The transformed vertices.

Return type:

np.ndarray

Example

Read world-space vertex positions after .at has been set:

sheet = scene.add("sheet").at(0, 0.6, 0)
world_vert = sheet.vertex(True)
class frontend.ObjectAdder(scene: Scene)[source]ΒΆ

Bases: object

Factory for introducing meshes into a Scene.

Reached as scene.add. Calling it returns an Object that can be chained with transforms, pins, and colors. Invisible colliders live under scene.add.invisible.

Example

Drop a cloth sheet and an invisible ground wall into a scene:

scene = app.scene.create()
scene.add("sheet").at(0, 0.6, 0)
scene.add.invisible.wall([0, 0, 0], [0, 1, 0])
fixed = scene.build()
invisibleΒΆ

The invisible object adder.

Type:

InvisibleAdder

class frontend.ParamDecoder[source]ΒΆ

Bases: object

Load and apply param.pickle written by the Blender addon.

Parameters are split across object-level (velocity, fTetWild hints, per-key param.set values), pin configuration, cross-stitch constraints, explicit merge pairs, invisible walls / spheres, and session-level scene settings. set_path() loads the pickle, then the apply_* methods dispatch each section.

Example

Apply a pickle to an existing scene and session (this is what BlenderApp.make() does internally):

from frontend._decoder_ import ParamDecoder

decoder = ParamDecoder().set_path("/path/to/param.pickle")
decoder.apply_to_objects(scene)
decoder.apply_pin_config(scene)
decoder.apply_invisible_colliders(scene)
fixed_scene = scene.build()
decoder.apply_to_session(session)
apply_invisible_colliders(scene, verbose: bool = False)[source]ΒΆ

Create invisible wall and sphere colliders on the scene from the loaded pickle data.

Must be called BEFORE scene.build().

Example

Add the pickle’s invisible walls and spheres just before building:

from frontend._decoder_ import ParamDecoder

decoder = ParamDecoder().set_path("/path/to/param.pickle")
decoder.apply_invisible_colliders(scene)
fixed_scene = scene.build()
apply_pin_config(scene, verbose: bool = False)[source]ΒΆ

Apply saved pin configuration to scene pin holders.

Applies unpin time, pull strength, pin group id, embedded move keyframes, and explicit pin operations (spin / scale / move_by / torque) to each pin holder that has a matching config entry. Call after the scene is populated and pins are created.

Example

Reapply pin settings after populating objects:

from frontend._decoder_ import ParamDecoder

decoder = ParamDecoder().set_path("/path/to/param.pickle")
decoder.apply_to_objects(scene)
decoder.apply_pin_config(scene, verbose=True)
apply_to_objects(scene: Scene, verbose: bool = False)[source]ΒΆ

Apply the loaded parameter data to the objects in scene.

Call set_path() first. Per-object dicts (velocity, velocity-schedule, collision-windows) are keyed by UUID; other keys are forwarded to obj.param.set. fTetWild overrides are consumed at populate-time and skipped here.

Parameters:
  • scene (Scene) – The scene to which the parameters will be applied.

  • verbose (bool) – Enable verbose logging.

Example

Load a pickle and apply per-object parameters to a populated scene:

from frontend._decoder_ import ParamDecoder

decoder = ParamDecoder().set_path("/path/to/param.pickle")
decoder.apply_to_objects(scene, verbose=True)
apply_to_session(session: Session, verbose: bool = False)[source]ΒΆ

Apply scene-level and dynamic parameters from the loaded data to session.

Call set_path() first. Static scene parameters are forwarded to session.param.set; inactive-momentum is additionally set as a dynamic hold. Dynamic parameter keyframes from dyn_param are applied via session.param.dyn(...).

Parameters:
  • session (Session) – The session to which the parameters will be applied.

  • verbose (bool) – Enable verbose logging.

Example

Apply scene-level and dynamic parameters to a freshly initialized session:

from frontend._decoder_ import ParamDecoder

decoder = ParamDecoder().set_path("/path/to/param.pickle")
session = app.session.create(scene).init(fixed_scene)
decoder.apply_to_session(session)
session.build().start()
property cross_stitch: listΒΆ
property explicit_merge_pairs: listΒΆ
set_path(filepath: str) ParamDecoder[source]ΒΆ

Load parameter data from a pickle file and cache it on this decoder.

Parameters:

filepath (str) – Path to the pickle file. Must end in .pickle.

Returns:

self for chaining.

Return type:

ParamDecoder

Example

Chain the load with a subsequent apply call:

from frontend._decoder_ import ParamDecoder

decoder = ParamDecoder().set_path("/path/to/param.pickle")
decoder.apply_to_objects(scene)
class frontend.ParamManager[source]ΒΆ

Bases: object

Class to manage simulation parameters.

Example

Configure the standard simulation parameters for a session before building it:

session = app.session.create(scene)
(
    session.param.set("frames", 120)
                 .set("dt", 0.01)
                 .set("fps", 30)
)
session = session.build()
change(value: Any) ParamManager[source]ΒΆ

Change the value of the dynamic parameter at the current time.

Parameters:

value (Any) – The new value of the dynamic parameter. May be a scalar, bool, or list/tuple of floats, depending on the key.

Returns:

The updated ParamManager object.

Return type:

ParamManager

Raises:

ValueError – If no dynamic key is currently selected.

Example

Slow playback to 10% after the third second via a dynamic key:

(
    session.param.dyn("playback")
                 .time(2.99).hold()
                 .time(3.0).change(0.1)
)
clear(key: str) ParamManager[source]ΒΆ

Reset a parameter to its default value and drop any dynamic entries for it.

Parameters:

key (str) – The parameter key.

Returns:

The updated ParamManager object.

Return type:

ParamManager

Example

Revert a single parameter back to its default after trying an override:

session.param.set("dt", 0.001)
session.param.clear("dt")
clear_all()[source]ΒΆ

Clear all parameters to their default values.

Example

Reset every parameter back to its default before configuring a new run:

session.param.clear_all()
session.param.set("frames", 60).set("dt", 0.01)
copy() ParamManager[source]ΒΆ

Copy the ParamManager object.

Returns:

The copied ParamManager object.

Return type:

ParamManager

Example

Snapshot the current parameters before tweaking one of them:

baseline = session.param.copy()
session.param.set("dt", 0.005)
dyn(key: str) ParamManager[source]ΒΆ

Select the current dynamic parameter key and reset the internal time cursor.

Parameters:

key (str) – The dynamic parameter key.

Returns:

The updated ParamManager object.

Return type:

ParamManager

Raises:

ValueError – If key does not exist.

Example

Flip gravity between t=1s and t=2s, then restore it:

g = session.param.get("gravity")
(session.param.dyn("gravity")
              .time(1.0).hold()
              .time(1.5).change([-x for x in g])
              .time(2.0).change(g))
export(path: str)[source]ΒΆ

Export the parameters to param.toml (and dyn_param.txt when present).

In fast-check mode, frames is forced to 1.

Parameters:

path (str) – The path to the export directory.

Example

Write the parameters alongside a session directory for inspection or external launching:

session.param.export(fixed_session.info.path)
get(key: str | None = None) Any[source]ΒΆ

Get the value of a parameter.

Parameters:

key (Optional[str]) – The parameter key. Must be specified.

Returns:

The value of the parameter.

Return type:

Any

Raises:

ValueError – If key is None.

Example

Read the current gravity vector so a dynamic override can flip its sign later:

g = session.param.get("gravity")
print(g)
hold() ParamManager[source]ΒΆ

Hold the current value of the dynamic parameter at the current time.

Returns:

The updated ParamManager object.

Return type:

ParamManager

Raises:

ValueError – If no dynamic key is currently selected.

Example

Keep playback steady until t=2.99s, then drop it at t=3.0s:

(
    session.param.dyn("playback")
                 .time(2.99).hold()
                 .time(3.0).change(0.1)
)
items()[source]ΒΆ

Get all parameter items.

Returns:

The parameter items.

Return type:

ItemsView

Example

Inspect every parameter currently configured on the session:

for key, value in session.param.items():
    print(f"{key} = {value}")
set(key: str, value: Any | None = None) ParamManager[source]ΒΆ

Set a parameter value.

If value is None, the parameter is set to True.

Parameters:
  • key (str) – The parameter key. Must not contain an underscore; use - instead.

  • value (Any, optional) – The parameter value. Defaults to None (interpreted as True).

Returns:

The updated ParamManager object.

Return type:

ParamManager

Raises:

ValueError – If key contains an underscore or does not exist.

Example

Chain several .set calls to configure a session. Keys use hyphens, never underscores:

(
    session.param.set("frames", 250)
                 .set("dt", 0.01)
                 .set("fps", 30)
                 .set("min-newton-steps", 32)
                 .set("gravity", [0, 0, 0])
)
time(time: float) ParamManager[source]ΒΆ

Advance the current time cursor for the dynamic parameter.

Parameters:

time (float) – The new current time. Must be strictly greater than the previous value.

Returns:

The updated ParamManager object.

Return type:

ParamManager

Raises:

ValueError – If time is not strictly increasing.

Example

Advance the cursor between two dynamic-value updates:

(
    session.param.dyn("playback")
                 .time(1.0).hold()
                 .time(2.0).change(0.5)
)
class frontend.Plot(engine: str, param: PlotParam)[source]ΒΆ

Bases: object

A plot backed by either the pythreejs or software rasterizer engine.

Example

Construct a plot via PlotManager.create() and draw a mesh:

plot = app.plot.create()
V, F = app.mesh.square(res=32)
plot.tri(V, F)
curve(vert: ndarray, _edge: ndarray = array([], dtype=float64), color: ndarray = array([], dtype=float64), param_override: dict | None = None) Plot[source]ΒΆ

Plot a curve as a sequence of connected line segments.

If _edge is empty, a closed loop is built by connecting each vertex to the next one (with wrap-around).

Parameters:
  • vert (np.ndarray) – The vertex positions (Nx3 or Nx2) of the curve. 2D inputs are padded to 3D by appending a zero column.

  • _edge (np.ndarray) – Optional explicit edge elements (Ex2) of the curve.

  • color (np.ndarray) – Per-vertex colors (Nx3) with each value in [0, 1].

  • param_override (Optional[dict]) – Fields to override on a copy of the plot parameters.

Returns:

self for method chaining.

Return type:

Plot

Example

Plot a parametric 2D curve as a closed loop:

N = 500
t = np.linspace(0, 2 * np.pi, N, endpoint=False)
r = 0.85 * np.cos(10 * t) + 1
P = np.column_stack([r * np.cos(t), r * np.sin(t)])
app.plot.create().curve(P)
edge(vert: ndarray, edge: ndarray, color: ndarray, param_override: dict | None = None) Plot[source]ΒΆ

Plot a set of edges.

Parameters:
  • vert (np.ndarray) – The vertices (Nx3) used by the edges.

  • edge (np.ndarray) – The edge elements (Ex2) as pairs of vertex indices.

  • color (np.ndarray) – Per-vertex colors (Nx3) with each value in [0, 1].

  • param_override (Optional[dict]) – Fields to override on a copy of the plot parameters.

Returns:

self for method chaining.

Return type:

Plot

Example

Draw just the edges of a mesh colored uniformly:

V, F = app.mesh.icosphere(r=1.0, subdiv_count=2)
edges = np.vstack([F[:, [0, 1]], F[:, [1, 2]], F[:, [2, 0]]])
colors = np.tile([1.0, 0.3, 0.3], (len(V), 1))
app.plot.create().edge(V, edges, colors)
is_jupyter_notebook() bool[source]ΒΆ

Return True if the code is running inside a Jupyter notebook.

Example

Skip expensive mesh conversion when not in a notebook:

plot = app.plot.create()
if plot.is_jupyter_notebook():
    plot.tri(V, F)
plot(vert: ndarray, color: ndarray = array([], dtype=float64), tri: ndarray = array([], dtype=float64), seg: ndarray = array([], dtype=float64), pts: ndarray = array([], dtype=float64), param_override: dict | None = None) Plot[source]ΒΆ

Plot a mesh with optional triangles, edges, and points.

Rendering is only performed when running inside a Jupyter notebook.

Parameters:
  • vert (np.ndarray) – The vertices (Nx3) of the mesh.

  • color (np.ndarray) – Per-vertex colors (Nx3) with each value in [0, 1].

  • tri (np.ndarray) – The triangle elements (Fx3) of the mesh.

  • seg (np.ndarray) – The edge elements (Ex2) of the mesh.

  • pts (np.ndarray) – The point element indices (Px1) of the mesh.

  • param_override (Optional[dict]) – Fields to override on a copy of the plot parameters.

Returns:

self for method chaining.

Return type:

Plot

Example

Render triangles and edges together with a camera override:

plot = app.plot.create()
plot.plot(V, tri=F, seg=edges, param_override={"fov": 30})
point(vert: ndarray, param_override: dict | None = None) Plot[source]ΒΆ

Plot a set of points.

Parameters:
  • vert (np.ndarray) – The vertex positions (Nx3) of the points.

  • param_override (Optional[dict]) – Fields to override on a copy of the plot parameters.

Returns:

self for method chaining.

Return type:

Plot

Example

Scatter a random point cloud:

pts = np.random.rand(200, 3)
app.plot.create().point(pts)
tet(vert: ndarray, tet: ndarray, axis: int = 0, cut: float = 0.5, color: ndarray = array([], dtype=float64), param_override: dict | None = None) Plot[source]ΒΆ

Plot a tetrahedral mesh by rendering the cut surface as triangles.

Tetrahedra whose centroid lies past cut along axis contribute their faces; internal faces shared by two such tetrahedra are removed, leaving only the visible surface of the cut region. flat_shading is forced on unless param_override explicitly sets it.

Parameters:
  • vert (np.ndarray) – The vertices (Nx3) of the mesh.

  • tet (np.ndarray) – The tetrahedral elements (Tx4) of the mesh.

  • axis (int) – The axis index (0, 1, or 2) along which to cut.

  • cut (float) – The cut ratio in [0, 1] along axis.

  • color (np.ndarray) – Per-vertex colors (Nx3) with each value in [0, 1].

  • param_override (Optional[dict]) – Fields to override on a copy of the plot parameters.

Returns:

self for method chaining.

Return type:

Plot

Example

Slice an armadillo tet mesh along the x axis and preview it:

V, F, T = app.mesh.preset("armadillo").decimate(19000).tetrahedralize()
app.plot.create().tet(V, T, axis=0, cut=0.5)
tri(vert: ndarray, tri: ndarray, stitch: tuple[ndarray, ndarray] = (array([], dtype=float64), array([], dtype=float64)), color: ndarray = array([], dtype=float64), param_override: dict | None = None) Plot[source]ΒΆ

Plot a triangle mesh, optionally with visualized stitch connections.

If stitch is provided, additional vertices and edges are generated to visualize each stitch as a line segment.

Parameters:
  • vert (np.ndarray) – The vertices (Nx3 or Nx2) of the mesh. 2D inputs are padded to 3D by appending a zero column.

  • tri (np.ndarray) – The triangle elements (Fx3) of the mesh.

  • stitch (tuple[np.ndarray, np.ndarray]) – Stitch data as a pair of arrays: an index array (Sx3) and a weight array (Sx2).

  • color (np.ndarray) – Per-vertex colors (Nx3) with each value in [0, 1].

  • param_override (Optional[dict]) – Fields to override on a copy of the plot parameters.

Returns:

self for method chaining.

Return type:

Plot

Raises:

ValueError – If tri does not have exactly 3 columns.

Example

Plot a tetrahedralized armadillo’s surface triangles:

V, F, T = app.mesh.preset("armadillo").decimate(19000).tetrahedralize()
app.plot.create().tri(V, F)
update(vert: ndarray | None = None, color: ndarray | None = None, recompute_normals: bool = True)[source]ΒΆ

Update the cached vertex or color buffers and forward to the engine.

Parameters:
  • vert (Optional[np.ndarray]) – New vertex positions. Only the leading len(vert) rows of the cached buffer are overwritten.

  • color (Optional[np.ndarray]) – New per-vertex colors. Only the leading len(color) rows of the cached buffer are overwritten.

  • recompute_normals (bool) – Whether to recompute normals after the update.

Example

Animate a cached plot by pushing new vertex positions each frame:

plot = app.plot.create().tri(V, F)
for t in range(10):
    V_t = V + np.array([0, 0.01 * t, 0])
    plot.update(vert=V_t)
class frontend.PlotManager[source]ΒΆ

Bases: object

Factory for creating Plot instances with shared parameters.

Example

Reach the plot manager from the app and render a sheet:

app = App.create("demo")
V, F = app.mesh.square(res=64)
app.plot.create().tri(V, F)
create(engine: str = 'threejs') Plot[source]ΒΆ

Create a new plot using the given rendering engine.

Parameters:

engine (str) – The rendering engine to use. Either "threejs" or "software".

Returns:

A new plot bound to this manager’s parameters.

Return type:

Plot

Example

Create a plot and chain a triangle viewer call:

V, F = app.mesh.icosphere(r=1.0, subdiv_count=3)
app.plot.create().tri(V, F)
is_jupyter_notebook() bool[source]ΒΆ

Return True if the code is running inside a Jupyter notebook.

Example

Only build a plot when running inside a notebook:

if app.plot.is_jupyter_notebook():
    app.plot.create().tri(V, F)
class frontend.Rod(iterable=(), /)[source]ΒΆ

Bases: tuple[ndarray, ndarray]

A class representing a rod mesh.

A rod mesh is a pair of vertices and edges. The first element of the tuple is the array of vertices and the second element is the array of edges.

Example

Obtain a rod from the line helper and unpack it:

rod = app.mesh.line([0, 0, 0], [1, 0, 0], n=16)
V, E = rod
app.asset.add.rod("strand", V, E)
normalize() Rod[source]ΒΆ

Normalize the rod mesh in place so that the maximum bounding box extent is 1.

Example

Normalize a freshly built rod and then scale it:

rod = app.mesh.line([0, 0, 0], [10, 0, 0], n=32).normalize().scale(0.5)
V, E = rod
scale(scale_x: float, scale_y: float | None = None, scale_z: float | None = None) Rod[source]ΒΆ

Scale the rod mesh in place with the given scaling factors.

Parameters:
  • scale_x (float) – scaling factor for the x-axis

  • scale_y (float | None) – scaling factor for the y-axis. If None, scale_x is used.

  • scale_z (float | None) – scaling factor for the z-axis. If None, scale_x is used.

Returns:

this rod with scaled vertices

Return type:

Rod

Example

Shrink a rod uniformly to a quarter of its original length:

rod = app.mesh.line([0, 0, 0], [4, 0, 0], n=32).scale(0.25)
V, E = rod
class frontend.Scene(name: str, plot: PlotManager | None, asset: AssetManager)[source]ΒΆ

Bases: object

A scene class.

A Scene collects objects, pins, invisible colliders, and stitch data, then compiles them into a FixedScene via build().

Example

Build a tiny drape scene from registered assets:

scene = app.scene.create()
sheet = scene.add("sheet").at(0, 0.6, 0)
sheet.pin(sheet.grab([-1, 0, -1]) + sheet.grab([1, 0, -1]))
scene.add("sphere").at(0, 0, 0).pin()
fixed = scene.build().report()
addΒΆ

The object adder.

Type:

ObjectAdder

property asset_manager: AssetManagerΒΆ

Get the asset manager.

Example

Reach into the asset manager attached to this scene:

mgr = scene.asset_manager
print(mgr.list())
build(progress_callback=None) FixedScene[source]ΒΆ

Build the fixed scene from the current scene.

Parameters:

progress_callback – Optional callable f(fraction, step_info) invoked as the build progresses.

Returns:

The built fixed scene.

Return type:

FixedScene

Example

Compile the scene and chain a summary print:

fixed = scene.build().report()
session = app.session.create(fixed).build()
clear() Scene[source]ΒΆ

Clear all objects from the scene.

Returns:

The cleared scene.

Return type:

Scene

Example

Start from a blank slate before re-adding objects:

scene.clear()
scene.add("sheet").at(0, 0.6, 0)
infoΒΆ

The scene information.

Type:

SceneInfo

max(axis: str) float[source]ΒΆ

Get the maximum value of the scene along a specific axis.

Parameters:

axis (str) – The axis to get the maximum value along, either β€œx”, β€œy”, or β€œz”.

Returns:

The maximum vertex coordinate along the specified axis.

Return type:

float

Example

Place a ceiling wall just above the highest vertex:

y_max = scene.max("y")
scene.add.invisible.wall([0, y_max + 0.01, 0], [0, -1, 0])
min(axis: str) float[source]ΒΆ

Get the minimum value of the scene along a specific axis.

Parameters:

axis (str) – The axis to get the minimum value along, either β€œx”, β€œy”, or β€œz”.

Returns:

The minimum vertex coordinate along the specified axis.

Return type:

float

Example

Place a ground wall just below the lowest vertex:

y_min = scene.min("y")
scene.add.invisible.wall([0, y_min - 0.01, 0], [0, 1, 0])
property object_dict: dict[str, Object]ΒΆ

Get the object dictionary.

Example

Iterate every added object by its reference name:

for name, obj in scene.object_dict.items():
    print(name, obj.obj_type)
select(name: str) Object[source]ΒΆ

Select an object from the scene by its name.

Parameters:

name (str) – The reference name of the object to select.

Returns:

The selected object.

Return type:

Object

Example

Adjust an already-added object by ref name:

scene.add("sheet")
sheet = scene.select("sheet")
sheet.at(0, 0.6, 0)
set_explicit_merge_pairs(pairs: list[dict])[source]ΒΆ

Set explicit per-vertex merge pairs captured at snap time.

Each entry must contain non-empty source_uuid and target_uuid; missing keys raise ValueError.

Example

Typically invoked internally by the Blender add-on decoder, but can be called directly to wire up per-vertex merge constraints:

scene.set_explicit_merge_pairs([
    {"source_uuid": "a", "target_uuid": "b",
     "source_vert": 0, "target_vert": 12},
])
set_surface_map(name: str, tri_indices: ndarray, coefs: ndarray, surf_tri: ndarray)[source]ΒΆ

Store frame-embedding surface mapping for a tetrahedralized object.

Parameters:
  • name – Object UUID key.

  • tri_indices – Closest triangle index in tet surface per original vertex (N,).

  • coefs – Frame coefficients (c1, c2, c3) per original vertex (N, 3).

  • surf_tri – Surface triangles of the tet mesh (Q, 3).

Example

Typically invoked internally by the Blender add-on decoder when a TetMesh is registered, but can be called directly to attach a surface map:

scene.set_surface_map("obj-uuid", tri_idx, coefs, surf_tri)
property sphere_list: list[Sphere]ΒΆ

Get the list of spheres.

Example

Enumerate invisible sphere colliders currently on the scene:

for sphere in scene.sphere_list:
    print(sphere.entry)
property wall_list: list[Wall]ΒΆ

Get the list of walls.

Example

Enumerate invisible wall colliders currently on the scene:

for wall in scene.wall_list:
    print(wall.normal, wall.entry)
class frontend.SceneDecoder(filepath: str, asset_manager: AssetManager, mesh_manager: MeshManager)[source]ΒΆ

Bases: object

Decode data.pickle written by the Blender addon into scene objects.

Handles canonical mesh deduplication, per-instance transforms, cached tetrahedralization for SOLID groups, pin registration with Blender-to-sim surface mapping, UV assignment for SHELL groups, and rod / static groups. Used internally by BlenderApp.

Example

Drive the decoder directly to populate a scene (this is what BlenderApp.populate() does internally):

from frontend._asset_ import AssetManager
from frontend._mesh_ import MeshManager
from frontend._decoder_ import SceneDecoder

assets = AssetManager()
meshes = MeshManager("/tmp/cache")
decoder = SceneDecoder("/path/to/data.pickle", assets, meshes)
decoder.populate_objects(scene, verbose=True)
populate_objects(scene: Scene, verbose: bool = False, progress_callback=None, ftetwild_by_uuid: dict | None = None) Scene[source]ΒΆ

Populate scene with objects from the decoder’s pickle data.

Handles STATIC, SOLID, SHELL, and ROD groups, including canonical mesh deduplication by UUID, per-instance transforms, cached tetrahedralization (with per-UUID fTetWild overrides), pin registration with Blender-to-sim surface mapping, and stitches.

Parameters:
  • scene (Scene) – The scene to populate.

  • verbose (bool) – Enable verbose logging.

  • progress_callback – Optional callable fn(progress: float, info: str) invoked during loading. progress is in [0.0, 1.0].

  • ftetwild_by_uuid (dict | None) – Optional mapping of object UUID to fTetWild keyword arguments used during tetrahedralization.

Returns:

The populated scene.

Return type:

Scene

Example

Populate a fresh scene from a Blender pickle:

from frontend._asset_ import AssetManager
from frontend._mesh_ import MeshManager
from frontend._decoder_ import SceneDecoder

decoder = SceneDecoder(
    "/path/to/data.pickle", AssetManager(), MeshManager("/tmp/cache"),
)
decoder.populate_objects(scene, verbose=True)
fixed_scene = scene.build()
class frontend.SceneInfo(name: str, scene: Scene)[source]ΒΆ

Bases: object

Lightweight metadata handle carrying the scene name.

Example

Look up the name of the scene you are currently editing:

print(scene.info.name)
class frontend.SceneManager(plot: PlotManager | None, asset: AssetManager)[source]ΒΆ

Bases: object

SceneManager class. Use this to manage scenes.

Example

Create a scene through the app’s scene manager and build it:

app = App.create("demo")
scene = app.scene.create()
scene.add("sheet").at(0, 0.6, 0)
fixed = scene.build()
clear()[source]ΒΆ

Clear all the scenes in the manager.

Example

Remove every scene before rebuilding from scratch:

app.scene.clear()
scene = app.scene.create()
create(name: str = '') Scene[source]ΒΆ

Create a new scene.

If a scene with the given name already exists, it is replaced.

Parameters:

name (str) – The name of the scene to create. If empty, defaults to "scene".

Returns:

The created scene.

Return type:

Scene

Example

Create two named scenes side by side:

cloth_scene = app.scene.create("cloth")
rods_scene = app.scene.create("rods")
list() list[str][source]ΒΆ

List all the scenes in the manager.

Returns:

A list of scene names.

Return type:

list[str]

Example

Inspect which scenes are currently registered:

for name in app.scene.list():
    print(name)
remove(name: str)[source]ΒΆ

Remove a scene from the manager.

Parameters:

name (str) – The name of the scene to remove.

Example

Drop a scene that is no longer needed:

app.scene.remove("cloth")
select(name: str, create: bool = True) Scene[source]ΒΆ

Select a scene.

If the scene exists, it is returned. If it does not exist and create is True, a new scene is created and returned.

Parameters:
  • name (str) – The name of the scene to select.

  • create (bool, optional) – Whether to create a new scene if it does not exist. Defaults to True.

Returns:

The selected (or newly created) scene.

Return type:

Scene

Example

Fetch an existing scene by name, creating it lazily:

scene = app.scene.select("cloth")
scene.add("sheet")
class frontend.Session(app_name: str, app_root: str, proj_root: str, data_dirpath: str, name: str, autogenerated: int | None = None)[source]ΒΆ

Bases: object

Class to setup a simulation session.

Instances are created via SessionManager.create(), configured via the param manager, then finalized with build() to produce a FixedSession.

Example

Configure a session and build it into a runnable fixed session:

session = app.session.create(scene)
session.param.set("frames", 120).set("dt", 0.01)
fixed_session = session.build()
fixed_session.start(blocking=True)
property app_name: strΒΆ

Get the application name.

Example

Read the owning application name:

session = app.session.create(scene)
print(session.app_name)
property app_root: strΒΆ

Get the application root directory.

Example

Locate the application root on disk:

session = app.session.create(scene)
print(session.app_root)
build() FixedSession[source]ΒΆ

Build and persist a FixedSession from this session.

Pickles the built session into its directory and creates a symlink (or a .txt fallback on Windows without symlink privileges) under the data dir for convenient access.

Returns:

The newly built fixed session.

Return type:

FixedSession

Example

Finalize a configured session and start it:

session.param.set("frames", 60).set("dt", 0.01)
fixed_session = session.build()
fixed_session.start(blocking=True)
property fixed_scene: FixedScene | NoneΒΆ

Get the fixed scene.

Returns:

The fixed scene object.

Return type:

Optional[FixedScene]

Example

Inspect the bound scene before finalizing:

session = app.session.create(scene)
print(session.fixed_scene)
property fixed_session: FixedSession | NoneΒΆ

Get the fixed session.

Returns:

The fixed session object, or None if build() has not been called yet.

Return type:

Optional[FixedSession]

Example

Access the built runnable session after calling build():

session = app.session.create(scene)
session.build()
fixed = session.fixed_session
init(scene: FixedScene) Session[source]ΒΆ

Attach a fixed scene to this session.

Parameters:

scene (FixedScene) – The fixed scene.

Returns:

This session, for chaining.

Return type:

Session

Example

SessionManager.create() calls this internally, but it can also be used to re-bind a scene to an existing session:

session.init(scene).param.set("frames", 60)
property name: strΒΆ

Get the session name.

Example

Retrieve the session name for logging or display:

session = app.session.create(scene)
print(session.name)
property param: ParamManagerΒΆ

Get the session parameter manager.

Example

Configure solver parameters before building the session:

session = app.session.create(scene)
session.param.set("frames", 120).set("dt", 0.01)
property proj_root: strΒΆ

Get the project root directory.

Example

Print the project root for the current session:

session = app.session.create(scene)
print(session.proj_root)
class frontend.SessionExport(fixed_session: FixedSession)[source]ΒΆ

Bases: object

Class to handle session export operations.

Example

Export every simulated frame and zip the output directory:

session.start(blocking=True)
session.export.animation().zip()
animation(path: str = '', ext='ply', include_static: bool = True, clear: bool = False, options: dict | None = None) Zippable[source]ΒΆ

Export the animation frames.

If no frames are available yet, waits for the simulation if it is running, otherwise returns early. When ffmpeg is available and rendered PNGs are produced, also encodes an frame.mp4 video in the export directory.

Parameters:
  • path (str) – The path to the export directory. If empty, a default path is used.

  • ext (str, optional) – The file extension. Defaults to "ply".

  • include_static (bool, optional) – Whether to include the static mesh. Defaults to True.

  • clear (bool, optional) – Whether to clear the existing files. Defaults to False.

  • options (dict, optional) – Additional arguments passed to the renderer.

Returns:

A handle to the export directory that can be zipped.

Return type:

Zippable

Example

Export every frame as PLY and zip the result:

session.start(blocking=True)
session.export.animation().zip()
frame(path: str = '', frame: int | None = None, include_static: bool = True, options: dict | None = None, delete_exist: bool = False) FixedSession[source]ΒΆ

Export a specific frame.

Parameters:
  • path (str) – The path to the export file.

  • frame (Optional[int], optional) – The frame number. If None, the latest available frame is used. Defaults to None.

  • include_static (bool, optional) – Whether to include the static mesh. Defaults to True.

  • options (dict, optional) – Additional arguments passed to the renderer.

  • delete_exist (bool, optional) – Whether to delete the existing file. Defaults to False.

Returns:

The owning fixed session object.

Return type:

FixedSession

Example

Export just the latest frame as an OBJ file:

session.export.frame("latest.obj")
shell_command(param: ParamManager) str[source]ΒΆ

Generate a platform-specific launcher script for the solver.

On Windows, writes a command.bat file; on Linux/macOS, writes a command.sh script (marked executable).

Parameters:

param (ParamManager) – The simulation parameters.

Returns:

The path to the generated launcher script.

Return type:

str

Example

Regenerate the solver launcher alongside a session (useful for re-running from the command line):

path = session.export.shell_command(session.session.param)
print(path)
class frontend.SessionGet(fixed_session: FixedSession)[source]ΒΆ

Bases: object

Class to handle session data retrieval operations.

Example

Pull the most recent vertex buffer and a log channel from a running or completed session:

vert, frame = session.get.vertex()
per_frame_ms = session.get.log.numbers("time-per-frame")
command() str | None[source]ΒΆ

Get the path to the solver launcher script.

On Windows this points at command.bat; elsewhere at command.sh.

Returns:

The path to the launcher script if it exists, None otherwise.

Return type:

Optional[str]

Example

Print the launcher path so it can be re-run from a shell:

path = session.get.command()
if path:
    print(path)
latest_frame() int[source]ΒΆ

Get the latest frame number.

Returns:

The latest frame number.

Return type:

int

Example

Poll the most recent frame index while the solver runs:

frame = session.get.latest_frame()
print(f"solver is on frame {frame}")
property log: SessionLogΒΆ

Get the session log object.

Example

Fetch the list of emitted log channels:

session = session.build().start(blocking=True)
channels = session.get.log.names()
nvidia_smi() None[source]ΒΆ

Read and print the exported nvidia-smi outputs.

Reads both nvidia-smi.txt and nvidia-smi-q.txt from the nvidia-smi directory and prints their concatenated contents.

Example

Inspect the GPU state captured at the start of a run:

session.get.nvidia_smi()
param_summary() list[str][source]ΒΆ

Get the parameter summary from the param_summary.txt file.

Returns:

The lines from the parameter summary file, or empty list if file doesn’t exist.

Return type:

list[str]

Example

Print the parameter summary captured by the solver at launch:

for line in session.get.param_summary():
    print(line)
saved() list[int][source]ΒΆ

Get the list of saved frame numbers.

Returns:

The list of saved frame numbers.

Return type:

list[int]

Example

Resume from the newest saved state if any exist:

saved = session.get.saved()
if saved:
    session.resume(max(saved))
vertex(n: int | None = None) tuple[ndarray, int] | None[source]ΒΆ

Get the vertex data for a specific frame.

Parameters:

n (Optional[int], optional) – The frame number. If None, the latest frame is returned. Defaults to None.

Returns:

A tuple (vertices, frame) where vertices has shape (N, 3) and dtype float32, or None if no data is available.

Return type:

Optional[tuple[np.ndarray, int]]

Example

Read the latest exported vertex buffer from disk:

result = session.get.vertex()
if result is not None:
    vert, frame = result
    print(vert.shape, frame)
vertex_frame_count() int[source]ΒΆ

Get the highest frame index that has an exported vertex buffer.

Returns:

The highest frame index found in output/vert_*.bin, or 0 if none exist.

Return type:

int

Example

Wait until at least one frame is on disk before replaying:

while session.get.vertex_frame_count() == 0:
    time.sleep(0.5)
class frontend.SessionInfo(name: str)[source]ΒΆ

Bases: object

Class to store session information.

Example

Read the on-disk directory path for a built session:

fixed_session = session.build()
print(fixed_session.info.name)
print(fixed_session.info.path)
property name: strΒΆ

Get the name of the session.

Example

Inspect the session name before starting a run:

session = app.session.create(fixed_scene).build()
print(session.info.name)
property path: strΒΆ

Get the path to the session directory.

Example

Read the on-disk session directory after building:

session = app.session.create(fixed_scene).build()
print(session.info.path)
set_path(path: str) SessionInfo[source]ΒΆ

Set the path to the session directory.

Parameters:

path (str) – The path to the session directory.

Returns:

This instance, for chaining.

Return type:

SessionInfo

Example

Normally this is called by SessionManager during session construction. A direct call may be useful when relocating an existing session on disk:

info = SessionInfo("my_run")
info.set_path("/data/sessions/my_run")
print(info.path)
class frontend.SessionManager(app_name: str, app_root: str, proj_root: str, data_dirpath: str)[source]ΒΆ

Bases: object

Class to manage simulation sessions.

Example

Create a session from a built scene and launch it:

session = app.session.create(scene)
session.param.set("frames", 60).set("dt", 0.01)
session = session.build()
session.start(blocking=True)
clear(force: bool = True)[source]ΒΆ

Clear all sessions.

Parameters:

force (bool, optional) – Whether to force clearing.

Example

Remove every session and any running solver before starting fresh:

app.session.clear(force=True)
create(scene: FixedScene, name: str = '') Session[source]ΒΆ

Create a new session.

If name is empty, an auto-generated name is used: "session" for the first call, then "session-1", "session-2", … for subsequent calls.

Parameters:
  • scene (FixedScene) – The scene object.

  • name (str) – The name of the session. Defaults to "" (auto-generated).

Returns:

The created session.

Return type:

Session

Raises:

Exception – If the scene has violations (self-intersections, contact-offset violations, etc.).

Example

Create a session from a built fixed scene and configure it:

scene = app.scene.create()
scene.add("sheet").at(0, 0.5, 0)
scene = scene.build()

session = app.session.create(scene)
session.param.set("frames", 60).set("dt", 0.01)
session = session.build()
delete(name: str, force: bool = True)[source]ΒΆ

Delete a session.

Parameters:
  • name (str) – The name of the session.

  • force (bool, optional) – Whether to force deletion.

Example

Tear down a named session, terminating the solver if it is still running:

app.session.delete("run-A", force=True)
list()[source]ΒΆ

List all sessions.

Returns:

The sessions.

Return type:

dict

Example

Print the names of every session currently tracked by the app:

for name in app.session.list():
    print(name)
select(name: str = 'session')[source]ΒΆ

Select an existing session by name.

Parameters:

name (str) – The name of the session. Defaults to "session".

Returns:

The selected session.

Return type:

Session

Raises:

ValueError – If no session with the given name exists.

Example

Re-fetch a previously-created session by name:

app.session.create(scene, name="run-A")
session = app.session.select("run-A")
class frontend.SessionOutput(session: FixedSession)[source]ΒΆ

Bases: object

Class to handle session output operations.

Example

Locate the solver output directory for a built session (used by exporters and log readers):

print(session.output.path)
property path: strΒΆ

Get the path to the output directory.

Example

Locate the solver output directory for post-processing:

session = session.build().start(blocking=True)
output_dir = session.output.path
class frontend.Sphere[source]ΒΆ

Bases: object

An invisible sphere class.

Example

Add an inverted hemispherical bowl as a collider:

scene.add.invisible.sphere([0, 1, 0], 1.0).invert().hemisphere()
add(pos: list[float], radius: float) Sphere[source]ΒΆ

Add an invisible sphere information.

Parameters:
  • pos (list[float]) – The position of the sphere.

  • radius (float) – The radius of the sphere.

Returns:

The sphere.

Return type:

Sphere

Example

Instantiate a unit sphere at the origin:

sphere = Sphere().add([0, 0, 0], 1.0)
property entry: list[tuple[list[float], float, float]]ΒΆ

Get the sphere entries.

Example

Inspect the keyframed (position, radius, time) list:

sphere = scene.add.invisible.sphere([0, 0, 0], 0.5)
for pos, r, t in sphere.entry:
    print(t, r, pos)
get_entry() list[tuple[list[float], float, float]][source]ΒΆ

Get the time-dependent sphere entries.

Example

Iterate over an animated sphere’s keyframes (position, radius, time):

sphere = scene.add.invisible.sphere([0, 0, 0], 0.25)
sphere.transform_to([0, 0.5, 0], 0.5, 2.0)
for pos, r, t in sphere.get_entry():
    print(t, r, pos)
hemisphere() Sphere[source]ΒΆ

Turn the sphere into a hemisphere, so the top half becomes empty, like a bowl.

Example

Cup-shaped collider by combining invert and hemisphere:

scene.add.invisible.sphere([0, 1, 0], 1.0).invert().hemisphere()
interp(transition: str) Sphere[source]ΒΆ

Set the transition type for the sphere.

Parameters:

transition (str) – The transition type. Defaults to "linear" if never set.

Returns:

The sphere.

Return type:

Sphere

Example

Ease the sphere motion with a smoothstep:

sphere = scene.add.invisible.sphere([0, 0, 0], 0.5)
sphere.move_to([0, 0.5, 0], 2.0).interp("smooth")
invert() Sphere[source]ΒΆ

Invert the sphere, so the inside becomes empty and the outside becomes solid.

Example

Trap objects inside an inverted sphere boundary:

scene.add.invisible.sphere([0, 0, 0], 2.0).invert()
property is_hemisphere: boolΒΆ

Get whether sphere is hemisphere.

Example

Check the hemisphere flag on a collider:

sphere = scene.add.invisible.sphere([0, 0, 0], 0.5)
assert sphere.is_hemisphere in (True, False)
property is_inverted: boolΒΆ

Get whether sphere is inverted.

Example

An inverted sphere acts as a containing bowl:

sphere = scene.add.invisible.sphere([0, 0, 0], 1.0, invert=True)
assert sphere.is_inverted
move_by(delta: list[float], time: float) Sphere[source]ΒΆ

Move the sphere by a positional delta at a specific time.

Parameters:
  • delta (list[float]) – The positional delta to move the sphere.

  • time (float) – The absolute time to move the sphere.

Returns:

The sphere.

Return type:

Sphere

Example

Slide a collider sphere 1 unit along +x by t=2:

sphere = scene.add.invisible.sphere([0, 0, 0], 0.5)
sphere.move_by([1, 0, 0], 2.0)
move_to(pos: list[float], time: float) Sphere[source]ΒΆ

Move the sphere to an absolute position at a specific time.

Parameters:
  • pos (list[float]) – The target position of the sphere.

  • time (float) – The absolute time to move the sphere.

Returns:

The sphere.

Return type:

Sphere

Example

Drop a ball from above to y=0 by t=1:

sphere = scene.add.invisible.sphere([0, 1, 0], 0.25)
sphere.move_to([0, 0, 0], 1.0)
property param: SphereParamΒΆ

Get the sphere parameters.

Example

Adjust a sphere parameter through the returned holder:

sphere = scene.add.invisible.sphere([0, 0, 0], 0.5)
sphere.param.set("friction", 0.2)
radius(radius: float, time: float) Sphere[source]ΒΆ

Change the radius of the sphere at a specific time.

Parameters:
  • radius (float) – The target radius of the sphere.

  • time (float) – The absolute time to change the radius.

Returns:

The sphere.

Return type:

Sphere

Example

Inflate the sphere from 0.25 to 0.5 by t=1:

sphere = scene.add.invisible.sphere([0, 0, 0], 0.25)
sphere.radius(0.5, 1.0)
transform_to(pos: list[float], radius: float, time: float) Sphere[source]ΒΆ

Change the sphere to a new position and radius at a specific time.

Parameters:
  • pos (list[float]) – The target position of the sphere.

  • radius (float) – The target radius of the sphere.

  • time (float) – The absolute time to transform the sphere.

Returns:

The sphere.

Return type:

Sphere

Example

Grow the collider sphere as it slides into place:

sphere = scene.add.invisible.sphere([0, 0, 0], 0.25)
sphere.transform_to([0, 0.5, 0], 0.5, 2.0)
property transition: strΒΆ

Get the sphere transition.

Example

Read back the interpolation mode:

sphere = scene.add.invisible.sphere([0, 0, 0], 0.5).interp("smooth")
assert sphere.transition == "smooth"
class frontend.TetMesh(iterable=(), /)[source]ΒΆ

Bases: tuple[ndarray, ndarray, ndarray]

A class representing a tetrahedral mesh.

A tetrahedral mesh is a triple of vertices, surface triangles, and tetrahedra.

surface_mapΒΆ

Optional tuple of (tri_indices, coefs) for reconstructing the original surface via frame-embedding interpolation. coefs are the three frame coefficients (c1, c2, c3) per original vertex, where c1 and c2 are affine coordinates in the triangle’s edge basis, and c3 is the signed normal offset in absolute world units.

Example

Tetrahedralize a surface mesh and unpack the result:

tet = app.mesh.icosphere(r=0.35, subdiv_count=4).tetrahedralize()
V, F, T = tet
app.asset.add.tet("sphere", V, F, T)
has_surface_mapping() bool[source]ΒΆ

Check if this TetMesh has surface mapping data.

Example

Guard a call to interpolate_surface() with this check:

tet = surface_mesh.tetrahedralize()
if tet.has_surface_mapping():
    orig = tet.interpolate_surface()
interpolate_surface(deformed_vert: ndarray | None = None) ndarray[source]ΒΆ

Reconstruct original-resolution positions from the deformed tet surface.

Uses the stored frame-embedding mapping p' = x0' + c1*b1' + c2*b2' + c3*n_hat'.

Parameters:

deformed_vert – deformed tet mesh vertices. If None, the current vertices are used.

Returns:

reconstructed vertex positions matching the original surface vertex count.

Return type:

np.ndarray

Raises:

ValueError – if no surface mapping has been set on this mesh.

Example

Recover the original-resolution surface from a deformed tet mesh:

tet = surface_mesh.tetrahedralize()
deformed = tet[0] * 2.0
orig_surface = tet.interpolate_surface(deformed)
app.plot.create().tri(orig_surface, surface_mesh[1])
normalize() TetMesh[source]ΒΆ

Return self after invoking normalize() on the vertex array.

Example

Normalize the armadillo tet mesh in one chain:

V, F, T = (
    app.mesh.preset("armadillo").decimate(19000).tetrahedralize().normalize()
)
scale(scale_x: float, scale_y: float | None = None, scale_z: float | None = None) TetMesh[source]ΒΆ

Scale the tet mesh with the given scaling factors.

Parameters:
  • scale_x (float) – scaling factor for the x-axis

  • scale_y (float | None) – scaling factor for the y-axis. If None, scale_x is used.

  • scale_z (float | None) – scaling factor for the z-axis. If None, scale_x is used.

Returns:

this tet mesh (self)

Return type:

TetMesh

Example

Squash a tet mesh along the y axis before registering it:

tet = app.mesh.tet_box(1, 1, 1).scale(1.0, 0.25, 1.0)
V, F, T = tet
set_surface_mapping(tri_indices: ndarray, coefs: ndarray) TetMesh[source]ΒΆ

Set the frame-embedding surface mapping used by interpolate_surface().

Parameters:
  • tri_indices – closest triangle in the tet surface per original vertex, shape (N,)

  • coefs – frame coefficients (c1, c2, c3) per original vertex, shape (N, 3)

Returns:

this tet mesh with the surface mapping attached

Return type:

TetMesh

Example

Attach a precomputed mapping back onto a tet mesh:

tet = app.mesh.create.tet(V, F, T).set_surface_mapping(tri_idx, coefs)
orig = tet.interpolate_surface()
class frontend.TriMesh(iterable=(), /)[source]ΒΆ

Bases: tuple[ndarray, ndarray]

A class representing a triangle mesh.

A triangle mesh is a pair of vertices and triangles.

Example

Create a sphere, decimate it, then tetrahedralize it in one chain:

tet = (
    app.mesh.icosphere(r=1.0, subdiv_count=4)
    .decimate(2000)
    .tetrahedralize()
)
V, F, T = tet
compute_cache_path(name: str) str[source]ΒΆ

Compute a cache file path derived from the mesh hash and the given tag.

Example

Typically invoked automatically by the mesh pipeline. To look up where a tagged cache entry would live on disk:

path = mesh.compute_cache_path("decimate__19000")
print(path)
static create(vert: ndarray, elm: ndarray, cache_dir: str) TriMesh[source]ΒΆ

Create a triangle mesh, recompute its hash, and bind the given cache directory.

Example

Typically invoked automatically by the mesh pipeline. To build a TriMesh directly from raw arrays:

import numpy as np
from frontend._mesh_ import TriMesh

mesh = TriMesh.create(V, F, cache_dir="/tmp/ppf-cache")
V2, F2 = mesh
decimate(target_tri: int) TriMesh[source]ΒΆ

Reduce the number of triangles in the mesh to the target count.

Uses quadric decimation via trimesh, followed by a cleanup pass that merges skinny triangles. Results are cached on disk.

Parameters:

target_tri (int) – target number of triangles (must be less than the current count)

Returns:

a decimated mesh

Return type:

TriMesh

Example

Decimate the armadillo preset down to 19000 triangles:

tet = app.mesh.preset("armadillo").decimate(19000).tetrahedralize()
export(path)[source]ΒΆ

Export the mesh to a file using trimesh.

The output format is inferred from the file extension of path (for example .ply, .obj, .stl).

Parameters:

path (str) – export path

Example

Save an icosphere as a PLY file:

app.mesh.icosphere(r=1.0, subdiv_count=3).export("sphere.ply")
load_cache(path: str) TriMesh | None[source]ΒΆ

Load a cached mesh from the given path, or return None if it does not exist.

Example

Typically invoked automatically by the mesh pipeline. To probe for a cached result before recomputing:

cached = mesh.load_cache(mesh.compute_cache_path("decimate__19000"))
if cached is None:
    cached = mesh.decimate(19000)
normalize() TriMesh[source]ΒΆ

Return self after invoking normalize() on the vertex array.

Example

Rescale an imported mesh so its largest extent is 1:

V, F = app.mesh.load_tri("big.ply").normalize()
recompute_hash() TriMesh[source]ΒΆ

Recompute the SHA-256 hash of the mesh from its vertex and element arrays.

Example

Typically invoked automatically after mutating operations. To refresh the hash manually (for example after editing mesh[0] in place):

mesh.recompute_hash()
cache_path = mesh.compute_cache_path("custom")
save_cache(path: str) TriMesh[source]ΒΆ

Save the mesh’s vertex and triangle arrays to the given .npz path.

Example

Typically invoked automatically by operations like decimate(). To snapshot a mesh to disk manually:

path = mesh.compute_cache_path("snapshot")
mesh.save_cache(path)
scale(scale_x: float, scale_y: float | None = None, scale_z: float | None = None) TriMesh[source]ΒΆ

Scale the triangle mesh with the given scaling factors.

Parameters:
  • scale_x (float) – scaling factor for the x-axis

  • scale_y (float | None) – scaling factor for the y-axis. If None, scale_x is used.

  • scale_z (float | None) – scaling factor for the z-axis. If None, scale_x is used.

Returns:

this triangle mesh (self)

Return type:

TriMesh

Example

Flatten a sphere into an oblate shape before registering it:

V, F = app.mesh.icosphere(r=1.0, subdiv_count=3).scale(1.0, 0.3, 1.0)
app.asset.add.tri("pill", V, F)
set_cache_dir(cache_dir: str) TriMesh[source]ΒΆ

Set the cache directory used by this mesh.

Example

Typically invoked automatically by the mesh pipeline. To retarget an existing mesh to a different cache location:

mesh.set_cache_dir("/tmp/ppf-cache-alt")
subdivide(n: int = 1, method: str = 'midpoint')[source]ΒΆ

Subdivide the mesh with the given number of iterations and method.

Results are cached on disk.

Parameters:
  • n (int) – number of subdivision iterations

  • method (str) – subdivision method. Available methods are "midpoint" and "loop".

Returns:

a subdivided mesh

Return type:

TriMesh

Raises:

Exception – if the mesh is not a triangle mesh or method is unknown.

Example

Subdivide a coarse box once using Loop subdivision:

V, F = app.mesh.box(1, 1, 1).subdivide(n=1, method="loop")
app.plot.create().tri(V, F)
tetrahedralize(*args, **kwargs) TetMesh[source]ΒΆ

Tetrahedralize a surface triangle mesh using fTetWild.

The original surface is preserved via a frame-embedding mapping, allowing interpolation of deformed positions back to the original surface structure. fTetWild is invoked in a subprocess so that other Python threads remain responsive. Results are cached on disk.

Keyword Arguments:
  • edge_length_fac (float) – tetrahedral edge length as a fraction of the bbox diagonal (default: 0.05).

  • optimize (bool) – whether to optimize the mesh (default: True).

  • epsilon (float) – fTetWild epsilon tolerance (optional).

  • stop_energy (float) – fTetWild stop-energy threshold (optional).

  • num_opt_iter (int) – number of optimization iterations (optional).

  • simplify (bool) – whether to simplify the input surface (optional).

  • coarsen (bool) – whether to coarsen the tet mesh (optional).

  • status_callback (callable | None) – called periodically with a status string while the subprocess runs.

  • status_interval (float) – polling interval in seconds for status_callback (default: 5.0).

Returns:

a tetrahedral mesh with surface mapping attached. Use TetMesh.interpolate_surface() to recover deformed positions in the original surface structure.

Return type:

TetMesh

Raises:

RuntimeError – if the fTetWild subprocess exits with a non-zero status.

Example

Tetrahedralize a sphere and recover the original surface after simulation via TetMesh.interpolate_surface():

tet = app.mesh.icosphere(r=0.35, subdiv_count=4).tetrahedralize(
    edge_length_fac=0.05
)
V, F, T = tet
app.asset.add.tet("sphere", V, F, T)
triangulate(target: int = 1024, min_angle: float = 20) TriMesh[source]ΒΆ

Triangulate a closed 2D line shape.

The current mesh must be a 2D line mesh (vertices with shape (N, 2) and segment elements of shape (M, 2)). Results are cached on disk.

Parameters:
  • target (int) – target number of triangles (used to derive a maximum triangle area)

  • min_angle (float) – minimum triangle angle in degrees passed to the triangulator

Returns:

a triangulated mesh

Return type:

TriMesh

Raises:

Exception – if the element array is not a line mesh.

Example

Turn a closed 2D curve into a filled triangle mesh:

pts = np.array(
    [[np.cos(t), np.sin(t)] for t in np.linspace(0, 2 * np.pi, 64, endpoint=False)]
)
V, F = app.mesh.create.tri(pts).triangulate(target=1024)
class frontend.Utils[source]ΒΆ

Bases: object

Utility class for frontend.

Example

Check whether the solver is running and stop it if so before kicking off a new simulation:

from frontend import Utils

if Utils.busy():
    Utils.terminate()
print("gpus:", Utils.get_gpu_count())
MIN_SM = 60ΒΆ
static busy() bool[source]ΒΆ

Check if the solver is running.

Returns:

True if the solver is running, False otherwise.

Return type:

bool

Example

Poll until the current simulation has released the GPU:

import time
from frontend import Utils

while Utils.busy():
    time.sleep(1)
static check_gpu()[source]ΒΆ

Check that an NVIDIA GPU with sufficient compute capability is present.

Raises:

RuntimeError – If nvidia-smi is not found or the GPU’s SM version is below MIN_SM.

Example

Validate the GPU before building a session, falling back to a helpful message on unsupported hardware:

from frontend import Utils

try:
    Utils.check_gpu()
except RuntimeError as e:
    print("GPU check failed:", e)
static ci_name() str | None[source]ΒΆ

Determine if the code is running in a CI environment.

Returns:

The name of the CI environment read from the .CI file, or None if no .CI file is present.

Return type:

Optional[str]

Raises:

ValueError – If the .CI file exists but is empty.

Example

Skip a heavy example when running under CI:

from frontend import Utils

if Utils.ci_name() is not None:
    print("running in CI; using short config")
static get_ci_dir() str[source]ΒΆ

Get the path to the CI local directory.

Example

Resolve the CI scratch directory when running under a CI environment:

from frontend import Utils

if Utils.ci_name() is not None:
    print(Utils.get_ci_dir())
static get_ci_root() str[source]ΒΆ

Get the path to the CI directory.

Example

Print the root directory under which per-CI-environment artifacts are stored:

from frontend import Utils

print(Utils.get_ci_root())
static get_driver_version() int | None[source]ΒΆ
static get_gpu_count()[source]ΒΆ
static in_jupyter_notebook()[source]ΒΆ

Determine if the code is running in a Jupyter notebook.

Returns False when a .CLI or .CI marker file is present alongside this module, or when IPython is unavailable.

Example

Gate optional rich widgets behind notebook detection:

from frontend import Utils

if Utils.in_jupyter_notebook():
    print("interactive widgets available")
else:
    print("running from a script")
static is_fast_check() bool[source]ΒΆ

Determine if fast check mode is enabled.

Fast check mode forces simulations to run for only 1 frame, enabling quick validation of all examples.

Returns:

True if fast check mode is enabled.

Return type:

bool

Example

Shorten a simulation when running under fast-check mode:

from frontend import Utils

frames = 1 if Utils.is_fast_check() else 240
print("frames:", frames)
static set_fast_check(enabled: bool = True)[source]ΒΆ

Set fast check mode.

Parameters:

enabled – Whether to enable fast check mode.

Example

Toggle fast-check mode on before running a smoke test and turn it back off afterwards:

from frontend import Utils

Utils.set_fast_check(True)
try:
    assert Utils.is_fast_check()
finally:
    Utils.set_fast_check(False)
static terminate()[source]ΒΆ

Terminate any running solver processes.

Sends a terminate signal to every non-zombie process whose name contains PROCESS_NAME.

Example

Kill a stuck solver process before starting a new run:

from frontend import Utils

if Utils.busy():
    Utils.terminate()
class frontend.Wall[source]ΒΆ

Bases: object

An invisible wall class.

Example

Box a trampoline in with four invisible walls:

gap = 0.025
scene.add.invisible.wall([1 + gap, 0, 0], [-1, 0, 0])
scene.add.invisible.wall([-1 - gap, 0, 0], [1, 0, 0])
scene.add.invisible.wall([0, 0, 1 + gap], [0, 0, -1])
scene.add.invisible.wall([0, 0, -1 - gap], [0, 0, 1])
add(pos: list[float], normal: list[float]) Wall[source]ΒΆ

Add the initial wall entry.

Parameters:
  • pos (list[float]) – The position of the wall.

  • normal (list[float]) – The outer normal of the wall.

Returns:

The invisible wall.

Return type:

Wall

Example

Place a ground wall at y=0 with the outer normal facing up:

wall = Wall().add([0, 0, 0], [0, 1, 0])
property entry: list[tuple[list[float], float]]ΒΆ

Get the wall entries.

Example

Iterate the wall animation keyframes:

wall = scene.add.invisible.wall([0, 0, 0], [0, 1, 0])
for pos, t in wall.entry:
    print(t, pos)
get_entry() list[tuple[list[float], float]][source]ΒΆ

Get a list of time-dependent wall entries.

Returns:

A list of time-dependent entries, each containing a position and time.

Return type:

list[tuple[list[float], float]]

Example

Inspect every keyframed position on an animated wall:

wall = scene.add.invisible.wall([0, 0, 0], [0, 1, 0])
wall.move_to([0, 0.2, 0], 2.0)
for pos, t in wall.get_entry():
    print(t, pos)
interp(transition: str) Wall[source]ΒΆ

Set the transition type for the wall.

Parameters:

transition (str) – The transition type. Defaults to "linear" if never set.

Returns:

The invisible wall.

Return type:

Wall

Example

Ease the wall motion with a smoothstep instead of a linear ramp:

wall.move_to([0.5, 0, 0], 3.0).interp("smooth")
move_by(delta: list[float], time: float) Wall[source]ΒΆ

Move the wall by a positional delta at a specific time.

Parameters:
  • delta (list[float]) – The positional delta to move the wall.

  • time (float) – The absolute time to move the wall.

Returns:

The invisible wall.

Return type:

Wall

Example

Raise the ground wall by 0.2 units at t=2 seconds:

wall = scene.add.invisible.wall([0, 0, 0], [0, 1, 0])
wall.move_by([0, 0.2, 0], 2.0)
move_to(pos: list[float], time: float) Wall[source]ΒΆ

Move the wall to an absolute position at a specific time.

Parameters:
  • pos (list[float]) – The target position of the wall.

  • time (float) – The absolute time to move the wall.

Returns:

The invisible wall.

Return type:

Wall

Example

Animate a piston wall toward an absolute target:

wall = scene.add.invisible.wall([0, 0, 0], [1, 0, 0])
wall.move_to([0.5, 0, 0], 3.0)
property normal: list[float]ΒΆ

Get the wall normal.

Example

Read the outer normal back from an added wall:

wall = scene.add.invisible.wall([0, 0, 0], [0, 1, 0])
print(wall.normal)
property param: WallParamΒΆ

Get the wall parameters.

Example

Tweak a wall parameter through the returned holder:

wall = scene.add.invisible.wall([0, 0, 0], [0, 1, 0])
wall.param.set("friction", 0.2)
property transition: strΒΆ

Get the wall transition.

Example

Check the interpolation mode of an animated wall:

wall = scene.add.invisible.wall([0, 0, 0], [0, 1, 0]).interp("smooth")
assert wall.transition == "smooth"
frontend.get_cache_dir() str[source]ΒΆ

Get the ppf-cts cache directory.

Returns a platform-appropriate cache directory for ppf-cts. - Linux/Mac: ~/.cache/ppf-cts - Windows: <project>/.cache/ppf-cts (project-relative)

Returns:

Path to the ppf-cts cache directory.

Return type:

str

Example

Stage a downloaded mesh inside the shared cache directory so later runs can reuse it:

import os
from frontend import get_cache_dir

cache = get_cache_dir()
mesh_path = os.path.join(cache, "fishingknot.ply")
print(mesh_path)