devagent-physical-engine 0.12.4__tar.gz → 0.12.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. {devagent_physical_engine-0.12.4/src/devagent_physical_engine.egg-info → devagent_physical_engine-0.12.5}/PKG-INFO +2 -2
  2. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/README.md +1 -1
  3. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/pyproject.toml +1 -1
  4. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/__init__.py +1 -1
  5. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/physical_motion.py +14 -1
  6. devagent_physical_engine-0.12.5/src/devagent_physical_engine/pose_math.py +134 -0
  7. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/reference_workcell.py +151 -39
  8. devagent_physical_engine-0.12.5/src/devagent_physical_engine/ros2/joint_state_probe.py +139 -0
  9. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/ros2/joint_state_recorder.py +35 -9
  10. devagent_physical_engine-0.12.5/src/devagent_physical_engine/ros2/moveit_joint_plan_probe.py +580 -0
  11. devagent_physical_engine-0.12.5/src/devagent_physical_engine/ros2/moveit_task_planner.py +279 -0
  12. devagent_physical_engine-0.12.5/src/devagent_physical_engine/ros2/task_adapter.py +160 -0
  13. devagent_physical_engine-0.12.5/src/devagent_physical_engine/ros2/task_visualization.py +602 -0
  14. devagent_physical_engine-0.12.5/src/devagent_physical_engine/ros2/trajectory.py +376 -0
  15. devagent_physical_engine-0.12.5/src/devagent_physical_engine/ros2/trajectory_action_probe.py +352 -0
  16. devagent_physical_engine-0.12.5/src/devagent_physical_engine/ros2/trajectory_tracking.py +201 -0
  17. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/ros2/workpiece_follow.py +79 -5
  18. devagent_physical_engine-0.12.5/src/devagent_physical_engine/ros2/workpiece_gazebo_follower.py +275 -0
  19. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/setup_profile.py +5 -0
  20. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5/src/devagent_physical_engine.egg-info}/PKG-INFO +2 -2
  21. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine.egg-info/SOURCES.txt +10 -0
  22. devagent_physical_engine-0.12.5/tests/test_joint_state_probe_v0125.py +54 -0
  23. devagent_physical_engine-0.12.5/tests/test_moveit_planning_diagnostics_v0125.py +104 -0
  24. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/tests/test_physical_motion_v09.py +5 -3
  25. devagent_physical_engine-0.12.5/tests/test_pose_math_v0125.py +42 -0
  26. devagent_physical_engine-0.12.5/tests/test_reference_workcell_ros_frames_v0125.py +41 -0
  27. devagent_physical_engine-0.12.5/tests/test_ros_trajectory_v09.py +300 -0
  28. devagent_physical_engine-0.12.5/tests/test_trajectory_action_probe_v0125.py +107 -0
  29. devagent_physical_engine-0.12.5/tests/test_trajectory_tracking_v0125.py +142 -0
  30. devagent_physical_engine-0.12.4/src/devagent_physical_engine/ros2/moveit_joint_plan_probe.py +0 -159
  31. devagent_physical_engine-0.12.4/src/devagent_physical_engine/ros2/moveit_task_planner.py +0 -139
  32. devagent_physical_engine-0.12.4/src/devagent_physical_engine/ros2/task_adapter.py +0 -52
  33. devagent_physical_engine-0.12.4/src/devagent_physical_engine/ros2/task_visualization.py +0 -325
  34. devagent_physical_engine-0.12.4/src/devagent_physical_engine/ros2/trajectory.py +0 -213
  35. devagent_physical_engine-0.12.4/src/devagent_physical_engine/ros2/workpiece_gazebo_follower.py +0 -218
  36. devagent_physical_engine-0.12.4/tests/test_ros_trajectory_v09.py +0 -116
  37. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/NOTICE +0 -0
  38. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/setup.cfg +0 -0
  39. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/agent/__init__.py +0 -0
  40. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/agent/compiler.py +0 -0
  41. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/agent/contracts.py +0 -0
  42. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/agent/coordinator.py +0 -0
  43. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/agent/critic.py +0 -0
  44. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/agent/evidence.py +0 -0
  45. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/agent/interpreter.py +0 -0
  46. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/agent/planner.py +0 -0
  47. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/agent/recovery.py +0 -0
  48. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/agent/routing.py +0 -0
  49. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/agent/runtime.py +0 -0
  50. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/agent/semantic.py +0 -0
  51. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/agent/structured.py +0 -0
  52. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/ai_cli.py +0 -0
  53. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/cli.py +0 -0
  54. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/continuous_clearance.py +0 -0
  55. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/doctor.py +0 -0
  56. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/engineering_agent.py +0 -0
  57. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/engineering_request.py +0 -0
  58. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/execution.py +0 -0
  59. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/models.py +0 -0
  60. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/operating_envelope.py +0 -0
  61. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/optimization/__init__.py +0 -0
  62. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/optimization/benchmark.py +0 -0
  63. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/optimization/candidates.py +0 -0
  64. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/optimization/contracts.py +0 -0
  65. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/optimization/evaluator.py +0 -0
  66. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/optimization/evidence.py +0 -0
  67. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/optimization/experience.py +0 -0
  68. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/optimization/measured.py +0 -0
  69. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/optimization/optimizer.py +0 -0
  70. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/optimization/orchestrator.py +0 -0
  71. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/physical_campaign.py +0 -0
  72. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/physical_evidence.py +0 -0
  73. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/planning.py +0 -0
  74. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/preexecution_contract.py +0 -0
  75. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/provider_adapters/__init__.py +0 -0
  76. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/provider_adapters/anthropic.py +0 -0
  77. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/provider_adapters/common.py +0 -0
  78. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/provider_adapters/factory.py +0 -0
  79. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/provider_adapters/gemini.py +0 -0
  80. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/provider_adapters/openai.py +0 -0
  81. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/provider_qualification.py +0 -0
  82. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/providers.py +0 -0
  83. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/qualification.py +0 -0
  84. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/qualification_cli.py +0 -0
  85. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/qualification_harness.py +0 -0
  86. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/robot_platform.py +0 -0
  87. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/robots.py +0 -0
  88. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/ros2/__init__.py +0 -0
  89. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/ros2/acceptance.py +0 -0
  90. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/ros2/commands.py +0 -0
  91. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/ros2/continuous_clearance_probe.py +0 -0
  92. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/ros2/doctor.py +0 -0
  93. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/ros2/fk_pose.py +0 -0
  94. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/ros2/fk_probe.py +0 -0
  95. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/ros2/frame_alignment.py +0 -0
  96. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/ros2/gazebo_cli.py +0 -0
  97. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/ros2/gazebo_pose.py +0 -0
  98. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/ros2/gazebo_world.py +0 -0
  99. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/ros2/measured_motion.py +0 -0
  100. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/ros2/moveit_scene.py +0 -0
  101. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/ros2/preexecution.py +0 -0
  102. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/ros2/qualification.py +0 -0
  103. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/ros2/qualification_v10.py +0 -0
  104. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/ros2/qualification_v11.py +0 -0
  105. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/ros2/qualification_v12.py +0 -0
  106. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/ros2/scene_probe.py +0 -0
  107. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/ros2/state_validity_probe.py +0 -0
  108. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/ros2/tf_probe.py +0 -0
  109. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/ros2/tool_scene.py +0 -0
  110. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/ros2/tool_scene_probe.py +0 -0
  111. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/ros2/ur5e.py +0 -0
  112. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/ros2/ur5e_adapter.py +0 -0
  113. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/ros2/ur5e_v10_adapter.py +0 -0
  114. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/ros2/workpiece_scene.py +0 -0
  115. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/ros2/workpiece_scene_probe.py +0 -0
  116. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/sim_real_qualification.py +0 -0
  117. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/simulation.py +0 -0
  118. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/simulation_platform.py +0 -0
  119. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/tool_collision.py +0 -0
  120. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/trajectory_qualification.py +0 -0
  121. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/twin.py +0 -0
  122. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/twin_builder.py +0 -0
  123. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/twin_materialization.py +0 -0
  124. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/verification.py +0 -0
  125. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine/workpiece_lifecycle.py +0 -0
  126. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine.egg-info/dependency_links.txt +0 -0
  127. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine.egg-info/entry_points.txt +0 -0
  128. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine.egg-info/requires.txt +0 -0
  129. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/src/devagent_physical_engine.egg-info/top_level.txt +0 -0
  130. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/tests/test_agent_core.py +0 -0
  131. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/tests/test_agent_semantic_hardening.py +0 -0
  132. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/tests/test_ai_cli.py +0 -0
  133. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/tests/test_ai_cli_v07.py +0 -0
  134. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/tests/test_continuous_clearance_v011.py +0 -0
  135. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/tests/test_core.py +0 -0
  136. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/tests/test_engineering_request_v07.py +0 -0
  137. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/tests/test_gazebo_cli_v0122.py +0 -0
  138. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/tests/test_interpreter_profiles_v08.py +0 -0
  139. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/tests/test_live_provider_qualification.py +0 -0
  140. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/tests/test_measured_motion_v10.py +0 -0
  141. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/tests/test_moveit_scene_v0123.py +0 -0
  142. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/tests/test_natural_language_agent_v07.py +0 -0
  143. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/tests/test_openai_strict_schema_v0113.py +0 -0
  144. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/tests/test_optimization_v05.py +0 -0
  145. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/tests/test_physical_campaign_v09.py +0 -0
  146. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/tests/test_physical_evidence_v09.py +0 -0
  147. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/tests/test_planning_fallback_v012.py +0 -0
  148. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/tests/test_preexecution_contract_v10.py +0 -0
  149. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/tests/test_provider_and_qualification.py +0 -0
  150. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/tests/test_qualification_cli_lifecycle_v06.py +0 -0
  151. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/tests/test_qualification_harness_v06.py +0 -0
  152. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/tests/test_readme_pypi.py +0 -0
  153. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/tests/test_real_provider_adapters.py +0 -0
  154. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/tests/test_reference_workcell_v012.py +0 -0
  155. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/tests/test_robot_platform_v08.py +0 -0
  156. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/tests/test_ros2_production_receipts_v011.py +0 -0
  157. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/tests/test_ros2_simulation.py +0 -0
  158. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/tests/test_setup_profile.py +0 -0
  159. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/tests/test_sim_real_qualification_v011.py +0 -0
  160. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/tests/test_simulation_platform_v08.py +0 -0
  161. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/tests/test_tool_collision_v011.py +0 -0
  162. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/tests/test_tool_scene_v0124.py +0 -0
  163. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/tests/test_trajectory_qualification_v09.py +0 -0
  164. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/tests/test_twin_materialization_v10.py +0 -0
  165. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/tests/test_twin_v08.py +0 -0
  166. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/tests/test_ur5e_adapter_scopes_v09.py +0 -0
  167. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/tests/test_ur5e_v10_adapter_scopes.py +0 -0
  168. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/tests/test_v052_stabilization.py +0 -0
  169. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/tests/test_v08_catalog_mapping_regression.py +0 -0
  170. {devagent_physical_engine-0.12.4 → devagent_physical_engine-0.12.5}/tests/test_workpiece_lifecycle_v011.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devagent-physical-engine
