yapcad.kinematics package

Submodules

yapcad.kinematics.chain module

Kinematic chain (tree of parts) with world transform computation.

Copyright (c) 2026 yapCAD contributors License: MIT

class yapcad.kinematics.chain.KinematicChain(name: str = 'assembly')[source]

Bases: object

Tree of kinematic parts with world transform computation.

A KinematicChain manages a hierarchy of parts connected by joints. It provides efficient world transform computation with caching and supports JSON import/export.

The chain has a virtual root node called GLOBAL_DATUM at the world origin. All root parts (parent=None) are implicitly attached to GLOBAL_DATUM.

name

Name of this kinematic chain/assembly

parts

Dict mapping part names to KinematicPart objects

DATUM_NAME

Name of the virtual root node (“GLOBAL_DATUM”)

Example:

chain = KinematicChain("robot_arm")

# Add parts
chain.add_part(KinematicPart(name="BASE"))
chain.add_part(KinematicPart(
    name="LINK1",
    parent="BASE",
    joint=Joint("j1", JointType.REVOLUTE),
))

# Set joint values
chain.set_joint_value("LINK1", 45.0)

# Get world transforms
world_tf = chain.get_world_transform("LINK1")

# Export to JSON
chain.export_json("positions.json")
DATUM_NAME = 'GLOBAL_DATUM'
add_part(part: KinematicPart) KinematicPart[source]

Add a part to the kinematic chain.

Parameters:

part – Part to add

Returns:

The added part (for chaining)

Raises:

ValueError – If part name already exists or parent not found

Parts with parent=None are attached to GLOBAL_DATUM.

export_json(filepath: str, include_world_transforms: bool = True) None[source]

Export chain to JSON file.

Parameters:
  • filepath – Output file path

  • include_world_transforms – If True, include computed world transforms

classmethod from_dict(data: dict) KinematicChain[source]

Create chain from dictionary.

Parameters:

data – Dictionary with chain data

Returns:

New KinematicChain

classmethod from_json(filepath: str) KinematicChain[source]

Load chain from JSON file.

Parameters:

filepath – Input file path

Returns:

Loaded KinematicChain

get_all_descendants(part_name: str) List[str][source]

Get all descendants of a part (recursive).

Parameters:

part_name – Root part

Returns:

List of all descendant part names

get_all_world_transforms() Dict[str, Transform][source]

Compute world transforms for all parts.

Returns:

Dict mapping part names to world transforms

get_chain_to_root(part_name: str) List[str][source]

Get ancestor chain from part to root.

Parameters:

part_name – Starting part

Returns:

List of part names [part, parent, grandparent, …, GLOBAL_DATUM]

get_children(part_name: str) List[str][source]

Get direct children of a part.

Parameters:

part_name – Parent part name

Returns:

List of child part names

get_joint_value(part_name: str) float[source]

Get current joint value for a part.

Parameters:

part_name – Part to query

Returns:

Current joint value

get_relative_transform(from_part: str, to_part: str, from_frame: str = 'ORIGIN', to_frame: str = 'ORIGIN') Transform[source]

Compute transform from one part/frame to another.

Parameters:
  • from_part – Source part name

  • to_part – Target part name

  • from_frame – Frame on source part

  • to_frame – Frame on target part

Returns:

Transform from source to target

get_world_transform(part_name: str, frame_name: str = 'ORIGIN') Transform[source]

Compute world transform for a part frame.

Parameters:
  • part_name – Part to get transform for

  • frame_name – Frame on the part (default “ORIGIN”)

Returns:

World transform (from global origin to part frame)

This method uses caching for efficiency. Transform is recomputed only when a joint value changes in the ancestor chain.

print_tree(indent: int = 0) None[source]

Print tree structure for debugging.

set_joint_value(part_name: str, value: float, clamp: bool = True) None[source]

Set joint value for a part and invalidate descendant caches.

Parameters:
  • part_name – Part whose joint to modify

  • value – New joint value (degrees for revolute, mm for prismatic)

  • clamp – If True, clamp to joint limits

to_dict(include_world_transforms: bool = True) dict[source]

Convert chain to dictionary.

Parameters:

include_world_transforms – If True, include computed world transforms

Returns:

JSON-serializable dictionary

validate() List[str][source]

Validate the kinematic chain.

Returns:

List of validation issues (empty if valid)

Checks for:
  • Orphaned parts (parent not found)

  • Missing parent frames

  • Circular dependencies

yapcad.kinematics.frame module

Coordinate frames for kinematic parts.

