Beispiel #1
0
def check_action_params(
    scene: CachedScene, project: CachedProject, action: common.Action, object_action: ObjectAction
) -> None:

    _, action_type = action.parse_type()

    assert action_type == object_action.name

    if len(object_action.parameters) != len(action.parameters):
        raise Arcor2Exception("Unexpected number of parameters.")

    for req_param in object_action.parameters:

        param = action.parameter(req_param.name)

        if param.type == common.ActionParameter.TypeEnum.CONSTANT:

            const = project.constant(param.str_from_value())

            param_meta = object_action.parameter(param.name)
            if param_meta.type != const.type:
                raise Arcor2Exception("Param type does not match constant type.")

        elif param.type == common.ActionParameter.TypeEnum.LINK:

            parsed_link = param.parse_link()

            if parsed_link.action_id == action.id:
                raise Arcor2Exception("Can't use own result as a parameter.")

            outputs = project.action(parsed_link.action_id).flow(parsed_link.flow_name).outputs

            assert len(outputs) == len(object_action.returns)

            param_meta = object_action.parameter(param.name)
            if param_meta.type != object_action.returns[parsed_link.output_index]:
                raise Arcor2Exception("Param type does not match action output type.")

        else:

            if param.type not in known_parameter_types():
                raise Arcor2Exception(f"Parameter {param.name} of action {action.name} has unknown type: {param.type}.")

            try:
                plugin_from_type_name(param.type).parameter_value(
                    get_types_dict(), scene, project, action.id, param.name
                )
            except ParameterPluginException as e:
                raise Arcor2Exception(f"Parameter {param.name} of action {action.name} has invalid value. {str(e)}")
Beispiel #2
0
async def remove_action_point_orientation_cb(
        req: srpc.p.RemoveActionPointOrientation.Request,
        ui: WsClient) -> None:
    """Removes orientation.

    :param req:
    :return:
    """

    assert glob.SCENE
    assert glob.PROJECT

    ap, orientation = glob.PROJECT.bare_ap_and_orientation(
        req.args.orientation_id)

    for act in glob.PROJECT.actions:
        for param in act.parameters:
            if plugin_from_type_name(param.type).uses_orientation(
                    glob.PROJECT, act.id, param.name, req.args.orientation_id):
                raise Arcor2Exception(
                    f"Orientation used in action {act.name} (parameter {param.name})."
                )

    if req.dry_run:
        return None

    glob.PROJECT.remove_orientation(req.args.orientation_id)

    evt = sevts.p.OrientationChanged(orientation)
    evt.change_type = Event.Type.REMOVE
    asyncio.ensure_future(notif.broadcast_event(evt))
    return None
Beispiel #3
0
async def remove_action_point_joints_cb(
        req: srpc.p.RemoveActionPointJoints.Request, ui: WsClient) -> None:
    """Removes joints from action point.

    :param req:
    :return:
    """

    assert glob.SCENE
    assert glob.PROJECT

    for act in glob.PROJECT.actions:
        for param in act.parameters:
            if plugin_from_type_name(param.type).uses_robot_joints(
                    glob.PROJECT, act.id, param.name, req.args.joints_id):
                raise Arcor2Exception(
                    f"Joints used in action {act.name} (parameter {param.name})."
                )

    joints_to_be_removed = glob.PROJECT.remove_joints(req.args.joints_id)

    glob.PROJECT.update_modified()

    evt = sevts.p.JointsChanged(joints_to_be_removed)
    evt.change_type = Event.Type.REMOVE
    asyncio.ensure_future(notif.broadcast_event(evt))
    return None
