Samples

Setup

Generate Planning Data

This example shows how to generate planning data for your robot cell, which is necessary to do before you can start the motion planner.

 1#include <Zivid/Motion/Application.h>
 2#include <Zivid/Motion/Generation.h>
 3
 4#include <iostream>
 5
 6namespace
 7{
 8    void printException(const std::exception &e, const int level = 0)
 9    {
10        std::cerr << std::string(level * 4, ' ') << (level ? "+ " : "") << "Exception: " << e.what() << '\n';
11        try
12        {
13            std::rethrow_if_nested(e);
14        }
15        catch(const std::exception &nestedException)
16        {
17            printException(nestedException, level + 1);
18        }
19        catch(...)
20        {}
21    }
22} // namespace
23
24int main()
25{
26    try
27    {
28        const Zivid::Motion::Application app;
29
30        // Specify which cell you want to generate data for. This is referenced later
31        // when using the data to instantiate a planner.
32        constexpr auto cellName = "demo_cell";
33
34        // Specify which profile you want to generate data for (testing or production)
35        constexpr auto profile = Zivid::Motion::Profile::testing;
36
37        std::cout << "Generating data for " << cellName << "...\n";
38        Zivid::Motion::generate(
39            app,
40            Zivid::Motion::PlannerSettings{ cellName, profile },
41            [](const double progress, const std::string &step) {
42                std::cout << "Generation progress on step " << step << ": " << progress << "% complete\n";
43            });
44        std::cout << "Generation complete. Results stored on drive under the specified cell name.\n";
45    }
46    catch(const std::exception &exception)
47    {
48        printException(exception);
49        return EXIT_FAILURE;
50    }
51    return EXIT_SUCCESS;
52}
 1from zividmotion import Application, PlannerSettings, Profile, generate
 2
 3
 4def _main() -> None:
 5    app = Application()
 6
 7    # Specify which cell you want to generate data for. This is referenced later
 8    # when using the data to instantiate a planner.
 9    cell_name = "demo_cell"
10
11    # Specify which profile you want to generate data for (testing or production)
12    profile = Profile.testing
13
14    print(f"Generating {profile} data for {cell_name}...")
15    generate(
16        app,
17        PlannerSettings(cell_name, profile),
18        progress_callback=lambda progress, step: print(f"Generation progress on step {step}: {progress:.2f}% complete"),
19    )
20    print("Generation complete. Results stored on drive under the specified cell name.")
21
22
23if __name__ == "__main__":
24    _main()

Package Planner Setup

This example shows how to package all the files necessary for running the motion planner into a zip archive, which can be easily distributed and unpacked on other machines.

Run this snippet on the workstation where you have completed the planner setup:

 1#include <Zivid/Motion/Application.h>
 2#include <Zivid/Motion/Packaging.h>
 3
 4#include <filesystem>
 5#include <iostream>
 6
 7namespace
 8{
 9    void printException(const std::exception &e, const int level = 0)
10    {
11        std::cerr << std::string(level * 4, ' ') << (level ? "+ " : "") << "Exception: " << e.what() << '\n';
12        try
13        {
14            std::rethrow_if_nested(e);
15        }
16        catch(const std::exception &nestedException)
17        {
18            printException(nestedException, level + 1);
19        }
20        catch(...)
21        {}
22    }
23} // namespace
24
25int main()
26{
27    try
28    {
29        const Zivid::Motion::Application app;
30        const std::filesystem::path outputPath = "demo_cell.zip";
31
32        // In this example, we package not only the configuration files for the cell, but also the generated data
33        // for the testing setup.
34        std::cout << "Packaging data...\n";
35        Zivid::Motion::packageCell(app, "demo_cell", outputPath, { Zivid::Motion::Profile::testing });
36
37        std::cout << "Data package stored at: " << std::filesystem::canonical(outputPath) << "\n";
38    }
39    catch(const std::exception &exception)
40    {
41        printException(exception);
42        return EXIT_FAILURE;
43    }
44    return EXIT_SUCCESS;
45}
 1from pathlib import Path
 2
 3from zividmotion import Application, Profile, package_cell
 4
 5
 6def _main() -> None:
 7    app = Application()
 8
 9    output_path = Path("demo_cell.zip")
10
11    # In this example, we package not only the configuration files for the cell, but also the generated data
12    # for the testing setup.
13    print("Packaging data...")
14    package_cell(app, cell_name="demo_cell", output_path=output_path, include_generated_data=[Profile.testing])
15    print(f"Data package stored at: {output_path.resolve()}")
16
17
18if __name__ == "__main__":
19    _main()

The recipients of the zip archive can then run this snippet to unpack the setup:

 1#include <Zivid/Motion/Application.h>
 2#include <Zivid/Motion/Packaging.h>
 3
 4#include <filesystem>
 5#include <iostream>
 6
 7namespace
 8{
 9    void printException(const std::exception &e, const int level = 0)
10    {
11        std::cerr << std::string(level * 4, ' ') << (level ? "+ " : "") << "Exception: " << e.what() << '\n';
12        try
13        {
14            std::rethrow_if_nested(e);
15        }
16        catch(const std::exception &nestedException)
17        {
18            printException(nestedException, level + 1);
19        }
20        catch(...)
21        {}
22    }
23} // namespace
24
25int main()
26{
27    try
28    {
29        const Zivid::Motion::Application app;
30        const std::filesystem::path packagePath = "demo_cell.zip";
31
32        std::cout << "Installing package from " << std::filesystem::canonical(packagePath) << "...\n";
33        Zivid::Motion::installPackage(app, packagePath);
34
35        std::cout << "Package installed.\n";
36    }
37    catch(const std::exception &exception)
38    {
39        printException(exception);
40        return EXIT_FAILURE;
41    }
42    return EXIT_SUCCESS;
43}
 1from pathlib import Path
 2
 3from zividmotion import Application, install_package
 4
 5
 6def _main() -> None:
 7    app = Application()
 8
 9    package_path = Path("demo_cell.zip")
10
11    print(f"Installing package from {package_path.resolve()}...")
12    install_package(app, package_path)
13
14    print("Package installed.")
15
16
17if __name__ == "__main__":
18    _main()

Visualize Cell

This example shows how to open a 3D visualization window for a configured robot cell. This is useful for inspecting your cell setup before generating or running the motion planner.

 1#include <Zivid/Motion/Application.h>
 2#include <Zivid/Motion/Visualizer.h>
 3
 4#include <iostream>
 5
 6namespace
 7{
 8    void printException(const std::exception &e, const int level = 0)
 9    {
10        std::cerr << std::string(level * 4, ' ') << (level ? "+ " : "") << "Exception: " << e.what() << '\n';
11        try
12        {
13            std::rethrow_if_nested(e);
14        }
15        catch(const std::exception &nestedException)
16        {
17            printException(nestedException, level + 1);
18        }
19        catch(...)
20        {}
21    }
22} // namespace
23
24int main()
25{
26    try
27    {
28        const Zivid::Motion::Application app;
29
30        // Specify the cell you want to visualize.
31        constexpr auto cellName = "demo_cell";
32
33        std::cout << "Opening visualizer for cell: " << cellName << "\n";
34        std::cout << "Close the window to exit.\n";
35
36        auto visualizer = Zivid::Motion::Visualizer::viewCell(app, cellName);
37        visualizer.wait();
38    }
39    catch(const std::exception &exception)
40    {
41        printException(exception);
42        return EXIT_FAILURE;
43    }
44    return EXIT_SUCCESS;
45}
 1from zividmotion import Application, Visualizer
 2
 3
 4def _main() -> None:
 5    app = Application()
 6
 7    # Specify the cell you want to visualize.
 8    cell_name = "demo_cell"
 9
10    print(f"Opening visualizer for cell: {cell_name}")
11    print("Close the window to exit.")
12
13    visualizer = Visualizer.view_cell(app, cell_name)
14    visualizer.wait()
15
16
17if __name__ == "__main__":
18    _main()

Planner

Basic Path Calls

Joint Goal

 1#include <Zivid/Motion/Application.h>
 2#include <Zivid/Motion/Planner.h>
 3#include <Zivid/Motion/Visualizer.h>
 4
 5#include <iostream>
 6#include <stdexcept>
 7
 8namespace
 9{
10    constexpr auto useVisualizer = true;
11
12    void printException(const std::exception &e, const int level = 0)
13    {
14        std::cerr << std::string(level * 4, ' ') << (level ? "+ " : "") << "Exception: " << e.what() << '\n';
15        try
16        {
17            std::rethrow_if_nested(e);
18        }
19        catch(const std::exception &nestedException)
20        {
21            printException(nestedException, level + 1);
22        }
23        catch(...)
24        {}
25    }
26
27    Zivid::Motion::Planner startPlanner(const Zivid::Motion::Application &app)
28    {
29        return app.createPlanner(
30            Zivid::Motion::PlannerSettings{
31                "demo_cell",
32                Zivid::Motion::Profile::testing,
33            });
34    }
35} // namespace
36
37int main()
38{
39    try
40    {
41        const Zivid::Motion::Application app;
42
43        std::cout << "Starting planner\n";
44        auto planner = startPlanner(app);
45        auto visualizer =
46            useVisualizer ? std::optional{ Zivid::Motion::Visualizer::viewPlanner(planner) } : std::nullopt;
47
48        const Zivid::Motion::Configuration startConfiguration{ 0.f, 0.f, 0.f, 0.f, 1.57f, 0.f };
49        const Zivid::Motion::Configuration goalConfiguration{ 1.57f, 0.f, 0.f, 0.f, 1.57f, 0.f };
50
51        Zivid::Motion::PathRequest pathRequest{};
52        pathRequest.goals = Zivid::Motion::Goals::fromConfigurations({ goalConfiguration });
53        pathRequest.description = "Path with joint goal";
54        const auto result = planner.path(Zivid::Motion::InitialState{ startConfiguration }, pathRequest);
55        if(!result)
56        {
57            throw std::runtime_error("Planning failed with result: " + result.toString());
58        }
59
60        std::cout << "Path result:\n" << result << "\n";
61
62        if(useVisualizer)
63        {
64            std::cout << "Close the window to exit.\n";
65            visualizer->wait();
66        }
67    }
68    catch(const std::exception &exception)
69    {
70        printException(exception);
71        std::cout << "Press enter to exit." << std::endl;
72        std::cin.get();
73        return EXIT_FAILURE;
74    }
75    return EXIT_SUCCESS;
76}
 1from typing import Optional
 2
 3from zividmotion import (
 4    Application,
 5    Configuration,
 6    Goals,
 7    InitialState,
 8    PathRequest,
 9    Planner,
10    PlannerSettings,
11    Profile,
12    Visualizer,
13)
14
15USE_VISUALIZER = True
16
17
18def _start_planner(app: Application) -> Planner:
19    planner_settings = PlannerSettings(cell_name="demo_cell", profile=Profile.testing)
20    return app.create_planner(planner_settings)
21
22
23def _main() -> None:
24    app = Application()
25
26    print("Starting planner")
27    planner = _start_planner(app)
28    visualizer: Optional[Visualizer] = Visualizer.view_planner(planner) if USE_VISUALIZER else None
29
30    start_configuration = Configuration([0.0, 0.0, 0.0, 0.0, 1.57, 0.0])
31    goal_configuration = Configuration([1.57, 0.0, 0.0, 0.0, 1.57, 0.0])
32
33    result = planner.path(
34        InitialState(start_configuration),
35        PathRequest(
36            goals=Goals.from_configurations([goal_configuration]),
37            description="Path with joint goal",
38        ),
39    )
40    if not result:
41        raise RuntimeError(f"Planning failed with error: {result.error}")
42
43    print(f"Path result: \n{result}\n")
44
45    if visualizer is not None:
46        print("Close the window to exit.")
47        visualizer.wait()
48
49
50if __name__ == "__main__":
51    _main()