3
- Version: 0.12.4
3
+ Version: 0.12.5
4
4
  Summary: Verification-first agentic engineering, simulation, and evidence runtime for robotics and industrial automation.
5
5
  Author: Tom Ha
6
6
  License: All Rights Reserved
@@ -424,7 +424,7 @@ Detailed engineering documentation lives in the repository:
424
424
 
425
425
  ## Project status
426
426
 
427
- **v0.12.4 — Beta software / evidence-gated task-specific physical simulation.**
427
+ **v0.12.5 — Beta software / evidence-gated task-specific physical simulation.**
428
428
 
429
429
  The software and release pipeline are production-oriented, but physical readiness is scope-specific and evidence-driven. The UR5e task-specific reference-workcell path must pass the executable v0.12 ROS/Gazebo/MoveIt campaign on the target workstation before that scope is considered simulation-qualified there. Real robot execution remains locked. Functional-safety certification and site qualification are not claimed.
430
430
 
@@ -383,7 +383,7 @@ Detailed engineering documentation lives in the repository:
383
383
 
384
384
  ## Project status
385
385
 
386
- **v0.12.4 — Beta software / evidence-gated task-specific physical simulation.**
386
+ **v0.12.5 — Beta software / evidence-gated task-specific physical simulation.**
387
387
 
