- Replace OndselSolver architecture doc with KCSolve pluggable solver architecture covering IKCSolver interface, SolverRegistry, OndselAdapter, Python bindings, file layout, and testing - Add kcsolve Python API reference with full type documentation, module functions, usage examples, and pybind11 vector-copy caveat - Add INTER_SOLVER.md spec (previously untracked) with Phase 1 and Phase 2 marked as complete - Update SUMMARY.md with new page links
7.7 KiB
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.
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) |
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:
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:
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.
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.
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.
solver = kcsolve.load("ondsel")
solver = kcsolve.load() # default solver
joints_for(name)
Query supported joint types for a registered solver.
joints = kcsolve.joints_for("ondsel")
# [BaseJointKind.Coincident, BaseJointKind.Fixed, ...]
set_default(name)
Set the default solver name. Returns True if the name is registered.
kcsolve.set_default("ondsel") # True
kcsolve.set_default("unknown") # False
get_default()
Get the current default solver name.
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.
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
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
- INTER_SOLVER.md -- full architecture specification