Beispiel #4
0
def check_action_params(scene: CachedScene, project: CachedProject,
                        action: common.Action,
                        object_action: ObjectAction) -> None:

    _, action_type = action.parse_type()

    assert action_type == object_action.name

    if len(object_action.parameters) != len(action.parameters):
        raise Arcor2Exception("Unexpected number of parameters.")

    for req_param in object_action.parameters:

        param = action.parameter(req_param.name)

        if param.type == common.ActionParameter.TypeEnum.PROJECT_PARAMETER:

            pparam = project.parameter(param.str_from_value())

            param_meta = object_action.parameter(param.name)
            if param_meta.type != pparam.type:
                raise Arcor2Exception(
                    "Action parameter type does not match project parameter type."
                )

        elif param.type == common.ActionParameter.TypeEnum.LINK:

            parsed_link = param.parse_link()

            if parsed_link.action_id == action.id:
                raise Arcor2Exception("Can't use own result as a parameter.")

            parent_action = project.action(parsed_link.action_id)
            source_action_pt = parent_action.parse_type()

            parent_action_meta = glob.OBJECT_TYPES[scene.object(
                source_action_pt.obj_id).type].actions[
                    source_action_pt.action_type]

            if len(parent_action.flow(parsed_link.flow_name).outputs) != len(
                    parent_action_meta.returns):
                raise Arcor2Exception(
                    "Source action does not have outputs specified.")

            param_meta = object_action.parameter(param.name)

            try:
                if param_meta.type != parent_action_meta.returns[
                        parsed_link.output_index]:
                    raise Arcor2Exception(
                        "Param type does not match action output type.")
            except IndexError:
                raise Arcor2Exception(
                    f"Index {parsed_link.output_index} is invalid for action {object_action.name},"
                    f" which returns {len(object_action.returns)} values.")

        else:

            if param.type not in known_parameter_types():
                raise Arcor2Exception(
                    f"Parameter {param.name} of action {action.name} has unknown type: {param.type}."
                )

            try:
                plugin_from_type_name(param.type).parameter_value(
                    get_types_dict(), scene, project, action.id, param.name)
            except ParameterPluginException as e:
                raise Arcor2Exception(
                    f"Parameter {param.name} of action {action.name} has invalid value. {str(e)}"
                )
Beispiel #5
0
    def _add_logic(container: Container,
                   current_action: Action,
                   super_container: Optional[Container] = None) -> None:

        # more paths could lead  to the same action, so it might be already added
        # ...this is easier than searching the tree
        if current_action.id in added_actions:
            logger.debug(
                f"Action {current_action.name} already added, skipping.")
            return

        inputs, outputs = project.action_io(current_action.id)
        logger.debug(
            f"Adding action {current_action.name}, with {len(inputs)} input(s) and {len(outputs)} output(s)."
        )

        act = current_action.parse_type()
        ac_obj = scene.object(act.obj_id).name

        args: List[AST] = []

        # TODO make sure that the order of parameters is correct / re-order
        for param in current_action.parameters:

            if param.type == ActionParameter.TypeEnum.LINK:

                parsed_link = param.parse_link()
                parent_action = project.action(parsed_link.action_id)

                # TODO add support for tuples
                assert len(parent_action.flow(FlowTypes.DEFAULT).outputs
                           ) == 1, "Only one result is supported atm."
                assert parsed_link.output_index == 0

                res_name = parent_action.flow(FlowTypes.DEFAULT).outputs[0]

                # make sure that the result already exists
                if parent_action.id not in added_actions:
                    raise SourceException(
                        f"Action {current_action.name} attempts to use result {res_name} "
                        f"of subsequent action {parent_action.name}.")

                args.append(Name(id=res_name, ctx=Load()))

            elif param.type == ActionParameter.TypeEnum.PROJECT_PARAMETER:
                args.append(
                    Name(id=project.parameter(param.str_from_value()).name,
                         ctx=Load()))
            else:

                plugin = plugin_from_type_name(param.type)

                args.append(
                    plugin.parameter_ast(type_defs, scene, project,
                                         current_action.id, param.name))

                list_of_imp_tup = plugin.need_to_be_imported(
                    type_defs, scene, project, current_action.id, param.name)

                if list_of_imp_tup:
                    # TODO what if there are two same names?
                    for imp_tup in list_of_imp_tup:
                        add_import(tree,
                                   imp_tup.module_name,
                                   imp_tup.class_name,
                                   try_to_import=False)

        add_method_call(
            container.body,
            ac_obj,
            act.action_type,
            args,
            [keyword(arg="an", value=Str(s=current_action.name, kind=""))],
            current_action.flow(FlowTypes.DEFAULT).outputs,
        )

        added_actions.add(current_action.id)

        if not outputs:
            raise SourceException(
                f"Action {current_action.name} has no outputs.")
        elif len(outputs) == 1:
            output = outputs[0]

            if output.end == output.END:
                # TODO this is just temporary (while there is while loop), should be rather Return()
                container.body.append(Continue())
                return

            seq_act = project.action(output.end)
            seq_act_inputs, _ = project.action_io(seq_act.id)
            if len(seq_act_inputs
                   ) > 1:  # the action belongs to a different block

                if seq_act.id in added_actions:
                    return

                logger.debug(
                    f"Action {seq_act.name} going to be added to super_container."
                )

                # test if this is the correct super_container -> count distance (number of blocks) to the START
                blocks_to_start: Dict[str, int] = {}

                for inp in seq_act_inputs:
                    parsed_start = inp.parse_start()
                    pact = project.action(parsed_start.start_action_id)
                    blocks_to_start[pact.id] = _blocks_to_start(pact)
                winner = min(blocks_to_start, key=blocks_to_start.get
                             )  # type: ignore  # TODO what is wrong with it?

                # TODO if blocks_to_start is cached somewhere, the second part of the condition is useless
                # it might happen that there are two different ways with the same distance
                if winner == current_action.id or all(
                        value == list(blocks_to_start.values())[0]
                        for value in blocks_to_start.values()):
                    assert super_container is not None
                    _add_logic(super_container, seq_act)
                return

            logger.debug(f"Sequential action: {seq_act.name}")
            _add_logic(container, seq_act, super_container)

        else:

            root_if: Optional[If] = None

            # action has more outputs - each output should have condition
            for idx, output in enumerate(outputs):
                if not output.condition:
                    raise SourceException("Missing condition.")

                # TODO use parameter plugin (action metadata will be needed - to get the return types)
                # TODO support for other countable types
                # ...this will only work for booleans
                from arcor2 import json

                condition_value = json.loads(output.condition.value)
                comp = NameConstant(value=condition_value, kind=None)
                what = output.condition.parse_what()
                output_name = project.action(what.action_id).flow(
                    what.flow_name).outputs[what.output_index]

                cond = If(
                    test=Compare(left=Name(id=output_name, ctx=Load()),
                                 ops=[Eq()],
                                 comparators=[comp]),
                    body=[],
                    orelse=[],
                )

                if idx == 0:
                    root_if = cond
                    container.body.append(root_if)
                    logger.debug(f"Adding branch for: {condition_value}")
                else:
                    assert isinstance(root_if, If)
                    root_if.orelse.append(cond)

                if output.end == output.END:
                    cond.body.append(
                        Continue())  # TODO should be rather return
                    continue

                _add_logic(cond, project.action(output.end), container)