388
388
  The software and release pipeline are production-oriented, but physical readiness is scope-specific and evidence-driven. The UR5e task-specific reference-workcell path must pass the executable v0.12 ROS/Gazebo/MoveIt campaign on the target workstation before that scope is considered simulation-qualified there. Real robot execution remains locked. Functional-safety certification and site qualification are not claimed.
389
389
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "devagent-physical-engine"
7
- version = "0.12.4"
7
+ version = "0.12.5"
8
8
  description = "Verification-first agentic engineering, simulation, and evidence runtime for robotics and industrial automation."
9
9
  readme = { file = "README.md", content-type = "text/markdown" }
10
10
  requires-python = ">=3.11"
@@ -41,4 +41,4 @@ __all__ = [
41
41
  "TwinValidator",
42
42
  ]
43
43
 
44
- __version__ = "0.12.4"
44
+ __version__ = "0.12.5"
@@ -160,6 +160,12 @@ class MotionExecutionMetrics:
160
160
  min_clearance_m: float | None = None
161
161
  final_tcp_error_m: float | None = None
162
162
  energy_proxy: float | None = None
163
+ joint_state_sample_rate_hz: float | None = None
164
+ max_sample_gap_s: float | None = None
165
+ trajectory_duration_error_s: float | None = None
166
+ tracking_sample_count: int | None = None
167
+ tracking_scope: str = "endpoint_only"
168
+ tracking_interpolation: str = "unspecified"
163
169
  planner_retries: int | None = None
164
170
  trajectory_retries: int | None = None
165
171
  failure_codes: tuple[str, ...] = ()
@@ -168,6 +174,10 @@ class MotionExecutionMetrics:
168
174
  def __post_init__(self) -> None:
169
175
  if not self.metrics_origin.strip():
170
176
  raise PhysicalMotionError("metrics_origin_required")
177
+ if not self.tracking_scope.strip():
178
+ raise PhysicalMotionError("tracking_scope_required")
179
+ if not self.tracking_interpolation.strip():
180
+ raise PhysicalMotionError("tracking_interpolation_required")
171
181
  for name in (
172
182
  "observed_duration_s",
173
183
  "planned_duration_s",
@@ -178,6 +188,9 @@ class MotionExecutionMetrics:
178
188
  "min_clearance_m",
179
189
  "final_tcp_error_m",
180
190
  "energy_proxy",
191
+ "joint_state_sample_rate_hz",
192
+ "max_sample_gap_s",
193
+ "trajectory_duration_error_s",
181
194
  ):
182
195
  value = getattr(self, name)
183
196
  if value is None:
@@ -185,7 +198,7 @@ class MotionExecutionMetrics:
185
198
  numeric = float(value)
186
199
  if not isfinite(numeric) or numeric < 0:
187
200
  raise PhysicalMotionError(f"metric_invalid:{name}")
188
- for name in ("planner_retries", "trajectory_retries"):
201
+ for name in ("tracking_sample_count", "planner_retries", "trajectory_retries"):
189
202
  value = getattr(self, name)
190
203
  if value is not None and value < 0:
191
204
  raise PhysicalMotionError(f"metric_invalid:{name}")