Pose Goal

 1#include <Zivid/Motion/Application.h>
 2#include <Zivid/Motion/Planner.h>
 3#include <Zivid/Motion/Visualizer.h>
 4
 5#include <iostream>
 6
 7namespace
 8{
 9    constexpr auto useVisualizer = true;
10
11    void printException(const std::exception &e, const int level = 0)
12    {
13        std::cerr << std::string(level * 4, ' ') << (level ? "+ " : "") << "Exception: " << e.what() << '\n';
14        try
15        {
16            std::rethrow_if_nested(e);
17        }
18        catch(const std::exception &nestedException)
19        {
20            printException(nestedException, level + 1);
21        }
22        catch(...)
23        {}
24    }
25
26    Zivid::Motion::Planner startPlanner(const Zivid::Motion::Application &app)
27    {
28        return app.createPlanner(
29            Zivid::Motion::PlannerSettings{
30                "demo_cell",
31                Zivid::Motion::Profile::testing,
32            });
33    }
34} // namespace
35
36int main()
37{
38    try
39    {
40        const Zivid::Motion::Application app;
41
42        std::cout << "Starting planner\n";
43        auto planner = startPlanner(app);
44        auto visualizer =
45            useVisualizer ? std::optional{ Zivid::Motion::Visualizer::viewPlanner(planner) } : std::nullopt;
46
47        const Zivid::Motion::Configuration startConfiguration{ 0.f, 0.f, 0.f, 0.f, 1.57f, 0.f };
48        const Zivid::Motion::Pose poseGoal{ Zivid::Motion::Matrix4x4{
49            { 0.f, -1.f, 0.f, 0.f },
50            { -1.f, 0.f, 0.f, 1.45f },
51            { 0.f, 0.f, -1.f, 1.63f },
52            { 0.f, 0.f, 0.f, 1.f },
53        } };
54
55        const auto jointGoal = planner.computeInverseKinematics({ poseGoal }, startConfiguration);
56        if(jointGoal.noneValid())
57        {
58            throw std::runtime_error("No valid inverse kinematics solution found");
59        }
60
61        Zivid::Motion::PathRequest pathRequest{};
62        pathRequest.goals = jointGoal;
63        pathRequest.description = "Path with goal from pose";
64        const auto result = planner.path(Zivid::Motion::InitialState{ startConfiguration }, pathRequest);
65        if(!result)
66        {
67            throw std::runtime_error("Planning failed with result: " + result.toString());
68        }
69
70        std::cout << "Path result:\n" << result << "\n";
71
72        if(useVisualizer)
73        {
74            std::cout << "Close the window to exit.\n";
75            visualizer->wait();
76        }
77    }
78    catch(const std::exception &exception)
79    {
80        printException(exception);
81        std::cout << "Press enter to exit." << std::endl;
82        std::cin.get();
83        return EXIT_FAILURE;
84    }
85    return EXIT_SUCCESS;
86}
 1from typing import Optional
 2
 3from zividmotion import (
 4    Application,
 5    Configuration,
 6    InitialState,
 7    PathRequest,
 8    Planner,
 9    PlannerSettings,
10    Pose,
11    Profile,
12    Visualizer,
13)
14
15USE_VISUALIZER = True
16
17
18def _start_planner(app: Application) -> Planner:
19    planner_settings = PlannerSettings(cell_name="demo_cell", profile=Profile.testing)
20    return app.create_planner(planner_settings)
21
22
23def _main() -> None:
24    app = Application()
25
26    print("Starting planner")
27    planner = _start_planner(app)
28    visualizer: Optional[Visualizer] = Visualizer.view_planner(planner) if USE_VISUALIZER else None
29
30    start_configuration = Configuration([0.0, 0.0, 0.0, 0.0, 1.57, 0.0])
31    pose_goal = Pose(
32        [
33            [0.0, -1.0, 0.0, 0.0],
34            [-1.0, 0.0, 0.0, 1.45],
35            [0.0, 0.0, -1.0, 1.63],
36            [0.0, 0.0, 0.0, 1.0],
37        ]
38    )
39
40    joint_goal = planner.compute_inverse_kinematics(
41        poses=[pose_goal],
42        reference_configuration=start_configuration,
43    )
44    if joint_goal.none_valid():
45        raise RuntimeError("No valid inverse kinematics solution found")
46
47    result = planner.path(
48        InitialState(start_configuration),
49        PathRequest(
50            goals=joint_goal,
51            description="Path with goal from pose",
52        ),
53    )
54    if not result:
55        raise RuntimeError(f"Planning failed with error: {result.error}")
56
57    print(f"Path result: \n{result}\n")
58
59    if visualizer is not None:
60        print("Close the window to exit.")
61        visualizer.wait()
62
63
64if __name__ == "__main__":
65    _main()

Multiple Goals and Prioritization

 1#include <Zivid/Motion/Application.h>
 2#include <Zivid/Motion/Planner.h>
 3#include <Zivid/Motion/Visualizer.h>
 4
 5#include <cassert>
 6#include <iostream>
 7
 8namespace
 9{
10    constexpr auto useVisualizer = true;
11
12    void printException(const std::exception &e, const int level = 0)
13    {
14        std::cerr << std::string(level * 4, ' ') << (level ? "+ " : "") << "Exception: " << e.what() << '\n';
15        try
16        {
17            std::rethrow_if_nested(e);
18        }
19        catch(const std::exception &nestedException)
20        {
21            printException(nestedException, level + 1);
22        }
23        catch(...)
24        {}
25    }
26
27    Zivid::Motion::Planner startPlanner(const Zivid::Motion::Application &app)
28    {
29        return app.createPlanner(
30            Zivid::Motion::PlannerSettings{
31                "demo_cell",
32                Zivid::Motion::Profile::testing,
33            });
34    }
35} // namespace
36
37int main()
38{
39    try
40    {
41        const Zivid::Motion::Application app;
42
43        std::cout << "Starting planner\n";
44        auto planner = startPlanner(app);
45        auto visualizer =
46            useVisualizer ? std::optional{ Zivid::Motion::Visualizer::viewPlanner(planner) } : std::nullopt;
47
48        const Zivid::Motion::Configuration startConfiguration{ 0.f, 0.f, 0.f, 0.f, 1.57f, 0.f };
49        const Zivid::Motion::InitialState initialState{ startConfiguration };
50
51        const Zivid::Motion::Configuration goalFarAway{ 3.14f, 0.f, 0.f, 0.f, 1.57f, 0.f };
52        const Zivid::Motion::Configuration goalCloser{ 1.57f, 0.1f, -0.1f, 0.f, 1.57f, 0.f };
53        const Zivid::Motion::Configuration goalClosest{ 1.57f, 0.f, 0.f, 0.f, 1.57f, 0.f };
54        const auto goals = Zivid::Motion::Goals::fromConfigurations({ goalFarAway, goalCloser, goalClosest });
55
56        Zivid::Motion::PathRequest shortestPathRequest{};
57        shortestPathRequest.goals = goals;
58        shortestPathRequest.description = "Multiple goals - Default (ShortestPath) prioritization";
59        const auto resultShortestPath = planner.path(initialState, shortestPathRequest);
60        if(!resultShortestPath)
61        {
62            throw std::runtime_error("Planning with ShortestPath failed with result: " + resultShortestPath.toString());
63        }
64        assert(resultShortestPath.selectedGoalIdx == 2u);
65        std::cout << "ShortestPath selected goal index: " << resultShortestPath.selectedGoalIdx.value() << "\n";
66
67        Zivid::Motion::PathRequest listOrderRequest{};
68        listOrderRequest.goalPrioritizationMethod = Zivid::Motion::PathRequest::GoalPrioritizationMethod::listOrder;
69        listOrderRequest.goals = goals;
70        listOrderRequest.description = "Multiple goals - ListOrder prioritization";
71        const auto resultListOrder = planner.path(initialState, listOrderRequest);
72        if(!resultListOrder)
73        {
74            throw std::runtime_error("Planning with ListOrder failed with result: " + resultListOrder.toString());
75        }
76        assert(resultListOrder.selectedGoalIdx == 0u);
77        std::cout << "ListOrder selected goal index: " << resultListOrder.selectedGoalIdx.value() << "\n";
78
79        if(useVisualizer)
80        {
81            std::cout << "Close the window to exit.\n";
82            visualizer->wait();
83        }
84    }
85    catch(const std::exception &exception)
86    {
87        printException(exception);
88        std::cout << "Press enter to exit." << std::endl;
89        std::cin.get();
90        return EXIT_FAILURE;
91    }
92    return EXIT_SUCCESS;
93}
 1from typing import Optional
 2
 3from zividmotion import (
 4    Application,
 5    Configuration,
 6    Goals,
 7    InitialState,
 8    PathRequest,
 9    Planner,
10    PlannerSettings,
11    Profile,
12    Visualizer,
13)
14
15USE_VISUALIZER = True
16
17
18def _start_planner(app: Application) -> Planner:
19    planner_settings = PlannerSettings(cell_name="demo_cell", profile=Profile.testing)
20    return app.create_planner(planner_settings)
21
22
23def _main() -> None:
24    app = Application()
25
26    print("Starting planner")
27    planner = _start_planner(app)
28    visualizer: Optional[Visualizer] = Visualizer.view_planner(planner) if USE_VISUALIZER else None
29
30    start_configuration = Configuration([0.0, 0.0, 0.0, 0.0, 1.57, 0.0])
31    initial_state = InitialState(start_configuration)
32
33    goal_far_away = Configuration([3.14, 0.0, 0.0, 0.0, 1.57, 0.0])
34    goal_closer = Configuration([1.57, 0.1, -0.1, 0.0, 1.57, 0.0])
35    goal_closest = Configuration([1.57, 0.0, 0.0, 0.0, 1.57, 0.0])
36    goals = Goals.from_configurations([goal_far_away, goal_closer, goal_closest])
37
38    result_shortest_path = planner.path(
39        initial_state,
40        PathRequest(
41            goals=goals,
42            description="Multiple goals - Default (ShortestPath) prioritization",
43        ),
44    )
45    if not result_shortest_path:
46        raise RuntimeError(f"Planning with ShortestPath failed with error: {result_shortest_path.error}")
47    assert result_shortest_path.selected_goal_idx == 2
48    print(f"ShortestPath selected goal index: {result_shortest_path.selected_goal_idx}")
49
50    result_list_order = planner.path(
51        initial_state,
52        PathRequest(
53            goal_prioritization_method=PathRequest.GoalPrioritizationMethod.listOrder,
54            goals=goals,
55            description="Multiple goals - ListOrder prioritization",
56        ),
57    )
58    if not result_list_order:
59        raise RuntimeError(f"Planning with ListOrder failed with error: {result_list_order.error}")
60    assert result_list_order.selected_goal_idx == 0
61    print(f"ListOrder selected goal index: {result_list_order.selected_goal_idx}")
62
63    if visualizer is not None:
64        print("Close the window to exit.")
65        visualizer.wait()
66
67
68if __name__ == "__main__":
69    _main()

