.. _context-menu: Context menus ============= To enable the user to configure a state machine in Desk a context menu is defined, which leads the user step-by-step through the configuration. Those context menus can be implemented using HTML5, CSS3 and JavaScript. To support dynamic markup, `KnockoutJS `_ with its data binding and component system is utilized. The basics about employed mechanisms like binding handlers and the binding context can be found in `the knockout documentation `_. To attach a context menu to an app, the ``contextMenu`` field of the app's ``clientData`` has to be set. Any HTML markup in this ``clientData`` field will then be rendered as a context menu in Desk. Context menus are mainly navigable via the pilot-buttons on the robot, i. e. ``cross``, ``circle``, ``check`` and arrow-buttons in four directions. Navigation via mouse/keyboard and touch on a tablet computer is also possible. Steps divide the process of configuring a state machine in small chunks. Steps are combined in a tree like structure. The following code-block depicts a basic step. .. code-block:: html A step needs to have an ``[id]``, but more attributes can be provided (see :ref:`step-api`). .. note:: All step ids - except for the top level steps - get postfixed with a consecutive number, e.g. ``pick-pose-0``. This has to be taken into account when referring to a step via its id, e.g. for styling purposes. .. note:: Use only classes for CSS styling. ``[style]`` attributes and ``[id]`` selectors should be avoided. This ensures that the context menu API can adapt the styling correctly to a common look (due to selector specifity). The first level of steps - so called *top level steps* - are rendered as tabs of the context menu and are navigable via ``check`` and ``cross``. A top level step can contain further steps - so called sub steps. If a top level step is focused, its sub steps are recursively focused until the first leaf step is reached. Inside a top level step navigation is possible via the arrow-buttons, where ``right`` and ``down`` focus the next leaf step and ``left`` and ``up`` focus the previous leaf step. However, it is possible to override this behavior if needed (see :ref:`pilot-navigation`) When creating a context menu, we first have to define the top level steps (i.e. divide the parameters to be configured into meaningful chunks). A good strategy is to cover all the parameters which can be taught via guiding in the beginning, such as robot poses and gripper widths. For our ``Tutorial`` app (see :ref:`pick-place-tutorial`), we have to parameterize the approach and retract poses along with the gripper width for the opened and closed gripper. For picking the object, the parameters are set in a step with the id ``#pick-motion``. A step with the id ``#place-motion`` is added for placing the object. Furthermore, the collision area and the velocity have to be parameterized. The structure of our context menu for the ``Tutorial`` app will therefore look like this: .. code-block:: html contextMenu : @{ }@; The following image depicts the context menu we want to create for our ``Tutorial`` app. The just defined top level steps are visible above the content area. .. image:: _static/menu.png Parameter modification ---------------------- Parameters of the surrounding state machine are accessible via ``parameter()``. To access only a subtree of the parameter, an expression has to be provided. ``parameter('velocity')`` accesses e.g. the ``velocity`` parameter in the ``Tutorial`` app, ``parameter('pick_pose.joint_angles')`` the joint angles of the pick pose. Sometimes, numerical values are internally handled in a unit which is not comfortable to modify for the user. Therefore several modifiers are provided for the parameters, which enable conversion. Those modifiers are ``add``, ``multiply`` and ``round``. To convert e.g. a temperature from degree Celsius to degree Fahrenheit with one decimal place, one would write .. code-block:: html parameter('temperature').add(-32).multiply(5/9).round(1) To enable the user to modify the parameters, a variety of input components is provided. Numerical values can be modified via sliders. Choosing between two or more options is enabled via ``checkbox-slider``, ``alternative-selector`` and ``drop-down-menu``. One or several poses can be taught via ``robot-pose`` and ``robot-pose-carousel``, respectively. Teaching the gripper-width isolated from the pose is obtained with ``gripper-control``. Components are used as follows: .. code-block:: html Each component has specific parameters, which have to be passed to the ``[params]`` attribute. .. note:: The components require you to also pass the ``step`` from the binding context into the component. This is necessary to allow for pilot navigation and correct visualization on acquiring focus. .. _linear-slider: Linear slider ````````````` Allows for setting a value within a range .. image:: _static/linear_slider.png **Parameters** * ``value`` [mandatory] (Number): the app parameter which should be modified * ``max`` [mandatory] (Number): the maximal value * ``min`` [default=0] (Number): the minimal value * ``increment`` [default=1] (Number): the amount by which the value is increased or decreased by pressing the arrow buttons or dragging the slider knob * ``initial`` [default=min] (Number): the initial value * ``unit`` (String or Array[String]): the unit of the parameter, either as a single string, e.g. ``'%'`` or as an array of strings, e.g. ``['second', 'seconds']``, where the first entry is displayed if the value is ``1`` and the last entry otherwise **Code Example** .. code-block:: html Toggle slider ````````````` Allows for modifying several numerical values at once. .. image:: _static/toggle_slider.png **Parameters** An array is passed to the ``toggle-slider`` which contains the parameters for all ``linear-slider`` inside, as listed in :ref:`linear-slider`, plus an additional ``label`` parameter, which is displayed in the button bar on the left. **Code Example** .. code-block:: html Arc slider `````````` Allows for a circular representation of the value space. .. image:: _static/arc_slider.png **Parameters** * ``value`` [mandatory] (Number): the app parameter which should be modified * ``min`` [default=0] (Number): the minimal value * ``max`` [default=Infinity] (Number): the maximal value * ``increment`` [default=1] (Number): the amount by which the value is increased or decreased * ``zeroValue`` [default=0] (Number): the start of a circle * ``fullValue`` [default=360] (Number): the end of a circle * ``initial`` [default=min] (Number): the initial value * ``unit`` (String or Array[String]): the unit of the parameter, either as a single string, e.g. ``'%'`` or as an array of strings, e.g. ``['second', 'seconds']``, where the first entry is displayed if the value is ``1`` and the last entry otherwise **Code Example** .. code-block:: html Checkbox slider ``````````````` Allows for modifying booleans. .. image:: _static/checkbox.png **Parameters** * ``value`` [mandatory] (Boolean): the app parameter which should be modified * ``unchecked`` [default='OFF'] (String): a string displayed on the left side of the ``checkbox-slider`` * ``checked`` [default='ON'] (String): a string displayed on the right side of the ``checkbox-slider`` **Code Example** .. code-block:: html Alternative selector ```````````````````` Allows for choosing between multiple values which are displayed as an image with a label. .. image:: _static/alternative.png **Parameters** * ``parameter`` [mandatory] (String): the app parameter which should be modified * ``alternatives`` (Array[Object]): an array of alternative objects, defined by - ``name`` (String): the displayed name of the alternative - ``image`` (String): an image displayed above the name - ``value`` [mandatory] (String): the value which is written into ``parameter`` when the corresponding alternative is selected **Code Example** .. code-block:: html Drop down menu `````````````` Allows for choosing between multiple values. .. image:: _static/dropdown.png **Parameters** * ``parameter`` [mandatory] (String): the app parameter which should be modified * ``items`` [mandatory] (Array[Object]): an array of item objects, defined by - ``value`` [mandatory] (String): the value which is written into ``parameter`` when the corresponding item is selected - ``text`` (String): the displayed name of the item * ``default`` (String): the value of the item selected by default **Code Example** .. code-block:: html .. _robot-pose: Robot pose `````````` Allows for writing the current pose of the robot into a parameter. This component allows to simultaneously write the gripper width by passing a parameter to ``gripper_open`` or ``gripper_closed``. Can be used to teach a single pose or multiple related poses. If multiple related poses are taught, one of the poses has to be marked as reference. Markup passed to the component will be rendered inside the context menu to represent the pose. Refer to :ref:`scene` for a detailed description to proper visualization. **Parameters** .. note:: Besides the step also the path and the componentProviderApi has to be passed from the binding context. * ``pose`` [mandatory] (Object): the app parameter which should be modified * ``gripper_open`` (Object): if passed an app parameter, it will be written simultaneously with the pose * ``gripper_closed`` (Object): if passed an app parameter, it will be written simultaneously with the pose * ``isRelativeToFirst`` (Boolean): refer to :ref:`grid-points` for a detailed description * ``isBottomRightFirst`` (Boolean): refer to :ref:`grid-points` for a detailed description If more than one robot pose is used: * ``reference`` (Boolean): set to true, if this is the pose which requires the most accuracy, e.g. if there is an approach pose and a pick pose, set true for the pick pose * ``relatedPoses`` (Object(Object)): contains all related poses with the id of the step as key and the parameter as value **Code example** .. code-block:: html
...
Robot pose carousel ``````````````````` Allows for teaching an arbitrary amount of poses. .. image:: _static/robot-pose-carousel.png Displays the first and last pose, when not editing. .. image:: _static/robot-pose-carousel-edit.png **Parameters** .. note:: Besides the step also the path and the componentProviderApi has to be passed from the binding context. * ``poses`` [mandatory] (Array[Object]): the app parameter which should be modified * ``maximumPoses`` (Number): limit the amount of teachable poses * ``motionParams`` (Array[Object]): allows to define expert settings per pose in `poses` - ``pose_name`` (String): custom name of the pose - ``use_custom_parameters`` (Boolean): activate the custom settings for the pose - ``custom_acceleration``: (Number): custom acceleration for motion to this pose - ``custom_deceleration``: (Number): custom deceleration for motion to this pose - ``custom_speed``: (Number): custom speed for motion to this pose - ``point2point`` (Boolean): go to exact pose (no spline interpolation) * ``motionType`` [default="cartesian"] (String): cartesian or joint motion. For "joint" the point2point and deceleration options are hidden. * ``relatedPoses`` (Object(Object)): contains all related poses with the id of the step as key and the parameter as value **Code Example** .. code-block:: html Gripper control ``````````````` Allows for teaching the gripper width independent from the pose. It displays the actual gripper width (grey indicator) and the current set width (blue indicator) and enables the user to adopt the current value. .. image:: _static/gripper.png **Parameters** * ``width`` [mandatory] (Number): the app parameter which should be modified **Code Example** .. code-block:: html .. _scene: Visualizing robot poses ----------------------- Typically, apps demand for teaching several related poses. These poses should be depicted in a scene, which represents the poses in relation to the workspace. Suitable images have to be passed to the ``robot-pose`` component, such that the current teaching situation is visualized in the context menu. Markup passed to the component will be rendered inside the context menu to represent the pose. Typically it consists of one or more wrapped up SVG images. .. note:: The markup passed to the robot-pose has to be sized properly, such that other elements inside the context-menu will not be overlapped by the overflow of the element's box. For a ``#pick_pose`` we could display the gripper grasping an object as depicted in the following image, consisting of an SVG image of the gripper and an SVG image of the object (see :ref:`svg`). .. image:: _static/pick_pose.png Both SVG images are passed to the ``robot-pose``. .. code-block:: html .. _positioning: Positioning ``````````` There are several classes provided to arrange the passed markup, i.e. ================ ================== ================= ``.left-top`` ``.center-top`` ``.right-top`` ``.left-center`` ``.center-center`` ``.right-center`` ``.left-bottom`` ``.center-bottom`` ``.right-bottom`` ================ ================== ================= As we have two elements, we arrange them centered at the top and bottom. .. code-block:: html
Notice that we added a ``.pose-content`` wrapper around the SVGs. The classes provided above add ``position: absolute`` to the elements such that they do not influence the size of the surrounding container. We therefore define ``width`` and ``height`` on the ``.pose-content``. .. _coloring: Coloring and visibility ``````````````````````` The coloring and visibility of the pose-content depends on the current focus. The content of leaf steps which prior siblings are not configured is hidden. This behavior can be customized by adding the classes ``.visible`` or ``.hidden`` to elements. There are three color schemes * ``light-gray``: default for focusable but not focused steps * ``dark-gray`` * ``highlighted``: default for focused steps as depicted in the following image from left to right. .. image:: _static/colors.png To adjust the appearance of elements inside a focused step the classes ``.light-gray`` or ``.dark-gray`` can be added. To enforce a certain color scheme no matter if the step is focused or not, the color classes can be prefixed with ``static-``, i.e. ``.static-highlighted``. For the ``#pick-pose``, we want the object to be visible all the time in a neutral color. Therefore the classes ``.visible`` as well as ``.static-dark-gray`` are added. .. code-block:: html
.. _svg: Guidelines for SVG images ------------------------- To get good looking results, the SVG images have to fulfill certain prerequisites. To address the SVG image like in the example above, an ``[id]`` has to be set. It is recommended to remove all ``[height]`` or ``[width]`` attributes from the SVG image and provide only a ``[viewbox]``. Sizing of the SVG images should take place inside the app, e.g. by adding a `` To be able to style the component dependent on the current focus, the step class ``.focused`` can be used, as depicted in the following code-block. .. code-block:: HTML There are a number of variables for colors which get replaced to pre-defined values. It is recommended to use these over hardcoded values to have consistent styling. Those are * ``$error.color`` * ``$warning.color`` * ``$circle.color`` * ``$check.color`` * ``$cross.color`` Similar to the ``highlighted`` color scheme * ``$focused.color`` * ``$focusedAccent.color`` Similar to the ``light-gray`` color scheme * ``$inactive.color`` * ``$inactiveAccent.color`` * ``$inactiveSubtle.color`` Similar to the ``dark-gray`` color scheme * ``$active.color`` * ``$activeAccent.color`` * ``$activeSubtle.color`` The following example template contains the HTML5 ```` element and a button bound to the component's click method. .. code-block:: HTML components : @{ }@ Viewmodel ^^^^^^^^^ The ```` tag is loaded as CommonJS module with a variety of requirable modules. However, the code is loaded without context, so everything has to be required. The declared exports or returned value is used as constructor for the component viewmodel, so make sure you export or return a function. Requirable modules are * `lodash 3.10.1 `_ * `jquery 3.4.1 `_ * `knockout 3.4.2 `_ * :ref:`matrix ` * :ref:`component_util ` The viewmodel constructor is provided with (as in Knockout components) the passed ``params`` and additional arguments: * ``api`` - The Desk API to play sound or retrieve robot poses * ``source`` - The element API for the skill/group which declared the component. This is used to provide components which not only parameterize the instantiating but also the providing skill. * ``element`` - A reference to the DOM element holding the rendered template. The instance created by invoking the constructor with new will then be available as ``$data`` and ``$component`` in the binding context. .. code-block:: javascript var _ = require("lodash") var NumberInput = module.exports = function(params, api, source, element) { this.value = params.value this.min = params.min || 0 this.max = params.max this.increment = params.increment || 1 this.click = function() { params.step.done() } } .. _matrix: Matrix ^^^^^^ The module ``matrix`` provides several matrix utility functions for transformation matrices, poses and trajectories. Transformation matrices are stored in column-major order in arrays of length 16. Poses are objects containing the transformation matrix and the joint angles as an array of length 7, i.e. ``{ pose: Array[16], joint_angles: Array[7] }``. Trajectories are arrays containing poses. * ``Matrix.transpose(m)``: transposes a matrix * *arguments*: ``m (Array)`` the matrix to process * *returns*: ``(Array)`` the transposed matrix * ``Matrix.invert(m)``: inverts a matrix * *arguments*: ``m (Array)`` the matrix to process * *returns*: ``(Array)`` the inverted matrix * ``Matrix.dot(m1, m2)``: multiplies two matrices * *arguments*: ``m1 (Array), m2 (Array)`` the matrices to process * *returns*: ``(Array)`` the result of the multiplication * ``Matrix.identity()``: returns a new identity matrix * *returns*: ``(Array)`` identity matrix * ``Matrix.translate(m, x, y, z)``: translates the transformation matrix * *arguments*: ``m (Array)`` the transformation matrix, ``x (Number), y (Number), z (Number)`` translation vector * *returns*: ``(Array)`` the result of the translation * ``Matrix.moveTrajectory(traj, a, b)``: moves a trajectory from a to b * *arguments*: ``traj (Array(Objects))`` the trajectory to be moved, ``a (Object), b (Object)`` the start (a) and end (b) pose as * *returns*: ``(Array(Objects))`` the moved trajectory * ``Matrix.movePose(pose, a, b)``: moves a pose from a to b * *arguments*: ``pose (Object)`` the pose to be moved, ``a (Object), b (Object)`` the start (a) and end (b) pose as * *returns*: ``(Object)`` the moved trajectory * ``Matrix.matricesEqual(a, b, [p])``: checks if two matrices are equal given a certain precision * *arguments*: ``a (Array), b (Array)`` the matrices to be compared, ``p [optional] (Number)`` the precision * *returns*: ``(Boolean)`` returns ``true`` if matrices are equivalent * ``Matrix.trajectoriesEqual(a, b, [p])``: checks if two trajectories are equal given a certain precision * *arguments*: ``a (Array(Object)), b (Array(Object))`` the trajectories to be compared, ``p [optional] (Number)`` the precision * *returns*: ``(Boolean)`` returns ``true`` if trajectories are equivalent .. _component_util: Component util ^^^^^^^^^^^^^^ The module ``component_util`` provides several utility functions. * ``Util.or([args])``: returns the first argument which is not ``undefined`` or ``null`` * *arguments*: ``args (...)`` the arguments to check for ``undefined`` and ``null`` * *returns*: ``(...)`` the first arguments which is not ``undefined`` or ``null`` * ``Util.clamp(value, min, max)``: clamps value within the lower and upper bounds * *arguments*: ``value (Number)`` the value to clamp, ``min (Number)`` the lower bound, ``max (Number) the upper bound`` * *returns*: ``(Number)`` the clamped value * ``Util.pluralize(unit, value)``: applies correct pluralization to unit, e.g. ``"second"`` instead of ``"seconds"`` if the value is 1 * *arguments*: ``unit (String or Array(String))`` the unit(s), ``value (Number)`` the current value * *returns*: ``(String)`` unit with correct number Custom groups ^^^^^^^^^^^^^ You can create your own groups, similar to `points` and `grid`. A ``.lf`` file is recognized as a group when it defines ``type: "group"`` within the ``clientData``. The group can modify the behavior of child apps in many ways. One is by registering their own components like ``robot-pose`` and thus overwriting the default ``robot-pose``: .. code-block:: html components: @{ }@; The icons displayed on the left and right can be defined by adding two more ``svg`` elements to the ``image`` ``clientData`` with the ids ``open`` and ``close``. .. code-block:: html clientData { ... image: @{ }@; } .. _reacting-to-changes-using-pure-computed: Reacting to changes of observable values using ``ko.pureComputed`` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Many values provided by the viewmodel apis implement an observer pattern to always provide up-to-date values to viewmodels. For instance, it is possible to check if the current step is focused by calling ``params.step.focused``: .. code-block:: javascript const MyViewmodel = function(params, api, source, element) { console.log(params.step.focused()) // will output if the step is focused at // the moment of invocation } To always display current data to users, it is possible to wrap observable values in a knockout ``ko.pureComputed``. A ``ko.pureComputed`` will keep track whether other observers are subscribed to its value, update if its dependencies change and it is being observed, and will dispose of its own subscriptions to observables automatically on teardown of the viewmodel. This allows for a number of patterns: .. code-block:: javascript const ko = require('knockout'); const MyViewmodel = module.exports = function(params, api, source, element) { this.isStepFocused = ko.pureComputed( () => { // simply return the observable value return params.step.focused(); }); this.isStepFocusedAndConfigured = ko.pureComputed( () => { // combine the value of multiple observables return params.step.focused() && params.step.configured(); }); this.gripperClosedLabel = ko.pureComputed( () => { // transform an observable value to another type return api.relativeGripperWidth() === 0 ? "The gripper is closed." : "The gripper is not closed!"; }); } There values can then be used in the HTML markup of the viewmodel: .. code-block:: HTML