Copyright (c) 2026 yapCAD contributors License: MIT

class yapcad.kinematics.frame.CoordinateFrame(name: str, transform: Transform | None = None, datum_source: str | None = None, datum_name: str | None = None, description: str = '', _cached_transform: Transform | None = None, _cache_valid: bool = False)[source]

Bases: object

Named reference frame on a kinematic part.

A coordinate frame defines a local coordinate system on a part that can be used for attaching child parts or defining features.

Frames can be defined via:
  • Explicit transform (transform field)

  • External datum lookup (datum_source + datum_name)

name

Unique identifier for this frame on the part

Type:

str

transform

Explicit transform from part origin to frame

Type:

yapcad.kinematics.transform.Transform | None

datum_source

Optional external source ID for datum lookup

Type:

str | None

datum_name

Optional datum name for external lookup

Type:

str | None

description

Human-readable description

Type:

str

Example:

# Explicit frame
mount = CoordinateFrame(
    name="SERVO_MOUNT",
    transform=Transform.from_translation(0, 0, 50),
    description="Servo mounting face"
)

# Frame from external datum (lazy lookup)
from_datum = CoordinateFrame(
    name="HORN_FACE",
    datum_source="cots/servo.json",
    datum_name="output_shaft",
)
datum_name: str | None = None
datum_source: str | None = None
description: str = ''
classmethod from_dict(data: dict) CoordinateFrame[source]

Create frame from dictionary.

get_transform() Transform[source]

Get frame transform (with caching for external lookups).

Returns:

Transform from part origin to this frame

If transform is set explicitly, returns it directly. If datum_source/datum_name are set, attempts external lookup. Falls back to identity transform if lookup fails.

invalidate_cache() None[source]

Invalidate cached transform (forces re-lookup).

name: str
to_dict() dict[source]

Convert to dictionary for JSON serialization.

transform: Transform | None = None

yapcad.kinematics.joint module

Joint types and Joint class for kinematic connections.

Copyright (c) 2026 yapCAD contributors License: MIT

class yapcad.kinematics.joint.Joint(name: str, joint_type: JointType = JointType.FIXED, base_transform: Transform = <factory>, axis: Tuple[float, float, float]=(0.0, 0.0, 1.0), value: float = 0.0, min_limit: float = -180.0, max_limit: float = 180.0)[source]

Bases: object

Connection between kinematic parts.

A joint defines how a child part connects to its parent part, including the base transform (fixed offset) and any articulated motion (rotation or translation along an axis).

name

Unique identifier for this joint

Type:

str

joint_type

Type of joint (FIXED, REVOLUTE, etc.)

Type:

yapcad.kinematics.joint.JointType

base_transform

Fixed transform from parent frame to joint origin

Type:

yapcad.kinematics.transform.Transform

axis

Motion axis for revolute/prismatic joints

Type:

Tuple[float, float, float]

value

Current joint position (angle in deg or distance in mm)

Type:

float

min_limit

Minimum joint value

Type:

float

max_limit

Maximum joint value

Type:

float

Example:

# Fixed joint (no motion)
fixed = Joint("mount", JointType.FIXED)

# Revolute joint about Z axis
revolute = Joint(
    name="shoulder",
    joint_type=JointType.REVOLUTE,
    axis=(0, 0, 1),
    min_limit=-90,
    max_limit=90,
)
revolute.value = 45.0  # Set current angle

# Get total transform
tf = revolute.get_transform()  # base_transform @ rotation(45°)
axis: Tuple[float, float, float] = (0.0, 0.0, 1.0)
base_transform: Transform
clamp_value(value: float) float[source]

Clamp value to joint limits.

Parameters:

value – Unclamped joint value

Returns:

Value clamped to [min_limit, max_limit]

classmethod from_dict(data: dict) Joint[source]

Create joint from dictionary.

get_transform() Transform[source]

Compute total joint transform (base + motion).

Returns:

Transform from parent frame to child origin

For FIXED joints, returns just the base_transform. For REVOLUTE joints, adds rotation about axis. For PRISMATIC joints, adds translation along axis.

joint_type: JointType = 'fixed'
max_limit: float = 180.0
min_limit: float = -180.0
name: str
set_value(value: float, clamp: bool = True) None[source]

Set joint value with optional clamping.

Parameters:
  • value – New joint value

  • clamp – If True, clamp to limits

to_dict() dict[source]

Convert to dictionary for JSON serialization.

value: float = 0.0
class yapcad.kinematics.joint.JointType(*values)[source]

