yapcad.assembly package

Submodules

yapcad.assembly.assembly module

Assembly orchestrator for the yapCAD constraint-based assembly system.

This module provides the main Assembly class that manages parts, mates, and constraints, and orchestrates the constraint solving and validation process.

The Assembly class is the primary user-facing interface for creating and validating assemblies in yapCAD. It coordinates between the datum, mate, and constraint subsystems to ensure assemblies meet design intent.

Key Features:
  • Add parts with initial transforms

  • Define mate relationships between parts

  • Add design constraints to validate intent

  • Solve mate constraints to compute part positions

  • Validate all design constraints

  • Export to URDF, Blender rigs, or other formats

  • Integration with existing kinematic_chain.py transforms

Example

>>> from yapcad.assembly import Assembly, PartDefinition, Datum, DatumType
>>> from yapcad.assembly import Mate, MateType, Constraint, ConstraintType
>>> import numpy as np
>>>
>>> # Create part definitions
>>> motor = PartDefinition("DDSM115_MOTOR")
>>> motor.add_datum(Datum("motor_axis", DatumType.AXIS,
...                       origin=(0, 0, 0), direction=(0, 1, 0)))
>>> motor.add_datum(Datum("stator_face", DatumType.PLANE,
...                       origin=(0, 10, 0), normal=(0, 1, 0)))
>>>
>>> bracket = PartDefinition("WHEEL_BRACKET")
>>> bracket.add_datum(Datum("bore_axis", DatumType.AXIS,
...                         origin=(0, 0, 0), direction=(0, 0, 1)))
>>> bracket.add_datum(Datum("motor_interface", DatumType.PLANE,
...                         origin=(0, 0, 5), normal=(0, 0, 1)))
>>>
>>> # Create assembly
>>> assembly = Assembly("wheel_module")
>>> assembly.add_part(motor, name="motor_1")
>>> assembly.add_part(bracket, name="bracket_1")
>>>
>>> # Add mates to position parts
>>> assembly.add_mate(Mate("mount_mate", MateType.FLUSH,
...                        part1="bracket_1", datum1="motor_interface",
...                        part2="motor_1", datum2="stator_face"))
>>> assembly.add_mate(Mate("axis_mate", MateType.CONCENTRIC,
...                        part1="bracket_1", datum1="bore_axis",
...                        part2="motor_1", datum2="motor_axis"))
>>>
>>> # Add design constraints
>>> assembly.add_constraint(Constraint(
...     "motor_tangent", ConstraintType.TANGENT_TO_CIRCLE,
...     part="motor_1", datum="motor_axis",
...     center=(0, 0, 0), radius=124.5,
...     description="Motor axis must be tangent to chassis for rolling motion"
... ))
>>>
>>> # Validate assembly
>>> result = assembly.validate()
>>> if result.is_valid:
...     print("Assembly is valid!")
...     motor_transform = assembly.transforms["motor_1"]
... else:
...     for error in result.failed_constraints:
...         print(f"ERROR: {error}")
...     for warning in result.warnings:
...         print(f"WARNING: {warning}")

See also

  • datum.py: Datum feature definitions

  • mate.py: Mate constraint definitions

  • constraint.py: Design constraint definitions

class yapcad.assembly.assembly.Assembly(name: str)[source]

Bases: object

Main assembly orchestrator for the yapCAD constraint system.

The Assembly class manages a collection of parts with their transforms, mate relationships that position parts, and design constraints that validate assembly intent.

Workflow:
  1. Create assembly: assembly = Assembly(“my_assembly”)

  2. Add parts: assembly.add_part(part_def, name=”part_1”)

  3. Add mates: assembly.add_mate(mate)

  4. Add constraints: assembly.add_constraint(constraint)

  5. Validate: result = assembly.validate()

The assembly can also import transforms from existing kinematic chains and export to various formats (URDF, Blender, etc.).

Variables:
  • name – Unique identifier for this assembly

  • parts – Dictionary mapping part name to PartDefinition

  • transforms – Dictionary mapping part name to 4x4 transform matrix

  • mates – List of Mate objects defining part relationships

  • constraints – List of Constraint objects to validate

Integration with yapCAD:
  • Transforms are 4x4 numpy arrays compatible with yapcad.xform.Matrix

  • Can import transforms from kinematic_chain.py JSON exports

  • Datums transform correctly through coordinate systems

Example

>>> # Create assembly
>>> assembly = Assembly("robot_wheel")
>>>
>>> # Add parts
>>> assembly.add_part(motor_def, name="motor")
>>> assembly.add_part(bracket_def, name="bracket")
>>>
>>> # Define relationships
>>> assembly.add_mate(Mate("align", MateType.CONCENTRIC,
...                        "bracket", "hole", "motor", "shaft"))
>>>
>>> # Validate design intent
>>> assembly.add_constraint(Constraint(
...     "tangent", ConstraintType.TANGENT_TO_CIRCLE,
...     "motor", "axis", center=(0,0,0), radius=124.5
... ))
>>>
>>> # Check if valid
>>> result = assembly.validate()
>>> if not result.is_valid:
...     assembly.validate_and_raise()  # Raises AssemblyError
add_bolt_pattern(parent_part: str, child_part: str, bolt_pattern) None[source]

Attach a BoltPattern to the interface between parent and child.

The pattern is keyed by the (parent, child) tuple, mirroring how the Mechatron exporter groups mates into a single Interface. If the same pair already has a bolt_pattern registered, this raises.

add_constraint(constraint: Constraint) None[source]

Add a design constraint to validate assembly intent.

Constraints validate high-level design requirements that go beyond simple positioning. They ensure the assembly meets its intended functional requirements.

Parameters:

constraint – Constraint object to validate

Raises:

AssemblyError – If referenced part or datum doesn’t exist

Example

>>> # Ensure motor axis is tangent to chassis
>>> assembly.add_constraint(Constraint(
...     "motor_tangent", ConstraintType.TANGENT_TO_CIRCLE,
...     part="motor_1", datum="motor_axis",
...     center=(0, 0, 0), radius=124.5,
...     description="Motor must be tangent for rolling motion"
... ))
add_load_case(load_case) None[source]

Register a structural LoadCase with this assembly.

Parameters:

load_case – a yapcad.assembly.load_case.LoadCase instance

Raises:

AssemblyError – if the LoadCase references a part not in the assembly (when attach.part != “_assembly”)

add_mate(mate: Mate) None[source]

Add a mate relationship between two parts.

Mates define geometric relationships between datum features on different parts. They are used to position parts relative to each other.

Parameters:

mate – Mate object defining the relationship

Raises:

AssemblyError – If referenced parts or datums don’t exist, or if mate is invalid for the given datums

Example

>>> # Align motor shaft with bracket hole
>>> assembly.add_mate(Mate(
...     "shaft_alignment", MateType.CONCENTRIC,
...     part1="bracket", datum1="bore_axis",
...     part2="motor", datum2="shaft_axis"
... ))
add_part(part: PartDefinition, name: str = None, transform: ndarray = None) None[source]

Add a part to the assembly.

Parameters:
  • part – PartDefinition with datum features

  • name – Name for this part instance (default: part.name)

  • transform – Initial 4x4 transform matrix (default: identity)

Raises:

AssemblyError – If part name already exists in assembly

Example

>>> motor = PartDefinition("DDSM115")
>>> assembly.add_part(motor, name="motor_1")
>>> # Or with initial position
>>> T = np.eye(4)
>>> T[0:3, 3] = [100, 0, 0]  # Translate 100mm in X
>>> assembly.add_part(motor, name="motor_2", transform=T)
export_to_blender_rig(output_path: str | Path) None[source]

Export assembly to Blender armature rig for animation.

Creates a Blender Python script that sets up an armature with bones positioned according to the assembly transforms. Useful for visualizing kinematics and creating animations.

Parameters:

output_path – Path where Blender Python script will be written

Note

This is currently a stub implementation. A full implementation would generate a Blender Python script that creates bones, constraints, and animations.

Example

>>> assembly.export_to_blender_rig("robot_rig.py")
>>> # In Blender: File > Import > Python Script > robot_rig.py
export_to_urdf(output_path: str | Path) None[source]

Export assembly to URDF format for ROS integration.

URDF (Unified Robot Description Format) is used by ROS for robot kinematics and dynamics. This export enables simulation and control of yapCAD assemblies in ROS.

Parameters:

output_path – Path where URDF file will be written

Note

This is currently a stub implementation. A full implementation would generate proper URDF XML with links, joints, and meshes.

Example

>>> assembly.export_to_urdf("robot.urdf")
>>> # Use in ROS: roslaunch robot_description display.launch
get_bolt_pattern(parent_part: str, child_part: str)[source]

Return the BoltPattern for an interface, or None if not set.

get_degrees_of_freedom() Dict[str, int][source]

Calculate remaining degrees of freedom for each part after mates.

Analyzes the mate constraints to determine how many degrees of freedom remain unconstrained for each part. A fully constrained part has 0 DOF.

Returns:

Dictionary mapping part name to DOF count (0-6)

Note

This is a simplified implementation. A full implementation would analyze the constraint graph to detect over/under-constrained situations and redundant constraints.

Example

>>> dof = assembly.get_degrees_of_freedom()
>>> for part, dof_count in dof.items():
...     if dof_count > 0:
...         print(f"{part} has {dof_count} DOF remaining")
get_load_case(lc_id: str)[source]

Return the LoadCase with the given id, or None if missing.

get_transformed_datum(part_name: str, datum_name: str) Datum[source]

Get a datum feature transformed to world coordinates.

Applies the part’s current transform to a datum to obtain its position and orientation in world coordinates.

Parameters:
  • part_name – Name of the part

  • datum_name – Name of the datum on that part

Returns:

Datum transformed to world coordinates

Raises:

AssemblyError – If part or datum not found

Example

>>> # Get motor axis in world coordinates
>>> motor_axis_world = assembly.get_transformed_datum(
...     "motor_1", "motor_axis"
... )
>>> print(f"Axis origin: {motor_axis_world.origin}")
>>> print(f"Axis direction: {motor_axis_world.direction}")
import_transforms_from_kinematic_chain(json_path: str | Path) None[source]

Import part transforms from a kinematic_chain.py JSON export.

The kinematic chain system computes transforms through a hierarchical tree. This method imports those transforms into the assembly so constraints can be validated against them.

Parameters:

json_path – Path to JSON file with kinematic chain transforms

Raises:

AssemblyError – If JSON cannot be loaded or part names don’t match

Example

>>> # Export from kinematic_chain.py
>>> chain.export_transforms("chain_transforms.json")
>>>
>>> # Import into assembly
>>> assembly.import_transforms_from_kinematic_chain(
...     "chain_transforms.json"
... )
>>>
>>> # Now validate against those transforms
>>> result = assembly.validate()
report() str[source]

Generate a comprehensive human-readable assembly report.

Returns:

Multi-line string with assembly status and validation results

Example

>>> print(assembly.report())
Assembly: robot_wheel
====================================
Parts: 2
  - motor_1 (DDSM115_MOTOR)
  - bracket_1 (WHEEL_BRACKET)
Mates: 2
  • shaft_alignment (CONCENTRIC)

  • mount_surface (FLUSH)

Constraints: 1
  • motor_tangent (TANGENT_TO_CIRCLE)

Validation: VALID

validate() AssemblyValidationResult[source]

Validate all design constraints against current part transforms.

Evaluates each constraint in the assembly and returns a comprehensive validation result. This does not solve mates - it validates the current state of the assembly.

Returns:

AssemblyValidationResult with detailed status

Example

>>> result = assembly.validate()
>>> if result.is_valid:
...     print("Assembly is valid!")
... else:
...     print(result.report())
validate_and_raise() None[source]

Validate assembly and raise AssemblyError if invalid.

Convenience method for strict validation where you want an exception on any constraint failure.

Raises:

AssemblyError – If any constraint fails

Example

>>> try:
...     assembly.validate_and_raise()
...     print("Assembly is valid, proceeding...")
... except AssemblyError as e:
...     print(f"Invalid assembly: {e}")
exception yapcad.assembly.assembly.AssemblyError(message: str, assembly_name: str = None, part_name: str = None, constraint_name: str = None)[source]

Bases: Exception

Exception raised when assembly operations fail.

This exception is raised for various assembly-related errors such as: - Invalid part names or references - Missing datums on parts - Invalid mate configurations - Constraint validation failures - Solver convergence failures

Variables:
  • message – Description of the error

  • assembly_name – Name of the assembly where error occurred

  • part_name – Name of the part involved (if applicable)

  • constraint_name – Name of the constraint that failed (if applicable)

class yapcad.assembly.assembly.AssemblyValidationResult(is_valid: bool, constraint_results: Dict[str, ~yapcad.assembly.constraint.ConstraintResult]=<factory>, failed_constraints: List[str] = <factory>, warnings: List[str] = <factory>)[source]

Bases: object

Result of validating an assembly’s constraints.

Contains the complete validation status including which constraints passed, failed, or produced warnings, along with detailed diagnostic information for each constraint.

Variables:
  • is_valid (bool) – True if all constraints are satisfied, False otherwise

  • constraint_results (Dict[str, yapcad.assembly.constraint.ConstraintResult]) – Dictionary mapping constraint name to ConstraintResult

  • failed_constraints (List[str]) – List of names of constraints that failed

  • warnings (List[str]) – List of warning messages from constraint validation

Example

>>> result = assembly.validate()
>>> if not result.is_valid:
...     print(f"Found {len(result.failed_constraints)} failures")
...     for name in result.failed_constraints:
...         cr = result.constraint_results[name]
...         print(f"  {name}: {cr.message}")
constraint_results: Dict[str, ConstraintResult]
failed_constraints: List[str]
is_valid: bool
report() str[source]

Generate detailed validation report.

warnings: List[str]

yapcad.assembly.constraint module

Assembly constraint validation for yapCAD.

This module provides high-level design constraints that validate assembly intent after mates have positioned parts. Constraints check that the resulting assembly meets design requirements like tangency, radial orientation, and clearances.

Constraints differ from mates: - Mates POSITION parts (determine transforms) - Constraints VALIDATE the result (check design intent)

Example

from yapcad.assembly.constraint import (

Constraint, ConstraintType, ConstraintResult

)

# Motor axis must be tangent to chassis (not radial) constraint = Constraint(

name=”wheel_axis_tangent”, constraint_type=ConstraintType.TANGENT_TO_CIRCLE, part=”DDSM115_MOTOR_1”, datum=”motor_axis”, center=(0.0, 0.0, 0.0), radius=124.5, description=”Motor axis tangent to wheel path for rolling motion”

)

# Evaluate against a datum in world coordinates result = constraint.evaluate(datum_world) if not result.passed:

print(f”Constraint violated: {result.error_message}”) print(f”Error: {result.error_value:.2f}°”)

class yapcad.assembly.constraint.Constraint(name: str, constraint_type: ConstraintType, part: str, datum: str, center: Tuple[float, float, float] | None = None, radius: float | None = None, direction: str | None = None, axis: Tuple[float, float, float] | None = None, reference_datum: str | None = None, plane_normal: Tuple[float, float, float] | None = None, min_value: float | None = None, max_value: float | None = None, validator: Callable[[Any], bool] | None = None, tolerance_deg: float = 1.0, tolerance_mm: float = 0.1, description: str = '', severity: str = 'error')[source]

Bases: object

A design constraint that validates assembly intent.

Constraints are evaluated after mates have positioned all parts. They verify that the resulting assembly meets design requirements.

Unlike mates (which determine part positions), constraints validate that the positions satisfy high-level design intent.

Variables:
  • name (str) – Unique identifier for this constraint

  • constraint_type (yapcad.assembly.constraint.ConstraintType) – Type of constraint to evaluate

  • part (str) – Name of the part containing the datum to check

  • datum (str) – Name of the datum feature to evaluate

  • center (Tuple[float, float, float] | None) – Reference center point for circular/radial constraints

  • radius (float | None) – Reference radius for circular constraints (mm)

  • direction (str | None) – Reference direction (“inward”, “outward”, “+x”, “-z”, etc.)

  • axis (Tuple[float, float, float] | None) – Reference axis vector for parallel/perpendicular constraints

  • reference_datum (str | None) – Reference datum for relative constraints

  • plane_normal (Tuple[float, float, float] | None) – Normal vector for plane constraints

  • min_value (float | None) – Minimum allowed value for range constraints

  • max_value (float | None) – Maximum allowed value for range constraints

  • validator (Callable[[Any], bool] | None) – Custom validation function for CUSTOM constraint type

  • tolerance_deg (float) – Angular tolerance in degrees (default: 1.0°)

  • tolerance_mm (float) – Linear tolerance in millimeters (default: 0.1mm)

  • description (str) – Human-readable description of design intent

  • severity (str) – “error”, “warning”, or “info”

Examples

# Motor axis tangent to wheel path (perpendicular to radial) >>> tangent = Constraint( … name=”wheel_axis_tangent”, … constraint_type=ConstraintType.TANGENT_TO_CIRCLE, … part=”DDSM115_MOTOR_1”, … datum=”motor_axis”, … center=(0.0, 0.0, 0.0), … radius=124.5, … tolerance_deg=2.0, … description=”Motor axis must be tangent for rolling motion” … )

# Tire tread must face outward toward tube wall >>> radial = Constraint( … name=”tire_faces_outward”, … constraint_type=ConstraintType.RADIAL_FROM_CENTER, … part=”DDSM115_MOTOR_1”, … datum=”tire_face”, … center=(0.0, 0.0, 0.0), … direction=”outward”, … tolerance_deg=5.0, … description=”Tire tread faces tube wall for traction” … )

# Mounting face must face upward >>> facing = Constraint( … name=”mount_faces_up”, … constraint_type=ConstraintType.FACING, … part=”BRACKET”, … datum=”mounting_face”, … direction=”+z”, … tolerance_deg=1.0, … description=”Mounting face must be horizontal” … )

# Part at specific radius from center >>> at_radius = Constraint( … name=”motor_at_wheel_radius”, … constraint_type=ConstraintType.AT_RADIUS, … part=”MOTOR”, … datum=”motor_center”, … center=(0.0, 0.0, 0.0), … radius=124.5, … tolerance_mm=0.5, … description=”Motor positioned on wheel path circle” … )

axis: Tuple[float, float, float] | None = None
center: Tuple[float, float, float] | None = None
constraint_type: ConstraintType
datum: str
description: str = ''
direction: str | None = None
evaluate(datum_world: Datum) ConstraintResult[source]

Evaluate this constraint against a datum in world coordinates.

Parameters:

datum_world – The datum feature transformed to world coordinates

Returns:

ConstraintResult indicating whether constraint is satisfied

Raises:
  • ValueError – If required parameters are missing

  • ImportError – If numpy is required but not available

max_value: float | None = None
min_value: float | None = None
name: str
part: str
plane_normal: Tuple[float, float, float] | None = None
radius: float | None = None
reference_datum: str | None = None
severity: str = 'error'
tolerance_deg: float = 1.0
tolerance_mm: float = 0.1
validator: Callable[[Any], bool] | None = None
class yapcad.assembly.constraint.ConstraintResult(passed: bool, error_value: float = 0.0, error_message: str = '', details: Dict[str, ~typing.Any]=<factory>)[source]