Setting Runtime Obstacles

Simple Obstacle Geometries

This example shows how you can add simple obstacles such as boxes to the motion planner’s dynamic environment model.

  1#include <Zivid/Motion/Application.h>
  2#include <Zivid/Motion/Planner.h>
  3#include <Zivid/Motion/Visualizer.h>
  4
  5#include <cassert>
  6#include <iostream>
  7
  8namespace
  9{
 10    constexpr auto useVisualizer = true;
 11
 12    void printException(const std::exception &e, const int level = 0)
 13    {
 14        std::cerr << std::string(level * 4, ' ') << (level ? "+ " : "") << "Exception: " << e.what() << '\n';
 15        try
 16        {
 17            std::rethrow_if_nested(e);
 18        }
 19        catch(const std::exception &nestedException)
 20        {
 21            printException(nestedException, level + 1);
 22        }
 23        catch(...)
 24        {}
 25    }
 26
 27    Zivid::Motion::Planner startPlanner(const Zivid::Motion::Application &app)
 28    {
 29        return app.createPlanner(
 30            Zivid::Motion::PlannerSettings{
 31                "demo_cell",
 32                Zivid::Motion::Profile::testing,
 33            });
 34    }
 35} // namespace
 36
 37int main()
 38{
 39    try
 40    {
 41        const Zivid::Motion::Application app;
 42
 43        std::cout << "Starting planner\n";
 44        auto planner = startPlanner(app);
 45        auto visualizer =
 46            useVisualizer ? std::optional{ Zivid::Motion::Visualizer::viewPlanner(planner) } : std::nullopt;
 47
 48        const Zivid::Motion::Configuration startConfiguration{ 0.f, 0.f, 0.f, 0.f, 1.57f, 0.f };
 49        const Zivid::Motion::Configuration goalConfiguration{ 1.57f, 0.f, 0.f, 0.f, 1.57f, 0.f };
 50        const Zivid::Motion::InitialState initialState{ startConfiguration };
 51        const auto goal = Zivid::Motion::Goals::fromConfigurations({ goalConfiguration });
 52
 53        Zivid::Motion::PathRequest withoutObstacleRequest{};
 54        withoutObstacleRequest.goals = goal;
 55        withoutObstacleRequest.description = "Without obstacle";
 56        const auto resultWithoutObstacle = planner.path(initialState, withoutObstacleRequest);
 57        if(!resultWithoutObstacle)
 58        {
 59            throw std::runtime_error(
 60                "Planning before obstacle is set failed with result: " + resultWithoutObstacle.toString());
 61        }
 62        assert(resultWithoutObstacle.path().size() == 1);
 63        std::cout << "Path before obstacle is set: " << resultWithoutObstacle.path().size() << " waypoint\n";
 64
 65        // Set an obstacle that obstructs the direct path
 66        const auto boxMesh = Zivid::Motion::Mesh::createBox(Zivid::Motion::Vector3f{ 0.2f, 0.2f, 0.2f })
 67                                 .transformInPlace(
 68                                     Zivid::Motion::Pose{ Zivid::Motion::Matrix4x4{
 69                                         { 1.f, 0.f, 0.f, 1.1f },
 70                                         { 0.f, 1.f, 0.f, 1.0f },
 71                                         { 0.f, 0.f, 1.f, 1.6f },
 72                                         { 0.f, 0.f, 0.f, 1.f },
 73                                     } });
 74        planner.setObstacles({ Zivid::Motion::Obstacle::fromMesh("box_obstacle", boxMesh) });
 75
 76        Zivid::Motion::PathRequest withObstacleRequest{};
 77        withObstacleRequest.goals = goal;
 78        withObstacleRequest.description = "With obstacle";
 79        const auto resultWithObstacle = planner.path(initialState, withObstacleRequest);
 80        if(!resultWithObstacle)
 81        {
 82            throw std::runtime_error(
 83                "Planning after obstacle is set failed with result: " + resultWithObstacle.toString());
 84        }
 85        assert(resultWithObstacle.path().size() > 1);
 86        std::cout << "Path after obstacle is set: " << resultWithObstacle.path().size() << " waypoints\n";
 87
 88        if(useVisualizer)
 89        {
 90            std::cout << "Close the window to exit.\n";
 91            visualizer->wait();
 92        }
 93    }
 94    catch(const std::exception &exception)
 95    {
 96        printException(exception);
 97        std::cout << "Press enter to exit." << std::endl;
 98        std::cin.get();
 99        return EXIT_FAILURE;
100    }
101    return EXIT_SUCCESS;
102}
 1from typing import Optional
 2
 3from zividmotion import (
 4    Application,
 5    Configuration,
 6    Goals,
 7    InitialState,
 8    Mesh,
 9    Obstacle,
10    PathRequest,
11    Planner,
12    PlannerSettings,
13    Pose,
14    Profile,
15    Vector3f,
16    Visualizer,
17)
18
19USE_VISUALIZER = True
20
21
22def _start_planner(app: Application) -> Planner:
23    planner_settings = PlannerSettings(cell_name="demo_cell", profile=Profile.testing)
24    return app.create_planner(planner_settings)
25
26
27def _main() -> None:
28    app = Application()
29
30    print("Starting planner")
31    planner = _start_planner(app)
32    visualizer: Optional[Visualizer] = Visualizer.view_planner(planner) if USE_VISUALIZER else None
33
34    start_configuration = Configuration([0.0, 0.0, 0.0, 0.0, 1.57, 0.0])
35    goal_configuration = Configuration([1.57, 0.0, 0.0, 0.0, 1.57, 0.0])
36    initial_state = InitialState(start_configuration=start_configuration)
37    goal = Goals.from_configurations([goal_configuration])
38
39    result_without_obstacle = planner.path(
40        initial_state=initial_state,
41        request=PathRequest(
42            goals=goal,
43            description="Without obstacle",
44        ),
45    )
46    if not result_without_obstacle:
47        raise RuntimeError(f"Planning before obstacle is set failed with error: {result_without_obstacle.error}")
48    assert len(result_without_obstacle.path) == 1
49    print(f"Path before obstacle is set: {len(result_without_obstacle.path)} waypoint")
50
51    # Set an obstacle that obstructs the direct path
52    box_mesh = Mesh.create_box(Vector3f(0.2, 0.2, 0.2)).transform_in_place(
53        Pose(
54            [
55                [1.0, 0.0, 0.0, 1.1],
56                [0.0, 1.0, 0.0, 1.0],
57                [0.0, 0.0, 1.0, 1.6],
58                [0.0, 0.0, 0.0, 1.0],
59            ]
60        )
61    )
62    planner.set_obstacles(
63        obstacles=[
64            Obstacle.from_mesh(name="box_obstacle", mesh=box_mesh),
65        ]
66    )
67
68    result_with_obstacle = planner.path(
69        initial_state=initial_state,
70        request=PathRequest(
71            goals=goal,
72            description="With obstacle",
73        ),
74    )
75    if not result_with_obstacle:
76        raise RuntimeError(f"Planning after obstacle is set failed with error: {result_with_obstacle.error}")
77    assert len(result_with_obstacle.path) > 1
78    print(f"Path after obstacle is set: {len(result_with_obstacle.path)} waypoints")
79
80    if visualizer is not None:
81        print("Close the window to exit.")
82        visualizer.wait()
83
84
85if __name__ == "__main__":
86    _main()

Obstacle from CAD File

This example shows how you can add custom cad files to the motion planner’s dynamic environment model. Note that cad files that represent static obstacles are more efficiently handled if added as static obstacles in the urdf files, rather than as dynamic obstacles in runtime.

  1#include <Zivid/Motion/Application.h>
  2#include <Zivid/Motion/Planner.h>
  3#include <Zivid/Motion/Visualizer.h>
  4
  5#include <assimp/Importer.hpp>
  6#include <assimp/postprocess.h>
  7#include <assimp/scene.h>
  8
  9#include <filesystem>
 10#include <iostream>
 11
 12namespace
 13{
 14    constexpr auto useVisualizer = true;
 15
 16    void printException(const std::exception &e, const int level = 0)
 17    {
 18        std::cerr << std::string(level * 4, ' ') << (level ? "+ " : "") << "Exception: " << e.what() << '\n';
 19        try
 20        {
 21            std::rethrow_if_nested(e);
 22        }
 23        catch(const std::exception &nestedException)
 24        {
 25            printException(nestedException, level + 1);
 26        }
 27        catch(...)
 28        {}
 29    }
 30
 31    Zivid::Motion::Planner startPlanner(const Zivid::Motion::Application &app)
 32    {
 33        return app.createPlanner(
 34            Zivid::Motion::PlannerSettings{
 35                "demo_cell",
 36                Zivid::Motion::Profile::testing,
 37            });
 38    }
 39
 40    Zivid::Motion::Triangles loadTrianglesFromCad(const std::filesystem::path &cadFilePath)
 41    {
 42        Assimp::Importer importer;
 43        const aiScene *scene = importer.ReadFile(cadFilePath, aiProcess_Triangulate | aiProcess_JoinIdenticalVertices);
 44
 45        if(!scene || !scene->HasMeshes())
 46        {
 47            throw std::runtime_error("Failed to load CAD file: " + cadFilePath.string());
 48        }
 49
 50        Zivid::Motion::Triangles triangles;
 51        for(unsigned int m = 0; m < scene->mNumMeshes; ++m)
 52        {
 53            const aiMesh *mesh = scene->mMeshes[m];
 54            for(unsigned int f = 0; f < mesh->mNumFaces; ++f)
 55            {
 56                const aiFace &face = mesh->mFaces[f];
 57                if(face.mNumIndices != 3)
 58                {
 59                    continue; // skip non-triangles (shouldn't happen with aiProcess_Triangulate)
 60                }
 61
 62                const auto toPoint = [&](unsigned int idx) {
 63                    const aiVector3D &v = mesh->mVertices[face.mIndices[idx]];
 64                    return Zivid::Motion::Vector3f{ v.x, v.y, v.z };
 65                };
 66
 67                triangles.push_back({ toPoint(0), toPoint(1), toPoint(2) });
 68            }
 69        }
 70
 71        if(triangles.empty())
 72        {
 73            throw std::runtime_error("No triangles found in CAD file: " + cadFilePath.filename().string());
 74        }
 75        return triangles;
 76    }
 77} // namespace
 78
 79int main()
 80{
 81    try
 82    {
 83        const Zivid::Motion::Application app;
 84
 85        std::cout << "Starting planner\n";
 86        auto planner = startPlanner(app);
 87        auto visualizer =
 88            useVisualizer ? std::optional{ Zivid::Motion::Visualizer::viewPlanner(planner) } : std::nullopt;
 89
 90        // Make sure the CAD uses meters as unit, not millimeters
 91        const std::filesystem::path cadFilePath = "path/to/obstacle.stl";
 92
 93        std::cout << "Loading CAD file from: " << cadFilePath << "\n";
 94        const auto triangles = loadTrianglesFromCad(cadFilePath);
 95
 96        planner.setObstacles(
 97            { Zivid::Motion::Obstacle::fromMesh("cad_obstacle", Zivid::Motion::Mesh::fromTriangles(triangles)) });
 98
 99        if(useVisualizer)
100        {
101            std::cout << "Close the window to exit.\n";
102            visualizer->wait();
103        }
104    }
105    catch(const std::exception &exception)
106    {
107        printException(exception);
108        std::cout << "Press enter to exit." << std::endl;
109        std::cin.get();
110        return EXIT_FAILURE;
111    }
112    return EXIT_SUCCESS;
113}

