devagent-physical-engine 0.12.3__tar.gz → 0.12.4__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.
- {devagent_physical_engine-0.12.3/src/devagent_physical_engine.egg-info → devagent_physical_engine-0.12.4}/PKG-INFO +4 -2
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/README.md +3 -1
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/pyproject.toml +1 -1
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/__init__.py +1 -1
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/tool_scene.py +62 -14
- devagent_physical_engine-0.12.4/src/devagent_physical_engine/ros2/tool_scene_probe.py +365 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4/src/devagent_physical_engine.egg-info}/PKG-INFO +4 -2
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine.egg-info/SOURCES.txt +1 -0
- devagent_physical_engine-0.12.4/tests/test_tool_scene_v0124.py +122 -0
- devagent_physical_engine-0.12.3/src/devagent_physical_engine/ros2/tool_scene_probe.py +0 -201
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/NOTICE +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/setup.cfg +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/agent/__init__.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/agent/compiler.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/agent/contracts.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/agent/coordinator.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/agent/critic.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/agent/evidence.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/agent/interpreter.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/agent/planner.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/agent/recovery.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/agent/routing.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/agent/runtime.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/agent/semantic.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/agent/structured.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ai_cli.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/cli.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/continuous_clearance.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/doctor.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/engineering_agent.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/engineering_request.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/execution.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/models.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/operating_envelope.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/optimization/__init__.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/optimization/benchmark.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/optimization/candidates.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/optimization/contracts.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/optimization/evaluator.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/optimization/evidence.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/optimization/experience.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/optimization/measured.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/optimization/optimizer.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/optimization/orchestrator.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/physical_campaign.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/physical_evidence.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/physical_motion.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/planning.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/preexecution_contract.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/provider_adapters/__init__.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/provider_adapters/anthropic.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/provider_adapters/common.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/provider_adapters/factory.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/provider_adapters/gemini.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/provider_adapters/openai.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/provider_qualification.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/providers.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/qualification.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/qualification_cli.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/qualification_harness.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/reference_workcell.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/robot_platform.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/robots.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/__init__.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/acceptance.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/commands.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/continuous_clearance_probe.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/doctor.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/fk_pose.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/fk_probe.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/frame_alignment.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/gazebo_cli.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/gazebo_pose.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/gazebo_world.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/joint_state_recorder.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/measured_motion.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/moveit_joint_plan_probe.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/moveit_scene.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/moveit_task_planner.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/preexecution.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/qualification.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/qualification_v10.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/qualification_v11.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/qualification_v12.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/scene_probe.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/state_validity_probe.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/task_adapter.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/task_visualization.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/tf_probe.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/trajectory.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/ur5e.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/ur5e_adapter.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/ur5e_v10_adapter.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/workpiece_follow.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/workpiece_gazebo_follower.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/workpiece_scene.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/ros2/workpiece_scene_probe.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/setup_profile.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/sim_real_qualification.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/simulation.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/simulation_platform.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/tool_collision.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/trajectory_qualification.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/twin.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/twin_builder.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/twin_materialization.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/verification.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine/workpiece_lifecycle.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine.egg-info/dependency_links.txt +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine.egg-info/entry_points.txt +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine.egg-info/requires.txt +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/src/devagent_physical_engine.egg-info/top_level.txt +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_agent_core.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_agent_semantic_hardening.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_ai_cli.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_ai_cli_v07.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_continuous_clearance_v011.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_core.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_engineering_request_v07.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_gazebo_cli_v0122.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_interpreter_profiles_v08.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_live_provider_qualification.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_measured_motion_v10.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_moveit_scene_v0123.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_natural_language_agent_v07.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_openai_strict_schema_v0113.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_optimization_v05.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_physical_campaign_v09.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_physical_evidence_v09.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_physical_motion_v09.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_planning_fallback_v012.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_preexecution_contract_v10.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_provider_and_qualification.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_qualification_cli_lifecycle_v06.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_qualification_harness_v06.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_readme_pypi.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_real_provider_adapters.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_reference_workcell_v012.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_robot_platform_v08.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_ros2_production_receipts_v011.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_ros2_simulation.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_ros_trajectory_v09.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_setup_profile.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_sim_real_qualification_v011.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_simulation_platform_v08.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_tool_collision_v011.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_trajectory_qualification_v09.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_twin_materialization_v10.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_twin_v08.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_ur5e_adapter_scopes_v09.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_ur5e_v10_adapter_scopes.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_v052_stabilization.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/tests/test_v08_catalog_mapping_regression.py +0 -0
- {devagent_physical_engine-0.12.3 → devagent_physical_engine-0.12.4}/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.
|
|
3
|
+
Version: 0.12.4
|
|
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
|
|
@@ -195,6 +195,8 @@ v0.12.2 additionally hardens Gazebo model-pose read-back across Harmonic CLI ren
|
|
|
195
195
|
|
|
196
196
|
v0.12.3 hardens MoveIt PlanningScene read-back when MoveIt serializes world collision objects in its own planning/root frame. A different observed frame is never trusted by label alone: DevAgent first proves the observed frame and canonical Twin frame are identity-equivalent through TF, then re-reads the scene and canonicalizes it back to the Twin planning frame before enforcing the exact geometry hash. Non-identity frames and geometry drift remain fail-closed.
|
|
197
197
|
|
|
198
|
+
v0.12.4 hardens attached-tool collision read-back against equivalent MoveIt `CollisionObject` pose decompositions. DevAgent now composes the object pose with the primitive pose, canonicalizes equivalent quaternion signs before hashing, and still requires the exact attachment link, object frame, shape, dimensions, operation, and effective geometry. Exact probe evidence is preserved beside the tool payload for target diagnostics; representation-only changes no longer create false mismatches, while physical geometry drift remains fail-closed.
|
|
199
|
+
|
|
198
200
|
The packaged reference workcell is **not customer-site geometry**. Arbitrary plant layouts require an imported or user-declared physical Twin with trusted poses, collision geometry, tool/TCP information, and workpiece properties. DevAgent does not invent those site facts from a natural-language prompt.
|
|
199
201
|
|
|
200
202
|
The v0.12 implementation remains evidence-gated: source/CI success does not prove the ROS target campaign has passed on a particular workstation. Use the executable qualification below before treating the task-specific path as qualified on that target.
|
|
@@ -422,7 +424,7 @@ Detailed engineering documentation lives in the repository:
|
|
|
422
424
|
|
|
423
425
|
## Project status
|
|
424
426
|
|
|
425
|
-
**v0.12.
|
|
427
|
+
**v0.12.4 — Beta software / evidence-gated task-specific physical simulation.**
|
|
426
428
|
|
|
427
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.
|
|
428
430
|
|
|
@@ -154,6 +154,8 @@ v0.12.2 additionally hardens Gazebo model-pose read-back across Harmonic CLI ren
|
|
|
154
154
|
|
|
155
155
|
v0.12.3 hardens MoveIt PlanningScene read-back when MoveIt serializes world collision objects in its own planning/root frame. A different observed frame is never trusted by label alone: DevAgent first proves the observed frame and canonical Twin frame are identity-equivalent through TF, then re-reads the scene and canonicalizes it back to the Twin planning frame before enforcing the exact geometry hash. Non-identity frames and geometry drift remain fail-closed.
|
|
156
156
|
|
|
157
|
+
v0.12.4 hardens attached-tool collision read-back against equivalent MoveIt `CollisionObject` pose decompositions. DevAgent now composes the object pose with the primitive pose, canonicalizes equivalent quaternion signs before hashing, and still requires the exact attachment link, object frame, shape, dimensions, operation, and effective geometry. Exact probe evidence is preserved beside the tool payload for target diagnostics; representation-only changes no longer create false mismatches, while physical geometry drift remains fail-closed.
|
|
158
|
+
|
|
157
159
|
The packaged reference workcell is **not customer-site geometry**. Arbitrary plant layouts require an imported or user-declared physical Twin with trusted poses, collision geometry, tool/TCP information, and workpiece properties. DevAgent does not invent those site facts from a natural-language prompt.
|
|
158
160
|
|
|
159
161
|
The v0.12 implementation remains evidence-gated: source/CI success does not prove the ROS target campaign has passed on a particular workstation. Use the executable qualification below before treating the task-specific path as qualified on that target.
|
|
@@ -381,7 +383,7 @@ Detailed engineering documentation lives in the repository:
|
|
|
381
383
|
|
|
382
384
|
## Project status
|
|
383
385
|
|
|
384
|
-
**v0.12.
|
|
386
|
+
**v0.12.4 — Beta software / evidence-gated task-specific physical simulation.**
|
|
385
387
|
|
|
386
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.
|
|
387
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.
|
|
7
|
+
version = "0.12.4"
|
|
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"
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from hashlib import sha256
|
|
4
4
|
import json
|
|
5
|
+
from math import isfinite, sqrt
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
|
|
7
8
|
from ..simulation_platform import SimulationPlatformError
|
|
@@ -13,6 +14,8 @@ from .commands import RosCommandRunner
|
|
|
13
14
|
|
|
14
15
|
def _canon_float(value: float) -> float:
|
|
15
16
|
value = float(value)
|
|
17
|
+
if not isfinite(value):
|
|
18
|
+
raise SimulationPlatformError("tool_collision_geometry_non_finite")
|
|
16
19
|
return 0.0 if abs(value) < 1e-12 else float(format(value, ".12g"))
|
|
17
20
|
|
|
18
21
|
|
|
@@ -21,6 +24,38 @@ def _hash(payload: object) -> str:
|
|
|
21
24
|
return sha256(text.encode("utf-8")).hexdigest()
|
|
22
25
|
|
|
23
26
|
|
|
27
|
+
def _canonical_quaternion(values) -> tuple[float, float, float, float]:
|
|
28
|
+
quaternion = tuple(float(value) for value in values)
|
|
29
|
+
if len(quaternion) != 4 or any(not isfinite(value) for value in quaternion):
|
|
30
|
+
raise SimulationPlatformError("tool_collision_quaternion_invalid")
|
|
31
|
+
norm = sqrt(sum(value * value for value in quaternion))
|
|
32
|
+
if norm <= 0.0:
|
|
33
|
+
raise SimulationPlatformError("tool_collision_quaternion_invalid")
|
|
34
|
+
quaternion = tuple(value / norm for value in quaternion)
|
|
35
|
+
for component in (quaternion[3], quaternion[0], quaternion[1], quaternion[2]):
|
|
36
|
+
if abs(component) <= 1e-15:
|
|
37
|
+
continue
|
|
38
|
+
if component < 0.0:
|
|
39
|
+
quaternion = tuple(-value for value in quaternion)
|
|
40
|
+
break
|
|
41
|
+
return tuple(_canon_float(value) for value in quaternion)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _canonical_tool_payload(
|
|
45
|
+
item: dict[str, object], binding: ToolCollisionBinding
|
|
46
|
+
) -> dict[str, object]:
|
|
47
|
+
return {
|
|
48
|
+
"id": item["id"],
|
|
49
|
+
"link_name": binding.mount_link,
|
|
50
|
+
"geometry_kind": item["geometry_kind"],
|
|
51
|
+
"dimensions_m": [
|
|
52
|
+
_canon_float(value) for value in item["dimensions_m"] # type: ignore[index]
|
|
53
|
+
],
|
|
54
|
+
"position_m": [_canon_float(value) for value in binding.position_m],
|
|
55
|
+
"quaternion_xyzw": list(_canonical_quaternion(binding.quaternion_xyzw)),
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
|
|
24
59
|
class MoveItToolCollisionBridge:
|
|
25
60
|
def __init__(
|
|
26
61
|
self,
|
|
@@ -36,25 +71,19 @@ class MoveItToolCollisionBridge:
|
|
|
36
71
|
self.timeout_s = float(timeout_s)
|
|
37
72
|
|
|
38
73
|
@staticmethod
|
|
39
|
-
def _geometry_payload(
|
|
74
|
+
def _geometry_payload(
|
|
75
|
+
tool: ToolSpec, binding: ToolCollisionBinding
|
|
76
|
+
) -> tuple[dict[str, object], str]:
|
|
40
77
|
if tool.geometry.kind not in {GeometryKind.BOX, GeometryKind.CYLINDER}:
|
|
41
78
|
raise SimulationPlatformError("tool_collision_geometry_not_materializable")
|
|
42
79
|
if not tool.geometry.collision_geometry:
|
|
43
80
|
raise SimulationPlatformError("tool_collision_geometry_disabled")
|
|
44
|
-
item = {
|
|
81
|
+
item: dict[str, object] = {
|
|
45
82
|
"id": f"devagent_tool_{sha256(tool.tool_id.encode('utf-8')).hexdigest()[:12]}",
|
|
46
83
|
"geometry_kind": tool.geometry.kind.value,
|
|
47
84
|
"dimensions_m": [float(value) for value in tool.geometry.dimensions_m],
|
|
48
85
|
}
|
|
49
|
-
|
|
50
|
-
"id": item["id"],
|
|
51
|
-
"link_name": binding.mount_link,
|
|
52
|
-
"geometry_kind": item["geometry_kind"],
|
|
53
|
-
"dimensions_m": [_canon_float(value) for value in item["dimensions_m"]],
|
|
54
|
-
"position_m": [_canon_float(value) for value in binding.position_m],
|
|
55
|
-
"quaternion_xyzw": [_canon_float(value) for value in binding.quaternion_xyzw],
|
|
56
|
-
}
|
|
57
|
-
return item, _hash(canonical)
|
|
86
|
+
return item, _hash(_canonical_tool_payload(item, binding))
|
|
58
87
|
|
|
59
88
|
def apply_and_verify(
|
|
60
89
|
self,
|
|
@@ -76,10 +105,17 @@ class MoveItToolCollisionBridge:
|
|
|
76
105
|
"tool_geometry_hash": geometry_hash,
|
|
77
106
|
"binding_hash": binding.fingerprint,
|
|
78
107
|
}
|
|
79
|
-
payload_path = self.work_dir /
|
|
108
|
+
payload_path = self.work_dir / (
|
|
109
|
+
f"{materialization.fingerprint}.{binding.fingerprint}.tool.json"
|
|
110
|
+
)
|
|
80
111
|
temporary = payload_path.with_suffix(payload_path.suffix + ".tmp")
|
|
81
112
|
temporary.write_text(
|
|
82
|
-
json.dumps(
|
|
113
|
+
json.dumps(
|
|
114
|
+
payload,
|
|
115
|
+
sort_keys=True,
|
|
116
|
+
separators=(",", ":"),
|
|
117
|
+
allow_nan=False,
|
|
118
|
+
),
|
|
83
119
|
encoding="utf-8",
|
|
84
120
|
)
|
|
85
121
|
temporary.replace(payload_path)
|
|
@@ -101,6 +137,16 @@ class MoveItToolCollisionBridge:
|
|
|
101
137
|
raise SimulationPlatformError("tool_scene_probe_output_invalid") from exc
|
|
102
138
|
if not isinstance(output, dict):
|
|
103
139
|
raise SimulationPlatformError("tool_scene_probe_output_invalid")
|
|
140
|
+
|
|
141
|
+
# Preserve the exact target read-back evidence even on failure. This keeps
|
|
142
|
+
# target-specific MoveIt serialization visible instead of collapsing it into
|
|
143
|
+
# a generic top-level qualification code.
|
|
144
|
+
output_path = payload_path.with_suffix(".probe.json")
|
|
145
|
+
output_path.write_text(
|
|
146
|
+
json.dumps(output, sort_keys=True, indent=2, allow_nan=False) + "\n",
|
|
147
|
+
encoding="utf-8",
|
|
148
|
+
)
|
|
149
|
+
|
|
104
150
|
receipt = ToolCollisionReceipt(
|
|
105
151
|
verified=bool(output.get("success")) and result.ok,
|
|
106
152
|
code=str(output.get("code") or "tool_scene_probe_failed"),
|
|
@@ -121,7 +167,9 @@ class MoveItToolCollisionBridge:
|
|
|
121
167
|
),
|
|
122
168
|
)
|
|
123
169
|
if not receipt.verified:
|
|
124
|
-
raise SimulationPlatformError(
|
|
170
|
+
raise SimulationPlatformError(
|
|
171
|
+
f"tool_collision_verification_failed:{receipt.code}"
|
|
172
|
+
)
|
|
125
173
|
if receipt.observed_link != binding.mount_link:
|
|
126
174
|
raise SimulationPlatformError("tool_collision_mount_link_mismatch")
|
|
127
175
|
return receipt
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
from hashlib import sha256
|
|
5
|
+
import json
|
|
6
|
+
from math import isfinite, sqrt
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _canon_float(value: float) -> float:
|
|
12
|
+
value = float(value)
|
|
13
|
+
if not isfinite(value):
|
|
14
|
+
raise ValueError("tool_pose_non_finite")
|
|
15
|
+
return 0.0 if abs(value) < 1e-12 else float(format(value, ".12g"))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _hash(payload: object) -> str:
|
|
19
|
+
text = json.dumps(payload, sort_keys=True, separators=(",", ":"), allow_nan=False)
|
|
20
|
+
return sha256(text.encode("utf-8")).hexdigest()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _qnorm(values):
|
|
24
|
+
values = tuple(float(value) for value in values)
|
|
25
|
+
if len(values) != 4 or any(not isfinite(value) for value in values):
|
|
26
|
+
raise ValueError("tool_quaternion_invalid")
|
|
27
|
+
norm = sqrt(sum(value * value for value in values))
|
|
28
|
+
if norm <= 0:
|
|
29
|
+
raise ValueError("tool_quaternion_zero")
|
|
30
|
+
return tuple(value / norm for value in values)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _qcanonical(values):
|
|
34
|
+
"""Canonicalize equivalent q/-q rotations to one deterministic representation."""
|
|
35
|
+
|
|
36
|
+
quaternion = _qnorm(values)
|
|
37
|
+
# Prefer positive w. For exact 180-degree rotations where w == 0, use the first
|
|
38
|
+
# non-zero xyz component as the deterministic sign discriminator.
|
|
39
|
+
for component in (quaternion[3], quaternion[0], quaternion[1], quaternion[2]):
|
|
40
|
+
if abs(component) <= 1e-15:
|
|
41
|
+
continue
|
|
42
|
+
if component < 0.0:
|
|
43
|
+
quaternion = tuple(-value for value in quaternion)
|
|
44
|
+
break
|
|
45
|
+
return tuple(_canon_float(value) for value in quaternion)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _qmul(first, second):
|
|
49
|
+
ax, ay, az, aw = first
|
|
50
|
+
bx, by, bz, bw = second
|
|
51
|
+
return (
|
|
52
|
+
aw * bx + ax * bw + ay * bz - az * by,
|
|
53
|
+
aw * by - ax * bz + ay * bw + az * bx,
|
|
54
|
+
aw * bz + ax * by - ay * bx + az * bw,
|
|
55
|
+
aw * bw - ax * bx - ay * by - az * bz,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _qconj(quaternion):
|
|
60
|
+
return (-quaternion[0], -quaternion[1], -quaternion[2], quaternion[3])
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _qrot(quaternion, vector):
|
|
64
|
+
quaternion = _qnorm(quaternion)
|
|
65
|
+
output = _qmul(
|
|
66
|
+
_qmul(quaternion, (vector[0], vector[1], vector[2], 0.0)),
|
|
67
|
+
_qconj(quaternion),
|
|
68
|
+
)
|
|
69
|
+
return output[:3]
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _compose(first_position, first_q, second_position, second_q):
|
|
73
|
+
"""Compose object pose with a shape pose as defined by CollisionObject.msg."""
|
|
74
|
+
|
|
75
|
+
first_q = _qnorm(first_q)
|
|
76
|
+
second_q = _qnorm(second_q)
|
|
77
|
+
rotated = _qrot(first_q, second_position)
|
|
78
|
+
position = tuple(
|
|
79
|
+
float(first_position[index]) + rotated[index] for index in range(3)
|
|
80
|
+
)
|
|
81
|
+
return position, _qcanonical(_qmul(first_q, second_q))
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _pose_from_msg(pose):
|
|
85
|
+
return (
|
|
86
|
+
(
|
|
87
|
+
float(pose.position.x),
|
|
88
|
+
float(pose.position.y),
|
|
89
|
+
float(pose.position.z),
|
|
90
|
+
),
|
|
91
|
+
(
|
|
92
|
+
float(pose.orientation.x),
|
|
93
|
+
float(pose.orientation.y),
|
|
94
|
+
float(pose.orientation.z),
|
|
95
|
+
float(pose.orientation.w),
|
|
96
|
+
),
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _pose_payload(position, quaternion) -> dict[str, list[float]]:
|
|
101
|
+
return {
|
|
102
|
+
"position_m": [_canon_float(value) for value in position],
|
|
103
|
+
"quaternion_xyzw": list(_qcanonical(quaternion)),
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _canonical_tool(
|
|
108
|
+
item: dict[str, Any], *, link_name: str, position, quaternion
|
|
109
|
+
) -> dict[str, Any]:
|
|
110
|
+
return {
|
|
111
|
+
"id": str(item["id"]),
|
|
112
|
+
"link_name": link_name,
|
|
113
|
+
"geometry_kind": str(item["geometry_kind"]),
|
|
114
|
+
"dimensions_m": [_canon_float(value) for value in item["dimensions_m"]],
|
|
115
|
+
"position_m": [_canon_float(value) for value in position],
|
|
116
|
+
"quaternion_xyzw": list(_qcanonical(quaternion)),
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _primitive(item, SolidPrimitive):
|
|
121
|
+
primitive = SolidPrimitive()
|
|
122
|
+
dimensions = [float(value) for value in item["dimensions_m"]]
|
|
123
|
+
kind = str(item["geometry_kind"])
|
|
124
|
+
if kind == "box":
|
|
125
|
+
primitive.type = SolidPrimitive.BOX
|
|
126
|
+
primitive.dimensions = dimensions
|
|
127
|
+
elif kind == "cylinder":
|
|
128
|
+
primitive.type = SolidPrimitive.CYLINDER
|
|
129
|
+
primitive.dimensions = [dimensions[1], dimensions[0]]
|
|
130
|
+
else:
|
|
131
|
+
raise ValueError(f"unsupported_tool_geometry:{kind}")
|
|
132
|
+
return primitive
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _wait_future(node, future, timeout_s: float) -> bool:
|
|
136
|
+
import rclpy
|
|
137
|
+
|
|
138
|
+
rclpy.spin_until_future_complete(node, future, timeout_sec=timeout_s)
|
|
139
|
+
return future.done()
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def apply_verify(path: Path, timeout_s: float) -> dict[str, Any]:
|
|
143
|
+
payload = json.loads(path.read_text(encoding="utf-8"))
|
|
144
|
+
item = dict(payload["tool"])
|
|
145
|
+
link_name = str(payload["mount_link"])
|
|
146
|
+
touch_links = [str(value) for value in payload.get("touch_links", [link_name])]
|
|
147
|
+
position = tuple(float(value) for value in payload["position_m"])
|
|
148
|
+
quaternion = _qcanonical(payload["quaternion_xyzw"])
|
|
149
|
+
expected_hash = str(payload["tool_geometry_hash"])
|
|
150
|
+
|
|
151
|
+
computed_expected_hash = _hash(
|
|
152
|
+
_canonical_tool(
|
|
153
|
+
item,
|
|
154
|
+
link_name=link_name,
|
|
155
|
+
position=position,
|
|
156
|
+
quaternion=quaternion,
|
|
157
|
+
)
|
|
158
|
+
)
|
|
159
|
+
if computed_expected_hash != expected_hash:
|
|
160
|
+
return {
|
|
161
|
+
"success": False,
|
|
162
|
+
"code": "tool_payload_hash_mismatch",
|
|
163
|
+
"expected_attachment_hash": expected_hash,
|
|
164
|
+
"computed_expected_attachment_hash": computed_expected_hash,
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
import rclpy
|
|
168
|
+
from geometry_msgs.msg import Pose
|
|
169
|
+
from moveit_msgs.msg import (
|
|
170
|
+
AttachedCollisionObject,
|
|
171
|
+
CollisionObject,
|
|
172
|
+
PlanningScene,
|
|
173
|
+
PlanningSceneComponents,
|
|
174
|
+
)
|
|
175
|
+
from moveit_msgs.srv import ApplyPlanningScene, GetPlanningScene
|
|
176
|
+
from shape_msgs.msg import SolidPrimitive
|
|
177
|
+
|
|
178
|
+
rclpy.init()
|
|
179
|
+
node = rclpy.create_node("devagent_tool_scene_probe")
|
|
180
|
+
apply_client = node.create_client(ApplyPlanningScene, "/apply_planning_scene")
|
|
181
|
+
get_client = node.create_client(GetPlanningScene, "/get_planning_scene")
|
|
182
|
+
try:
|
|
183
|
+
if not apply_client.wait_for_service(timeout_sec=timeout_s):
|
|
184
|
+
return {"success": False, "code": "apply_planning_scene_unavailable"}
|
|
185
|
+
if not get_client.wait_for_service(timeout_sec=timeout_s):
|
|
186
|
+
return {"success": False, "code": "get_planning_scene_unavailable"}
|
|
187
|
+
|
|
188
|
+
collision = CollisionObject()
|
|
189
|
+
collision.header.frame_id = link_name
|
|
190
|
+
collision.id = str(item["id"])
|
|
191
|
+
collision.operation = CollisionObject.ADD
|
|
192
|
+
collision.pose = Pose()
|
|
193
|
+
collision.pose.position.x = position[0]
|
|
194
|
+
collision.pose.position.y = position[1]
|
|
195
|
+
collision.pose.position.z = position[2]
|
|
196
|
+
collision.pose.orientation.x = quaternion[0]
|
|
197
|
+
collision.pose.orientation.y = quaternion[1]
|
|
198
|
+
collision.pose.orientation.z = quaternion[2]
|
|
199
|
+
collision.pose.orientation.w = quaternion[3]
|
|
200
|
+
identity = Pose()
|
|
201
|
+
identity.orientation.w = 1.0
|
|
202
|
+
collision.primitives = [_primitive(item, SolidPrimitive)]
|
|
203
|
+
collision.primitive_poses = [identity]
|
|
204
|
+
|
|
205
|
+
attached = AttachedCollisionObject()
|
|
206
|
+
attached.link_name = link_name
|
|
207
|
+
attached.touch_links = touch_links
|
|
208
|
+
attached.object = collision
|
|
209
|
+
|
|
210
|
+
scene = PlanningScene()
|
|
211
|
+
scene.is_diff = True
|
|
212
|
+
scene.robot_state.is_diff = True
|
|
213
|
+
scene.robot_state.attached_collision_objects.append(attached)
|
|
214
|
+
request = ApplyPlanningScene.Request()
|
|
215
|
+
request.scene = scene
|
|
216
|
+
future = apply_client.call_async(request)
|
|
217
|
+
if not _wait_future(node, future, timeout_s):
|
|
218
|
+
return {"success": False, "code": "tool_apply_timeout"}
|
|
219
|
+
response = future.result()
|
|
220
|
+
if response is None or not response.success:
|
|
221
|
+
return {"success": False, "code": "tool_apply_rejected"}
|
|
222
|
+
|
|
223
|
+
get_request = GetPlanningScene.Request()
|
|
224
|
+
get_request.components.components = (
|
|
225
|
+
PlanningSceneComponents.ROBOT_STATE_ATTACHED_OBJECTS
|
|
226
|
+
)
|
|
227
|
+
future = get_client.call_async(get_request)
|
|
228
|
+
if not _wait_future(node, future, timeout_s):
|
|
229
|
+
return {"success": False, "code": "tool_readback_timeout"}
|
|
230
|
+
response = future.result()
|
|
231
|
+
if response is None:
|
|
232
|
+
return {"success": False, "code": "tool_readback_empty"}
|
|
233
|
+
|
|
234
|
+
matches = [
|
|
235
|
+
value
|
|
236
|
+
for value in response.scene.robot_state.attached_collision_objects
|
|
237
|
+
if value.object.id == str(item["id"])
|
|
238
|
+
]
|
|
239
|
+
if len(matches) != 1:
|
|
240
|
+
return {
|
|
241
|
+
"success": False,
|
|
242
|
+
"code": "tool_attachment_readback_count_mismatch",
|
|
243
|
+
"observed_attachment_count": len(matches),
|
|
244
|
+
}
|
|
245
|
+
observed_attachment = matches[0]
|
|
246
|
+
if observed_attachment.link_name != link_name:
|
|
247
|
+
return {
|
|
248
|
+
"success": False,
|
|
249
|
+
"code": "tool_attachment_link_mismatch",
|
|
250
|
+
"observed_link": observed_attachment.link_name,
|
|
251
|
+
"expected_link": link_name,
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
observed = observed_attachment.object
|
|
255
|
+
observed_header_frame = str(observed.header.frame_id)
|
|
256
|
+
if observed_header_frame != link_name:
|
|
257
|
+
return {
|
|
258
|
+
"success": False,
|
|
259
|
+
"code": "tool_attachment_frame_mismatch",
|
|
260
|
+
"observed_link": observed_attachment.link_name,
|
|
261
|
+
"observed_header_frame": observed_header_frame,
|
|
262
|
+
"expected_header_frame": link_name,
|
|
263
|
+
}
|
|
264
|
+
if observed.operation != CollisionObject.ADD:
|
|
265
|
+
return {
|
|
266
|
+
"success": False,
|
|
267
|
+
"code": "tool_attachment_operation_mismatch",
|
|
268
|
+
"observed_operation": int(observed.operation),
|
|
269
|
+
}
|
|
270
|
+
if len(observed.primitives) != 1 or len(observed.primitive_poses) != 1:
|
|
271
|
+
return {
|
|
272
|
+
"success": False,
|
|
273
|
+
"code": "tool_attachment_shape_invalid",
|
|
274
|
+
"observed_primitive_count": len(observed.primitives),
|
|
275
|
+
"observed_primitive_pose_count": len(observed.primitive_poses),
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
primitive = observed.primitives[0]
|
|
279
|
+
if primitive.type == SolidPrimitive.BOX:
|
|
280
|
+
kind = "box"
|
|
281
|
+
dimensions = list(primitive.dimensions)
|
|
282
|
+
elif primitive.type == SolidPrimitive.CYLINDER:
|
|
283
|
+
kind = "cylinder"
|
|
284
|
+
dimensions = [
|
|
285
|
+
primitive.dimensions[SolidPrimitive.CYLINDER_RADIUS],
|
|
286
|
+
primitive.dimensions[SolidPrimitive.CYLINDER_HEIGHT],
|
|
287
|
+
]
|
|
288
|
+
else:
|
|
289
|
+
return {
|
|
290
|
+
"success": False,
|
|
291
|
+
"code": "tool_attachment_shape_unsupported",
|
|
292
|
+
"observed_shape_type": int(primitive.type),
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
observed_item = {
|
|
296
|
+
"id": observed.id,
|
|
297
|
+
"geometry_kind": kind,
|
|
298
|
+
"dimensions_m": dimensions,
|
|
299
|
+
}
|
|
300
|
+
object_position, object_quaternion = _pose_from_msg(observed.pose)
|
|
301
|
+
primitive_position, primitive_quaternion = _pose_from_msg(
|
|
302
|
+
observed.primitive_poses[0]
|
|
303
|
+
)
|
|
304
|
+
effective_position, effective_quaternion = _compose(
|
|
305
|
+
object_position,
|
|
306
|
+
object_quaternion,
|
|
307
|
+
primitive_position,
|
|
308
|
+
primitive_quaternion,
|
|
309
|
+
)
|
|
310
|
+
observed_hash = _hash(
|
|
311
|
+
_canonical_tool(
|
|
312
|
+
observed_item,
|
|
313
|
+
link_name=link_name,
|
|
314
|
+
position=effective_position,
|
|
315
|
+
quaternion=effective_quaternion,
|
|
316
|
+
)
|
|
317
|
+
)
|
|
318
|
+
success = observed_hash == expected_hash
|
|
319
|
+
return {
|
|
320
|
+
"success": success,
|
|
321
|
+
"code": (
|
|
322
|
+
"tool_collision_verified"
|
|
323
|
+
if success
|
|
324
|
+
else "tool_collision_hash_mismatch"
|
|
325
|
+
),
|
|
326
|
+
"expected_attachment_hash": expected_hash,
|
|
327
|
+
"observed_attachment_hash": observed_hash,
|
|
328
|
+
"observed_link": observed_attachment.link_name,
|
|
329
|
+
"observed_header_frame": observed_header_frame,
|
|
330
|
+
"observed_object_pose": _pose_payload(
|
|
331
|
+
object_position, object_quaternion
|
|
332
|
+
),
|
|
333
|
+
"observed_primitive_pose": _pose_payload(
|
|
334
|
+
primitive_position, primitive_quaternion
|
|
335
|
+
),
|
|
336
|
+
"observed_effective_pose": _pose_payload(
|
|
337
|
+
effective_position, effective_quaternion
|
|
338
|
+
),
|
|
339
|
+
}
|
|
340
|
+
finally:
|
|
341
|
+
node.destroy_node()
|
|
342
|
+
rclpy.shutdown()
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def main() -> int:
|
|
346
|
+
parser = argparse.ArgumentParser()
|
|
347
|
+
parser.add_argument("--input", required=True)
|
|
348
|
+
parser.add_argument("--timeout", type=float, default=20.0)
|
|
349
|
+
args = parser.parse_args()
|
|
350
|
+
try:
|
|
351
|
+
result = apply_verify(
|
|
352
|
+
Path(args.input).expanduser().resolve(), float(args.timeout)
|
|
353
|
+
)
|
|
354
|
+
except Exception as exc:
|
|
355
|
+
result = {
|
|
356
|
+
"success": False,
|
|
357
|
+
"code": f"tool_scene_probe_error:{type(exc).__name__}",
|
|
358
|
+
"detail": str(exc),
|
|
359
|
+
}
|
|
360
|
+
print(json.dumps(result, sort_keys=True))
|
|
361
|
+
return 0 if result.get("success") else 2
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
if __name__ == "__main__":
|
|
365
|
+
raise SystemExit(main())
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: devagent-physical-engine
|
|
3
|
-
Version: 0.12.
|
|
3
|
+
Version: 0.12.4
|
|
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
|
|
@@ -195,6 +195,8 @@ v0.12.2 additionally hardens Gazebo model-pose read-back across Harmonic CLI ren
|
|
|
195
195
|
|
|
196
196
|
v0.12.3 hardens MoveIt PlanningScene read-back when MoveIt serializes world collision objects in its own planning/root frame. A different observed frame is never trusted by label alone: DevAgent first proves the observed frame and canonical Twin frame are identity-equivalent through TF, then re-reads the scene and canonicalizes it back to the Twin planning frame before enforcing the exact geometry hash. Non-identity frames and geometry drift remain fail-closed.
|
|
197
197
|
|
|
198
|
+
v0.12.4 hardens attached-tool collision read-back against equivalent MoveIt `CollisionObject` pose decompositions. DevAgent now composes the object pose with the primitive pose, canonicalizes equivalent quaternion signs before hashing, and still requires the exact attachment link, object frame, shape, dimensions, operation, and effective geometry. Exact probe evidence is preserved beside the tool payload for target diagnostics; representation-only changes no longer create false mismatches, while physical geometry drift remains fail-closed.
|
|
199
|
+
|
|
198
200
|
The packaged reference workcell is **not customer-site geometry**. Arbitrary plant layouts require an imported or user-declared physical Twin with trusted poses, collision geometry, tool/TCP information, and workpiece properties. DevAgent does not invent those site facts from a natural-language prompt.
|
|
199
201
|
|
|
200
202
|
The v0.12 implementation remains evidence-gated: source/CI success does not prove the ROS target campaign has passed on a particular workstation. Use the executable qualification below before treating the task-specific path as qualified on that target.
|
|
@@ -422,7 +424,7 @@ Detailed engineering documentation lives in the repository:
|
|
|
422
424
|
|
|
423
425
|
## Project status
|
|
424
426
|
|
|
425
|
-
**v0.12.
|
|
427
|
+
**v0.12.4 — Beta software / evidence-gated task-specific physical simulation.**
|
|
426
428
|
|
|
427
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.
|
|
428
430
|
|
|
@@ -140,6 +140,7 @@ tests/test_setup_profile.py
|
|
|
140
140
|
tests/test_sim_real_qualification_v011.py
|
|
141
141
|
tests/test_simulation_platform_v08.py
|
|
142
142
|
tests/test_tool_collision_v011.py
|
|
143
|
+
tests/test_tool_scene_v0124.py
|
|
143
144
|
tests/test_trajectory_qualification_v09.py
|
|
144
145
|
tests/test_twin_materialization_v10.py
|
|
145
146
|
tests/test_twin_v08.py
|