@@ -0,0 +1,134 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from math import acos, isfinite, sqrt
5
+
6
+
7
+ class PoseMathError(ValueError):
8
+ pass
9
+
10
+
11
+ def _finite(values: tuple[float, ...], *, code: str) -> tuple[float, ...]:
12
+ converted = tuple(float(value) for value in values)
13
+ if any(not isfinite(value) for value in converted):
14
+ raise PoseMathError(code)
15
+ return converted
16
+
17
+
18
+ def normalize_quaternion(
19
+ quaternion_xyzw: tuple[float, float, float, float],
20
+ ) -> tuple[float, float, float, float]:
21
+ values = _finite(tuple(quaternion_xyzw), code="quaternion_non_finite")
22
+ if len(values) != 4:
23
+ raise PoseMathError("quaternion_size_invalid")
24
+ norm = sqrt(sum(value * value for value in values))
25
+ if norm <= 1e-12:
26
+ raise PoseMathError("quaternion_zero")
27
+ normalized = tuple(value / norm for value in values)
28
+ # q and -q encode the same rotation. Keep one deterministic representation.
29
+ for component in (normalized[3], normalized[0], normalized[1], normalized[2]):
30
+ if abs(component) <= 1e-15:
31
+ continue
32
+ return tuple(-value for value in normalized) if component < 0.0 else normalized
33
+ raise PoseMathError("quaternion_zero")
34
+
35
+
36
+ def quaternion_multiply(
37
+ first: tuple[float, float, float, float],
38
+ second: tuple[float, float, float, float],
39
+ ) -> tuple[float, float, float, float]:
40
+ ax, ay, az, aw = normalize_quaternion(first)
41
+ bx, by, bz, bw = normalize_quaternion(second)
42
+ return normalize_quaternion(
43
+ (
44
+ aw * bx + ax * bw + ay * bz - az * by,
45
+ aw * by - ax * bz + ay * bw + az * bx,
46
+ aw * bz + ax * by - ay * bx + az * bw,
47
+ aw * bw - ax * bx - ay * by - az * bz,
48
+ )
49
+ )
50
+
51
+
52
+ def quaternion_conjugate(
53
+ quaternion_xyzw: tuple[float, float, float, float],
54
+ ) -> tuple[float, float, float, float]:
55
+ x, y, z, w = normalize_quaternion(quaternion_xyzw)
56
+ return (-x, -y, -z, w)
57
+
58
+
59
+ def rotate_vector(
60
+ quaternion_xyzw: tuple[float, float, float, float],
61
+ vector_xyz: tuple[float, float, float],
62
+ ) -> tuple[float, float, float]:
63
+ qx, qy, qz, qw = normalize_quaternion(quaternion_xyzw)
64
+ vx, vy, vz = _finite(tuple(vector_xyz), code="vector_non_finite")
65
+ if len((vx, vy, vz)) != 3:
66
+ raise PoseMathError("vector_size_invalid")
67
+ # Unit-quaternion vector rotation without constructing temporary quaternions.
68
+ tx = 2.0 * (qy * vz - qz * vy)
69
+ ty = 2.0 * (qz * vx - qx * vz)
70
+ tz = 2.0 * (qx * vy - qy * vx)
71
+ return (
72
+ vx + qw * tx + (qy * tz - qz * ty),
73
+ vy + qw * ty + (qz * tx - qx * tz),
74
+ vz + qw * tz + (qx * ty - qy * tx),
75
+ )
76
+
77
+
78
+ @dataclass(frozen=True, slots=True)
79
+ class RigidPose:
80
+ position_m: tuple[float, float, float]
81
+ quaternion_xyzw: tuple[float, float, float, float]
82
+
83
+ def __post_init__(self) -> None:
84
+ position = _finite(tuple(self.position_m), code="pose_position_non_finite")
85
+ if len(position) != 3:
86
+ raise PoseMathError("pose_position_size_invalid")
87
+ object.__setattr__(self, "position_m", position)
88
+ object.__setattr__(
89
+ self,
90
+ "quaternion_xyzw",
91
+ normalize_quaternion(tuple(self.quaternion_xyzw)),
92
+ )
93
+
94
+ @classmethod
95
+ def identity(cls) -> "RigidPose":
96
+ return cls((0.0, 0.0, 0.0), (0.0, 0.0, 0.0, 1.0))
97
+
98
+ def compose(self, child: "RigidPose") -> "RigidPose":
99
+ offset = rotate_vector(self.quaternion_xyzw, child.position_m)
100
+ return RigidPose(
101
+ tuple(self.position_m[index] + offset[index] for index in range(3)),
102
+ quaternion_multiply(self.quaternion_xyzw, child.quaternion_xyzw),
103
+ )
104
+
105
+ def inverse(self) -> "RigidPose":
106
+ inverse_q = quaternion_conjugate(self.quaternion_xyzw)
107
+ inverse_position = rotate_vector(
108
+ inverse_q,
109
+ tuple(-value for value in self.position_m),
110
+ )
111
+ return RigidPose(inverse_position, inverse_q)
112
+
113
+ def relative_to(self, parent: "RigidPose") -> "RigidPose":
114
+ return parent.inverse().compose(self)
115
+
116
+ def position_error_m(self, other: "RigidPose") -> float:
117
+ return sqrt(
118
+ sum(
119
+ (self.position_m[index] - other.position_m[index]) ** 2
120
+ for index in range(3)
121
+ )
122
+ )
123
+
124
+ def orientation_error_rad(self, other: "RigidPose") -> float:
125
+ first = normalize_quaternion(self.quaternion_xyzw)
126
+ second = normalize_quaternion(other.quaternion_xyzw)
127
+ dot = abs(sum(a * b for a, b in zip(first, second)))
128
+ return 2.0 * acos(max(-1.0, min(1.0, dot)))
129
+
130
+ def to_dict(self) -> dict[str, list[float]]:
131
+ return {
132
+ "position_m": list(self.position_m),
133
+ "quaternion_xyzw": list(self.quaternion_xyzw),
134
+ }
@@ -34,11 +34,21 @@ _PLATFORM_DIMS_M = (0.36, 0.36, 0.18)
34
34
  _TOOL_DIMS_M = (0.04, 0.04, 0.03)
35
35
  _TOOL_COLLISION_CENTER_LOCAL_M = (0.0, 0.0, 0.015)
36
36
  _WORKPIECE_CENTER_FROM_TOOL_LOCAL_M = (0.0, 0.0, 0.07)