Beispiel #6
0
async def remove_action_point_cb(req: srpc.p.RemoveActionPoint.Request,
                                 ui: WsClient) -> None:

    assert glob.PROJECT

    ap = glob.PROJECT.bare_action_point(req.args.id)

    for proj_ap in glob.PROJECT.action_points_with_parent:
        if proj_ap.parent == ap.id:
            raise Arcor2Exception(
                f"Can't remove parent of '{proj_ap.name}' AP.")

    ap_action_ids = glob.PROJECT.ap_action_ids(ap.id)

    # check if AP's actions aren't involved in logic
    # TODO 'force' param to remove logical connections?
    for logic in glob.PROJECT.logic:
        if (logic.start in ap_action_ids or logic.end in ap_action_ids or
            (logic.condition
             and logic.condition.parse_what().action_id in ap_action_ids)):
            raise Arcor2Exception("Remove logic connections first.")

    for act in glob.PROJECT.actions:

        if act.id in ap_action_ids:
            continue

        for param in act.parameters:
            if param.type == common.ActionParameter.TypeEnum.LINK:
                parsed_link = param.parse_link()
                linking_action = glob.PROJECT.action(parsed_link.action_id)
                if parsed_link.action_id in ap_action_ids:
                    raise Arcor2Exception(
                        f"Result of '{act.name}' is linked from '{linking_action.name}'."
                    )

            if not param.is_value():
                continue

            for joints in glob.PROJECT.ap_joints(ap.id):
                if plugin_from_type_name(param.type).uses_robot_joints(
                        glob.PROJECT, act.id, param.name, joints.id):
                    raise Arcor2Exception(
                        f"Joints {joints.name} used in action {act.name} (parameter {param.name})."
                    )

            for ori in glob.PROJECT.ap_orientations(ap.id):
                if plugin_from_type_name(param.type).uses_orientation(
                        glob.PROJECT, act.id, param.name, ori.id):
                    raise Arcor2Exception(
                        f"Orientation {ori.name} used in action {act.name} (parameter {param.name})."
                    )

            # TODO some hypothetical parameter type could use just bare ActionPoint (its position)

    if req.dry_run:
        return None

    glob.PROJECT.remove_action_point(req.args.id)

    evt = sevts.p.ActionPointChanged(ap)
    evt.change_type = Event.Type.REMOVE
    asyncio.ensure_future(notif.broadcast_event(evt))
    return None