Bases: object

Result of evaluating a design constraint.

Variables:
  • passed (bool) – True if constraint is satisfied within tolerance

  • error_value (float) – Numeric measure of constraint violation (degrees or mm)

  • error_message (str) – Human-readable description of the result

  • details (Dict[str, Any]) – Additional information about the evaluation

details: Dict[str, Any]
error_message: str = ''
error_value: float = 0.0
passed: bool
class yapcad.assembly.constraint.ConstraintType(*values)[source]

Bases: Enum

High-level design constraints that validate assembly intent.

These constraint types capture common design requirements in mechanical assemblies that are difficult to express with low-level mates alone.

Variables:
  • TANGENT_TO_CIRCLE – Axis is tangent (perpendicular to radial) to a circle. Used for wheels that must roll along a circular path.

  • RADIAL_FROM_CENTER – Datum normal points toward (inward) or away from (outward) a center point. Used for features that must face the center or periphery of an assembly.

  • FACING – Datum normal points in a specified global direction (+x, -y, +z, etc.).

  • AT_RADIUS – Datum origin is at a specific radius from a center point.

  • PARALLEL_TO – Datum direction is parallel to a reference direction.

  • PERPENDICULAR_TO – Datum direction is perpendicular to a reference direction.

AT_RADIUS = 'at_radius'
CUSTOM = 'custom'
FACING = 'facing'
IN_PLANE = 'in_plane'
MIN_DISTANCE = 'min_distance'
NO_INTERFERENCE = 'no_interference'
ON_CIRCLE = 'on_circle'
PARALLEL_TO = 'parallel_to'
PERPENDICULAR_TO = 'perpendicular_to'
RADIAL_FROM_CENTER = 'radial_from_center'
TANGENT_TO_CIRCLE = 'tangent_to_circle'
yapcad.assembly.constraint.angle_between_vectors(v1: Tuple[float, float, float], v2: Tuple[float, float, float]) float[source]

Calculate angle between two vectors in degrees.

Parameters:
  • v1 – First vector (x, y, z)

  • v2 – Second vector (x, y, z)

Returns:

Angle in degrees [0, 180]

Example

>>> angle_between_vectors((1, 0, 0), (0, 1, 0))
90.0
>>> angle_between_vectors((1, 0, 0), (1, 0, 0))
0.0
yapcad.assembly.constraint.distance_to_point(p1: Tuple[float, float, float], p2: Tuple[float, float, float]) float[source]

Calculate Euclidean distance between two points.

Parameters:
  • p1 – First point (x, y, z)

  • p2 – Second point (x, y, z)

Returns:

Distance in same units as input

Example

>>> distance_to_point((0, 0, 0), (3, 4, 0))
5.0
yapcad.assembly.constraint.dot_product(v1: Tuple[float, float, float], v2: Tuple[float, float, float]) float[source]

Calculate dot product of two vectors.

Parameters:
  • v1 – First vector (x, y, z)

  • v2 – Second vector (x, y, z)

Returns:

Dot product (scalar)

Example

>>> dot_product((1, 0, 0), (0, 1, 0))
0.0
>>> dot_product((1, 2, 3), (1, 2, 3))
14.0
yapcad.assembly.constraint.is_radial_from_center(point: Tuple[float, float, float], normal: Tuple[float, float, float], center: Tuple[float, float, float], direction: str = 'outward', tolerance_deg: float = 1.0) bool[source]

Check if a normal vector points radially from a center point.

Parameters:
  • point – Location of the datum (x, y, z)

  • normal – Normal vector to check (x, y, z)

  • center – Center point (x, y, z)

  • direction – “outward” or “inward”

  • tolerance_deg – Angular tolerance in degrees

Returns:

True if normal is radial within tolerance

Example

>>> # Point at (10, 0, 0) with normal pointing in +x (outward from origin)
>>> is_radial_from_center((10, 0, 0), (1, 0, 0), (0, 0, 0), "outward")
True
yapcad.assembly.constraint.is_tangent_to_circle(axis_origin: Tuple[float, float, float], axis_direction: Tuple[float, float, float], circle_center: Tuple[float, float, float], circle_radius: float, tolerance_deg: float = 1.0) bool[source]

Check if an axis is tangent to a circle in the XY plane.

An axis is tangent when its direction is perpendicular to the radial vector from the circle center to the axis origin.

Parameters:
  • axis_origin – Point on the axis (x, y, z)

  • axis_direction – Direction vector of the axis (x, y, z)

  • circle_center – Center of the circle (x, y, z)

  • circle_radius – Radius of the circle

  • tolerance_deg – Angular tolerance in degrees

Returns:

True if axis is tangent within tolerance

Example

>>> # Axis at (10, 0, 0) pointing in +y direction, tangent to origin circle
>>> is_tangent_to_circle((10, 0, 0), (0, 1, 0), (0, 0, 0), 10.0)
True

yapcad.assembly.datum module

Datum feature system for constraint-based assembly in yapCAD.

This module provides datum features (geometric references) and part definitions that enable declarative assembly constraints. Datums are named geometric features (points, axes, planes, frames, circles) defined in a part’s local coordinate system that can be used to establish mates and constraints between parts.

Key concepts:
  • Datum: A named geometric reference on a part (point, axis, plane, etc.)

  • PartDefinition: A part with its named datum features

  • Datums transform with the part when assembled

  • Datums enable declarative mate constraints (FLUSH, CONCENTRIC, etc.)

Example usage:

from yapcad.assembly.datum import Datum, DatumType, PartDefinition
from yapcad.geom import point

# Define a motor with datum features
motor = PartDefinition(
    name="DDSM115_MOTOR",
    geometry_source="cots/motor.dsl",
    is_printable=False,
    material="Aluminum"
)

# Add mounting plane datum
motor.add_datum(Datum(
    name="stator_face",
    datum_type=DatumType.PLANE,
    origin=point(0, 10, 0),
    normal=point(0, 1, 0, 0),
    description="Stator mounting surface facing +Y"
))

# Add rotation axis datum
motor.add_datum(Datum(
    name="motor_axis",
    datum_type=DatumType.AXIS,
    origin=point(0, 0, 0),
    direction=point(0, 1, 0, 0),
    description="Motor rotation axis along Y"
))

# Add mounting hole circle datum
motor.add_datum(Datum(
    name="mounting_holes",
    datum_type=DatumType.CIRCLE,
    origin=point(0, 10, 0),
    normal=point(0, 1, 0, 0),
    radius=15.2,
    description="M2.5 mounting hole pattern at r=15.2mm"
))

# Validate all datums
issues = motor.validate_datums()
if issues:
    print("Datum validation issues:", issues)
class yapcad.assembly.datum.Datum(name: str, datum_type: DatumType, origin: List[float] = <factory>, direction: List[float] | None = None, normal: List[float] | None = None, x_axis: List[float] | None = None, y_axis: List[float] | None = None, radius: float | None = None, description: str = '', tags: List[str] = <factory>)[source]

Bases: object

A named geometric reference feature on a part.

Datums are defined in the part’s local coordinate system and transform with the part when assembled. They provide explicit geometric references that can be used to establish assembly mates and constraints.

All geometric parameters use yapCAD’s homogeneous coordinate convention:
  • Points: [x, y, z, 1] (w=1 for positions)

  • Directions/Normals: [x, y, z, 0] (w=0 for direction vectors)

Variables:
  • name (str) – Unique identifier for this datum within the part

  • datum_type (yapcad.assembly.datum.DatumType) – Type of geometric feature (POINT, AXIS, PLANE, etc.)

  • origin (List[float]) – Origin point [x, y, z, 1] in part’s local coordinates

  • direction (List[float] | None) – Direction vector [x, y, z, 0] for AXIS datum

  • normal (List[float] | None) – Normal vector [x, y, z, 0] for PLANE or CIRCLE datum

  • x_axis (List[float] | None) – X-axis direction [x, y, z, 0] for FRAME datum

  • y_axis (List[float] | None) – Y-axis direction [x, y, z, 0] for FRAME datum

  • radius (float | None) – Radius value (in mm) for CIRCLE datum

  • description (str) – Human-readable description of this datum’s purpose

  • tags (List[str]) – Optional metadata tags for filtering/searching datums

Example:

# Mounting plane on motor stator
stator_face = Datum(
    name="stator_face",
    datum_type=DatumType.PLANE,
    origin=[0, 10, 0, 1],
    normal=[0, 1, 0, 0],
    description="Stator mounting surface facing +Y"
)

# Rotation axis through motor shaft
motor_axis = Datum(
    name="motor_axis",
    datum_type=DatumType.AXIS,
    origin=[0, 0, 0, 1],
    direction=[0, 1, 0, 0],
    description="Motor rotation axis along Y"
)

# Mounting hole pattern
holes = Datum(
    name="mounting_holes",
    datum_type=DatumType.CIRCLE,
    origin=[0, 10, 0, 1],
    normal=[0, 1, 0, 0],
    radius=15.2,
    description="M2.5 mounting holes at 15.2mm radius"
)
datum_type: DatumType
description: str = ''
direction: List[float] | None = None
name: str
normal: List[float] | None = None
origin: List[float]
radius: float | None = None
tags: List[str]
transform(matrix: Matrix | List[List[float]]) Datum[source]

Return a new Datum transformed by the given 4x4 matrix.

Transforms the datum’s geometric features by applying the given transformation matrix. Points are translated, direction vectors are rotated (but not translated), and the radius is preserved (assuming uniform scaling).

Parameters:

matrix – 4x4 transformation matrix, either as yapCAD Matrix object or as a list of 4 rows (each row is a 4-element list)

Returns:

A new Datum with transformed geometry

Example:

from yapcad.xform import Translation
from yapcad.geom import point

# Original datum at origin
datum = Datum("mount", DatumType.POINT, origin=point(0, 0, 0))

# Transform by translation
T = Translation(point(10, 20, 30))
new_datum = datum.transform(T)
# new_datum.origin is now [10, 20, 30, 1]
x_axis: List[float] | None = None
y_axis: List[float] | None = None
class yapcad.assembly.datum.DatumType(*values)[source]

Bases: Enum

Types of datum features that can be defined on a part.

Datum types correspond to common geometric references used in mechanical design and assembly constraints:

  • POINT: A single point in 3D space

  • AXIS: An infinite line defined by origin + direction

  • PLANE: An infinite plane defined by origin + normal

  • FRAME: A full coordinate frame with origin + 3 orthogonal axes

  • CIRCLE: A circle defined by center + normal + radius

AXIS = 'axis'
CIRCLE = 'circle'
FRAME = 'frame'
PLANE = 'plane'
POINT = 'point'
class yapcad.assembly.datum.PartDefinition(name: str, datums: Dict[str, ~yapcad.assembly.datum.Datum]=<factory>, geometry_source: str | None = None, is_printable: bool = True, material: str = 'PETG', description: str = '')[source]

Bases: object

Definition of a part with its datum features.

A PartDefinition extends the concept of a part to include explicit geometric datum features that can be used for assembly constraints. This enables declarative mate definitions (FLUSH, CONCENTRIC, etc.) that reference named datums rather than requiring manual transform calculations.

Variables:
  • name (str) – Unique identifier for this part

  • datums (Dict[str, yapcad.assembly.datum.Datum]) – Dictionary mapping datum names to Datum objects

  • geometry_source (str | None) – Path to STL file, DSL command, or other geometry

  • is_printable (bool) – Whether this part is 3D printed (vs. COTS)

  • material (str) – Material specification (e.g., “PETG”, “Aluminum”)

  • description (str) – Human-readable description of part function

Example:

from yapcad.assembly.datum import PartDefinition, Datum, DatumType
from yapcad.geom import point

# Define a wheel bracket part
bracket = PartDefinition(
    name="WHEEL_BRACKET",
    geometry_source="parts/bracket.dsl",
    is_printable=True,
    material="PETG",
    description="Mounting bracket for drive wheel motor"
)

# Add datum for motor mounting face
bracket.add_datum(Datum(
    name="motor_interface",
    datum_type=DatumType.PLANE,
    origin=point(0, 0, 0),
    normal=point(0, 0, 1, 0),
    description="Motor mounting face normal to +Z"
))

# Add datum for motor bore axis
bracket.add_datum(Datum(
    name="bore_axis",
    datum_type=DatumType.AXIS,
    origin=point(0, 0, 0),
    direction=point(0, 0, 1, 0),
    description="Motor shaft bore along Z axis"
))

# Validate all datums
issues = bracket.validate_datums()
if issues:
    print("Warning:", issues)
add_datum(datum: Datum) PartDefinition[source]

Add a datum feature to this part.

Parameters:

datum – The Datum object to add

Returns:

Self (for method chaining)

Raises:

ValueError – If a datum with the same name already exists

Example:

bracket = PartDefinition("BRACKET")
bracket.add_datum(Datum("face", DatumType.PLANE, ...))
bracket.add_datum(Datum("axis", DatumType.AXIS, ...))
datums: Dict[str, Datum]
description: str = ''
geometry_source: str | None = None
get_datum(name: str) Datum[source]

Get a datum by name.

Parameters:

name – Name of the datum to retrieve

Returns:

The Datum object

Raises:

KeyError – If no datum with that name exists

Example:

bracket = PartDefinition("BRACKET")
bracket.add_datum(Datum("mount_face", ...))
face = bracket.get_datum("mount_face")
is_printable: bool = True
material: str = 'PETG'
name: str
validate_datums() List[str][source]

Validate all datum definitions, returning list of issues.

Performs validation checks on all datums to detect potential errors:
  • Duplicate definitions (same origin and type)

  • Overlapping features that might indicate copy-paste errors

Returns:

List of warning/error messages (empty if all valid)

Example:

bracket = PartDefinition("BRACKET")
bracket.add_datum(Datum("face1", DatumType.PLANE, origin=[0,0,0], ...))
bracket.add_datum(Datum("face2", DatumType.PLANE, origin=[0,0,0], ...))

issues = bracket.validate_datums()
# issues will contain warning about duplicate origin

yapcad.assembly.datum_registry module

Central datum registry for cross-DSL datum linking.

This module provides a unified interface to access datums from various sources: - DSL files (via sidecar JSON or metadata extraction) - COTS surrogate JSON files (for commercial off-the-shelf parts) - Programmatically defined datums

The registry enables face-to-face mate computation by providing a single point of access for datum definitions regardless of their source.

Example usage:

from yapcad.assembly.datum_registry import DatumRegistry

# Get a datum from a COTS surrogate file
stator_face = DatumRegistry.get_datum(
    "cots/xh430_surrogate.json",
    "stator_mounting_face"
)

# Get a datum by part name (searches registered sources)
servo_axis = DatumRegistry.get_datum(
    "AXIS3_SERVO_XH430",
    "rotation_axis"
)

Copyright (c) 2026 yapCAD contributors License: MIT

class yapcad.assembly.datum_registry.DatumRegistry[source]

Bases: object

Central registry for datums from DSL files and COTS surrogates.

The registry provides a unified interface to access datums regardless of their source (DSL, STL metadata, surrogate JSON files).

This is implemented as a singleton to ensure consistent state across the application.

Class Methods:

get_datum(source, name): Get a datum by source and name register_source(source_id, datums): Register a source with datums register_datum(source_id, datum): Add a single datum to a source list_sources(): List all registered sources list_datums(source): List all datums in a source clear(): Clear all registered datums (for testing)

Example:

# Load from surrogate JSON
stator = DatumRegistry.get_datum(
    "cots/xh430_surrogate.json",
    "stator_mounting_face"
)

# Register programmatic datums
DatumRegistry.register_datum(
    "LINK_2_3",
    Datum("servo_mount_face", DatumType.PLANE, ...)
)
classmethod add_search_path(path: str) None[source]

Add a directory to search for datum source files.

Parameters:

path – Directory path to add to search paths

classmethod clear() None[source]

Clear all registered datums (useful for testing).

classmethod get_datum(source: str, name: str) Datum[source]

Get a datum by source and name.

Parameters:
  • source – Source identifier - can be: - DSL file path: “scara_arm/scara_arm.dsl” - Surrogate JSON: “cots/xh430_surrogate.json” - Part name: “AXIS3_SERVO_XH430”, “LINK_2_3”

  • name – Datum name within the source e.g., “servo_mount_face”, “stator_face”

Returns:

The Datum object

Raises:

KeyError – If datum not found

classmethod get_datums_for_source(source: str) Dict[str, Datum][source]

Get all datums from a source.

Parameters:

source – Source identifier

Returns:

Dictionary of datum name -> Datum object

classmethod list_datums(source: str) List[str][source]

List all datum names in a source.

Parameters:

source – Source identifier

Returns:

List of datum names

classmethod list_sources() List[str][source]

List all registered source identifiers.

classmethod register_datum(source_id: str, datum: Datum) None[source]

Add a single datum to a source.

Creates the source if it doesn’t exist.

Parameters:
  • source_id – Source identifier

  • datum – The Datum to register

classmethod register_part(part: PartDefinition) None[source]

Register a PartDefinition and all its datums.

Parameters:

part – PartDefinition with datum features

classmethod register_source(source_id: str, datums: Dict[str, Datum], source_type: str = 'programmatic', metadata: Dict[str, Any] | None = None) None[source]

Register a source with its datums.

Parameters:
  • source_id – Unique identifier for this source

  • datums – Dictionary of datum name -> Datum object

  • source_type – Type of source (“json”, “dsl”, “programmatic”)

  • metadata – Optional metadata about the source

class yapcad.assembly.datum_registry.DatumSource(source_id: str, source_type: str, file_path: str | None = None, datums: Dict[str, ~yapcad.assembly.datum.Datum]=<factory>, part_names: Set[str] = <factory>, metadata: Dict[str, ~typing.Any]=<factory>)[source]

Bases: object

Represents a source of datum definitions.

Variables:
  • source_id (str) – Unique identifier for this source (path or name)

  • source_type (str) – Type of source (“json”, “dsl”, “programmatic”)

  • file_path (str | None) – Absolute path to source file (if file-based)

  • datums (Dict[str, yapcad.assembly.datum.Datum]) – Dictionary of datum name -> Datum object

  • part_names (Set[str]) – Set of part names defined in this source

  • metadata (Dict[str, Any]) – Additional source metadata

datums: Dict[str, Datum]
file_path: str | None = None
metadata: Dict[str, Any]
part_names: Set[str]
source_id: str
source_type: str
yapcad.assembly.datum_registry.datum_to_transform_matrix(datum: Datum) Any | None[source]

Convert a Datum to a 4x4 transformation matrix.

For FRAME datums, constructs the full transform from axes. For PLANE datums, constructs a frame with normal as Z-axis. For AXIS datums, constructs a frame with direction as Z-axis.

Parameters:

datum – The datum to convert

Returns:

4x4 numpy array (if numpy available), or None

yapcad.assembly.face_mate module

Face-to-face mate system with automatic transform computation.

This module provides the FaceToFaceMate class which computes transforms automatically from datum face constraints, replacing hardcoded transform values with explicit geometric relationships.

