def put_fk() -> RespT: """Get the current state. --- put: description: Get the current state. tags: - Robot requestBody: content: application/json: schema: type: array items: $ref: Joint responses: 200: description: Ok content: application/json: schema: $ref: Pose 403: description: Not started """ assert _dobot is not None if not isinstance(request.json, list): raise FlaskException("Body should be a JSON array containing joints.", error_code=400) joints = [Joint.from_dict(j) for j in request.json] return jsonify(_dobot.forward_kinematics(joints))
def put_eef_pose() -> RespT: """Set the EEF pose. --- put: description: Set the EEF pose. tags: - Robot parameters: - in: query name: moveType schema: type: string enum: - JUMP - LINEAR - JOINTS required: true description: Move type - name: velocity in: query schema: type: number format: float minimum: 0 maximum: 100 - name: acceleration in: query schema: type: number format: float minimum: 0 maximum: 100 requestBody: content: application/json: schema: $ref: Pose responses: 200: description: Ok content: application/json: schema: $ref: Pose 403: description: Not started """ assert _dobot is not None if not isinstance(request.json, dict): raise FlaskException("Body should be a JSON dict containing Pose.", error_code=400) pose = Pose.from_dict(request.json) move_type: str = request.args.get("moveType", "jump") velocity = float(request.args.get("velocity", default=50.0)) acceleration = float(request.args.get("acceleration", default=50.0)) _dobot.move(pose, MoveType(move_type), velocity, acceleration) return jsonify("ok")
def put_ik() -> RespT: """Get the current state. --- put: description: Get the current state. tags: - Robot requestBody: content: application/json: schema: $ref: Pose responses: 200: description: Ok content: application/json: schema: type: array items: $ref: Joint 403: description: Not started """ assert _dobot is not None if not isinstance(request.json, dict): raise FlaskException("Body should be a JSON dict containing Pose.", error_code=400) pose = Pose.from_dict(request.json) return jsonify(_dobot.inverse_kinematics(pose))
def put_box() -> RespT: """Add or update box. --- put: tags: - Models description: Add or update service type. requestBody: content: application/json: schema: $ref: Box responses: 200: description: Ok content: application/json: schema: type: string """ if not isinstance(request.json, dict): raise FlaskException("Body should be a JSON dict containing Box.", error_code=400) # box = object_type.Box.from_dict(humps.decamelize(request.json)) # TODO disabled because of bug in pyhumps box = object_type.Box(request.json["id"], request.json["sizeX"], request.json["sizeY"], request.json["sizeZ"]) BOXES[box.id] = box return jsonify("ok"), 200
def put_mesh() -> RespT: """Add or update collision mesh. --- put: tags: - Collisions description: Add or update collision mesh. parameters: - name: meshId in: query schema: type: string - name: meshFileId in: query schema: type: string - name: meshScaleX in: query schema: type: number format: float default: 1.0 - name: meshScaleY in: query schema: type: number format: float default: 1.0 - name: meshScaleZ in: query schema: type: number format: float default: 1.0 requestBody: content: application/json: schema: $ref: Pose responses: 200: description: Ok content: application/json: schema: type: string """ if not isinstance(request.json, dict): raise FlaskException("Body should be a JSON dict containing Pose.", error_code=400) args = humps.decamelize(request.args.to_dict()) mesh = object_type.Mesh(args["mesh_id"], args["mesh_file_id"]) collision_objects[mesh.id] = CollisionObject( mesh, common.Pose.from_dict(humps.decamelize(request.json))) return jsonify("ok"), 200
def put_start() -> RespT: """Start the robot. --- put: description: Start the robot. tags: - State parameters: - in: query name: port schema: type: string default: /dev/dobot description: Dobot port - in: query name: model schema: type: string enum: - magician - m1 required: true description: Dobot model requestBody: content: application/json: schema: $ref: Pose responses: 204: description: Ok 403: description: Already started """ if started(): return "Already started.", 403 model: str = request.args.get("model", default="magician") port: str = request.args.get("port", default="/dev/dobot") if not isinstance(request.json, dict): raise FlaskException("Body should be a JSON dict containing Pose.", error_code=400) pose = Pose.from_dict(request.json) mapping: dict[str, type[Dobot]] = { "magician": DobotMagician, "m1": DobotM1 } global _dobot _dobot = mapping[model](pose, port, _mock) return Response(status=204)
def get_base_from_imported_package(obj_type: ObjectType, types_dict: dict[str, ObjectType], zip_file: zipfile.ZipFile, tmp_dir: str, ast: ast.AST) -> None: for idx, base in enumerate(base_from_source(ast, obj_type.id)): if base in types_dict.keys() | built_in_types_names(): continue logger.debug(f"Getting {base} as base of {obj_type.id}.") try: base_obj_type_src = read_str_from_zip( zip_file, f"object_types/{humps.depascalize(base)}.py") except KeyError: raise FlaskException( f"Could not find {base} object type (base of {obj_type.id}).", error_code=401) # first try if the code is valid try: base_ast = parse(base_obj_type_src) except Arcor2Exception: raise FlaskException( f"Invalid code of the {base} (base of {obj_type.id}).", error_code=401) types_dict[base] = ObjectType(base, base_obj_type_src) # try to get base of the base get_base_from_imported_package(types_dict[base], types_dict, zip_file, tmp_dir, base_ast) # then, try to import it (no need to store the result) if idx == 0: # this is the base ObjectType save_and_import_type_def(base_obj_type_src, base, Generic, tmp_dir, OBJECT_TYPE_MODULE) else: # these are potential mixins save_and_import_type_def(base_obj_type_src, base, object, tmp_dir, OBJECT_TYPE_MODULE)
def put_box() -> RespT: """Add or update collision box. --- put: tags: - Collisions description: Add or update collision box. parameters: - name: boxId in: query schema: type: string - name: sizeX in: query schema: type: number format: float - name: sizeY in: query schema: type: number format: float - name: sizeZ in: query schema: type: number format: float requestBody: content: application/json: schema: $ref: Pose responses: 200: description: Ok content: application/json: schema: type: string """ if not isinstance(request.json, dict): raise FlaskException("Body should be a JSON dict containing Pose.", error_code=400) args = request.args.to_dict() box = object_type.Box(args["boxId"], float(args["sizeX"]), float(args["sizeY"]), float(args["sizeZ"])) collision_objects[box.id] = CollisionObject( box, common.Pose.from_dict(humps.decamelize(request.json))) return jsonify("ok"), 200
def put_scene() -> RespT: """Add or update scene. --- put: tags: - Scene description: Add or update scene. requestBody: content: application/json: schema: $ref: Scene responses: 200: description: Timestamp of last scene modification. content: application/json: schema: type: string format: date-time 404: description: Object type with specific id related to putted scene not exist. content: application/json: schema: $ref: WebApiError """ if not isinstance(request.json, dict): raise FlaskException("Body should be a JSON dict containing Scene.", error_code=400) scene = common.Scene.from_dict(humps.decamelize(request.json)) for obj in scene.objects: if obj.type not in OBJECT_TYPES: return common.WebApiError(f"ObjectType {obj.type} does not exist.", PROJECT_SERVICE_NAME).to_json(), 404 scene.modified = datetime.now(tz=timezone.utc) scene.int_modified = None if scene.id not in SCENES: scene.created = scene.modified else: scene.created = SCENES[scene.id].created SCENES[scene.id] = scene return jsonify(scene.modified.isoformat())
def put_project() -> RespT: """Add or update project. --- put: tags: - Project description: Add or update project. requestBody: content: application/json: schema: $ref: Project responses: 200: description: Timestamp of last project modification. content: application/json: schema: type: string 404: description: Scene with specific id related to project not found. content: application/json: schema: $ref: WebApiError """ if not isinstance(request.json, dict): raise FlaskException("Body should be a JSON dict containing Project.", error_code=400) project = common.Project.from_dict(humps.decamelize(request.json)) if project.scene_id not in SCENES: return common.WebApiError(f"Scene {id} does not exist.", PROJECT_SERVICE_NAME).to_json(), 404 project.modified = datetime.now(tz=timezone.utc) project.int_modified = None if project.id not in PROJECTS: project.created = project.modified else: project.created = PROJECTS[project.id].created PROJECTS[project.id] = project return jsonify(project.modified.isoformat())
def put_cylinder() -> RespT: """Add or update collision cylinder. --- put: tags: - Collisions description: Add or update collision cylinder. parameters: - name: cylinderId in: query schema: type: string - name: radius in: query schema: type: number format: float - name: height in: query schema: type: number format: float requestBody: content: application/json: schema: $ref: Pose responses: 200: description: Ok content: application/json: schema: type: string """ if not isinstance(request.json, dict): raise FlaskException("Body should be a JSON dict containing Pose.", error_code=400) args = humps.decamelize(request.args.to_dict()) cylinder = object_type.Cylinder(args["cylinder_id"], float(args["radius"]), float(args["height"])) collision_objects[cylinder.id] = CollisionObject( cylinder, common.Pose.from_dict(humps.decamelize(request.json))) return jsonify("ok"), 200
def get_base_from_project_service( types_dict: TypesDict, tmp_dir: str, scene_object_types: set[str], obj_type: ObjectType, zf: zipfile.ZipFile, ot_path: str, ast: ast.AST, ) -> None: for idx, base in enumerate(base_from_source(ast, obj_type.id)): if base in types_dict.keys() | built_in_types_names( ) | scene_object_types: continue logger.debug(f"Getting {base} as base of {obj_type.id}.") base_obj_type = ps.get_object_type(base) # first try if the code is valid try: base_ast = parse(base_obj_type.source) except Arcor2Exception: raise FlaskException( f"Invalid code of the {base_obj_type.id} (base of {obj_type.id}).", error_code=401) # try to get base of the base get_base_from_project_service(types_dict, tmp_dir, scene_object_types, base_obj_type, zf, ot_path, base_ast) if idx == 0: # this is the base ObjectType types_dict[base_obj_type.id] = save_and_import_type_def( base_obj_type.source, base_obj_type.id, Generic, tmp_dir, OBJECT_TYPE_MODULE) else: # these are potential mixins (just try to import them, no need to store them) save_and_import_type_def(base_obj_type.source, base_obj_type.id, object, tmp_dir, OBJECT_TYPE_MODULE) scene_object_types.add(base_obj_type.id) zf.writestr( os.path.join(ot_path, humps.depascalize(base_obj_type.id)) + ".py", base_obj_type.source)
def put_object_type() -> RespT: """Add or update object type. --- put: tags: - ObjectType description: Add or update object type. requestBody: content: application/json: schema: $ref: ObjectType responses: 200: description: Ok content: application/json: schema: type: string format: date-time """ if not isinstance(request.json, dict): raise FlaskException( "Body should be a JSON dict containing ObjectType.", error_code=400) obj_type = object_type.ObjectType.from_dict(humps.decamelize(request.json)) obj_type.modified = datetime.now(tz=timezone.utc) if obj_type.id not in OBJECT_TYPES: obj_type.created = obj_type.modified else: obj_type.created = OBJECT_TYPES[obj_type.id].created OBJECT_TYPES[obj_type.id] = obj_type return jsonify(obj_type.modified.isoformat()), 200
def put_box() -> RespT: """Add or update box. --- put: tags: - Models description: Add or update service type. requestBody: content: application/json: schema: $ref: Box responses: 200: description: Ok """ if not isinstance(request.json, dict): raise FlaskException("Body should be a JSON dict containing Box.", error_code=400) box = object_type.Box.from_dict(humps.decamelize(request.json)) BOXES[box.id] = box return Response(status=200)
def put_cylinder() -> RespT: """Add or update cylinder. --- put: tags: - Models description: Add or update service type. requestBody: content: application/json: schema: $ref: Cylinder responses: 200: description: Ok """ if not isinstance(request.json, dict): raise FlaskException("Body should be a JSON dict containing Cylinder.", error_code=400) cylinder = object_type.Cylinder.from_dict(humps.decamelize(request.json)) CYLINDERS[cylinder.id] = cylinder return Response(status=200)
def put_sphere() -> RespT: """Add or update sphere. --- put: tags: - Models description: Add or update sphere. requestBody: content: application/json: schema: $ref: Sphere responses: 200: description: Ok """ if not isinstance(request.json, dict): raise FlaskException("Body should be a JSON dict containing Sphere.", error_code=400) sphere = object_type.Sphere.from_dict(humps.decamelize(request.json)) SPHERES[sphere.id] = sphere return Response(status=200)
def put_mesh() -> RespT: """Add or update mesh. --- put: tags: - Models description: Add or update mesh. requestBody: content: application/json: schema: $ref: Mesh responses: 200: description: Ok """ if not isinstance(request.json, dict): raise FlaskException("Body should be a JSON dict containing Mesh.", error_code=400) mesh = object_type.Mesh.from_dict(humps.decamelize(request.json)) MESHES[mesh.id] = mesh return Response(status=200)
def project_import() -> RespT: """Imports a project from execution package. --- put: description: Imports a project from execution package. parameters: - in: query name: overwriteScene schema: type: boolean default: false - in: query name: overwriteProject schema: type: boolean default: false - in: query name: overwriteObjectTypes schema: type: boolean default: false - in: query name: overwriteProjectSources schema: type: boolean default: false - in: query name: overwriteCollisionModels schema: type: boolean default: false requestBody: content: multipart/form-data: schema: type: object required: - executionPackage properties: executionPackage: type: string format: binary responses: 200: description: Ok content: application/json: schema: $ref: ImportResult 400: description: Some other error occurred. content: application/json: schema: type: string 401: description: Invalid execution package. content: application/json: schema: type: string 402: description: A difference between package/project service detected (overwrite needed). content: application/json: schema: type: string 404: description: Something is missing. content: application/json: schema: type: string """ file = request.files["executionPackage"] overwrite_scene = request.args.get("overwriteScene", default="false") == "true" overwrite_project = request.args.get("overwriteProject", default="false") == "true" overwrite_object_types = request.args.get("overwriteObjectTypes", default="false") == "true" overwrite_project_sources = request.args.get("overwriteProjectSources", default="false") == "true" overwrite_collision_models = request.args.get("overwriteCollisionModels", default="false") == "true" objects: Dict[str, ObjectType] = {} models: Dict[str, Models] = {} """ 1) get and validate all data from zip 2) check what is already on the Project service 3) do updates """ # BytesIO + stream.read() = workaround for a Python bug (SpooledTemporaryFile not seekable) with zipfile.ZipFile(BytesIO(file.stream.read())) as zip_file: try: project = read_dc_from_zip(zip_file, "data/project.json", Project) except KeyError: raise FlaskException("Could not find project.json.", error_code=404) except (JSONDecodeError, ValidationError) as e: raise FlaskException(f"Failed to process project.json. {str(e)}", error_code=401) try: scene = read_dc_from_zip(zip_file, "data/scene.json", Scene) except KeyError: raise FlaskException("Could not find scene.json.", error_code=404) except (JSONDecodeError, ValidationError) as e: return json.dumps(f"Failed to process scene.json. {str(e)}"), 401 if project.scene_id != scene.id: raise FlaskException("Project assigned to different scene id.", error_code=401) for scene_obj in scene.objects: obj_type_name = scene_obj.type if obj_type_name in objects: continue try: obj_type_src = read_str_from_zip( zip_file, f"object_types/{humps.depascalize(obj_type_name)}.py") except KeyError: raise FlaskException( f"Object type {obj_type_name} is missing in the package.", error_code=404) try: parse(obj_type_src) except Arcor2Exception: raise FlaskException( f"Invalid code of the {obj_type_name} object type.", error_code=401) # TODO description (is it used somewhere?) objects[obj_type_name] = ObjectType(obj_type_name, obj_type_src) logger.debug(f"Just imported {obj_type_name}.") while True: base = base_from_source(obj_type_src, obj_type_name) if not base: return json.dumps( f"Could not determine base class for {scene_obj.type}." ), 401 if base in objects.keys() | built_in_types_names(): break logger.debug(f"Importing {base} as a base of {obj_type_name}.") try: base_obj_type_src = read_str_from_zip( zip_file, f"object_types/{humps.depascalize(base)}.py") except KeyError: return json.dumps( f"Could not find {base} object type (base of {obj_type_name})." ), 404 try: parse(base_obj_type_src) except Arcor2Exception: return json.dumps( f"Invalid code of the {base} object type (base of {obj_type_name})." ), 401 objects[base] = ObjectType(base, base_obj_type_src) obj_type_name = base obj_type_src = base_obj_type_src for obj_type in objects.values(): # handle models try: model = read_dc_from_zip( zip_file, f"data/models/{humps.depascalize(obj_type.id)}.json", ObjectModel).model() except KeyError: continue logger.debug(f"Found model {model.id} of type {model.type}.") obj_type.model = model.metamodel() if obj_type.id != obj_type.model.id: raise FlaskException( f"Model id ({obj_type.model.id}) has to be the same as ObjectType id ({obj_type.id}).", error_code=401, ) models[obj_type.id] = model if not project.has_logic: logger.debug("Importing the main script.") try: script = zip_file.read("script.py").decode("UTF-8") except KeyError: raise FlaskException("Could not find script.py.", error_code=404) try: parse(script) except Arcor2Exception: raise FlaskException("Invalid code of the main script.", error_code=401) # check that we are not going to overwrite something if not overwrite_scene: try: ps_scene = ps.get_scene(scene.id) except ps.ProjectServiceException: pass else: ps_scene.modified = scene.modified # modified is updated with each PUT if ps_scene != scene: raise FlaskException( "Scene difference detected. Overwrite needed.", error_code=402) if not overwrite_project: try: ps_project = ps.get_project(project.id) except ps.ProjectServiceException: pass else: ps_project.modified = project.modified if ps_project != project: raise FlaskException( "Project difference detected. Overwrite needed.", error_code=402) if not overwrite_object_types: for obj_type in objects.values(): try: if ps.get_object_type(obj_type.id) != obj_type: raise FlaskException( f"Difference detected for {obj_type.id} object type. Overwrite needed.", error_code=402) except ps.ProjectServiceException: pass if not overwrite_project_sources and not project.has_logic: try: if ps.get_project_sources(project.id).script != script: raise FlaskException( "Script difference detected. Overwrite needed.", error_code=402) except ps.ProjectServiceException: pass if not overwrite_collision_models: for model in models.values(): try: if model != ps.get_model(model.id, model.type()): raise FlaskException( "Collision model difference detected. Overwrite needed.", error_code=402) except ps.ProjectServiceException: pass for model in models.values(): ps.put_model(model) for obj_type in objects.values(): ps.update_object_type(obj_type) ps.update_scene(scene) ps.update_project(project) if not project.has_logic: ps.update_project_sources(ProjectSources(project.id, script)) logger.info( f"Imported project {project.name} (scene {scene.name}), with {len(objects)} " f"object type(s) and {len(models)} model(s).") return ImportResult(scene.id, project.id).to_json(), 200
def project_import() -> RespT: """Imports a project from execution package. --- put: tags: - Build summary: Imports a project from execution package. operationId: ProjectImport parameters: - in: query name: overwriteScene schema: type: boolean default: false description: overwrite Scene - in: query name: overwriteProject schema: type: boolean default: false description: overwrite Project - in: query name: overwriteObjectTypes schema: type: boolean default: false description: overwrite ObjectTypes - in: query name: overwriteProjectSources schema: type: boolean default: false description: overwrite ProjectSources - in: query name: overwriteCollisionModels schema: type: boolean default: false description: overwrite collision models requestBody: content: multipart/form-data: schema: type: object required: - executionPackage properties: executionPackage: type: string format: binary responses: 200: description: Ok content: application/json: schema: $ref: ImportResult 400: description: Some other error occurred. content: application/json: schema: type: string 401: description: Invalid execution package. content: application/json: schema: type: string 402: description: A difference between package/project service detected (overwrite needed). content: application/json: schema: type: string 404: description: Something is missing. content: application/json: schema: type: string """ file = request.files["executionPackage"] overwrite_scene = request.args.get("overwriteScene", default="false") == "true" overwrite_project = request.args.get("overwriteProject", default="false") == "true" overwrite_object_types = request.args.get("overwriteObjectTypes", default="false") == "true" overwrite_project_sources = request.args.get("overwriteProjectSources", default="false") == "true" overwrite_collision_models = request.args.get("overwriteCollisionModels", default="false") == "true" objects: dict[str, ObjectType] = {} models: dict[str, Models] = {} """ 1) get and validate all data from zip 2) check what is already on the Project service 3) do updates """ # BytesIO + stream.read() = workaround for a Python bug (SpooledTemporaryFile not seekable) with zipfile.ZipFile(BytesIO(file.stream.read())) as zip_file: try: project = read_dc_from_zip(zip_file, "data/project.json", Project) except KeyError: raise FlaskException("Could not find project.json.", error_code=404) except (json.JsonException, ValidationError) as e: raise FlaskException(f"Failed to process project.json. {str(e)}", error_code=401) try: scene = read_dc_from_zip(zip_file, "data/scene.json", Scene) except KeyError: raise FlaskException("Could not find scene.json.", error_code=404) except (json.JsonException, ValidationError) as e: return json.dumps(f"Failed to process scene.json. {str(e)}"), 401 if project.scene_id != scene.id: raise FlaskException("Project assigned to different scene id.", error_code=401) with tempfile.TemporaryDirectory() as tmp_dir: # restore original environment sys.path = list(original_sys_path) sys.modules = dict(original_sys_modules) prepare_object_types_dir(tmp_dir, OBJECT_TYPE_MODULE) for scene_obj in scene.objects: obj_type_name = scene_obj.type if obj_type_name in objects: # there might be more instances of the same type continue logger.debug(f"Importing {obj_type_name}.") try: obj_type_src = read_str_from_zip( zip_file, f"object_types/{humps.depascalize(obj_type_name)}.py") except KeyError: raise FlaskException( f"Object type {obj_type_name} is missing in the package.", error_code=404) try: ast = parse(obj_type_src) except Arcor2Exception: raise FlaskException( f"Invalid code of the {obj_type_name} object type.", error_code=401) # TODO fill in OT description (is it used somewhere?) objects[obj_type_name] = ObjectType(obj_type_name, obj_type_src) get_base_from_imported_package(objects[obj_type_name], objects, zip_file, tmp_dir, ast) type_def = save_and_import_type_def(obj_type_src, obj_type_name, Generic, tmp_dir, OBJECT_TYPE_MODULE) assert obj_type_name == type_def.__name__ if type_def.abstract(): raise FlaskException( f"Scene contains abstract object type: {obj_type_name}.", error_code=401) for obj_type in objects.values(): # handle models # TODO rather iterate on content of data/models? try: model = read_dc_from_zip( zip_file, f"data/models/{humps.depascalize(obj_type.id)}.json", ObjectModel).model() except KeyError: continue logger.debug(f"Found model {model.id} of type {model.type}.") obj_type.model = model.metamodel() if obj_type.id != obj_type.model.id: raise FlaskException( f"Model id ({obj_type.model.id}) has to be the same as ObjectType id ({obj_type.id}).", error_code=401, ) models[obj_type.id] = model if not project.has_logic: logger.debug("Importing the main script.") try: script = zip_file.read("script.py").decode("UTF-8") except KeyError: raise FlaskException("Could not find script.py.", error_code=404) try: parse(script) except Arcor2Exception: raise FlaskException("Invalid code of the main script.", error_code=401) # check that we are not going to overwrite something if not overwrite_scene: try: ps_scene = ps.get_scene(scene.id) except ps.ProjectServiceException: pass else: # do not take created / modified into account ps_scene.created = scene.created = None ps_scene.modified = scene.modified = None if ps_scene != scene: raise FlaskException( "Scene difference detected. Overwrite needed.", error_code=402) if not overwrite_project: try: ps_project = ps.get_project(project.id) except ps.ProjectServiceException: pass else: # do not take created / modified into account ps_project.created = project.created = None ps_project.modified = project.modified = None if ps_project != project: raise FlaskException( "Project difference detected. Overwrite needed.", error_code=402) if not overwrite_object_types: for obj_type in objects.values(): try: ot = ps.get_object_type(obj_type.id) # ignore changes in description (no one cares) if ot.source != obj_type.source or ot.model != obj_type.model: raise FlaskException( f"Difference detected for {obj_type.id} object type. Overwrite needed.", error_code=402) except ps.ProjectServiceException: pass if not overwrite_project_sources and not project.has_logic: try: if ps.get_project_sources(project.id).script != script: raise FlaskException( "Script difference detected. Overwrite needed.", error_code=402) except ps.ProjectServiceException: pass if not overwrite_collision_models: for model in models.values(): try: if model != ps.get_model(model.id, model.type()): raise FlaskException( "Collision model difference detected. Overwrite needed.", error_code=402) except ps.ProjectServiceException: pass for model in models.values(): ps.put_model(model) for obj_type in objects.values(): ps.update_object_type(obj_type) ps.update_scene(scene) ps.update_project(project) if not project.has_logic: ps.update_project_sources(ProjectSources(project.id, script)) logger.info( f"Imported project {project.name} (scene {scene.name}), with {len(objects)} " f"object type(s) and {len(models)} model(s).") return ImportResult(scene.id, project.id).to_json(), 200
def _publish(project_id: str, package_name: str) -> RespT: mem_zip = BytesIO() logger.debug( f"Generating package {package_name} for project_id: {project_id}.") types_dict: TypesDict = {} # restore original environment sys.path = list(original_sys_path) sys.modules = dict(original_sys_modules) with tempfile.TemporaryDirectory() as tmp_dir: prepare_object_types_dir(tmp_dir, OBJECT_TYPE_MODULE) with zipfile.ZipFile(mem_zip, mode="w", compression=zipfile.ZIP_DEFLATED) as zf: try: logger.debug("Getting scene and project.") project = ps.get_project(project_id) cached_project = CachedProject(project) scene = ps.get_scene(project.scene_id) cached_scene = CachedScene(scene) if not package_name: package_name = project.name data_path = "data" ot_path = "object_types" zf.writestr(os.path.join(ot_path, "__init__.py"), "") zf.writestr(os.path.join(data_path, "project.json"), project.to_json()) zf.writestr(os.path.join(data_path, "scene.json"), scene.to_json()) obj_types = set(cached_scene.object_types) obj_types_with_models: set[str] = set() if __debug__: # this should uncover potential problems with order in which ObjectTypes are processed import random random.shuffle(scene.objects) for scene_obj in scene.objects: if scene_obj.type in types_dict: continue logger.debug( f"Getting scene object type {scene_obj.type}.") obj_type = ps.get_object_type(scene_obj.type) if obj_type.model and obj_type.id not in obj_types_with_models: obj_types_with_models.add(obj_type.id) model = ps.get_model(obj_type.model.id, obj_type.model.type) obj_model = ObjectModel( obj_type.model.type, **{model.type().value.lower(): model} # type: ignore ) zf.writestr( os.path.join( data_path, "models", humps.depascalize(obj_type.id) + ".json"), obj_model.to_json(), ) zf.writestr( os.path.join(ot_path, humps.depascalize(obj_type.id)) + ".py", obj_type.source) # handle inheritance get_base_from_project_service(types_dict, tmp_dir, obj_types, obj_type, zf, ot_path, parse(obj_type.source)) types_dict[scene_obj.type] = save_and_import_type_def( obj_type.source, scene_obj.type, Generic, tmp_dir, OBJECT_TYPE_MODULE) except Arcor2Exception as e: logger.exception( f"Failed to prepare package content. {str(e)}") raise FlaskException(str(e), error_code=404) script_path = "script.py" try: if project.has_logic: logger.debug("Generating script from project logic.") zf.writestr( script_path, program_src(types_dict, cached_project, cached_scene, True)) else: try: logger.debug("Getting project sources.") script = ps.get_project_sources(project.id).script # check if it is a valid Python code try: parse(script) except SourceException: logger.exception( "Failed to parse code of the uploaded script.") raise FlaskException("Invalid code.", error_code=501) zf.writestr(script_path, script) except ps.ProjectServiceException: logger.info( "Script not found on project service, creating one from scratch." ) # write script without the main loop zf.writestr( script_path, program_src(types_dict, cached_project, cached_scene, False)) logger.debug("Generating supplementary files.") logger.debug("action_points.py") zf.writestr("action_points.py", global_action_points_class(cached_project)) logger.debug("package.json") zf.writestr( "package.json", PackageMeta(package_name, datetime.now(tz=timezone.utc)).to_json()) except Arcor2Exception as e: logger.exception("Failed to generate script.") raise FlaskException(str(e), error_code=501) logger.info( f"Done with {package_name} (scene {scene.name}, project {project.name})." ) mem_zip.seek(0) return send_file(mem_zip, as_attachment=True, max_age=0, download_name=f"{package_name}_package.zip")
def put_line_safe() -> RespT: """Checks whether the line between two points intersects any object. --- put: tags: - Utils description: Returns true if line is safe (without collisions). requestBody: content: application/json: schema: $ref: LineCheck responses: 200: description: Ok content: application/json: schema: $ref: LineCheckResult """ if not isinstance(request.json, dict): raise FlaskException( "Body should be a JSON dict containing LineCheck.", error_code=400) if not collision_objects: logger.debug("Safe. No collision object.") return jsonify(scene.LineCheckResult(True).to_dict()) pts = scene.LineCheck.from_dict(request.json) logger.debug(f"pt1: {pts.pt1}") logger.debug(f"pt2: {pts.pt2}") o3d_scene = o3d.t.geometry.RaycastingScene() o3d_id: dict[str, str] = {} # o3d id to our id """ o3d uses different coordinates, but in this case it should not matter o3d x right, y down, z forward arcor2/ros x forward, y left, z up unity x Right, y Up, z Forward """ for obj_id, (model, pose) in collision_objects.items(): if isinstance(model, object_type.Box): # The left bottom corner on the front will be placed at (0, 0, 0) sx = model.size_x + inflation sy = model.size_y + inflation sz = model.size_z + inflation tm = o3d.geometry.TriangleMesh.create_box(sx, sy, sz) tm = tm.translate([ pose.position.x - sx / 2, pose.position.y - sy / 2, pose.position.z - sz / 2 ]) elif isinstance(model, object_type.Cylinder): tm = o3d.geometry.TriangleMesh.create_cylinder( model.radius + inflation, model.height + inflation) elif isinstance(model, object_type.Sphere): tm = o3d.geometry.TriangleMesh.create_sphere(model.radius + inflation) else: # TODO mesh logger.warning( f"Unsupported type of collision model: {model.type()}.") continue tm.rotate( quaternion.as_rotation_matrix(pose.orientation.as_quaternion())) tm = o3d.t.geometry.TriangleMesh.from_legacy(tm) o3d_id[o3d_scene.add_triangles(tm)] = obj_id dir_vec = np.array( [pts.pt2.x - pts.pt1.x, pts.pt2.y - pts.pt1.y, pts.pt2.z - pts.pt1.z]) dir_vec = dir_vec / np.linalg.norm(dir_vec) logger.debug(f"Direction: {dir_vec}") rays = o3d.core.Tensor( [[pts.pt1.x, pts.pt1.y, pts.pt1.z, dir_vec[0], dir_vec[1], dir_vec[2]] ], dtype=o3d.core.Dtype.Float32, ) ans = o3d_scene.cast_rays(rays) dist_btw_points = math.dist(pts.pt1, pts.pt2) dist_to_hit = float(ans["t_hit"].numpy()[0]) if dist_to_hit > dist_btw_points: logger.debug( f"Safe. Distance to hit {dist_to_hit:.3f} larger than distance between points {dist_btw_points:.3f}." ) return jsonify(scene.LineCheckResult(True).to_dict()) collision_with = o3d_id[ans["geometry_ids"].numpy()[0]] logger.debug( f"Unsafe. Distance to hit {dist_to_hit:.3f}, there is collision with {collision_with}." ) return jsonify(scene.LineCheckResult(False, collision_with))
def put_eef_pose() -> RespT: """Set the EEF pose. --- put: description: Set the EEF pose. tags: - Robot parameters: - in: query name: moveType schema: type: string enum: - JUMP - LINEAR - JOINTS required: true description: Move type - name: velocity in: query schema: type: number format: float minimum: 0 maximum: 100 - name: acceleration in: query schema: type: number format: float minimum: 0 maximum: 100 - in: query name: safe schema: type: boolean default: false requestBody: content: application/json: schema: $ref: Pose responses: 200: description: Ok 403: description: Not started 404: description: Can't find safe path. """ assert _dobot is not None if not isinstance(request.json, dict): raise FlaskException("Body should be a JSON dict containing Pose.", error_code=400) pose = Pose.from_dict(request.json) move_type = MoveType(request.args.get("moveType", "jump")) velocity = float(request.args.get("velocity", default=50.0)) acceleration = float(request.args.get("acceleration", default=50.0)) safe = request.args.get("safe") == "true" if safe: cp = _dobot.get_end_effector_pose() ip1 = copy.deepcopy(cp) ip2 = copy.deepcopy(pose) for _attempt in range(20): res = scene_service.line_check( LineCheck(ip1.position, ip2.position)) if res.safe: break if move_type == MoveType.LINEAR: raise FlaskException("There might be a collision.", error_code=400) ip1.position.z += 0.01 ip2.position.z += 0.01 else: return "Can't find safe path.", 404 logger.debug(f"Collision avoidance attempts: {_attempt}") if _attempt > 0: _dobot.move(ip1, move_type, velocity, acceleration) _dobot.move(ip2, move_type, velocity, acceleration) _dobot.move(pose, move_type, velocity, acceleration) return Response(status=204)