Install additional dependencies with:

pip install trimesh
 1from pathlib import Path
 2from typing import Optional
 3
 4import numpy as np
 5import trimesh
 6from zividmotion import (
 7    Application,
 8    Mesh,
 9    Obstacle,
10    Planner,
11    PlannerSettings,
12    Profile,
13    Triangles,
14    Visualizer,
15)
16
17USE_VISUALIZER = True
18
19
20def _start_planner(app: Application) -> Planner:
21    planner_settings = PlannerSettings(cell_name="demo_cell", profile=Profile.testing)
22    return app.create_planner(planner_settings)
23
24
25def _load_triangles_from_cad_file(cad_file_path: Path) -> Triangles:
26    mesh = trimesh.load(cad_file_path)
27    return Triangles(mesh.triangles.astype(np.float32))
28
29
30def _main() -> None:
31    app = Application()
32
33    print("Starting planner")
34    planner = _start_planner(app)
35    visualizer: Optional[Visualizer] = Visualizer.view_planner(planner) if USE_VISUALIZER else None
36
37    # Make sure the CAD uses meters as unit, not millimeters
38    cad_file_path = Path("path/to/obstacle.stl")
39
40    print("Loading CAD file from:", cad_file_path)
41    triangles = _load_triangles_from_cad_file(cad_file_path)
42
43    planner.set_obstacles(
44        obstacles=[
45            Obstacle.from_mesh(name="cad_obstacle", mesh=Mesh.from_triangles(triangles)),
46        ]
47    )
48
49    if visualizer is not None:
50        print("Close the window to exit.")
51        visualizer.wait()
52
53
54if __name__ == "__main__":
55    _main()

Obstacle from Zivid Point Cloud

This example shows how to add a point cloud from a Zivid camera to the motion planner’s dynamic environment model. This requires having the Zivid SDK installed, and being connected to a Zivid camera.

 1#include <Zivid/Motion/Application.h>
 2#include <Zivid/Motion/Planner.h>
 3#include <Zivid/Motion/Visualizer.h>
 4#include <Zivid/Zivid.h>
 5
 6#include <iostream>
 7
 8namespace
 9{
10    constexpr auto useVisualizer = true;
11    constexpr auto useFileCamera = true;
12
13    Zivid::Motion::Planner startPlanner(const Zivid::Motion::Application &app)
14    {
15        return app.createPlanner(
16            Zivid::Motion::PlannerSettings{
17                "demo_cell",
18                Zivid::Motion::Profile::testing,
19            });
20    }
21} // namespace
22
23int main()
24{
25    try
26    {
27        Zivid::Application cameraApp;
28        const Zivid::Motion::Application app;
29
30        std::cout << "Connecting to camera\n";
31        auto camera = useFileCamera ? cameraApp.createFileCamera("/path/to/FileCamera.zfc") : cameraApp.connectCamera();
32
33        std::cout << "Starting planner\n";
34        auto planner = startPlanner(app);
35        auto visualizer =
36            useVisualizer ? std::optional{ Zivid::Motion::Visualizer::viewPlanner(planner) } : std::nullopt;
37
38        // Retrieved from hand-eye calibration of your setup.
39        // In an eye-in-hand setup, this would be the handeye_transform * robot_capture_pose.
40        // In an eye-to-hand setup, this would simply be the handeye_transform.
41        const Zivid::Matrix4x4 cameraToBaseTransform{ {
42            { 1.f, 0.f, 0.f, 0.f },
43            { 0.f, 1.f, 0.f, 0.f },
44            { 0.f, 0.f, 1.f, 0.f },
45            { 0.f, 0.f, 0.f, 1.f },
46        } };
47
48        const Zivid::Matrix4x4 millimetersToMetersTransform{ {
49            { 0.001f, 0.f, 0.f, 0.f },
50            { 0.f, 0.001f, 0.f, 0.f },
51            { 0.f, 0.f, 0.001f, 0.f },
52            { 0.f, 0.f, 0.f, 1.f },
53        } };
54
55        std::cout << "Capturing with default capture settings\n";
56        const auto settings =
57            Zivid::Settings{ Zivid::Settings::Acquisitions{ Zivid::Settings::Acquisition{} },
58                             Zivid::Settings::Color{ Zivid::Settings2D{
59                                 Zivid::Settings2D::Acquisitions{ Zivid::Settings2D::Acquisition{} } } } };
60        const auto frame = camera.capture(settings);
61
62        // Downsampling improves speed, if you don't need the extra resolution
63        frame.pointCloud().downsample(Zivid::PointCloud::Downsampling::by4x4);
64
65        // Convert point cloud to unorganized point cloud
66        auto unorganizedPointCloud = frame.pointCloud().toUnorganizedPointCloud();
67
68        // Transform the point cloud from the camera frame to the base frame of the planner
69        unorganizedPointCloud.transform(cameraToBaseTransform);
70
71        // Transform the point cloud from millimeters to meters, which is the expected unit in the planner
72        unorganizedPointCloud.transform(millimetersToMetersTransform);
73
74        const auto xyzData = unorganizedPointCloud.copyPointsXYZ();
75        std::vector<Zivid::Motion::Vector3f> points;
76        points.reserve(xyzData.size());
77        for(const auto &p : xyzData)
78        {
79            constexpr auto mmToM = 1.f / 1000.f; // Convert from millimeters to meters
80            points.emplace_back(Zivid::Motion::Vector3f{ p.x * mmToM, p.y * mmToM, p.z * mmToM });
81        }
82        planner.setObstacles({ Zivid::Motion::Obstacle::fromPointCloud("point_cloud_obstacle", points) });
83
84        if(useVisualizer)
85        {
86            std::cout << "Close the window to exit.\n";
87            visualizer->wait();
88        }
89    }
90    catch(const std::exception &e)
91    {
92        std::cerr << "Error: " << e.what() << std::endl;
93        std::cout << "Press enter to exit." << std::endl;
94        std::cin.get();
95        return EXIT_FAILURE;
96    }
97    return EXIT_SUCCESS;
98}
 1from typing import Optional
 2
 3import numpy as np
 4import zivid
 5from zividmotion import Application, Obstacle, Planner, PlannerSettings, Profile, Visualizer
 6
 7USE_VISUALIZER = True
 8USE_FILE_CAMERA = True
 9
10
11def _start_planner(app: Application) -> Planner:
12    planner_settings = PlannerSettings(cell_name="demo_cell", profile=Profile.testing)
13    return app.create_planner(planner_settings)
14
15
16def _main() -> None:
17    camera_app = zivid.Application()
18    motion_app = Application()
19
20    print("Connecting to camera")
21    camera = (
22        camera_app.create_file_camera("/path/to/FileCamera.zfc") if USE_FILE_CAMERA else camera_app.connect_camera()
23    )
24
25    print("Starting planner")
26    planner = _start_planner(motion_app)
27    visualizer: Optional[Visualizer] = Visualizer.view_planner(planner) if USE_VISUALIZER else None
28
29    # Retrieved from hand-eye calibration of your setup.
30    # In an eye-in-hand setup, this would be the handeye_transform * robot_capture_pose.
31    # In an eye-to-hand setup, this would simply be the handeye_transform.
32    camera_to_base_transform = np.eye(4)
33
34    millimeters_to_meters_transform = np.diag([0.001, 0.001, 0.001, 1])
35
36    print("Capturing with default capture settings")
37    # Replace with your own capture settings for better results
38    capture_settings = zivid.Settings(
39        acquisitions=[zivid.Settings.Acquisition()],
40        color=zivid.Settings2D(acquisitions=[zivid.Settings2D.Acquisition()]),
41    )
42
43    frame = camera.capture(capture_settings)
44
45    # Downsampling improves speed, if you don't need the extra resolution
46    frame.point_cloud().downsample(zivid.PointCloud.Downsampling.by4x4)
47
48    # Convert point cloud to unorganized point cloud
49    unorganized_point_cloud = frame.point_cloud().to_unorganized_point_cloud()
50
51    # Transform the point cloud from the camera frame to the base frame of the planner
52    unorganized_point_cloud.transform(camera_to_base_transform)
53
54    # Transform the point cloud from millimeters to meters, which is the expected unit in the planner
55    unorganized_point_cloud.transform(millimeters_to_meters_transform)
56
57    points = Obstacle.PointCloud(unorganized_point_cloud.copy_data("xyz"))
58    obstacle = Obstacle.from_point_cloud(name="point_cloud_obstacle", points=points)
59    planner.set_obstacles(obstacles=[obstacle])
60
61    if USE_VISUALIZER:
62        input("Press enter to continue:")
63
64    # Set the obstacle again, but this time with color.
65    # Note that this has a slight performance impact, not recommended in production code.
66    planner.clear_obstacles()
67
68    colors = Obstacle.Colors(unorganized_point_cloud.copy_data("rgba"))
69    colored_obstacle = Obstacle.from_colored_point_cloud(name="colored_obstacle", points=points, colors=colors)
70    planner.set_obstacles(obstacles=[colored_obstacle])
71
72    if visualizer is not None:
73        print("Close the window to exit.")
74        visualizer.wait()
75
76
77if __name__ == "__main__":
78    _main()