Key Features:
  • Automatic transform computation from datum geometry

  • Support for FLUSH, ALIGNED, OFFSET, and CONCENTRIC constraints

  • Validation of mate satisfaction with tolerances

  • Integration with DatumRegistry for cross-DSL datum lookup

Example usage:

from yapcad.assembly.face_mate import FaceToFaceMate
from yapcad.assembly.mate import MateType

# Define a mate between servo stator and link mounting face
servo_mate = FaceToFaceMate(
    name="axis3_servo_to_link",
    parent_part="LINK_2_3",
    parent_datum="servo_mount_face",
    parent_source="scara_arm/scara_arm.dsl",
    child_part="AXIS3_SERVO_XH430",
    child_datum="stator_mounting_face",
    child_source="cots/xh430_surrogate.json",
    constraint=MateType.COINCIDENT,
)

# Compute the transform automatically
transform_matrix = servo_mate.compute_transform()

Copyright (c) 2026 yapCAD contributors License: MIT

class yapcad.assembly.face_mate.FaceToFaceMate(name: str, parent_part: str, parent_datum: str, child_part: str, child_datum: str, parent_source: str | None = None, child_source: str | None = None, constraint: MateType = MateType.COINCIDENT, offset_distance: float = 0.0, rotation_about_normal: float = 0.0, _computed_transform: Any | None = None, _parent_datum_obj: Datum | None = None, _child_datum_obj: Datum | None = None)[source]

Bases: object

Defines how two parts mate via their datum faces.

This is the key abstraction that replaces hardcoded transforms with explicit geometric constraints. The transform is computed automatically from the datum geometry.

Constraint Types:
  • COINCIDENT/FLUSH: Faces touch, normals point in opposite directions

  • CONCENTRIC: Axes are colinear (for cylindrical features)

  • PARALLEL: Faces parallel with optional offset

  • ALIGNED: Faces touch, normals point in same direction

Variables:
  • name (str) – Descriptive name for this mate

  • parent_part (str) – Name of the parent (fixed) part

  • parent_datum (str) – Datum name on parent part

  • parent_source (str | None) – Source file for parent datums (DSL or surrogate JSON)

  • child_part (str) – Name of the child (moving) part

  • child_datum (str) – Datum name on child part

  • child_source (str | None) – Source file for child datums

  • constraint (yapcad.assembly.mate.MateType) – Constraint type (MateType enum)

  • offset_distance (float) – Optional offset along normal (for OFFSET constraint)

  • rotation_about_normal (float) – Optional rotation about mating axis (degrees)

child_datum: str
child_part: str
child_source: str | None = None
compute_transform(parent_world_transform: Any | None = None) Any | None[source]

Compute the transform that places child relative to parent.

This is the core algorithm that replaces hardcoded transforms.

Parameters:

parent_world_transform – Optional 4x4 world transform of parent (for computing absolute child position)

Returns:

4x4 numpy array transform, or None if numpy not available

Raises:
  • KeyError – If datums cannot be found

  • ValueError – If constraint type not supported

constraint: MateType = 'coincident'
invalidate_cache() None[source]

Clear cached transform (call if datums change).

name: str
offset_distance: float = 0.0
parent_datum: str
parent_part: str
parent_source: str | None = None
rotation_about_normal: float = 0.0
validate(tolerance_mm: float = 0.1, tolerance_deg: float = 1.0) MateValidationResult[source]

Validate that the mate constraint is satisfied.

Parameters:
  • tolerance_mm – Linear tolerance in millimeters

  • tolerance_deg – Angular tolerance in degrees

Returns:

MateValidationResult with satisfaction status and errors

class yapcad.assembly.face_mate.MateValidationResult(satisfied: bool, error_distance: float = 0.0, error_angle: float = 0.0, error_message: str = '', details: Dict[str, ~typing.Any]=<factory>)[source]

Bases: object

Result of validating a mate constraint.

Variables:
  • satisfied (bool) – True if constraint is satisfied within tolerance

  • error_distance (float) – Position error in mm

  • error_angle (float) – Angular error in degrees

  • error_message (str) – Human-readable description

  • details (Dict[str, Any]) – Additional validation information

details: Dict[str, Any]
error_angle: float = 0.0
error_distance: float = 0.0
error_message: str = ''
satisfied: bool
yapcad.assembly.face_mate.compute_concentric_transform(parent_datum: Datum, child_datum: Datum, offset: float = 0.0) Any[source]

Compute transform to make child axis colinear with parent axis.

For CONCENTRIC mates, the axes must be colinear but origins can differ.

Parameters:
  • parent_datum – Parent axis datum

  • child_datum – Child axis datum

  • offset – Offset along axis direction

Returns:

4x4 numpy transform matrix

yapcad.assembly.face_mate.compute_flush_transform(parent_datum: Datum, child_datum: Datum, offset: float = 0.0, rotation_deg: float = 0.0, flip_normal: bool = True) Any[source]

Compute transform to make child flush with parent.

The result is a transform T such that: - child_datum.origin moves to parent_datum.origin (+ offset along normal) - child_datum.normal aligns with parent_datum.normal

(opposite if flip_normal=True for face-to-face contact)

Uses Rodrigues’ rotation formula for efficient axis-angle rotation.

Parameters:
  • parent_datum – Parent datum (target)

  • child_datum – Child datum (to be transformed)

  • offset – Optional offset along parent normal

  • rotation_deg – Optional rotation about mating axis

  • flip_normal – If True, child normal opposes parent (face-to-face)

Returns:

4x4 numpy transform matrix

yapcad.assembly.face_mate.compute_parallel_transform(parent_datum: Datum, child_datum: Datum, offset: float = 0.0) Any[source]

Compute transform to make child parallel to parent.

For PARALLEL mates, normals align but origins don’t necessarily coincide.

Parameters:
  • parent_datum – Parent plane datum

  • child_datum – Child plane datum

  • offset – Offset along normal direction

Returns:

4x4 numpy transform matrix

yapcad.assembly.face_mate.create_axis_mate(name: str, parent_part: str, parent_datum: str, child_part: str, child_datum: str, parent_source: str | None = None, child_source: str | None = None, offset: float = 0.0) FaceToFaceMate[source]

Create a concentric (axis-aligned) mate.

Convenience function for coaxial alignment.

Parameters:
  • name – Mate name

  • parent_part – Parent part name

  • parent_datum – Parent axis datum name

  • child_part – Child part name

  • child_datum – Child axis datum name

  • parent_source – Optional source file for parent

  • child_source – Optional source file for child

  • offset – Optional offset along axis

Returns:

Configured FaceToFaceMate

yapcad.assembly.face_mate.create_face_mate(name: str, parent_part: str, parent_datum: str, child_part: str, child_datum: str, parent_source: str | None = None, child_source: str | None = None, offset: float = 0.0, rotation: float = 0.0) FaceToFaceMate[source]

Create a face-to-face (COINCIDENT) mate.

Convenience function for the most common mate type.

Parameters:
  • name – Mate name

  • parent_part – Parent part name

  • parent_datum – Parent datum name

  • child_part – Child part name

  • child_datum – Child datum name

  • parent_source – Optional source file for parent

  • child_source – Optional source file for child

  • offset – Optional offset along normal

  • rotation – Optional rotation about normal (degrees)

Returns:

Configured FaceToFaceMate

yapcad.assembly.intent module

Declarative Assembly Intent System for yapCAD.

This module provides a declarative approach to assembly design where designers specify WHAT parts must do (functional requirements) rather than HOW to achieve it (explicit transforms). The system derives geometry from requirements.

Key Concepts:

AssemblyIntent: Top-level container for declarative assembly specification FunctionalRequirement: What a part must DO (contact, roll, align, etc.) Connection: How parts relate (topology, not exact position) Clearance: What must NOT happen (collision avoidance) ReferenceGeometry: Fixed geometry that parts must relate to

Example - Wheel Assembly:
>>> tube = ReferenceGeometry(
...     name="tube_inner_wall",
...     geometry_type="cylinder",
...     center=(0, 0, 0),
...     axis=(0, 0, 1),
...     radius=175.0,
... )
>>>
>>> wheel_assembly = AssemblyIntent(
...     name="wheel_pod",
...     reference_geometry={"tube": tube},
...     functional_requirements=[
...         ContactRequirement(
...             name="wheel_contact",
...             part="motor",
...             surface="tire_outer",
...             target="tube_inner_wall",
...             contact_type="rolling",
...         ),
...         RollRequirement(
...             name="roll_along_z",
...             part="motor",
...             roll_direction="along_tube_axis",
...         ),
...     ],
...     connections=[
...         Connection(
...             parent="chassis.pivot_boss",
...             child="wheel_arm.pivot_bore",
...             joint_type="revolute",
...             axis="tangent",
...         ),
...     ],
...     clearances=[
...         Clearance("motor", "chassis", min_distance=5.0),
...     ],
...     derived_parameters=["wheel_arm.length", "wheel_center_radius"],
... )
>>>
>>> result = wheel_assembly.solve()
>>> print(result.derived["wheel_arm.length"])

Copyright (c) 2026 yapCAD contributors License: MIT

class yapcad.assembly.intent.AssemblyIntent(name: str, description: str = '', functional_requirements: List[FunctionalRequirement] = <factory>, connections: List[Connection] = <factory>, clearances: List[Clearance] = <factory>, reference_geometry: Dict[str, ~yapcad.assembly.intent.ReferenceGeometry]=<factory>, part_definitions: Dict[str, ~yapcad.assembly.datum.PartDefinition]=<factory>, derived_parameters: List[str] = <factory>, explicit_constraints: List[Constraint] = <factory>)[source]

Bases: object

Declarative specification of assembly requirements.

AssemblyIntent is the top-level container for a declarative assembly. Instead of specifying transforms, designers specify: - What parts must DO (functional requirements) - How parts CONNECT (topology) - What must NOT happen (clearances)

The system derives the geometry that satisfies all requirements.

Variables:

Example

>>> assembly = AssemblyIntent(
...     name="wheel_pod",
...     functional_requirements=[...],
...     connections=[...],
...     clearances=[...],
... )
>>> result = assembly.solve()
add_clearance(clearance: Clearance) AssemblyIntent[source]

Add a clearance constraint.

add_connection(conn: Connection) AssemblyIntent[source]

Add a connection.

add_reference_geometry(geom: ReferenceGeometry) AssemblyIntent[source]

Add reference geometry.

add_requirement(req: FunctionalRequirement) AssemblyIntent[source]

Add a functional requirement.

clearances: List[Clearance]
collect_constraints() List[Dict[str, Any]][source]

Collect all constraints from requirements.

Converts high-level functional requirements into low-level constraint specifications.

Returns:

List of constraint specification dicts

collect_derived_parameters() List[str][source]

Collect all parameters that need to be derived.

Returns:

List of parameter names

connections: List[Connection]
derived_parameters: List[str]
description: str = ''
explicit_constraints: List[Constraint]
functional_requirements: List[FunctionalRequirement]
name: str
part_definitions: Dict[str, PartDefinition]
reference_geometry: Dict[str, ReferenceGeometry]
report() str[source]

Generate human-readable summary of the assembly intent.

solve() SolveResult[source]

Solve the assembly intent to derive parameters.

This is the main entry point for converting a declarative specification into actual geometry.

Returns:

SolveResult with derived values and validation status

to_assembly() Assembly[source]

Convert solved intent to yapCAD Assembly object.

Returns:

Assembly object with parts, mates, and constraints

validate() List[str][source]

Validate the assembly intent specification.

Checks for: - Missing reference geometry - Invalid part references - Conflicting requirements - Under/over-constrained systems

Returns:

List of validation error/warning messages

class yapcad.assembly.intent.AxisOrientationRequirement(name: str, part: str, description: str = '', priority: int = 1, axis_datum: str = '', orientation: str = 'tangent', reference: str = 'global', pointing: str = 'toward')[source]

Bases: FunctionalRequirement

Part axis must have specific orientation relative to reference.

This requirement specifies that a datum axis on a part must point in a specific direction relative to the assembly or reference geometry.

Variables:
  • axis_datum (str) – Name of the axis datum on the part

  • orientation (str) – Direction specification - “tangent”: Tangent to reference cylinder at part position - “radial”: Pointing toward/away from reference center - “axial”: Parallel to reference axis (e.g., tube Z-axis) - “+x”, “-y”, “+z”: Global direction - tuple: Custom direction vector

  • reference (str) – Name of reference geometry or “global”

  • pointing (str) – “toward” or “away” for radial orientation

Example

>>> axis_req = AxisOrientationRequirement(
...     name="motor_tangent",
...     part="motor",
...     axis_datum="motor_axis",
...     orientation="tangent",
...     reference="tube",
... )
axis_datum: str = ''
get_implied_constraints(reference_geometry: Dict[str, ReferenceGeometry]) List[Dict[str, Any]][source]

Convert orientation requirement to constraint.

orientation: str = 'tangent'
pointing: str = 'toward'
reference: str = 'global'
class yapcad.assembly.intent.Clearance(part_a: str, part_b: str, min_distance: float, check_type: str = 'bounding_box', critical: bool = True)[source]

Bases: object

Parts must maintain minimum distance (no collision).

Clearance constraints ensure that parts do not interfere with each other or with reference geometry.

Variables:
  • part_a (str) – First part name

  • part_b (str) – Second part name (or reference geometry name)

  • min_distance (float) – Minimum allowed distance (mm)

  • check_type (str) – How to measure distance - “bounding_box”: Use axis-aligned bounding boxes (fast) - “convex_hull”: Use convex hull approximation - “mesh”: Use full mesh collision (slow, accurate)

  • critical (bool) – If True, violation is error; else warning

Example

>>> clearance = Clearance(
...     part_a="motor",
...     part_b="chassis_plate",
...     min_distance=5.0,
... )
check_type: str = 'bounding_box'
critical: bool = True
min_distance: float
part_a: str
part_b: str
validate(transforms: Dict[str, Any], part_bounds: Dict[str, Any]) ClearanceResult[source]

Check if clearance is satisfied.

Parameters:
  • transforms – Part name -> transform matrix

  • part_bounds – Part name -> bounding box/mesh

Returns:

ClearanceResult with validation status

class yapcad.assembly.intent.ClearanceResult(satisfied: bool, actual_distance: float, required_distance: float, message: str = '', interference_point: Tuple[float, float, float] | None = None)[source]

Bases: object

Result of clearance validation.

actual_distance: float
interference_point: Tuple[float, float, float] | None = None
message: str = ''
required_distance: float
satisfied: bool
class yapcad.assembly.intent.Connection(parent: str, child: str, joint_type: str = 'rigid', axis: str | ~typing.Tuple[float, float, float] | None=None, limits: Tuple[float, float] | None=None, interface_type: str | None = None, interface_details: Dict[str, ~typing.Any]=<factory>)[source]

Bases: object

Defines how two parts connect (topology).

Connections specify the parent-child relationships between parts and the type of joint (rigid, revolute, prismatic, etc.).

Variables:
  • parent (str) – Parent datum in “part.datum” format

  • child (str) – Child datum in “part.datum” format

  • joint_type (str) – Type of connection - “rigid”: No relative motion - “revolute”: Rotation about axis - “prismatic”: Translation along axis - “cylindrical”: Rotation + translation on axis - “spherical”: Ball-and-socket

  • axis (str | Tuple[float, float, float] | None) – Axis specification for joints - “tangent”, “radial”, “axial” for reference-relative - tuple (x, y, z) for explicit direction

  • limits (Tuple[float, float] | None) – (min, max) for joint limits (degrees or mm)

  • interface_type (str | None) – Physical connection type

  • interface_details (Dict[str, Any]) – Additional interface parameters

Example

>>> conn = Connection(
...     parent="chassis.pivot_boss",
...     child="wheel_arm.pivot_bore",
...     joint_type="revolute",
...     axis="tangent",
...     limits=(-15, 15),
... )
axis: str | Tuple[float, float, float] | None = None
child: str
get_child_datum() str[source]

Extract child datum name.

get_child_part() str[source]

Extract child part name.

get_parent_datum() str[source]

Extract parent datum name.

get_parent_part() str[source]

Extract parent part name.

interface_details: Dict[str, Any]
interface_type: str | None = None
joint_type: str = 'rigid'
limits: Tuple[float, float] | None = None
parent: str
to_mate_spec() Dict[str, Any][source]

Convert to mate specification dict.

class yapcad.assembly.intent.ContactRequirement(name: str, part: str, description: str = '', priority: int = 1, surface: str = '', target: str = '', contact_type: str = 'static', preload_source: str | None = None)[source]

Bases: FunctionalRequirement

Part surface must contact a reference surface.

This requirement specifies that a datum surface on a part must touch a reference geometry surface. Common examples: - Wheel tire contacting tube inner wall - Mounting face flush with bracket surface - Ball bearing in socket

Variables:
  • surface (str) – Datum name on part (e.g., “tire_outer_diameter”)

  • target (str) – Reference geometry name (e.g., “tube_inner_wall”)

  • contact_type (str) – “static”, “rolling”, or “sliding”

  • preload_source (str | None) – Optional name of what provides contact force

Example

>>> contact = ContactRequirement(
...     name="wheel_contact",
...     part="ddsm115_motor",
...     surface="tire_outer_diameter",
...     target="tube_inner_wall",
...     contact_type="rolling",
...     preload_source="suspension_spring",
... )
contact_type: str = 'static'
get_derived_parameters() List[str][source]

List parameters that can be derived from this requirement.

Returns:

List of parameter names in “part.parameter” format

get_implied_constraints(reference_geometry: Dict[str, ReferenceGeometry]) List[Dict[str, Any]][source]

Contact implies position at specific radius/distance.

preload_source: str | None = None
surface: str = ''
target: str = ''
class yapcad.assembly.intent.FunctionalRequirement(name: str, part: str, description: str = '', priority: int = 1)[source]

Bases: ABC

Base class for functional requirements.

Functional requirements describe WHAT a part must DO, not HOW to achieve it. The system derives constraints and parameters from these requirements.

Subclasses implement get_implied_constraints() to convert the high-level requirement into low-level constraints that the solver can work with.

Variables:
  • name (str) – Unique identifier for this requirement

  • part (str) – Name of the part that must satisfy this requirement

  • description (str) – Human-readable description of the requirement

  • priority (int) – Relative importance (higher = more important)

description: str = ''
get_derived_parameters() List[str][source]

List parameters that can be derived from this requirement.

Returns:

List of parameter names in “part.parameter” format

abstractmethod get_implied_constraints(reference_geometry: Dict[str, ReferenceGeometry]) List[Dict[str, Any]][source]

Derive low-level constraints from this requirement.

Parameters:

reference_geometry – Dictionary of available reference geometry

Returns:

List of constraint specifications (dicts that can create Constraints)

name: str
part: str
priority: int = 1
class yapcad.assembly.intent.GeometryType(*values)[source]

Bases: Enum

Types of reference geometry.

CONE = 'cone'
CYLINDER = 'cylinder'
LINE = 'line'
PLANE = 'plane'
POINT = 'point'
SPHERE = 'sphere'
class yapcad.assembly.intent.ParallelAxesRequirement(name: str, part: str, description: str = '', priority: int = 1, axis_a: str = '', axis_b: str = '', allow_opposite: bool = True)[source]

