PyBulletFleet - Design Documentation
Architecture Overview
This package provides a modular, reusable PyBullet simulation framework designed for multi-agent scenarios. The architecture is organized into several key components, each with specific responsibilities.
Note: The methods, attributes, and parameters listed in this document are representative highlights — not exhaustive lists. Refer to the source code or API reference for the full interface of each class.
┌─────────────────────────────────────────────────────────────┐
│ User Application │
└───────────────────┬─────────────────────────────────────────┘
│
┌───────────────┴───────────────┐
│ MultiRobotSimulationCore │ ← Main simulation engine
│ (core_simulation.py) │
└───────────────┬───────────────┘
│
┌───────────────┼───────────────┬───────────────┬──────────────┐
│ │ │ │ │
┌───▼───┐ ┌─────▼──────┐ ┌────▼────┐ ┌────▼────┐ ┌────▼────┐
│ Agent │ │ Agent │ │ Action │ │ Tools │ │Visualizer│
│ │ │ Manager │ │ System │ │ (utils) │ │ Monitor │
└───────┘ └────────────┘ └─────────┘ └─────────┘ └─────────┘
Core Components
1. core_simulation.py
Purpose: Main simulation engine and foundational classes
Key Classes:
MultiRobotSimulationCore
The central orchestrator for PyBullet simulations.
Responsibilities:
PyBullet engine initialization and configuration
Simulation loop management (timestep control, speed multiplier)
Camera setup (manual/automatic positioning)
Visualization control (visual shapes, collision shapes, transparency)
Performance monitoring integration
Structure body tracking
Callback management for user-defined updates
Keyboard event handling (SPACE, v, c, t keys)
Collision detection system integration
Key Methods:
from_dict(config)/from_yaml(path): Factory methods for initializationrun_simulation(update_callback, final_callback): Main simulation loopstep_once(): Single simulation stepsetup_camera(): Camera positioningconfigure_visualizer(): Visual settings configurationregister_static_body(body_id): Track static structure elementsregister_callback(callback, frequency): Register custom update callbacks_handle_keyboard_events(): Process keyboard inputs
Associated Params:
SimulationParams— Configuration dataclass holding all parameters forMultiRobotSimulationCore. Passed to the constructor to configure the simulation engine.Attributes:
gui,timestep,target_rtf,duration(core settings),physics,monitor(feature toggles),enable_floor(plane.urdf loading, defaultTrue),camera_*(camera config),enable_*(visualization),spatial_hash_*(collision detection)Creation:
SimulationParams(gui=False, target_rtf=0, ...),SimulationParams.from_dict(config),SimulationParams.from_config("config/config.yaml")enable_floor=Falseskips loadingplane.urdfin bothsetup_pybullet()andreset(), allowing custom floor handling (e.g., transparent floors, environment SDF meshes)
SimObject
Base class for all simulation objects (single rigid body, no joints or links).
SimObject represents a single-body object in PyBullet — it has only a base link and does not support joints, links, or URDF loading. For objects that require multi-link bodies with joint control (e.g., URDF robots), use Agent instead.
Responsibilities:
Common interface for objects in simulation
Position and orientation management via Pose
Metadata storage
Object attachment system (base-link attachment only)
Shared shape caching for performance
Key Methods:
from_params(spawn_params)/from_mesh(...): Factory methods for creationget_pose()/set_pose(pose): Position and orientation managementset_collision_mode(mode): Change collision detection modeattach_object(obj)/detach_object(obj): Parent-child attachment with constraintsget_attached_objects()/is_attached(): Query attachment stateregister_callback(callback, frequency): Register custom update callbacks
Key Features:
Support for mesh and primitive shapes (created via
createMultiBody)Collision and visual shape separation
Pickable/non-pickable objects
Parent-child attachment with constraints
No joint/link support — single rigid body only
Associated Params:
SimObjectSpawnParams— Parameters for spawning a SimObject (visual/collision shapes, initial pose, mass, pickable, collision mode, name, user_data). Pass toSimObject.from_params().ShapeParams— Visual or collision shape definition (shape type, mesh path, half extents, radius, colour, frame offset). Referenced bySimObjectSpawnParams.visual_shapeand.collision_shape.
LogLevelManager
Utility for managing PyBullet log verbosity.
Key Methods:
set_log_level(level): Control PyBullet logging output
2. agent.py
Purpose: Agent with goal-based navigation and action system
Key Classes:
Agent (extends SimObject)
Agent extends SimObject to support URDF loading with multi-link bodies and joint control.
While SimObject is limited to single rigid bodies, Agent can load URDF models that contain
multiple links connected by joints, and provides joint state management and link-level
object attachment via update_attached_objects_kinematics().
Responsibilities:
Goal-based navigation (move towards target pose)
Action execution (MoveTo, Pick, Drop, Wait)
Velocity and acceleration limiting
Path following
Object manipulation (supports link-level attachment for URDF robots)
Collision handling
URDF model loading with joint/link support
Model name resolution —
from_urdf()callsresolve_urdf()internally, accepting both model names (e.g.,"panda") and direct file paths
Key Methods:
from_urdf(urdf_path, ...): Factory method — accepts a model name (resolved viaresolve_urdf()) or a direct URDF pathset_goal(pose): Set target destinationupdate(dt): Update agent state per timestepexecute_action(action): Execute high-level actionis_goal_reached(): Check if at destinationpick(obj): Attach object to agentdrop(): Detach currently held object
Motion Modes:
Omnidirectional: Move in any direction without rotation
Differential: Rotate towards goal then move forward
Control Algorithm:
Proportional controller for position
Linear interpolation for smooth motion
Velocity clamping based on max_linear_vel and max_linear_accel
Joint Control Modes:
Physics mode (
mass > 0,physics=True):setJointMotorControl2— PyBullet motor control with torque limitsKinematic mode (
mass=0.0orphysics=False):resetJointStatewith per-step interpolation — joints move at URDF<limit velocity="...">rates, falling back to per-joint-type defaults when unspecified:_KINEMATIC_JOINT_FALLBACK_VELOCITY(2.0 rad/s) for revolute joints and_KINEMATIC_PRISMATIC_FALLBACK_VELOCITY(0.5 m/s) for prismatic joints. Mode selected once at init via_compute_use_kinematic_joints()and cached in_use_kinematic_joints.Kinematic joint cache (
_kinematic_joint_positions): Joint positions cached in a Python dict, initialized via batchp.getJointStates(), updated after eachresetJointState().get_joint_state()returns cached values for kinematic robots — zero PyBullet calls per step.
Key Joint Methods:
set_joint_target(index, position): Set single joint target (transparent mode switching)set_all_joints_targets(positions): Set all joint targets at onceare_all_joints_at_targets(targets, tolerance): Check if all joints reached targetsare_joints_at_targets(targets, tolerance): Unified check — accepts list, dict, orNone(uses_last_joint_targets)_update_kinematic_joints(dt): Internal per-step interpolation (called fromupdate())
Inverse Kinematics (IK):
move_end_effector(target_position, target_orientation, end_effector_link): High-level EE position command. Solves IK internally, checks reachability, sets joint targets. ReturnsTrueif reachable,Falseif not (best-effort targets still set).
Associated Params:
AgentSpawnParams— Configuration for agent initialization: motion limits (max_linear_vel,max_linear_accel,max_angular_vel,max_angular_accel), motion mode ("omnidirectional"/"differential"), orientation, mass, collision toggle. Immutable after creation.IKParams— IK solver configuration dataclass:max_outer_iterations,convergence_threshold,max_inner_iterations,residual_threshold,reachability_tolerance,seed_quartiles,ik_joint_names. Passed toAgent.from_urdf(ik_params=...). Default: 5 outer iterations, 0.01 m threshold.ik_joint_names(optionaltuple[str, ...]) — When set, only the named joints participate in IK; all other movable joints are locked at their current positions. WhenNone(default), the solver auto-detects:JOINT_FIXEDjoints are skipped, and continuous joints (lower limit ≥ upper limit, e.g. wheels) are locked automatically. This makes IK work correctly on composite robots like mobile manipulators without manual configuration.
Agent-Level Tolerance:
Agent.joint_tolerance— property (float, list, dict, or None) that provides a default tolerance forJointActionwhentolerance=None. Supports dict keyed by joint name or list indexed by absolute joint index for per-joint thresholds (useful for mixed prismatic/revolute arms). Out-of-range list indices fall back to the class default (0.01). Fallback: instance value → class default (0.01). Can be set at construction viaAgent.from_urdf(joint_tolerance=...)or updated via the property setter.
3. agent_manager.py
Purpose: Multi-agent coordination and spawning
Key Classes:
SimObjectManager
Base manager for all simulation objects. Parametrised by an object_class
(default SimObject) so that spawning methods automatically create the right type.
Key Methods:
spawn_objects_grid(num_objects, grid_params, spawn_params): Create objects in grid patternspawn_grid_mixed(num_objects, grid_params, spawn_params_list): Mixed type spawningspawn_grid_counts(grid_params, spawn_params_count_list): Exact count spawningspawn_objects_batch(params_list): Batch spawn with explicit poses
AgentManager
Extends SimObjectManager with object_class=Agent.
Additional Responsibilities:
Goal management and update callbacks
Query moving/stopped agents
Convenience Aliases:
spawn_agents_grid(...)→spawn_objects_grid(...)spawn_agents_grid_mixed(...)→spawn_grid_mixed(...)spawn_agent_grid_counts(...)→spawn_grid_counts(...)
Agent-Specific Methods:
register_callback(callback): Register custom goal logicset_goal_pose(agent_index, goal): Set goal for a specific agentget_moving_count(): Count moving agents
Note:
Agent.update() is automatically called by MultiRobotSimulationCore.step_once()
AgentManager focuses on goal management, not movement updates
Associated Params:
GridSpawnParams— Grid layout configuration: boundaries (x_min/x_max,y_min/y_max,z_min/z_max), spacing, offset. Automatically distributes agents evenly usingceil(sqrt(n)).
4. action.py
Purpose: High-level action system for agents
Key Classes:
Action (Base Class)
Abstract base class for all actions.
Key Methods:
start(agent): Initialize actionupdate(agent, dt): Update action stateis_complete(): Check if action finishedstop(agent): Clean up action
MoveTo
Navigate agent to target pose.
Key Parameters:
target_pose: Destination posetolerance: Distance threshold for completion
Pick
Pick up an object and attach it to agent.
Key Parameters:
target_object_id: Specific object body ID to pick (optional)target_position: Pick from position — auto-select nearest pickable object (optional)search_radius: Search radius when usingtarget_position(default: 0.5m)attach_link: Link index or name to attach to (default: -1 for base)attach_relative_pose: Offset in link’s frame as Poseuse_approach: Whether to execute the approach/retreat phases (default:True). WhenTrue, the agent navigates to an approach pose → moves forward to the pick position → picks → retreats. WhenFalse, the pick is executed immediately at the agent’s current position — useful for arm robots and mobile manipulators where the EE is already positioned via IK.approach_offset: Distance from target for auto-calculated approach pose (default: 1.0 m)
Drop
Drop an attached object at a specified location.
Key Parameters:
drop_pose: Where to drop the object (position and orientation)drop_relative_pose: OptionalPoseoffset — when set, the object is placed at its current (pre-detach) position transformed by this offset instead of being teleported todrop_pose. Useful for EE-attached objects on mobile manipulators where the absolute world drop position is hard to predict.target_object_id: Specific object to drop (None = first attached)place_gently: Place at exact position vs drop from height (default: True)use_approach: Whether to execute the approach/retreat phases (default:True). WhenTrue, the agent navigates to an approach pose neardrop_pose→ moves forward → drops → retreats. WhenFalse, the drop is executed immediately — useful for arm robots and mobile manipulators where the EE is already positioned.approach_offset: Distance from drop pose for auto-calculated approach pose (default: 1.0 m)drop_offset: Distance fromdrop_posewhere the actual drop occurs (default: 0.0 = atdrop_pose)
Wait
Wait for specified duration.
Key Parameters:
duration: Wait time in seconds
JointAction
Move all joints to target positions.
Key Parameters:
target_joint_positions: List of target positions for all controllable joints (radians for revolute, metres for prismatic), or dict keyed by joint nametolerance: Completion threshold per joint — scalarfloat,listindexed by absolute joint index,dictkeyed by joint name, orNone(resolved fromagent.joint_toleranceon first tick; default: 0.01). Out-of-range list indices fall back to the class default (0.01)max_force: Motor force for physics mode (default: 500.0 N·m)
Tolerance resolution: When tolerance is None, it is resolved once from agent.joint_tolerance at the first execute() call and written back to action.tolerance. Fallback chain: Action → Agent → class default (0.01). Dict tolerance enables per-joint thresholds for mixed prismatic/revolute arms.
Completion: All joints within tolerance of their targets. Works transparently
in both physics mode (motor control) and kinematic mode (interpolation).
PoseAction
Move end-effector to a Cartesian target position via IK.
Key Parameters:
target_position: EE target[x, y, z]in world frametarget_orientation: Optional quaternion[x, y, z, w]for orientation controlend_effector_link: Link index, name, orNone(auto-detect last link)tolerance: EE Cartesian distance threshold in metres (default: 0.02 m)max_force: Motor force for physics mode (default: 500.0 N·m)
Completion: Joints within default joint tolerance of the IK solution and EE within tolerance
of the target position. Calls move_end_effector() on start,
then monitors are_joints_at_targets() and are_ee_at_target() each step.
Unreachable targets: If the IK solver determines the target is unreachable, the action does not
fail immediately. Best-effort joint targets are set and joints move toward them. After settling,
the action completes with ActionStatus.FAILED (not COMPLETED). A warning is logged at start
and the error_message attribute is set to "IK target was not reachable".
IK integration in Pick/Drop:
PickAction and DropAction accept an optional ee_target_position parameter.
When set, the action delegates to a PoseAction sub-action to position the EE via IK before
performing the pick/drop operation, as an alternative to JointAction-based positioning.
A continue_on_ik_failure flag (default: True) controls whether the pick/drop
proceeds even when the IK target is unreachable.
5. geometry.py
Purpose: Geometric data structures (Pose, Path) used throughout the codebase for position/orientation representation and waypoint sequences.
6. tools.py
Purpose: Utility functions for pose calculation (approach/offset poses for pick/drop actions).
7. data_monitor.py
Purpose: Optional real-time GUI monitor (DataMonitor) displaying FPS and step-time metrics in a tkinter window. Enabled via monitor: true in config.
8. robot_models.py
Purpose: Robot model resolution and introspection. Provides a tiered registry (KNOWN_MODELS) that maps model names to URDF paths across multiple sources, plus auto-detection of robot capabilities.
Key Functions:
resolve_urdf(name_or_path)
Resolves a model name to an absolute URDF file path by searching through tiers in order:
Tier |
Source |
Example |
|---|---|---|
0 — |
|
|
1 — |
PyBullet’s bundled data directory |
|
2 — |
ROS install paths ( |
|
3 — |
|
|
Direct file paths (containing / or ending in .urdf/.sdf) pass through unchanged.
Called internally by Agent.from_urdf(), so users can pass model names directly.
KNOWN_MODELS is a curated subset — not every model from each tier is pre-registered.
Unlisted models are resolved automatically via fallback scanning of pybullet_data and robot_descriptions.
For name-based lookup, user search paths (registered via add_search_path()) are checked before the KNOWN_MODELS tiers.
register_model(name, path_or_entry) / unregister_model(name)
Add or remove entries from KNOWN_MODELS at runtime. path_or_entry accepts an absolute path string (tier defaults to "user") or a ModelEntry for full tier metadata. Prevents accidental overwrites by default (force=True to override).
discover_models(tier)
Scan an entire tier ("pybullet_data" or "robot_descriptions") and return all discoverable models as {name: path}. Unlike KNOWN_MODELS, this returns every model found in the installed package. Also used internally by resolve_urdf() as a fallback for unlisted models.
add_search_path(directory) / remove_search_path(directory) / get_search_paths()
Register custom directories for name-based URDF lookup. User search paths take priority over KNOWN_MODELS, enabling users to shadow built-in models with custom versions. add_search_path() validates the directory exists and is idempotent.
auto_detect_profile(body_id_or_path, client)
Inspects a loaded PyBullet body (or loads a URDF temporarily) and returns a RobotProfile dataclass:
robot_type—"arm","mobile","mobile_manipulator", or"static"num_joints,movable_joint_names,movable_joint_indicesee_link_name,ee_link_index— end-effector detectionjoint_lower_limits,joint_upper_limits,joint_max_velocities
Accepts Union[str, int] — when given an int (body_id), skips load/removeBody overhead.
list_all_models()
Returns a dict of all registered models with tier, availability, and resolved path or error message.
detect_robot_type(body_id, client)
Lightweight type detection (arm/mobile/mobile_manipulator/static) without full profile analysis.
Key Data:
KNOWN_MODELS—dict[str, ModelEntry]mapping model names to their tier and path resolverRobotProfile— Frozen dataclass with all introspection resultsModelEntry— Named tuple:(tier, path_func)for each model
Performance Considerations
Bottlenecks:
GUI Rendering: 2-3x slower than headless mode
Collision Detection: O(N) with spatial hashing, O(N²) without
Mesh Complexity: High-poly meshes slow down rendering
Monitor Updates: tkinter GUI overhead
Shape Creation: Repeated shape creation for many objects
Optimizations:
Disable GUI:
gui: falsefor batch simulationsSpatial Hashing: Enabled by default for collision detection
Shared Shapes: Automatic shape caching reduces OpenGL overhead
Increase Timestep: Trade accuracy for speed (default: 1/240s)
Simple Shapes: Use boxes/cylinders instead of complex meshes
Batch Operations: Update all agents in single pass
Disable Monitor:
monitor: falsein productionCell Size Tuning: Use
constantmode with optimal cell_size for best performance
See docs/PERFORMANCE_ANALYSIS.md and docs/OPTIMIZATION_RESULTS.md for detailed benchmarks.