Interaction Planning

Simple Touch Approach

This example shows how to use the Touch functionality with a compliant replaceable tool to plan an approach to an object to be interacted with in a simplified setup.

  1#include <Zivid/Motion/Application.h>
  2#include <Zivid/Motion/Mesh.h>
  3#include <Zivid/Motion/Planner.h>
  4#include <Zivid/Motion/Visualizer.h>
  5
  6#include <cassert>
  7#include <cmath>
  8#include <iostream>
  9#include <random>
 10
 11namespace
 12{
 13    constexpr auto useVisualizer = true;
 14
 15    void printException(const std::exception &e, const int level = 0)
 16    {
 17        std::cerr << std::string(level * 4, ' ') << (level ? "+ " : "") << "Exception: " << e.what() << '\n';
 18        try
 19        {
 20            std::rethrow_if_nested(e);
 21        }
 22        catch(const std::exception &nestedException)
 23        {
 24            printException(nestedException, level + 1);
 25        }
 26        catch(...)
 27        {}
 28    }
 29
 30    Zivid::Motion::Planner startPlanner(const Zivid::Motion::Application &app)
 31    {
 32        return app.createPlanner(
 33            Zivid::Motion::PlannerSettings{
 34                "demo_cell",
 35                Zivid::Motion::Profile::testing,
 36            });
 37    }
 38
 39    // Utility function for creating a dummy point-cloud obstacle
 40    std::vector<Zivid::Motion::Vector3f>
 41    generateSpherePoints(const Zivid::Motion::Vector3f &center, const float radius, const int numPoints)
 42    {
 43        // Fixed seed for determinism
 44        std::mt19937 rng(42);
 45        std::uniform_real_distribution<float> thetaDist(0, 2 * M_PI);
 46        std::uniform_real_distribution<float> phiDist(0, M_PI);
 47
 48        std::vector<Zivid::Motion::Vector3f> points;
 49        points.reserve(numPoints);
 50        for(int i = 0; i < numPoints; ++i)
 51        {
 52            const float theta = thetaDist(rng);
 53            const float phi = phiDist(rng);
 54            points.push_back(
 55                {
 56                    center.x + radius * std::sin(phi) * std::cos(theta),
 57                    center.y + radius * std::sin(phi) * std::sin(theta),
 58                    center.z + radius * std::cos(phi),
 59                });
 60        }
 61        return points;
 62    }
 63
 64    // Create a pick pose with the end-effector pointing downwards at the given point
 65    // (180-degree rotation around Y-axis)
 66    Zivid::Motion::Pose getPickPose(const Zivid::Motion::Vector3f &pickPoint)
 67    {
 68        return Zivid::Motion::Pose{ Zivid::Motion::Matrix4x4{
 69            { -1.f, 0.f, 0.f, pickPoint.x },
 70            { 0.f, 1.f, 0.f, pickPoint.y },
 71            { 0.f, 0.f, -1.f, pickPoint.z },
 72            { 0.f, 0.f, 0.f, 1.f },
 73        } };
 74    }
 75} // namespace
 76
 77int main()
 78{
 79    try
 80    {
 81        const Zivid::Motion::Application app;
 82
 83        std::cout << "Starting planner\n";
 84        auto planner = startPlanner(app);
 85        auto visualizer =
 86            useVisualizer ? std::optional{ Zivid::Motion::Visualizer::viewPlanner(planner) } : std::nullopt;
 87
 88        const Zivid::Motion::Configuration startConfiguration{ 0.f, 0.f, 0.f, 0.f, 1.57f, 0.f };
 89
 90        // Set a dummy obstacle to be interacted with
 91        constexpr Zivid::Motion::Vector3f obstacleCenter{ 1.1f, 1.0f, 0.9f };
 92        constexpr float obstacleRadius = 0.2f;
 93        planner.setObstacles(
 94            { Zivid::Motion::Obstacle::fromPointCloud(
 95                "interaction_object",
 96                Zivid::Motion::Obstacle::PointCloud{ generateSpherePoints(obstacleCenter, obstacleRadius, 1000) }) });
 97
 98        // Set a tool that simulates a vacuum gripper with a suction cup with 2 cm compliance
 99        constexpr Zivid::Motion::Vector3f toolBaseDimensions{ 0.15, 0.15, 0.15 };
100        auto suctionCupMatrix = Zivid::Motion::Matrix4x4::identity();
101        suctionCupMatrix(2, 3) = toolBaseDimensions.z;
102        constexpr Zivid::Motion::Vector3f suctionCupDimensions{ 0.05, 0.05, 0.02 };
103
104        auto rigidTool = Zivid::Motion::Mesh::createBox(toolBaseDimensions).bottomCenterTransformInPlace();
105        auto compliantTool = Zivid::Motion::Mesh::createBox(suctionCupDimensions)
106                                 .bottomCenterTransformInPlace()
107                                 .transformInPlace(Zivid::Motion::Pose{ suctionCupMatrix });
108        planner.setReplaceableTool(rigidTool, compliantTool);
109
110        // Set the new tcp to be at the center tip of the gripper, when the suction cup is compressed 1 cm for good contact
111        auto tcpMatrix = Zivid::Motion::Matrix4x4::identity();
112        tcpMatrix(2, 3) += toolBaseDimensions.z + suctionCupDimensions.z - 0.01f;
113        planner.setTcp(Zivid::Motion::Tcp{ Zivid::Motion::Pose{ tcpMatrix }, { 0.f, 0.f, 1.f } });
114
115        // Find the pick joint configuration, gripping the top of the object with the new TCP
116        constexpr Zivid::Motion::Vector3f pickPoint{ obstacleCenter.x,
117                                                     obstacleCenter.y,
118                                                     obstacleCenter.z + obstacleRadius };
119        const auto pickGoal = planner.computeInverseKinematics({ getPickPose(pickPoint) }, startConfiguration);
120        if(pickGoal.noneValid())
121        {
122            throw std::runtime_error("No valid inverse kinematics solution found for pick pose");
123        }
124
125        const Zivid::Motion::InitialState initialState{ startConfiguration };
126
127        // Path type defaults to Free
128        Zivid::Motion::PathRequest freeRequest{};
129        freeRequest.goals = pickGoal;
130        freeRequest.description = "Free path to pick goal";
131        const auto resultFree = planner.path(initialState, freeRequest);
132        // Expect blocked end with path type Free, since the compliant part of the tool has to enter the obstacle for the
133        // TCP to reach the goal
134        assert(resultFree.error == Zivid::Motion::PathResult::Error::blockedEnd);
135
136        Zivid::Motion::PathRequest touchRequest{};
137        touchRequest.type = Zivid::Motion::PathRequest::Type::touch;
138        touchRequest.goals = pickGoal;
139        touchRequest.description = "Touch path to pick goal";
140        const auto resultTouch = planner.path(initialState, touchRequest);
141        if(!resultTouch)
142        {
143            throw std::runtime_error("Planning with path type Touch failed with result: " + resultTouch.toString());
144        }
145
146        std::cout << "\nSuccessful touch path: " << resultTouch << "\n";
147
148        if(useVisualizer)
149        {
150            std::cout << "Close the window to exit.\n";
151            visualizer->wait();
152        }
153    }
154    catch(const std::exception &exception)
155    {
156        printException(exception);
157        std::cout << "Press enter to exit." << std::endl;
158        std::cin.get();
159        return EXIT_FAILURE;
160    }
161    return EXIT_SUCCESS;
162}

Install additional dependencies with:

pip install scipy
  1from typing import Optional
  2
  3import numpy as np
  4from scipy.spatial.transform import Rotation
  5from zividmotion import (
  6    Application,
  7    Configuration,
  8    InitialState,
  9    Matrix4x4,
 10    Mesh,
 11    Obstacle,
 12    PathRequest,
 13    PathResult,
 14    Planner,
 15    PlannerSettings,
 16    Pose,
 17    Profile,
 18    Tcp,
 19    Vector3f,
 20    Visualizer,
 21)
 22
 23USE_VISUALIZER = True
 24
 25
 26def _start_planner(app: Application) -> Planner:
 27    planner_settings = PlannerSettings(cell_name="demo_cell", profile=Profile.testing)
 28    return app.create_planner(planner_settings)
 29
 30
 31# Utility method for creating a dummy obstacle
 32def _generate_sphere_points(center_point: list[float], radius: float, num_points: int) -> Obstacle.PointCloud:
 33    np.random.seed(42)  # Set a fixed random seed for determinism
 34    angles = np.random.uniform(0.0, 1.0, size=(num_points, 2))
 35    theta = angles[:, 0] * 2 * np.pi
 36    phi = angles[:, 1] * np.pi
 37    points = np.empty((num_points, 3), dtype=np.float32)
 38    points[:, 0] = center_point[0] + radius * np.sin(phi) * np.cos(theta)
 39    points[:, 1] = center_point[1] + radius * np.sin(phi) * np.sin(theta)
 40    points[:, 2] = center_point[2] + radius * np.cos(phi)
 41    return Obstacle.PointCloud(points)
 42
 43
 44def _get_pick_pose(pick_point: list[float]) -> Pose:
 45    # Create a simple pick pose with the end-effector pointing downwards at the pick point
 46    pick_pose = np.eye(4, dtype=np.float32)
 47    pick_pose[:3, 3] = pick_point
 48    # Rotate 180 degrees around Y-axis to point downwards
 49    pick_pose[:3, :3] = Rotation.from_rotvec([0, np.pi, 0]).as_matrix()
 50    return Pose(pick_pose)
 51
 52
 53def _main() -> None:
 54    app = Application()
 55
 56    print("Starting planner")
 57    planner = _start_planner(app)
 58    visualizer: Optional[Visualizer] = Visualizer.view_planner(planner) if USE_VISUALIZER else None
 59
 60    start_configuration = Configuration([0.0, 0.0, 0.0, 0.0, 1.57, 0.0])
 61
 62    # Set a dummy obstacle to be interacted with
 63    center_point = [1.1, 1.0, 0.9]
 64    radius = 0.2
 65    planner.set_obstacles(
 66        obstacles=[
 67            Obstacle.from_point_cloud(
 68                name="interaction_object",
 69                points=_generate_sphere_points(center_point=center_point, radius=radius, num_points=1000),
 70            )
 71        ]
 72    )
 73
 74    # Set a tool that simulates a vacuum gripper with a suction cup with 2 cm compliance
 75    tool_base_dimensions = Vector3f(0.15, 0.15, 0.15)
 76    suction_cup_matrix = Matrix4x4.identity()
 77    suction_cup_matrix[2, 3] = tool_base_dimensions.z
 78    suction_cup_dimensions = Vector3f(0.05, 0.05, 0.02)
 79    rigid_tool = Mesh.create_box(tool_base_dimensions).bottom_center_transform_in_place()
 80    compliant_tool = (
 81        Mesh.create_box(suction_cup_dimensions)
 82        .bottom_center_transform_in_place()
 83        .transform_in_place(Pose(suction_cup_matrix))
 84    )
 85    planner.set_replaceable_tool(replaceable_tool=rigid_tool, compliant_section=compliant_tool)
 86
 87    # Set the new tcp to be at the center tip of the gripper, when the suction cup is compressed 1 cm for good contact
 88    tcp_matrix = Matrix4x4.identity()
 89    tcp_matrix[2, 3] += tool_base_dimensions.z + suction_cup_dimensions.z - 0.01
 90    planner.set_tcp(tcp=Tcp(transform=Pose(tcp_matrix), tool_direction=Vector3f(0.0, 0.0, 1.0)))
 91
 92    # Find the pick joint configuration, gripping the top of the object with the new TCP
 93    pick_point = [center_point[0], center_point[1], center_point[2] + radius]
 94    pick_pose = _get_pick_pose(pick_point)
 95
 96    pick_goal = planner.compute_inverse_kinematics(poses=[pick_pose], reference_configuration=start_configuration)
 97    if pick_goal.none_valid():
 98        raise RuntimeError("No valid inverse kinematics solution found for pick pose")
 99