Bases: Enum

Types of kinematic joints.

FIXED

No relative motion (rigid attachment)

REVOLUTE

Rotation about single axis

PRISMATIC

Translation along single axis

CYLINDRICAL

Rotation + translation about same axis

SPHERICAL

Ball joint (3 rotational DOF)

CYLINDRICAL = 'cylindrical'
FIXED = 'fixed'
PRISMATIC = 'prismatic'
REVOLUTE = 'revolute'
SPHERICAL = 'spherical'

yapcad.kinematics.part module

Kinematic part (tree node) for kinematic chains.

Copyright (c) 2026 yapCAD contributors License: MIT

class yapcad.kinematics.part.KinematicPart(name: str, parent: str | None = None, parent_frame: str = 'ORIGIN', joint: Joint = <factory>, frames: Dict[str, ~yapcad.kinematics.frame.CoordinateFrame]=<factory>, stl_path: str | None = None, is_printable: bool = True, material: str = 'PETG', color: Tuple[float, float, float]=(0.5, 0.5, 0.5), description: str = '', _world_transform: Transform | None = None, _transform_dirty: bool = True)[source]

Bases: object

Node in a kinematic tree.

A KinematicPart represents a rigid body in an articulated assembly. It connects to a parent part via a joint and can have multiple named coordinate frames for attaching children or defining features.

name

Unique identifier for this part

Type:

str

parent

Name of parent part (None for root)

Type:

str | None

parent_frame

Frame on parent to attach to (default “ORIGIN”)

Type:

str

joint

Joint connecting to parent

Type:

yapcad.kinematics.joint.Joint

frames

Dict of named coordinate frames on this part

Type:

Dict[str, yapcad.kinematics.frame.CoordinateFrame]

stl_path

Optional path to STL geometry file

Type:

str | None

is_printable

Whether this is a 3D printable part

Type:

bool

material

Material specification (e.g., “PETG”, “aluminum”)

Type:

str

color

RGB color tuple for visualization

Type:

Tuple[float, float, float]

description

Human-readable description

Type:

str

Example:

# Create a part with frames
link = KinematicPart(
    name="LINK1",
    parent="BASE",
    parent_frame="MOUNT",
    joint=Joint("joint1", JointType.REVOLUTE),
)

# Add frames for child attachments
link.add_frame(
    "END_EFFECTOR",
    Transform.from_translation(100, 0, 0),
    "Tool attachment point"
)
add_frame(name: str, transform: Transform, description: str = '') CoordinateFrame[source]

Add a coordinate frame to this part.

Parameters:
  • name – Unique frame name

  • transform – Transform from part origin to frame

  • description – Human-readable description

Returns:

The created frame

color: Tuple[float, float, float] = (0.5, 0.5, 0.5)
description: str = ''
frames: Dict[str, CoordinateFrame]
classmethod from_dict(data: dict) KinematicPart[source]

Create part from dictionary.

get_cached_world_transform() Transform | None[source]

Get cached world transform if valid.

get_frame(name: str) CoordinateFrame | None[source]

Get a coordinate frame by name.

Parameters:

name – Frame name

Returns:

CoordinateFrame or None if not found

invalidate_cache() None[source]

Mark world transform cache as dirty.

is_cache_valid() bool[source]

Check if world transform cache is valid.

is_printable: bool = True
joint: Joint
material: str = 'PETG'
name: str
parent: str | None = None
parent_frame: str = 'ORIGIN'
set_cached_world_transform(transform: Transform) None[source]

Set cached world transform (called by KinematicChain).

stl_path: str | None = None
to_dict() dict[source]

Convert to dictionary for JSON serialization.

yapcad.kinematics.transform module

4x4 Homogeneous Transformation Matrix.

This module provides the Transform class for representing rigid body transformations in 3D space using 4x4 homogeneous matrices.

Copyright (c) 2026 yapCAD contributors License: MIT

class yapcad.kinematics.transform.Transform(matrix: ndarray = <factory>)[source]

Bases: object

4x4 homogeneous transformation matrix.

Represents a rigid body transformation (rotation + translation) in 3D space. The matrix format is:

[R11 R12 R13 Tx]
[R21 R22 R23 Ty]
[R31 R32 R33 Tz]
[0   0   0   1 ]

Where the upper-left 3x3 is the rotation matrix and the right column (top 3 elements) is the translation vector.

Transforms can be composed via matrix multiplication using the @ operator:

world_T_tool = world_T_base @ base_T_link @ link_T_tool
matrix

4x4 numpy array representing the transform