Bases: FunctionalRequirement

Two axes must be parallel.

This requirement specifies that two datum axes (on same or different parts) must be parallel. Common use: pivot axis parallel to motor axis for clean suspension motion.

Variables:
  • axis_a (str) – First axis in “part.datum” format

  • axis_b (str) – Second axis in “part.datum” format

  • allow_opposite (bool) – If True, axes can point in opposite directions

Example

>>> parallel = ParallelAxesRequirement(
...     name="pivot_motor_parallel",
...     part="wheel_arm",  # Primary part
...     axis_a="wheel_arm.pivot_bore_axis",
...     axis_b="motor.motor_axis",
... )
allow_opposite: bool = True
axis_a: str = ''
axis_b: str = ''
get_implied_constraints(reference_geometry: Dict[str, ReferenceGeometry]) List[Dict[str, Any]][source]

Generate parallel constraint.

class yapcad.assembly.intent.ReachRequirement(name: str, part: str, description: str = '', priority: int = 1, end_effector: str = '', base: str = '', min_reach: float = 0.0, max_reach: float = 100.0)[source]

Bases: FunctionalRequirement

End effector must have specified reach envelope.

For serial manipulators (SCARA arms, etc.), specifies the minimum and maximum reach from the base.

Variables:
  • end_effector (str) – Datum name on end effector part (e.g., “tool_point”)

  • base (str) – Datum name on base part (e.g., “base_center”)

  • min_reach (float) – Minimum distance from base (mm)

  • max_reach (float) – Maximum distance from base (mm)

Example

>>> reach = ReachRequirement(
...     name="scara_workspace",
...     part="wrist",
...     end_effector="wrist.tool_point",
...     base="tower.axis1_center",
...     min_reach=50.0,
...     max_reach=200.0,
... )
base: str = ''
end_effector: str = ''
get_derived_parameters() List[str][source]

List parameters that can be derived from this requirement.

Returns:

List of parameter names in “part.parameter” format

get_implied_constraints(reference_geometry: Dict[str, ReferenceGeometry]) List[Dict[str, Any]][source]

Reach implies link length constraints.

max_reach: float = 100.0
min_reach: float = 0.0
class yapcad.assembly.intent.ReferenceGeometry(name: str, geometry_type: str, center: Tuple[float, float, float] | None = None, axis: Tuple[float, float, float] | None = None, radius: float | None = None, normal: Tuple[float, float, float] | None = None, inner: bool = False)[source]

Bases: object

Fixed reference geometry that parts must relate to.

Reference geometry defines the environment or constraints that the assembly operates within. Examples include tube walls, mounting surfaces, and clearance envelopes.

Variables:
  • name (str) – Unique identifier for this geometry

  • geometry_type (str) – Type of geometry (cylinder, plane, etc.)

  • center (Tuple[float, float, float] | None) – Center point (x, y, z) for most geometry types

  • axis (Tuple[float, float, float] | None) – Direction vector for cylinders, cones, lines

  • radius (float | None) – Radius for cylinders, spheres, cones

  • normal (Tuple[float, float, float] | None) – Normal vector for planes

  • inner (bool) – True if surface is inner (e.g., inner wall of tube)

Example

>>> # 350mm ID tube
>>> tube = ReferenceGeometry(
...     name="tube_inner_wall",
...     geometry_type="cylinder",
...     center=(0, 0, 0),
...     axis=(0, 0, 1),
...     radius=175.0,
...     inner=True,
... )
axis: Tuple[float, float, float] | None = None
center: Tuple[float, float, float] | None = None
geometry_type: str
get_normal_at(point: Tuple[float, float, float]) Tuple[float, float, float][source]

Get the surface normal at a given point.

For cylinders: radial direction from axis. For planes: constant normal. For spheres: radial direction from center.

Returns:

Unit normal vector (x, y, z)

get_surface_point(theta: float = 0.0, z: float = 0.0) Tuple[float, float, float][source]

Get a point on the surface at given parameters.

For cylinders: theta is angle in radians, z is height along axis. For planes: theta and z are ignored. For spheres: theta is azimuth, z is elevation (radians).

Returns:

Point (x, y, z) on the surface

inner: bool = False
name: str
normal: Tuple[float, float, float] | None = None
radius: float | None = None
class yapcad.assembly.intent.RollRequirement(name: str, part: str, description: str = '', priority: int = 1, roll_direction: str = 'along_tube_axis', axis_datum: str = 'rotation_axis')[source]

Bases: FunctionalRequirement

Part must roll in a specified direction.

This requirement specifies that a rotating part (wheel, roller, etc.) must roll in a specific direction. This IMPLIES that the rotation axis must be perpendicular to the roll direction.

For a wheel rolling along the Z-axis (tube axis): - Roll direction: (0, 0, 1) - Rotation axis must be tangent (perpendicular to both Z and radial)

Variables:
  • roll_direction (str) – Direction of travel - “along_tube_axis” or “+z” for vertical tube - “(x, y, z)” tuple for custom direction

  • axis_datum (str) – Name of rotation axis datum on part

Example

>>> roll = RollRequirement(
...     name="wheel_rolls_z",
...     part="motor",
...     roll_direction="along_tube_axis",
...     axis_datum="motor_axis",
... )
axis_datum: str = 'rotation_axis'
get_implied_constraints(reference_geometry: Dict[str, ReferenceGeometry]) List[Dict[str, Any]][source]

Rolling implies rotation axis perpendicular to roll direction.

roll_direction: str = 'along_tube_axis'
class yapcad.assembly.intent.SolveResult(success: bool, derived: Dict[str, float]=<factory>, transforms: Dict[str, ~typing.Any]=<factory>, constraints_generated: Dict[str, ~typing.Any]]=<factory>, validation: Any | None = None, errors: List[str] = <factory>, warnings: List[str] = <factory>)[source]

Bases: object

Result of solving an AssemblyIntent.

Contains derived parameters, computed transforms, and validation status.

Variables:
  • success (bool) – True if all requirements could be satisfied

  • derived (Dict[str, float]) – Dictionary of derived parameter values

  • transforms (Dict[str, Any]) – Dictionary of computed transforms (part name -> 4x4 matrix)

  • constraints_generated (List[Dict[str, Any]]) – List of constraint specifications generated

  • validation (Any | None) – Validation result for all constraints

  • errors (List[str]) – List of error messages if solve failed

  • warnings (List[str]) – List of warning messages

constraints_generated: List[Dict[str, Any]]
derived: Dict[str, float]
errors: List[str]
report() str[source]

Generate human-readable solve report.

success: bool
transforms: Dict[str, Any]
validation: Any | None = None
warnings: List[str]
yapcad.assembly.intent.create_scara_arm_intent() AssemblyIntent[source]

Create example SCARA arm assembly intent.

Returns:

AssemblyIntent for a 3-axis SCARA arm

yapcad.assembly.intent.create_wheel_assembly_intent() AssemblyIntent[source]

Create example wheel assembly intent for the tube robot.

This demonstrates how to declare a wheel assembly using functional requirements rather than explicit transforms.

Returns:

AssemblyIntent for a wheel pod assembly

yapcad.assembly.kinematic_integration module

Kinematic chain integration for yapCAD assembly system.

This module provides integration between the assembly constraint/mate system and the kinematic chain transform propagation system. It enables:

  1. Transform propagation from kinematic chains to assembly validation

  2. Constraint evaluation in world coordinates using chain transforms

  3. Forward kinematics: root-to-leaf transform computation

  4. Assembly-wide constraint validation with comprehensive reporting

Key Classes:

KinematicConstraint: Enhanced constraint that works with world transforms ConstraintEvaluator: Evaluates constraints given world transform dict ValidationReport: Comprehensive assembly validation results AssemblyValidator: Validates full assemblies against constraint sets

Example

>>> from yapcad.assembly.kinematic_integration import (
...     AssemblyValidator, KinematicConstraint, ConstraintType
... )
>>>
>>> # Create constraints
>>> tangent = KinematicConstraint(
...     name="wheel_tangent",
...     constraint_type=ConstraintType.TANGENT,
...     frame_a="DDSM115_MOTOR_1",
...     frame_b=None,  # World reference
...     reference_center=(0, 0, 0),
...     reference_radius=124.5,
...     tolerance_deg=2.0
... )
>>>
>>> # Get transforms from kinematic chain
>>> from yapcad.kinematics import KinematicChain
>>> chain = KinematicChain("my_assembly")
>>> transforms = chain.get_all_world_transforms()
>>>
>>> # Validate
>>> validator = AssemblyValidator()
>>> validator.add_constraint(tangent)
>>> report = validator.validate(transforms)
>>> print(report)

Copyright (c) 2026 yapCAD contributors License: MIT

class yapcad.assembly.kinematic_integration.AssemblyValidator(name: str = 'assembly')[source]

Bases: object

Validates assemblies against a set of kinematic constraints.

The validator maintains a collection of constraints and evaluates them against world transforms from a kinematic chain.

Example

>>> validator = AssemblyValidator()
>>> validator.add_constraint(KinematicConstraint(...))
>>> validator.add_constraint(KinematicConstraint(...))
>>>
>>> # Get transforms from kinematic chain
>>> transforms = chain.get_all_world_transforms()
>>>
>>> # Validate
>>> report = validator.validate(transforms)
>>> if not report.is_valid:
...     print(report.detailed_report())
add_constraint(constraint: KinematicConstraint) None[source]

Add a constraint to the validator.

Parameters:

constraint – KinematicConstraint to add

add_constraints(constraints: List[KinematicConstraint]) None[source]

Add multiple constraints.

Parameters:

constraints – List of constraints to add

clear_constraints() None[source]

Remove all constraints.

validate(world_transforms: Dict[str, Any]) ValidationReport[source]

Validate all constraints against given world transforms.

Parameters:

world_transforms – Dictionary mapping part names to Transform objects or 4x4 numpy arrays

Returns:

ValidationReport with comprehensive evaluation results

validate_and_raise(world_transforms: Dict[str, Any]) None[source]

Validate and raise exception if any error-severity constraint fails.

Parameters:

world_transforms – Dictionary mapping part names to transforms

Raises:

ValueError – If any error-severity constraint fails

class yapcad.assembly.kinematic_integration.ConstraintEvaluationResult(satisfied: bool, error: float = 0.0, error_vector: Tuple[float, float, float] | None=None, error_message: str = '', details: Dict[str, ~typing.Any]=<factory>)[source]

Bases: object

Result of evaluating a single constraint.

Variables:
  • satisfied (bool) – True if constraint is satisfied within tolerance

  • error (float) – Numeric error value (distance in mm or angle in degrees)

  • error_vector (Tuple[float, float, float] | None) – Optional direction vector of error for visualization

  • error_message (str) – Human-readable description

  • details (Dict[str, Any]) – Additional diagnostic information

details: Dict[str, Any]
error: float = 0.0
error_message: str = ''
error_vector: Tuple[float, float, float] | None = None
satisfied: bool
class yapcad.assembly.kinematic_integration.KinematicConstraint(name: str, constraint_type: KinematicConstraintType, frame_a: str, frame_b: str | None = None, face_a: str | None = None, face_b: str | None = None, axis_a: str | Tuple[float, float, float] = 'z', axis_b: str | Tuple[float, float, float] = 'z', reference_center: Tuple[float, float, float] | None = None, reference_radius: float | None = None, reference_axis: Tuple[float, float, float] | None = None, reference_normal: Tuple[float, float, float] | None = None, pattern_count: int = 0, pattern_radius: float = 0.0, pattern_offset_deg: float = 0.0, tolerance_mm: float = 0.1, tolerance_deg: float = 1.0, bounds_a: Tuple[Tuple[float, float, float], Tuple[float, float, float]] | None = None, bounds_b: Tuple[Tuple[float, float, float], Tuple[float, float, float]] | None = None, stl_path_a: str | None = None, stl_path_b: str | None = None, description: str = '', severity: str = 'error', validator: Callable[[Dict[str, Any]], ConstraintEvaluationResult] | None = None)[source]

Bases: object

A constraint that operates on world transforms from kinematic chains.

This constraint type is designed to work with Transform objects (4x4 matrices) from the kinematic chain system, enabling validation of assembly relationships in world coordinates.