100    initial_state = InitialState(start_configuration=start_configuration)
101    # Path type defaults to Free
102    result_free = planner.path(
103        initial_state=initial_state,
104        request=PathRequest(
105            goals=pick_goal,
106            description="Free path to pick goal",
107        ),
108    )
109    # Expect blocked end with path type Free, since the compliant part of the tool has to enter the obstacle for the
110    # TCP to reach the goal
111    assert result_free.error == PathResult.Error.blockedEnd
112
113    # Specify type touch
114    result_touch = planner.path(
115        initial_state=initial_state,
116        request=PathRequest(
117            type=PathRequest.Type.touch,
118            goals=pick_goal,
119            description="Touch path to pick goal",
120        ),
121    )
122    if not result_touch:
123        raise RuntimeError(f"Planning with path type Touch failed with error: {result_touch.error}")
124
125    print("\nSuccessful touch path:", result_touch.path)
126
127    if visualizer is not None:
128        print("Close the window to exit.")
129        visualizer.wait()
130
131
132if __name__ == "__main__":
133    _main()

Pick Approach and Retract with Replaceable Tool

This example shows how to do a simplified object pick, including approach and retract paths, with a replaceable tool and carried object.

  1#include <Zivid/Motion/Application.h>
  2#include <Zivid/Motion/Mesh.h>
  3#include <Zivid/Motion/Planner.h>
  4#include <Zivid/Motion/Visualizer.h>
  5
  6#include <cmath>
  7#include <iostream>
  8#include <random>
  9
 10namespace
 11{
 12    constexpr auto useVisualizer = true;
 13
 14    void printException(const std::exception &e, const int level = 0)
 15    {
 16        std::cerr << std::string(level * 4, ' ') << (level ? "+ " : "") << "Exception: " << e.what() << '\n';
 17        try
 18        {
 19            std::rethrow_if_nested(e);
 20        }
 21        catch(const std::exception &nestedException)
 22        {
 23            printException(nestedException, level + 1);
 24        }
 25        catch(...)
 26        {}
 27    }
 28
 29    Zivid::Motion::Planner startPlanner(const Zivid::Motion::Application &app)
 30    {
 31        return app.createPlanner(
 32            Zivid::Motion::PlannerSettings{
 33                "demo_cell",
 34                Zivid::Motion::Profile::testing,
 35            });
 36    }
 37
 38    // Utility function for creating a dummy point-cloud obstacle
 39    std::vector<Zivid::Motion::Vector3f>
 40    generateSpherePoints(const Zivid::Motion::Vector3f &center, const float radius, const int numPoints)
 41    {
 42        // Fixed seed for determinism
 43        std::mt19937 rng(42);
 44        std::uniform_real_distribution<float> thetaDist(0, 2 * M_PI);
 45        std::uniform_real_distribution<float> phiDist(0, M_PI);
 46
 47        std::vector<Zivid::Motion::Vector3f> points;
 48        points.reserve(numPoints);
 49        for(int i = 0; i < numPoints; ++i)
 50        {
 51            const float theta = thetaDist(rng);
 52            const float phi = phiDist(rng);
 53            points.push_back(
 54                {
 55                    center.x + radius * std::sin(phi) * std::cos(theta),
 56                    center.y + radius * std::sin(phi) * std::sin(theta),
 57                    center.z + radius * std::cos(phi),
 58                });
 59        }
 60        return points;
 61    }
 62
 63    std::vector<Zivid::Motion::Obstacle> getDummyObstacles(const Zivid::Motion::Vector3f &center, const float radius)
 64    {
 65        std::vector<Zivid::Motion::Obstacle> obstacles;
 66        for(const float dx : { -2.f * radius, 0.f, 2.f * radius })
 67        {
 68            for(const float dy : { -2.f * radius, 0.f, 2.f * radius })
 69            {
 70                const Zivid::Motion::Vector3f c{ center.x + dx, center.y + dy, center.z };
 71                const auto name = "obstacle_" + std::to_string(obstacles.size());
 72                obstacles.push_back(
 73                    Zivid::Motion::Obstacle::fromPointCloud(
 74                        name, Zivid::Motion::Obstacle::PointCloud{ generateSpherePoints(c, radius, 400) }));
 75            }
 76        }
 77        return obstacles;
 78    }
 79} // namespace
 80
 81int main()
 82{
 83    try
 84    {
 85        const Zivid::Motion::Application app;
 86
 87        std::cout << "Starting planner\n";
 88        auto planner = startPlanner(app);
 89        auto visualizer =
 90            useVisualizer ? std::optional{ Zivid::Motion::Visualizer::viewPlanner(planner) } : std::nullopt;
 91
 92        constexpr Zivid::Motion::Vector3f obstacleCenter{ 1.1f, 1.0f, 0.2f };
 93        constexpr float obstacleRadius = 0.15f;
 94        planner.setObstacles(getDummyObstacles(obstacleCenter, obstacleRadius));
 95
 96        // For example purposes, simulate a tool mounted asymmetrically at 45 degrees with a compliant tip
 97        // (e.g. a vacuum gripper with compliant suction cups)
 98        constexpr float angle = M_PI_4;
 99        const float cosA = std::cos(angle);
100        const float sinA = std::sin(angle);
101        const Zivid::Motion::Pose toolTransform{ Zivid::Motion::Matrix4x4{
102            { cosA, -sinA, 0.f, 0.f },
103            { sinA, cosA, 0.f, 0.f },
104            { 0.f, 0.f, 1.f, 0.05f },
105            { 0.f, 0.f, 0.f, 1.f },
106        } };
107        constexpr Zivid::Motion::Vector3f rigidToolDimensions{ 0.35f, 0.35f, 0.04f };
108        // Specify the last 2cm of the tool as the compliant area
109        const Zivid::Motion::Pose compliantToolTransform{ Zivid::Motion::Matrix4x4{
110            { 1.f, 0.f, 0.f, 0.f },
111            { 0.f, 1.f, 0.f, 0.f },
112            { 0.f, 0.f, 1.f, rigidToolDimensions.z },
113            { 0.f, 0.f, 0.f, 1.f },
114        } };
115        constexpr Zivid::Motion::Vector3f compliantToolDimensions{ 0.4f, 0.4f, 0.02f };
116        auto rigidTool = Zivid::Motion::Mesh::createBox(rigidToolDimensions)
117                             .bottomCenterTransformInPlace()
118                             .transformInPlace(toolTransform);
119        auto compliantTool = Zivid::Motion::Mesh::createBox(compliantToolDimensions)
120                                 .bottomCenterTransformInPlace()
121                                 .transformInPlace(compliantToolTransform)
122                                 .transformInPlace(toolTransform);
123        planner.setReplaceableTool(rigidTool, compliantTool);
124
125        // Set the new tcp to be at the center tip of the tool, with the compliant area compressed by 1cm for good contact
126        auto tcpTransform = toolTransform.toMatrix();
127        tcpTransform(2, 3) += rigidToolDimensions.z + compliantToolDimensions.z - 0.01f;
128        planner.setTcp(Zivid::Motion::Tcp{ Zivid::Motion::Pose{ tcpTransform }, { 0.f, 0.f, 1.f } });
129
130        const Zivid::Motion::Configuration startConfiguration{ 0.f, 0.f, 0.f, 0.f, 1.57f, 0.f };
131
132        // Define a pick pose on the top center of the object with the end-effector rotated 180 degrees around Y to point downwards
133        const Zivid::Motion::Pose pickPose{ Zivid::Motion::Matrix4x4{
134            { -1.f, 0.f, 0.f, obstacleCenter.x },
135            { 0.f, 1.f, 0.f, obstacleCenter.y },
136            { 0.f, 0.f, -1.f, obstacleCenter.z + obstacleRadius },
137            { 0.f, 0.f, 0.f, 1.f },
138        } };
139        // Compute the pick joint configuration with the new tcp
140        const auto pickGoal = planner.computeInverseKinematics({ pickPose }, startConfiguration);
141        if(pickGoal.noneValid())
142        {
143            throw std::runtime_error("No valid inverse kinematics solution found for pick pose");
144        }
145
146        // Compute a pick approach path with the new tool
147        Zivid::Motion::PathRequest approachRequest{};
148        approachRequest.type = Zivid::Motion::PathRequest::Type::touch;
149        approachRequest.goals = pickGoal;
150        approachRequest.description = "Pick approach";
151        const auto approachResult = planner.path(Zivid::Motion::InitialState{ startConfiguration }, approachRequest);
152        if(!approachResult)
153        {
154            throw std::runtime_error("Planning pick approach failed with result: " + approachResult.toString());
155        }
156
157        // Set carried object (bounding box around the picked obstacle)
158        constexpr float objectSize = obstacleRadius * 2.f;
159        planner.setCarriedObject(
160            Zivid::Motion::Mesh::createBox(Zivid::Motion::Vector3f{ objectSize, objectSize, objectSize })
161                .bottomCenterTransformInPlace());
162
163        // Compute a pick retract path with custom retract direction
164        // This retract direction is the same as the negative tcp tool direction expressed in the base frame when the robot
165        // is in the start configuration for this path call. Meaning it in this case is a redundant specification, just to
166        // show the signature for example purposes.
167        Zivid::Motion::PathRequest retractRequest{};
168        retractRequest.retractDirection = { 0.f, 0.f, 1.f };
169        retractRequest.goals = Zivid::Motion::Goals::fromConfigurations({ startConfiguration });
170        retractRequest.description = "Pick retract";
171        const auto retractResult = planner.path(Zivid::Motion::InitialState{ approachResult }, retractRequest);
172        if(!retractResult)
173        {
174            throw std::runtime_error("Planning pick retract failed with result: " + retractResult.toString());
175        }
176
177        std::cout << retractResult << "\n";
178
179        if(useVisualizer)
180        {
181            std::cout << "Close the window to exit.\n";
182            visualizer->wait();
183        }
184    }
185    catch(const std::exception &exception)
186    {
187        printException(exception);
188        std::cout << "Press enter to exit." << std::endl;
189        std::cin.get();
190        return EXIT_FAILURE;
191    }
192    return EXIT_SUCCESS;
193}