Type:

numpy.ndarray

Example:

# Create transforms
t1 = Transform.from_translation(10, 0, 0)
t2 = Transform.from_rotation_z(45)  # degrees

# Compose (t2 applied after t1)
t3 = t1 @ t2

# Get components
pos = t3.translation  # (x, y, z)
R = t3.rotation_matrix  # 3x3
classmethod from_axis_angle(axis: Tuple[float, float, float], angle_deg: float) Transform[source]

Create rotation from axis-angle representation.

Uses Rodrigues’ rotation formula.

Parameters:
  • axis – Unit rotation axis (x, y, z)

  • angle_deg – Rotation angle in degrees

Returns:

Transform with specified rotation

classmethod from_dict(data: dict) Transform[source]

Create transform from dictionary.

Parameters:

data – Dict with ‘matrix’ key (4x4 list)

Returns:

Transform from matrix

classmethod from_euler_xyz(rx_deg: float, ry_deg: float, rz_deg: float) Transform[source]

Create rotation from Euler angles (intrinsic XYZ order).

Parameters:
  • rx_deg – Rotation about X axis in degrees

  • ry_deg – Rotation about Y axis in degrees

  • rz_deg – Rotation about Z axis in degrees

Returns:

Transform with combined rotation

classmethod from_matrix(matrix: Any) Transform[source]

Create transform from 4x4 matrix (numpy array or nested list).

Parameters:

matrix – 4x4 array-like

Returns:

Transform wrapping the matrix

classmethod from_position_rpy(x: float, y: float, z: float, roll_deg: float, pitch_deg: float, yaw_deg: float) Transform[source]

Create transform from position and roll-pitch-yaw angles.

Parameters:
  • x – X position

  • y – Y position

  • z – Z position

  • roll_deg – Roll angle (rotation about X) in degrees

  • pitch_deg – Pitch angle (rotation about Y) in degrees

  • yaw_deg – Yaw angle (rotation about Z) in degrees

Returns:

Combined transform

classmethod from_rotation_x(angle_deg: float) Transform[source]

Create rotation about X axis.

Parameters:

angle_deg – Rotation angle in degrees

Returns:

Transform with rotation about X

classmethod from_rotation_y(angle_deg: float) Transform[source]

Create rotation about Y axis.

Parameters:

angle_deg – Rotation angle in degrees

Returns:

Transform with rotation about Y

classmethod from_rotation_z(angle_deg: float) Transform[source]

Create rotation about Z axis.

Parameters:

angle_deg – Rotation angle in degrees

Returns:

Transform with rotation about Z

classmethod from_translation(x: float, y: float, z: float) Transform[source]

Create translation-only transform.

Parameters:
  • x – Translation along X axis

  • y – Translation along Y axis

  • z – Translation along Z axis

Returns:

Transform with specified translation

classmethod identity() Transform[source]

Create identity transform (no rotation, no translation).

inverse() Transform[source]

Compute inverse transform.

Returns:

Transform T^-1 such that T @ T^-1 = I

For rigid transforms, this is computed efficiently as:

R^T | -R^T * t
----+----------
0   |    1
matrix: ndarray
property rotation_matrix: ndarray

Get 3x3 rotation matrix.

to_dict() dict[source]

Convert to dictionary for JSON serialization.

Returns:

Dict with translation, rotation_xyz, and matrix

to_euler_xyz() Tuple[float, float, float][source]

Extract Euler angles (intrinsic XYZ order) in degrees.

Returns:

(rx, ry, rz) rotation angles in degrees

Note: May have gimbal lock issues when pitch is near ±90°.

transform_point(point: Tuple[float, float, float]) Tuple[float, float, float][source]

Transform a point (applies rotation + translation).

Parameters:

point – Point as (x, y, z)

Returns:

Transformed point as (x, y, z)

transform_vector(vector: Tuple[float, float, float]) Tuple[float, float, float][source]

Transform a direction vector (rotation only, no translation).

Parameters:

vector – Direction vector as (x, y, z)

Returns:

Transformed vector as (x, y, z)

property translation: Tuple[float, float, float]

Get translation component as (x, y, z) tuple.

Module contents

Kinematic Chain System for Multi-Body Assemblies.

OVERVIEW

This package provides a complete kinematic tree system for representing articulated assemblies with joints, frames, and transforms. It forms the foundation for computing world positions of parts in multi-body mechatronic assemblies.