37
-
38
- # Universal Robots published UR5e nominal DH dimensions in metres. These values are
39
- # used only to build the packaged reference workcell. Runtime MoveIt FK is required
40
- # to agree before a BOX is attached, so this template cannot silently override the
41
- # robot model installed on the workstation.
37
+ # tool0 and flange are fixed UR end-effector frames without collision geometry;
38
+ # wrist_3_link owns the physical wrist collision mesh at the mounting interface.
39
+ # The packaged reference gripper starts at that interface, so those three links are
40
+ # the only robot links it may touch. All other robot/environment collisions remain
41
+ # authoritative in MoveIt.
42
+ _REFERENCE_TOOL_TOUCH_LINKS = ("tool0", "flange", "wrist_3_link")
43
+
44
+ # Universal Robots published UR5e nominal DH dimensions in metres. Standard UR DH
45
+ # kinematics are expressed in the controller ``base`` convention. The ROS 2 Jazzy
46
+ # UR description defines ``base_link`` as REP-103 and ``base`` at the same origin
47
+ # rotated by pi around Z. DevAgent's canonical Twin and MoveIt FK both use
48
+ # ``base_link``, so the reference FK applies that documented fixed transform before
49
+ # the DH chain. Runtime MoveIt FK still has authority and must agree before a BOX is
50
+ # attached, so this template cannot silently override the robot model installed on
51
+ # the workstation.
42
52
  _UR5E_DH_A = (0.0, -0.425, -0.3922, 0.0, 0.0, 0.0)
43
53
  _UR5E_DH_D = (0.1625, 0.0, 0.0, 0.1333, 0.0997, 0.0996)
44
54
  _UR5E_DH_ALPHA = (pi / 2, 0.0, 0.0, pi / 2, -pi / 2, 0.0)
@@ -58,46 +68,88 @@ class LoadPhaseTarget:
58
68
  synchronize_workpiece_pose: bool = False
59
69
 
60
70
 
61
- def _matmul(first: tuple[tuple[float, ...], ...], second: tuple[tuple[float, ...], ...]):
71
+ def _matmul(
72
+ first: tuple[tuple[float, ...], ...],
73
+ second: tuple[tuple[float, ...], ...],
74
+ ):
62
75
  return tuple(
63
- tuple(sum(first[row][k] * second[k][col] for k in range(4)) for col in range(4))
76
+ tuple(
77
+ sum(first[row][k] * second[k][col] for k in range(4))
78
+ for col in range(4)
79
+ )
64
80
  for row in range(4)
65
81
  )
66
82
 
67
83
 
68
- def _rotation_to_quaternion(rotation: tuple[tuple[float, ...], ...]) -> tuple[float, float, float, float]:
84
+ def _rotation_to_quaternion(
85
+ rotation: tuple[tuple[float, ...], ...],
86
+ ) -> tuple[float, float, float, float]:
69
87
  r00, r01, r02 = rotation[0]
70
88
  r10, r11, r12 = rotation[1]
71
89
  r20, r21, r22 = rotation[2]
72
90
  trace = r00 + r11 + r22
73
91
  if trace > 0.0:
74
92
  s = sqrt(trace + 1.0) * 2.0
75
- q = ((r21 - r12) / s, (r02 - r20) / s, (r10 - r01) / s, 0.25 * s)
93
+ q = (
94
+ (r21 - r12) / s,
95
+ (r02 - r20) / s,
96
+ (r10 - r01) / s,
97
+ 0.25 * s,
98
+ )
76
99
  elif r00 > r11 and r00 > r22:
77
100
  s = sqrt(1.0 + r00 - r11 - r22) * 2.0
78
- q = (0.25 * s, (r01 + r10) / s, (r02 + r20) / s, (r21 - r12) / s)
101
+ q = (
102
+ 0.25 * s,
103
+ (r01 + r10) / s,
104
+ (r02 + r20) / s,
105
+ (r21 - r12) / s,
106
+ )
79
107
  elif r11 > r22:
80
108
  s = sqrt(1.0 + r11 - r00 - r22) * 2.0
81
- q = ((r01 + r10) / s, 0.25 * s, (r12 + r21) / s, (r02 - r20) / s)
109
+ q = (
110
+ (r01 + r10) / s,
111
+ 0.25 * s,
112
+ (r12 + r21) / s,
113
+ (r02 - r20) / s,
114
+ )
82
115
  else:
83
116
  s = sqrt(1.0 + r22 - r00 - r11) * 2.0
84
- q = ((r02 + r20) / s, (r12 + r21) / s, 0.25 * s, (r10 - r01) / s)
117
+ q = (
118
+ (r02 + r20) / s,
119
+ (r12 + r21) / s,
120
+ 0.25 * s,
121
+ (r10 - r01) / s,
122
+ )
85
123
  norm = sqrt(sum(value * value for value in q))
86
124
  normalized = tuple(value / norm for value in q)
87
- return tuple(-value for value in normalized) if normalized[3] < 0.0 else normalized
125
+ return (
126
+ tuple(-value for value in normalized)
127
+ if normalized[3] < 0.0
128
+ else normalized
129
+ )
88
130
 
89
131
 
90
132
  def ur5e_reference_fk(joints_rad: Iterable[float]) -> ReferenceToolPose:
91
133
  joints = tuple(float(value) for value in joints_rad)
92
134
  if len(joints) != 6:
93
135
  raise ValueError("ur5e_reference_fk_requires_six_joints")
