def make_relative_ap_global(scene: Scene, project: Project, ap: ProjectActionPoint) -> None: """ Transforms (in place) relative AP into a global one. :param scene: :param project: :param ap: :return: """ if not ap.parent: return if ap.parent in scene.object_ids: old_parent_pose = scene.object(ap.parent).pose elif ap.parent in project.action_points_ids: old_parent_pose = Pose(project.action_point(ap.parent).position, Orientation()) else: raise Arcor2Exception("AP has unknown parent_id.") ap.position = make_pose_abs(old_parent_pose, Pose(ap.position, Orientation())).position for ori in ap.orientations: ori.orientation = make_orientation_abs(old_parent_pose.orientation, ori.orientation) if ap.parent in project.action_points_ids: parent_ap = project.action_point(ap.parent) if parent_ap.parent: ap.parent = parent_ap.parent make_relative_ap_global(scene, project, ap) ap.parent = None
def value(cls, type_defs: TypesDict, scene: Scene, project: Project, action_id: str, parameter_id: str) -> Pose: action = project.action(action_id) param = action.parameter(parameter_id) ori_id: str = cls.param_value(param) ap, ori = project.ap_and_orientation(ori_id) return Pose(ap.position, ori.orientation)
def execution_value(cls, type_defs: TypesDict, scene: Scene, project: Project, action_id: str, parameter_id: str) -> Pose: action = project.action(action_id) param = action.parameter(parameter_id) ori_id: str = cls.param_value(param) ap, _ = project.ap_and_orientation(ori_id) copy_of_ap = copy.deepcopy(ap) tr.make_relative_ap_global(scene, project, copy_of_ap) return Pose(copy_of_ap.position, ap.orientation(ori_id).orientation)
def uses_robot_joints(cls, project: Project, action_id: str, parameter_id: str, robot_joints_id: str) -> bool: param = project.action(action_id).parameter(parameter_id) value_id = cls.param_value(param) return value_id == robot_joints_id
async def list_packages_cb(req: rpc.execution.ListPackagesRequest, ui: WsClient) ->\ rpc.execution.ListPackagesResponse: resp = rpc.execution.ListPackagesResponse() subfolders = [f.path for f in os.scandir(PROJECT_PATH) if f.is_dir()] for folder_path in subfolders: if not os.path.isfile(os.path.join(folder_path, MAIN_SCRIPT_NAME)): continue package_dir = os.path.basename(folder_path) with open(os.path.join(folder_path, "data", "project.json")) as project_file: try: project = Project.from_json(project_file.read()) except ValidationError as e: await logger.error( f"Failed to parse project file of {package_dir}: {e}") continue assert project.modified resp.data.append( rpc.execution.PackageSummary(package_dir, project.id, project.modified, read_package_meta(package_dir))) # TODO report manual changes (check last modification of files)? return resp
def test_make_relative_ap_global_and_relative_again(): scene = Scene("s1", "s1") scene.objects.append( SceneObject("so1", "so1", "WhatEver", Pose(Position(3, 0, 0), Orientation()))) project = Project("p1", "p1", "s1") project.action_points.append( ProjectActionPoint("ap1", "ap1", Position(-1, 0, 0), parent="so1")) project.action_points.append( ProjectActionPoint("ap2", "ap2", Position(-1, 0, 0), parent="ap1")) ap3 = ProjectActionPoint("ap3", "ap3", Position(-1, 0, 0), parent="ap2") project.action_points.append(ap3) make_relative_ap_global(scene, project, ap3) assert ap3.parent is None assert ap3.position.x == .0 make_global_ap_relative(scene, project, ap3, "ap2") assert ap3.parent == "ap2" assert ap3.position.x == -1 ap3.parent = "something_unknown" with pytest.raises(Arcor2Exception): make_relative_ap_global(scene, project, ap3)
def test_global_aps_cls() -> None: proj = Project("test", "scene_id") pos = Position(1, 2, 3) ap1 = ActionPoint("ap1", pos) ap1_o1 = NamedOrientation("o1", Orientation(0.707, 0, 0, 0.707)) ap1.orientations.append(ap1_o1) ap1_j1 = ProjectRobotJoints("j1", "robot", [Joint("whatever", 1234)]) ap1.robot_joints.append(ap1_j1) proj.action_points.append(ap1) os.environ["ARCOR2_PROJECT_PATH"] = "/tmp" import arcor2.resources # noqa my_name = "my_module" my_spec = importlib.util.spec_from_loader(my_name, loader=None) my_module = importlib.util.module_from_spec(my_spec) cproj = CachedProject(proj) src = global_action_points_class(cproj) exec(src, my_module.__dict__) sys.modules["my_module"] = my_module aps = my_module.ActionPoints(SimResources(cproj)) # type: ignore assert aps.ap1.position == pos assert aps.ap1.position is not pos assert aps.ap1.poses.o1 == Pose(ap1.position, ap1_o1.orientation) assert aps.ap1.poses.o1.orientation is not ap1_o1.orientation assert aps.ap1.joints.j1 == ap1_j1 assert aps.ap1.joints.j1 is not ap1_j1
def make_global_ap_relative(scene: Scene, project: Project, ap: ProjectActionPoint, parent_id: str) -> None: """ Transforms (in place) global AP into a relative one with given parent (can be object or another AP). :param scene: :param project: :param ap: :param parent_id: :return: """ assert project.scene_id == scene.id if parent_id in scene.object_ids: new_parent_pose = scene.object(parent_id).pose elif parent_id in project.action_points_ids: parent_ap = project.action_point(parent_id) if parent_ap.parent: make_global_ap_relative(scene, project, ap, parent_ap.parent) new_parent_pose = Pose(parent_ap.position, Orientation()) else: raise Arcor2Exception("Unknown parent_id.") ap.position = make_pose_rel(new_parent_pose, Pose(ap.position, Orientation())).position for ori in ap.orientations: ori.orientation = make_orientation_rel(new_parent_pose.orientation, ori.orientation) ap.parent = parent_id
def value(cls, type_defs: TypesDict, scene: Scene, project: Project, action_id: str, parameter_id: str) -> Any: param = project.action(action_id).parameter(parameter_id) try: return json.loads(param.value) except json.decoder.JSONDecodeError as e: raise ParameterPluginException(f"Value of {action_id}/{parameter_id} is not a valid JSON.", e)
def test_obj_relative_ap_global() -> None: scene = Scene("s1") # object rotated 90° clock-wise so1 = SceneObject( "so1", "WhatEver", Pose(Position(1, 0, 0), Orientation(0, 0, -0.707, 0.707))) scene.objects.append(so1) cached_scene = CachedScene(scene) project = Project("p1", scene.id) ap1 = ActionPoint("ap1", Position(1, 0, 0), parent=so1.id) project.action_points.append(ap1) ap2 = ActionPoint("ap2", Position(1, 0, 0), parent=ap1.id) no1 = NamedOrientation("o1", Orientation()) ap2.orientations.append(no1) project.action_points.append(ap2) cached_project = CachedProject(project) make_relative_ap_global(cached_scene, cached_project, ap2) check_ap(ap2) assert ap2.position == Position(1, -2, 0) assert so1.pose assert no1.orientation == so1.pose.orientation make_global_ap_relative(cached_scene, cached_project, ap2, ap1.id) assert ap2.position == Position(1, 0, 0) assert no1.orientation == Orientation() check_ap(ap2)
def test_blind_branch() -> None: scene = Scene("s1") obj = SceneObject("test_name", Test.__name__) scene.objects.append(obj) project = Project("p1", "s1") ap1 = ActionPoint("ap1", Position()) project.action_points.append(ap1) ac1 = Action("ac1", f"{obj.id}/test", flows=[Flow(outputs=["bool_res"])]) ap1.actions.append(ac1) ac2 = Action("ac2", f"{obj.id}/test", flows=[Flow()]) ap1.actions.append(ac2) ac3 = Action("ac3", f"{obj.id}/test", flows=[Flow()]) ap1.actions.append(ac3) ac4 = Action("ac4", f"{obj.id}/test", flows=[Flow()]) ap1.actions.append(ac4) project.logic.append(LogicItem(LogicItem.START, ac1.id)) project.logic.append(LogicItem(ac1.id, ac2.id, ProjectLogicIf(f"{ac1.id}/default/0", json.dumps(True)))) project.logic.append(LogicItem(ac1.id, ac3.id, ProjectLogicIf(f"{ac1.id}/default/0", json.dumps(False)))) project.logic.append(LogicItem(ac2.id, ac4.id)) project.logic.append(LogicItem(ac4.id, LogicItem.END)) with pytest.raises(SourceException, match=f"Action {ac3.name} has no outputs."): program_src({Test.__name__: Test}, CachedProject(project), CachedScene(scene))
def test_constant() -> None: scene = Scene("s1") obj = SceneObject("test_name", Test.__name__) scene.objects.append(obj) project = Project("p1", "s1") ap1 = ActionPoint("ap1", Position()) project.action_points.append(ap1) const_value = 1234 const = ProjectParameter("int_const", "integer", json.dumps(const_value)) project.parameters.append(const) ac1 = Action( "ac1", f"{obj.id}/test_par", flows=[Flow()], parameters=[ ActionParameter("param", ActionParameter.TypeEnum.CONSTANT, json.dumps(const.id)) ], ) ap1.actions.append(ac1) project.logic.append(LogicItem(LogicItem.START, ac1.id)) project.logic.append(LogicItem(ac1.id, LogicItem.END)) src = program_src({Test.__name__: Test}, CachedProject(project), CachedScene(scene)) assert f"{const.name} = {const_value}" in src assert f"test_name.{Test.test_par.__name__}({const.name}, an='ac1')" in src
def make_pose_rel_to_parent(scene: Scene, project: Project, pose: Pose, parent_id: str) -> Pose: """ Transforms global Pose into Pose that is relative to a given parent (can be object or AP). :param scene: :param project: :param pose: :param parent_id: :return: """ if parent_id in scene.object_ids: parent_pose = scene.object(parent_id).pose elif parent_id in project.action_points_ids: parent_ap = project.action_point(parent_id) if parent_ap.parent: pose = make_pose_rel_to_parent(scene, project, pose, parent_ap.parent) parent_pose = Pose(parent_ap.position, Orientation()) else: raise Arcor2Exception("Unknown parent_id.") return make_pose_rel(parent_pose, pose)
def value(cls, type_defs: TypesDict, scene: Scene, project: Project, action_id: str, parameter_id: str) ->\ RelativePose: param = project.action(action_id).parameter(parameter_id) try: return RelativePose.from_json(param.value) except ValidationError as e: raise ParameterPluginException(e)
async def update_project(project: Project) -> datetime: assert project.id ret = await ps.update_project(project) project.modified = ret if not project.created: project.created = project.modified _projects_list.listing[project.id] = IdDesc(project.id, project.name, project.created, project.modified, project.description) _projects[project.id] = deepcopy(project) _projects[project.id].int_modified = None return ret
def test_slots() -> None: """Tests whether classes from cached module uses __slots__.""" s = Scene("") p = Project("", s.id) assert not hasattr(CachedScene(s), "__dict__") assert not hasattr(CachedProject(p), "__dict__") assert not hasattr(UpdateableCachedScene(s), "__dict__") assert not hasattr(UpdateableCachedProject(p), "__dict__")
def uses_orientation(cls, project: Project, action_id: str, parameter_id: str, orientation_id: str) -> bool: action = project.action(action_id) param = action.parameter(parameter_id) for ori_id in cls.param_value_list(param): if ori_id == orientation_id: return True return False
def test_get_value() -> None: p = Pose(Position(1, 2, 3), Orientation(1, 0, 0, 0)) scene = Scene("s1") obj = SceneObject("test_name", TestObject.__name__) scene.objects.append(obj) project = Project("p1", "s1") ap1 = ActionPoint("ap1", Position(1, 0, 0)) project.action_points.append(ap1) ap2 = ActionPoint("ap2", Position(), parent=ap1.id) project.action_points.append(ap2) ori1 = NamedOrientation("ori1", p.orientation) ap2.orientations.append(ori1) invalid_param_name = "invalid_param" ac1 = Action( "ac1", f"{obj.id}/{TestObject.action.__name__}", parameters=[ ActionParameter(param_name, PosePlugin.type_name(), json.dumps(ori1.id)), ActionParameter(invalid_param_name, PosePlugin.type_name(), json.dumps("non_sense")), ], ) ap1.actions.append(ac1) cscene = CachedScene(scene) cproject = CachedProject(project) with pytest.raises(Arcor2Exception): PosePlugin.parameter_value(type_defs, cscene, cproject, ac1.id, "non_sense") with pytest.raises(Arcor2Exception): PosePlugin.parameter_value(type_defs, cscene, cproject, "non_sense", param_name) with pytest.raises(ParameterPluginException): PosePlugin.parameter_value(type_defs, cscene, cproject, ac1.id, invalid_param_name) value = PosePlugin.parameter_value(type_defs, cscene, cproject, ac1.id, param_name) exe_value = PosePlugin.parameter_execution_value(type_defs, cscene, cproject, ac1.id, param_name) assert value == value assert value != exe_value
def project() -> UpdateableCachedProject: project = Project("p1", "s1") ap1 = ActionPoint("ap1", Position()) project.action_points.append(ap1) ap1.actions.append(ac1) ap1.actions.append(ac2) ap1.actions.append(ac3) ap1.actions.append(ac4) return UpdateableCachedProject(project)
def value(cls, type_defs: TypesDict, scene: Scene, project: Project, action_id: str, parameter_id: str) \ -> List[Pose]: ret: List[Pose] = [] ap, action = project.action_point_and_action(action_id) parameter = action.parameter(parameter_id) for orientation_id in cls.param_value_list(parameter): ret.append( Pose(ap.position, ap.orientation(orientation_id).orientation)) return ret
def test_get_value() -> None: img = Image.new("RGB", (320, 240)) scene = Scene("s1", "s1") obj = SceneObject("test_name", TestObject.__name__) scene.objects.append(obj) project = Project("p1", "s1") ap1 = ActionPoint("ap1", Position(1, 0, 0)) project.action_points.append(ap1) invalid_param_name = "invalid_param" ac1 = Action( "ac1", f"{obj.id}/{TestObject.action.__name__}", parameters=[ ActionParameter(param_name, ImagePlugin.type_name(), ImagePlugin.value_to_json(img)), ActionParameter(invalid_param_name, ImagePlugin.type_name(), json.dumps("non_sense")), ], ) ap1.actions.append(ac1) cscene = CachedScene(scene) cproject = CachedProject(project) with pytest.raises(Arcor2Exception): ImagePlugin.parameter_value(type_defs, cscene, cproject, ac1.id, "non_sense") with pytest.raises(Arcor2Exception): ImagePlugin.parameter_value(type_defs, cscene, cproject, "non_sense", param_name) with pytest.raises(ParameterPluginException): ImagePlugin.parameter_value(type_defs, cscene, cproject, ac1.id, invalid_param_name) value = ImagePlugin.parameter_value(type_defs, cscene, cproject, ac1.id, param_name) exe_value = ImagePlugin.parameter_execution_value(type_defs, cscene, cproject, ac1.id, param_name) assert value == value assert value == exe_value
def test_get_value() -> None: scene = Scene("s1", "s1") obj = SceneObject("test_name", TestObject.__name__) prj = ProjectRobotJoints("name", obj.id, [Joint("name", 0.333)]) scene.objects.append(obj) project = Project("p1", "s1") ap1 = ActionPoint("ap1", Position(1, 0, 0)) ap1.robot_joints.append(prj) project.action_points.append(ap1) invalid_param_name = "invalid_param" act = Action( "ac1", f"{obj.id}/{TestObject.action.__name__}", parameters=[ ActionParameter(param_name, JointsPlugin.type_name(), json.dumps(prj.id)), ActionParameter(invalid_param_name, JointsPlugin.type_name(), json.dumps("non_sense")), ], ) ap1.actions.append(act) cscene = CachedScene(scene) cproject = CachedProject(project) with pytest.raises(Arcor2Exception): JointsPlugin.parameter_value(type_defs, cscene, cproject, act.id, "non_sense") with pytest.raises(Arcor2Exception): JointsPlugin.parameter_value(type_defs, cscene, cproject, "non_sense", param_name) with pytest.raises(ParameterPluginException): JointsPlugin.parameter_value(type_defs, cscene, cproject, act.id, invalid_param_name) value = JointsPlugin.parameter_value(type_defs, cscene, cproject, act.id, param_name) exe_value = JointsPlugin.parameter_execution_value(type_defs, cscene, cproject, act.id, param_name) assert value == value assert value == exe_value
def test_get_value(self, val: str) -> None: scene = Scene("s1") obj = SceneObject("test_name", TestObject.__name__) scene.objects.append(obj) project = Project("p1", "s1") ap1 = ActionPoint("ap1", Position()) project.action_points.append(ap1) invalid_param_name = "invalid_param" ac1 = Action( "ac1", f"{obj.id}/{TestObject.action.__name__}", parameters=[ ActionParameter(param_name, StringPlugin.type_name(), StringPlugin.value_to_json(val)), ActionParameter(invalid_param_name, StringPlugin.type_name(), json.dumps(666)), ], ) ap1.actions.append(ac1) cscene = CachedScene(scene) cproject = CachedProject(project) with pytest.raises(Arcor2Exception): StringPlugin.parameter_value({}, cscene, cproject, ac1.id, "non_sense") with pytest.raises(Arcor2Exception): StringPlugin.parameter_value({}, cscene, cproject, "non_sense", param_name) with pytest.raises(ParameterPluginException): StringPlugin.parameter_value({}, cscene, cproject, ac1.id, invalid_param_name) value = StringPlugin.parameter_value({}, cscene, cproject, ac1.id, param_name) exe_value = StringPlugin.parameter_execution_value({}, cscene, cproject, ac1.id, param_name) assert value == val assert value == exe_value
def clone_project( project_id: str, new_project_name: str, new_description: Optional[str] = None, new_project_id: Optional[str] = None ) -> Project: if not new_project_id: new_project_id = Project.uid() params: Dict[str, str] = { "project_id": project_id, "new_project_name": new_project_name, "new_project_id": new_project_id, } if new_description: params["new_description"] = new_description return rest.call(rest.Method.PUT, f"{URL}/projects/close", params=params, return_type=Project)
def test_make_relative_ap_global_and_relative_again() -> None: scene = Scene("s1") so1 = SceneObject("so1", "WhatEver", Pose(Position(3, 0, 0), Orientation())) scene.objects.append(so1) cached_scene = CachedScene(scene) project = Project("p1", scene.id) ap1 = ActionPoint("ap1", Position(-1, 0, 0), parent=so1.id) project.action_points.append(ap1) ap2 = ActionPoint("ap2", Position(-1, 0, 0), parent=ap1.id) project.action_points.append(ap2) ap3 = ActionPoint( "ap3", Position(-1, 0, 0), parent=ap2.id, orientations=[NamedOrientation("bla", random_orientation())]) project.action_points.append(ap3) cached_project = CachedProject(project) assert ap3.parent ap3_parent = get_parent_pose(cached_scene, cached_project, ap3.parent) assert Pose(ap2.position, Orientation()) == ap3_parent.pose assert ap3_parent.parent_id == ap1.id make_relative_ap_global(cached_scene, cached_project, ap3) check_ap(ap3) assert ap3.parent is None assert ap3.position.x == 0.0 # type: ignore make_global_ap_relative(cached_scene, cached_project, ap3, ap2.id) check_ap(ap3) assert ap3.parent == ap2.id assert ap3.position.x == -1 ap3.parent = "something_unknown" with pytest.raises(Arcor2Exception): make_relative_ap_global(cached_scene, cached_project, ap3)
def test_prev_result() -> None: scene = Scene("s1") obj = SceneObject("test_name", Test.__name__) scene.objects.append(obj) project = Project("p1", "s1") ap1 = ActionPoint("ap1", Position()) project.action_points.append(ap1) ac1 = Action("ac1", f"{obj.id}/{Test.get_int.__name__}", flows=[Flow(outputs=["res"])]) ap1.actions.append(ac1) ac2 = Action( "ac2", f"{obj.id}/{Test.test_par.__name__}", flows=[Flow()], parameters=[ ActionParameter("param", ActionParameter.TypeEnum.LINK, json.dumps(f"{ac1.id}/default/0")) ], ) ap1.actions.append(ac2) project.logic.append(LogicItem(LogicItem.START, ac1.id)) project.logic.append(LogicItem(ac1.id, ac2.id)) project.logic.append(LogicItem(ac2.id, LogicItem.END)) src = program_src({Test.__name__: Test}, CachedProject(project), CachedScene(scene)) assert f"res = test_name.{Test.get_int.__name__}(an='{ac1.name}')" in src assert f"test_name.{Test.test_par.__name__}(res, an='{ac2.name}')" in src # test wrong order of logic project.logic.clear() project.logic.append(LogicItem(LogicItem.START, ac2.id)) project.logic.append(LogicItem(ac2.id, ac1.id)) project.logic.append(LogicItem(ac1.id, LogicItem.END)) with pytest.raises(SourceException): program_src({Test.__name__: Test}, CachedProject(project), CachedScene(scene))
def execution_value(cls, type_defs: TypesDict, scene: Scene, project: Project, action_id: str, parameter_id: str) \ -> List[Pose]: ap, action = project.action_point_and_action(action_id) if not ap.parent: return cls.value(type_defs, scene, project, action_id, parameter_id) parameter = action.parameter(parameter_id) ret: List[Pose] = [] copy_of_ap = copy.deepcopy(ap) tr.make_relative_ap_global(scene, project, copy_of_ap) for orientation_id in cls.param_value_list(parameter): ret.append( Pose(ap.position, ap.orientation(orientation_id).orientation)) return ret
def value(cls, type_defs: TypesDict, scene: Scene, project: Project, action_id: str, parameter_id: str) -> \ ProjectRobotJoints: ap, action = project.action_point_and_action(action_id) param = action.parameter(parameter_id) joints_id = cls.param_value(param) robot_id, action_method_name = action.parse_type() robot_type = scene.object_or_service(robot_id) if issubclass(type_defs[robot_type.type], RobotService): for param in action.parameters: if param.id == "robot_id": robot_id = json.loads(param.value) break else: raise ParameterPluginException( f"Parameter {param.id} of action {action.id} depends on" f" 'robot_id' parameter, which could not be found.") return ap.joints_for_robot(robot_id, joints_id)
def value(cls, type_defs: TypesDict, scene: Scene, project: Project, action_id: str, parameter_id: str) -> Enum: action = project.action(action_id) param = action.parameter(parameter_id) obj_id, action_type = action.parse_type() obj_type = type_defs[scene.object_or_service(obj_id).type] method = getattr(obj_type, action_type) ttype = get_type_hints(method)[param.id] if not issubclass(ttype, cls.type()): raise ParameterPluginException( f"Type {ttype.__name__} is not subclass of {cls.type().__name__}." ) try: return ttype(json.loads(param.value)) except ValueError: raise ParameterPluginException( f"Parameter {parameter_id} of action {action.name} has invalid value." )
def test_list_problems(start_processes: None, ars: ARServer) -> None: """This is a test for a bug in get_scene_problems and get_project_problems functions. Because of the bug, the RPC returned result=False when there was non-existing ObjectType. :param start_processes: :param ars: :return: """ upload_def(DummyMultiArmRobot ) # OT has to exist first, otherwise scene can't be stored invalid_scene_1 = Scene("invalidScene", objects=[ SceneObject("invalidObject", DummyMultiArmRobot.__name__, Pose()) ]) invalid_project_1 = Project("invalidProject", invalid_scene_1.id) project_service.update_scene(invalid_scene_1) project_service.update_project(invalid_project_1) project_service.delete_object_type( DummyMultiArmRobot.__name__) # now the OT can be deleted # initial event show_main_screen_event = event(ars, events.c.ShowMainScreen) assert show_main_screen_event.data assert show_main_screen_event.data.what == events.c.ShowMainScreen.Data.WhatEnum.ScenesList scenes = ars.call_rpc(rpc.s.ListScenes.Request(get_id()), rpc.s.ListScenes.Response) assert scenes.result assert scenes.data assert len(scenes.data) == 1 assert scenes.data[0].id == invalid_scene_1.id assert scenes.data[0].name == invalid_scene_1.name assert scenes.data[0].problems assert len(scenes.data[0].problems) == 1 projects = ars.call_rpc(rpc.p.ListProjects.Request(get_id()), rpc.p.ListProjects.Response) assert projects.result assert projects.data assert len(projects.data) == 1 assert projects.data[0].id == invalid_project_1.id assert projects.data[0].name == invalid_project_1.name assert projects.data[0].problems assert len(projects.data[0].problems) == 1 upload_def(DummyMultiArmRobot) # now the ObjectType exists, so the scene/project should be ok scenes2 = ars.call_rpc(rpc.s.ListScenes.Request(get_id()), rpc.s.ListScenes.Response) assert scenes2.result assert scenes2.data assert len(scenes2.data) == 1 assert scenes2.data[0].id == invalid_scene_1.id assert scenes2.data[0].name == invalid_scene_1.name assert scenes2.data[0].problems is None projects2 = ars.call_rpc(rpc.p.ListProjects.Request(get_id()), rpc.p.ListProjects.Response) assert projects2.result assert projects2.data assert len(projects2.data) == 1 assert projects2.data[0].id == invalid_project_1.id assert projects2.data[0].name == invalid_project_1.name assert projects2.data[0].problems is None