Key Concepts:
  • Transform: 4x4 homogeneous matrix for 6DOF positioning

  • Joint: Connection between parts (fixed, revolute, prismatic, etc.)

  • CoordinateFrame: Named reference frame on a part

  • KinematicPart: Tree node with parent, frames, and joint

  • KinematicChain: Tree container with world transform computation

Quick Start:

from yapcad.kinematics import (
    KinematicChain, KinematicPart, Joint, JointType, Transform
)

# Create a kinematic chain
chain = KinematicChain("my_robot")

# Add base part (attached to world origin)
base = KinematicPart(
    name="BASE",
    parent=None,
    joint=Joint("base_joint", JointType.FIXED),
)
chain.add_part(base)

# Add link with revolute joint
link = KinematicPart(
    name="LINK1",
    parent="BASE",
    parent_frame="MOUNT",
    joint=Joint("joint1", JointType.REVOLUTE, axis=(0, 0, 1)),
)
chain.add_part(link)

# Set joint angle and get world transform
chain.set_joint_value("LINK1", 45.0)  # degrees
world_tf = chain.get_world_transform("LINK1")

# Export to JSON
chain.export_json("positions.json")

Kinematic chain system contributed by Jeremy Mika.

Copyright (c) 2026 yapCAD contributors License: MIT

class yapcad.kinematics.CoordinateFrame(name: str, transform: Transform | None = None, datum_source: str | None = None, datum_name: str | None = None, description: str = '', _cached_transform: Transform | None = None, _cache_valid: bool = False)[source]

Bases: object

Named reference frame on a kinematic part.

A coordinate frame defines a local coordinate system on a part that can be used for attaching child parts or defining features.

Frames can be defined via:
  • Explicit transform (transform field)

  • External datum lookup (datum_source + datum_name)

name

Unique identifier for this frame on the part

Type:

str

transform

Explicit transform from part origin to frame

Type:

yapcad.kinematics.transform.Transform | None

datum_source

Optional external source ID for datum lookup

Type:

str | None

datum_name

Optional datum name for external lookup

Type:

str | None

description

Human-readable description

Type:

str

Example:

# Explicit frame
mount = CoordinateFrame(
    name="SERVO_MOUNT",
    transform=Transform.from_translation(0, 0, 50),
    description="Servo mounting face"
)

# Frame from external datum (lazy lookup)
from_datum = CoordinateFrame(
    name="HORN_FACE",
    datum_source="cots/servo.json",
    datum_name="output_shaft",
)
datum_name: str | None = None
datum_source: str | None = None
description: str = ''
classmethod from_dict(data: dict) CoordinateFrame[source]

Create frame from dictionary.

get_transform() Transform[source]

Get frame transform (with caching for external lookups).

Returns:

Transform from part origin to this frame

If transform is set explicitly, returns it directly. If datum_source/datum_name are set, attempts external lookup. Falls back to identity transform if lookup fails.

invalidate_cache() None[source]

Invalidate cached transform (forces re-lookup).

name: str
to_dict() dict[source]

Convert to dictionary for JSON serialization.

transform: Transform | None = None
class yapcad.kinematics.Joint(name: str, joint_type: JointType = JointType.FIXED, base_transform: Transform = <factory>, axis: Tuple[float, float, float]=(0.0, 0.0, 1.0), value: float = 0.0, min_limit: float = -180.0, max_limit: float = 180.0)[source]

Bases: object

Connection between kinematic parts.

A joint defines how a child part connects to its parent part, including the base transform (fixed offset) and any articulated motion (rotation or translation along an axis).

name

Unique identifier for this joint

Type:

str

joint_type

Type of joint (FIXED, REVOLUTE, etc.)

Type:

yapcad.kinematics.joint.JointType

base_transform

Fixed transform from parent frame to joint origin

Type:

yapcad.kinematics.transform.Transform

axis

Motion axis for revolute/prismatic joints

Type:

Tuple[float, float, float]

value

Current joint position (angle in deg or distance in mm)

Type:

float

min_limit

Minimum joint value

Type:

float

max_limit

Maximum joint value

Type:

float

Example:

# Fixed joint (no motion)
fixed = Joint("mount", JointType.FIXED)

# Revolute joint about Z axis
revolute = Joint(
    name="shoulder",
    joint_type=JointType.REVOLUTE,
    axis=(0, 0, 1),
    min_limit=-90,
    max_limit=90,
)
revolute.value = 45.0  # Set current angle

# Get total transform
tf = revolute.get_transform()  # base_transform @ rotation(45°)
axis: Tuple[float, float, float] = (0.0, 0.0, 1.0)
base_transform: Transform
clamp_value(value: float) float[source]

