docs(kcsolve): expand Python API reference with full method docs
All checks were successful
Build and Test / build (pull_request) Successful in 29m49s

Expand SolveContext field descriptions (motions, simulation, bundle_fixed),
Constraint params table, marker explanations, Constraint.Limit descriptions,
MotionDef field descriptions, SimulationParams field descriptions, and all
optional IKCSolver methods with signatures, parameter docs, and usage
examples (interactive drag protocol, kinematic simulation, diagnostics,
export_native, capability queries).
This commit is contained in:
forbes
2026-02-19 19:06:08 -06:00
parent 406e120180
commit bd43e62822

View File

@@ -70,57 +70,92 @@ Note: quaternion convention is `(w, x, y, z)`, which differs from FreeCAD's `Bas
### Constraint
| Field | Type | Default |
|-------|------|---------|
| `id` | `str` | `""` |
| `part_i` | `str` | `""` |
| `marker_i` | `Transform` | identity |
| `part_j` | `str` | `""` |
| `marker_j` | `Transform` | identity |
| `type` | `BaseJointKind` | `Coincident` |
| `params` | `list[float]` | `[]` |
| `limits` | `list[Constraint.Limit]` | `[]` |
| `activated` | `bool` | `True` |
A constraint between two parts, built from a FreeCAD JointObject by the adapter layer.
| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `id` | `str` | `""` | FreeCAD document object name (e.g. `"Joint001"`) |
| `part_i` | `str` | `""` | Solver-side part ID for first reference |
| `marker_i` | `Transform` | identity | Coordinate system on `part_i` (attachment point/orientation) |
| `part_j` | `str` | `""` | Solver-side part ID for second reference |
| `marker_j` | `Transform` | identity | Coordinate system on `part_j` (attachment point/orientation) |
| `type` | `BaseJointKind` | `Coincident` | Constraint type |
| `params` | `list[float]` | `[]` | Scalar parameters (interpretation depends on `type`) |
| `limits` | `list[Constraint.Limit]` | `[]` | Joint travel limits |
| `activated` | `bool` | `True` | Whether this constraint is active |
**`marker_i` / `marker_j`** -- Define the local coordinate frames on each part where the joint acts. For example, a Revolute joint's markers define the hinge axis direction and attachment points on each part.
**`params`** -- Interpretation depends on `type`:
| Type | params[0] | params[1] |
|------|-----------|-----------|
| `Angle` | angle (radians) | |
| `RackPinion` | pitch radius | |
| `Screw` | pitch | |
| `Gear` | radius I | radius J (negative for belt) |
| `DistancePointPoint` | distance | |
| `DistanceCylSph` | distance | |
| `Planar` | offset | |
| `Concentric` | distance | |
| `PointInPlane` | offset | |
| `LineInPlane` | offset | |
### Constraint.Limit
| Field | Type | Default |
|-------|------|---------|
| `kind` | `LimitKind` | `TranslationMin` |
| `value` | `float` | `0.0` |
| `tolerance` | `float` | `1e-9` |
Joint travel limits (translation or rotation bounds).
| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `kind` | `LimitKind` | `TranslationMin` | Which degree of freedom to limit |
| `value` | `float` | `0.0` | Limit value (meters for translation, radians for rotation) |
| `tolerance` | `float` | `1e-9` | Solver tolerance for limit enforcement |
### MotionDef
| Field | Type | Default |
|-------|------|---------|
| `kind` | `MotionKind` | `Rotational` |
| `joint_id` | `str` | `""` |
| `marker_i` | `str` | `""` |
| `marker_j` | `str` | `""` |
| `rotation_expr` | `str` | `""` |
| `translation_expr` | `str` | `""` |
A motion driver for kinematic simulation. Defines time-dependent actuation of a constraint.
| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `kind` | `MotionKind` | `Rotational` | Type of motion: `Rotational`, `Translational`, or `General` (both) |
| `joint_id` | `str` | `""` | ID of the constraint this motion drives |
| `marker_i` | `str` | `""` | Reference marker on first part |
| `marker_j` | `str` | `""` | Reference marker on second part |
| `rotation_expr` | `str` | `""` | Rotation law as a function of time `t` (e.g. `"2*pi*t"`) |
| `translation_expr` | `str` | `""` | Translation law as a function of time `t` (e.g. `"10*t"`) |
For `Rotational` kind, only `rotation_expr` is used. For `Translational`, only `translation_expr`. For `General`, both are set.
### SimulationParams
| Field | Type | Default |
|-------|------|---------|
| `t_start` | `float` | `0.0` |
| `t_end` | `float` | `1.0` |
| `h_out` | `float` | `0.01` |
| `h_min` | `float` | `1e-9` |
| `h_max` | `float` | `1.0` |
| `error_tol` | `float` | `1e-6` |
Time-stepping parameters for kinematic simulation via `run_kinematic()`.
| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `t_start` | `float` | `0.0` | Simulation start time (seconds) |
| `t_end` | `float` | `1.0` | Simulation end time (seconds) |
| `h_out` | `float` | `0.01` | Output time step -- controls frame rate (e.g. `0.04` = 25 fps) |
| `h_min` | `float` | `1e-9` | Minimum internal integration step |
| `h_max` | `float` | `1.0` | Maximum internal integration step |
| `error_tol` | `float` | `1e-6` | Error tolerance for adaptive time stepping |
### SolveContext
| Field | Type | Default |
|-------|------|---------|
| `parts` | `list[Part]` | `[]` |
| `constraints` | `list[Constraint]` | `[]` |
| `motions` | `list[MotionDef]` | `[]` |
| `simulation` | `SimulationParams` or `None` | `None` |
| `bundle_fixed` | `bool` | `False` |
Complete input to a solve operation. Built by the adapter layer from FreeCAD document objects, or constructed manually for scripted solving.
| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `parts` | `list[Part]` | `[]` | All parts in the assembly |
| `constraints` | `list[Constraint]` | `[]` | Constraints between parts |
| `motions` | `list[MotionDef]` | `[]` | Motion drivers for kinematic simulation |
| `simulation` | `SimulationParams` or `None` | `None` | Time-stepping parameters for `run_kinematic()` |
| `bundle_fixed` | `bool` | `False` | Hint to merge Fixed-joint-connected parts into rigid bodies |
**`motions`** -- Motion drivers define time-dependent joint actuation for kinematic simulation. Each `MotionDef` references a constraint by `joint_id` and provides expressions (functions of time `t`) for rotation and/or translation. Only used when calling `run_kinematic()`.
**`simulation`** -- When set, provides time-stepping parameters (`t_start`, `t_end`, step sizes, error tolerance) for kinematic simulation via `run_kinematic()`. When `None`, kinematic simulation is not requested.
**`bundle_fixed`** -- When `True`, parts connected by `Fixed` joints should be merged into single rigid bodies before solving, reducing the problem size. If the solver reports `supports_bundle_fixed() == True`, it handles this internally. Otherwise, the caller (adapter layer) pre-bundles before building the context.
**Important:** pybind11 returns copies of `list` fields, not references. Use whole-list assignment:
@@ -179,21 +214,102 @@ class MySolver(kcsolve.IKCSolver):
return result
```
Optional overrides (all have default implementations):
All other methods are optional and have default implementations. Override them to add capabilities beyond basic solving.
| Method | Default behavior |
|--------|-----------------|
| `update(ctx)` | Delegates to `solve()` |
| `pre_drag(ctx, drag_parts)` | Delegates to `solve()` |
| `drag_step(drag_placements)` | Returns Success with no placements |
| `post_drag()` | No-op |
| `run_kinematic(ctx)` | Returns Failed |
| `num_frames()` | Returns 0 |
| `update_for_frame(index)` | Returns Failed |
| `diagnose(ctx)` | Returns empty list |
| `is_deterministic()` | Returns `True` |
| `export_native(path)` | No-op |
| `supports_bundle_fixed()` | Returns `False` |
#### update(ctx) -> SolveResult
Incrementally re-solve after parameter changes (e.g. joint angle adjusted during creation). Solvers can optimize this path since only parameters changed, not topology. Default: delegates to `solve()`.
```python
def update(self, ctx):
# Only re-evaluate changed constraints, reuse cached factorization
return self._incremental_solve(ctx)
```
#### Interactive drag protocol
Three-phase protocol for interactive part dragging in the viewport. Solvers can maintain internal state across the drag session for better performance.
**pre_drag(ctx, drag_parts) -> SolveResult** -- Prepare for a drag session. `drag_parts` is a `list[str]` of part IDs being dragged. Solve the initial state and cache internal data. Default: delegates to `solve()`.
**drag_step(drag_placements) -> SolveResult** -- Called on each mouse move. `drag_placements` is a `list[SolveResult.PartResult]` with the current positions of dragged parts. Returns updated placements for all affected parts. Default: returns Success with no placements.
**post_drag()** -- End the drag session and release internal state. Default: no-op.
```python
def pre_drag(self, ctx, drag_parts):
self._cached_system = self._build_system(ctx)
return self.solve(ctx)
def drag_step(self, drag_placements):
# Use cached system for fast incremental solve
for dp in drag_placements:
self._cached_system.set_placement(dp.id, dp.placement)
return self._cached_system.solve_incremental()
def post_drag(self):
self._cached_system = None
```
#### Kinematic simulation
**run_kinematic(ctx) -> SolveResult** -- Run a kinematic simulation over the time range in `ctx.simulation`. After this call, `num_frames()` returns the frame count and `update_for_frame(i)` retrieves individual frames. Requires `ctx.simulation` to be set and `ctx.motions` to contain at least one motion driver. Default: returns Failed.
**num_frames() -> int** -- Number of simulation frames available after `run_kinematic()`. Default: returns 0.
**update_for_frame(index) -> SolveResult** -- Retrieve part placements for simulation frame at `index` (0-based, must be < `num_frames()`). Default: returns Failed.
```python
# Run a kinematic simulation
ctx.simulation = kcsolve.SimulationParams()
ctx.simulation.t_start = 0.0
ctx.simulation.t_end = 2.0
ctx.simulation.h_out = 0.04 # 25 fps
motion = kcsolve.MotionDef()
motion.kind = kcsolve.MotionKind.Rotational
motion.joint_id = "Joint001"
motion.rotation_expr = "2*pi*t" # one revolution per second
ctx.motions = [motion]
solver = kcsolve.load("ondsel")
result = solver.run_kinematic(ctx)
for i in range(solver.num_frames()):
frame = solver.update_for_frame(i)
for pr in frame.placements:
print(f"frame {i}: {pr.id} at {list(pr.placement.position)}")
```
#### diagnose(ctx) -> list[ConstraintDiagnostic]
Analyze the assembly for redundant, conflicting, or malformed constraints. May require a prior `solve()` call for some solvers. Returns a list of `ConstraintDiagnostic` objects. Default: returns empty list.
```python
diags = solver.diagnose(ctx)
for d in diags:
if d.kind == kcsolve.DiagnosticKind.Redundant:
print(f"Redundant: {d.constraint_id} - {d.detail}")
elif d.kind == kcsolve.DiagnosticKind.Conflicting:
print(f"Conflict: {d.constraint_id} - {d.detail}")
```
#### is_deterministic() -> bool
Whether this solver produces identical results given identical input. Used for regression testing and result caching. Default: returns `True`.
#### export_native(path)
Write a solver-native debug/diagnostic file (e.g. ASMT format for OndselSolver). Requires a prior `solve()` or `run_kinematic()` call. Default: no-op.
```python
solver.solve(ctx)
solver.export_native("/tmp/debug.asmt")
```
#### supports_bundle_fixed() -> bool
Whether this solver handles Fixed-joint part bundling internally. When `False`, the caller merges Fixed-joint-connected parts into single rigid bodies before building the `SolveContext`, reducing problem size. When `True`, the solver receives unbundled parts and optimizes internally. Default: returns `False`.
### OndselAdapter