Variables:
  • name (str) – Unique identifier for this constraint

  • constraint_type (yapcad.assembly.kinematic_integration.KinematicConstraintType) – Type of constraint to evaluate

  • frame_a (str) – Name of first frame (part name in kinematic chain)

  • frame_b (str | None) – Name of second frame, or None for world reference

  • specification (# Axis)

  • face_a (str | None) –

    Face specification on frame_a. Can be: - “TOP” / “BOTTOM”: Calculated from bounds_a (max_z / min_z face center) - “FRONT” / “BACK”: Calculated from bounds_a (max_y / min_y face center) - “LEFT” / “RIGHT”: Calculated from bounds_a (min_x / max_x face center) - Frame name (e.g., “OUTPUT_SHAFT”, “SUN_INPUT”): Looked up from

    world_transforms using key “part_name.frame_name”

    When specified, constraint position is taken from face instead of part origin.

  • face_b (str | None) – Face specification on frame_b (same options as face_a).

  • specification

  • axis_a (str | Tuple[float, float, float]) – Local axis on frame_a to use (“x”, “y”, “z”, or tuple)

  • axis_b (str | Tuple[float, float, float]) – Local axis on frame_b to use (“x”, “y”, “z”, or tuple)

  • constraints (# Reference geometry for world-referenced)

  • reference_center (Tuple[float, float, float] | None) – Center point for tangent/radial constraints

  • reference_radius (float | None) – Radius for tangent/radial constraints

  • reference_axis (Tuple[float, float, float] | None) – Reference axis direction for parallel/perpendicular

  • reference_normal (Tuple[float, float, float] | None) – Reference normal for plane constraints

  • parameters (# Pattern)

  • pattern_count (int) – Number of holes in bolt pattern

  • pattern_radius (float) – Bolt circle radius

  • pattern_offset_deg (float) – Angular offset between patterns

  • Tolerances (#)

  • tolerance_mm (float) – Linear tolerance in mm (default: 0.1)

  • tolerance_deg (float) – Angular tolerance in degrees (default: 1.0)

  • Metadata (#)

  • description (str) – Human-readable description

  • severity (str) – “error”, “warning”, or “info”

  • validator (Callable[[Dict[str, Any]], yapcad.assembly.kinematic_integration.ConstraintEvaluationResult] | None)

  • validator – Optional custom validation function

Example with face-based constraints:

# Clearance measured from servo OUTPUT_SHAFT face to ring SUN_INPUT face
constraint = KinematicConstraint(
    name="servo_ring_interface",
    constraint_type=KinematicConstraintType.AT_DISTANCE,
    frame_a="AXIS2_SERVO_XH430",
    frame_b="AXIS2_RING_HOUSING",
    face_a="OUTPUT_SHAFT",    # Use servo's output shaft frame position
    face_b="SUN_INPUT",       # Use ring's sun input frame position
    reference_radius=0.0,     # Expected distance (touching)
    tolerance_mm=0.5
)

# Contact constraint using standard face names
constraint = KinematicConstraint(
    name="stacked_parts",
    constraint_type=KinematicConstraintType.COINCIDENT,
    frame_a="GEARBOX_TOP",
    frame_b="GEARBOX_BOTTOM",
    face_a="BOTTOM",          # Bottom face of top gearbox (from bounds_a)
    face_b="TOP",             # Top face of bottom gearbox (from bounds_b)
    bounds_a=((-30, -30, 0), (30, 30, 50)),
    bounds_b=((-30, -30, 0), (30, 30, 50)),
    tolerance_mm=0.1
)
axis_a: str | Tuple[float, float, float] = 'z'
axis_b: str | Tuple[float, float, float] = 'z'
bounds_a: Tuple[Tuple[float, float, float], Tuple[float, float, float]] | None = None
bounds_b: Tuple[Tuple[float, float, float], Tuple[float, float, float]] | None = None
constraint_type: KinematicConstraintType
description: str = ''
evaluate(world_transforms: Dict[str, Any]) ConstraintEvaluationResult[source]

Evaluate this constraint given world transforms of all parts.

Parameters:

world_transforms – Dictionary mapping part names to Transform objects or 4x4 numpy arrays

Returns:

ConstraintEvaluationResult with evaluation status and error metrics

face_a: str | None = None
face_b: str | None = None
frame_a: str
frame_b: str | None = None
name: str
pattern_count: int = 0
pattern_offset_deg: float = 0.0
pattern_radius: float = 0.0
reference_axis: Tuple[float, float, float] | None = None
reference_center: Tuple[float, float, float] | None = None
reference_normal: Tuple[float, float, float] | None = None
reference_radius: float | None = None
severity: str = 'error'
stl_path_a: str | None = None
stl_path_b: str | None = None
tolerance_deg: float = 1.0
tolerance_mm: float = 0.1
validator: Callable[[Dict[str, Any]], ConstraintEvaluationResult] | None = None
class yapcad.assembly.kinematic_integration.KinematicConstraintType(*values)[source]

Bases: Enum

Constraint types for kinematic chain validation.

These constraints operate on world-space transforms from kinematic chains and validate geometric relationships between frames.

AT_DISTANCE = 'at_distance'
BOLT_PATTERN = 'bolt_pattern'
COINCIDENT = 'coincident'
CONCENTRIC = 'concentric'
CUSTOM = 'custom'
MIN_DISTANCE = 'min_distance'
NO_OVERLAP = 'no_overlap'
PARALLEL = 'parallel'
PERPENDICULAR = 'perpendicular'
TANGENT = 'tangent'
Z_STACK_CLEARANCE = 'z_stack_clearance'
class yapcad.assembly.kinematic_integration.MeshCollisionResult(collides: bool, penetration_depth: float = 0.0, collision_volume: float = 0.0, contact_points: Tuple[float, float, float]]=<factory>, error_message: str = '')[source]

Bases: object

Result of mesh-based collision detection between two parts.

Variables:
  • collides (bool) – True if the meshes intersect

  • penetration_depth (float) – Estimated penetration depth in mm (0 if no collision)

  • collision_volume (float) – Volume of the intersection region in mm^3 (0 if no collision)

  • contact_points (List[Tuple[float, float, float]]) – List of approximate contact/intersection points

  • error_message (str) – Error message if collision check failed

collides: bool
collision_volume: float = 0.0
contact_points: List[Tuple[float, float, float]]
error_message: str = ''
penetration_depth: float = 0.0
class yapcad.assembly.kinematic_integration.ValidationReport(is_valid: bool, total_constraints: int = 0, passed_count: int = 0, failed_count: int = 0, warning_count: int = 0, constraint_results: Dict[str, ~yapcad.assembly.kinematic_integration.ConstraintEvaluationResult]=<factory>, failed_constraints: List[str] = <factory>, warnings: List[str] = <factory>, info: List[str] = <factory>)[source]

Bases: object

Comprehensive assembly validation report.

Variables:
  • is_valid (bool) – True if all error-severity constraints pass

  • total_constraints (int) – Total number of constraints evaluated

  • passed_count (int) – Number of constraints that passed

  • failed_count (int) – Number of constraints that failed

  • warning_count (int) – Number of warning-severity constraints that failed

  • constraint_results (Dict[str, yapcad.assembly.kinematic_integration.ConstraintEvaluationResult]) – Mapping of constraint name to evaluation result

  • failed_constraints (List[str]) – List of names of failed constraints

  • warnings (List[str]) – List of warning messages

  • info (List[str]) – List of informational messages

constraint_results: Dict[str, ConstraintEvaluationResult]
detailed_report() str[source]

Generate detailed multi-line report.

failed_constraints: List[str]
failed_count: int = 0
info: List[str]
is_valid: bool
passed_count: int = 0
total_constraints: int = 0
warning_count: int = 0
warnings: List[str]
yapcad.assembly.kinematic_integration.check_mesh_collision(stl_path_a: str, transform_a: Any, stl_path_b: str, transform_b: Any) MeshCollisionResult[source]

Check for collision between two STL meshes with applied transforms.

This function loads two STL files, applies 4x4 transformation matrices, and checks if the resulting meshes intersect. It provides detailed collision information including penetration depth and intersection volume.

Parameters:
  • stl_path_a – Path to the first STL file

  • transform_a – 4x4 transformation matrix (numpy array or Transform object)

  • stl_path_b – Path to the second STL file

  • transform_b – 4x4 transformation matrix (numpy array or Transform object)

Returns:

MeshCollisionResult with collision details

Example

>>> from yapcad.assembly.kinematic_integration import check_mesh_collision
>>> import numpy as np
>>>
>>> # Identity transform (no transformation)
>>> identity = np.eye(4)
>>>
>>> # Translation by 100mm in X
>>> translated = np.eye(4)
>>> translated[0, 3] = 100.0
>>>
>>> result = check_mesh_collision(
...     "part_a.stl", identity,
...     "part_b.stl", translated
... )
>>> if result.collides:
...     print(f"Collision! Penetration: {result.penetration_depth}mm")
yapcad.assembly.kinematic_integration.create_coincident_constraint(name: str, frame_a: str, frame_b: str | None = None, reference_point: Tuple[float, float, float] | None = None, tolerance_mm: float = 0.1, description: str = '') KinematicConstraint[source]

Create a coincident constraint (origins at same location).

Parameters:
  • name – Constraint name

  • frame_a – First part name

  • frame_b – Second part name (or None for world reference)

  • reference_point – World reference point (if frame_b is None)

  • tolerance_mm – Distance tolerance in mm

  • description – Description of constraint purpose

Returns:

Configured KinematicConstraint

yapcad.assembly.kinematic_integration.create_face_coincident_constraint(name: str, frame_a: str, face_a: str, frame_b: str, face_b: str, tolerance_mm: float = 0.1, bounds_a: Tuple[Tuple[float, float, float], Tuple[float, float, float]] | None = None, bounds_b: Tuple[Tuple[float, float, float], Tuple[float, float, float]] | None = None, description: str = '') KinematicConstraint[source]

Create a face-to-face coincident constraint.

Validates that two faces are at the same position (touching/mating). This is a convenience wrapper around create_face_distance_constraint with expected_distance=0.

Parameters:
  • name – Constraint name

  • frame_a – First part name

  • face_a – Face specification on frame_a

  • frame_b – Second part name

  • face_b – Face specification on frame_b

  • tolerance_mm – Allowed position deviation

  • bounds_a – Bounding box of part A (required for standard face names)

  • bounds_b – Bounding box of part B (required for standard face names)

  • description – Description of constraint purpose

Returns:

Configured KinematicConstraint

Example

>>> # Servo output shaft should coincide with ring gear input
>>> constraint = create_face_coincident_constraint(
...     name="servo_ring_mate",
...     frame_a="AXIS2_SERVO",
...     face_a="OUTPUT_SHAFT",
...     frame_b="AXIS2_RING",
...     face_b="SUN_INPUT"
... )
yapcad.assembly.kinematic_integration.create_face_distance_constraint(name: str, frame_a: str, face_a: str, frame_b: str, face_b: str, expected_distance: float = 0.0, tolerance_mm: float = 0.5, bounds_a: Tuple[Tuple[float, float, float], Tuple[float, float, float]] | None = None, bounds_b: Tuple[Tuple[float, float, float], Tuple[float, float, float]] | None = None, description: str = '') KinematicConstraint[source]

Create a face-to-face distance constraint.

Measures distance between specific faces on two parts, rather than part origins. Useful for validating mating interfaces like servo output shafts to gearbox inputs.

Face specifications can be: - Standard faces: “TOP”, “BOTTOM”, “FRONT”, “BACK”, “LEFT”, “RIGHT”

(requires bounds_a/bounds_b to calculate face center positions)

  • Named frames: e.g., “OUTPUT_SHAFT”, “SUN_INPUT” (looked up from world_transforms using “part_name.frame_name”)

Parameters:
  • name – Constraint name

  • frame_a – First part name (e.g., “AXIS2_SERVO_XH430”)

  • face_a – Face specification on frame_a (e.g., “OUTPUT_SHAFT” or “TOP”)

  • frame_b – Second part name (e.g., “AXIS2_RING_HOUSING”)

  • face_b – Face specification on frame_b (e.g., “SUN_INPUT” or “BOTTOM”)

  • expected_distance – Expected distance between faces in mm (0.0 = touching)

  • tolerance_mm – Allowed deviation from expected distance

  • bounds_a – Bounding box of part A (required for standard face names)

  • bounds_b – Bounding box of part B (required for standard face names)

  • description – Description of constraint purpose

Returns:

Configured KinematicConstraint

Example

>>> # Servo output shaft should mate with ring gear input
>>> constraint = create_face_distance_constraint(
...     name="servo_ring_interface",
...     frame_a="AXIS2_SERVO_XH430",
...     face_a="OUTPUT_SHAFT",
...     frame_b="AXIS2_RING_HOUSING",
...     face_b="SUN_INPUT",
...     expected_distance=0.0,  # Should be touching
...     tolerance_mm=0.5
... )
>>>
>>> # Top face of lower part should meet bottom face of upper part
>>> constraint = create_face_distance_constraint(
...     name="stacked_contact",
...     frame_a="LOWER_PART",
...     face_a="TOP",
...     frame_b="UPPER_PART",
...     face_b="BOTTOM",
...     expected_distance=0.0,
...     bounds_a=((-25, -25, 0), (25, 25, 30)),
...     bounds_b=((-25, -25, 0), (25, 25, 40))
... )
yapcad.assembly.kinematic_integration.create_mesh_collision_constraint(name: str, frame_a: str, frame_b: str, stl_path_a: str, stl_path_b: str, description: str = '') KinematicConstraint[source]

Create a mesh-based collision constraint.

Uses trimesh library for accurate collision detection with actual part geometry. This is more accurate than AABB-based detection but requires the trimesh library and STL files for both parts.

Parameters:
  • name – Constraint name

  • frame_a – First part name (must match kinematic chain frame name)

  • frame_b – Second part name (must match kinematic chain frame name)

  • stl_path_a – Path to STL file for part A

  • stl_path_b – Path to STL file for part B

  • description – Description of constraint purpose

Returns:

Configured KinematicConstraint for mesh collision detection

Example

>>> from yapcad.assembly.kinematic_integration import (
...     create_mesh_collision_constraint, AssemblyValidator
... )
>>>
>>> constraint = create_mesh_collision_constraint(
...     "gearbox_servo_collision",
...     "AXIS1_RING_HOUSING",
...     "AXIS1_SERVO_XH430",
...     "/path/to/ring_housing.stl",
...     "/path/to/servo.stl",
...     description="Check servo doesn't penetrate ring housing"
... )
>>>
>>> validator = AssemblyValidator()
>>> validator.add_constraint(constraint)
>>> report = validator.validate(world_transforms)
>>> if not report.is_valid:
...     print(report.detailed_report())
yapcad.assembly.kinematic_integration.create_min_distance_constraint(name: str, frame_a: str, frame_b: str, min_distance: float, bounds_a: Tuple[Tuple[float, float, float], Tuple[float, float, float]] | None = None, bounds_b: Tuple[Tuple[float, float, float], Tuple[float, float, float]] | None = None, description: str = '') KinematicConstraint[source]

Create a minimum distance constraint.

Parts must be at least min_distance mm apart. If bounding boxes are provided, uses them for more accurate separation calculation.

Parameters:
  • name – Constraint name

  • frame_a – First part name

  • frame_b – Second part name

  • min_distance – Minimum required separation in mm

  • bounds_a – Optional bounding box of part A

  • bounds_b – Optional bounding box of part B

  • description – Description of constraint purpose

Returns:

Configured KinematicConstraint

yapcad.assembly.kinematic_integration.create_no_overlap_constraint(name: str, frame_a: str, frame_b: str, bounds_a: Tuple[Tuple[float, float, float], Tuple[float, float, float]] | None = None, bounds_b: Tuple[Tuple[float, float, float], Tuple[float, float, float]] | None = None, stl_path_a: str | None = None, stl_path_b: str | None = None, description: str = '') KinematicConstraint[source]

Create a no-overlap constraint.

When STL paths are provided, uses accurate mesh-based collision detection. Otherwise uses axis-aligned bounding boxes (fast but conservative).

Parameters:
  • name – Constraint name

  • frame_a – First part name

  • frame_b – Second part name

  • bounds_a – Bounding box of part A ((min_x, min_y, min_z), (max_x, max_y, max_z))

  • bounds_b – Bounding box of part B

  • stl_path_a – Path to STL file for part A (enables mesh collision)

  • stl_path_b – Path to STL file for part B (enables mesh collision)

  • description – Description of constraint purpose

Returns:

Configured KinematicConstraint

Example

>>> # AABB-based collision (fast, conservative)
>>> constraint = create_no_overlap_constraint(
...     "servo_ring_no_overlap",
...     "SERVO",
...     "RING_HOUSING",
...     bounds_a=((-15, -25, -17), (15, 25, 17)),
...     bounds_b=((-30, -30, 0), (30, 30, 8))
... )
>>>
>>> # Mesh-based collision (accurate, requires trimesh)
>>> constraint = create_no_overlap_constraint(
...     "servo_ring_no_overlap",
...     "SERVO",
...     "RING_HOUSING",
...     stl_path_a="parts/servo.stl",
...     stl_path_b="parts/ring_housing.stl"
... )
yapcad.assembly.kinematic_integration.create_parallel_constraint(name: str, frame_a: str, frame_b: str | None = None, axis_a: str = 'z', axis_b: str = 'z', reference_axis: Tuple[float, float, float] | None = None, tolerance_deg: float = 1.0, description: str = '') KinematicConstraint[source]

Create a parallel constraint between two axes.

Parameters:
  • name – Constraint name

  • frame_a – First part name

  • frame_b – Second part name (or None for world reference)

  • axis_a – Local axis on frame_a

  • axis_b – Local axis on frame_b

  • reference_axis – World reference axis (if frame_b is None)

  • tolerance_deg – Angular tolerance

  • description – Description of constraint purpose

Returns:

Configured KinematicConstraint

yapcad.assembly.kinematic_integration.create_tangent_constraint(name: str, frame: str, center: Tuple[float, float, float], axis: str = 'y', tolerance_deg: float = 2.0, description: str = '') KinematicConstraint[source]

Create a tangent constraint for wheel/motor assemblies.

Parameters:
  • name – Constraint name

  • frame – Part name in kinematic chain

  • center – Center point for tangent calculation

  • axis – Which local axis to check (“x”, “y”, or “z”)

  • tolerance_deg – Angular tolerance

  • description – Description of constraint purpose

Returns:

Configured KinematicConstraint

yapcad.assembly.kinematic_integration.create_z_stack_clearance_constraint(name: str, frame_a: str, frame_b: str, bounds_a: Tuple[Tuple[float, float, float], Tuple[float, float, float]], bounds_b: Tuple[Tuple[float, float, float], Tuple[float, float, float]], min_clearance: float = 0.0, description: str = '') KinematicConstraint[source]

Create a Z-stack clearance constraint for stacked parts.

Validates that vertically stacked parts (like servo + gearbox) have proper Z-axis separation to avoid collision. Useful for detecting servo bodies penetrating ring housings.

Parameters:
  • name – Constraint name

  • frame_a – First part name (e.g., “AXIS2_SERVO_XH430”)

  • frame_b – Second part name (e.g., “AXIS2_RING_HOUSING”)

  • bounds_a – Bounding box of part A ((min_x, min_y, min_z), (max_x, max_y, max_z))

  • bounds_b – Bounding box of part B

  • min_clearance – Minimum required clearance in mm (default: 0 = touching OK)

  • description – Description of constraint purpose

Returns:

Configured KinematicConstraint

Example

>>> # XH430 servo: 28.5 x 46.5 x 34mm, origin at body center
>>> servo_bounds = ((-14.25, -23.25, -17), (14.25, 23.25, 17))
>>> # Ring housing: ~60mm diameter x 8mm height
>>> ring_bounds = ((-30, -30, 0), (30, 30, 8))
>>> constraint = create_z_stack_clearance_constraint(
...     "axis2_servo_ring_clearance",
...     "AXIS2_SERVO_XH430",
...     "AXIS2_RING_HOUSING",
...     servo_bounds,
...     ring_bounds,
...     min_clearance=1.0
... )
yapcad.assembly.kinematic_integration.validate_assembly(constraints: List[KinematicConstraint], world_transforms: Dict[str, Any]) ValidationReport[source]

Convenience function to validate constraints against transforms.

Parameters:
  • constraints – List of KinematicConstraint objects

  • world_transforms – Dictionary mapping part names to transforms

Returns:

ValidationReport with evaluation results

yapcad.assembly.load_case module

LoadCase: structural FEA load case attached to an Assembly.

A LoadCase carries the information an FEA setup needs to know what force is applied, where, in which direction, and against which interface — without the FEA author having to invent or hand-extract that data from side-files.

Designed to round-trip through Mechatron’s LoadCase graph entity (see crates/assembly-graph/src/types.rs). The yapCAD dataclass lives on the Assembly instance under assembly.load_cases.

History:

Created 2026-05-20 in response to the Agentic-1 chute-deploy FEA iterations (v4→v7) where load case data was scattered across:

  • designs/assembly-dashboard-v2/index.html LOAD_CASES (hardcoded)

  • requirements/LOAD-CASES-DRAFT.md (draft notes)

  • specs/load-cases/canonical.json (jarvis-rich’s hand extract)

The lack of a single source of truth caused several iteration cycles to use wrong attach points / clock angles / part assignments. See Whiteboard message 20260520201704-46e1d134 for context.

class yapcad.assembly.load_case.BoltPattern(pcd_mm: float, n_bolts: int, bolt_spec: str, access_direction: Sequence[float])[source]

Bases: object

A bolt circle attached to an Assembly interface.

Round-trips through Mechatron’s BoltPattern type (see crates/assembly-graph/src/types.rs).

Fields match the mechatron schema; bolt_spec is a canonical string like “1-4-20-x-1in-button-head-shcs” that can be resolved against the cots-db MCP for stiffness, weight, COTS line item, etc.

access_direction: Sequence[float]
bolt_spec: str
classmethod from_dict(data: Dict[str, Any]) BoltPattern[source]
n_bolts: int
pcd_mm: float
to_dict() Dict[str, Any][source]
class yapcad.assembly.load_case.LoadAttach(part: str, position: Sequence[float] = <factory>, interface: str | None = None, clock_deg: float | None = None, bolt_index: int | None = None)[source]

Bases: object

Where a LoadCase applies its force/moment on the assembly.

Resolution priority for FEA setup:
  1. If interface is set, the load enters via that mechatron Interface (typically a bolted joint). The FEA solver looks up the interface’s bolt_pattern and applies the load at the bolt nearest clock_deg (or at the specific bolt_index if given).

  2. Otherwise, if part is set and != “_assembly”, the load is applied at position (interpreted in the load case’s coordinate_frame) on that part’s surface DOFs. Useful for distributed nose-tip loads, engine thrust at engine-mount, etc.

  3. If part == “_assembly”, the load is a whole-stack body force / inertial load. position is a notional centroid for visualization only; FEA applies the equivalent ρ·a body force on all parts.

bolt_index: int | None = None
clock_deg: float | None = None
interface: str | None = None
part: str
position: Sequence[float]
to_dict() Dict[str, Any][source]
class yapcad.assembly.load_case.LoadCase(id: str, name: str, group: str, direction: Sequence[float], attach: LoadAttach, magnitude_n: float | None = None, magnitude_g: float | None = None, coordinate_frame: str = 'vehicle', description: str = '', source: str = '', status: str = 'estimated', warn: bool = False)[source]

Bases: object

A structural load case applied to an Assembly.

Round-trips through Mechatron’s LoadCase type.

attach: LoadAttach
coordinate_frame: str = 'vehicle'
description: str = ''
direction: Sequence[float]
classmethod from_dict(data: Dict[str, Any]) LoadCase[source]

Build from a dict (e.g. graph.json round-trip).

group: str
id: str
magnitude_g: float | None = None
magnitude_n: float | None = None
name: str
source: str = ''
status: str = 'estimated'
to_dict() Dict[str, Any][source]
warn: bool = False

yapcad.assembly.mate module

Assembly mate/constraint system for yapCAD.

This module provides kinematic constraints for defining relationships between components in an assembly. Mates define both static positioning and allowed motion, supporting mechanical design, animation export, and physics simulation.

Based on industry standards from SolidWorks, Fusion 360, CATIA, Siemens NX, and PTC Creo, adapted for yapCAD’s programmatic workflow.

Key concepts:
  • Mate: Geometric relationship that constrains degrees of freedom (DOF)

  • DOF Removal: Each mate removes one or more of 6 DOF (3 trans + 3 rot)

  • Limits: Min/max position or angle constraints for joints

  • Dynamics: Friction, damping, stiffness for motion simulation

  • Coupling: Gear ratios and other motion relationships

Example usage:

from yapcad.assembly.mate import Mate, MateType, MateLimits from yapcad.geom import point, vect

# Define a revolute joint (hinge) for a robot arm shoulder = Mate(

name=”shoulder_pitch”, mate_type=MateType.REVOLUTE, part_a=”base”, datum_a=”shoulder_axis”, part_b=”upper_arm”, datum_b=”arm_root_axis”, offset=0.0, angle=0.0, limits=MateLimits(

min_value=-1.57, # -90 degrees max_value=1.57, # +90 degrees max_velocity=2.0 # rad/s

)

)

# Check degrees of freedom dof = shoulder.degrees_of_freedom() # Returns 1 (rotation only)

# Evaluate constraint satisfaction result = shoulder.evaluate() print(f”DOF remaining: {result[‘dof_remaining’]}”) print(f”Constraint error: {result[‘error’]}”)

For animation and simulation export, see the CAD_MATE_SYSTEMS_RESEARCH.md document for URDF, SDF, and Blender armature generation strategies.

class yapcad.assembly.mate.CoincidentResult(satisfied: bool, error_distance: float = 0.0, error_angle: float = 0.0, error_message: str = '', details: Dict[str, ~typing.Any]=<factory>)[source]

Bases: object

Result of evaluating a COINCIDENT constraint.

Variables:
  • satisfied (bool) – True if constraint is satisfied within tolerance

  • error_distance (float) – Distance error in mm (for point/origin alignment)

  • error_angle (float) – Angular error in degrees (for plane/axis alignment)

  • error_message (str) – Human-readable description of the result

  • details (Dict[str, Any]) – Additional information about the evaluation

details: Dict[str, Any]
error_angle: float = 0.0
error_distance: float = 0.0
error_message: str = ''
satisfied: bool
class yapcad.assembly.mate.Mate(name: str, mate_type: MateType, part_a: str, datum_a: str, part_b: str, datum_b: str, offset: float = 0.0, angle: float = 0.0, axis: List[float] = <factory>, secondary_axis: List[float] = <factory>, limits: MateLimits | None = None, dynamics: MateDynamics | None = None, coupling_ratio: float = 1.0, coupling_offset: float = 0.0, coupling_reverse: bool = False, coupling_pitch: float | None = None, metadata: Dict[str, ~typing.Any]=<factory>)[source]

Bases: object

Kinematic constraint defining relationship between two assembly components.

A mate constrains the relative position and/or orientation of two parts by removing degrees of freedom (DOF). Mates can be purely geometric (COINCIDENT, PARALLEL) or define motion primitives (REVOLUTE, PRISMATIC).

The mate references two parts via named datum features (points, axes, planes, or surfaces). The constraint solver uses these datums to compute the relative transformation that satisfies the mate.

Variables:
  • name (str) – Human-readable identifier (e.g., “shoulder_pitch”, “wheel_axle”)

  • mate_type (yapcad.assembly.mate.MateType) – Type of constraint from MateType enum

  • part_a (str) – Identifier of first (parent/base) component

  • datum_a (str) – Named datum feature on part_a (e.g., “mounting_axis”)

  • part_b (str) – Identifier of second (child/moving) component

  • datum_b (str) – Named datum feature on part_b (e.g., “joint_axis”)

  • offset (float) – Distance offset between datums (mm, used by DISTANCE mate)

  • angle (float) – Angular offset between datums (radians, used by ANGLE mate)

  • axis (List[float]) – Primary motion axis for joints (e.g., [0,0,1,0] for Z-axis)

  • secondary_axis (List[float]) – Secondary reference axis for compound joints

  • limits (yapcad.assembly.mate.MateLimits | None) – Optional position/velocity/effort limits

  • dynamics (yapcad.assembly.mate.MateDynamics | None) – Optional friction/damping/stiffness parameters

  • coupling_ratio (float) – Motion ratio for coupled mates (GEAR, SCREW)

  • coupling_offset (float) – Phase offset for coupled motion (radians or mm)

  • coupling_reverse (bool) – Reverse direction of coupled motion

  • coupling_pitch (float | None) – Thread pitch for SCREW mates (mm per revolution)

  • metadata (Dict[str, Any]) – Additional application-specific data

Example

# Revolute joint for robot shoulder shoulder_mate = Mate(

name=”shoulder_pitch”, mate_type=MateType.REVOLUTE, part_a=”robot_base”, datum_a=”shoulder_mount_axis”, part_b=”upper_arm”, datum_b=”arm_root_axis”, offset=0.0, angle=0.0, axis=[0, 1, 0, 0], # Y-axis rotation limits=MateLimits(

min_value=-math.pi/2, max_value=math.pi/2, max_velocity=1.5, max_effort=100.0

), dynamics=MateDynamics(

friction_static=0.05, damping=0.2

)

)

# Gear coupling between two shafts gear_mate = Mate(

name=”gear_1_to_2”, mate_type=MateType.GEAR, part_a=”gear_1”, datum_a=”gear_1_axis”, part_b=”gear_2”, datum_b=”gear_2_axis”, coupling_ratio=2.5, # gear_2 rotates 2.5x for each rotation of gear_1 coupling_reverse=True # Opposite rotation direction

)