Install additional dependencies with:

pip install scipy
  1from typing import Optional
  2
  3import numpy as np
  4from scipy.spatial.transform import Rotation
  5from zividmotion import (
  6    Application,
  7    Configuration,
  8    Goals,
  9    InitialState,
 10    Matrix4x4,
 11    Mesh,
 12    Obstacle,
 13    PathRequest,
 14    Planner,
 15    PlannerSettings,
 16    Pose,
 17    Profile,
 18    Tcp,
 19    Vector3f,
 20    Visualizer,
 21)
 22
 23USE_VISUALIZER = True
 24
 25
 26def _start_planner(app: Application) -> Planner:
 27    planner_settings = PlannerSettings(cell_name="demo_cell", profile=Profile.testing)
 28    return app.create_planner(planner_settings)
 29
 30
 31# Utility method for creating a dummy obstacle
 32def _generate_sphere_points(center_point: list[float], radius: float, num_points: int) -> Obstacle.PointCloud:
 33    np.random.seed(42)  # Set a fixed random seed for determinism
 34    angles = np.random.uniform(0.0, 1.0, size=(num_points, 2))
 35    theta = angles[:, 0] * 2 * np.pi
 36    phi = angles[:, 1] * np.pi
 37    points = np.empty((num_points, 3), dtype=np.float32)
 38    points[:, 0] = center_point[0] + radius * np.sin(phi) * np.cos(theta)
 39    points[:, 1] = center_point[1] + radius * np.sin(phi) * np.sin(theta)
 40    points[:, 2] = center_point[2] + radius * np.cos(phi)
 41    return Obstacle.PointCloud(points)
 42
 43
 44def _get_dummy_obstacles(center_point: list[float], radius: float) -> list[Obstacle]:
 45    obstacles: list[Obstacle] = []
 46    for x in [center_point[0] - 2 * radius, center_point[0], center_point[0] + 2 * radius]:
 47        for y in [center_point[1] - 2 * radius, center_point[1], center_point[1] + 2 * radius]:
 48            center = [x, y, center_point[2]]
 49            points = _generate_sphere_points(center_point=center, radius=radius, num_points=400)
 50            obstacles.append(Obstacle.from_point_cloud(name=f"obstacle_{len(obstacles)}", points=points))
 51    return obstacles
 52
 53
 54def _main() -> None:
 55    app = Application()
 56
 57    print("Starting planner")
 58    planner = _start_planner(app)
 59    visualizer: Optional[Visualizer] = Visualizer.view_planner(planner) if USE_VISUALIZER else None
 60
 61    obstacle_center = [1.1, 1.0, 0.2]
 62    obstacle_radius = 0.15
 63    planner.set_obstacles(obstacles=_get_dummy_obstacles(center_point=obstacle_center, radius=obstacle_radius))
 64
 65    # For example purposes, simulate a tool mounted asymmetrically at 45 degrees with a compliant tip
 66    # (e.g. a vacuum gripper with compliant suction cups)
 67    tool_matrix = np.eye(4)
 68    tool_matrix[1, 3] = 0.05
 69    tool_matrix[:3, :3] = Rotation.from_rotvec([0, 0, np.pi / 4]).as_matrix()
 70    rigid_tool_dimensions = Vector3f(0.35, 0.35, 0.04)
 71    # Specify the last 2cm of the tool as the compliant area
 72    compliant_matrix = Matrix4x4.identity()
 73    compliant_matrix[2, 3] = rigid_tool_dimensions.z
 74    compliant_tool_dimensions = Vector3f(0.35, 0.35, 0.02)
 75    rigid_tool = (
 76        Mesh.create_box(rigid_tool_dimensions).bottom_center_transform_in_place().transform_in_place(Pose(tool_matrix))
 77    )
 78    compliant_tool = (
 79        Mesh.create_box(compliant_tool_dimensions)
 80        .bottom_center_transform_in_place()
 81        .transform_in_place(Pose(tool_matrix @ compliant_matrix))
 82    )
 83    planner.set_replaceable_tool(replaceable_tool=rigid_tool, compliant_section=compliant_tool)
 84
 85    # Set the new tcp to be at the center tip of the tool, with the compliant area compressed by 1cm for good contact
 86    tcp_matrix = tool_matrix.copy()
 87    tcp_matrix[2, 3] += rigid_tool_dimensions.z + compliant_tool_dimensions.z - 0.01
 88    planner.set_tcp(tcp=Tcp(transform=Pose(tcp_matrix), tool_direction=Vector3f(0.0, 0.0, 1.0)))
 89
 90    start_configuration = Configuration([0.0, 0.0, 0.0, 0.0, 1.57, 0.0])
 91
 92    # Define a pick pose on the top center of the object with the end-effector rotated 180 degrees around Y to point downwards
 93    pick_position = [obstacle_center[0], obstacle_center[1], obstacle_center[2] + obstacle_radius]
 94    pick_matrix = np.eye(4)
 95    pick_matrix[:3, 3] = pick_position
 96    pick_matrix[:3, :3] = Rotation.from_rotvec([0, np.pi, 0]).as_matrix()
 97    pick_pose = Pose(pick_matrix)
 98
 99    # Compute the pick joint configuration with the new tcp
100    pick_goal = planner.compute_inverse_kinematics(poses=[pick_pose], reference_configuration=start_configuration)
101    if pick_goal.none_valid():
102        raise RuntimeError("No valid inverse kinematics solution found for pick pose")
103
104    # Compute a pick approach path with the new tool
105    approach_result = planner.path(
106        initial_state=InitialState(start_configuration=start_configuration),
107        request=PathRequest(
108            type=PathRequest.Type.touch,
109            goals=pick_goal,
110            description="Pick approach",
111        ),
112    )
113    if not approach_result:
114        raise RuntimeError(f"Planning pick approach failed with error: {approach_result.error}")
115
116    # Set carried object (bounding box around the picked obstacle)
117    obstacle_size = obstacle_radius * 2
118    planner.set_carried_object(
119        carried_object=Mesh.create_box(
120            Vector3f(obstacle_size, obstacle_size, obstacle_size)
121        ).bottom_center_transform_in_place()
122    )
123
124    # Compute a pick retract path with custom retract direction
125    # This retract direction is the same as the negative tcp tool direction expressed in the base frame when the robot
126    # is in the start configuration for this path call. Meaning it in this case is a redundant specification, just to
127    # show the signature for example purposes.
128    retract_result = planner.path(
129        initial_state=InitialState(previous_result=approach_result),
130        request=PathRequest(
131            retract_direction=Vector3f(0.0, 0.0, 1.0),
132            goals=Goals.from_configurations([start_configuration]),
133            description="Pick retract",
134        ),
135    )
136    if not retract_result:
137        raise RuntimeError(f"Planning pick retract failed with error: {retract_result.error}")
138
139    print(retract_result)
140
141    if visualizer is not None:
142        print("Close the window to exit.")
143        visualizer.wait()
144
145
146if __name__ == "__main__":
147    _main()

Robot Attachments

