# KCSolve Python API Reference The `kcsolve` module provides Python access to the KCSolve pluggable solver framework. It is built with pybind11 and installed alongside the Assembly module. ```python import kcsolve ``` ## Module constants | Name | Value | Description | |------|-------|-------------| | `API_VERSION_MAJOR` | `1` | KCSolve API major version | ## Enums ### BaseJointKind Primitive constraint types. 24 values: `Coincident`, `PointOnLine`, `PointInPlane`, `Concentric`, `Tangent`, `Planar`, `LineInPlane`, `Parallel`, `Perpendicular`, `Angle`, `Fixed`, `Revolute`, `Cylindrical`, `Slider`, `Ball`, `Screw`, `Universal`, `Gear`, `RackPinion`, `Cam`, `Slot`, `DistancePointPoint`, `DistanceCylSph`, `Custom` ### SolveStatus | Value | Meaning | |-------|---------| | `Success` | Solve converged | | `Failed` | Solve did not converge | | `InvalidFlip` | Orientation flipped past threshold | | `NoGroundedParts` | No grounded parts in assembly | ### DiagnosticKind `Redundant`, `Conflicting`, `PartiallyRedundant`, `Malformed` ### MotionKind `Rotational`, `Translational`, `General` ### LimitKind `TranslationMin`, `TranslationMax`, `RotationMin`, `RotationMax` ## Structs ### Transform Rigid-body transform: position + unit quaternion. | Field | Type | Default | Description | |-------|------|---------|-------------| | `position` | `list[float]` (3) | `[0, 0, 0]` | Translation (x, y, z) | | `quaternion` | `list[float]` (4) | `[1, 0, 0, 0]` | Unit quaternion (w, x, y, z) | ```python t = kcsolve.Transform() t = kcsolve.Transform.identity() # same as default ``` Note: quaternion convention is `(w, x, y, z)`, which differs from FreeCAD's `Base.Rotation(x, y, z, w)`. The adapter layer handles conversion. ### Part | Field | Type | Default | |-------|------|---------| | `id` | `str` | `""` | | `placement` | `Transform` | identity | | `mass` | `float` | `1.0` | | `grounded` | `bool` | `False` | ### 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` | ### Constraint.Limit | Field | Type | Default | |-------|------|---------| | `kind` | `LimitKind` | `TranslationMin` | | `value` | `float` | `0.0` | | `tolerance` | `float` | `1e-9` | ### MotionDef | Field | Type | Default | |-------|------|---------| | `kind` | `MotionKind` | `Rotational` | | `joint_id` | `str` | `""` | | `marker_i` | `str` | `""` | | `marker_j` | `str` | `""` | | `rotation_expr` | `str` | `""` | | `translation_expr` | `str` | `""` | ### 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` | ### SolveContext | Field | Type | Default | |-------|------|---------| | `parts` | `list[Part]` | `[]` | | `constraints` | `list[Constraint]` | `[]` | | `motions` | `list[MotionDef]` | `[]` | | `simulation` | `SimulationParams` or `None` | `None` | | `bundle_fixed` | `bool` | `False` | **Important:** pybind11 returns copies of `list` fields, not references. Use whole-list assignment: ```python ctx = kcsolve.SolveContext() p = kcsolve.Part() p.id = "box1" ctx.parts = [p] # correct # ctx.parts.append(p) # does NOT modify ctx ``` ### ConstraintDiagnostic | Field | Type | Default | |-------|------|---------| | `constraint_id` | `str` | `""` | | `kind` | `DiagnosticKind` | `Redundant` | | `detail` | `str` | `""` | ### SolveResult | Field | Type | Default | |-------|------|---------| | `status` | `SolveStatus` | `Success` | | `placements` | `list[SolveResult.PartResult]` | `[]` | | `dof` | `int` | `-1` | | `diagnostics` | `list[ConstraintDiagnostic]` | `[]` | | `num_frames` | `int` | `0` | ### SolveResult.PartResult | Field | Type | Default | |-------|------|---------| | `id` | `str` | `""` | | `placement` | `Transform` | identity | ## Classes ### IKCSolver Abstract base class for solver backends. Subclass in Python to create custom solvers. Three methods must be implemented: ```python class MySolver(kcsolve.IKCSolver): def name(self): return "My Solver" def supported_joints(self): return [kcsolve.BaseJointKind.Fixed, kcsolve.BaseJointKind.Revolute] def solve(self, ctx): result = kcsolve.SolveResult() result.status = kcsolve.SolveStatus.Success return result ``` Optional overrides (all have default implementations): | 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` | ### OndselAdapter Built-in solver wrapping OndselSolver's Lagrangian constraint formulation. Inherits `IKCSolver`. ```python solver = kcsolve.OndselAdapter() solver.name() # "OndselSolver (Lagrangian)" ``` In practice, use `kcsolve.load("ondsel")` rather than constructing directly, as this goes through the registry. ## Module functions ### available() Return names of all registered solvers. ```python kcsolve.available() # ["ondsel"] ``` ### load(name="") Create an instance of the named solver. If `name` is empty, uses the default. Returns `None` if the solver is not found. ```python solver = kcsolve.load("ondsel") solver = kcsolve.load() # default solver ``` ### joints_for(name) Query supported joint types for a registered solver. ```python joints = kcsolve.joints_for("ondsel") # [BaseJointKind.Coincident, BaseJointKind.Fixed, ...] ``` ### set_default(name) Set the default solver name. Returns `True` if the name is registered. ```python kcsolve.set_default("ondsel") # True kcsolve.set_default("unknown") # False ``` ### get_default() Get the current default solver name. ```python kcsolve.get_default() # "ondsel" ``` ### register_solver(name, solver_class) Register a Python solver class with the SolverRegistry. `solver_class` must be a callable that returns an `IKCSolver` subclass instance. ```python class MySolver(kcsolve.IKCSolver): def name(self): return "MySolver" def supported_joints(self): return [kcsolve.BaseJointKind.Fixed] def solve(self, ctx): r = kcsolve.SolveResult() r.status = kcsolve.SolveStatus.Success return r kcsolve.register_solver("my_solver", MySolver) solver = kcsolve.load("my_solver") ``` ## Complete example ```python import kcsolve # Build a two-part assembly with a Fixed joint ctx = kcsolve.SolveContext() base = kcsolve.Part() base.id = "base" base.grounded = True arm = kcsolve.Part() arm.id = "arm" arm.placement.position = [100.0, 0.0, 0.0] joint = kcsolve.Constraint() joint.id = "Joint001" joint.part_i = "base" joint.part_j = "arm" joint.type = kcsolve.BaseJointKind.Fixed ctx.parts = [base, arm] ctx.constraints = [joint] # Solve solver = kcsolve.load("ondsel") result = solver.solve(ctx) print(result.status) # SolveStatus.Success for pr in result.placements: print(f"{pr.id}: pos={list(pr.placement.position)}") # Diagnostics diags = solver.diagnose(ctx) for d in diags: print(f"{d.constraint_id}: {d.kind} - {d.detail}") ``` ## Related - [KCSolve Architecture](../architecture/ondsel-solver.md) - [INTER_SOLVER.md](../../INTER_SOLVER.md) -- full architecture specification