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)}")
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
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
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)}" )
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)
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
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
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.")