Clamp value to joint limits.

Parameters:

value – Unclamped joint value

Returns:

Value clamped to [min_limit, max_limit]

classmethod from_dict(data: dict) Joint[source]

Create joint from dictionary.

get_transform() Transform[source]

Compute total joint transform (base + motion).

Returns:

Transform from parent frame to child origin

For FIXED joints, returns just the base_transform. For REVOLUTE joints, adds rotation about axis. For PRISMATIC joints, adds translation along axis.

joint_type: JointType = 'fixed'
max_limit: float = 180.0
min_limit: float = -180.0
name: str
set_value(value: float, clamp: bool = True) None[source]

Set joint value with optional clamping.

Parameters:
  • value – New joint value

  • clamp – If True, clamp to limits

to_dict() dict[source]

Convert to dictionary for JSON serialization.

value: float = 0.0
class yapcad.kinematics.JointType(*values)[source]

Bases: Enum

Types of kinematic joints.

FIXED

No relative motion (rigid attachment)

REVOLUTE

Rotation about single axis

PRISMATIC

Translation along single axis

CYLINDRICAL

Rotation + translation about same axis

SPHERICAL

Ball joint (3 rotational DOF)

CYLINDRICAL = 'cylindrical'
FIXED = 'fixed'
PRISMATIC = 'prismatic'
REVOLUTE = 'revolute'
SPHERICAL = 'spherical'
class yapcad.kinematics.KinematicChain(name: str = 'assembly')[source]

Bases: object

Tree of kinematic parts with world transform computation.

A KinematicChain manages a hierarchy of parts connected by joints. It provides efficient world transform computation with caching and supports JSON import/export.

The chain has a virtual root node called GLOBAL_DATUM at the world origin. All root parts (parent=None) are implicitly attached to GLOBAL_DATUM.

name

Name of this kinematic chain/assembly

parts

Dict mapping part names to KinematicPart objects

Type:

Dict[str, yapcad.kinematics.part.KinematicPart]

DATUM_NAME

Name of the virtual root node (“GLOBAL_DATUM”)

Example:

chain = KinematicChain("robot_arm")

# Add parts
chain.add_part(KinematicPart(name="BASE"))
chain.add_part(KinematicPart(
    name="LINK1",
    parent="BASE",
    joint=Joint("j1", JointType.REVOLUTE),
))

# Set joint values
chain.set_joint_value("LINK1", 45.0)

# Get world transforms
world_tf = chain.get_world_transform("LINK1")

# Export to JSON
chain.export_json("positions.json")
DATUM_NAME = 'GLOBAL_DATUM'
add_part(part: KinematicPart) KinematicPart[source]

Add a part to the kinematic chain.

Parameters:

part – Part to add

Returns:

The added part (for chaining)

Raises:

ValueError – If part name already exists or parent not found

Parts with parent=None are attached to GLOBAL_DATUM.

export_json(filepath: str, include_world_transforms: bool = True) None[source]

Export chain to JSON file.

Parameters:
  • filepath – Output file path

  • include_world_transforms – If True, include computed world transforms

classmethod from_dict(data: dict) KinematicChain[source]

Create chain from dictionary.

Parameters:

data – Dictionary with chain data

Returns:

New KinematicChain

classmethod from_json(filepath: str) KinematicChain[source]

Load chain from JSON file.

Parameters:

filepath – Input file path

Returns:

Loaded KinematicChain

get_all_descendants(part_name: str) List[str][source]

Get all descendants of a part (recursive).

Parameters:

part_name – Root part

Returns:

List of all descendant part names

get_all_world_transforms() Dict[str, Transform][source]

Compute world transforms for all parts.

Returns:

Dict mapping part names to world transforms

get_chain_to_root(part_name: str) List[str][source]

Get ancestor chain from part to root.

Parameters:

part_name – Starting part

Returns:

List of part names [part, parent, grandparent, …, GLOBAL_DATUM]

get_children(part_name: str) List[str][source]

Get direct children of a part.

Parameters:

part_name – Parent part name

Returns:

List of child part names

get_joint_value(part_name: str) float[source]

Get current joint value for a part.

Parameters:

part_name – Part to query

Returns:

Current joint value

get_relative_transform(from_part: str, to_part: str, from_frame: str = 'ORIGIN', to_frame: str = 'ORIGIN') Transform[source]

Compute transform from one part/frame to another.

Parameters:
  • from_part – Source part name

  • to_part – Target part name

  • from_frame – Frame on source part

  • to_frame – Frame on target part