# Check constraint properties dof = shoulder_mate.degrees_of_freedom() # Returns 1 result = shoulder_mate.evaluate()

angle: float = 0.0
axis: List[float]
compute_coupled_motion(driver_position: float) float[source]

Compute driven position from driver position for coupled mates.

For coupled mates (GEAR, SCREW, RACK_PINION), compute the position of the driven component given the position of the driving component.

Parameters:

driver_position – Position of driving component (mm or radians)

Returns:

Position of driven component (mm or radians)

Raises:

ValueError – If mate is not a coupled type

Example

# Gear mate with 3:1 ratio gear_mate = Mate(mate_type=MateType.GEAR, coupling_ratio=3.0, …) driven_angle = gear_mate.compute_coupled_motion(1.0) # Returns 3.0

# Screw mate with 2mm pitch screw_mate = Mate(mate_type=MateType.SCREW, coupling_pitch=2.0, …) linear_pos = screw_mate.compute_coupled_motion(math.pi) # Returns 2.0

coupling_offset: float = 0.0
coupling_pitch: float | None = None
coupling_ratio: float = 1.0
coupling_reverse: bool = False
property datum1: str

Alias for datum_a (datum on first component).

property datum2: str

Alias for datum_b (datum on second component).

datum_a: str
datum_b: str
degrees_of_freedom() int[source]

Return the number of degrees of freedom (DOF) remaining after this mate.

An unconstrained rigid body has 6 DOF (3 translational + 3 rotational). Each mate removes one or more DOF. This method returns how many DOF remain after applying this mate constraint.

Returns:

Number of remaining DOF (0-6)

DOF by mate type:

RIGID: 0 DOF (fully constrained) REVOLUTE, PRISMATIC, SCREW: 1 DOF CYLINDRICAL, PIN_SLOT, UNIVERSAL: 2 DOF SPHERICAL, PLANAR: 3 DOF COINCIDENT: depends on geometry (1-3 DOF removed) CONCENTRIC: 2 DOF (translation along + rotation about axis) DISTANCE, ANGLE, PARALLEL, PERPENDICULAR, TANGENT: varies GEAR, RACK_PINION, CAM: coupled (creates relationship, not DOF)

Example

shoulder = Mate(name=”shoulder”, mate_type=MateType.REVOLUTE, …) dof = shoulder.degrees_of_freedom() # Returns 1

dynamics: MateDynamics | None = None
evaluate(current_position: float | None = None, current_velocity: float | None = None, current_effort: float | None = None) Dict[str, Any][source]

Evaluate constraint satisfaction and compute error metrics.

This method checks if the mate is satisfied given current state, computes constraint violation errors, and validates limits.

Parameters:
  • current_position – Current position (mm) or angle (radians) of primary DOF

  • current_velocity – Current velocity (mm/s or rad/s)

  • current_effort – Current force (N) or torque (N*m)

Returns:

  • dof_remaining: Number of DOF after constraint (int)

  • error: Constraint violation error magnitude (float)

  • satisfied: True if constraint is satisfied within tolerance (bool)

  • limit_violations: List of violated limits (List[str])

  • dynamics_active: True if dynamics parameters are defined (bool)

Return type:

Dictionary containing

Example

result = mate.evaluate(current_position=0.5, current_velocity=1.2) if not result[‘satisfied’]:

print(f”Constraint error: {result[‘error’]}”)

if result[‘limit_violations’]:

print(f”Limit violations: {result[‘limit_violations’]}”)

classmethod from_dict(data: Dict[str, Any]) Mate[source]

Deserialize mate from dictionary.

Parameters:

data – Dictionary representation of mate

Returns:

Mate instance

Example

import json mate_dict = json.load(f) mate = Mate.from_dict(mate_dict)

is_coupled() bool[source]

Return True if this mate defines a coupled motion relationship.

Coupled mates (GEAR, RACK_PINION, SCREW, CAM) create motion relationships between components rather than removing DOF.

Returns:

True if mate is a coupled motion type

Example

if mate.is_coupled():

print(f”Coupling ratio: {mate.coupling_ratio}”)

limits: MateLimits | None = None
mate_type: MateType
metadata: Dict[str, Any]
name: str
offset: float = 0.0
property part1: str

Alias for part_a (first/parent component).

property part2: str

Alias for part_b (second/child component).

part_a: str
part_b: str
secondary_axis: List[float]
to_dict() Dict[str, Any][source]

Serialize mate to dictionary for export to JSON, YAML, or other formats.

Returns:

Dictionary representation of mate with all parameters

Example

mate_dict = mate.to_dict() import json json.dump(mate_dict, f, indent=2)

validate(datum_a: Datum, datum_b: Datum) List[str][source]

Validate that this mate is compatible with the given datums.

Checks that the mate type is appropriate for the datum types. For example, CONCENTRIC requires two AXIS or CIRCLE datums, COINCIDENT can work with POINT, PLANE, or CIRCLE datums.

Parameters:
  • datum_a – First datum feature

  • datum_b – Second datum feature

Returns:

List of validation error messages (empty if valid)

Example

>>> mate = Mate("test", MateType.CONCENTRIC, ...)
>>> issues = mate.validate(axis_datum, point_datum)
>>> if issues:
...     print(f"Invalid: {issues}")
class yapcad.assembly.mate.MateDynamics(friction_static: float = 0.0, friction_kinetic: float = 0.0, friction_viscous: float = 0.0, damping: float = 0.0, stiffness: float = 0.0, rest_position: float = 0.0)[source]

Bases: object

Physical dynamics parameters for motion simulation.

Defines friction, damping, and compliance for realistic motion behavior. Used for physics simulation, animation, and digital twin applications.

Friction types:
  • Static (Coulomb): Resistance to start motion

  • Kinetic (dynamic): Resistance during motion

  • Viscous: Velocity-dependent resistance (linear with speed)

Typical values:
  • Lubricated metal bearings: friction_static=0.01-0.05

  • Dry metal: friction_static=0.15-0.30

  • Typical joint damping: 0.1-1.0

  • Stiffness: 0 for rigid, >0 for compliant/springy

Variables:
  • friction_static (float) – Static (Coulomb) friction coefficient

  • friction_kinetic (float) – Kinetic/dynamic friction coefficient

  • friction_viscous (float) – Viscous friction coefficient (velocity-dependent)

  • damping (float) – Viscous damping coefficient (energy dissipation)

  • stiffness (float) – Spring stiffness for compliant joints (N/m or N*m/rad)

  • rest_position (float) – Equilibrium position for spring (mm or radians)

Example

# Low-friction bearing with slight damping dynamics = MateDynamics(

friction_static=0.02, friction_kinetic=0.015, damping=0.5

)

# Compliant joint with spring return dynamics = MateDynamics(

stiffness=1000.0, # Spring constant rest_position=0.0, # Returns to center damping=50.0 # Energy dissipation

)

damping: float = 0.0
friction_kinetic: float = 0.0
friction_static: float = 0.0
friction_viscous: float = 0.0
rest_position: float = 0.0
stiffness: float = 0.0
class yapcad.assembly.mate.MateLimits(min_value: float | None = None, max_value: float | None = None, min_velocity: float | None = None, max_velocity: float | None = None, min_effort: float | None = None, max_effort: float | None = None, min_secondary: float | None = None, max_secondary: float | None = None, limit_stiffness: float = 1000000.0, limit_damping: float = 1000.0, restitution: float = 0.0)[source]

Bases: object

Position, velocity, and effort limits for mate degrees of freedom.

Used for both mechanical design (hard stops) and motion simulation (soft limits, compliance). All limits are optional; None means unlimited.

For revolute joints:
  • min_value/max_value in radians

  • max_velocity in rad/s

  • max_effort in N*m (torque)

For prismatic joints:
  • min_value/max_value in mm

  • max_velocity in mm/s

  • max_effort in N (force)

For multi-DOF joints (cylindrical, spherical, planar):
  • Use min_value/max_value for primary DOF

  • Use min_secondary/max_secondary for secondary DOF

Variables:
  • min_value (float | None) – Minimum position (mm) or angle (radians) for primary DOF

  • max_value (float | None) – Maximum position (mm) or angle (radians) for primary DOF

  • min_velocity (float | None) – Minimum velocity (can be negative for reversal)

  • max_velocity (float | None) – Maximum velocity (mm/s or rad/s)

  • min_effort (float | None) – Minimum force/torque (N or N*m)

  • max_effort (float | None) – Maximum force/torque (N or N*m)

  • min_secondary (float | None) – Minimum value for secondary DOF (multi-DOF joints)

  • max_secondary (float | None) – Maximum value for secondary DOF (multi-DOF joints)

  • limit_stiffness (float) – Stiffness of soft limit (N/m or N*m/rad), 1e6 = hard

  • limit_damping (float) – Damping at limit stop (N*s/m or N*m*s/rad)

  • restitution (float) – Bounce coefficient at limits (0.0 = no bounce, 1.0 = perfect)

Example

# Revolute joint with ±90 degree limits limits = MateLimits(

min_value=-math.pi/2, max_value=math.pi/2, max_velocity=2.0, # 2 rad/s max_effort=50.0 # 50 N*m torque limit

)

# Prismatic joint (linear slide) with hard stops limits = MateLimits(

min_value=0.0, max_value=100.0, # 100mm travel max_velocity=50.0, # 50 mm/s limit_stiffness=1e6 # Very stiff stop

)

limit_damping: float = 1000.0
limit_stiffness: float = 1000000.0
max_effort: float | None = None
max_secondary: float | None = None
max_value: float | None = None
max_velocity: float | None = None
min_effort: float | None = None
min_secondary: float | None = None
min_value: float | None = None
min_velocity: float | None = None
restitution: float = 0.0
class yapcad.assembly.mate.MateType(*values)[source]

Bases: Enum

Kinematic mate types based on industry CAD standards.

Each mate type removes specific degrees of freedom (DOF) from a rigid body. An unconstrained body has 6 DOF: 3 translational + 3 rotational.

Basic Mates (geometric constraints):

COINCIDENT: Points, edges, or faces share same location (1-3 DOF removed) CONCENTRIC: Cylindrical axes are colinear (4 DOF removed: 2t + 2r) PARALLEL: Directions remain parallel (2 DOF removed) PERPENDICULAR: Directions maintain 90-degree angle (1 DOF removed) TANGENT: Surfaces remain tangent (1 DOF removed) DISTANCE: Fixed offset between datums (1 DOF removed) ANGLE: Fixed angular relationship (1 DOF removed)

Standard Joints (motion primitives):

RIGID: No relative motion (6 DOF removed, 0 remaining) REVOLUTE: Rotation about single axis (5 DOF removed, 1 remaining) PRISMATIC: Translation along single axis (5 DOF removed, 1 remaining) CYLINDRICAL: Rotation + translation on same axis (4 DOF removed, 2 remaining) SPHERICAL: Ball-and-socket, rotation only (3 DOF removed, 3 remaining) PLANAR: Two translation + one rotation in plane (3 DOF removed, 3 remaining)

Compound Joints:

PIN_SLOT: Translation along slot + rotation about pin (4 DOF removed, 2 remaining) UNIVERSAL: Two perpendicular rotation axes (4 DOF removed, 2 remaining) SCREW: Coupled rotation and translation (5 DOF removed, 1 coupled)

Coupled Mates (motion relationships):

GEAR: Coupled rotation with ratio (no DOF removed, creates relationship) RACK_PINION: Couples rotation to translation (no DOF removed, creates relationship) CAM: Follower constrained to cam profile path (path-following) SLOT: Linear sliding constraint along trajectory (varies by slot type)

References

  • SolidWorks Mates Overview (2025)

  • Fusion 360 Joint Types

  • CATIA DMU Kinematics

  • Siemens NX Motion Joints

  • PTC Creo Mechanism Design

ANGLE = 'angle'
CAM = 'cam'
COINCIDENT = 'coincident'
CONCENTRIC = 'concentric'
CYLINDRICAL = 'cylindrical'
DISTANCE = 'distance'
GEAR = 'gear'
PARALLEL = 'parallel'
PERPENDICULAR = 'perpendicular'
PIN_SLOT = 'pin_slot'
PLANAR = 'planar'
PRISMATIC = 'prismatic'
RACK_PINION = 'rack_pinion'
REVOLUTE = 'revolute'
RIGID = 'rigid'
SCREW = 'screw'
SLOT = 'slot'
SPHERICAL = 'spherical'
TANGENT = 'tangent'
UNIVERSAL = 'universal'
yapcad.assembly.mate.check_bolt_circle_alignment(bolt_circle_a: Datum, bolt_circle_b: Datum, hole_count: int, tolerance_mm: float = 0.1, angular_offset_deg: float = 0.0) CoincidentResult[source]

Check if two bolt circles align for proper mating.

This validates that mounting hole patterns match for servo horn to gear hub interfaces, motor mounts, etc.

Parameters:
  • bolt_circle_a – First bolt circle datum (e.g., servo horn)

  • bolt_circle_b – Second bolt circle datum (e.g., sun gear hub)

  • hole_count – Number of holes in each pattern

  • tolerance_mm – Position tolerance for hole centers

  • angular_offset_deg – Expected angular offset between patterns (e.g., 45 degrees)

Returns:

CoincidentResult with alignment status

Example

>>> # XH540 servo horn (4 holes at 45 deg offset) to sun gear
>>> result = check_bolt_circle_alignment(
...     horn_bolt_circle,
...     sun_gear_bolt_circle,
...     hole_count=4,
...     angular_offset_deg=45.0
... )
yapcad.assembly.mate.create_coincident_mate(name: str, part_a: str, datum_a: str, part_b: str, datum_b: str, offset: float = 0.0) Mate[source]

Convenience function to create a COINCIDENT mate.

A COINCIDENT mate constrains two features to occupy the same location: - Points share the same position - Planes are coplanar - Bolt circles align (centers coincide)

Parameters:
  • name – Descriptive name (e.g., “horn_to_sun_gear”)

  • part_a – First component identifier

  • datum_a – Datum on first component

  • part_b – Second component identifier

  • datum_b – Datum on second component

  • offset – Optional offset distance along normal (default: 0)

Returns:

Mate configured as COINCIDENT constraint

Example

>>> # Align servo horn bolt holes with sun gear bolt holes
>>> mate = create_coincident_mate(
...     name="horn_bolt_alignment",
...     part_a="xh540_servo",
...     datum_a="horn_bolt_circle",
...     part_b="axis1_sun_gear",
...     datum_b="hub_bolt_circle"
... )
yapcad.assembly.mate.create_gear_mate(name: str, part_a: str, datum_a: str, part_b: str, datum_b: str, ratio: float, reverse: bool = False) Mate[source]

Convenience function to create a gear coupling mate.

Couples rotation of two components with a fixed ratio. The ratio is defined as: ratio = driven_rotations / driver_rotations

Parameters:
  • name – Descriptive name (e.g., “gear_1_to_2”, “pulley_coupling”)

  • part_a – Driver (input) component identifier

  • datum_a – Rotation axis on driver

  • part_b – Driven (output) component identifier

  • datum_b – Rotation axis on driven

  • ratio – Motion ratio (driven_rotations / driver_rotations)

  • reverse – If True, gears rotate in opposite directions