136
+
137
+ # base_link_T_base: same origin, 180 deg around Z. This is the fixed transform
138
+ # documented by Universal Robots for the ROS ``base_link`` -> controller ``base``
139
+ # convention. Multiplying it before the standard DH chain expresses tool0 in
140
+ # ``base_link`` exactly like MoveIt / robot_state_publisher.
94
141
  transform: tuple[tuple[float, ...], ...] = (
95
- (1.0, 0.0, 0.0, 0.0),
96
- (0.0, 1.0, 0.0, 0.0),
142
+ (-1.0, 0.0, 0.0, 0.0),
143
+ (0.0, -1.0, 0.0, 0.0),
97
144
  (0.0, 0.0, 1.0, 0.0),
98
145
  (0.0, 0.0, 0.0, 1.0),
99
146
  )
100
- for theta, a, d, alpha in zip(joints, _UR5E_DH_A, _UR5E_DH_D, _UR5E_DH_ALPHA):
147
+ for theta, a, d, alpha in zip(
148
+ joints,
149
+ _UR5E_DH_A,
150
+ _UR5E_DH_D,
151
+ _UR5E_DH_ALPHA,
152
+ ):
101
153
  ct, st = cos(theta), sin(theta)
102
154
  ca, sa = cos(alpha), sin(alpha)
103
155
  local = (
@@ -107,17 +159,22 @@ def ur5e_reference_fk(joints_rad: Iterable[float]) -> ReferenceToolPose:
107
159
  (0.0, 0.0, 0.0, 1.0),
108
160
  )
109
161
  transform = _matmul(transform, local)
110
- rotation = tuple(tuple(transform[row][col] for col in range(3)) for row in range(3))
162
+ rotation = tuple(
163
+ tuple(transform[row][col] for col in range(3))
164
+ for row in range(3)
165
+ )
111
166
  return ReferenceToolPose(
112
167
  position_m=tuple(float(transform[row][3]) for row in range(3)),
113
168
  quaternion_xyzw=_rotation_to_quaternion(rotation),
114
169
  )
115
170
 
116
171
 
117
- def _rotate(quaternion: tuple[float, float, float, float], vector: tuple[float, float, float]):
172
+ def _rotate(
173
+ quaternion: tuple[float, float, float, float],
174
+ vector: tuple[float, float, float],
175
+ ):
118
176
  x, y, z, w = quaternion
119
177
  vx, vy, vz = vector
120
- # Optimized unit-quaternion vector rotation.
121
178
  tx = 2.0 * (y * vz - z * vy)
122
179
  ty = 2.0 * (z * vx - x * vz)
123
180
  tz = 2.0 * (x * vy - y * vx)
@@ -128,12 +185,19 @@ def _rotate(quaternion: tuple[float, float, float, float], vector: tuple[float,
128
185
  )
129
186
 
130
187
 
131
- def _translated_tool_pose(tool: ReferenceToolPose, local_offset_m: tuple[float, float, float]) -> tuple[float, float, float]:
188
+ def _translated_tool_pose(
189
+ tool: ReferenceToolPose,
190
+ local_offset_m: tuple[float, float, float],
191
+ ) -> tuple[float, float, float]:
132
192
  offset = _rotate(tool.quaternion_xyzw, local_offset_m)
133
193
  return tuple(tool.position_m[index] + offset[index] for index in range(3))
134
194
 
135
195
 
136
- def _box(dimensions_m: tuple[float, float, float], *, origin: EvidenceOrigin = EvidenceOrigin.DEFAULT) -> GeometrySpec:
196
+ def _box(
197
+ dimensions_m: tuple[float, float, float],
198
+ *,
199
+ origin: EvidenceOrigin = EvidenceOrigin.DEFAULT,
200
+ ) -> GeometrySpec:
137
201
  return GeometrySpec(
138
202
  GeometryKind.BOX,
139
203
  origin,
@@ -156,7 +220,9 @@ def ur5e_load_phase_targets() -> tuple[LoadPhaseTarget, ...]:
156
220
  )
157
221
 
158
222
 
159
- def ur5e_reference_load_twin(request: ValidatedEngineeringRequest) -> TwinSpec:
223
+ def ur5e_reference_load_twin(
224
+ request: ValidatedEngineeringRequest,
225
+ ) -> TwinSpec:
160
226
  if request.robot_key != "ur5e":
161
227
  raise ValueError("reference_load_workcell_requires_ur5e")
162
228
  if request.operation != "load":
@@ -166,13 +232,25 @@ def ur5e_reference_load_twin(request: ValidatedEngineeringRequest) -> TwinSpec:
166
232
 
167
233
  pick_tool = ur5e_reference_fk(UR5E_PICK)
168
234
  place_tool = ur5e_reference_fk(UR5E_PLACE)
169
- source_box_center = _translated_tool_pose(pick_tool, _WORKPIECE_CENTER_FROM_TOOL_LOCAL_M)
170
- destination_box_center = _translated_tool_pose(place_tool, _WORKPIECE_CENTER_FROM_TOOL_LOCAL_M)
235
+ source_box_center = _translated_tool_pose(
236
+ pick_tool,
237
+ _WORKPIECE_CENTER_FROM_TOOL_LOCAL_M,
238
+ )
239
+ destination_box_center = _translated_tool_pose(
240
+ place_tool,
241
+ _WORKPIECE_CENTER_FROM_TOOL_LOCAL_M,
242
+ )
171
243
 
172
244
  workpiece_bottom_z = source_box_center[2] - _WORKPIECE_DIMS_M[2] / 2.0
173
- source_platform_center_z = workpiece_bottom_z - _PLATFORM_DIMS_M[2] / 2.0
174
- destination_bottom_z = destination_box_center[2] - _WORKPIECE_DIMS_M[2] / 2.0
175
- destination_platform_center_z = destination_bottom_z - _PLATFORM_DIMS_M[2] / 2.0
245
+ source_platform_center_z = (
246
+ workpiece_bottom_z - _PLATFORM_DIMS_M[2] / 2.0
247
+ )
248
+ destination_bottom_z = (
249
+ destination_box_center[2] - _WORKPIECE_DIMS_M[2] / 2.0
250
+ )
251
+ destination_platform_center_z = (
252
+ destination_bottom_z - _PLATFORM_DIMS_M[2] / 2.0
253
+ )
176
254
 
177
255
  source = TwinEntity(
178
256
  request.source,
@@ -185,7 +263,10 @@ def ur5e_reference_load_twin(request: ValidatedEngineeringRequest) -> TwinSpec:
185
263
  source_ref=REFERENCE_SOURCE_REF,
186
264
  ),
187
265
  _box(_PLATFORM_DIMS_M),
188
- metadata={"reference_role": "source", "template": REFERENCE_WORKCELL_ID},
266
+ metadata={
267
+ "reference_role": "source",
268
+ "template": REFERENCE_WORKCELL_ID,
269
+ },
189
270
  )