Returns:

Transform from source to target

get_world_transform(part_name: str, frame_name: str = 'ORIGIN') Transform[source]

Compute world transform for a part frame.

Parameters:
  • part_name – Part to get transform for

  • frame_name – Frame on the part (default “ORIGIN”)

Returns:

World transform (from global origin to part frame)

This method uses caching for efficiency. Transform is recomputed only when a joint value changes in the ancestor chain.

parts: Dict[str, KinematicPart]
print_tree(indent: int = 0) None[source]

Print tree structure for debugging.

set_joint_value(part_name: str, value: float, clamp: bool = True) None[source]

Set joint value for a part and invalidate descendant caches.

Parameters:
  • part_name – Part whose joint to modify

  • value – New joint value (degrees for revolute, mm for prismatic)

  • clamp – If True, clamp to joint limits

to_dict(include_world_transforms: bool = True) dict[source]

Convert chain to dictionary.

Parameters:

include_world_transforms – If True, include computed world transforms

Returns:

JSON-serializable dictionary

validate() List[str][source]

Validate the kinematic chain.

Returns:

List of validation issues (empty if valid)

Checks for:
  • Orphaned parts (parent not found)

  • Missing parent frames

  • Circular dependencies

class yapcad.kinematics.KinematicPart(name: str, parent: str | None = None, parent_frame: str = 'ORIGIN', joint: Joint = <factory>, frames: Dict[str, ~yapcad.kinematics.frame.CoordinateFrame]=<factory>, stl_path: str | None = None, is_printable: bool = True, material: str = 'PETG', color: Tuple[float, float, float]=(0.5, 0.5, 0.5), description: str = '', _world_transform: Transform | None = None, _transform_dirty: bool = True)[source]

Bases: object

Node in a kinematic tree.

A KinematicPart represents a rigid body in an articulated assembly. It connects to a parent part via a joint and can have multiple named coordinate frames for attaching children or defining features.

name

Unique identifier for this part

Type:

str

parent

Name of parent part (None for root)

Type:

str | None

parent_frame

Frame on parent to attach to (default “ORIGIN”)

Type:

str

joint

Joint connecting to parent

Type:

yapcad.kinematics.joint.Joint

frames

Dict of named coordinate frames on this part

Type:

Dict[str, yapcad.kinematics.frame.CoordinateFrame]

stl_path

Optional path to STL geometry file

Type:

str | None

is_printable

Whether this is a 3D printable part

Type:

bool

material

Material specification (e.g., “PETG”, “aluminum”)

Type:

str

color

RGB color tuple for visualization

Type:

Tuple[float, float, float]

description

Human-readable description

Type:

str

Example:

# Create a part with frames
link = KinematicPart(
    name="LINK1",
    parent="BASE",
    parent_frame="MOUNT",
    joint=Joint("joint1", JointType.REVOLUTE),
)

# Add frames for child attachments
link.add_frame(
    "END_EFFECTOR",
    Transform.from_translation(100, 0, 0),
    "Tool attachment point"
)
add_frame(name: str, transform: Transform, description: str = '') CoordinateFrame[source]

Add a coordinate frame to this part.

Parameters:
  • name – Unique frame name

  • transform – Transform from part origin to frame

  • description – Human-readable description

Returns:

The created frame

color: Tuple[float, float, float] = (0.5, 0.5, 0.5)
description: str = ''
frames: Dict[str, CoordinateFrame]
classmethod from_dict(data: dict) KinematicPart[source]

Create part from dictionary.

get_cached_world_transform() Transform | None[source]

Get cached world transform if valid.

get_frame(name: str) CoordinateFrame | None[source]

Get a coordinate frame by name.

Parameters:

name – Frame name

Returns:

CoordinateFrame or None if not found

invalidate_cache() None[source]

Mark world transform cache as dirty.

is_cache_valid() bool[source]

Check if world transform cache is valid.

is_printable: bool = True
joint: Joint
material: str = 'PETG'
name: str
parent: str | None = None
parent_frame: str = 'ORIGIN'
set_cached_world_transform(transform: Transform) None[source]

Set cached world transform (called by KinematicChain).

stl_path: str | None = None
to_dict() dict[source]

Convert to dictionary for JSON serialization.

class yapcad.kinematics.Transform(matrix: ndarray = <factory>)[source]

Bases: object

4x4 homogeneous transformation matrix.

Represents a rigid body transformation (rotation + translation) in 3D space. The matrix format is:

