async def remove_from_scene_cb(req: srpc.s.RemoveFromScene.Request, ui: WsClient) -> None: scene = glob.LOCK.scene_or_exception(ensure_project_closed=True) user_name = glob.USERS.user_name(ui) to_lock = await get_unlocked_objects(req.args.id, user_name) async with ctx_write_lock(to_lock, user_name, auto_unlock=req.dry_run): can_modify_scene() if not req.args.force and { proj.name async for proj in projects_using_object(scene.id, req.args.id) }: raise Arcor2Exception( "Can't remove object that is used in project(s).") if req.dry_run: return None if req.args.id not in scene.object_ids: raise Arcor2Exception("Unknown id.") await glob.LOCK.write_unlock(req.args.id, user_name) obj = scene.object(req.args.id) scene.delete_object(req.args.id) if req.args.id in glob.OBJECTS_WITH_UPDATED_POSE: glob.OBJECTS_WITH_UPDATED_POSE.remove(req.args.id) evt = sevts.s.SceneObjectChanged(obj) evt.change_type = Event.Type.REMOVE asyncio.ensure_future(notif.broadcast_event(evt)) # TODO this should be done after scene is saved asyncio.ensure_future( remove_object_references_from_projects(req.args.id)) return None
async def update_override_cb(req: srpc.o.UpdateOverride.Request, ui: WsClient) -> None: scene = glob.LOCK.scene_or_exception() project = glob.LOCK.project_or_exception() obj = check_override(scene, project, req.args.id, req.args.override) await ensure_locked(req.args.id, ui) if req.dry_run: return for override in project.overrides[obj.id]: if override.name == override.name: override.value = req.args.override.value project.update_modified() evt = sevts.o.OverrideUpdated(req.args.override) evt.change_type = events.Event.Type.UPDATE evt.parent_id = req.args.id asyncio.ensure_future(notif.broadcast_event(evt))
async def delete_object_type_cb(req: srpc.o.DeleteObjectType.Request, ui: WsClient) -> None: try: obj_type = glob.OBJECT_TYPES[req.args.id] except KeyError: raise Arcor2Exception("Unknown object type.") if obj_type.meta.built_in: raise Arcor2Exception("Can't delete built-in type.") for obj in glob.OBJECT_TYPES.values(): if obj.meta.base == req.args.id: raise Arcor2Exception(f"Object type is base of '{obj.meta.type}'.") async for scene in scenes(): check_scene_for_object_type(scene, req.args.id) if glob.SCENE: check_scene_for_object_type(glob.SCENE, req.args.id) if req.dry_run: return await storage.delete_object_type(req.args.id) # do not care so much if delete_model fails if obj_type.meta.object_model: try: await storage.delete_model(obj_type.meta.object_model.model().id) except storage.ProjectServiceException as e: glob.logger.error(str(e)) del glob.OBJECT_TYPES[req.args.id] remove_object_type(req.args.id) evt = sevts.o.ChangedObjectTypes([obj_type.meta]) evt.change_type = events.Event.Type.REMOVE asyncio.ensure_future(notif.broadcast_event(evt))
async def hand_teaching_mode_cb(req: srpc.r.HandTeachingMode.Request, ui: WsClient) -> None: glob.LOCK.scene_or_exception() ensure_scene_started() robot_inst = await osa.get_robot_instance(req.args.robot_id) # in this case, method name does not correspond to feature name await check_feature(robot_inst, "hand_teaching") hand_teaching_mode = await run_in_executor(robot_inst.get_hand_teaching_mode) if req.args.enable == hand_teaching_mode: raise Arcor2Exception("That's the current state.") await ensure_locked(req.args.robot_id, ui) if req.dry_run: return await run_in_executor(robot_inst.set_hand_teaching_mode, req.args.enable) evt = HandTeachingMode(HandTeachingMode.Data(req.args.robot_id, req.args.enable)) asyncio.ensure_future(notif.broadcast_event(evt))
async def update_action_point_joints_cb( req: srpc.p.UpdateActionPointJoints.Request, ui: WsClient) -> None: assert glob.SCENE assert glob.PROJECT robot_joints = glob.PROJECT.joints(req.args.joints_id) if {joint.name for joint in req.args.joints } != {joint.name for joint in robot_joints.joints}: raise Arcor2Exception("Joint names does not match the robot.") # TODO maybe joints values should be normalized? To <0, 2pi> or to <-pi, pi>? robot_joints.joints = req.args.joints robot_joints.is_valid = True glob.PROJECT.update_modified() evt = sevts.p.JointsChanged(robot_joints) evt.change_type = Event.Type.UPDATE asyncio.ensure_future(notif.broadcast_event(evt)) return None
async def update_object_model_cb(req: srpc.o.UpdateObjectModel.Request, ui: WsClient) -> None: can_modify_scene() glob.LOCK.scene_or_exception(True) # only allow while editing scene obj_data = glob.OBJECT_TYPES[req.args.object_type_id] if not obj_data.type_def: raise Arcor2Exception("ObjectType disabled.") if not issubclass(obj_data.type_def, CollisionObject): raise Arcor2Exception("Not a CollisionObject.") assert obj_data.meta.object_model assert obj_data.ast if req.args.object_model == obj_data.meta.object_model: raise Arcor2Exception("No change requested.") await ensure_write_locked(req.args.object_type_id, glob.USERS.user_name(ui)) if req.dry_run: return await update_object_model(obj_data.meta, req.args.object_model) obj_data.meta.object_model = req.args.object_model ot = obj_data.meta.to_object_type() ot.source = tree_to_str(obj_data.ast) obj_data.meta.modified = await storage.update_object_type(ot) evt = sevts.o.ChangedObjectTypes([obj_data.meta]) evt.change_type = events.Event.Type.UPDATE asyncio.ensure_future(notif.broadcast_event(evt))
async def delete_override_cb(req: srpc.o.DeleteOverride.Request, ui: WsClient) -> None: assert glob.PROJECT obj = check_override(req.args.id, req.args.override) if req.dry_run: return glob.PROJECT.overrides[obj.id] = [ ov for ov in glob.PROJECT.overrides[obj.id] if ov.name != req.args.override.name ] if not glob.PROJECT.overrides[obj.id]: del glob.PROJECT.overrides[obj.id] glob.PROJECT.update_modified() evt = sevts.o.OverrideUpdated(req.args.override) evt.change_type = events.Event.Type.REMOVE evt.parent_id = req.args.id asyncio.ensure_future(notif.broadcast_event(evt))
async def add_action_point_orientation_using_robot_cb( req: srpc.p.AddActionPointOrientationUsingRobot.Request, ui: WsClient) -> None: """Adds orientation and joints to the action point. :param req: :return: """ ensure_scene_started() assert glob.SCENE assert glob.PROJECT ap = glob.PROJECT.bare_action_point(req.args.action_point_id) hlp.is_valid_identifier(req.args.name) unique_name(req.args.name, glob.PROJECT.ap_orientation_names(ap.id)) if req.dry_run: return None new_pose = await get_end_effector_pose(req.args.robot.robot_id, req.args.robot.end_effector) if ap.parent: new_pose = tr.make_pose_rel_to_parent(glob.SCENE, glob.PROJECT, new_pose, ap.parent) orientation = common.NamedOrientation(req.args.name, new_pose.orientation) glob.PROJECT.upsert_orientation(ap.id, orientation) evt = sevts.p.OrientationChanged(orientation) evt.change_type = Event.Type.ADD evt.parent_id = ap.id asyncio.ensure_future(notif.broadcast_event(evt)) return None
async def remove_constant_cb(req: srpc.p.RemoveConstant.Request, ui: WsClient) -> None: assert glob.PROJECT assert glob.SCENE const = glob.PROJECT.constant(req.args.constant_id) # check for usage for act in glob.PROJECT.actions: for param in act.parameters: if param.type == common.ActionParameter.TypeEnum.CONSTANT and param.str_from_value( ) == const.id: raise Arcor2Exception("Constant used as action parameter.") if req.dry_run: return glob.PROJECT.remove_constant(const.id) evt = sevts.p.ProjectConstantChanged(const) evt.change_type = Event.Type.REMOVE asyncio.ensure_future(notif.broadcast_event(evt)) return None
async def hand_teaching_mode_cb(req: srpc.r.HandTeachingMode.Request, ui: WsClient) -> None: ensure_scene_started() robot_inst = await osa.get_robot_instance(req.args.robot_id) otd = osa.get_obj_type_data(req.args.robot_id) assert otd.robot_meta is not None if not otd.robot_meta.features.hand_teaching: raise Arcor2Exception("Robot does not support hand teaching.") hand_teaching_mode = await run_in_executor( robot_inst.get_hand_teaching_mode) if req.args.enable == hand_teaching_mode: raise Arcor2Exception("That's the current state.") if req.dry_run: return await run_in_executor(robot_inst.set_hand_teaching_mode, req.args.enable) evt = HandTeachingMode( HandTeachingMode.Data(req.args.robot_id, req.args.enable)) asyncio.ensure_future(notif.broadcast_event(evt))
async def set_scene_state(state: SceneState.Data.StateEnum, message: Optional[str] = None) -> None: global _scene_state _scene_state = SceneState(SceneState.Data(state, message)) asyncio.create_task(notif.broadcast_event(_scene_state))
async def new_object_type_cb(req: srpc.o.NewObjectType.Request, ui: WsClient) -> None: async with ctx_write_lock(glob.LOCK.SpecialValues.ADDING_OBJECT, ui): meta = req.args if meta.type in glob.OBJECT_TYPES: raise Arcor2Exception("Object type already exists.") hlp.is_valid_type(meta.type) if meta.base not in glob.OBJECT_TYPES: raise Arcor2Exception( f"Unknown base object type '{meta.base}', " f"known types are: {', '.join(glob.OBJECT_TYPES.keys())}.") base = glob.OBJECT_TYPES[meta.base] if base.meta.disabled: raise Arcor2Exception("Base object is disabled.") assert base.type_def is not None if issubclass(base.type_def, Robot): raise Arcor2Exception("Can't subclass Robot.") meta.has_pose = issubclass(base.type_def, GenericWithPose) if not meta.has_pose and meta.object_model: raise Arcor2Exception( "Object without pose can't have collision model.") if req.dry_run: return None obj = meta.to_object_type() ast = new_object_type(glob.OBJECT_TYPES[meta.base].meta, meta) obj.source = tree_to_str(ast) if meta.object_model: if meta.object_model.type == Model3dType.MESH: # TODO check whether mesh id exists - if so, then use existing mesh, if not, upload a new one # ...get whole mesh (focus_points) based on mesh id assert meta.object_model.mesh try: meta.object_model.mesh = await storage.get_mesh( meta.object_model.mesh.id) except storage.ProjectServiceException as e: glob.logger.error(e) raise Arcor2Exception( f"Mesh ID {meta.object_model.mesh.id} does not exist.") else: meta.object_model.model().id = meta.type await storage.put_model(meta.object_model.model()) type_def = await hlp.run_in_executor( hlp.save_and_import_type_def, obj.source, obj.id, base.type_def, settings.OBJECT_TYPE_PATH, settings.OBJECT_TYPE_MODULE, ) assert issubclass(type_def, base.type_def) actions = object_actions(type_def, ast) await storage.update_object_type(obj) glob.OBJECT_TYPES[meta.type] = ObjectTypeData(meta, type_def, actions, ast) add_ancestor_actions(meta.type, glob.OBJECT_TYPES) evt = sevts.o.ChangedObjectTypes([meta]) evt.change_type = events.Event.Type.ADD asyncio.ensure_future(notif.broadcast_event(evt)) return None
async def get_object_types() -> UpdatedObjectTypes: """Serves to initialize or update knowledge about awailable ObjectTypes. :return: """ initialization = False # initialize with built-in types, this has to be done just once if not glob.OBJECT_TYPES: logger.debug("Initialization of ObjectTypes.") initialization = True await hlp.run_in_executor(prepare_object_types_dir, settings.OBJECT_TYPE_PATH, settings.OBJECT_TYPE_MODULE) glob.OBJECT_TYPES.update(built_in_types_data()) updated_object_types: ObjectTypeDict = {} object_type_ids: Union[set[str], list[str]] = await storage.get_object_type_ids() if __debug__: # this should uncover potential problems with order in which ObjectTypes are processed import random object_type_ids = list(object_type_ids) random.shuffle(object_type_ids) for obj_id in object_type_ids: await get_object_data(updated_object_types, obj_id) removed_object_ids = { obj for obj in glob.OBJECT_TYPES.keys() if obj not in object_type_ids } - built_in_types_names() updated_object_ids = { k for k in updated_object_types.keys() if k in glob.OBJECT_TYPES } new_object_ids = { k for k in updated_object_types.keys() if k not in glob.OBJECT_TYPES } logger.debug(f"Removed ids: {removed_object_ids}") logger.debug(f"Updated ids: {updated_object_ids}") logger.debug(f"New ids: {new_object_ids}") if not initialization and removed_object_ids: # TODO remove it from sys.modules remove_evt = ChangedObjectTypes([ v.meta for k, v in glob.OBJECT_TYPES.items() if k in removed_object_ids ]) remove_evt.change_type = Event.Type.REMOVE asyncio.ensure_future(notif.broadcast_event(remove_evt)) for removed in removed_object_ids: assert removed not in built_in_types_names( ), "Attempt to remove built-in type." del glob.OBJECT_TYPES[removed] await hlp.run_in_executor(remove_object_type, removed) glob.OBJECT_TYPES.update(updated_object_types) logger.debug(f"All known ids: {glob.OBJECT_TYPES.keys()}") for obj_type in updated_object_types.values(): # if description is missing, try to get it from ancestor(s) if not obj_type.meta.description: try: obj_type.meta.description = obj_description_from_base( glob.OBJECT_TYPES, obj_type.meta) except otu.DataError as e: logger.error( f"Failed to get info from base for {obj_type}, error: '{e}'." ) if not obj_type.meta.disabled and not obj_type.meta.built_in: add_ancestor_actions(obj_type.meta.type, glob.OBJECT_TYPES) if not initialization: if updated_object_ids: update_evt = ChangedObjectTypes([ v.meta for k, v in glob.OBJECT_TYPES.items() if k in updated_object_ids ]) update_evt.change_type = Event.Type.UPDATE asyncio.ensure_future(notif.broadcast_event(update_evt)) if new_object_ids: add_evt = ChangedObjectTypes([ v.meta for k, v in glob.OBJECT_TYPES.items() if k in new_object_ids ]) add_evt.change_type = Event.Type.ADD asyncio.ensure_future(notif.broadcast_event(add_evt)) for obj_type in updated_object_types.values(): if obj_type.type_def and issubclass( obj_type.type_def, Robot) and not obj_type.type_def.abstract(): await get_robot_meta(obj_type) # if object does not change but its base has changed, it has to be reloaded for obj_id, obj in glob.OBJECT_TYPES.items(): if obj_id in updated_object_ids: continue if obj.type_def and obj.meta.base in updated_object_ids: logger.debug( f"Re-importing {obj.meta.type} because its base {obj.meta.base} type has changed." ) obj.type_def = await hlp.run_in_executor( hlp.import_type_def, obj.meta.type, Generic, settings.OBJECT_TYPE_PATH, settings.OBJECT_TYPE_MODULE, ) return UpdatedObjectTypes(new_object_ids, updated_object_ids, removed_object_ids)
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 new_object_type_cb(req: srpc.o.NewObjectType.Request, ui: WsClient) -> None: async with ctx_write_lock(glob.LOCK.SpecialValues.ADDING_OBJECT, glob.USERS.user_name(ui)): meta = req.args if meta.type in glob.OBJECT_TYPES: raise Arcor2Exception("Object type already exists.") hlp.is_valid_type(meta.type) if meta.base not in glob.OBJECT_TYPES: raise Arcor2Exception( f"Unknown base object type '{meta.base}', " f"known types are: {', '.join(glob.OBJECT_TYPES.keys())}.") base = glob.OBJECT_TYPES[meta.base] if base.meta.disabled: raise Arcor2Exception("Base object is disabled.") assert base.type_def is not None if issubclass(base.type_def, Robot): raise Arcor2Exception("Can't subclass Robot.") meta.has_pose = issubclass(base.type_def, GenericWithPose) if issubclass(base.type_def, CollisionObject): if not meta.object_model: raise Arcor2Exception( "Objects based on CollisionObject must have collision model." ) else: if meta.object_model: raise Arcor2Exception( "Only objects based on CollisionObject can have collision model." ) if req.dry_run: return None obj = meta.to_object_type() ast = new_object_type(glob.OBJECT_TYPES[meta.base].meta, meta) obj.source = tree_to_str(ast) if meta.object_model: await update_object_model(meta, meta.object_model) type_def = await hlp.run_in_executor( hlp.save_and_import_type_def, obj.source, obj.id, base.type_def, settings.OBJECT_TYPE_PATH, settings.OBJECT_TYPE_MODULE, ) assert issubclass(type_def, base.type_def) actions = object_actions(type_def, ast) meta.modified = await storage.update_object_type(obj) glob.OBJECT_TYPES[meta.type] = ObjectTypeData(meta, type_def, actions, ast) add_ancestor_actions(meta.type, glob.OBJECT_TYPES) evt = sevts.o.ChangedObjectTypes([meta]) evt.change_type = events.Event.Type.ADD asyncio.ensure_future(notif.broadcast_event(evt)) return None