190
271
  destination = TwinEntity(
191
272
  request.destination,
@@ -198,7 +279,10 @@ def ur5e_reference_load_twin(request: ValidatedEngineeringRequest) -> TwinSpec:
198
279
  source_ref=REFERENCE_SOURCE_REF,
199
280
  ),
200
281
  _box(_PLATFORM_DIMS_M),
201
- metadata={"reference_role": "destination", "template": REFERENCE_WORKCELL_ID},
282
+ metadata={
283
+ "reference_role": "destination",
284
+ "template": REFERENCE_WORKCELL_ID,
285
+ },
202
286
  )
203
287
  workpiece = TwinEntity(
204
288
  request.object_id,
@@ -211,14 +295,29 @@ def ur5e_reference_load_twin(request: ValidatedEngineeringRequest) -> TwinSpec:
211
295
  _box(_WORKPIECE_DIMS_M),
212
296
  physics=PhysicsSpec(
213
297
  mass_kg=EvidenceValue(
214
- float(request.payload_kg) if request.payload_kg is not None else 1.0,
215
- EvidenceOrigin.USER_DECLARED if request.payload_kg is not None else EvidenceOrigin.DEFAULT,
298
+ float(request.payload_kg)
299
+ if request.payload_kg is not None
300
+ else 1.0,
301
+ EvidenceOrigin.USER_DECLARED
302
+ if request.payload_kg is not None
303
+ else EvidenceOrigin.DEFAULT,
304
+ source_ref=REFERENCE_SOURCE_REF,
305
+ ),
306
+ friction_coefficient=EvidenceValue(
307
+ 0.6,
308
+ EvidenceOrigin.DEFAULT,
309
+ source_ref=REFERENCE_SOURCE_REF,
310
+ ),
311
+ restitution=EvidenceValue(
312
+ 0.0,
313
+ EvidenceOrigin.DEFAULT,
216
314
  source_ref=REFERENCE_SOURCE_REF,
217
315
  ),
218
- friction_coefficient=EvidenceValue(0.6, EvidenceOrigin.DEFAULT, source_ref=REFERENCE_SOURCE_REF),
219
- restitution=EvidenceValue(0.0, EvidenceOrigin.DEFAULT, source_ref=REFERENCE_SOURCE_REF),
220
316
  ),
221
- metadata={"reference_role": "workpiece", "template": REFERENCE_WORKCELL_ID},
317
+ metadata={
318
+ "reference_role": "workpiece",
319
+ "template": REFERENCE_WORKCELL_ID,
320
+ },
222
321
  )
223
322
  return TwinSpec(
224
323
  twin_id=f"TWIN-{request.request_id}-{REFERENCE_WORKCELL_ID}",
@@ -244,18 +343,31 @@ def ur5e_reference_load_twin(request: ValidatedEngineeringRequest) -> TwinSpec:
244
343
  source_ref=REFERENCE_SOURCE_REF,
245
344
  ),
246
345
  geometry=_box(_TOOL_DIMS_M, origin=EvidenceOrigin.DEFAULT),
247
- max_payload_kg=EvidenceValue(5.0, EvidenceOrigin.ROBOT_PROFILE, source_ref=REFERENCE_SOURCE_REF),
346
+ max_payload_kg=EvidenceValue(
347
+ 5.0,
348
+ EvidenceOrigin.ROBOT_PROFILE,
349
+ source_ref=REFERENCE_SOURCE_REF,
350
+ ),
248
351
  ),
249
352
  controller=ControllerModel(
250
- speed_scale_pct=EvidenceValue(30.0, EvidenceOrigin.DEFAULT, source_ref=REFERENCE_SOURCE_REF)
353
+ speed_scale_pct=EvidenceValue(
354
+ 30.0,
355
+ EvidenceOrigin.DEFAULT,
356
+ source_ref=REFERENCE_SOURCE_REF,
357
+ )
251
358
  ),
252
359
  metadata={
253
360
  "template": REFERENCE_WORKCELL_ID,
254
- "template_scope": "packaged_reference_simulation_not_customer_site_geometry",
361
+ "template_scope": (
362
+ "packaged_reference_simulation_not_customer_site_geometry"
363
+ ),
255
364
  "request_id": request.request_id,
365
+ "kinematic_reference_frame": "base_link",
256
366
  "expected_pick_tcp_position_m": list(pick_tool.position_m),
257
367
  "expected_place_tcp_position_m": list(place_tool.position_m),
258
- "workpiece_tool_offset_local_m": list(_WORKPIECE_CENTER_FROM_TOOL_LOCAL_M),
368
+ "workpiece_tool_offset_local_m": list(
369
+ _WORKPIECE_CENTER_FROM_TOOL_LOCAL_M
370
+ ),
259
371
  "physical_qualification": False,
260
372
  "site_qualification": False,
261
373
  },