[R11 R12 R13 Tx]
[R21 R22 R23 Ty]
[R31 R32 R33 Tz]
[0   0   0   1 ]

Where the upper-left 3x3 is the rotation matrix and the right column (top 3 elements) is the translation vector.

Transforms can be composed via matrix multiplication using the @ operator:

world_T_tool = world_T_base @ base_T_link @ link_T_tool
matrix

4x4 numpy array representing the transform

Type:

numpy.ndarray

Example:

# Create transforms
t1 = Transform.from_translation(10, 0, 0)
t2 = Transform.from_rotation_z(45)  # degrees

# Compose (t2 applied after t1)
t3 = t1 @ t2

# Get components
pos = t3.translation  # (x, y, z)
R = t3.rotation_matrix  # 3x3
classmethod from_axis_angle(axis: Tuple[float, float, float], angle_deg: float) Transform[source]

Create rotation from axis-angle representation.

Uses Rodrigues’ rotation formula.

Parameters:
  • axis – Unit rotation axis (x, y, z)

  • angle_deg – Rotation angle in degrees

Returns:

Transform with specified rotation

classmethod from_dict(data: dict) Transform[source]

Create transform from dictionary.

Parameters:

data – Dict with ‘matrix’ key (4x4 list)

Returns:

Transform from matrix

classmethod from_euler_xyz(rx_deg: float, ry_deg: float, rz_deg: float) Transform[source]

Create rotation from Euler angles (intrinsic XYZ order).

Parameters:
  • rx_deg – Rotation about X axis in degrees

  • ry_deg – Rotation about Y axis in degrees

  • rz_deg – Rotation about Z axis in degrees

Returns:

Transform with combined rotation

classmethod from_matrix(matrix: Any) Transform[source]

Create transform from 4x4 matrix (numpy array or nested list).

Parameters:

matrix – 4x4 array-like

Returns:

Transform wrapping the matrix

classmethod from_position_rpy(x: float, y: float, z: float, roll_deg: float, pitch_deg: float, yaw_deg: float) Transform[source]

Create transform from position and roll-pitch-yaw angles.

Parameters:
  • x – X position

  • y – Y position

  • z – Z position

  • roll_deg – Roll angle (rotation about X) in degrees

  • pitch_deg – Pitch angle (rotation about Y) in degrees

  • yaw_deg – Yaw angle (rotation about Z) in degrees

Returns:

Combined transform

classmethod from_rotation_x(angle_deg: float) Transform[source]

Create rotation about X axis.

Parameters:

angle_deg – Rotation angle in degrees

Returns:

Transform with rotation about X

classmethod from_rotation_y(angle_deg: float) Transform[source]

Create rotation about Y axis.

Parameters:

angle_deg – Rotation angle in degrees

Returns:

Transform with rotation about Y

classmethod from_rotation_z(angle_deg: float) Transform[source]

Create rotation about Z axis.

Parameters:

angle_deg – Rotation angle in degrees

Returns:

Transform with rotation about Z

classmethod from_translation(x: float, y: float, z: float) Transform[source]

Create translation-only transform.

Parameters:
  • x – Translation along X axis

  • y – Translation along Y axis

  • z – Translation along Z axis

Returns:

Transform with specified translation

classmethod identity() Transform[source]

Create identity transform (no rotation, no translation).

inverse() Transform[source]

Compute inverse transform.

Returns:

Transform T^-1 such that T @ T^-1 = I

For rigid transforms, this is computed efficiently as:

R^T | -R^T * t
----+----------
0   |    1
matrix: ndarray
property rotation_matrix: ndarray

Get 3x3 rotation matrix.

to_dict() dict[source]

Convert to dictionary for JSON serialization.

Returns:

Dict with translation, rotation_xyz, and matrix

to_euler_xyz() Tuple[float, float, float][source]

Extract Euler angles (intrinsic XYZ order) in degrees.

Returns:

(rx, ry, rz) rotation angles in degrees

Note: May have gimbal lock issues when pitch is near ±90°.

transform_point(point: Tuple[float, float, float]) Tuple[float, float, float][source]

Transform a point (applies rotation + translation).

Parameters:

point – Point as (x, y, z)

Returns:

Transformed point as (x, y, z)

transform_vector(vector: Tuple[float, float, float]) Tuple[float, float, float][source]

Transform a direction vector (rotation only, no translation).

Parameters:

vector – Direction vector as (x, y, z)

Returns:

Transformed vector as (x, y, z)

property translation: Tuple[float, float, float]

Get translation component as (x, y, z) tuple.