Returns:

Mate configured as gear coupling

Example

# 3:1 reduction gearbox (output rotates 1/3 speed of input) gearbox = create_gear_mate(

name=”motor_to_output”, part_a=”motor_shaft”, datum_a=”motor_axis”, part_b=”output_shaft”, datum_b=”output_axis”, ratio=1.0/3.0, reverse=False

)

yapcad.assembly.mate.create_prismatic_mate(name: str, part_a: str, datum_a: str, part_b: str, datum_b: str, axis: List[float] | None = None, min_position: float | None = None, max_position: float | None = None, max_velocity: float | None = None, max_force: float | None = None, friction: float = 0.0, damping: float = 0.0) Mate[source]

Convenience function to create a prismatic (slider) joint mate.

A prismatic joint allows translation along a single axis with no rotation. Common for linear actuators, pistons, and drawer slides.

Parameters:
  • name – Descriptive name (e.g., “piston_stroke”, “drawer_slide”)

  • part_a – Parent/base component identifier

  • datum_a – Axis datum on parent component

  • part_b – Child/moving component identifier

  • datum_b – Axis datum on child component

  • axis – Translation axis direction [x,y,z,w], defaults to [0,0,1,0] (Z-axis)

  • min_position – Minimum position in mm (None = unlimited)

  • max_position – Maximum position in mm (None = unlimited)

  • max_velocity – Maximum velocity in mm/s (None = unlimited)

  • max_force – Maximum force in N (None = unlimited)

  • friction – Static friction coefficient (0.0 = frictionless)

  • damping – Viscous damping coefficient (0.0 = no damping)

Returns:

Mate configured as prismatic joint

Example

# Linear actuator with 100mm stroke actuator = create_prismatic_mate(

name=”z_axis_slide”, part_a=”base”, datum_a=”rail_axis”, part_b=”carriage”, datum_b=”slider_axis”, min_position=0.0, max_position=100.0, max_velocity=50.0, friction=0.05, damping=5.0

)

yapcad.assembly.mate.create_revolute_mate(name: str, part_a: str, datum_a: str, part_b: str, datum_b: str, axis: List[float] | None = None, min_angle: float | None = None, max_angle: float | None = None, max_velocity: float | None = None, max_torque: float | None = None, friction: float = 0.0, damping: float = 0.0) Mate[source]

Convenience function to create a revolute (hinge) joint mate.

A revolute joint allows rotation about a single axis. This is the most common joint type for mechanisms, robots, and articulated assemblies.

Parameters:
  • name – Descriptive name (e.g., “shoulder_pitch”, “door_hinge”)

  • part_a – Parent/base component identifier

  • datum_a – Axis datum on parent component

  • part_b – Child/moving component identifier

  • datum_b – Axis datum on child component

  • axis – Rotation axis direction [x,y,z,w], defaults to [0,0,1,0] (Z-axis)

  • min_angle – Minimum rotation angle in radians (None = unlimited)

  • max_angle – Maximum rotation angle in radians (None = unlimited)

  • max_velocity – Maximum angular velocity in rad/s (None = unlimited)

  • max_torque – Maximum torque in N*m (None = unlimited)

  • friction – Static friction coefficient (0.0 = frictionless)

  • damping – Viscous damping coefficient (0.0 = no damping)

Returns:

Mate configured as revolute joint

Example

# Robot elbow with 180-degree range elbow = create_revolute_mate(

name=”elbow_flex”, part_a=”upper_arm”, datum_a=”elbow_axis”, part_b=”forearm”, datum_b=”forearm_root_axis”, min_angle=0.0, max_angle=math.pi, max_velocity=2.0, friction=0.02, damping=0.1

)

yapcad.assembly.mate.evaluate_coincident(datum_a: Datum, datum_b: Datum, tolerance_mm: float = 0.1, tolerance_deg: float = 1.0) CoincidentResult[source]

Evaluate a COINCIDENT constraint between two datums.

COINCIDENT means two geometric features occupy the same location/orientation: - Point-to-Point: Origins coincide - Point-to-Plane: Point lies on plane - Plane-to-Plane: Planes are coplanar (same origin + parallel normals) - Circle-to-Circle: Centers coincide (for bolt circle alignment) - Axis-to-Axis: Axes are colinear

Parameters:
  • datum_a – First datum in world coordinates

  • datum_b – Second datum in world coordinates

  • tolerance_mm – Linear tolerance in millimeters (default: 0.1mm)

  • tolerance_deg – Angular tolerance in degrees (default: 1.0 deg)

Returns:

CoincidentResult with evaluation status and error metrics

Example

>>> # Check if bolt holes align
>>> result = evaluate_coincident(
...     horn_bolt_circle,
...     sun_gear_bolt_circle,
...     tolerance_mm=0.05
... )
>>> if result.satisfied:
...     print("Bolt holes align!")

yapcad.assembly.mate_examples module

Example usage of the yapCAD assembly mate system.

This file demonstrates how to define kinematic constraints for mechanical assemblies using the mate module.

yapcad.assembly.mate_examples.example_gearbox()[source]

Define a 3-stage planetary gearbox with gear mates.

Each stage has a 3:1 reduction, resulting in 27:1 overall.

yapcad.assembly.mate_examples.example_linear_actuator()[source]

Define a linear actuator with prismatic joint and screw coupling.

yapcad.assembly.mate_examples.example_robot_arm()[source]

Define a simple 2-DOF robot arm with revolute joints.

The arm consists of: - Base (fixed) - Shoulder joint (revolute, ±90°) - Upper arm - Elbow joint (revolute, 0-180°) - Forearm

yapcad.assembly.mate_examples.example_serialization()[source]

Demonstrate mate serialization to/from dictionary for JSON export.

yapcad.assembly.mate_examples.example_spherical_joint()[source]

Define a spherical (ball-and-socket) joint with multi-axis limits.

yapcad.assembly.mate_examples.main()[source]

Run all examples.

yapcad.assembly.mate_surrogates module

Mate Surrogates for COTS (Commercial Off-The-Shelf) Parts.

This module defines explicit reference frame definitions for COTS parts that lack native CAD mate features. These “mate surrogates” provide unambiguous interface definitions for kinematic chain assembly.

PROBLEM STATEMENT

COTS parts like Dynamixel servos and DDSM115 hub motors come with STL/STEP files that have geometry but not explicit mate features (datum planes, reference axes). When assembling them into a kinematic chain, we need clear interface definitions:

  1. Where is the output shaft axis?

  2. Where is the mounting face?

  3. What’s the reference frame for orientation?

  4. Where are bolt patterns relative to the origin?

Without explicit surrogates, these interfaces are implicitly defined in code (e.g., kinematic_chain.py), making them: - Hard to verify - Prone to errors when updating - Scattered across multiple files

SOLUTION: MATE SURROGATES

A mate surrogate defines a reference frame attached to a COTS part with: - origin: The reference point (typically mounting face center) - x_axis, y_axis, z_axis: Orthonormal frame axes - features: Named geometric features with positions and orientations

This is similar to how professional CAD systems (SolidWorks, Creo) handle imported geometry - you create reference geometry on top of the dumb solid.

EXISTING GEOMETRY ANALYSIS

FINDING: The existing DSL files and kinematic_chain.py already contain most of the information needed for proper mating, but it’s IMPLICIT rather than EXPLICIT:

  1. DDSM115 Motor (ddsm115_motor.dsl): - EXCELLENT documentation in comments (lines 1-71) - Origin at mounting face (Z=0), wheel in +Z, stator body in -Z - Motor rotation axis is ALONG the Y-axis in STL coordinates (not Z!) - Triangular boss pattern: 3x at 120deg, radius 10.5mm - Bolt circle: 15.2mm diameter, 3x M2.5 at 120deg - ISSUE: The DSL defines the geometry but kinematic_chain.py has to

    manually apply transforms (Ry(-90) @ Rz(30)) to orient correctly

  2. Dynamixel Servos (XH540, XH430, XL330): - STL files exist but no DSL definitions - Interface specs are in gearbox_mates.py (ServoHornSpec class) - Horn bolt circles, protrusion dimensions are documented - Servo body dimensions are in scara_arm.dsl (lines 41-86) - ISSUE: No explicit coordinate system definition in STL files

  3. Planetary Gearboxes (planetary_gearbox.dsl): - Sun gear interface matches servo horn bolt patterns - Center counterbore clears horn protrusion - Output carrier has bolt pattern for next stage - ISSUE: Mating relies on correct Z-stack positioning

  4. Existing Mate Files: - gearbox_mates.py: Excellent! Defines ServoHornSpec and SunGearHubSpec - scara_assembly_mates.py: Complete interface catalog - HOWEVER: These define CONSTRAINTS between parts, not the reference

    frames on individual parts

CONCLUSION

The existing geometry IS sufficient for unambiguous mating, BUT the reference frame information is scattered and implicit. Mate surrogates consolidate this information into a single source of truth.

RECOMMENDATION: - For Dynamixel servos: Minimal surrogates needed (bolt patterns well-documented) - For DDSM115: Essential - the Y-axis rotation is non-obvious and critical - For gearboxes: Already well-defined in planetary_gearbox.dsl, just need to

consolidate frame definitions


class yapcad.assembly.mate_surrogates.Feature(name: str, feature_type: FeatureType, point: Tuple[float, float, float] = (0.0, 0.0, 0.0), direction: Tuple[float, float, float] = (0.0, 0.0, 1.0), radius: float | None = None, count: int | None = None, angle_offset_deg: float = 0.0, vertices: List[Tuple[float, float, float]] | None = None, height: float | None = None, description: str = '')[source]

Bases: object

A named geometric feature on a part.

Variables:
  • name (str) – Feature name (e.g., “stator_face”, “bolt_circle”)

  • feature_type (yapcad.assembly.mate_surrogates.FeatureType) – Type of geometric feature

  • point (Tuple[float, float, float]) – Reference point [x, y, z] in part local coordinates

  • direction (Tuple[float, float, float]) – Direction vector [x, y, z] (for axes, normals)

  • radius (float | None) – Radius for circular features (mm)

  • count (int | None) – Number of items (for hole patterns)

  • angle_offset_deg (float) – Angular offset of first item from +X (degrees)

  • vertices (List[Tuple[float, float, float]] | None) – List of vertex positions (for polygons)

  • height (float | None) – Height for cylindrical features (mm)

  • description (str) – Human-readable description

angle_offset_deg: float = 0.0
count: int | None = None
description: str = ''
direction: Tuple[float, float, float] = (0.0, 0.0, 1.0)
feature_type: FeatureType
get_hole_positions() List[Tuple[float, float, float]][source]

Get positions of holes in a bolt circle pattern.

height: float | None = None
name: str
point: Tuple[float, float, float] = (0.0, 0.0, 0.0)
radius: float | None = None
vertices: List[Tuple[float, float, float]] | None = None
class yapcad.assembly.mate_surrogates.FeatureType(*values)[source]

Bases: Enum

Types of geometric features on COTS parts.

AXIS = 'axis'
CIRCLE = 'circle'
CYLINDER = 'cylinder'
PLANE = 'plane'
POLYGON = 'polygon'
class yapcad.assembly.mate_surrogates.MateSurrogate(part_name: str, description: str = '', origin: Tuple[float, float, float]=(0.0, 0.0, 0.0), x_axis: Tuple[float, float, float]=(1.0, 0.0, 0.0), y_axis: Tuple[float, float, float]=(0.0, 1.0, 0.0), z_axis: Tuple[float, float, float]=(0.0, 0.0, 1.0), features: Dict[str, ~yapcad.assembly.mate_surrogates.Feature]=<factory>, stl_file: str | None = None, step_file: str | None = None, notes: str = '')[source]

Bases: object

Complete reference frame definition for a COTS part.

The surrogate defines a local coordinate system and named features that can be used for assembly mating.

Variables:
  • part_name (str) – Name of the COTS part (e.g., “DDSM115”)

  • description (str) – Part description

  • origin (Tuple[float, float, float]) – Origin point in the part’s STL/native coordinate system

  • x_axis (Tuple[float, float, float]) – X-axis direction vector

  • y_axis (Tuple[float, float, float]) – Y-axis direction vector

  • z_axis (Tuple[float, float, float]) – Z-axis direction vector

  • features (Dict[str, yapcad.assembly.mate_surrogates.Feature]) – Dictionary of named features

  • stl_file (str | None) – Path to STL file (relative to project output directory)

  • step_file (str | None) – Path to STEP file if available

  • notes (str) – Additional notes about the part

add_feature(feature: Feature) None[source]

Add a feature to this surrogate.

description: str = ''
features: Dict[str, Feature]
get_feature(name: str) Feature[source]

Get a feature by name.

get_transform_to_standard_frame() Dict[str, Any][source]

Get transform parameters to convert from STL coords to standard frame.

Returns dict with: - rotation_rpy: (roll, pitch, yaw) in degrees - translation: (x, y, z) offset

notes: str = ''
origin: Tuple[float, float, float] = (0.0, 0.0, 0.0)
part_name: str
step_file: str | None = None
stl_file: str | None = None
x_axis: Tuple[float, float, float] = (1.0, 0.0, 0.0)
y_axis: Tuple[float, float, float] = (0.0, 1.0, 0.0)
z_axis: Tuple[float, float, float] = (0.0, 0.0, 1.0)
yapcad.assembly.mate_surrogates.create_ddsm115_surrogate() MateSurrogate[source]

Create mate surrogate for Waveshare DDSM115 hub motor.

CRITICAL COORDINATE SYSTEM (from STEP file analysis): - The motor rotation axis is the STL Y-AXIS (not Z!) - Tire (rotor/wheel) is at Y < 0 (faces -Y direction) - Stator (mounting face) is at Y > 0 (faces +Y direction)

This is non-intuitive because most parts have their rotation axis along Z. The kinematic_chain.py applies Ry(-90) to map motor Y-axis to tangent direction.

PHYSICAL STRUCTURE: - Hub motor: outer rotor (tire), inner stator (mounting hub) - Tire OD: 100.7mm, Width: 50mm - Motor body diameter: 67mm - Triangular anti-rotation bosses on stator side

STL ORIGIN: - At the interface between tire and stator body - Y=0 is approximately at the tire-stator boundary - Stator mounting face is at Y=+10mm - Tire center is at Y=-26.5mm (50mm wide tire)

yapcad.assembly.mate_surrogates.create_sun_gear_surrogate(axis_name: str, sun_teeth: int, module_mm: float, face_width: float, hub_height: float, bolt_circle: float, servo_type: str) MateSurrogate[source]

Create mate surrogate for a planetary sun gear.

Sun gears have a flanged hub that mates with servo horns. The hub extends BELOW the gear teeth (in -Z).

Parameters:
  • axis_name – Axis identifier (e.g., “AXIS1”)

  • sun_teeth – Number of teeth on sun gear

  • module_mm – Gear module in mm

  • face_width – Gear face width in mm

  • hub_height – Total hub height (extends below gear)

  • bolt_circle – Bolt circle diameter for servo horn interface

  • servo_type – Servo model (e.g., “XH540”, “XH430”, “XL330”)

yapcad.assembly.mate_surrogates.create_xh430_surrogate() MateSurrogate[source]

Create mate surrogate for Robotis Dynamixel XH430-W350 servo.

XH430 is the mid-size servo used for Axis 2 and Axis 3. Smaller than XH540 but still high-torque.

yapcad.assembly.mate_surrogates.create_xh540_surrogate() MateSurrogate[source]

Create mate surrogate for Robotis Dynamixel XH540-W270 servo.

XH540 is the largest Dynamixel X-series servo used in this robot. It drives Axis 1 (shoulder yaw) through a 6:1 planetary gearbox.

COORDINATE SYSTEM: - STL origin at servo body center - Z-axis is servo output shaft axis (horn faces +Z) - Servo body is symmetric about Z-axis in XY plane

PHYSICAL DIMENSIONS (from datasheet): - Body: 33.5 x 58.5 x 44mm (W x D x H) - Horn bolt circle: 18mm diameter, 4x M2.5 at 45deg offset - Output shaft at one end, back shell at other

yapcad.assembly.mate_surrogates.create_xl330_surrogate() MateSurrogate[source]

Create mate surrogate for Robotis Dynamixel XL330-M288 servo.

XL330 is the compact servo used for Axis 4 (wrist rotation). Lightweight and suitable for end effector mounting.

yapcad.assembly.mate_surrogates.get_all_surrogates() Dict[str, MateSurrogate][source]

Get dictionary of all COTS part mate surrogates.

yapcad.assembly.mate_surrogates.print_surrogate_summary() None[source]

Print a summary of all mate surrogates.

yapcad.assembly.mate_surrogates.validate_surrogates() List[str][source]

Validate all mate surrogates for consistency.

Returns:

List of validation issues (empty if all valid)

yapcad.assembly.mechatron_export module

Mechatron assembly-graph JSON exporter.

Translates a yapcad.assembly.Assembly into the GraphSnapshot JSON shape consumed by Mechatron’s assembly-graph MCP service (the save_json/load_json codec at crates/assembly-graph/src/graph.rs). After export, a Mechatron client can POST the JSON or feed it to AssemblyGraph::load_json to materialize the same graph on the consumer side, where it becomes the source of truth for kinematics, MJCF/URDF generation, design-constraint validation, and the Agentic-1 Assembly Dashboard at port 8765.

Schema reference

The target shape is roughly:

{
  "parts": [
    {
      "id":        "<part name>",
      "name":      "<part name>",
      "material":  "PETG",                     # PascalCase Material enum
      "process":   "FDM",                       # PascalCase ManufacturingProcess (FDM|WAAM|CNC|Cast|...)
      "datums": [
        {
          "name":       "shaft",
          "datum_type": "Axis",                # PascalCase DatumType
          "origin":     {"x": 0.0, "y": 0.0, "z": 0.0},
          "direction":  {"x": 0.0, "y": 0.0, "z": 1.0},  # optional
          "normal":     null,                              # optional
          "radius":     null                                # optional
        }
      ],
      "tags": []
    }
  ],
  "interfaces": [
    {
      "id":           "iface_0",
      "parent_part":  "nosecone",
      "child_part":   "bulkhead",
      "joint":        {"type": "Fixed"},        # tagged Joint enum
      "mate_constraints": [
        {"type": "Concentric",
         "parent_datum": "shaft",
         "child_datum": "shaft"}
      ]
    }
  ],
  "last_script": "yapcad-dsl: forward_section"   # provenance hint
}

What this exporter does

  • Walks asm.parts and emits one Part per PartDefinition. Material is taken from PartDefinition.material (defaults to PETG) and mapped to the Mechatron Material PascalCase enum. Process is read from part_def.process if set, otherwise defaults to FDM.

  • For each part, walks its datums and emits one Mechatron Datum per yapcad Datum. yapCAD’s homogeneous coordinates ([x, y, z, w]) are dropped to Point3D/Vec3D shapes.

  • Walks asm.mates and emits one Interface per Mate. yapCAD’s motion mates (REVOLUTE, PRISMATIC, SPHERICAL, RIGID) map onto Mechatron Joint::Revolute/Joint::Prismatic/Joint::Ball/Joint::Fixed. Other yapCAD mate kinds (CONCENTRIC, COINCIDENT, TANGENT, ANGLE) collapse to Joint::Fixed and are emitted as Mechatron MateConstraint entries on the interface (so the downstream solver has the semantic context). yapCAD mate kinds without a Mechatron MateConstraint equivalent (PARALLEL, PERPENDICULAR, DISTANCE, PLANAR, PIN_SLOT, UNIVERSAL, SCREW, GEAR, RACK_PINION, CAM, SLOT) are dropped from the export.

  • last_script is set to a provenance hint identifying the assembly by name so a round-trip through Mechatron preserves the source.