Path Planning with Attachment

  1#include <Zivid/Motion/Application.h>
  2#include <Zivid/Motion/Planner.h>
  3#include <Zivid/Motion/Visualizer.h>
  4
  5#include <cassert>
  6#include <cmath>
  7#include <iostream>
  8#include <random>
  9
 10namespace
 11{
 12    constexpr auto useVisualizer = true;
 13
 14    void printException(const std::exception &e, const int level = 0)
 15    {
 16        std::cerr << std::string(level * 4, ' ') << (level ? "+ " : "") << "Exception: " << e.what() << '\n';
 17        try
 18        {
 19            std::rethrow_if_nested(e);
 20        }
 21        catch(const std::exception &nestedException)
 22        {
 23            printException(nestedException, level + 1);
 24        }
 25        catch(...)
 26        {}
 27    }
 28
 29    Zivid::Motion::Planner startPlanner(const Zivid::Motion::Application &app)
 30    {
 31        return app.createPlanner(
 32            Zivid::Motion::PlannerSettings{
 33                "demo_cell",
 34                Zivid::Motion::Profile::testing,
 35            });
 36    }
 37
 38    // Utility function for creating a dummy point-cloud obstacle
 39    std::vector<Zivid::Motion::Vector3f>
 40    generateSpherePoints(const Zivid::Motion::Vector3f &center, const float radius, const int numPoints)
 41    {
 42        // Fixed seed for determinism
 43        std::mt19937 rng(42);
 44        std::uniform_real_distribution<float> thetaDist(0, 2 * M_PI);
 45        std::uniform_real_distribution<float> phiDist(0, M_PI);
 46
 47        std::vector<Zivid::Motion::Vector3f> points;
 48        points.reserve(numPoints);
 49        for(int i = 0; i < numPoints; ++i)
 50        {
 51            const float theta = thetaDist(rng);
 52            const float phi = phiDist(rng);
 53            points.push_back(
 54                {
 55                    center.x + radius * std::sin(phi) * std::cos(theta),
 56                    center.y + radius * std::sin(phi) * std::sin(theta),
 57                    center.z + radius * std::cos(phi),
 58                });
 59        }
 60        return points;
 61    }
 62} // namespace
 63
 64int main()
 65{
 66    try
 67    {
 68        const Zivid::Motion::Application app;
 69
 70        std::cout << "Starting planner\n";
 71        auto planner = startPlanner(app);
 72        auto visualizer =
 73            useVisualizer ? std::optional{ Zivid::Motion::Visualizer::viewPlanner(planner) } : std::nullopt;
 74
 75        const Zivid::Motion::Configuration startConfiguration{ 0.f, 0.f, 0.f, 0.f, 1.57f, 0.f };
 76        const Zivid::Motion::Configuration goalConfiguration{ 1.57f, 0.f, 0.f, 0.f, 1.57f, 0.f };
 77
 78        // Must match a name in the attachments definition in the planner config file
 79        const std::string attachmentName = "demo_attachment";
 80
 81        // Set an obstacle that obstructs the direct path for the attachment
 82        planner.setObstacles(
 83            { Zivid::Motion::Obstacle::fromPointCloud(
 84                "sphere_obstacle", generateSpherePoints({ 1.1f, 1.0f, 1.2f }, 0.2f, 1000)) });
 85
 86        const Zivid::Motion::InitialState initialState{ startConfiguration };
 87        const auto goal = Zivid::Motion::Goals::fromConfigurations({ goalConfiguration });
 88
 89        Zivid::Motion::PathRequest withoutAttachmentRequest{};
 90        withoutAttachmentRequest.goals = goal;
 91        withoutAttachmentRequest.description = "Path without attachment";
 92        const auto resultWithoutAttachment = planner.path(initialState, withoutAttachmentRequest);
 93        if(!resultWithoutAttachment)
 94        {
 95            throw std::runtime_error(
 96                "Planning without active attachment failed with result: " + resultWithoutAttachment.toString());
 97        }
 98        assert(resultWithoutAttachment.path().size() == 1);
 99        std::cout << "Path without attachment: " << resultWithoutAttachment.path().size() << " waypoints\n";
100
101        std::cout << "Setting attachment " << attachmentName << "\n";
102        planner.setAttachments({ attachmentName });
103
104        Zivid::Motion::PathRequest withAttachmentRequest{};
105        withAttachmentRequest.goals = goal;
106        withAttachmentRequest.description = "Path with attachment";
107        const auto resultWithAttachment = planner.path(initialState, withAttachmentRequest);
108        if(!resultWithAttachment)
109        {
110            std::cout << "Planning with active attachment failed with result: " << resultWithAttachment.toString()
111                      << "\n";
112        }
113        assert(resultWithAttachment.path().size() > 1);
114        std::cout << "With attachment: " << resultWithAttachment.path().size() << " waypoints\n";
115
116        if(useVisualizer)
117        {
118            std::cout << "Close the window to exit.\n";
119            visualizer->wait();
120        }
121    }
122    catch(const std::exception &exception)
123    {
124        printException(exception);
125        std::cout << "Press enter to exit." << std::endl;
126        std::cin.get();
127        return EXIT_FAILURE;
128    }
129    return EXIT_SUCCESS;
130}
 1from typing import Optional
 2
 3import numpy as np
 4from zividmotion import (
 5    Application,
 6    Configuration,
 7    Goals,
 8    InitialState,
 9    Obstacle,
10    PathRequest,
11    Planner,
12    PlannerSettings,
13    Profile,
14    Visualizer,
15)
16
17USE_VISUALIZER = True
18
19
20def _start_planner(app: Application) -> Planner:
21    planner_settings = PlannerSettings(cell_name="demo_cell", profile=Profile.testing)
22    return app.create_planner(planner_settings)
23
24
25# Utility method for creating a dummy obstacle
26def _generate_sphere_points(center_point: list[float], radius: float, num_points: int) -> Obstacle.PointCloud:
27    np.random.seed(42)  # Set a fixed random seed for determinism
28    angles = np.random.uniform(0.0, 1.0, size=(num_points, 2))
29    theta = angles[:, 0] * 2 * np.pi
30    phi = angles[:, 1] * np.pi
31    points = np.empty((num_points, 3), dtype=np.float32)
32    points[:, 0] = center_point[0] + radius * np.sin(phi) * np.cos(theta)
33    points[:, 1] = center_point[1] + radius * np.sin(phi) * np.sin(theta)
34    points[:, 2] = center_point[2] + radius * np.cos(phi)
35    return Obstacle.PointCloud(points)
36
37
38def _main() -> None:
39    app = Application()
40
41    print("Starting planner")
42    planner = _start_planner(app)
43    visualizer: Optional[Visualizer] = Visualizer.view_planner(planner) if USE_VISUALIZER else None
44
45    start_configuration = Configuration([0.0, 0.0, 0.0, 0.0, 1.57, 0.0])
46    goal_configuration = Configuration([1.57, 0.0, 0.0, 0.0, 1.57, 0.0])
47    attachment_name = "demo_attachment"  # Must match a name in the attachments definition in the planner config file
48
49    # Set an obstacle that obstructs the direct path for the attachment
50    planner.set_obstacles(
51        obstacles=[
52            Obstacle.from_point_cloud(
53                name="sphere_obstacle",
54                points=_generate_sphere_points(center_point=[1.1, 1.0, 1.2], radius=0.2, num_points=1000),
55            )
56        ]
57    )
58
59    initial_state = InitialState(start_configuration=start_configuration)
60    goal = Goals.from_configurations([goal_configuration])
61
62    result_without_attachment = planner.path(
63        initial_state=initial_state,
64        request=PathRequest(
65            goals=goal,
66            description="Path without attachment",
67        ),
68    )
69    if not result_without_attachment:
70        raise RuntimeError(f"Planning without active attachment failed with error: {result_without_attachment.error}")
71    assert len(result_without_attachment.path) == 1
72    print(f"Path without attachment: {len(result_without_attachment.path)} waypoints")
73
74    print("Setting attachment", attachment_name)
75    planner.set_attachments(attachments=[attachment_name])
76
77    result_with_attachment = planner.path(
78        initial_state=initial_state,
79        request=PathRequest(
80            goals=goal,
81            description="Path with attachment",
82        ),
83    )
84    if not result_with_attachment:
85        print(f"Planning with active attachment failed with error: {result_with_attachment.error}")
86    assert len(result_with_attachment.path) > 1
87    print(f"Path with attachment: {len(result_with_attachment.path)} waypoints")
88
89    if visualizer is not None:
90        print("Close the window to exit.")
91        visualizer.wait()
92
93
94if __name__ == "__main__":
95    _main()

Debugging

Export and Package API Log

This example shows how to create a zip archive containing everything needed to recreate your planner setup and re-run a failing API call from the same planner state. Note that this example is expected to throw an error.

 1#include <Zivid/Motion/Application.h>
 2#include <Zivid/Motion/Packaging.h>
 3#include <Zivid/Motion/Planner.h>
 4
 5#include <filesystem>
 6#include <iostream>
 7
 8namespace
 9{
10    void printException(const std::exception &e, const int level = 0)
11    {
12        std::cerr << std::string(level * 4, ' ') << (level ? "+ " : "") << "Exception: " << e.what() << '\n';
13        try
14        {
15            std::rethrow_if_nested(e);
16        }
17        catch(const std::exception &nestedException)
18        {
19            printException(nestedException, level + 1);
20        }
21        catch(...)
22        {}
23    }
24
25    Zivid::Motion::Planner startPlanner(const Zivid::Motion::Application &app)
26    {
27        return app.createPlanner(
28            Zivid::Motion::PlannerSettings{
29                "demo_cell",
30                Zivid::Motion::Profile::testing,
31            });
32    }
33} // namespace
34
35int main()
36{
37    const Zivid::Motion::Application app;
38
39    std::cout << "Starting planner\n";
40    auto planner = startPlanner(app);
41
42    try
43    {
44        const Zivid::Motion::Configuration startConfiguration{ 0.f, 0.f, 0.f, 0.f, 1.57f, 0.f };
45        const Zivid::Motion::Configuration invalidGoalConfiguration{ 100.f, 0.f, 0.f, 0.f, 1.57f, 0.f };
46
47        Zivid::Motion::PathRequest unreachableRequest{};
48        unreachableRequest.goals = Zivid::Motion::Goals::fromConfigurations({ invalidGoalConfiguration });
49        unreachableRequest.description = "Path with unreachable goal";
50        const auto unsuccessfulResult =
51            planner.path(Zivid::Motion::InitialState{ startConfiguration }, unreachableRequest);
52        std::cout << "Path result has status: " << unsuccessfulResult.toString() << "\n";
53
54        // Some operation that fails, like creating an InitialState from an unsuccessful path result
55        const Zivid::Motion::InitialState initialState{ unsuccessfulResult };
56        Zivid::Motion::PathRequest invalidStateRequest{};
57        invalidStateRequest.goals = Zivid::Motion::Goals::fromConfigurations({ startConfiguration });
58        invalidStateRequest.description = "Path with invalid initial state";
59        const auto result = planner.path(Zivid::Motion::InitialState{ startConfiguration }, invalidStateRequest);
60        std::cout << result << "\n";
61    }
62    catch(const std::exception &exception)
63    {
64        const std::filesystem::path outputFolder = "/tmp";
65
66        const auto logPath = planner.exportApiLog(outputFolder);
67        const auto packagePath = Zivid::Motion::packageApiLog(app, logPath);
68        std::cerr << "Planner failed, debug package available at: " + std::filesystem::canonical(packagePath).string()
69                  << std::endl;
70
71        printException(exception);
72        return EXIT_SUCCESS; // for the purpose of this sample, saving a debug package is success
73    }
74    return EXIT_FAILURE;
75}
 1from pathlib import Path
 2
 3from zividmotion import (
 4    Application,
 5    Configuration,
 6    Goals,
 7    InitialState,
 8    PathRequest,
 9    Planner,
10    PlannerSettings,
11    Profile,
12    package_api_log,
13)
14
15
16def _start_planner(app: Application) -> Planner:
17    planner_settings = PlannerSettings(cell_name="demo_cell", profile=Profile.testing)
18    return app.create_planner(planner_settings)
19
20
21def _main() -> None:
22    app = Application()
23
24    print("Starting planner")
25    planner = _start_planner(app)
26
27    start_configuration = Configuration([0, 0, 0, 0, 1.57, 0])
28    invalid_goal_configuration = Configuration([100, 0, 0, 0, 1.57, 0])
29
30    unsuccessful_result = planner.path(
31        initial_state=InitialState(start_configuration=start_configuration),
32        request=PathRequest(
33            goals=Goals.from_configurations([invalid_goal_configuration]),
34            description="Path with unreachable goal",
35        ),
36    )
37    assert unsuccessful_result.error is not None
38    print("Path result has error: ", unsuccessful_result.error)
39
40    try:
41        # Some operation that fails, like creating an InitialState from an unsuccessful path result
42        initial_state = InitialState(previous_result=unsuccessful_result)
43        result = planner.path(
44            initial_state=initial_state,
45            request=PathRequest(
46                goals=Goals.from_configurations([start_configuration]),
47                description="Path with invalid initial state",
48            ),
49        )
50        print(result)
51
52    except Exception:
53        output_folder = Path("/tmp")
54
55        log_path = planner.export_api_log(output_directory=output_folder)
56        package_path = package_api_log(application=app, api_log_path=log_path)
57
58        print(f"Planner failed, debug package available at: {package_path.resolve()}")
59
60
61if __name__ == "__main__":
62    _main()