diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c3870b11e..d4ee3bcda 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -75,18 +75,25 @@ jobs: PROGRAM_FOLDER: 'tests/resources/dockerursim/programs/polyscopex' - ROBOT_MODEL: 'ur7e' URSIM_VERSION: '10.13.0' - PROGRAM_FOLDER: 'tests/resources/dockerursim/programs/polyscopex' + PROGRAM_FOLDER: 'tests/resources/dockerursim/programs/polyscopex/10.13.0/ur7e' + POLYSCOPE_X_WITH_REMOTE_CONTROL: 'true' + CITADEL_DB_FOLDER: 'tests/resources/dockerursim/citadelDB' steps: - uses: actions/checkout@v7 - name: start ursim run: | - scripts/start_ursim.sh -m $ROBOT_MODEL -v $URSIM_VERSION -p $PROGRAM_FOLDER -d -f DISABLED + CITADEL_DB_ARG="" + if [ -n "$CITADEL_DB_FOLDER" ]; then + CITADEL_DB_ARG="-c $CITADEL_DB_FOLDER" + fi + scripts/start_ursim.sh -m $ROBOT_MODEL -v $URSIM_VERSION -p $PROGRAM_FOLDER $CITADEL_DB_ARG -d -f DISABLED env: DOCKER_RUN_OPTS: --network ursim_net ROBOT_MODEL: ${{matrix.env.ROBOT_MODEL}} URSIM_VERSION: ${{matrix.env.URSIM_VERSION}} PROGRAM_FOLDER: ${{matrix.env.PROGRAM_FOLDER}} + CITADEL_DB_FOLDER: ${{matrix.env.CITADEL_DB_FOLDER || ''}} - id: check_polyscopex run: | if [[ "${{matrix.env.URSIM_VERSION}}" == "10."* ]]; then @@ -103,18 +110,27 @@ jobs: - name: Extract CMake build directory run: tar -xzf build.tar.gz - name: Create folder for test artifacts - run: mkdir -p test_artifacts + run: mkdir -p test_artifacts/screenshots - name: Access PolyScope if: ${{ steps.check_polyscopex.outputs.is_polyscopex == 'true' }} run: chrome --no-sandbox --disable-settuid-sandbox --headless=new 192.168.56.101 & + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' - name: Install Python dependencies - run: sudo apt-get update && sudo apt-get install -y python3-pandas python3-lxml + run: pip install pandas lxml + - name: Install Playwright for PolyScope X screenshots + if: ${{ steps.check_polyscopex.outputs.is_polyscopex == 'true' }} + run: pip install playwright && playwright install chromium - name: Generate rtde outputs lists run: python3 tests/resources/generate_rtde_outputs.py - name: test run: cd build && ctest --output-on-failure --output-junit junit.xml env: URSIM_VERSION: ${{matrix.env.URSIM_VERSION}} + POLYSCOPE_X_TESTS_WITH_REMOTE_CONTROL: ${{matrix.env.POLYSCOPE_X_WITH_REMOTE_CONTROL || ''}} + POLYSCOPE_X_SCREENSHOT_DIR: ${{github.workspace}}/test_artifacts/screenshots - name: Upload test results to Codecov uses: codecov/codecov-action@v7 with: @@ -168,6 +184,25 @@ jobs: path: test_artifacts if-no-files-found: error retention-days: 10 + - name: Capture PolyScope X URSim logs + if: ${{ always() && steps.check_polyscopex.outputs.is_polyscopex == 'true' }} + run: docker logs ursim > test_artifacts/ursim_docker.log 2>&1 + - name: Upload PolyScope X URSim logs + uses: actions/upload-artifact@v7 + if: ${{ always() && steps.check_polyscopex.outputs.is_polyscopex == 'true' }} + with: + name: ${{matrix.env.ROBOT_MODEL}}_${{matrix.env.URSIM_VERSION}}_URSim_Logs + path: test_artifacts/ursim_docker.log + if-no-files-found: error + retention-days: 10 + - name: Upload PolyScope X screenshots + uses: actions/upload-artifact@v7 + if: ${{ failure() && steps.check_polyscopex.outputs.is_polyscopex == 'true' }} + with: + name: ${{matrix.env.ROBOT_MODEL}}_${{matrix.env.URSIM_VERSION}}_screenshots + path: test_artifacts/screenshots + if-no-files-found: ignore + retention-days: 10 - name: Upload generated scripts uses: actions/upload-artifact@v6 if: ${{ always() }} diff --git a/scripts/start_ursim.sh b/scripts/start_ursim.sh index bd9bafa56..64f1042d2 100755 --- a/scripts/start_ursim.sh +++ b/scripts/start_ursim.sh @@ -49,6 +49,10 @@ help() for available versions. Defaults to 'latest'" echo " -p Location from which programs are read / to which programs are written. If not specified, will fallback to ${PERSISTENT_BASE}/${ROBOT_SERIES}/${ROBOT_MODEL}/programs" + echo " -c Location of the citadelDB to mount into the URSim container. + Only used for PolyScope X. Enables persisting settings such as + remote-control mode across container restarts. + If not specified, no citadelDB volume is mounted." echo " -u Location from which URCaps are read / to which URCaps are written. If not specified, will fallback to ${PERSISTENT_BASE}/${ROBOT_SERIES}/urcaps" echo " -n Name of the docker container. Defaults to '$CONTAINER_NAME'" @@ -64,6 +68,7 @@ ROBOT_SERIES="" URSIM_VERSION=latest PORT_FORWARDING="" PROGRAM_STORAGE_ARG="" +CITADEL_DB_STORAGE_ARG="" URCAP_STORAGE_ARG="" DETACHED=false @@ -351,7 +356,7 @@ post_setup_polyscopex() } parse_arguments(){ - while getopts ":hm:v:p:u:i:f:n:dt" option; do + while getopts ":hm:v:p:c:u:i:f:n:dt" option; do case $option in h) # display Help help @@ -365,6 +370,9 @@ parse_arguments(){ p) # program_folder PROGRAM_STORAGE_ARG=${OPTARG} ;; + c) # citadel_db_folder + CITADEL_DB_STORAGE_ARG=${OPTARG} + ;; u) # urcaps_folder URCAP_STORAGE_ARG=${OPTARG} ;; @@ -469,12 +477,6 @@ main() { PORT_FORWARDING="" fi - DOCKER_ARGS="" - - if [ "$ROBOT_SERIES" == "polyscopex" ]; then - DOCKER_ARGS="$DOCKER_ARGS --privileged" - fi - if [ -n "$PROGRAM_STORAGE_ARG" ]; then PROGRAM_STORAGE="$PROGRAM_STORAGE_ARG" fi @@ -492,18 +494,22 @@ main() { mkdir -p "${PROGRAM_STORAGE}" PROGRAM_STORAGE=$(realpath "$PROGRAM_STORAGE") - ROBOT_MODEL_CONTROLLER_FLAG="" - verlte "10.7.0" "$URSIM_VERSION" && verlte "$URSIM_VERSION" "10.8.0" && ROBOT_MODEL_CONTROLLER_FLAG="-e ROBOT_TYPE_CONTROLLER=${ROBOT_MODEL}" - - docker_cmd="docker run --rm -d \ - --net ursim_net --ip $IP_ADDRESS \ - -v ${PROGRAM_STORAGE}:/ur/bin/backend/applications \ - -e ROBOT_TYPE=${ROBOT_MODEL} \ - $ROBOT_MODEL_CONTROLLER_FLAG \ - $PORT_FORWARDING \ - $DOCKER_ARGS \ - --name $CONTAINER_NAME \ - universalrobots/ursim_${ROBOT_SERIES}:$URSIM_VERSION" + docker_args=(docker run --rm -d + --net ursim_net --ip "$IP_ADDRESS" + --privileged + -v "${PROGRAM_STORAGE}:/ur/bin/backend/applications" + -e "ROBOT_TYPE=${ROBOT_MODEL}" + ) + + if [ -n "$CITADEL_DB_STORAGE_ARG" ]; then + mkdir -p "$CITADEL_DB_STORAGE_ARG" + CITADEL_DB_STORAGE=$(realpath "$CITADEL_DB_STORAGE_ARG") + docker_args+=(-v "${CITADEL_DB_STORAGE}:/citadelDB") + fi + + verlte "10.7.0" "$URSIM_VERSION" && verlte "$URSIM_VERSION" "10.8.0" && \ + docker_args+=(-e "ROBOT_TYPE_CONTROLLER=${ROBOT_MODEL}") + else # Create local storage for programs and URCaps mkdir -p "${URCAP_STORAGE}" @@ -538,21 +544,26 @@ main() { fi fi fi - docker_cmd="docker run --rm -d --net ursim_net --ip $IP_ADDRESS\ - -v ${URCAP_STORAGE}:/urcaps \ - -v ${PROGRAM_STORAGE}:/ursim/programs \ - -v ${POLYSCOPE_STORAGE}:/ursim/.polyscope \ - -e ROBOT_MODEL=${ROBOT_MODEL} \ - $PORT_FORWARDING \ - --name $CONTAINER_NAME \ - universalrobots/ursim_${ROBOT_SERIES}:$URSIM_VERSION" + + docker_args=(docker run --rm -d + --net ursim_net --ip "$IP_ADDRESS" + -v "${URCAP_STORAGE}:/urcaps" + -v "${PROGRAM_STORAGE}:/ursim/programs" + -v "${POLYSCOPE_STORAGE}:/ursim/.polyscope" + -e "ROBOT_MODEL=${ROBOT_MODEL}" + ) fi + # PORT_FORWARDING is a space-separated list of -p flags; word-splitting is intentional + # shellcheck disable=SC2206 + [[ -n "$PORT_FORWARDING" ]] && docker_args+=($PORT_FORWARDING) + docker_args+=(--name "$CONTAINER_NAME" "universalrobots/ursim_${ROBOT_SERIES}:$URSIM_VERSION") + if [ "$TEST_RUN" = true ]; then - echo "$docker_cmd" | tr -s ' ' + echo "${docker_args[*]}" | tr -s ' ' exit 0 fi - $docker_cmd || exit 2 + "${docker_args[@]}" || exit 2 # Stop container when interrupted TRAP_CMD=" diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 6c473e454..072139e5d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -23,7 +23,6 @@ target_link_libraries(fake_primary_server PRIVATE ur_client_library::urcl) include(GoogleTest) option(INTEGRATION_TESTS "Build the integration tests that require a running robot / URSim" OFF) -option(POLYSCOPE_X_TESTS_WITH_REMOTE_CONTROL "Run Polyscope X tests that require remote control" OFF) option(CHECK_RTDE_DOCS_RECIPE "Fetch the RTDE documentation to auto-generate a recipe containing all output fields and check that with the RTDE client. Requires python3 and pandas and an internet connection." OFF) set(INTEGRATION_TESTS_ROBOT_IP "" CACHE STRING "If set, override the default robot IP (192.168.56.101) compiled into each integration test main() by passing --robot_ip to every ctest-registered integration test.") # Build Tests @@ -36,10 +35,6 @@ if (INTEGRATION_TESTS) set(INTEGRATION_TESTS_ROBOT_IP_ARG --robot_ip ${INTEGRATION_TESTS_ROBOT_IP}) endif() - if(POLYSCOPE_X_TESTS_WITH_REMOTE_CONTROL) - add_compile_definitions(POLYSCOPE_X_TESTS_WITH_REMOTE_CONTROL=1 ) - endif() - add_executable(rtde_tests test_rtde_client.cpp fake_rtde_server.cpp) target_link_libraries(rtde_tests PRIVATE ur_client_library::urcl GTest::gmock_main) gtest_add_tests(TARGET rtde_tests diff --git a/tests/resources/dockerursim/citadelDB/000001.sst b/tests/resources/dockerursim/citadelDB/000001.sst new file mode 100644 index 000000000..98f21568a Binary files /dev/null and b/tests/resources/dockerursim/citadelDB/000001.sst differ diff --git a/tests/resources/dockerursim/citadelDB/000001.vlog b/tests/resources/dockerursim/citadelDB/000001.vlog new file mode 100644 index 000000000..ff7d4f081 Binary files /dev/null and b/tests/resources/dockerursim/citadelDB/000001.vlog differ diff --git a/tests/resources/dockerursim/citadelDB/DISCARD b/tests/resources/dockerursim/citadelDB/DISCARD new file mode 100644 index 000000000..9e0f96a2a Binary files /dev/null and b/tests/resources/dockerursim/citadelDB/DISCARD differ diff --git a/tests/resources/dockerursim/citadelDB/KEYREGISTRY b/tests/resources/dockerursim/citadelDB/KEYREGISTRY new file mode 100644 index 000000000..7aeb4b5c6 --- /dev/null +++ b/tests/resources/dockerursim/citadelDB/KEYREGISTRY @@ -0,0 +1 @@ +¼ò%¬{.AØGé„ÛƒzHello Badger \ No newline at end of file diff --git a/tests/resources/dockerursim/citadelDB/MANIFEST b/tests/resources/dockerursim/citadelDB/MANIFEST new file mode 100644 index 000000000..4683694ac Binary files /dev/null and b/tests/resources/dockerursim/citadelDB/MANIFEST differ diff --git a/tests/resources/dockerursim/programs/polyscopex/10.13.0/ur7e/.allow-empty b/tests/resources/dockerursim/programs/polyscopex/10.13.0/ur7e/.allow-empty new file mode 100755 index 000000000..e69de29bb diff --git a/tests/resources/dockerursim/programs/polyscopex/10.13.0/ur7e/.id-counters.json b/tests/resources/dockerursim/programs/polyscopex/10.13.0/ur7e/.id-counters.json new file mode 100644 index 000000000..b631f1db1 --- /dev/null +++ b/tests/resources/dockerursim/programs/polyscopex/10.13.0/ur7e/.id-counters.json @@ -0,0 +1,4 @@ +{ + "application": 0, + "program": 0 +} \ No newline at end of file diff --git a/tests/resources/dockerursim/programs/polyscopex/10.13.0/ur7e/00000000000.xd b/tests/resources/dockerursim/programs/polyscopex/10.13.0/ur7e/00000000000.xd new file mode 100644 index 000000000..f2b16e4cc Binary files /dev/null and b/tests/resources/dockerursim/programs/polyscopex/10.13.0/ur7e/00000000000.xd differ diff --git a/tests/resources/dockerursim/programs/polyscopex/10.13.0/ur7e/blobs/4.blob b/tests/resources/dockerursim/programs/polyscopex/10.13.0/ur7e/blobs/4.blob new file mode 100644 index 000000000..f92d6e768 --- /dev/null +++ b/tests/resources/dockerursim/programs/polyscopex/10.13.0/ur7e/blobs/4.blob @@ -0,0 +1 @@ +{"jsonContent":{"applicationContributions":{"universal-robots-external-control-external-control-application":{"type":"universal-robots-external-control-external-control-application","version":"1.0.0","port":50002,"robotIP":"192.168.56.1"},"ur-mounting":{"type":"ur-mounting","version":"0.0.1","mounting":{"baseAngle":{"value":0,"unit":"deg"},"tiltAngle":{"value":0,"unit":"deg"}}},"ur-frames":{"type":"ur-frames","version":"0.0.7","framesList":[{"name":"base","nameVariable":{"name":"base","reference":false,"type":"$$Variable","valueType":"frame","id":"6252a467-7b48-0790-36af-5a9122953653","_IDENTIFIER":"VariableDeclaration"},"parent":"world","pose":{"position":[0,0,0],"orientation":[0,0,0]}},{"name":"tcp","nameVariable":{"name":"tcp","reference":false,"type":"$$Variable","valueType":"frame","id":"902de4ca-4cc6-24d1-d208-a7d64dca1939","_IDENTIFIER":"VariableDeclaration"},"parent":"flange","pose":{"position":[0,0,0],"orientation":[0,0,0]}},{"name":"world","nameVariable":{"name":"world","reference":false,"type":"$$Variable","valueType":"frame","id":"1abb892b-4453-7a0b-b117-ab746b820185","_IDENTIFIER":"VariableDeclaration"},"pose":{"position":[0,0,0],"orientation":[0,0,0]}},{"name":"flange","nameVariable":{"name":"flange","reference":false,"type":"$$Variable","valueType":"frame","id":"f05af2d5-8459-68af-fae9-b1468f81a987","_IDENTIFIER":"VariableDeclaration"},"parent":"base","pose":{"position":[0,0,0],"orientation":[0,0,0]}}]},"ur-grid-pattern":{"type":"ur-grid-pattern","version":"0.0.3","grids":[{"grid":{"name":"grid","reference":false,"type":"$$Variable","valueType":"grid","id":"bb906c62-298c-7c7e-f5f7-8820f778f926","_IDENTIFIER":"VariableDeclaration"},"waypoint":{"name":"grid_iterator","reference":false,"type":"$$Variable","valueType":"waypoint","id":"6be80bf7-b0e6-2d12-c3e3-fb3503fde43e","_IDENTIFIER":"VariableDeclaration"},"corners":[null,null,null,null],"numRows":4,"numColumns":5}]},"ur-end-effector":{"type":"ur-end-effector","version":"0.0.2","endEffectors":[{"id":"3035d318-72cd-77ab-9edf-b665bb775514","name":"Robot","payload":{"weight":{"value":0,"unit":"kg"}},"cog":{"cx":{"value":0,"unit":"m"},"cy":{"value":0,"unit":"m"},"cz":{"value":0,"unit":"m"}},"inertia":{"Ixx":{"value":0,"unit":"kg*m^2"},"Iyy":{"value":0,"unit":"kg*m^2"},"Izz":{"value":0,"unit":"kg*m^2"},"Ixy":{"value":0,"unit":"kg*m^2"},"Ixz":{"value":0,"unit":"kg*m^2"},"Iyz":{"value":0,"unit":"kg*m^2"}},"useCustomInertia":false,"tcps":[{"id":"d645c72d-cb4c-148c-b939-c91def1d47c7","name":"Tool_flange","x":{"value":0,"unit":"m"},"y":{"value":0,"unit":"m"},"z":{"value":0,"unit":"m"},"rx":{"value":0,"unit":"rad"},"ry":{"value":0,"unit":"rad"},"rz":{"value":0,"unit":"rad"}}]}],"defaultTcp":{"endEffectorId":"3035d318-72cd-77ab-9edf-b665bb775514","tcpId":"d645c72d-cb4c-148c-b939-c91def1d47c7"}},"ur-motion-profiles":{"type":"ur-motion-profiles","version":"0.0.1","moveProfiles":{"joint":[{"isDefault":false,"profile":{"name":"Joint_fast","reference":false,"type":"$$Variable","valueType":"profile","id":"0142e66e-ed0c-594b-9607-10ea1d224f74","_IDENTIFIER":"VariableDeclaration"},"parameters":{"speedType":"OptiMove","speed":{"entity":{"value":1.0471975511965976,"unit":"rad/s"},"selectedType":"VALUE","value":1.0471975511965976},"acceleration":{"entity":{"value":1.3962634015954636,"unit":"rad/s^2"},"selectedType":"VALUE","value":1.3962634015954636},"optiMoveSpeed":{"entity":{"value":50,"unit":"%"},"selectedType":"VALUE","value":50},"optiMoveAcceleration":{"entity":{"value":25,"unit":"%"},"selectedType":"VALUE","value":25}}},{"isDefault":true,"profile":{"name":"Joint_slow","reference":false,"type":"$$Variable","valueType":"profile","id":"6469ab80-19ff-2edf-d4f1-696f6f1643f0","_IDENTIFIER":"VariableDeclaration"},"parameters":{"speedType":"OptiMove","speed":{"entity":{"value":1.0471975511965976,"unit":"rad/s"},"selectedType":"VALUE","value":1.0471975511965976},"acceleration":{"entity":{"value":1.3962634015954636,"unit":"rad/s^2"},"selectedType":"VALUE","value":1.3962634015954636},"optiMoveSpeed":{"entity":{"value":20,"unit":"%"},"selectedType":"VALUE","value":20},"optiMoveAcceleration":{"entity":{"value":4,"unit":"%"},"selectedType":"VALUE","value":4}}}],"linear":[{"isDefault":false,"profile":{"name":"Linear_fast","reference":false,"type":"$$Variable","valueType":"profile","id":"092041a5-d60a-b98e-8975-7b4b8c432127","_IDENTIFIER":"VariableDeclaration"},"parameters":{"speedType":"OptiMove","speed":{"entity":{"value":0.25,"unit":"m/s"},"selectedType":"VALUE","value":0.25},"acceleration":{"entity":{"value":1.2,"unit":"m/s^2"},"selectedType":"VALUE","value":1.2},"optiMoveSpeed":{"entity":{"value":50,"unit":"%"},"selectedType":"VALUE","value":50},"optiMoveAcceleration":{"entity":{"value":25,"unit":"%"},"selectedType":"VALUE","value":25}}},{"isDefault":true,"profile":{"name":"Linear_slow","reference":false,"type":"$$Variable","valueType":"profile","id":"125202dc-7124-f4dc-70e1-6025e87d825d","_IDENTIFIER":"VariableDeclaration"},"parameters":{"speedType":"OptiMove","speed":{"entity":{"value":0.25,"unit":"m/s"},"selectedType":"VALUE","value":0.25},"acceleration":{"entity":{"value":1.2,"unit":"m/s^2"},"selectedType":"VALUE","value":1.2},"optiMoveSpeed":{"entity":{"value":20,"unit":"%"},"selectedType":"VALUE","value":20},"optiMoveAcceleration":{"entity":{"value":4,"unit":"%"},"selectedType":"VALUE","value":4}}}],"process":[{"isDefault":true,"profile":{"name":"Process","reference":false,"type":"$$Variable","valueType":"profile","id":"31c939a7-423a-f3b0-1f74-5387636ccc77","_IDENTIFIER":"VariableDeclaration"},"parameters":{"speedType":"Classic","speed":{"entity":{"value":0.25,"unit":"m/s"},"selectedType":"VALUE","value":0.25},"acceleration":{"entity":{"value":1.2,"unit":"m/s^2"},"selectedType":"VALUE","value":1.2}}}]}},"ur-smart-skills":{"type":"ur-smart-skills","version":"0.0.3","preamble":"# Start of Forces\n###\n# Transforms the force and torque values along the axes of the given pose\n# @param pose pose Any valid pose, defaults to base, the x, y, and z values are ignored\n# @return array 6D force torque vector with [Fx, Fy, Fz, Mx, My, Mz] aligned to pose in N and Nm respectively\n###\ndef get_tcp_wrench_in_frame(pose = p[0.0, 0.0, 0.0, 0.0, 0.0, 0.0]):\n # we are only interested in the rotation of pose, set translations to zero\n local target_pose = pose\n target_pose[0] = 0\n target_pose[1] = 0\n target_pose[2] = 0\n # the conversion needs to happen as poses, so we need to convert back and forth a bit\n local force = get_tcp_force()\n local force_vector_as_pose = p[force[0], force[1], force[2], 0, 0, 0]\n local torque_vector_as_pose = p[force[3], force[4], force[5], 0, 0, 0]\n local transformed_force_as_pose = pose_trans(pose_inv(target_pose), force_vector_as_pose)\n local transformed_torque_as_pose = pose_trans(pose_inv(target_pose), torque_vector_as_pose)\n return [transformed_force_as_pose[0], transformed_force_as_pose[1], transformed_force_as_pose[2], transformed_torque_as_pose[0], transformed_torque_as_pose[1], transformed_torque_as_pose[2]]\nend\n###\n# See documentation for @link:get_tcp_wrench_in_frame()\n# @return forces and torques measured in TCP frame\n###\ndef get_tcp_wrench():\n return get_tcp_wrench_in_frame(get_target_tcp_pose())\nend\n###\n# Projects the measured TCP force along the axis given\n# @param axis array 3D vector\n###\ndef project_tcp_force(axis):\n local wrench = get_tcp_wrench()\n local force = [wrench[0], wrench[1], wrench[2]]\n return dot(force, axis)\nend\n# End of Forces\n# Start of Math\n# Definitions of constants\nglobal PI = acos(-1)\n###\n# Calculates the cross product between to 3D vectors\n# @param v1 array 3D vector\n# @param v2 array 3D vector\n###\ndef cross(v1, v2):\n if length(v1) != length(v2):\n popup(str_cat(\"For computing the cross product, the two vectors must have the same length. Provided lengths: \", [length(v1), length(v2)]), error=True, blocking=True)\n return -1\n end\n if length(v1) != 3:\n popup(str_cat(\"For computing the cross product, the two vectors must have length 3. Provided lengths: \", [length(v1), length(v2)]), error=True, blocking=True)\n return -1\n end\n local cross = [0.0, 0.0, 0.0]\n local i = 0\n while i < 3:\n local j = (i + 1) % 3 # The next index in a cyclic order\n local k = (i + 2) % 3 # The next next index in a cyclic order\n cross[i] = v1[j] * v2[k] - v1[k] * v2[j]\n i = i + 1\n end\n return cross\nend\n###\n# Calculates the dot product between to n-dimensional vectors\n# @param v1 array nD vector\n# @param v2 array nD vector\n###\ndef dot(v1, v2):\n if length(v1) != length(v2):\n popup(str_cat(\"For computing the dot product, the two vectors must have the same length. Provided lengths: \", [length(v1), length(v2)]), error=True, blocking=True)\n return -1\n end\n local result = 0\n local i = 0\n while i < length(v1):\n result = result + (v1[i] * v2[i])\n i = i + 1\n end\n return result\nend\n###\n# Return the larger number of a and b\n# @param a number a\n# @param b number b\n###\ndef max(a, b):\n if a > b:\n return a\n end\n return b\nend\n###\n# Find the maximum value in a list. The list must be of non-zero length and contain numbers\n# @param list array list\n###\ndef list_max(list):\n local length = get_list_length(list)\n if length == 0:\n popup(\"Getting the maximum of an empty list is impossible in list_max().\", error = True, blocking = True)\n halt\n end\n local i = 0\n local max = list[0]\n while i < length:\n if list[i] > max:\n max = list[i]\n end\n i = i + 1\n sync_at_multiple(i, 30)\n end\n return max\nend\ndef sync_at_multiple(i, n):\n local tmp = i / n\n if tmp == floor(tmp):\n sync()\n end\nend\n# End of Math\n# Start of Move Helper\nur_move_until_force_distance = 0.1\nur_move_until_force_direction = [0, 0, 1]\nur_move_until_force_velocity = 0.1\nur_move_until_force_acceleration = 0.2\ndef ur_move_tcp_direction(distance, direction, velocity, acceleration, blend_radius):\n local current_pose = get_target_tcp_pose()\n local movement = normalize(direction) * distance\n local target_pose = pose_trans(current_pose, p[movement[0], movement[1], movement[2], 0, 0, 0])\n movel(target_pose, a = 0.2, v = velocity, r = blend_radius)\nend\nthread ur_move_until_force_thread():\n ur_move_tcp_direction(ur_move_until_force_distance, ur_move_until_force_direction, ur_move_until_force_velocity, ur_move_until_force_acceleration, 0)\n popup(\"No contact detected.\", title = \"No Contact\", warning = False, error = True, blocking = False)\n halt\nend\n###\n# Moves the robot in the TCP direction specified until a contact point is reached *or*\n# the robot reaches the maximum distance allowed specified by the distance parameter.\n# @param distance number The maximum distance the robot is allowed to travel in the direction specified\n# @param direction array 3D vector determining the move direction of the TCP\n# @param velocity number Velocity of the robot\n# @param acceleration number Acceleration of the robot\n# @param stop_force number Maximum search radius\n###\ndef ur_move_until_force(distance = 0.1, direction = [0, 0, 1], velocity = 0.1, acceleration = 0.2, stop_force = 20):\n ur_move_until_force_distance = distance\n ur_move_until_force_direction = direction\n ur_move_until_force_velocity = velocity\n ur_move_until_force_acceleration = acceleration\n \n thrd = run ur_move_until_force_thread()\n while - project_tcp_force(direction) < stop_force:\n sync()\n end\n kill thrd\n local actual_pose = get_actual_tcp_pose()\n stopl(1.0)\n return actual_pose\nend\ndef ur_get_joint_speeds_before_offset(previous_q, time):\n local current_q = get_joint_positions()\n local delta_q = current_q - previous_q\n return delta_q / time\nend\ndef ur_path_move(end_q, v, rampdown=False):\n # Calculate distance to target\n local start_q = get_joint_positions()\n local delta_q = end_q - start_q\n local positive_delta_q = [norm(delta_q[0]), norm(delta_q[1]), norm(delta_q[2]), norm(delta_q[3]), norm(delta_q[4]), norm(delta_q[5])]\n # Calculate time to move based on desired velocity\n local t = list_max(positive_delta_q) / v\n servoj(end_q , 0, 0, t, lookahead_time=0.1, gain=500)\n if(rampdown):\n while(norm(ur_get_joint_speeds_before_offset(start_q, t)) > 0.0001):\n t = max(t, 0.001)\n start_q = get_joint_positions()\n servoj(end_q , 0, 0, t)\n end\n end\nend\n# End of Move Helper"},"ur-application-variables":{"type":"ur-application-variables","version":"0.0.1","variables":{}}},"sourceConfig":{"labelMap":{},"analogDomainMap":{},"presets":{}},"sourcesNodes":{"robot":{"groupId":"robot","version":"1.0.0.","sources":[{"sourceID":"ur-wired-io","signals":[{"signalID":"DI 0","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DI 1","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DI 2","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DI 3","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DI 4","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DI 5","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DI 6","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DI 7","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DO 0","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"DO 1","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"DO 2","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"DO 3","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"DO 4","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"DO 5","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"DO 6","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"DO 7","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"CI 0","direction":"IN","valueType":"BOOLEAN"},{"signalID":"CI 1","direction":"IN","valueType":"BOOLEAN"},{"signalID":"CI 2","direction":"IN","valueType":"BOOLEAN"},{"signalID":"CI 3","direction":"IN","valueType":"BOOLEAN"},{"signalID":"CI 4","direction":"IN","valueType":"BOOLEAN"},{"signalID":"CI 5","direction":"IN","valueType":"BOOLEAN"},{"signalID":"CI 6","direction":"IN","valueType":"BOOLEAN"},{"signalID":"CI 7","direction":"IN","valueType":"BOOLEAN"},{"signalID":"CO 0","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"CO 1","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"CO 2","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"CO 3","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"CO 4","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"CO 5","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"CO 6","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"CO 7","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"AI 0","direction":"IN","valueType":"FLOAT"},{"signalID":"AI 1","direction":"IN","valueType":"FLOAT"},{"signalID":"AO 0","direction":"OUT","valueType":"FLOAT"},{"signalID":"AO 1","direction":"OUT","valueType":"FLOAT"}],"webSocketURL":"/sources/wired-io"},{"sourceID":"ur-tool-io","signals":[{"signalID":"DI 0","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DI 1","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DO 0","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"DO 1","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"AI 0","direction":"IN","valueType":"FLOAT"},{"signalID":"AI 1","direction":"IN","valueType":"FLOAT"}],"webSocketURL":"/sources/tool-io"}],"isDynamic":false},"ur-modbus":{"groupId":"ur-modbus","isDynamic":true,"version":"1.0.0","sources":[]},"ur-robot-io":{"type":"ur-robot-io","groupId":"ur-robot-io","isDynamic":false,"version":"1.0.2","sources":[{"sourceID":"ur-robot-wired-io","name":"Wired I/O","signals":[{"direction":"IN","signalID":"DI 0","valueType":"BOOLEAN"},{"direction":"IN","signalID":"DI 1","valueType":"BOOLEAN"},{"direction":"IN","signalID":"DI 2","valueType":"BOOLEAN"},{"direction":"IN","signalID":"DI 3","valueType":"BOOLEAN"},{"direction":"IN","signalID":"DI 4","valueType":"BOOLEAN"},{"direction":"IN","signalID":"DI 5","valueType":"BOOLEAN"},{"direction":"IN","signalID":"DI 6","valueType":"BOOLEAN"},{"direction":"IN","signalID":"DI 7","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"DO 0","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"DO 1","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"DO 2","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"DO 3","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"DO 4","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"DO 5","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"DO 6","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"DO 7","valueType":"BOOLEAN"},{"direction":"IN","signalID":"CI 0","valueType":"BOOLEAN"},{"direction":"IN","signalID":"CI 1","valueType":"BOOLEAN"},{"direction":"IN","signalID":"CI 2","valueType":"BOOLEAN"},{"direction":"IN","signalID":"CI 3","valueType":"BOOLEAN"},{"direction":"IN","signalID":"CI 4","valueType":"BOOLEAN"},{"direction":"IN","signalID":"CI 5","valueType":"BOOLEAN"},{"direction":"IN","signalID":"CI 6","valueType":"BOOLEAN"},{"direction":"IN","signalID":"CI 7","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"CO 0","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"CO 1","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"CO 2","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"CO 3","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"CO 4","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"CO 5","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"CO 6","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"CO 7","valueType":"BOOLEAN"},{"direction":"IN","signalID":"AI 0","valueType":"FLOAT"},{"direction":"IN","signalID":"AI 1","valueType":"FLOAT"},{"direction":"OUT","signalID":"AO 0","valueType":"FLOAT"},{"direction":"OUT","signalID":"AO 1","valueType":"FLOAT"}]},{"sourceID":"ur-robot-tool-io","name":"Tool I/O","signals":[{"direction":"IN","signalID":"DI 0","valueType":"BOOLEAN"},{"direction":"IN","signalID":"DI 1","valueType":"BOOLEAN"},{"direction":"IN","signalID":"AI 0","valueType":"FLOAT"},{"direction":"IN","signalID":"AI 1","valueType":"FLOAT"},{"direction":"OUT","signalID":"DO 0","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"DO 1","valueType":"BOOLEAN"}]}],"parameters":{"sourceConfig":{"labelMap":{},"analogDomainMap":{},"presets":{},"toolOutput":{"dualPinPower":false,"voltage":{"value":0,"unit":"V"},"powerOutput":{"DO 0":1,"DO 1":1}}},"migrateSourceConfigDone":true}}},"safety":{"settings":{"io":{"automaticModeSafeguardResetInput":{"name":"automaticModeSafeguardResetInput","valueA":255,"valueB":255},"automaticModeSafeguardStopInput":{"name":"automaticModeSafeguardStopInput","valueA":255,"valueB":255},"emergencyStopInput":{"name":"emergencyStopInput","valueA":255,"valueB":255},"notReducedModeOutput":{"name":"notReducedModeOutput","ossdEnabled":false,"valueA":255,"valueB":255},"operationalModeInput":{"name":"operationalModeInput","valueA":255,"valueB":255},"reducedModeInput":{"name":"reducedModeInput","valueA":255,"valueB":255},"reducedModeOutput":{"name":"reducedModeOutput","ossdEnabled":false,"valueA":255,"valueB":255},"robotMovingOutput":{"name":"robotMovingOutput","ossdEnabled":false,"valueA":255,"valueB":255},"robotNotStoppingOutput":{"name":"robotNotStoppingOutput","ossdEnabled":false,"valueA":255,"valueB":255},"safeHomeOutput":{"name":"safeHomeOutput","ossdEnabled":false,"valueA":255,"valueB":255},"safeguardResetInput":{"name":"safeguardResetInput","valueA":0,"valueB":1},"systemEmergencyStoppedOutput":{"name":"systemEmergencyStoppedOutput","ossdEnabled":false,"valueA":255,"valueB":255},"threePositionSwitchInput":{"name":"threePositionSwitchInput","valueA":255,"valueB":255},"freedriveEnabledInput":{"name":"freedriveEnabledInput","valueA":255,"valueB":255},"threePositionEnablingStopOutput":{"name":"threePositionEnablingStopOutput","ossdEnabled":false,"valueA":255,"valueB":255},"notThreePositionEnablingStopOutput":{"name":"notThreePositionEnablingStopOutput","ossdEnabled":false,"valueA":255,"valueB":255}},"major":5,"minor":14,"normalJointPositions":{"base":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"elbow":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"shoulder":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"wrist1":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"wrist2":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"wrist3":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false}},"normalJointSpeeds":{"base":3.3415926,"shoulder":3.3415926,"elbow":3.3415926,"wrist1":3.3415926,"wrist2":3.3415926,"wrist3":3.3415926},"normalRobotLimits":{"elbowForce":150,"elbowSpeed":1.5,"momentum":25,"power":300,"stoppingDistance":0.5,"stoppingTime":0.4,"toolForce":150,"toolSpeed":1.5},"reducedJointPositions":{"base":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"elbow":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"shoulder":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"wrist1":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"wrist2":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"wrist3":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false}},"reducedJointSpeeds":{"base":3.3415926,"shoulder":3.3415926,"elbow":3.3415926,"wrist1":3.3415926,"wrist2":3.3415926,"wrist3":3.3415926},"reducedRobotLimits":{"elbowForce":120,"elbowSpeed":0.75,"momentum":10,"power":200,"stoppingDistance":0.3,"stoppingTime":0.3,"toolForce":120,"toolSpeed":0.75},"safetyHardware":{"injectionMoldingMachineInterface":"NONE","teachPendant":"NORMAL"},"safetyPlanes":{"planes":[{"id":"fa9294a4-967c-0579-2598-bbad56efc589","name":"UNDEFINED","safetyPlane":{"normalModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModeTriggerPlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true},"tilt":0,"offset":0,"rotation":0,"restriction":"disabled"},{"id":"215f99a1-821b-78ec-6ca8-ebf9af0305f9","name":"UNDEFINED","safetyPlane":{"normalModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModeTriggerPlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true},"tilt":0,"offset":0,"rotation":0,"restriction":"disabled"},{"id":"54b27eb2-3f22-d0d6-4392-1bca84eaba07","name":"UNDEFINED","safetyPlane":{"normalModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModeTriggerPlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true},"tilt":0,"offset":0,"rotation":0,"restriction":"disabled"},{"id":"c56f70d3-b7d5-5e11-efc2-cd03174894ef","name":"UNDEFINED","safetyPlane":{"normalModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModeTriggerPlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true},"tilt":0,"offset":0,"rotation":0,"restriction":"disabled"},{"id":"24b0b581-f988-5112-2298-db6ec78b8697","name":"UNDEFINED","safetyPlane":{"normalModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModeTriggerPlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true},"tilt":0,"offset":0,"rotation":0,"restriction":"disabled"},{"id":"0f8b6df0-cb79-53b4-b4e7-87e506d0b74a","name":"UNDEFINED","safetyPlane":{"normalModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModeTriggerPlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true},"tilt":0,"offset":0,"rotation":0,"restriction":"disabled"},{"id":"31eeb60c-4d58-13be-cfc4-514e8c663223","name":"UNDEFINED","safetyPlane":{"normalModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModeTriggerPlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true},"tilt":0,"offset":0,"rotation":0,"restriction":"disabled"},{"id":"f80f48d0-f136-fe07-0df6-2155d1bd261c","name":"UNDEFINED","safetyPlane":{"normalModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModeTriggerPlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true},"tilt":0,"offset":0,"rotation":0,"restriction":"disabled"}],"ioSafetyPlanes":[{"id":"eabab9c5-2d28-c06f-3d80-2acac453bb2c","name":"UNDEFINED","ioSafetyPlane":{"triggerOutput":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"triggerSafeguard":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true,"inputConfiguration":{"name":"UNDEFINED","valueA":255,"valueB":255},"outputConfiguration":{"name":"UNDEFINED","ossdEnabled":false,"valueA":255,"valueB":255}},"tilt":0,"offset":0,"rotation":0,"trigger":"disabled"},{"id":"fbe1a183-0d6d-5ffb-db55-0702ac3586d4","name":"UNDEFINED","ioSafetyPlane":{"triggerOutput":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"triggerSafeguard":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true,"inputConfiguration":{"name":"UNDEFINED","valueA":255,"valueB":255},"outputConfiguration":{"name":"UNDEFINED","ossdEnabled":false,"valueA":255,"valueB":255}},"tilt":0,"offset":0,"rotation":0,"trigger":"disabled"},{"id":"bebca814-b309-296e-3b85-f11afbbd7a34","name":"UNDEFINED","ioSafetyPlane":{"triggerOutput":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"triggerSafeguard":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true,"inputConfiguration":{"name":"UNDEFINED","valueA":255,"valueB":255},"outputConfiguration":{"name":"UNDEFINED","ossdEnabled":false,"valueA":255,"valueB":255}},"tilt":0,"offset":0,"rotation":0,"trigger":"disabled"},{"id":"f5585223-652e-a47b-ee33-1ea2fbc16f6c","name":"UNDEFINED","ioSafetyPlane":{"triggerOutput":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"triggerSafeguard":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true,"inputConfiguration":{"name":"UNDEFINED","valueA":255,"valueB":255},"outputConfiguration":{"name":"UNDEFINED","ossdEnabled":false,"valueA":255,"valueB":255}},"tilt":0,"offset":0,"rotation":0,"trigger":"disabled"},{"id":"f21f0288-3694-1561-5529-15653ad65d6d","name":"UNDEFINED","ioSafetyPlane":{"triggerOutput":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"triggerSafeguard":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true,"inputConfiguration":{"name":"UNDEFINED","valueA":255,"valueB":255},"outputConfiguration":{"name":"UNDEFINED","ossdEnabled":false,"valueA":255,"valueB":255}},"tilt":0,"offset":0,"rotation":0,"trigger":"disabled"},{"id":"354479b5-9e7e-e940-1485-97c1ccf4c80b","name":"UNDEFINED","ioSafetyPlane":{"triggerOutput":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"triggerSafeguard":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true,"inputConfiguration":{"name":"UNDEFINED","valueA":255,"valueB":255},"outputConfiguration":{"name":"UNDEFINED","ossdEnabled":false,"valueA":255,"valueB":255}},"tilt":0,"offset":0,"rotation":0,"trigger":"disabled"},{"id":"5491a1c6-1740-6878-0fd5-45e1fdb3ed9a","name":"UNDEFINED","ioSafetyPlane":{"triggerOutput":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"triggerSafeguard":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true,"inputConfiguration":{"name":"UNDEFINED","valueA":255,"valueB":255},"outputConfiguration":{"name":"UNDEFINED","ossdEnabled":false,"valueA":255,"valueB":255}},"tilt":0,"offset":0,"rotation":0,"trigger":"disabled"},{"id":"85ceb891-c975-13c5-ca8f-5b2d051225d0","name":"UNDEFINED","ioSafetyPlane":{"triggerOutput":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"triggerSafeguard":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true,"inputConfiguration":{"name":"UNDEFINED","valueA":255,"valueB":255},"outputConfiguration":{"name":"UNDEFINED","ossdEnabled":false,"valueA":255,"valueB":255}},"tilt":0,"offset":0,"rotation":0,"trigger":"disabled"}]},"safetySafeHome":{"base":-1,"elbow":-1,"shoulder":-1,"wrist1":-1,"wrist2":-1,"wrist3":-1,"enabled":false},"safetyAPIParameters":{"numberOfClients":0,"clients":[]},"safetyFieldbusses":{"enablePROFIsafe":false,"sourceAddressPROFIsafe":0,"destAddressPROFIsafe":0,"modeControlPROFIsafe":false},"threePosition":{"allowManualHighSpeed":true,"useTeachPendantAs3PE":false},"toolDirection":{"limitDeviation":6.2831855,"limitDirection":{"x":0,"y":0,"z":1},"limitRestriction":"DISABLED","tcp":{"id":"toolFlangeTcpId","name":"Tool_flange"},"toolPan":0,"toolTilt":0,"limitTilt":0,"limitRotation":0},"toolPositions":{"toolPositions":[{"name":"Tool Flange","center":{"x":0,"y":0,"z":0},"radius":0,"definition":2},{"name":"UNDEFINED","center":{"x":0,"y":0,"z":0},"radius":0,"definition":0},{"name":"UNDEFINED","center":{"x":0,"y":0,"z":0},"radius":0,"definition":0}]},"normalWristClamp":{"enableWristClampPosition":"LIMIT_ENABLED","enableWristClampTorque":"LIMIT_ENABLED"},"reducedWristClamp":{"enableWristClampPosition":"LIMIT_ENABLED","enableWristClampTorque":"LIMIT_ENABLED"}},"crc":"2850523693","confirmed":true},"operatorScreens":[{"type":"ur-operator-screen-default","version":"0.0.2","parameters":{"status":[],"configuration":[]}}],"sidebarItems":[{"type":"ur-global-variables","version":"1.0.0","disabled":{"master":false,"automaticMode":false,"remoteMode":true}},{"type":"ur-log-messages-sidebar","version":"0.0.1","disabled":{"master":false,"automaticMode":false,"remoteMode":true}}],"activeOperatorScreen":"ur-operator-screen-default","smartSkills":[{"name":"Align to Plane","enabled":true,"type":"ur-align-to-plane","parameters":{"radius":0.05,"push_force":20,"n_plane_points":3,"max_distance":0.25,"velocity_slow":0.001,"velocity_search":0.035,"velocity_move":0.1,"acceleration":0.1}},{"name":"Align Z to Nearest Axis","enabled":true,"type":"ur-align-z-to-nearest-axis"},{"name":"Center","enabled":true,"type":"ur-center","parameters":{"push_force":10,"velocity_move":0.05,"acc_move":0.2,"max_radius_search":0.05,"num_fingers":3}},{"name":"Freedrive","enabled":true,"type":"ur-freedrive","version":"1.0.0","recordingFrequency":50,"recordingSignals":["timestamp","target_q","actual_TCP_pose","tcp_offset"]},{"name":"Move into Contact","enabled":true,"type":"ur-move-into-contact","parameters":{"force":10,"velocity":0.05,"acceleration":0.2,"max_distance":0.25,"retract":0}},{"name":"Retract","enabled":true,"type":"ur-retract","parameters":{"distance":-0.1,"acceleration":0.4,"velocity":0.1}},{"name":"Put into Box","enabled":false,"type":"ur-put-in-box","version":"1.0.0"},{"name":"Custom","enabled":false,"type":"ur-custom-smart-skill","parameters":{"includePreamble":true,"includeModules":false},"version":"1.0.0"},{"name":"Home","enabled":true,"type":"ur-position","version":"1.1.2","parameters":{"actualWaypoint":{"frame":"base","pose":{"position":[-1.8246917738038495E-9,-0.2329000001676105,1.0793999999522315],"orientation":[3.987257497300885E-9,2.2214414675120993,-2.221441467056474]},"qNear":{"base":0,"shoulder":-1.5707963249999999,"elbow":0,"wrist1":-1.5707963249999999,"wrist2":0,"wrist3":0}},"variable":{"name":"Home","reference":false,"type":"$$Variable","valueType":"waypoint","id":"1a7b4663-70cf-4964-6c73-a28c5664b8a2","_IDENTIFIER":"VariableDeclaration"}}}]},"script":{"script":"set_safety_mode_transition_hardness(1)\nreset_world_model()\nset_input_actions_to_default()\nset_analog_outputdomain(0,0)\nset_analog_outputdomain(1,0)\nset_standard_analog_input_domain(0,0)\nset_standard_analog_input_domain(1,0)\nset_tool_output_mode(0)\nset_tool_voltage(0)\nset_tool_digital_output_mode(0,1)\nset_tool_digital_output_mode(1,1)\nset_tool_analog_input_domain(0,0)\nset_tool_analog_input_domain(1,0)\nset_gravity([0, 0, 9.82])\nlocal existingBaseParent = get_frame_parent(\"base\")\nlocal basePose = get_pose(\"base\", existingBaseParent)\nbasePose[3] = 0\nbasePose[4] = 0\nbasePose[5] = 0\nmove_frame(\"base\", basePose, existingBaseParent)\nglobal base = \"base\"\nglobal tcp = \"tcp\"\nglobal world = \"world\"\nglobal flange = \"flange\"\nset_target_payload(0, [0, 0, 0], [0, 0, 0, 0, 0, 0])\nset_tcp(p[0, 0, 0, 0, 0, 0], \"Tool_flange\")\n# Start of Forces\n###\n# Transforms the force and torque values along the axes of the given pose\n# @param pose pose Any valid pose, defaults to base, the x, y, and z values are ignored\n# @return array 6D force torque vector with [Fx, Fy, Fz, Mx, My, Mz] aligned to pose in N and Nm respectively\n###\ndef get_tcp_wrench_in_frame(pose = p[0.0, 0.0, 0.0, 0.0, 0.0, 0.0]):\n # we are only interested in the rotation of pose, set translations to zero\n local target_pose = pose\n target_pose[0] = 0\n target_pose[1] = 0\n target_pose[2] = 0\n # the conversion needs to happen as poses, so we need to convert back and forth a bit\n local force = get_tcp_force()\n local force_vector_as_pose = p[force[0], force[1], force[2], 0, 0, 0]\n local torque_vector_as_pose = p[force[3], force[4], force[5], 0, 0, 0]\n local transformed_force_as_pose = pose_trans(pose_inv(target_pose), force_vector_as_pose)\n local transformed_torque_as_pose = pose_trans(pose_inv(target_pose), torque_vector_as_pose)\n return [transformed_force_as_pose[0], transformed_force_as_pose[1], transformed_force_as_pose[2], transformed_torque_as_pose[0], transformed_torque_as_pose[1], transformed_torque_as_pose[2]]\nend\n###\n# See documentation for @link:get_tcp_wrench_in_frame()\n# @return forces and torques measured in TCP frame\n###\ndef get_tcp_wrench():\n return get_tcp_wrench_in_frame(get_target_tcp_pose())\nend\n###\n# Projects the measured TCP force along the axis given\n# @param axis array 3D vector\n###\ndef project_tcp_force(axis):\n local wrench = get_tcp_wrench()\n local force = [wrench[0], wrench[1], wrench[2]]\n return dot(force, axis)\nend\n# End of Forces\n# Start of Math\n# Definitions of constants\nglobal PI = acos(-1)\n###\n# Calculates the cross product between to 3D vectors\n# @param v1 array 3D vector\n# @param v2 array 3D vector\n###\ndef cross(v1, v2):\n if length(v1) != length(v2):\n popup(str_cat(\"For computing the cross product, the two vectors must have the same length. Provided lengths: \", [length(v1), length(v2)]), error=True, blocking=True)\n return -1\n end\n if length(v1) != 3:\n popup(str_cat(\"For computing the cross product, the two vectors must have length 3. Provided lengths: \", [length(v1), length(v2)]), error=True, blocking=True)\n return -1\n end\n local cross = [0.0, 0.0, 0.0]\n local i = 0\n while i < 3:\n local j = (i + 1) % 3 # The next index in a cyclic order\n local k = (i + 2) % 3 # The next next index in a cyclic order\n cross[i] = v1[j] * v2[k] - v1[k] * v2[j]\n i = i + 1\n end\n return cross\nend\n###\n# Calculates the dot product between to n-dimensional vectors\n# @param v1 array nD vector\n# @param v2 array nD vector\n###\ndef dot(v1, v2):\n if length(v1) != length(v2):\n popup(str_cat(\"For computing the dot product, the two vectors must have the same length. Provided lengths: \", [length(v1), length(v2)]), error=True, blocking=True)\n return -1\n end\n local result = 0\n local i = 0\n while i < length(v1):\n result = result + (v1[i] * v2[i])\n i = i + 1\n end\n return result\nend\n###\n# Return the larger number of a and b\n# @param a number a\n# @param b number b\n###\ndef max(a, b):\n if a > b:\n return a\n end\n return b\nend\n###\n# Find the maximum value in a list. The list must be of non-zero length and contain numbers\n# @param list array list\n###\ndef list_max(list):\n local length = get_list_length(list)\n if length == 0:\n popup(\"Getting the maximum of an empty list is impossible in list_max().\", error = True, blocking = True)\n halt\n end\n local i = 0\n local max = list[0]\n while i < length:\n if list[i] > max:\n max = list[i]\n end\n i = i + 1\n sync_at_multiple(i, 30)\n end\n return max\nend\ndef sync_at_multiple(i, n):\n local tmp = i / n\n if tmp == floor(tmp):\n sync()\n end\nend\n# End of Math\n# Start of Move Helper\nur_move_until_force_distance = 0.1\nur_move_until_force_direction = [0, 0, 1]\nur_move_until_force_velocity = 0.1\nur_move_until_force_acceleration = 0.2\ndef ur_move_tcp_direction(distance, direction, velocity, acceleration, blend_radius):\n local current_pose = get_target_tcp_pose()\n local movement = normalize(direction) * distance\n local target_pose = pose_trans(current_pose, p[movement[0], movement[1], movement[2], 0, 0, 0])\n movel(target_pose, a = 0.2, v = velocity, r = blend_radius)\nend\nthread ur_move_until_force_thread():\n ur_move_tcp_direction(ur_move_until_force_distance, ur_move_until_force_direction, ur_move_until_force_velocity, ur_move_until_force_acceleration, 0)\n popup(\"No contact detected.\", title = \"No Contact\", warning = False, error = True, blocking = False)\n halt\nend\n###\n# Moves the robot in the TCP direction specified until a contact point is reached *or*\n# the robot reaches the maximum distance allowed specified by the distance parameter.\n# @param distance number The maximum distance the robot is allowed to travel in the direction specified\n# @param direction array 3D vector determining the move direction of the TCP\n# @param velocity number Velocity of the robot\n# @param acceleration number Acceleration of the robot\n# @param stop_force number Maximum search radius\n###\ndef ur_move_until_force(distance = 0.1, direction = [0, 0, 1], velocity = 0.1, acceleration = 0.2, stop_force = 20):\n ur_move_until_force_distance = distance\n ur_move_until_force_direction = direction\n ur_move_until_force_velocity = velocity\n ur_move_until_force_acceleration = acceleration\n \n thrd = run ur_move_until_force_thread()\n while - project_tcp_force(direction) < stop_force:\n sync()\n end\n kill thrd\n local actual_pose = get_actual_tcp_pose()\n stopl(1.0)\n return actual_pose\nend\ndef ur_get_joint_speeds_before_offset(previous_q, time):\n local current_q = get_joint_positions()\n local delta_q = current_q - previous_q\n return delta_q / time\nend\ndef ur_path_move(end_q, v, rampdown=False):\n # Calculate distance to target\n local start_q = get_joint_positions()\n local delta_q = end_q - start_q\n local positive_delta_q = [norm(delta_q[0]), norm(delta_q[1]), norm(delta_q[2]), norm(delta_q[3]), norm(delta_q[4]), norm(delta_q[5])]\n # Calculate time to move based on desired velocity\n local t = list_max(positive_delta_q) / v\n servoj(end_q , 0, 0, t, lookahead_time=0.1, gain=500)\n if(rampdown):\n while(norm(ur_get_joint_speeds_before_offset(start_q, t)) > 0.0001):\n t = max(t, 0.001)\n start_q = get_joint_positions()\n servoj(end_q , 0, 0, t)\n end\n end\nend\n# End of Move Helper\n# Waypoint variable for Home smart skill\nglobal Home = struct(p=p[-1.8246917738038495e-9, -0.2329000001676105, 1.0793999999522315, 3.987257497300885e-9, 2.2214414675120993, -2.221441467056474], frame=\"base\", q=[0, -1.5707963249999999, 0, -1.5707963249999999, 0, 0])\n# Start of Align to Plane\n###\n# Align to plane will touch up a plane by moving the robot into contact with the table or part in several locations to determine its orientation. Afterwards the robot will orient its tool to the plane.\n# @param radius number Radius [m] of the circle within the plane will be touched up\n# @param push_force number How hard to robot pushed against the plane\n# @param n_plane_points number Number of points that the robot uses to compute the plane\n# @param max_distance number Maximum distance that the robot searches\n# @param velocity_slow number Velocity when pressing downwards\n# @param velocity_search number Velocity used when approaching the touch up point\n# @param velocity_move number Velocity used in freespace\n# @param acceleration number Acceleration of the robot\n# @param direction array 3D vector determining the direction of the TCP for touching up the plane\n###\ndef ur_align_to_plane(radius = 0.05, push_force = 20, n_plane_points = 3, max_distance = 0.25, velocity_slow = 0.001, velocity_search = 0.035, velocity_move = 0.10, acceleration = 0.1, direction = [0, 0, 1]):\n local angle = 2 * PI / n_plane_points\n local start_pos = get_target_tcp_pose()\n local retract_distance = -0.015\n ur_move_tcp_direction(retract_distance, direction, velocity_move, acceleration, 0)\n sleep(0.25)\n zero_ftsensor()\n local cnt = 0\n local t_base_target = get_target_tcp_pose()\n local mean_point = [0.0, 0.0, 0.0]\n local A = [[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]]\n local b = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]\n while cnt < n_plane_points:\n local new_pos = pose_trans(t_base_target, p[cos(angle * cnt) * radius, sin(angle * cnt) * radius, 0.0, 0.0, 0.0, 0.0])\n local blend_radius = norm(point_dist(get_actual_tcp_pose(), new_pos))/5\n movel(new_pos, a = acceleration, v = velocity_move, r = blend_radius)\n ur_move_until_force(max_distance + norm(retract_distance), direction, velocity_search, acceleration, push_force)\n local movement = normalize(direction * -1) * 0.0005\n local target_pose = pose_trans(get_actual_tcp_pose(), p[movement[0], movement[1], movement[2], 0, 0, 0])\n movel(target_pose)\n sleep(0.2)\n ur_move_until_force(max_distance + norm(retract_distance), direction, velocity_slow, acceleration, push_force)\n sleep(0.2)\n while (not is_steady()):\n sync()\n end\n local poked_point = get_target_tcp_pose()\n poked_point = pose_trans(inv(t_base_target), poked_point)\n A[cnt, 0] = poked_point[0]\n A[cnt, 1] = poked_point[1]\n A[cnt, 2] = 1.0\n b[cnt] = poked_point[2]\n mean_point = mean_point + [poked_point[0], poked_point[1], poked_point[2]]\n movel(new_pos, a = 0.2, v = velocity_move, r = blend_radius)\n cnt = cnt + 1\n end\n mean_point = mean_point / n_plane_points\n cnt = 0\n while cnt < n_plane_points:\n local cntj = 0\n while cntj < 2:\n A[cnt, cntj] = A[cnt, cntj] - mean_point[cntj]\n cntj = cntj + 1\n end\n b[cnt] = b[cnt] - mean_point[2]\n cnt = cnt + 1\n end\n local x1 = inv(transpose(A) * A) * transpose(A) * b\n local x = normalize([x1[0], x1[1], -1])\n local d = dot(mean_point, x)\n local dval = dot(direction, x)\n if dval < 0:\n x = -x\n dval = -dval\n end\n local eaa = [0.0, 0.0, 0.0]\n local EPSILON = 1e-10\n if norm(dval - 1) < EPSILON:\n # if the projection is close to 1 then the angle between the vectors are almost 0 and we cannot\n # reliably determine the perpendicular axis.\n # A good approximation is therefore just to set the EAA equal to 0.\n eaa = [0.0, 0.0, 0.0]\n else:\n local axis = cross(direction, x)\n local eaa = normalize(axis) * acos(dval)\n end\n local t_base_target_aligned = pose_trans(t_base_target, p[0, 0, 0, eaa[0], eaa[1], eaa[2]])\n movel(t_base_target_aligned, a = 0.2, v = velocity_move)\nend\n# End of Align to Plane\n# Start of Align Z to Nearest Axis\n###\n# Aligns the TCP Z axis to the nearest axis of the given frame\n# @param frame_id string frame_id to lookup frame\n###\ndef ur_align_z_to_nearest_axis(frame_id = \"world\"):\n ###\n # Given a reference frame as input this function returns a struct with the nearest\n # pose which aligns the z-axis of the robot TCP with the z-axis of the given reference frame.\n # The pose is in the reference of the given frame.\n # @param frame bool frame\n # @returns struct pose, distance, referencePose\n ###\n def get_aligned_z_pose(frame):\n local actualPose = get_actual_tcp_pose()\n local actualPoseInFrame = pose_trans(pose_inv(frame), actualPose)\n # Create rotation vector and convert that to RPY representation\n local actualRotInFrame = [actualPoseInFrame[3], actualPoseInFrame[4], actualPoseInFrame[5]]\n local actRPY = rotvec2rpy(actualRotInFrame)\n # Set RX and RY to 0 and convert back to rotation vector\n local alignedRot = rpy2rotvec([0, 0, actRPY[2]])\n local alignedRotFlipped = rpy2rotvec([PI, 0, actRPY[2]])\n local zUpPose = actualPoseInFrame\n zUpPose[3] = alignedRot[0]\n zUpPose[4] = alignedRot[1]\n zUpPose[5] = alignedRot[2]\n zUpStruct = struct(pose = zUpPose, distance=pose_dist(actualPoseInFrame, zUpPose), referencePose=frame)\n local zDownPose = actualPoseInFrame\n zDownPose[3] = alignedRotFlipped[0]\n zDownPose[4] = alignedRotFlipped[1]\n zDownPose[5] = alignedRotFlipped[2]\n local zDownStruct = struct(pose = zDownPose, distance=pose_dist(actualPoseInFrame, zDownPose), referencePose=frame)\n # Return the solution which is closer to the current robot pose\n if (zDownStruct.distance > zUpStruct.distance):\n return zUpStruct\n else:\n return zDownStruct\n end\n end\n local frame = get_pose(frame_id)\n # Rotate the given frame so that Z can be align to X-Y-Z respectively \n local rotZtoX = rpy2rotvec([0,0.5*PI,0])\n local rotZtoY = rpy2rotvec([0.5*PI,0,0])\n local rotZtoZ = rpy2rotvec([0,0,0])\n # Get aligned poses for each of the rotated frames\n local structAlignedToX = get_aligned_z_pose(pose_trans(frame, p[0,0,0,rotZtoX[0],rotZtoX[1],rotZtoX[2]]))\n structAlignedToY = get_aligned_z_pose(pose_trans(frame, p[0,0,0,rotZtoY[0],rotZtoY[1],rotZtoY[2]]))\n structAlignedToZ = get_aligned_z_pose(pose_trans(frame, p[0,0,0,rotZtoZ[0],rotZtoZ[1],rotZtoZ[2]]))\n # Find the nearest alignement\n local structAligned = structAlignedToZ\n if(structAligned.distance > structAlignedToX.distance):\n structAligned = structAlignedToX \n end\n if(structAligned.distance > structAlignedToY.distance):\n structAligned = structAlignedToY \n end\n # Move the robot to the aligned pose\n movel(pose_trans(get_actual_tcp_pose(), p[0,0,0.00001,0,0,0]), v = 0.1)\n movel(pose_trans(structAligned.referencePose, structAligned.pose ), v = 0.1)\nend\n# End of Align Z to Nearest Axis\n# Start of Center to Object\n###\n# Centers to an object by touching the externals of it. It works well for fixtured or heavy parts.\n# @param push_force number Force the robot uses to determine if a contact has been achieved\n# @param velocity_move number Velocity in freespace\n# @param velocity_search number First move is used then search\n# @param acc_move number Acceleration in freespace\n# @param max_radius_search number Maximum search radius\n# @param num_fingers number Number of fingers that the gripper has\n###\ndef ur_center_to_object(push_force = 10, velocity_move = 0.10, velocity_search = 0.01, acc_move = 0.2, max_radius_search = 0.05, num_fingers = 3):\n def compute_circle_center(p_list):\n # Compute the circle center by circular regression\n # Source: https://math.stackexchange.com/questions/2898295/how-to-quickly-fit-a-circle-by-given-random-arc-points\n local itr = 0\n local x = 0\n local y = 1\n \n local m1 = [[0,0,0],[0,0,0],[0,0,0]]\n local m2 = [[0,0],[0,0],[0,0]]\n local m3 = [[0],[0],[0]]\n \n while(itr < get_list_length(p_list)):\n local p = p_list[itr]\n \n if(p_list[itr] == p[0,0,0,0,0,0]):\n break\n end\n \n m1[0,0] = m1[0,0] + (p[x]*p[x])\n m1[0,1] = m1[0,1] + (p[x]*p[y])\n m1[0,2] = m1[0,2] + (p[x])\n \n m1[1,0] = m1[1,0] + (p[x]*p[y])\n m1[1,1] = m1[1,1] + (p[y]*p[y])\n m1[1,2] = m1[1,2] + (p[y])\n \n m1[2,0] = m1[2,0] + (p[x])\n m1[2,1] = m1[2,1] + (p[y])\n \n m2[0,0] = m2[0,0] + (pow(p[x], 3))\n m2[0,1] = m2[0,1] + (p[x] * pow(p[y], 2))\n \n m2[1,0] = m2[1,0] + (pow(p[y], 3))\n m2[1,1] = m2[1,1] + (pow(p[x], 2) * p[y])\n \n m2[2,0] = m2[2,0] + (pow(p[x], 2))\n m2[2,1] = m2[2,1] + (pow(p[y], 2))\n \n itr = itr +1\n end\n \n if(itr < 2):\n return p[0,0,0,0,0,0]\n elif(itr > get_list_length(p_list)):\n return p[0,0,0,0,0,0]\n end\n \n m1[0,0] = 2 * m1[0,0]\n m1[0,1] = 2 * m1[0,1]\n m1[1,0] = 2 * m1[1,0]\n m1[1,1] = 2 * m1[1,1]\n m1[2,0] = 2 * m1[2,0]\n m1[2,1] = 2 * m1[2,1]\n m1[2,2] = itr\n m3[0,0] = m2[0,0] + m2[0,1]\n m3[1,0] = m2[1,0] + m2[1,1]\n m3[2,0] = m2[2,0] + m2[2,1]\n \n local center = inv(m1) * m3\n \n return p[center[0,0], center[1,0],0,0,0,0]\n end\n \n def sanity_checked_move(p_org, p_new, max_diff, acc, vel):\n if (pose_dist(p_org, p_new) > max_diff):\n movel(p_org, a = acc, v = vel)\n popup(\"New pose is too far away from original. Returning to original\", title = \"Failed\", warning = False, error = True, blocking = True)\n else:\n movel(p_new, a = acc, v = vel)\n end\n end\n # Start by zeroing the FT sensor\n sleep(0.25)\n zero_ftsensor()\n local p_start = get_actual_tcp_pose()\n local p0 = p[0,0,0,0,0,0]\n local DIR_X = [1, 0, 0]\n if (num_fingers == 2):\n local dir_list = [DIR_X, -DIR_X, DIR_X, -DIR_X]\n local start_offset = [p[0,0,0,0,0,0], p[0,0,0,0,0,0], p[0,0,0,0,0,0.35], p[0,0,0,0,0,0.35]]\n local p_list = [p0, p0, p0, p0]\n elif (num_fingers == 3):\n local DIR_P1 = DIR_X\n local DIR_P2 = [-1 / 2, sqrt(3.0) / 2.0, 0]\n local DIR_P3 = [-1 / 2, -sqrt(3.0) / 2.0, 0]\n local dir_list = [DIR_P1, DIR_P2, DIR_P3, DIR_P1, DIR_P2, DIR_P3]\n local start_offset = [p[0,0,0,0,0,0], p[0,0,0,0,0,0], p[0,0,0,0,0,0], p[0,0,0,0,0,0.35], p[0,0,0,0,0,0.35], p[0,0,0,0,0,0.35]]\n local p_list = [p0, p0, p0, p0, p0, p0]\n else:\n popup(\"Number of fingers not supported\")\n halt\n end\n # Loop through directions\n local it = 0\n local dir_list_size = size(dir_list)\n local dir_list_length = dir_list_size[0]\n while(it < dir_list_length):\n # Move to starting position if more than 3 positions is stored then calculate a new starting position\n if(it < 3):\n movel(pose_trans(p_start, start_offset[it]), a = acc_move, v = velocity_move)\n else:\n local p_start_temp = pose_trans(pose_trans(p_start, compute_circle_center(p_list)), start_offset[it])\n local p_start_w_offset = pose_trans(p_start, start_offset[it])\n sanity_checked_move(p_start_w_offset, p_start_temp, max_radius_search, acc_move, velocity_move)\n end\n local p_start_temp = get_actual_tcp_pose()\n # Move into contact and store contact point\n sleep(0.1)\n local contact_point = ur_move_until_force(distance = max_radius_search, direction = [dir_list[it, 0], dir_list[it, 1], dir_list[it, 2]], velocity = velocity_search, acceleration = acc_move, stop_force = push_force)\n \n local dir = [dir_list[it, 0], dir_list[it, 1], dir_list[it, 2]]\n dir = normalize(dir) * 0.05\n contact_point = pose_trans(contact_point, p[dir[0], dir[1], dir[2], 0, 0, 0])\n p_list[it] = pose_trans(pose_inv(p_start), contact_point)\n # Move out of contact\n movel(p_start_temp, a = acc_move, v = velocity_move)\n it = it + 1\n end\n # Find circle center based on n stored points\n local center_offset_xy = compute_circle_center(p_list)\n local p_center = pose_trans(p_start, center_offset_xy)\n \n # Move the robot to the center if it can\n sanity_checked_move(p_start, p_center, max_radius_search, acc_move, velocity_move)\nend\n# End of Center to Object\n# Start of Move Into Contact\n###\n# Moves the robot into contact in the TCP direction set\n# @param force number Force that determines when a contact has been achieved\n# @param velocity number Velocity of the robot\n# @param acceleration number Acceleration of the robot\n# @param max_distance number Maximum distance that the robot searches\n# @param velocity_search number velocity_search\n# @param retract number Retract distance after a contact has been found\n# @param move_tcp_dir array TCP direction (3D vector)\n# @param zero_ft_on_start bool Determines if the force-torque sensor should be zeroed on start\n###\ndef ur_move_into_contact(force = 10, velocity = 0.05, acceleration = 0.1, max_distance = 0.25, retract = 0.0, move_tcp_dir = [0, 0, 1], zero_ft_on_start = True):\n # Zero the force torque sensor\n if (zero_ft_on_start):\n sleep(0.25)\n zero_ftsensor()\n end\n # Move the robot\n ur_move_until_force(max_distance, move_tcp_dir, velocity, acceleration, force)\n # If a retract distance is set, move the robot back to that position\n if (retract != 0):\n # Compute position offset from TCP direction and retract distance\n local position = normalize(move_tcp_dir) * retract\n movel(pose_trans(get_actual_tcp_pose(), p[position[0], position[1], position[2], 0, 0, 0]))\n end\nend\n# End of Move Into Contact\n# Start of Retract\n###\n# Retract in the TCP direction set\n# @param distance number Retraction distance\n# @param direction array TCP direction to move in (3D vector)\n# @param acceleration number Acceleration used by the robot\n# @param velocity number Velocity used by the robot\n###\ndef ur_retract(distance = -0.1, direction = [0, 0, 1], acceleration = 0.4, velocity = 0.1):\n local movement = normalize(direction) * distance\n movel(pose_trans(get_actual_tcp_pose(), p[movement[0], movement[1], movement[2], 0, 0, 0]), a = acceleration, v = velocity)\nend\n# End of Retract","nodeIDList":[]}} \ No newline at end of file diff --git a/tests/resources/dockerursim/programs/polyscopex/10.13.0/ur7e/blobs/5.blob b/tests/resources/dockerursim/programs/polyscopex/10.13.0/ur7e/blobs/5.blob new file mode 100644 index 000000000..f92d6e768 --- /dev/null +++ b/tests/resources/dockerursim/programs/polyscopex/10.13.0/ur7e/blobs/5.blob @@ -0,0 +1 @@ +{"jsonContent":{"applicationContributions":{"universal-robots-external-control-external-control-application":{"type":"universal-robots-external-control-external-control-application","version":"1.0.0","port":50002,"robotIP":"192.168.56.1"},"ur-mounting":{"type":"ur-mounting","version":"0.0.1","mounting":{"baseAngle":{"value":0,"unit":"deg"},"tiltAngle":{"value":0,"unit":"deg"}}},"ur-frames":{"type":"ur-frames","version":"0.0.7","framesList":[{"name":"base","nameVariable":{"name":"base","reference":false,"type":"$$Variable","valueType":"frame","id":"6252a467-7b48-0790-36af-5a9122953653","_IDENTIFIER":"VariableDeclaration"},"parent":"world","pose":{"position":[0,0,0],"orientation":[0,0,0]}},{"name":"tcp","nameVariable":{"name":"tcp","reference":false,"type":"$$Variable","valueType":"frame","id":"902de4ca-4cc6-24d1-d208-a7d64dca1939","_IDENTIFIER":"VariableDeclaration"},"parent":"flange","pose":{"position":[0,0,0],"orientation":[0,0,0]}},{"name":"world","nameVariable":{"name":"world","reference":false,"type":"$$Variable","valueType":"frame","id":"1abb892b-4453-7a0b-b117-ab746b820185","_IDENTIFIER":"VariableDeclaration"},"pose":{"position":[0,0,0],"orientation":[0,0,0]}},{"name":"flange","nameVariable":{"name":"flange","reference":false,"type":"$$Variable","valueType":"frame","id":"f05af2d5-8459-68af-fae9-b1468f81a987","_IDENTIFIER":"VariableDeclaration"},"parent":"base","pose":{"position":[0,0,0],"orientation":[0,0,0]}}]},"ur-grid-pattern":{"type":"ur-grid-pattern","version":"0.0.3","grids":[{"grid":{"name":"grid","reference":false,"type":"$$Variable","valueType":"grid","id":"bb906c62-298c-7c7e-f5f7-8820f778f926","_IDENTIFIER":"VariableDeclaration"},"waypoint":{"name":"grid_iterator","reference":false,"type":"$$Variable","valueType":"waypoint","id":"6be80bf7-b0e6-2d12-c3e3-fb3503fde43e","_IDENTIFIER":"VariableDeclaration"},"corners":[null,null,null,null],"numRows":4,"numColumns":5}]},"ur-end-effector":{"type":"ur-end-effector","version":"0.0.2","endEffectors":[{"id":"3035d318-72cd-77ab-9edf-b665bb775514","name":"Robot","payload":{"weight":{"value":0,"unit":"kg"}},"cog":{"cx":{"value":0,"unit":"m"},"cy":{"value":0,"unit":"m"},"cz":{"value":0,"unit":"m"}},"inertia":{"Ixx":{"value":0,"unit":"kg*m^2"},"Iyy":{"value":0,"unit":"kg*m^2"},"Izz":{"value":0,"unit":"kg*m^2"},"Ixy":{"value":0,"unit":"kg*m^2"},"Ixz":{"value":0,"unit":"kg*m^2"},"Iyz":{"value":0,"unit":"kg*m^2"}},"useCustomInertia":false,"tcps":[{"id":"d645c72d-cb4c-148c-b939-c91def1d47c7","name":"Tool_flange","x":{"value":0,"unit":"m"},"y":{"value":0,"unit":"m"},"z":{"value":0,"unit":"m"},"rx":{"value":0,"unit":"rad"},"ry":{"value":0,"unit":"rad"},"rz":{"value":0,"unit":"rad"}}]}],"defaultTcp":{"endEffectorId":"3035d318-72cd-77ab-9edf-b665bb775514","tcpId":"d645c72d-cb4c-148c-b939-c91def1d47c7"}},"ur-motion-profiles":{"type":"ur-motion-profiles","version":"0.0.1","moveProfiles":{"joint":[{"isDefault":false,"profile":{"name":"Joint_fast","reference":false,"type":"$$Variable","valueType":"profile","id":"0142e66e-ed0c-594b-9607-10ea1d224f74","_IDENTIFIER":"VariableDeclaration"},"parameters":{"speedType":"OptiMove","speed":{"entity":{"value":1.0471975511965976,"unit":"rad/s"},"selectedType":"VALUE","value":1.0471975511965976},"acceleration":{"entity":{"value":1.3962634015954636,"unit":"rad/s^2"},"selectedType":"VALUE","value":1.3962634015954636},"optiMoveSpeed":{"entity":{"value":50,"unit":"%"},"selectedType":"VALUE","value":50},"optiMoveAcceleration":{"entity":{"value":25,"unit":"%"},"selectedType":"VALUE","value":25}}},{"isDefault":true,"profile":{"name":"Joint_slow","reference":false,"type":"$$Variable","valueType":"profile","id":"6469ab80-19ff-2edf-d4f1-696f6f1643f0","_IDENTIFIER":"VariableDeclaration"},"parameters":{"speedType":"OptiMove","speed":{"entity":{"value":1.0471975511965976,"unit":"rad/s"},"selectedType":"VALUE","value":1.0471975511965976},"acceleration":{"entity":{"value":1.3962634015954636,"unit":"rad/s^2"},"selectedType":"VALUE","value":1.3962634015954636},"optiMoveSpeed":{"entity":{"value":20,"unit":"%"},"selectedType":"VALUE","value":20},"optiMoveAcceleration":{"entity":{"value":4,"unit":"%"},"selectedType":"VALUE","value":4}}}],"linear":[{"isDefault":false,"profile":{"name":"Linear_fast","reference":false,"type":"$$Variable","valueType":"profile","id":"092041a5-d60a-b98e-8975-7b4b8c432127","_IDENTIFIER":"VariableDeclaration"},"parameters":{"speedType":"OptiMove","speed":{"entity":{"value":0.25,"unit":"m/s"},"selectedType":"VALUE","value":0.25},"acceleration":{"entity":{"value":1.2,"unit":"m/s^2"},"selectedType":"VALUE","value":1.2},"optiMoveSpeed":{"entity":{"value":50,"unit":"%"},"selectedType":"VALUE","value":50},"optiMoveAcceleration":{"entity":{"value":25,"unit":"%"},"selectedType":"VALUE","value":25}}},{"isDefault":true,"profile":{"name":"Linear_slow","reference":false,"type":"$$Variable","valueType":"profile","id":"125202dc-7124-f4dc-70e1-6025e87d825d","_IDENTIFIER":"VariableDeclaration"},"parameters":{"speedType":"OptiMove","speed":{"entity":{"value":0.25,"unit":"m/s"},"selectedType":"VALUE","value":0.25},"acceleration":{"entity":{"value":1.2,"unit":"m/s^2"},"selectedType":"VALUE","value":1.2},"optiMoveSpeed":{"entity":{"value":20,"unit":"%"},"selectedType":"VALUE","value":20},"optiMoveAcceleration":{"entity":{"value":4,"unit":"%"},"selectedType":"VALUE","value":4}}}],"process":[{"isDefault":true,"profile":{"name":"Process","reference":false,"type":"$$Variable","valueType":"profile","id":"31c939a7-423a-f3b0-1f74-5387636ccc77","_IDENTIFIER":"VariableDeclaration"},"parameters":{"speedType":"Classic","speed":{"entity":{"value":0.25,"unit":"m/s"},"selectedType":"VALUE","value":0.25},"acceleration":{"entity":{"value":1.2,"unit":"m/s^2"},"selectedType":"VALUE","value":1.2}}}]}},"ur-smart-skills":{"type":"ur-smart-skills","version":"0.0.3","preamble":"# Start of Forces\n###\n# Transforms the force and torque values along the axes of the given pose\n# @param pose pose Any valid pose, defaults to base, the x, y, and z values are ignored\n# @return array 6D force torque vector with [Fx, Fy, Fz, Mx, My, Mz] aligned to pose in N and Nm respectively\n###\ndef get_tcp_wrench_in_frame(pose = p[0.0, 0.0, 0.0, 0.0, 0.0, 0.0]):\n # we are only interested in the rotation of pose, set translations to zero\n local target_pose = pose\n target_pose[0] = 0\n target_pose[1] = 0\n target_pose[2] = 0\n # the conversion needs to happen as poses, so we need to convert back and forth a bit\n local force = get_tcp_force()\n local force_vector_as_pose = p[force[0], force[1], force[2], 0, 0, 0]\n local torque_vector_as_pose = p[force[3], force[4], force[5], 0, 0, 0]\n local transformed_force_as_pose = pose_trans(pose_inv(target_pose), force_vector_as_pose)\n local transformed_torque_as_pose = pose_trans(pose_inv(target_pose), torque_vector_as_pose)\n return [transformed_force_as_pose[0], transformed_force_as_pose[1], transformed_force_as_pose[2], transformed_torque_as_pose[0], transformed_torque_as_pose[1], transformed_torque_as_pose[2]]\nend\n###\n# See documentation for @link:get_tcp_wrench_in_frame()\n# @return forces and torques measured in TCP frame\n###\ndef get_tcp_wrench():\n return get_tcp_wrench_in_frame(get_target_tcp_pose())\nend\n###\n# Projects the measured TCP force along the axis given\n# @param axis array 3D vector\n###\ndef project_tcp_force(axis):\n local wrench = get_tcp_wrench()\n local force = [wrench[0], wrench[1], wrench[2]]\n return dot(force, axis)\nend\n# End of Forces\n# Start of Math\n# Definitions of constants\nglobal PI = acos(-1)\n###\n# Calculates the cross product between to 3D vectors\n# @param v1 array 3D vector\n# @param v2 array 3D vector\n###\ndef cross(v1, v2):\n if length(v1) != length(v2):\n popup(str_cat(\"For computing the cross product, the two vectors must have the same length. Provided lengths: \", [length(v1), length(v2)]), error=True, blocking=True)\n return -1\n end\n if length(v1) != 3:\n popup(str_cat(\"For computing the cross product, the two vectors must have length 3. Provided lengths: \", [length(v1), length(v2)]), error=True, blocking=True)\n return -1\n end\n local cross = [0.0, 0.0, 0.0]\n local i = 0\n while i < 3:\n local j = (i + 1) % 3 # The next index in a cyclic order\n local k = (i + 2) % 3 # The next next index in a cyclic order\n cross[i] = v1[j] * v2[k] - v1[k] * v2[j]\n i = i + 1\n end\n return cross\nend\n###\n# Calculates the dot product between to n-dimensional vectors\n# @param v1 array nD vector\n# @param v2 array nD vector\n###\ndef dot(v1, v2):\n if length(v1) != length(v2):\n popup(str_cat(\"For computing the dot product, the two vectors must have the same length. Provided lengths: \", [length(v1), length(v2)]), error=True, blocking=True)\n return -1\n end\n local result = 0\n local i = 0\n while i < length(v1):\n result = result + (v1[i] * v2[i])\n i = i + 1\n end\n return result\nend\n###\n# Return the larger number of a and b\n# @param a number a\n# @param b number b\n###\ndef max(a, b):\n if a > b:\n return a\n end\n return b\nend\n###\n# Find the maximum value in a list. The list must be of non-zero length and contain numbers\n# @param list array list\n###\ndef list_max(list):\n local length = get_list_length(list)\n if length == 0:\n popup(\"Getting the maximum of an empty list is impossible in list_max().\", error = True, blocking = True)\n halt\n end\n local i = 0\n local max = list[0]\n while i < length:\n if list[i] > max:\n max = list[i]\n end\n i = i + 1\n sync_at_multiple(i, 30)\n end\n return max\nend\ndef sync_at_multiple(i, n):\n local tmp = i / n\n if tmp == floor(tmp):\n sync()\n end\nend\n# End of Math\n# Start of Move Helper\nur_move_until_force_distance = 0.1\nur_move_until_force_direction = [0, 0, 1]\nur_move_until_force_velocity = 0.1\nur_move_until_force_acceleration = 0.2\ndef ur_move_tcp_direction(distance, direction, velocity, acceleration, blend_radius):\n local current_pose = get_target_tcp_pose()\n local movement = normalize(direction) * distance\n local target_pose = pose_trans(current_pose, p[movement[0], movement[1], movement[2], 0, 0, 0])\n movel(target_pose, a = 0.2, v = velocity, r = blend_radius)\nend\nthread ur_move_until_force_thread():\n ur_move_tcp_direction(ur_move_until_force_distance, ur_move_until_force_direction, ur_move_until_force_velocity, ur_move_until_force_acceleration, 0)\n popup(\"No contact detected.\", title = \"No Contact\", warning = False, error = True, blocking = False)\n halt\nend\n###\n# Moves the robot in the TCP direction specified until a contact point is reached *or*\n# the robot reaches the maximum distance allowed specified by the distance parameter.\n# @param distance number The maximum distance the robot is allowed to travel in the direction specified\n# @param direction array 3D vector determining the move direction of the TCP\n# @param velocity number Velocity of the robot\n# @param acceleration number Acceleration of the robot\n# @param stop_force number Maximum search radius\n###\ndef ur_move_until_force(distance = 0.1, direction = [0, 0, 1], velocity = 0.1, acceleration = 0.2, stop_force = 20):\n ur_move_until_force_distance = distance\n ur_move_until_force_direction = direction\n ur_move_until_force_velocity = velocity\n ur_move_until_force_acceleration = acceleration\n \n thrd = run ur_move_until_force_thread()\n while - project_tcp_force(direction) < stop_force:\n sync()\n end\n kill thrd\n local actual_pose = get_actual_tcp_pose()\n stopl(1.0)\n return actual_pose\nend\ndef ur_get_joint_speeds_before_offset(previous_q, time):\n local current_q = get_joint_positions()\n local delta_q = current_q - previous_q\n return delta_q / time\nend\ndef ur_path_move(end_q, v, rampdown=False):\n # Calculate distance to target\n local start_q = get_joint_positions()\n local delta_q = end_q - start_q\n local positive_delta_q = [norm(delta_q[0]), norm(delta_q[1]), norm(delta_q[2]), norm(delta_q[3]), norm(delta_q[4]), norm(delta_q[5])]\n # Calculate time to move based on desired velocity\n local t = list_max(positive_delta_q) / v\n servoj(end_q , 0, 0, t, lookahead_time=0.1, gain=500)\n if(rampdown):\n while(norm(ur_get_joint_speeds_before_offset(start_q, t)) > 0.0001):\n t = max(t, 0.001)\n start_q = get_joint_positions()\n servoj(end_q , 0, 0, t)\n end\n end\nend\n# End of Move Helper"},"ur-application-variables":{"type":"ur-application-variables","version":"0.0.1","variables":{}}},"sourceConfig":{"labelMap":{},"analogDomainMap":{},"presets":{}},"sourcesNodes":{"robot":{"groupId":"robot","version":"1.0.0.","sources":[{"sourceID":"ur-wired-io","signals":[{"signalID":"DI 0","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DI 1","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DI 2","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DI 3","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DI 4","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DI 5","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DI 6","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DI 7","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DO 0","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"DO 1","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"DO 2","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"DO 3","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"DO 4","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"DO 5","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"DO 6","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"DO 7","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"CI 0","direction":"IN","valueType":"BOOLEAN"},{"signalID":"CI 1","direction":"IN","valueType":"BOOLEAN"},{"signalID":"CI 2","direction":"IN","valueType":"BOOLEAN"},{"signalID":"CI 3","direction":"IN","valueType":"BOOLEAN"},{"signalID":"CI 4","direction":"IN","valueType":"BOOLEAN"},{"signalID":"CI 5","direction":"IN","valueType":"BOOLEAN"},{"signalID":"CI 6","direction":"IN","valueType":"BOOLEAN"},{"signalID":"CI 7","direction":"IN","valueType":"BOOLEAN"},{"signalID":"CO 0","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"CO 1","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"CO 2","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"CO 3","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"CO 4","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"CO 5","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"CO 6","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"CO 7","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"AI 0","direction":"IN","valueType":"FLOAT"},{"signalID":"AI 1","direction":"IN","valueType":"FLOAT"},{"signalID":"AO 0","direction":"OUT","valueType":"FLOAT"},{"signalID":"AO 1","direction":"OUT","valueType":"FLOAT"}],"webSocketURL":"/sources/wired-io"},{"sourceID":"ur-tool-io","signals":[{"signalID":"DI 0","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DI 1","direction":"IN","valueType":"BOOLEAN"},{"signalID":"DO 0","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"DO 1","direction":"OUT","valueType":"BOOLEAN"},{"signalID":"AI 0","direction":"IN","valueType":"FLOAT"},{"signalID":"AI 1","direction":"IN","valueType":"FLOAT"}],"webSocketURL":"/sources/tool-io"}],"isDynamic":false},"ur-modbus":{"groupId":"ur-modbus","isDynamic":true,"version":"1.0.0","sources":[]},"ur-robot-io":{"type":"ur-robot-io","groupId":"ur-robot-io","isDynamic":false,"version":"1.0.2","sources":[{"sourceID":"ur-robot-wired-io","name":"Wired I/O","signals":[{"direction":"IN","signalID":"DI 0","valueType":"BOOLEAN"},{"direction":"IN","signalID":"DI 1","valueType":"BOOLEAN"},{"direction":"IN","signalID":"DI 2","valueType":"BOOLEAN"},{"direction":"IN","signalID":"DI 3","valueType":"BOOLEAN"},{"direction":"IN","signalID":"DI 4","valueType":"BOOLEAN"},{"direction":"IN","signalID":"DI 5","valueType":"BOOLEAN"},{"direction":"IN","signalID":"DI 6","valueType":"BOOLEAN"},{"direction":"IN","signalID":"DI 7","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"DO 0","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"DO 1","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"DO 2","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"DO 3","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"DO 4","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"DO 5","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"DO 6","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"DO 7","valueType":"BOOLEAN"},{"direction":"IN","signalID":"CI 0","valueType":"BOOLEAN"},{"direction":"IN","signalID":"CI 1","valueType":"BOOLEAN"},{"direction":"IN","signalID":"CI 2","valueType":"BOOLEAN"},{"direction":"IN","signalID":"CI 3","valueType":"BOOLEAN"},{"direction":"IN","signalID":"CI 4","valueType":"BOOLEAN"},{"direction":"IN","signalID":"CI 5","valueType":"BOOLEAN"},{"direction":"IN","signalID":"CI 6","valueType":"BOOLEAN"},{"direction":"IN","signalID":"CI 7","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"CO 0","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"CO 1","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"CO 2","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"CO 3","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"CO 4","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"CO 5","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"CO 6","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"CO 7","valueType":"BOOLEAN"},{"direction":"IN","signalID":"AI 0","valueType":"FLOAT"},{"direction":"IN","signalID":"AI 1","valueType":"FLOAT"},{"direction":"OUT","signalID":"AO 0","valueType":"FLOAT"},{"direction":"OUT","signalID":"AO 1","valueType":"FLOAT"}]},{"sourceID":"ur-robot-tool-io","name":"Tool I/O","signals":[{"direction":"IN","signalID":"DI 0","valueType":"BOOLEAN"},{"direction":"IN","signalID":"DI 1","valueType":"BOOLEAN"},{"direction":"IN","signalID":"AI 0","valueType":"FLOAT"},{"direction":"IN","signalID":"AI 1","valueType":"FLOAT"},{"direction":"OUT","signalID":"DO 0","valueType":"BOOLEAN"},{"direction":"OUT","signalID":"DO 1","valueType":"BOOLEAN"}]}],"parameters":{"sourceConfig":{"labelMap":{},"analogDomainMap":{},"presets":{},"toolOutput":{"dualPinPower":false,"voltage":{"value":0,"unit":"V"},"powerOutput":{"DO 0":1,"DO 1":1}}},"migrateSourceConfigDone":true}}},"safety":{"settings":{"io":{"automaticModeSafeguardResetInput":{"name":"automaticModeSafeguardResetInput","valueA":255,"valueB":255},"automaticModeSafeguardStopInput":{"name":"automaticModeSafeguardStopInput","valueA":255,"valueB":255},"emergencyStopInput":{"name":"emergencyStopInput","valueA":255,"valueB":255},"notReducedModeOutput":{"name":"notReducedModeOutput","ossdEnabled":false,"valueA":255,"valueB":255},"operationalModeInput":{"name":"operationalModeInput","valueA":255,"valueB":255},"reducedModeInput":{"name":"reducedModeInput","valueA":255,"valueB":255},"reducedModeOutput":{"name":"reducedModeOutput","ossdEnabled":false,"valueA":255,"valueB":255},"robotMovingOutput":{"name":"robotMovingOutput","ossdEnabled":false,"valueA":255,"valueB":255},"robotNotStoppingOutput":{"name":"robotNotStoppingOutput","ossdEnabled":false,"valueA":255,"valueB":255},"safeHomeOutput":{"name":"safeHomeOutput","ossdEnabled":false,"valueA":255,"valueB":255},"safeguardResetInput":{"name":"safeguardResetInput","valueA":0,"valueB":1},"systemEmergencyStoppedOutput":{"name":"systemEmergencyStoppedOutput","ossdEnabled":false,"valueA":255,"valueB":255},"threePositionSwitchInput":{"name":"threePositionSwitchInput","valueA":255,"valueB":255},"freedriveEnabledInput":{"name":"freedriveEnabledInput","valueA":255,"valueB":255},"threePositionEnablingStopOutput":{"name":"threePositionEnablingStopOutput","ossdEnabled":false,"valueA":255,"valueB":255},"notThreePositionEnablingStopOutput":{"name":"notThreePositionEnablingStopOutput","ossdEnabled":false,"valueA":255,"valueB":255}},"major":5,"minor":14,"normalJointPositions":{"base":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"elbow":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"shoulder":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"wrist1":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"wrist2":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"wrist3":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false}},"normalJointSpeeds":{"base":3.3415926,"shoulder":3.3415926,"elbow":3.3415926,"wrist1":3.3415926,"wrist2":3.3415926,"wrist3":3.3415926},"normalRobotLimits":{"elbowForce":150,"elbowSpeed":1.5,"momentum":25,"power":300,"stoppingDistance":0.5,"stoppingTime":0.4,"toolForce":150,"toolSpeed":1.5},"reducedJointPositions":{"base":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"elbow":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"shoulder":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"wrist1":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"wrist2":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false},"wrist3":{"maximum":6.33555,"maximumJointPosition":0.05235988,"maximumRevolutionCounter":1,"minimum":-6.33555,"minimumJointPosition":6.2308254,"minimumRevolutionCounter":-2,"unlimited":false}},"reducedJointSpeeds":{"base":3.3415926,"shoulder":3.3415926,"elbow":3.3415926,"wrist1":3.3415926,"wrist2":3.3415926,"wrist3":3.3415926},"reducedRobotLimits":{"elbowForce":120,"elbowSpeed":0.75,"momentum":10,"power":200,"stoppingDistance":0.3,"stoppingTime":0.3,"toolForce":120,"toolSpeed":0.75},"safetyHardware":{"injectionMoldingMachineInterface":"NONE","teachPendant":"NORMAL"},"safetyPlanes":{"planes":[{"id":"fa9294a4-967c-0579-2598-bbad56efc589","name":"UNDEFINED","safetyPlane":{"normalModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModeTriggerPlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true},"tilt":0,"offset":0,"rotation":0,"restriction":"disabled"},{"id":"215f99a1-821b-78ec-6ca8-ebf9af0305f9","name":"UNDEFINED","safetyPlane":{"normalModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModeTriggerPlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true},"tilt":0,"offset":0,"rotation":0,"restriction":"disabled"},{"id":"54b27eb2-3f22-d0d6-4392-1bca84eaba07","name":"UNDEFINED","safetyPlane":{"normalModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModeTriggerPlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true},"tilt":0,"offset":0,"rotation":0,"restriction":"disabled"},{"id":"c56f70d3-b7d5-5e11-efc2-cd03174894ef","name":"UNDEFINED","safetyPlane":{"normalModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModeTriggerPlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true},"tilt":0,"offset":0,"rotation":0,"restriction":"disabled"},{"id":"24b0b581-f988-5112-2298-db6ec78b8697","name":"UNDEFINED","safetyPlane":{"normalModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModeTriggerPlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true},"tilt":0,"offset":0,"rotation":0,"restriction":"disabled"},{"id":"0f8b6df0-cb79-53b4-b4e7-87e506d0b74a","name":"UNDEFINED","safetyPlane":{"normalModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModeTriggerPlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true},"tilt":0,"offset":0,"rotation":0,"restriction":"disabled"},{"id":"31eeb60c-4d58-13be-cfc4-514e8c663223","name":"UNDEFINED","safetyPlane":{"normalModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModeTriggerPlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true},"tilt":0,"offset":0,"rotation":0,"restriction":"disabled"},{"id":"f80f48d0-f136-fe07-0df6-2155d1bd261c","name":"UNDEFINED","safetyPlane":{"normalModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModePlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"reducedModeTriggerPlane":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true},"tilt":0,"offset":0,"rotation":0,"restriction":"disabled"}],"ioSafetyPlanes":[{"id":"eabab9c5-2d28-c06f-3d80-2acac453bb2c","name":"UNDEFINED","ioSafetyPlane":{"triggerOutput":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"triggerSafeguard":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true,"inputConfiguration":{"name":"UNDEFINED","valueA":255,"valueB":255},"outputConfiguration":{"name":"UNDEFINED","ossdEnabled":false,"valueA":255,"valueB":255}},"tilt":0,"offset":0,"rotation":0,"trigger":"disabled"},{"id":"fbe1a183-0d6d-5ffb-db55-0702ac3586d4","name":"UNDEFINED","ioSafetyPlane":{"triggerOutput":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"triggerSafeguard":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true,"inputConfiguration":{"name":"UNDEFINED","valueA":255,"valueB":255},"outputConfiguration":{"name":"UNDEFINED","ossdEnabled":false,"valueA":255,"valueB":255}},"tilt":0,"offset":0,"rotation":0,"trigger":"disabled"},{"id":"bebca814-b309-296e-3b85-f11afbbd7a34","name":"UNDEFINED","ioSafetyPlane":{"triggerOutput":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"triggerSafeguard":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true,"inputConfiguration":{"name":"UNDEFINED","valueA":255,"valueB":255},"outputConfiguration":{"name":"UNDEFINED","ossdEnabled":false,"valueA":255,"valueB":255}},"tilt":0,"offset":0,"rotation":0,"trigger":"disabled"},{"id":"f5585223-652e-a47b-ee33-1ea2fbc16f6c","name":"UNDEFINED","ioSafetyPlane":{"triggerOutput":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"triggerSafeguard":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true,"inputConfiguration":{"name":"UNDEFINED","valueA":255,"valueB":255},"outputConfiguration":{"name":"UNDEFINED","ossdEnabled":false,"valueA":255,"valueB":255}},"tilt":0,"offset":0,"rotation":0,"trigger":"disabled"},{"id":"f21f0288-3694-1561-5529-15653ad65d6d","name":"UNDEFINED","ioSafetyPlane":{"triggerOutput":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"triggerSafeguard":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true,"inputConfiguration":{"name":"UNDEFINED","valueA":255,"valueB":255},"outputConfiguration":{"name":"UNDEFINED","ossdEnabled":false,"valueA":255,"valueB":255}},"tilt":0,"offset":0,"rotation":0,"trigger":"disabled"},{"id":"354479b5-9e7e-e940-1485-97c1ccf4c80b","name":"UNDEFINED","ioSafetyPlane":{"triggerOutput":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"triggerSafeguard":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true,"inputConfiguration":{"name":"UNDEFINED","valueA":255,"valueB":255},"outputConfiguration":{"name":"UNDEFINED","ossdEnabled":false,"valueA":255,"valueB":255}},"tilt":0,"offset":0,"rotation":0,"trigger":"disabled"},{"id":"5491a1c6-1740-6878-0fd5-45e1fdb3ed9a","name":"UNDEFINED","ioSafetyPlane":{"triggerOutput":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"triggerSafeguard":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true,"inputConfiguration":{"name":"UNDEFINED","valueA":255,"valueB":255},"outputConfiguration":{"name":"UNDEFINED","ossdEnabled":false,"valueA":255,"valueB":255}},"tilt":0,"offset":0,"rotation":0,"trigger":"disabled"},{"id":"85ceb891-c975-13c5-ca8f-5b2d051225d0","name":"UNDEFINED","ioSafetyPlane":{"triggerOutput":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"triggerSafeguard":{"distance":0,"vector":{"x":0,"y":0,"z":0}},"restrictsElbow":false,"restrictsToolFlange":true,"inputConfiguration":{"name":"UNDEFINED","valueA":255,"valueB":255},"outputConfiguration":{"name":"UNDEFINED","ossdEnabled":false,"valueA":255,"valueB":255}},"tilt":0,"offset":0,"rotation":0,"trigger":"disabled"}]},"safetySafeHome":{"base":-1,"elbow":-1,"shoulder":-1,"wrist1":-1,"wrist2":-1,"wrist3":-1,"enabled":false},"safetyAPIParameters":{"numberOfClients":0,"clients":[]},"safetyFieldbusses":{"enablePROFIsafe":false,"sourceAddressPROFIsafe":0,"destAddressPROFIsafe":0,"modeControlPROFIsafe":false},"threePosition":{"allowManualHighSpeed":true,"useTeachPendantAs3PE":false},"toolDirection":{"limitDeviation":6.2831855,"limitDirection":{"x":0,"y":0,"z":1},"limitRestriction":"DISABLED","tcp":{"id":"toolFlangeTcpId","name":"Tool_flange"},"toolPan":0,"toolTilt":0,"limitTilt":0,"limitRotation":0},"toolPositions":{"toolPositions":[{"name":"Tool Flange","center":{"x":0,"y":0,"z":0},"radius":0,"definition":2},{"name":"UNDEFINED","center":{"x":0,"y":0,"z":0},"radius":0,"definition":0},{"name":"UNDEFINED","center":{"x":0,"y":0,"z":0},"radius":0,"definition":0}]},"normalWristClamp":{"enableWristClampPosition":"LIMIT_ENABLED","enableWristClampTorque":"LIMIT_ENABLED"},"reducedWristClamp":{"enableWristClampPosition":"LIMIT_ENABLED","enableWristClampTorque":"LIMIT_ENABLED"}},"crc":"2850523693","confirmed":true},"operatorScreens":[{"type":"ur-operator-screen-default","version":"0.0.2","parameters":{"status":[],"configuration":[]}}],"sidebarItems":[{"type":"ur-global-variables","version":"1.0.0","disabled":{"master":false,"automaticMode":false,"remoteMode":true}},{"type":"ur-log-messages-sidebar","version":"0.0.1","disabled":{"master":false,"automaticMode":false,"remoteMode":true}}],"activeOperatorScreen":"ur-operator-screen-default","smartSkills":[{"name":"Align to Plane","enabled":true,"type":"ur-align-to-plane","parameters":{"radius":0.05,"push_force":20,"n_plane_points":3,"max_distance":0.25,"velocity_slow":0.001,"velocity_search":0.035,"velocity_move":0.1,"acceleration":0.1}},{"name":"Align Z to Nearest Axis","enabled":true,"type":"ur-align-z-to-nearest-axis"},{"name":"Center","enabled":true,"type":"ur-center","parameters":{"push_force":10,"velocity_move":0.05,"acc_move":0.2,"max_radius_search":0.05,"num_fingers":3}},{"name":"Freedrive","enabled":true,"type":"ur-freedrive","version":"1.0.0","recordingFrequency":50,"recordingSignals":["timestamp","target_q","actual_TCP_pose","tcp_offset"]},{"name":"Move into Contact","enabled":true,"type":"ur-move-into-contact","parameters":{"force":10,"velocity":0.05,"acceleration":0.2,"max_distance":0.25,"retract":0}},{"name":"Retract","enabled":true,"type":"ur-retract","parameters":{"distance":-0.1,"acceleration":0.4,"velocity":0.1}},{"name":"Put into Box","enabled":false,"type":"ur-put-in-box","version":"1.0.0"},{"name":"Custom","enabled":false,"type":"ur-custom-smart-skill","parameters":{"includePreamble":true,"includeModules":false},"version":"1.0.0"},{"name":"Home","enabled":true,"type":"ur-position","version":"1.1.2","parameters":{"actualWaypoint":{"frame":"base","pose":{"position":[-1.8246917738038495E-9,-0.2329000001676105,1.0793999999522315],"orientation":[3.987257497300885E-9,2.2214414675120993,-2.221441467056474]},"qNear":{"base":0,"shoulder":-1.5707963249999999,"elbow":0,"wrist1":-1.5707963249999999,"wrist2":0,"wrist3":0}},"variable":{"name":"Home","reference":false,"type":"$$Variable","valueType":"waypoint","id":"1a7b4663-70cf-4964-6c73-a28c5664b8a2","_IDENTIFIER":"VariableDeclaration"}}}]},"script":{"script":"set_safety_mode_transition_hardness(1)\nreset_world_model()\nset_input_actions_to_default()\nset_analog_outputdomain(0,0)\nset_analog_outputdomain(1,0)\nset_standard_analog_input_domain(0,0)\nset_standard_analog_input_domain(1,0)\nset_tool_output_mode(0)\nset_tool_voltage(0)\nset_tool_digital_output_mode(0,1)\nset_tool_digital_output_mode(1,1)\nset_tool_analog_input_domain(0,0)\nset_tool_analog_input_domain(1,0)\nset_gravity([0, 0, 9.82])\nlocal existingBaseParent = get_frame_parent(\"base\")\nlocal basePose = get_pose(\"base\", existingBaseParent)\nbasePose[3] = 0\nbasePose[4] = 0\nbasePose[5] = 0\nmove_frame(\"base\", basePose, existingBaseParent)\nglobal base = \"base\"\nglobal tcp = \"tcp\"\nglobal world = \"world\"\nglobal flange = \"flange\"\nset_target_payload(0, [0, 0, 0], [0, 0, 0, 0, 0, 0])\nset_tcp(p[0, 0, 0, 0, 0, 0], \"Tool_flange\")\n# Start of Forces\n###\n# Transforms the force and torque values along the axes of the given pose\n# @param pose pose Any valid pose, defaults to base, the x, y, and z values are ignored\n# @return array 6D force torque vector with [Fx, Fy, Fz, Mx, My, Mz] aligned to pose in N and Nm respectively\n###\ndef get_tcp_wrench_in_frame(pose = p[0.0, 0.0, 0.0, 0.0, 0.0, 0.0]):\n # we are only interested in the rotation of pose, set translations to zero\n local target_pose = pose\n target_pose[0] = 0\n target_pose[1] = 0\n target_pose[2] = 0\n # the conversion needs to happen as poses, so we need to convert back and forth a bit\n local force = get_tcp_force()\n local force_vector_as_pose = p[force[0], force[1], force[2], 0, 0, 0]\n local torque_vector_as_pose = p[force[3], force[4], force[5], 0, 0, 0]\n local transformed_force_as_pose = pose_trans(pose_inv(target_pose), force_vector_as_pose)\n local transformed_torque_as_pose = pose_trans(pose_inv(target_pose), torque_vector_as_pose)\n return [transformed_force_as_pose[0], transformed_force_as_pose[1], transformed_force_as_pose[2], transformed_torque_as_pose[0], transformed_torque_as_pose[1], transformed_torque_as_pose[2]]\nend\n###\n# See documentation for @link:get_tcp_wrench_in_frame()\n# @return forces and torques measured in TCP frame\n###\ndef get_tcp_wrench():\n return get_tcp_wrench_in_frame(get_target_tcp_pose())\nend\n###\n# Projects the measured TCP force along the axis given\n# @param axis array 3D vector\n###\ndef project_tcp_force(axis):\n local wrench = get_tcp_wrench()\n local force = [wrench[0], wrench[1], wrench[2]]\n return dot(force, axis)\nend\n# End of Forces\n# Start of Math\n# Definitions of constants\nglobal PI = acos(-1)\n###\n# Calculates the cross product between to 3D vectors\n# @param v1 array 3D vector\n# @param v2 array 3D vector\n###\ndef cross(v1, v2):\n if length(v1) != length(v2):\n popup(str_cat(\"For computing the cross product, the two vectors must have the same length. Provided lengths: \", [length(v1), length(v2)]), error=True, blocking=True)\n return -1\n end\n if length(v1) != 3:\n popup(str_cat(\"For computing the cross product, the two vectors must have length 3. Provided lengths: \", [length(v1), length(v2)]), error=True, blocking=True)\n return -1\n end\n local cross = [0.0, 0.0, 0.0]\n local i = 0\n while i < 3:\n local j = (i + 1) % 3 # The next index in a cyclic order\n local k = (i + 2) % 3 # The next next index in a cyclic order\n cross[i] = v1[j] * v2[k] - v1[k] * v2[j]\n i = i + 1\n end\n return cross\nend\n###\n# Calculates the dot product between to n-dimensional vectors\n# @param v1 array nD vector\n# @param v2 array nD vector\n###\ndef dot(v1, v2):\n if length(v1) != length(v2):\n popup(str_cat(\"For computing the dot product, the two vectors must have the same length. Provided lengths: \", [length(v1), length(v2)]), error=True, blocking=True)\n return -1\n end\n local result = 0\n local i = 0\n while i < length(v1):\n result = result + (v1[i] * v2[i])\n i = i + 1\n end\n return result\nend\n###\n# Return the larger number of a and b\n# @param a number a\n# @param b number b\n###\ndef max(a, b):\n if a > b:\n return a\n end\n return b\nend\n###\n# Find the maximum value in a list. The list must be of non-zero length and contain numbers\n# @param list array list\n###\ndef list_max(list):\n local length = get_list_length(list)\n if length == 0:\n popup(\"Getting the maximum of an empty list is impossible in list_max().\", error = True, blocking = True)\n halt\n end\n local i = 0\n local max = list[0]\n while i < length:\n if list[i] > max:\n max = list[i]\n end\n i = i + 1\n sync_at_multiple(i, 30)\n end\n return max\nend\ndef sync_at_multiple(i, n):\n local tmp = i / n\n if tmp == floor(tmp):\n sync()\n end\nend\n# End of Math\n# Start of Move Helper\nur_move_until_force_distance = 0.1\nur_move_until_force_direction = [0, 0, 1]\nur_move_until_force_velocity = 0.1\nur_move_until_force_acceleration = 0.2\ndef ur_move_tcp_direction(distance, direction, velocity, acceleration, blend_radius):\n local current_pose = get_target_tcp_pose()\n local movement = normalize(direction) * distance\n local target_pose = pose_trans(current_pose, p[movement[0], movement[1], movement[2], 0, 0, 0])\n movel(target_pose, a = 0.2, v = velocity, r = blend_radius)\nend\nthread ur_move_until_force_thread():\n ur_move_tcp_direction(ur_move_until_force_distance, ur_move_until_force_direction, ur_move_until_force_velocity, ur_move_until_force_acceleration, 0)\n popup(\"No contact detected.\", title = \"No Contact\", warning = False, error = True, blocking = False)\n halt\nend\n###\n# Moves the robot in the TCP direction specified until a contact point is reached *or*\n# the robot reaches the maximum distance allowed specified by the distance parameter.\n# @param distance number The maximum distance the robot is allowed to travel in the direction specified\n# @param direction array 3D vector determining the move direction of the TCP\n# @param velocity number Velocity of the robot\n# @param acceleration number Acceleration of the robot\n# @param stop_force number Maximum search radius\n###\ndef ur_move_until_force(distance = 0.1, direction = [0, 0, 1], velocity = 0.1, acceleration = 0.2, stop_force = 20):\n ur_move_until_force_distance = distance\n ur_move_until_force_direction = direction\n ur_move_until_force_velocity = velocity\n ur_move_until_force_acceleration = acceleration\n \n thrd = run ur_move_until_force_thread()\n while - project_tcp_force(direction) < stop_force:\n sync()\n end\n kill thrd\n local actual_pose = get_actual_tcp_pose()\n stopl(1.0)\n return actual_pose\nend\ndef ur_get_joint_speeds_before_offset(previous_q, time):\n local current_q = get_joint_positions()\n local delta_q = current_q - previous_q\n return delta_q / time\nend\ndef ur_path_move(end_q, v, rampdown=False):\n # Calculate distance to target\n local start_q = get_joint_positions()\n local delta_q = end_q - start_q\n local positive_delta_q = [norm(delta_q[0]), norm(delta_q[1]), norm(delta_q[2]), norm(delta_q[3]), norm(delta_q[4]), norm(delta_q[5])]\n # Calculate time to move based on desired velocity\n local t = list_max(positive_delta_q) / v\n servoj(end_q , 0, 0, t, lookahead_time=0.1, gain=500)\n if(rampdown):\n while(norm(ur_get_joint_speeds_before_offset(start_q, t)) > 0.0001):\n t = max(t, 0.001)\n start_q = get_joint_positions()\n servoj(end_q , 0, 0, t)\n end\n end\nend\n# End of Move Helper\n# Waypoint variable for Home smart skill\nglobal Home = struct(p=p[-1.8246917738038495e-9, -0.2329000001676105, 1.0793999999522315, 3.987257497300885e-9, 2.2214414675120993, -2.221441467056474], frame=\"base\", q=[0, -1.5707963249999999, 0, -1.5707963249999999, 0, 0])\n# Start of Align to Plane\n###\n# Align to plane will touch up a plane by moving the robot into contact with the table or part in several locations to determine its orientation. Afterwards the robot will orient its tool to the plane.\n# @param radius number Radius [m] of the circle within the plane will be touched up\n# @param push_force number How hard to robot pushed against the plane\n# @param n_plane_points number Number of points that the robot uses to compute the plane\n# @param max_distance number Maximum distance that the robot searches\n# @param velocity_slow number Velocity when pressing downwards\n# @param velocity_search number Velocity used when approaching the touch up point\n# @param velocity_move number Velocity used in freespace\n# @param acceleration number Acceleration of the robot\n# @param direction array 3D vector determining the direction of the TCP for touching up the plane\n###\ndef ur_align_to_plane(radius = 0.05, push_force = 20, n_plane_points = 3, max_distance = 0.25, velocity_slow = 0.001, velocity_search = 0.035, velocity_move = 0.10, acceleration = 0.1, direction = [0, 0, 1]):\n local angle = 2 * PI / n_plane_points\n local start_pos = get_target_tcp_pose()\n local retract_distance = -0.015\n ur_move_tcp_direction(retract_distance, direction, velocity_move, acceleration, 0)\n sleep(0.25)\n zero_ftsensor()\n local cnt = 0\n local t_base_target = get_target_tcp_pose()\n local mean_point = [0.0, 0.0, 0.0]\n local A = [[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]]\n local b = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]\n while cnt < n_plane_points:\n local new_pos = pose_trans(t_base_target, p[cos(angle * cnt) * radius, sin(angle * cnt) * radius, 0.0, 0.0, 0.0, 0.0])\n local blend_radius = norm(point_dist(get_actual_tcp_pose(), new_pos))/5\n movel(new_pos, a = acceleration, v = velocity_move, r = blend_radius)\n ur_move_until_force(max_distance + norm(retract_distance), direction, velocity_search, acceleration, push_force)\n local movement = normalize(direction * -1) * 0.0005\n local target_pose = pose_trans(get_actual_tcp_pose(), p[movement[0], movement[1], movement[2], 0, 0, 0])\n movel(target_pose)\n sleep(0.2)\n ur_move_until_force(max_distance + norm(retract_distance), direction, velocity_slow, acceleration, push_force)\n sleep(0.2)\n while (not is_steady()):\n sync()\n end\n local poked_point = get_target_tcp_pose()\n poked_point = pose_trans(inv(t_base_target), poked_point)\n A[cnt, 0] = poked_point[0]\n A[cnt, 1] = poked_point[1]\n A[cnt, 2] = 1.0\n b[cnt] = poked_point[2]\n mean_point = mean_point + [poked_point[0], poked_point[1], poked_point[2]]\n movel(new_pos, a = 0.2, v = velocity_move, r = blend_radius)\n cnt = cnt + 1\n end\n mean_point = mean_point / n_plane_points\n cnt = 0\n while cnt < n_plane_points:\n local cntj = 0\n while cntj < 2:\n A[cnt, cntj] = A[cnt, cntj] - mean_point[cntj]\n cntj = cntj + 1\n end\n b[cnt] = b[cnt] - mean_point[2]\n cnt = cnt + 1\n end\n local x1 = inv(transpose(A) * A) * transpose(A) * b\n local x = normalize([x1[0], x1[1], -1])\n local d = dot(mean_point, x)\n local dval = dot(direction, x)\n if dval < 0:\n x = -x\n dval = -dval\n end\n local eaa = [0.0, 0.0, 0.0]\n local EPSILON = 1e-10\n if norm(dval - 1) < EPSILON:\n # if the projection is close to 1 then the angle between the vectors are almost 0 and we cannot\n # reliably determine the perpendicular axis.\n # A good approximation is therefore just to set the EAA equal to 0.\n eaa = [0.0, 0.0, 0.0]\n else:\n local axis = cross(direction, x)\n local eaa = normalize(axis) * acos(dval)\n end\n local t_base_target_aligned = pose_trans(t_base_target, p[0, 0, 0, eaa[0], eaa[1], eaa[2]])\n movel(t_base_target_aligned, a = 0.2, v = velocity_move)\nend\n# End of Align to Plane\n# Start of Align Z to Nearest Axis\n###\n# Aligns the TCP Z axis to the nearest axis of the given frame\n# @param frame_id string frame_id to lookup frame\n###\ndef ur_align_z_to_nearest_axis(frame_id = \"world\"):\n ###\n # Given a reference frame as input this function returns a struct with the nearest\n # pose which aligns the z-axis of the robot TCP with the z-axis of the given reference frame.\n # The pose is in the reference of the given frame.\n # @param frame bool frame\n # @returns struct pose, distance, referencePose\n ###\n def get_aligned_z_pose(frame):\n local actualPose = get_actual_tcp_pose()\n local actualPoseInFrame = pose_trans(pose_inv(frame), actualPose)\n # Create rotation vector and convert that to RPY representation\n local actualRotInFrame = [actualPoseInFrame[3], actualPoseInFrame[4], actualPoseInFrame[5]]\n local actRPY = rotvec2rpy(actualRotInFrame)\n # Set RX and RY to 0 and convert back to rotation vector\n local alignedRot = rpy2rotvec([0, 0, actRPY[2]])\n local alignedRotFlipped = rpy2rotvec([PI, 0, actRPY[2]])\n local zUpPose = actualPoseInFrame\n zUpPose[3] = alignedRot[0]\n zUpPose[4] = alignedRot[1]\n zUpPose[5] = alignedRot[2]\n zUpStruct = struct(pose = zUpPose, distance=pose_dist(actualPoseInFrame, zUpPose), referencePose=frame)\n local zDownPose = actualPoseInFrame\n zDownPose[3] = alignedRotFlipped[0]\n zDownPose[4] = alignedRotFlipped[1]\n zDownPose[5] = alignedRotFlipped[2]\n local zDownStruct = struct(pose = zDownPose, distance=pose_dist(actualPoseInFrame, zDownPose), referencePose=frame)\n # Return the solution which is closer to the current robot pose\n if (zDownStruct.distance > zUpStruct.distance):\n return zUpStruct\n else:\n return zDownStruct\n end\n end\n local frame = get_pose(frame_id)\n # Rotate the given frame so that Z can be align to X-Y-Z respectively \n local rotZtoX = rpy2rotvec([0,0.5*PI,0])\n local rotZtoY = rpy2rotvec([0.5*PI,0,0])\n local rotZtoZ = rpy2rotvec([0,0,0])\n # Get aligned poses for each of the rotated frames\n local structAlignedToX = get_aligned_z_pose(pose_trans(frame, p[0,0,0,rotZtoX[0],rotZtoX[1],rotZtoX[2]]))\n structAlignedToY = get_aligned_z_pose(pose_trans(frame, p[0,0,0,rotZtoY[0],rotZtoY[1],rotZtoY[2]]))\n structAlignedToZ = get_aligned_z_pose(pose_trans(frame, p[0,0,0,rotZtoZ[0],rotZtoZ[1],rotZtoZ[2]]))\n # Find the nearest alignement\n local structAligned = structAlignedToZ\n if(structAligned.distance > structAlignedToX.distance):\n structAligned = structAlignedToX \n end\n if(structAligned.distance > structAlignedToY.distance):\n structAligned = structAlignedToY \n end\n # Move the robot to the aligned pose\n movel(pose_trans(get_actual_tcp_pose(), p[0,0,0.00001,0,0,0]), v = 0.1)\n movel(pose_trans(structAligned.referencePose, structAligned.pose ), v = 0.1)\nend\n# End of Align Z to Nearest Axis\n# Start of Center to Object\n###\n# Centers to an object by touching the externals of it. It works well for fixtured or heavy parts.\n# @param push_force number Force the robot uses to determine if a contact has been achieved\n# @param velocity_move number Velocity in freespace\n# @param velocity_search number First move is used then search\n# @param acc_move number Acceleration in freespace\n# @param max_radius_search number Maximum search radius\n# @param num_fingers number Number of fingers that the gripper has\n###\ndef ur_center_to_object(push_force = 10, velocity_move = 0.10, velocity_search = 0.01, acc_move = 0.2, max_radius_search = 0.05, num_fingers = 3):\n def compute_circle_center(p_list):\n # Compute the circle center by circular regression\n # Source: https://math.stackexchange.com/questions/2898295/how-to-quickly-fit-a-circle-by-given-random-arc-points\n local itr = 0\n local x = 0\n local y = 1\n \n local m1 = [[0,0,0],[0,0,0],[0,0,0]]\n local m2 = [[0,0],[0,0],[0,0]]\n local m3 = [[0],[0],[0]]\n \n while(itr < get_list_length(p_list)):\n local p = p_list[itr]\n \n if(p_list[itr] == p[0,0,0,0,0,0]):\n break\n end\n \n m1[0,0] = m1[0,0] + (p[x]*p[x])\n m1[0,1] = m1[0,1] + (p[x]*p[y])\n m1[0,2] = m1[0,2] + (p[x])\n \n m1[1,0] = m1[1,0] + (p[x]*p[y])\n m1[1,1] = m1[1,1] + (p[y]*p[y])\n m1[1,2] = m1[1,2] + (p[y])\n \n m1[2,0] = m1[2,0] + (p[x])\n m1[2,1] = m1[2,1] + (p[y])\n \n m2[0,0] = m2[0,0] + (pow(p[x], 3))\n m2[0,1] = m2[0,1] + (p[x] * pow(p[y], 2))\n \n m2[1,0] = m2[1,0] + (pow(p[y], 3))\n m2[1,1] = m2[1,1] + (pow(p[x], 2) * p[y])\n \n m2[2,0] = m2[2,0] + (pow(p[x], 2))\n m2[2,1] = m2[2,1] + (pow(p[y], 2))\n \n itr = itr +1\n end\n \n if(itr < 2):\n return p[0,0,0,0,0,0]\n elif(itr > get_list_length(p_list)):\n return p[0,0,0,0,0,0]\n end\n \n m1[0,0] = 2 * m1[0,0]\n m1[0,1] = 2 * m1[0,1]\n m1[1,0] = 2 * m1[1,0]\n m1[1,1] = 2 * m1[1,1]\n m1[2,0] = 2 * m1[2,0]\n m1[2,1] = 2 * m1[2,1]\n m1[2,2] = itr\n m3[0,0] = m2[0,0] + m2[0,1]\n m3[1,0] = m2[1,0] + m2[1,1]\n m3[2,0] = m2[2,0] + m2[2,1]\n \n local center = inv(m1) * m3\n \n return p[center[0,0], center[1,0],0,0,0,0]\n end\n \n def sanity_checked_move(p_org, p_new, max_diff, acc, vel):\n if (pose_dist(p_org, p_new) > max_diff):\n movel(p_org, a = acc, v = vel)\n popup(\"New pose is too far away from original. Returning to original\", title = \"Failed\", warning = False, error = True, blocking = True)\n else:\n movel(p_new, a = acc, v = vel)\n end\n end\n # Start by zeroing the FT sensor\n sleep(0.25)\n zero_ftsensor()\n local p_start = get_actual_tcp_pose()\n local p0 = p[0,0,0,0,0,0]\n local DIR_X = [1, 0, 0]\n if (num_fingers == 2):\n local dir_list = [DIR_X, -DIR_X, DIR_X, -DIR_X]\n local start_offset = [p[0,0,0,0,0,0], p[0,0,0,0,0,0], p[0,0,0,0,0,0.35], p[0,0,0,0,0,0.35]]\n local p_list = [p0, p0, p0, p0]\n elif (num_fingers == 3):\n local DIR_P1 = DIR_X\n local DIR_P2 = [-1 / 2, sqrt(3.0) / 2.0, 0]\n local DIR_P3 = [-1 / 2, -sqrt(3.0) / 2.0, 0]\n local dir_list = [DIR_P1, DIR_P2, DIR_P3, DIR_P1, DIR_P2, DIR_P3]\n local start_offset = [p[0,0,0,0,0,0], p[0,0,0,0,0,0], p[0,0,0,0,0,0], p[0,0,0,0,0,0.35], p[0,0,0,0,0,0.35], p[0,0,0,0,0,0.35]]\n local p_list = [p0, p0, p0, p0, p0, p0]\n else:\n popup(\"Number of fingers not supported\")\n halt\n end\n # Loop through directions\n local it = 0\n local dir_list_size = size(dir_list)\n local dir_list_length = dir_list_size[0]\n while(it < dir_list_length):\n # Move to starting position if more than 3 positions is stored then calculate a new starting position\n if(it < 3):\n movel(pose_trans(p_start, start_offset[it]), a = acc_move, v = velocity_move)\n else:\n local p_start_temp = pose_trans(pose_trans(p_start, compute_circle_center(p_list)), start_offset[it])\n local p_start_w_offset = pose_trans(p_start, start_offset[it])\n sanity_checked_move(p_start_w_offset, p_start_temp, max_radius_search, acc_move, velocity_move)\n end\n local p_start_temp = get_actual_tcp_pose()\n # Move into contact and store contact point\n sleep(0.1)\n local contact_point = ur_move_until_force(distance = max_radius_search, direction = [dir_list[it, 0], dir_list[it, 1], dir_list[it, 2]], velocity = velocity_search, acceleration = acc_move, stop_force = push_force)\n \n local dir = [dir_list[it, 0], dir_list[it, 1], dir_list[it, 2]]\n dir = normalize(dir) * 0.05\n contact_point = pose_trans(contact_point, p[dir[0], dir[1], dir[2], 0, 0, 0])\n p_list[it] = pose_trans(pose_inv(p_start), contact_point)\n # Move out of contact\n movel(p_start_temp, a = acc_move, v = velocity_move)\n it = it + 1\n end\n # Find circle center based on n stored points\n local center_offset_xy = compute_circle_center(p_list)\n local p_center = pose_trans(p_start, center_offset_xy)\n \n # Move the robot to the center if it can\n sanity_checked_move(p_start, p_center, max_radius_search, acc_move, velocity_move)\nend\n# End of Center to Object\n# Start of Move Into Contact\n###\n# Moves the robot into contact in the TCP direction set\n# @param force number Force that determines when a contact has been achieved\n# @param velocity number Velocity of the robot\n# @param acceleration number Acceleration of the robot\n# @param max_distance number Maximum distance that the robot searches\n# @param velocity_search number velocity_search\n# @param retract number Retract distance after a contact has been found\n# @param move_tcp_dir array TCP direction (3D vector)\n# @param zero_ft_on_start bool Determines if the force-torque sensor should be zeroed on start\n###\ndef ur_move_into_contact(force = 10, velocity = 0.05, acceleration = 0.1, max_distance = 0.25, retract = 0.0, move_tcp_dir = [0, 0, 1], zero_ft_on_start = True):\n # Zero the force torque sensor\n if (zero_ft_on_start):\n sleep(0.25)\n zero_ftsensor()\n end\n # Move the robot\n ur_move_until_force(max_distance, move_tcp_dir, velocity, acceleration, force)\n # If a retract distance is set, move the robot back to that position\n if (retract != 0):\n # Compute position offset from TCP direction and retract distance\n local position = normalize(move_tcp_dir) * retract\n movel(pose_trans(get_actual_tcp_pose(), p[position[0], position[1], position[2], 0, 0, 0]))\n end\nend\n# End of Move Into Contact\n# Start of Retract\n###\n# Retract in the TCP direction set\n# @param distance number Retraction distance\n# @param direction array TCP direction to move in (3D vector)\n# @param acceleration number Acceleration used by the robot\n# @param velocity number Velocity used by the robot\n###\ndef ur_retract(distance = -0.1, direction = [0, 0, 1], acceleration = 0.4, velocity = 0.1):\n local movement = normalize(direction) * distance\n movel(pose_trans(get_actual_tcp_pose(), p[movement[0], movement[1], movement[2], 0, 0, 0]), a = acceleration, v = velocity)\nend\n# End of Retract","nodeIDList":[]}} \ No newline at end of file diff --git a/tests/resources/dockerursim/programs/polyscopex/10.13.0/ur7e/blobs/version b/tests/resources/dockerursim/programs/polyscopex/10.13.0/ur7e/blobs/version new file mode 100644 index 000000000..720d64f4b Binary files /dev/null and b/tests/resources/dockerursim/programs/polyscopex/10.13.0/ur7e/blobs/version differ diff --git a/tests/resources/polyscopex_screenshot.py b/tests/resources/polyscopex_screenshot.py new file mode 100644 index 000000000..f02bc1a9d --- /dev/null +++ b/tests/resources/polyscopex_screenshot.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python3 +"""Take a screenshot of the PolyScope X UI with a real post-load delay for SPA rendering. + +Usage: polyscopex_screenshot.py +""" +import sys + +from playwright.sync_api import sync_playwright + + +def main(): + if len(sys.argv) != 4: + print(f"Usage: {sys.argv[0]} ") + sys.exit(1) + + url, output_file, delay_ms = sys.argv[1], sys.argv[2], int(sys.argv[3]) + + with sync_playwright() as p: + browser = p.chromium.launch(args=["--no-sandbox", "--disable-setuid-sandbox"]) + page = browser.new_page(viewport={"width": 1920, "height": 1080}) + page.goto(url, wait_until="load") + page.wait_for_timeout(delay_ms) + page.screenshot(path=output_file) + browser.close() + + +if __name__ == "__main__": + main() diff --git a/tests/test_dashboard_client_x.cpp b/tests/test_dashboard_client_x.cpp index 83432fd70..d8a8788a2 100644 --- a/tests/test_dashboard_client_x.cpp +++ b/tests/test_dashboard_client_x.cpp @@ -32,7 +32,15 @@ #include #include #include +#include +#include #include +#include +#ifndef _WIN32 +# include +# include +# include +#endif #include "gtest/gtest.h" #include "test_utils.h" #include "ur_client_library/comm/tcp_socket.h" @@ -64,11 +72,14 @@ class DashboardClientTestX : public ::testing::Test protected: void SetUp() { -#ifdef POLYSCOPE_X_TESTS_WITH_REMOTE_CONTROL -# if POLYSCOPE_X_TESTS_WITH_REMOTE_CONTROL == 1 - skip_remote_control_tests = false; -# endif -#endif + if (std::getenv("POLYSCOPE_X_TESTS_WITH_REMOTE_CONTROL") != nullptr) + { + std::string env_var = std::getenv("POLYSCOPE_X_TESTS_WITH_REMOTE_CONTROL"); + if (env_var != "" && parseBoolean(env_var)) + { + skip_remote_control_tests = false; + } + } urcl::comm::INotifier notifier; primary_client_.reset(new urcl::primary_interface::PrimaryClient(g_ROBOT_IP, notifier)); primary_client_->start(); @@ -151,6 +162,8 @@ TEST_F(DashboardClientTestX, unlock_protective_stop) GTEST_SKIP_("Skipping test that would require remote control to be enabled on robot"); } ASSERT_TRUE(dashboard_client_->connect()); + dashboard_client_->commandPowerOff(); + ASSERT_NO_THROW(waitForRobotMode(RobotMode::POWER_OFF)); dashboard_client_->commandPowerOn(); ASSERT_NO_THROW(waitForRobotMode(RobotMode::IDLE)); DashboardResponse response; @@ -204,7 +217,7 @@ TEST_F(DashboardClientTestX, program_interaction) auto resp = dashboard_client_->commandGetLoadedProgram(); return std::get(resp.data["program_name"]) == "wait_program"; }, - std::chrono::milliseconds(1000)); + std::chrono::milliseconds(5000)); } response = dashboard_client_->commandPowerOn(); ASSERT_TRUE(response.ok); @@ -507,6 +520,53 @@ TEST_F(DashboardClientTestX, microsecond_receive_timeout_makes_connect_fail) EXPECT_FALSE(dashboard_client_->connect()); } +class PolyScopeScreenshotListener : public ::testing::EmptyTestEventListener +{ +public: + void OnTestEnd(const ::testing::TestInfo& test_info) override + { + if (!test_info.result()->Failed()) + { + return; + } + + const char* dir_env = std::getenv("POLYSCOPE_X_SCREENSHOT_DIR"); + std::filesystem::path screenshot_dir = dir_env ? dir_env : "test_artifacts/screenshots"; + std::filesystem::create_directories(screenshot_dir); + + std::string filename = + (screenshot_dir / (std::string(test_info.test_suite_name()) + "." + test_info.name() + ".png")).string(); + std::string url = "http://" + g_ROBOT_IP; + std::string script = "../tests/resources/polyscopex_screenshot.py"; + std::string delay = "5000"; + +#ifndef _WIN32 + // Build argv as a proper array — no shell involved, so spaces and metacharacters + // in url, filename, or script path are passed through safely. + std::vector args = { const_cast("python3"), const_cast(script.c_str()), + const_cast(url.c_str()), const_cast(filename.c_str()), + const_cast(delay.c_str()), nullptr }; + + pid_t pid = fork(); + if (pid == 0) + { + int devnull = open("/dev/null", O_WRONLY); + if (devnull >= 0) + { + dup2(devnull, STDERR_FILENO); + close(devnull); + } + execvp("python3", args.data()); + _exit(1); + } + else if (pid > 0) + { + waitpid(pid, nullptr, 0); + } +#endif + } +}; + int main(int argc, char* argv[]) { ::testing::InitGoogleTest(&argc, argv); @@ -520,5 +580,7 @@ int main(int argc, char* argv[]) } } + ::testing::UnitTest::GetInstance()->listeners().Append(new PolyScopeScreenshotListener()); + return RUN_ALL_TESTS(); } diff --git a/tests/test_start_ursim.bats b/tests/test_start_ursim.bats index e4f74b05b..32a594b5e 100644 --- a/tests/test_start_ursim.bats +++ b/tests/test_start_ursim.bats @@ -484,6 +484,30 @@ setup() { [ "$program_mount" = "$target_dir" ] } +@test "citadel_db_mounted_when_specified_for_polyscopex" { + target_dir=$(mktemp -d) + run main -v 10.13.0 -c "$target_dir" -t + echo "$output" + [ $status -eq 0 ] + citadel_mount=$(echo "$output" | tail -n1 | grep -Po "\-v\ [\/\w+\.\-]+:\/citadelDB" | cut -d ':' -f1 | cut -d " " -f 2) + [ "$citadel_mount" = "$target_dir" ] +} + +@test "citadel_db_not_mounted_without_flag_for_polyscopex" { + run main -v 10.13.0 -t + echo "$output" + [ $status -eq 0 ] + [[ "$(echo "$output" | tail -n1)" != *"/citadelDB"* ]] +} + +@test "citadel_db_not_mounted_for_e_series" { + target_dir=$(mktemp -d) + run main -v 5.26.0 -c "$target_dir" -t + echo "$output" + [ $status -eq 0 ] + [[ "$(echo "$output" | tail -n1)" != *"/citadelDB"* ]] +} + @test "setting_ip_addresss" { run main -t -i 123.123.123.123 echo "$output"