acmerobotics / ftc-dashboard

React-based web dashboard designed for FTC
https://acmerobotics.github.io/ftc-dashboard
Other
168 stars 125 forks source link

Add a visual roadrunner path editor #134

Open nash-pillai opened 1 year ago

nash-pillai commented 1 year ago

Some places it could be improved:

nash-pillai commented 1 year ago

I used the following opmode to run the path that is uploaded (it is in kotlin):

package org.firstinspires.ftc.teamcode

import com.acmerobotics.dashboard.message.redux.UploadPath
import com.acmerobotics.dashboard.path.DashboardPath
import com.acmerobotics.dashboard.path.HeadingType
import com.acmerobotics.dashboard.path.PathSegment
import com.acmerobotics.dashboard.path.SegmentType
import com.acmerobotics.roadrunner.geometry.Pose2d
import com.acmerobotics.roadrunner.geometry.Vector2d
import com.qualcomm.robotcore.eventloop.opmode.Autonomous
import com.qualcomm.robotcore.eventloop.opmode.LinearOpMode
import org.firstinspires.ftc.teamcode.drive.SampleMecanumDrive
import org.firstinspires.ftc.teamcode.trajectorysequence.TrajectorySequence

@DashboardPath
@Autonomous
class CustomPath : LinearOpMode() {
    companion object {
        @JvmField var dashboardPath = UploadPath(PathSegment(), arrayOf())
    }
    fun makeTrajectory(drive: SampleMecanumDrive): TrajectorySequence {
        val builder = drive.trajectorySequenceBuilder(
            Pose2d(dashboardPath.start.x, dashboardPath.start.y, dashboardPath.start.heading),
            dashboardPath.start.tangent,
        )
        drive.poseEstimate = Pose2d(dashboardPath.start.x, dashboardPath.start.y, dashboardPath.start.heading)

        for (s in dashboardPath.segments) when (s.type) {
            SegmentType.WAIT -> builder.waitSeconds(s.time)
            SegmentType.LINE -> when (s.headingType) {
                HeadingType.TANGENT -> builder.lineTo(Vector2d(s.x, s.y))
                HeadingType.CONSTANT -> builder.lineToConstantHeading(Vector2d(s.x, s.y))
                HeadingType.LINEAR -> builder.lineToLinearHeading(Pose2d(s.x, s.y, s.heading))
                HeadingType.SPLINE -> builder.lineToSplineHeading(Pose2d(s.x, s.y, s.heading))
                else -> error("Unknown HeadingType ${s.headingType}")
            }
            SegmentType.SPLINE -> when (s.headingType) {
                HeadingType.TANGENT -> builder.splineTo(Vector2d(s.x, s.y), s.tangent)
                HeadingType.CONSTANT -> builder.splineToConstantHeading(Vector2d(s.x, s.y), s.tangent)
                HeadingType.LINEAR -> builder.splineToLinearHeading(Pose2d(s.x, s.y, s.heading), s.tangent)
                HeadingType.SPLINE -> builder.splineToSplineHeading(Pose2d(s.x, s.y, s.heading), s.tangent)
                else -> error("Unknown HeadingType ${s.headingType}")
            }
            else -> error("Unknown SegmentType ${s.type}")
        }

        return builder.build()
    }
    override fun runOpMode() {
        val drive = SampleMecanumDrive(hardwareMap)
        val path = makeTrajectory(drive)

        waitForStart()
        if (isStopRequested) return
        drive.followTrajectorySequence(path)
        sleep(5000)
    }
}
NoahBres commented 1 year ago

Do you have a screen recording of this? I've always had the idea of a visual path editor brewing in the back of my mind 👀

My own PR idea was building out primitives for a generalizable events (mouse click, drag, etc) interaction + drawing api though. It would build on the current field drawing widget which gives dash canvas draw commands. But providing a more generalizable API would imo prove to be more flexible in the future.

E.g. Instead of a Field widget, provide a general Canvas widget (or Field widget can just be a special extension of this) that supports the current canvas operations + include an image drawing command. This way, the Dash client doesn't need to be updated for every season. Rather, the RR quickstart repo provided can just push a new image or users can just draw their own image. I do have a PR for custom image uploads though. But I think this would provide a better foundation for the future as it can also provide primitives for events (the aforementioned mouse click, drag, etc.) so that you can support a RR visual path editor but have it handled by the quickstart repo instead of the dash client.

Just an idea though. I haven't made an RFC PR since it was just ideas floating around.

nash-pillai commented 1 year ago

I used Konva to make the field interactive. I only drew the control points of the path and I overlayed the preexisting path drawings you have. This is a picture of an example path I made when testing it: image Here is a video from a while ago demonstrating some of the controls:

https://user-images.githubusercontent.com/47931961/221471969-16c4fa20-6916-4a20-8000-1e2091beb139.mp4

I'll make a more up-to-date video tomorrow

rbrott commented 1 year ago

Very cool! I got it working locally, and I'm impressed.

I would normally worry about contaminating dash with "knowledge" of RR, though you've helpfully kept a RR dependency out of the patch. I don't want hermetic separation between RR and dash to obstruct a useful feature like this, and the API is general enough to work with RR 1.0.0 I think.

My main desire is to leverage the config system to store all of the path state. I imagine the Path widget could scan the config tree for classes with a particular field and shunt updates through the existing config messages. This probably involves adding lists/arrays to the config model, which is a useful and orthogonal feature people have been asking about for some time (#7!).

A general third-party component system (a la Noah's RFC) would be a great fit for this PR, but I'm not sure that yak is worth shaving yet with the current scope of the path editor.

NoahBres commented 1 year ago

but I'm not sure that yak is worth shaving yet with the current scope of the path editor

Very much agree! Always for delivering value first. Just wanted to drop the idea around for others to see :) CleanShot 2023-03-16 at 14 31 33@2x This kind of customizable input would also be very neat to see!

Awesome work :))