What this exporter does NOT do

  • Solve the assembly. Mechatron’s solver runs add_interface_solved on load; we emit interfaces with their semantic mate constraints attached. Origin/orientation transforms come out of the Mechatron solver, not us.

  • Export design constraints. yapCAD’s Constraint system (the constraint.py module) has different semantics than Mechatron’s DesignConstraint; cross-mapping is deferred to a future pass.

  • Round-trip Mechatron-only features: loop_closures, joint_couplings, keyframes, generators, deformables. These come back as empty lists in the output JSON.

yapcad.assembly.mechatron_export.to_mechatron_json(assembly, *, indent: int = 2) str[source]

Convenience wrapper: serialize to_mechatron_snapshot to JSON.

yapcad.assembly.mechatron_export.to_mechatron_snapshot(assembly) Dict[str, Any][source]

Build a Mechatron GraphSnapshot-shaped dict from an Assembly.

Parameters:

assembly – A yapcad.assembly.Assembly instance, typically built via the Phase-2 DSL builtins (assembly, add_part, add_mate).

Returns:

A plain dict whose shape matches Mechatron’s GraphSnapshot serde representation. Pass through json.dumps to obtain a string consumable by AssemblyGraph::load_json.

Notes

  • Order is preserved: parts are emitted in the order they were added to the assembly; mates likewise. The exporter is deterministic (no dict iteration order surprises).

  • Empty mate_constraints lists are still emitted on interfaces even though Mechatron skips them when serializing; we set them explicitly because every yapcad mate carries semantic context worth preserving.

yapcad.assembly.resolver module

Assembly operation resolver — pluggable strategy for applying operation metadata to assembled geometry.

This module implements the configurable resolver strategy described in RFC §16 and metadata-namespace-v1.1. The selection interface mirrors YAPCAD_BOOLEAN_ENGINE:

# Programmatic
from yapcad.assembly.resolver import resolve_assembly, BasicResolver, StagedResolver
resolved = resolve_assembly(parts, resolver=StagedResolver(stages=["position", "cut"]))

# Via environment variable (mirrors boolean-kernel selection)
YAPCAD_ASSEMBLY_RESOLVER=basic|staged|<entry-point>

Resolvers register via the yapcad.assembly_resolvers Python entry point so external packages (e.g. Mechatron) can supply their own without forking.

Built-in strategies

BasicResolver (default)

Bucket → resolve targets → sort by priority → apply sequentially per target. No positioning awareness. Handles static assemblies such as the Agentic-1 forward bulkhead with two radial cutouts.

StagedResolver

Multi-pass: caller declares ordered stage names; each operation is tagged with stage:. The resolver runs all operations in stage 0 to completion before moving to stage 1. Use when some cuts must follow placement (e.g. clearance cuts that are only meaningful after an articulated part reaches its assembled pose).

KinematicResolver

Scoped for v1.2 — placeholder registered here so external code can reference the name. Raises NotImplementedError if instantiated.

Resolution algorithm (BasicResolver — normative, per spec §8.2)

  1. Collect all parts whose operation metadata has kind set.

  2. Validate that each cutter has a non-empty target_filter.

  3. For each target part, gather all operations that list it in target_filter.

  4. Sort gathered operations by priority (ascending — lower number = first).

  5. Apply each operation in order using the active boolean engine.

  6. Handle policy: strict raises on failure; warn logs; ignore continues silently.

  7. If through is True, assert the operation did not produce a watertight-closed surface on the removed volume (i.e. the cut goes all the way through). If the assertion fails, apply policy.

  8. If consume is False, retain the cutter geometry in the output. Default (True) drops it.

class yapcad.assembly.resolver.AssemblyResolver[source]

Bases: ABC

Abstract base for pluggable assembly resolvers.

abstractmethod resolve(parts: Dict[str, Any], meta_map: Dict[str, Dict], *, boolean_engine: str | None = None) ResolveResult[source]

Apply all operation metadata to geometry.

Parameters:
  • parts{part_id: geometry} dict. Geometry may be any yapCAD solid.

  • meta_map{part_id: metadata_dict} — the full metadata dict (as returned by get_metadata(solid)) for each part.

  • boolean_engine – Override the active boolean engine name. None uses the env var or the yapCAD default.

Return type:

ResolveResult

class yapcad.assembly.resolver.BasicResolver[source]

Bases: AssemblyResolver

Default resolver — per spec §8.2.

Static assemblies. No staging, no positioning awareness. BasicResolver ignores the stage field on operations.

resolve(parts: Dict[str, Any], meta_map: Dict[str, Dict], *, boolean_engine: str | None = None) ResolveResult[source]

Apply all operation metadata to geometry.

Parameters:
  • parts{part_id: geometry} dict. Geometry may be any yapCAD solid.

  • meta_map{part_id: metadata_dict} — the full metadata dict (as returned by get_metadata(solid)) for each part.

  • boolean_engine – Override the active boolean engine name. None uses the env var or the yapCAD default.

Return type:

ResolveResult

class yapcad.assembly.resolver.KinematicResolver(**kwargs: Any)[source]

Bases: AssemblyResolver

StagedResolver + joint-graph-aware positioning. Scoped for v1.2.

Raises NotImplementedError if instantiated — registered here so external code can reference the class name for forward-compat.

resolve(*args: Any, **kwargs: Any) ResolveResult[source]

Apply all operation metadata to geometry.

Parameters:
  • parts{part_id: geometry} dict. Geometry may be any yapCAD solid.

  • meta_map{part_id: metadata_dict} — the full metadata dict (as returned by get_metadata(solid)) for each part.

  • boolean_engine – Override the active boolean engine name. None uses the env var or the yapCAD default.

Return type:

ResolveResult

class yapcad.assembly.resolver.ResolveResult(parts: ~typing.Dict[str, ~yapcad.assembly.resolver.ResolvedPart], execution_log: ~typing.List[str], success: bool = True, errors: ~typing.List[str] = <factory>)[source]

Bases: object

Full result returned by resolve_assembly().

errors: List[str]
execution_log: List[str]
parts: Dict[str, ResolvedPart]
success: bool = True
class yapcad.assembly.resolver.ResolvedPart(part_id: str, geometry: ~typing.Any, applied_operations: ~typing.List[str], retained_cutters: ~typing.List[str], warnings: ~typing.List[str] = <factory>, errors: ~typing.List[str] = <factory>)[source]

Bases: object

A single part after all applicable operations have been applied.

applied_operations: List[str]
errors: List[str]
geometry: Any
part_id: str
retained_cutters: List[str]
warnings: List[str]
class yapcad.assembly.resolver.StagedResolver(stages: Sequence[str])[source]

Bases: AssemblyResolver

Multi-stage resolver for assemblies where cut order depends on position.

The caller declares an ordered list of stage names. Each operation specifies a stage field (a free-form string matching one entry in the stage list). Operations with no stage field default to the last stage.

The resolver runs BasicResolver on each stage’s subset in order, feeding the resolved geometries of stage N as the input to stage N+1.

Parameters:

stages – Ordered stage names, e.g. ["pre_position", "post_position", "final"]. Must be non-empty. Operations with unknown stage tags are collected into a warning and processed in the last stage.

resolve(parts: Dict[str, Any], meta_map: Dict[str, Dict], *, boolean_engine: str | None = None) ResolveResult[source]

Apply all operation metadata to geometry.

Parameters:
  • parts{part_id: geometry} dict. Geometry may be any yapCAD solid.

  • meta_map{part_id: metadata_dict} — the full metadata dict (as returned by get_metadata(solid)) for each part.

  • boolean_engine – Override the active boolean engine name. None uses the env var or the yapCAD default.

Return type:

ResolveResult

yapcad.assembly.resolver.get_resolver(name: str) type | None[source]

Return the resolver class for name, or None if not registered.

yapcad.assembly.resolver.resolve_assembly(parts: Dict[str, Any], meta_map: Dict[str, Dict], *, resolver: AssemblyResolver | None = None, boolean_engine: str | None = None) ResolveResult[source]

Apply all operation metadata in meta_map to the geometries in parts.

Parameters:
  • parts{part_id: geometry} dict.

  • meta_map{part_id: metadata_dict} for each part.

  • resolver – A pre-constructed resolver instance. When None, the resolver is selected via the YAPCAD_ASSEMBLY_RESOLVER environment variable (basic | staged | <entry-point-name>). Defaults to BasicResolver().

  • boolean_engine – Override the active boolean engine. None uses the yapCAD default.

Return type:

ResolveResult

yapcad.assembly.solver module

Generic Mate Constraint Solver for Multi-Body Assemblies.

OVERVIEW

This module provides a generic constraint solver that computes 6DOF transforms from mate constraints between parts. It serves as the core computation engine for datum-driven assembly positioning, eliminating hardcoded transform values.

Key Concepts:
  • Mate Constraint: Geometric relationship between two datum features

  • Datum: Named geometric reference (point, axis, plane, frame) on a part

  • Transform: 4x4 homogeneous transformation matrix positioning a part

  • Mate Axis: Coordinate remapping for rotated reference frames

Quick Start:

from yapcad.assembly.solver import MateConstraintSolver, MateMateSolveResult
from yapcad.assembly.mate import Mate, MateType
from yapcad.assembly.datum_registry import DatumRegistry

# Register datums for parts
DatumRegistry.register_source("servo", servo_datums)
DatumRegistry.register_source("bracket", bracket_datums)

# Create mate constraint
mate = Mate(
    name="servo_to_bracket",
    mate_type=MateType.COINCIDENT,
    part_a="bracket",
    datum_a="servo_mount_face",
    part_b="servo",
    datum_b="stator_face",
)

# Solve for child transform
solver = MateConstraintSolver()
result = solver.solve_mate(mate)

if result.success:
    child_transform = result.transform  # 4x4 numpy array

Note

This module’s MateMateSolveResult is distinct from yapcad.assembly.intent.MateSolveResult.

Mate constraint solver contributed by Jeremy Mika.

Copyright (c) 2026 yapCAD contributors License: MIT

class yapcad.assembly.solver.MateConstraintSolver(tolerance: float = 0.001, angle_tolerance: float = 0.1, use_cache: bool = True)[source]

Bases: object

Generic constraint solver for computing transforms from mates.

This solver takes Mate constraint definitions and computes the 4x4 homogeneous transform that positions the child part relative to the parent part such that the constraint is satisfied.

Supported constraint types:
  • COINCIDENT: Face-to-face contact (normals opposed)

  • CONCENTRIC: Axes colinear

  • PARALLEL: Directions aligned

  • PERPENDICULAR: Directions at 90 degrees

  • DISTANCE: Fixed offset along normal

  • ANGLE: Fixed angular relationship

  • REVOLUTE: Rotation about axis (returns base transform)

Example:

solver = MateConstraintSolver()

# Solve single mate
result = solver.solve_mate(my_mate)

# Solve assembly
results = solver.solve_all([mate1, mate2, mate3], base_transforms)
Variables:
  • cache – Dict mapping mate names to cached transform results

  • tolerance – Position tolerance in mm (default 0.001)

  • angle_tolerance – Angular tolerance in degrees (default 0.1)

invalidate_cache(mate_name: str | None = None) None[source]

Invalidate cached transforms.

Parameters:

mate_name – Specific mate to invalidate, or None for all

solve_all(mates: List[Mate], parent_transforms: Dict[str, Any] | None = None) Dict[str, MateMateSolveResult][source]

Solve multiple mate constraints.

Parameters:
  • mates – List of Mate constraints to solve

  • parent_transforms – Optional dict of {part_name: 4x4 transform}

Returns:

Dict mapping mate names to MateMateSolveResults

solve_mate(mate: Mate, parent_world_transform: Any | None = None, mate_axis: str = 'Z') MateMateSolveResult[source]

Solve a mate constraint to compute the child transform.

Parameters:
  • mate – Mate constraint definition

  • parent_world_transform – Optional 4x4 world transform of parent

  • mate_axis – Coordinate axis for mate (“X”, “Y”, “Z”, “-Z”)

Returns:

MateSolveResult with transform or error

The returned transform positions the child part origin in the parent part’s coordinate frame such that the mate constraint is satisfied.

validate_transform(transform: Any, tolerance_det: float = 1e-06) ValidationResult[source]

Validate a 4x4 transformation matrix.

Parameters:
  • transform – 4x4 numpy array to validate

  • tolerance_det – Tolerance for determinant check

Returns:

ValidationResult with validation details

Checks:
  • Shape is (4, 4)

  • Bottom row is [0, 0, 0, 1]

  • Rotation part is orthonormal (R^T R = I)

  • Determinant is +1 (proper rotation, not reflection)

class yapcad.assembly.solver.MateMateSolveResult(success: bool, transform: Any | None = None, error_message: str = '', residual: float = 0.0, details: Dict[str, ~typing.Any]=<factory>)[source]

Bases: object

Result of solving a mate constraint.

Variables:
  • success (bool) – True if constraint was successfully solved

  • transform (Any | None) – 4x4 homogeneous transform matrix (child in parent frame)

  • error_message (str) – Human-readable error description if failed

  • residual (float) – Constraint satisfaction error (0.0 = perfect)

  • details (Dict[str, Any]) – Additional solver information

details: Dict[str, Any]
error_message: str = ''
residual: float = 0.0
success: bool
transform: Any | None = None
class yapcad.assembly.solver.ValidationResult(valid: bool, is_rigid: bool = True, is_orthonormal: bool = True, position_error: float = 0.0, orientation_error: float = 0.0, error_messages: List[str] = <factory>)[source]

Bases: object

Result of validating a transform matrix.

Variables:
  • valid (bool) – True if transform is valid (orthonormal, proper shape)

  • is_rigid (bool) – True if rotation is orthonormal with det=+1

  • is_orthonormal (bool) – True if rotation columns are unit length

  • position_error (float) – Maximum deviation from orthonormality

  • orientation_error (float) – Deviation from det=1

  • error_messages (List[str]) – List of validation failure reasons

error_messages: List[str]
is_orthonormal: bool = True
is_rigid: bool = True
orientation_error: float = 0.0
position_error: float = 0.0
valid: bool
yapcad.assembly.solver.apply_mate_axis_rotation(transform: Any, mate_axis: str) Any[source]

Apply mate axis coordinate remapping to a transform.

Parameters:
  • transform – 4x4 numpy array

  • mate_axis – Target axis (“X”, “Y”, “Z”, “-X”, “-Y”, “-Z”)

Returns:

Modified 4x4 transform

Example:

# Remap Z axis to Y axis
new_tf = apply_mate_axis_rotation(transform, "Y")
yapcad.assembly.solver.solve_mate_chain(mates: List[Mate], base_transform: Any | None = None, solver: MateConstraintSolver | None = None) Dict[str, Any][source]

Solve a chain of mate constraints sequentially.

Parameters:
  • mates – Ordered list of mates (parent -> child order)

  • base_transform – Optional world transform for first parent

  • solver – Optional solver instance (creates one if not provided)

Returns:

Dict mapping part names to world transforms

Example:

world_transforms = solve_mate_chain(
    [base_to_link1, link1_to_link2, link2_to_tool],
    base_transform=np.eye(4)
)

Module contents

Constraint-based assembly system for yapCAD.

This package provides a declarative assembly system inspired by modern CAD tools (SolidWorks mates, Fusion 360 joints) but tailored for programmatic 3D generative design workflows. It enables definition and validation of assembly relationships to prevent common orientation errors.

Key Concepts:
Datum Features:

Named geometric references (points, axes, planes, frames, circles) defined on parts in local coordinates. Datums transform with parts.

Assembly Mates:

Relationships between datum features that position parts relative to each other. Types include coincident, concentric, parallel, perpendicular, tangent, and angle mates.

Design Constraints:

High-level rules that validate assembly intent beyond positioning. Examples: “motor axis must be tangent to chassis circle”, “stator face must point inward toward mounting surface”.

Constraint Solver:

Computes transforms from mates and validates all constraints, providing clear error messages when design intent is violated.

Design Principles:
  • Constraints are first-class executable objects, not documentation

  • Fail-fast validation detects invalid assemblies at definition time

  • Separation of concerns: mates position, constraints verify intent

  • Composability: complex assemblies built from validated subassemblies

  • Integration with existing kinematic chain transform system

Quick Start:
>>> from yapcad.assembly import (
...     Datum, DatumType, PartDefinition,
...     Mate, MateType,
...     Constraint, ConstraintType,
...     Assembly
... )
>>> # Define a part with datum features
>>> motor = PartDefinition("motor")
>>> motor.add_datum(Datum(
...     "axis", DatumType.AXIS,
...     origin=(0, 0, 0), direction=(0, 1, 0),
...     description="Motor rotation axis"
... ))
>>> motor.add_datum(Datum(
...     "stator_face", DatumType.PLANE,
...     origin=(0, 5, 0), normal=(0, 1, 0),
...     description="Mounting surface"
... ))
>>> # Define assembly with mates
>>> assembly = Assembly("wheel_assembly")
>>> assembly.add_part(motor)
>>> assembly.add_mate(Mate(
...     MateType.CONCENTRIC,
...     "motor", "axis",
...     "bracket", "hole_axis"
... ))
>>> # Add design constraints
>>> assembly.add_constraint(Constraint(
...     ConstraintType.TANGENT_TO_CIRCLE,
...     "motor", "axis",
...     "chassis", "wheel_orbit",
...     description="Motor axis tangent for proper wheel rolling"
... ))
>>> # Solve and validate
>>> result = assembly.solve()
>>> if result.is_valid:
...     transforms = result.transforms
>>> else:
...     print(result.errors)
Available Classes:
Datum Types:

Datum - Named geometric reference feature on a part DatumType - Enum: POINT, AXIS, PLANE, FRAME, CIRCLE PartDefinition - Part with datum features and metadata

Mate System:

Mate - Relationship between two datum features MateType - Enum: COINCIDENT, CONCENTRIC, PARALLEL, PERPENDICULAR,

TANGENT, ANGLE, ALIGNED, OPPOSED

Constraint System:

Constraint - Design rule that validates assembly intent ConstraintType - Enum: TANGENT_TO_CIRCLE, POINTS_TOWARD,

POINTS_AWAY, AXIS_PARALLEL_TO_PLANE, AXIS_PERPENDICULAR_TO_PLANE, CUSTOM

Assembly:

Assembly - Container for parts, mates, and constraints AssemblyResult - Validation result with transforms or errors

See also

  • Examples: examples/assembly_demo.py

  • Integration: src/yapcad/assembly/solver.py