Beispiel #7
0
async def execute_action_cb(req: srpc.p.ExecuteAction.Request,
                            ui: WsClient) -> None:

    assert glob.SCENE
    assert glob.PROJECT

    ensure_scene_started()

    if glob.RUNNING_ACTION:
        raise Arcor2Exception(
            f"Action {glob.RUNNING_ACTION} is being executed. "
            f"Only one action can be executed at a time.")

    action = glob.PROJECT.action(req.args.action_id)

    obj_id, action_name = action.parse_type()

    params: List[Any] = []

    for param in action.parameters:

        if param.type == common.ActionParameter.TypeEnum.LINK:

            parsed_link = param.parse_link()
            try:
                results = project.PREV_RESULTS[parsed_link.action_id]
            except KeyError:
                prev_action = glob.PROJECT.action(parsed_link.action_id)
                raise Arcor2Exception(
                    f"Action '{prev_action.name}' has to be executed first.")

            # an action result could be a tuple or a single value
            if isinstance(results, tuple):
                params.append(results[parsed_link.output_index])
            else:
                assert parsed_link.output_index == 0
                params.append(results)

        elif param.type == common.ActionParameter.TypeEnum.CONSTANT:
            const = glob.PROJECT.constant(param.str_from_value())
            # TODO use plugin to get the value
            import json

            params.append(json.loads(const.value))
        else:

            try:
                params.append(
                    plugin_from_type_name(
                        param.type).parameter_execution_value(
                            get_types_dict(), glob.SCENE, glob.PROJECT,
                            action.id, param.name))
            except ParameterPluginException as e:
                glob.logger.error(e)
                raise Arcor2Exception(
                    f"Failed to get value for parameter {param.name}.")

    obj = get_instance(obj_id)

    if isinstance(obj, Robot):
        if obj.move_in_progress:
            raise Arcor2Exception(
                "Can't execute actions while the robot moves.")

    if not hasattr(obj, action_name):
        raise Arcor2Exception(
            "Internal error: object does not have the requested method.")

    if req.dry_run:
        return

    glob.RUNNING_ACTION = action.id
    glob.RUNNING_ACTION_PARAMS = params

    glob.logger.debug(
        f"Running action {action.name} ({type(obj)}/{action_name}), params: {params}."
    )

    # schedule execution and return success
    asyncio.ensure_future(
        project.execute_action(getattr(obj, action_name), params))
    return None
Beispiel #8
0
def check_logic_item(parent: LogicContainer,
                     logic_item: common.LogicItem) -> None:
    """Checks if newly added/updated ProjectLogicItem is ok.

    :param parent:
    :param logic_item:
    :return:
    """

    assert glob.SCENE

    action_ids = parent.action_ids()

    if logic_item.start == common.LogicItem.START and logic_item.end == common.LogicItem.END:
        raise Arcor2Exception("This does not make sense.")

    if logic_item.start != common.LogicItem.START:

        start_action_id, start_flow = logic_item.parse_start()

        if start_action_id == logic_item.end:
            raise Arcor2Exception("Start and end can't be the same.")

        if start_action_id not in action_ids:
            raise Arcor2Exception("Logic item has unknown start.")

        if start_flow != "default":
            raise Arcor2Exception("Only flow 'default' is supported so far.'")

    if logic_item.end != common.LogicItem.END:

        if logic_item.end not in action_ids:
            raise Arcor2Exception("Logic item has unknown end.")

    if logic_item.condition is not None:

        what = logic_item.condition.parse_what()
        action = parent.action(
            what.action_id
        )  # action that produced the result which we depend on here
        flow = action.flow(what.flow_name)
        try:
            flow.outputs[what.output_index]
        except IndexError:
            raise Arcor2Exception(
                f"Flow {flow.type} does not have output with index {what.output_index}."
            )

        action_meta = find_object_action(glob.SCENE, action)

        try:
            return_type = action_meta.returns[what.output_index]
        except IndexError:
            raise Arcor2Exception(
                f"Invalid output index {what.output_index} for action {action_meta.name}."
            )

        return_type_plugin = plugin_from_type_name(return_type)

        if not return_type_plugin.COUNTABLE:
            raise Arcor2Exception(
                f"Output of type {return_type} can't be branched.")

        # TODO for now there is only support for bool
        if return_type_plugin.type() != bool:
            raise Arcor2Exception("Unsupported condition type.")

        # check that condition value is ok, actual value is not interesting
        # TODO perform this check using plugin
        import json

        if not isinstance(json.loads(logic_item.condition.value), bool):
            raise Arcor2Exception("Invalid condition value.")

    for existing_item in parent.logic:

        if existing_item.id == logic_item.id:  # item is updated
            continue

        if logic_item.start == logic_item.START and existing_item.start == logic_item.START:
            raise Arcor2Exception("START already defined.")

        if logic_item.start == existing_item.start:

            if None in (logic_item.condition, existing_item.condition):
                raise Arcor2Exception(
                    "Two junctions has the same start action without condition."
                )

            # when there are more logical connections from A to B, their condition values must be different
            if logic_item.condition == existing_item.condition:
                raise Arcor2Exception(
                    "Two junctions with the same start should have different conditions."
                )

        if logic_item.end == existing_item.end:
            if logic_item.start == existing_item.start:
                raise Arcor2Exception(
                    "Junctions can't have the same start and end.")