@@ -267,5 +379,5 @@ def ur5e_reference_tool_binding() -> ToolCollisionBinding:
267
379
  mount_link="tool0",
268
380
  position_m=_TOOL_COLLISION_CENTER_LOCAL_M,
269
381
  quaternion_xyzw=(0.0, 0.0, 0.0, 1.0),
270
- touch_links=("tool0",),
382
+ touch_links=_REFERENCE_TOOL_TOUCH_LINKS,
271
383
  )
@@ -0,0 +1,139 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import json
5
+ from math import isfinite
6
+ import os
7
+ from time import monotonic
8
+ from typing import Any, Sequence
9
+
10
+
11
+ def _validated_payload(
12
+ names: Sequence[str],
13
+ positions: Sequence[float],
14
+ required_joint_names: Sequence[str],
15
+ ) -> tuple[dict[str, Any] | None, str]:
16
+ canonical_names = tuple(str(name) for name in names)
17
+ try:
18
+ canonical_positions = tuple(float(value) for value in positions)
19
+ except (TypeError, ValueError):
20
+ return None, "joint_state_values_invalid"
21
+
22
+ if not canonical_names or len(canonical_names) != len(canonical_positions):
23
+ return None, "joint_state_shape_invalid"
24
+ if any(not name.strip() for name in canonical_names):
25
+ return None, "joint_state_empty_name"
26
+ if len(set(canonical_names)) != len(canonical_names):
27
+ return None, "joint_state_duplicate_name"
28
+ if any(not isfinite(value) for value in canonical_positions):
29
+ return None, "joint_state_non_finite"
30
+
31
+ required = tuple(str(name).strip() for name in required_joint_names)
32
+ if any(not name for name in required) or len(set(required)) != len(required):
33
+ return None, "joint_state_required_names_invalid"
34
+ missing = tuple(sorted(set(required) - set(canonical_names)))
35
+ if missing:
36
+ return None, "joint_state_missing:" + ",".join(missing)
37
+
38
+ return (
39
+ {
40
+ "success": True,
41
+ "code": "joint_state_snapshot_verified",
42
+ "name": list(canonical_names),
43
+ "position": list(canonical_positions),
44
+ },
45
+ "joint_state_snapshot_verified",
46
+ )
47
+
48
+
49
+ def capture_joint_state(
50
+ *,
51
+ topic: str,
52
+ timeout_s: float,
53
+ required_joint_names: Sequence[str],
54
+ ) -> dict[str, Any]:
55
+ if timeout_s <= 0:
56
+ raise ValueError("joint_state_timeout_must_be_positive")
57
+ if not topic.startswith("/") or any(character.isspace() for character in topic):
58
+ raise ValueError("joint_state_topic_invalid")
59
+
60
+ import rclpy
61
+ from rclpy.qos import qos_profile_sensor_data
62
+ from sensor_msgs.msg import JointState
63
+
64
+ rclpy.init(args=None)
65
+ node = rclpy.create_node(f"devagent_joint_state_probe_{os.getpid()}")
66
+ result: dict[str, Any] | None = None
67
+ received_samples = 0
68
+ last_invalid_code = "joint_state_timeout"
69
+
70
+ def receive(message: JointState) -> None:
71
+ nonlocal result, received_samples, last_invalid_code
72
+ received_samples += 1
73
+ payload, code = _validated_payload(
74
+ message.name,
75
+ message.position,
76
+ required_joint_names,
77
+ )
78
+ if payload is not None:
79
+ result = payload
80
+ else:
81
+ last_invalid_code = code
82
+
83
+ subscription = node.create_subscription(
84
+ JointState,
85
+ topic,
86
+ receive,
87
+ qos_profile_sensor_data,
88
+ )
89
+ deadline = monotonic() + float(timeout_s)
90
+ try:
91
+ while rclpy.ok() and result is None:
92
+ remaining = deadline - monotonic()
93
+ if remaining <= 0:
94
+ break
95
+ rclpy.spin_once(node, timeout_sec=min(0.2, remaining))
96
+
97
+ if result is not None:
98
+ return {
99
+ **result,
100
+ "received_samples": received_samples,
101
+ }
102
+ return {
103
+ "success": False,
104
+ "code": last_invalid_code,
105
+ "received_samples": received_samples,
106
+ }
107
+ finally:
108
+ node.destroy_subscription(subscription)
109
+ node.destroy_node()
110
+ if rclpy.ok():
111
+ rclpy.shutdown()
112
+
113
+
114
+ def main() -> int:
115
+ parser = argparse.ArgumentParser()
116
+ parser.add_argument("--topic", default="/joint_states")
117
+ parser.add_argument("--timeout", type=float, default=8.0)
118
+ parser.add_argument("--required-joint", action="append", default=[])
119
+ args = parser.parse_args()
120
+
121
+ try:
122
+ result = capture_joint_state(
123
+ topic=str(args.topic),
124
+ timeout_s=float(args.timeout),
125
+ required_joint_names=tuple(str(value) for value in args.required_joint),
126
+ )
127
+ except Exception as exc:
128
+ result = {
129
+ "success": False,
130
+ "code": f"joint_state_probe_error:{type(exc).__name__}",
131
+ "received_samples": 0,
132
+ }
133
+
134
+ print(json.dumps(result, sort_keys=True, separators=(",", ":"), allow_nan=False))
135
+ return 0 if result.get("success") else 2
136
+
137
+
138
+ if __name__ == "__main__":
139
+ raise SystemExit(main())