Esempio n. 1
0
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))
Esempio n. 2
0
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")
Esempio n. 3
0
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))
Esempio n. 4
0
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
Esempio n. 5
0
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
Esempio n. 6
0
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)
Esempio n. 7
0
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)
Esempio n. 8
0
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
Esempio n. 9
0
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())
Esempio n. 10
0
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())
Esempio n. 11
0
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
Esempio n. 12
0
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)
Esempio n. 13
0
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
Esempio n. 14
0
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)
Esempio n. 15
0
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)
Esempio n. 16
0
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)
Esempio n. 17
0
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)
Esempio n. 18
0
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
Esempio n. 19
0
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
Esempio n. 20
0
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")
Esempio n. 21
0
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))
Esempio n. 22
0
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)