From 315ac2a25dbe7ace525859fbcfe0f0a2758df4b9 Mon Sep 17 00:00:00 2001 From: forbes Date: Thu, 19 Feb 2026 19:06:08 -0600 Subject: [PATCH] docs(kcsolve): expand Python API reference with full method docs 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). --- docs/src/reference/kcsolve-python.md | 222 ++++++++++++++++++++------- 1 file changed, 169 insertions(+), 53 deletions(-) diff --git a/docs/src/reference/kcsolve-python.md b/docs/src/reference/kcsolve-python.md index f99ac7a1cd..2a75739813 100644 --- a/docs/src/reference/kcsolve-python.md +++ b/docs/src/reference/kcsolve-python.md @@ -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