├── choreolib ├── py │ ├── choreo │ │ ├── py.typed │ │ └── util │ │ │ ├── traj_schema_version.py │ │ │ └── field_dimensions.py │ ├── README.md │ ├── pyproject.toml │ └── _choreo_version.py ├── .gitattributes ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── settings.gradle ├── .wpilib │ └── wpilib_preferences.json ├── README.md ├── src │ ├── test │ │ ├── native │ │ │ └── cpp │ │ │ │ └── main.cpp │ │ └── java │ │ │ ├── edu │ │ │ └── wpi │ │ │ │ └── first │ │ │ │ └── wpilibj2 │ │ │ │ └── command │ │ │ │ └── SchedulerMaker.java │ │ │ └── choreo │ │ │ ├── auto │ │ │ ├── AutoTestHelper.java │ │ │ └── RoutineKillNoAllianceTest.java │ │ │ └── trajectory │ │ │ └── TrajectoryTestHelper.java │ └── main │ │ ├── java │ │ └── choreo │ │ │ ├── util │ │ │ ├── FieldDimensions.java │ │ │ ├── TrajSchemaVersion.java │ │ │ ├── ChoreoArrayUtil.java │ │ │ └── ChoreoAlert.java │ │ │ └── trajectory │ │ │ └── TrajectorySample.java │ │ └── native │ │ ├── include │ │ └── choreo │ │ │ ├── util │ │ │ ├── TrajSchemaVersion.h │ │ │ ├── FieldDimensions.h │ │ │ └── Map.h │ │ │ └── trajectory │ │ │ ├── struct │ │ │ ├── SwerveSampleStruct.h │ │ │ └── DifferentialSampleStruct.h │ │ │ ├── EventMarker.h │ │ │ └── TrajectorySample.h │ │ └── cpp │ │ └── choreo │ │ └── trajectory │ │ ├── EventMarker.cpp │ │ └── DifferentialSample.cpp ├── test_deploy │ └── test.chor ├── vendordeps │ └── WPILibNewCommands.json ├── ChoreoLib2025.json ├── ChoreoLib2025Beta.json ├── vendor_jsons │ ├── ChoreoLib2026Beta.json │ └── ChoreoLib2027Alpha.json └── docs │ └── doxygen-awesome-css │ └── doxygen-awesome-sidebar-only-darkmode-toggle.css ├── docs ├── CNAME ├── requirements.txt ├── media │ ├── focus.png │ ├── samples.png │ ├── sidebar.png │ ├── trajectory.png │ ├── waypoints.png │ ├── event-markers.png │ ├── field_options.png │ ├── icon1024x1024.png │ ├── pose_waypoint.png │ ├── project_info.png │ ├── empty_waypoint.png │ ├── generating_path.gif │ ├── keep_out_circle.png │ ├── readmeScreenshot.png │ ├── waypoint-types.png │ ├── waypoints+navbar.png │ ├── constraint_navbar.png │ ├── event-marker-error.png │ ├── pp-path-splitting.png │ ├── project_not_saved.png │ ├── samples-layer-wpts.png │ ├── view_options_panel.png │ ├── NACA-TN-1629-figure3.png │ ├── choreo-path-splitting.png │ ├── constraint-scope-line.png │ ├── export-config-panel.png │ ├── keep-out-constraints.png │ ├── keep_out_circle_icon.png │ ├── translation_waypoint.png │ ├── waypoint_config_panel.png │ ├── constraint_config_panel.png │ ├── document-settings-menu.png │ ├── samples-mini-waypoints.png │ ├── bifilar-torsional-pendulum.png │ ├── choreolib-branching-auto.png │ ├── robot-config-theoretical.png │ ├── REV-2024-starterBot-massProps.png │ └── path_gradient │ │ ├── path_gradient_none.png │ │ ├── path_gradient_progress.png │ │ ├── path_gradient_velocity.png │ │ ├── path_gradient_acceleration.png │ │ ├── path_gradient_interval_dt.png │ │ ├── path_gradient_angular_velocity.png │ │ └── path_gradient_split_trajectories.png ├── js │ └── mathjax.js ├── document-settings.md ├── contributing │ ├── building-docs.md │ ├── building-choreolib.md │ ├── sample-flipping.md │ ├── release-process.md │ └── contributing-guide.md └── installation.md ├── .wpiformat ├── src-core ├── src │ ├── integration_tests │ │ └── mod.rs │ ├── spec │ │ ├── traj_schema_version.rs │ │ ├── project_schema_version.rs │ │ └── mod.rs │ └── generation │ │ ├── mod.rs │ │ └── transformers │ │ └── callback.rs └── Cargo.toml ├── .wpiformat-license ├── src ├── vite-env.d.ts ├── components │ ├── field │ │ ├── OverlayWaypoint.module.css │ │ ├── svg │ │ │ ├── fields │ │ │ │ └── FieldDimensions.tsx │ │ │ ├── FieldMatrixContext.ts │ │ │ ├── FieldAxisLines.tsx │ │ │ ├── FieldPathLines.tsx │ │ │ ├── FieldGrid.tsx │ │ │ ├── FieldSamples.tsx │ │ │ ├── FieldEventMarkerAddLayer.tsx │ │ │ ├── FieldGeneratedWaypoints.tsx │ │ │ ├── FieldEventMarkers.tsx │ │ │ └── constraintDisplay │ │ │ │ └── PointAtOverlay.tsx │ │ └── Field.module.css │ ├── input │ │ ├── ExpressionInputList.tsx │ │ ├── InputList.tsx │ │ ├── InputList.module.css │ │ └── BooleanInput.tsx │ ├── config │ │ ├── robotconfig │ │ │ ├── DifferentialConfigPanel.tsx │ │ │ ├── MotorCurves.tsx │ │ │ ├── SwerveConfigPanel.tsx │ │ │ ├── ModuleConfigPanel.tsx │ │ │ └── DimensionsConfigPanel.tsx │ │ ├── WaypointConfigPanel.module.css │ │ ├── variables │ │ │ ├── ExpressionsConfigPanel.tsx │ │ │ └── VariableRenamingInput.tsx │ │ └── ScopeSlider.tsx │ ├── navbar │ │ ├── Navbar.module.css │ │ └── Navbar.tsx │ └── sidebar │ │ └── ProjectSaveStatusIndicator.tsx ├── document │ ├── schema │ │ ├── TrajSchemaVersion.ts │ │ └── ProjectSchemaVersion.ts │ ├── CodeGenStore.ts │ └── path │ │ ├── PathUIStore.ts │ │ └── utils.ts ├── util │ ├── version.ts │ ├── LocalStorageKeys.ts │ ├── mobxutils.ts │ ├── BuildInfo.ts │ └── UnitConversions.ts ├── assets │ ├── SaveInProgress.tsx │ ├── GenerateInProgress.tsx │ ├── KeepInLane.tsx │ ├── InitialGuessPoint.tsx │ ├── Waypoint.tsx │ ├── IconInProgress.tsx │ ├── EventMarker.tsx │ ├── MoI.tsx │ ├── Angle.tsx │ ├── Mass.tsx │ └── Torque.tsx ├── styles.css ├── main.tsx ├── Body.tsx └── App.css ├── favicon.ico ├── trajoptlib ├── .wpiformat-license ├── TrajoptLibConfig.cmake.in ├── Cargo.toml ├── src │ ├── util │ │ └── cancellation.cpp │ └── error.rs ├── include │ └── trajopt │ │ ├── util │ │ └── cancellation.hpp │ │ ├── path │ │ └── path.hpp │ │ ├── constraint │ │ ├── detail │ │ │ └── line_point_squared_distance.hpp │ │ ├── translation_equality_constraint.hpp │ │ ├── pose_equality_constraint.hpp │ │ ├── linear_velocity_direction_constraint.hpp │ │ ├── angular_velocity_max_magnitude_constraint.hpp │ │ ├── linear_velocity_max_magnitude_constraint.hpp │ │ └── linear_acceleration_max_magnitude_constraint.hpp │ │ └── spline │ │ └── cubic_hermite_spline1d.hpp ├── cmake │ ├── SubdirList.cmake │ └── CompilerFlags.cmake ├── test │ └── src │ │ ├── util │ │ ├── trajopt_util_test.cpp │ │ └── generate_linear_initial_guess_test.cpp │ │ ├── differential_path_builder_test.cpp │ │ ├── geometry │ │ ├── pose2d_test.cpp │ │ └── rotation2d_test.cpp │ │ └── swerve_path_builder_test.cpp ├── docs │ ├── doxygen-awesome-css │ │ └── doxygen-awesome-sidebar-only-darkmode-toggle.css │ └── footer.html ├── examples │ ├── differential.rs │ └── swerve.rs ├── CMakePresets.json ├── LICENSE.txt └── build.rs ├── .prettierrc.json ├── src-tauri ├── icons │ ├── 32x32.png │ ├── icon.icns │ ├── icon.ico │ ├── 128x128.png │ └── 128x128@2x.png ├── build.rs ├── capabilities │ └── main.json ├── Cargo.toml ├── tauri.conf.json └── src │ └── main.rs ├── .vscode ├── extensions.json └── settings.json ├── Cargo.toml ├── tsconfig.node.json ├── .prettierignore ├── force-update-deps.sh ├── index.html ├── .github ├── workflows │ ├── pr-labeler.yml │ ├── cache-cleanup.yml │ ├── fix_compile_commands.py │ ├── docs.yml │ ├── release.yml │ └── sanitizers.yml └── labeler.yml ├── shell.nix ├── src-cli └── Cargo.toml ├── make-docs.sh ├── tsconfig.json ├── .gitattributes ├── .gitignore ├── copy-sidecar.cjs ├── vite.config.ts ├── LICENSE.txt ├── eslint.config.mjs ├── test-jsons └── project │ ├── 0 │ ├── swerve.chor │ └── differential.chor │ ├── 1 │ ├── swerve.chor │ └── differential.chor │ ├── 2 │ ├── swerve.chor │ └── differential.chor │ └── beta-6 │ ├── swerve.chor │ └── differential.chor ├── README.md └── update_project_schema.py /choreolib/py/choreo/py.typed: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/CNAME: -------------------------------------------------------------------------------- 1 | choreo.autos 2 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | mkdocs 2 | mkdocs-material 3 | -------------------------------------------------------------------------------- /.wpiformat: -------------------------------------------------------------------------------- 1 | cppHeaderFileInclude { 2 | \.h$ 3 | } 4 | -------------------------------------------------------------------------------- /src-core/src/integration_tests/mod.rs: -------------------------------------------------------------------------------- 1 | mod generate; 2 | -------------------------------------------------------------------------------- /.wpiformat-license: -------------------------------------------------------------------------------- 1 | // Copyright (c) Choreo contributors 2 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SleipnirGroup/Choreo/HEAD/favicon.ico -------------------------------------------------------------------------------- /trajoptlib/.wpiformat-license: -------------------------------------------------------------------------------- 1 | // Copyright (c) TrajoptLib contributors 2 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "none", 3 | "endOfLine": "lf" 4 | } 5 | -------------------------------------------------------------------------------- /choreolib/.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /docs/media/focus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SleipnirGroup/Choreo/HEAD/docs/media/focus.png -------------------------------------------------------------------------------- /docs/media/samples.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SleipnirGroup/Choreo/HEAD/docs/media/samples.png -------------------------------------------------------------------------------- /docs/media/sidebar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SleipnirGroup/Choreo/HEAD/docs/media/sidebar.png -------------------------------------------------------------------------------- /docs/media/trajectory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SleipnirGroup/Choreo/HEAD/docs/media/trajectory.png -------------------------------------------------------------------------------- /docs/media/waypoints.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SleipnirGroup/Choreo/HEAD/docs/media/waypoints.png -------------------------------------------------------------------------------- /src-tauri/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SleipnirGroup/Choreo/HEAD/src-tauri/icons/32x32.png -------------------------------------------------------------------------------- /src-tauri/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SleipnirGroup/Choreo/HEAD/src-tauri/icons/icon.icns -------------------------------------------------------------------------------- /src-tauri/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SleipnirGroup/Choreo/HEAD/src-tauri/icons/icon.ico -------------------------------------------------------------------------------- /docs/media/event-markers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SleipnirGroup/Choreo/HEAD/docs/media/event-markers.png -------------------------------------------------------------------------------- /docs/media/field_options.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SleipnirGroup/Choreo/HEAD/docs/media/field_options.png -------------------------------------------------------------------------------- /docs/media/icon1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SleipnirGroup/Choreo/HEAD/docs/media/icon1024x1024.png -------------------------------------------------------------------------------- /docs/media/pose_waypoint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SleipnirGroup/Choreo/HEAD/docs/media/pose_waypoint.png -------------------------------------------------------------------------------- /docs/media/project_info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SleipnirGroup/Choreo/HEAD/docs/media/project_info.png -------------------------------------------------------------------------------- /src-tauri/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SleipnirGroup/Choreo/HEAD/src-tauri/icons/128x128.png -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["tauri-apps.tauri-vscode", "rust-lang.rust-analyzer"] 3 | } 4 | -------------------------------------------------------------------------------- /docs/media/empty_waypoint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SleipnirGroup/Choreo/HEAD/docs/media/empty_waypoint.png -------------------------------------------------------------------------------- /docs/media/generating_path.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SleipnirGroup/Choreo/HEAD/docs/media/generating_path.gif -------------------------------------------------------------------------------- /docs/media/keep_out_circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SleipnirGroup/Choreo/HEAD/docs/media/keep_out_circle.png -------------------------------------------------------------------------------- /docs/media/readmeScreenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SleipnirGroup/Choreo/HEAD/docs/media/readmeScreenshot.png -------------------------------------------------------------------------------- /docs/media/waypoint-types.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SleipnirGroup/Choreo/HEAD/docs/media/waypoint-types.png -------------------------------------------------------------------------------- /docs/media/waypoints+navbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SleipnirGroup/Choreo/HEAD/docs/media/waypoints+navbar.png -------------------------------------------------------------------------------- /src-tauri/icons/128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SleipnirGroup/Choreo/HEAD/src-tauri/icons/128x128@2x.png -------------------------------------------------------------------------------- /trajoptlib/TrajoptLibConfig.cmake.in: -------------------------------------------------------------------------------- 1 | @PACKAGE_INIT@ 2 | 3 | include("${CMAKE_CURRENT_LIST_DIR}/TrajoptLib.cmake") 4 | -------------------------------------------------------------------------------- /choreolib/py/choreo/util/traj_schema_version.py: -------------------------------------------------------------------------------- 1 | # Auto-generated by update_traj_schema.py 2 | TRAJ_SCHEMA_VERSION = 2 3 | -------------------------------------------------------------------------------- /docs/media/constraint_navbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SleipnirGroup/Choreo/HEAD/docs/media/constraint_navbar.png -------------------------------------------------------------------------------- /docs/media/event-marker-error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SleipnirGroup/Choreo/HEAD/docs/media/event-marker-error.png -------------------------------------------------------------------------------- /docs/media/pp-path-splitting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SleipnirGroup/Choreo/HEAD/docs/media/pp-path-splitting.png -------------------------------------------------------------------------------- /docs/media/project_not_saved.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SleipnirGroup/Choreo/HEAD/docs/media/project_not_saved.png -------------------------------------------------------------------------------- /docs/media/samples-layer-wpts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SleipnirGroup/Choreo/HEAD/docs/media/samples-layer-wpts.png -------------------------------------------------------------------------------- /docs/media/view_options_panel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SleipnirGroup/Choreo/HEAD/docs/media/view_options_panel.png -------------------------------------------------------------------------------- /docs/media/NACA-TN-1629-figure3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SleipnirGroup/Choreo/HEAD/docs/media/NACA-TN-1629-figure3.png -------------------------------------------------------------------------------- /docs/media/choreo-path-splitting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SleipnirGroup/Choreo/HEAD/docs/media/choreo-path-splitting.png -------------------------------------------------------------------------------- /docs/media/constraint-scope-line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SleipnirGroup/Choreo/HEAD/docs/media/constraint-scope-line.png -------------------------------------------------------------------------------- /docs/media/export-config-panel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SleipnirGroup/Choreo/HEAD/docs/media/export-config-panel.png -------------------------------------------------------------------------------- /docs/media/keep-out-constraints.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SleipnirGroup/Choreo/HEAD/docs/media/keep-out-constraints.png -------------------------------------------------------------------------------- /docs/media/keep_out_circle_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SleipnirGroup/Choreo/HEAD/docs/media/keep_out_circle_icon.png -------------------------------------------------------------------------------- /docs/media/translation_waypoint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SleipnirGroup/Choreo/HEAD/docs/media/translation_waypoint.png -------------------------------------------------------------------------------- /docs/media/waypoint_config_panel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SleipnirGroup/Choreo/HEAD/docs/media/waypoint_config_panel.png -------------------------------------------------------------------------------- /src/components/field/OverlayWaypoint.module.css: -------------------------------------------------------------------------------- 1 | .Container { 2 | position: absolute; 3 | bottom: 0; 4 | left: 0; 5 | } 6 | -------------------------------------------------------------------------------- /docs/media/constraint_config_panel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SleipnirGroup/Choreo/HEAD/docs/media/constraint_config_panel.png -------------------------------------------------------------------------------- /docs/media/document-settings-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SleipnirGroup/Choreo/HEAD/docs/media/document-settings-menu.png -------------------------------------------------------------------------------- /docs/media/samples-mini-waypoints.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SleipnirGroup/Choreo/HEAD/docs/media/samples-mini-waypoints.png -------------------------------------------------------------------------------- /src-core/src/spec/traj_schema_version.rs: -------------------------------------------------------------------------------- 1 | // Auto-generated by update_traj_schema.py 2 | pub const TRAJ_SCHEMA_VERSION: u32 = 2; 3 | -------------------------------------------------------------------------------- /src/document/schema/TrajSchemaVersion.ts: -------------------------------------------------------------------------------- 1 | // Auto-generated by update_traj_schema.py 2 | export const TRAJ_SCHEMA_VERSION = 2; 3 | -------------------------------------------------------------------------------- /docs/media/bifilar-torsional-pendulum.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SleipnirGroup/Choreo/HEAD/docs/media/bifilar-torsional-pendulum.png -------------------------------------------------------------------------------- /docs/media/choreolib-branching-auto.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SleipnirGroup/Choreo/HEAD/docs/media/choreolib-branching-auto.png -------------------------------------------------------------------------------- /docs/media/robot-config-theoretical.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SleipnirGroup/Choreo/HEAD/docs/media/robot-config-theoretical.png -------------------------------------------------------------------------------- /src/document/schema/ProjectSchemaVersion.ts: -------------------------------------------------------------------------------- 1 | // Auto-generated by update_project_schema.py 2 | export const PROJECT_SCHEMA_VERSION = 2; 3 | -------------------------------------------------------------------------------- /choreolib/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SleipnirGroup/Choreo/HEAD/choreolib/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src-core/src/spec/project_schema_version.rs: -------------------------------------------------------------------------------- 1 | // Auto-generated by update_project_schema.py 2 | pub const PROJECT_SCHEMA_VERSION: u32 = 2; 3 | -------------------------------------------------------------------------------- /choreolib/py/choreo/util/field_dimensions.py: -------------------------------------------------------------------------------- 1 | # Auto-generated by update_field_dimensions.py 2 | FIELD_LENGTH = 17.548 3 | FIELD_WIDTH = 8.052 4 | -------------------------------------------------------------------------------- /choreolib/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | mavenLocal() 4 | gradlePluginPortal() 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /docs/media/REV-2024-starterBot-massProps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SleipnirGroup/Choreo/HEAD/docs/media/REV-2024-starterBot-massProps.png -------------------------------------------------------------------------------- /docs/media/path_gradient/path_gradient_none.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SleipnirGroup/Choreo/HEAD/docs/media/path_gradient/path_gradient_none.png -------------------------------------------------------------------------------- /docs/media/path_gradient/path_gradient_progress.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SleipnirGroup/Choreo/HEAD/docs/media/path_gradient/path_gradient_progress.png -------------------------------------------------------------------------------- /docs/media/path_gradient/path_gradient_velocity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SleipnirGroup/Choreo/HEAD/docs/media/path_gradient/path_gradient_velocity.png -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | members = [ 4 | "src-cli", 5 | "src-core", 6 | "src-tauri", 7 | "trajoptlib", 8 | ] 9 | resolver = "2" 10 | -------------------------------------------------------------------------------- /docs/media/path_gradient/path_gradient_acceleration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SleipnirGroup/Choreo/HEAD/docs/media/path_gradient/path_gradient_acceleration.png -------------------------------------------------------------------------------- /docs/media/path_gradient/path_gradient_interval_dt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SleipnirGroup/Choreo/HEAD/docs/media/path_gradient/path_gradient_interval_dt.png -------------------------------------------------------------------------------- /src/util/version.ts: -------------------------------------------------------------------------------- 1 | import { getVersion } from "@tauri-apps/api/app"; 2 | 3 | export let version: string = "unknown"; 4 | 5 | getVersion().then((v) => (version = v)); 6 | -------------------------------------------------------------------------------- /docs/media/path_gradient/path_gradient_angular_velocity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SleipnirGroup/Choreo/HEAD/docs/media/path_gradient/path_gradient_angular_velocity.png -------------------------------------------------------------------------------- /docs/media/path_gradient/path_gradient_split_trajectories.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SleipnirGroup/Choreo/HEAD/docs/media/path_gradient/path_gradient_split_trajectories.png -------------------------------------------------------------------------------- /src/components/field/svg/fields/FieldDimensions.tsx: -------------------------------------------------------------------------------- 1 | // Auto-generated by update_field_dimensions.py 2 | export const FIELD_LENGTH = 17.548; 3 | export const FIELD_WIDTH = 8.052; 4 | -------------------------------------------------------------------------------- /choreolib/.wpilib/wpilib_preferences.json: -------------------------------------------------------------------------------- 1 | { 2 | "enableCppIntellisense": true, 3 | "currentLanguage": "cpp", 4 | "projectYear": "intellisense", 5 | "teamNumber": 9999 6 | } 7 | -------------------------------------------------------------------------------- /choreolib/README.md: -------------------------------------------------------------------------------- 1 | # ChoreoLib 2 | 3 | This is the robot-side library for loading and following Choreo paths. See the [Docs](https://choreo.autos/choreolib/installation/) for more details. 4 | -------------------------------------------------------------------------------- /choreolib/py/README.md: -------------------------------------------------------------------------------- 1 | # ChoreoLib 2 | 3 | This is the robot-side library for loading and following Choreo paths. See the [Docs](https://choreo.autos/choreolib/installation/) for more details. 4 | -------------------------------------------------------------------------------- /src/util/LocalStorageKeys.ts: -------------------------------------------------------------------------------- 1 | enum LocalStorageKeys { 2 | LAST_OPENED_FILE_LOCATION = "last_opened_project_payload", 3 | PATH_GRADIENT = "path_gradient" 4 | } 5 | 6 | export default LocalStorageKeys; 7 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | *.md 3 | dist/ 4 | public/ 5 | choreolib/* 6 | !choreolib/ChoreoLib2025Beta.json 7 | !choreolib/ChoreoLib2025.json 8 | src-tauri/target/ 9 | docs/ 10 | 11 | .clang-format 12 | .clang-tidy 13 | pnpm-lock.yaml 14 | -------------------------------------------------------------------------------- /force-update-deps.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # This script forcibly updates all Rust and TypeScript dependencies. 3 | # Make sure the result compiles and runs correctly before committing. 4 | cargo update --breaking -Z unstable-options 5 | cargo update 6 | pnpm update --latest 7 | -------------------------------------------------------------------------------- /choreolib/src/test/native/cpp/main.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Choreo contributors 2 | 3 | #include 4 | 5 | #include "choreo/Choreo.h" 6 | 7 | int main(int argc, char** argv) { 8 | ::testing::InitGoogleTest(&argc, argv); 9 | return RUN_ALL_TESTS(); 10 | } 11 | -------------------------------------------------------------------------------- /src/assets/SaveInProgress.tsx: -------------------------------------------------------------------------------- 1 | import { Save } from "@mui/icons-material"; 2 | import IconInProgress from "./IconInProgress"; 3 | 4 | function SaveInProgress(props: any) { 5 | return }>; 6 | } 7 | export default SaveInProgress; 8 | -------------------------------------------------------------------------------- /trajoptlib/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "trajoptlib" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | cxx = "1.0" 8 | serde = { version = "1.0", features = ["derive"] } 9 | thiserror = "2.0.17" 10 | 11 | [build-dependencies] 12 | cmake = "0.1" 13 | cxx-build = "1.0" 14 | -------------------------------------------------------------------------------- /choreolib/src/test/java/edu/wpi/first/wpilibj2/command/SchedulerMaker.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) Choreo contributors 2 | 3 | package edu.wpi.first.wpilibj2.command; 4 | 5 | public class SchedulerMaker { 6 | public static CommandScheduler make() { 7 | return new CommandScheduler(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/components/field/svg/FieldMatrixContext.ts: -------------------------------------------------------------------------------- 1 | import { createContext } from "react"; 2 | 3 | export const DOMMatrixIdentity = DOMMatrix.fromFloat32Array( 4 | Float32Array.from([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]) 5 | ); 6 | export const FieldMatrixContext = createContext(DOMMatrixIdentity); 7 | -------------------------------------------------------------------------------- /src/util/mobxutils.ts: -------------------------------------------------------------------------------- 1 | import { getIdentifier, IAnyStateTreeNode } from "mobx-state-tree"; 2 | 3 | export const safeGetIdentifier = (target: IAnyStateTreeNode | undefined) => { 4 | if (target === undefined) { 5 | return undefined; 6 | } else { 7 | return getIdentifier(target); 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /trajoptlib/src/util/cancellation.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) TrajoptLib contributors 2 | 3 | #include "trajopt/util/cancellation.hpp" 4 | 5 | namespace trajopt { 6 | 7 | std::atomic& get_cancellation_flag() { 8 | static std::atomic flag{0}; 9 | return flag; 10 | } 11 | 12 | } // namespace trajopt 13 | -------------------------------------------------------------------------------- /choreolib/src/main/java/choreo/util/FieldDimensions.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) Choreo contributors 2 | 3 | // Auto-generated by update_field_dimensions.py 4 | package choreo.util; 5 | 6 | class FieldDimensions { 7 | static final double FIELD_LENGTH = 17.548; 8 | static final double FIELD_WIDTH = 8.052; 9 | } 10 | -------------------------------------------------------------------------------- /choreolib/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /choreolib/src/main/native/include/choreo/util/TrajSchemaVersion.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Choreo contributors 2 | 3 | // Auto-generated by update_traj_schema.py 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | namespace choreo { 10 | 11 | inline constexpr uint32_t kTrajSchemaVersion = 2; 12 | 13 | } // namespace choreo 14 | -------------------------------------------------------------------------------- /trajoptlib/include/trajopt/util/cancellation.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) TrajoptLib contributors 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | #include "trajopt/util/symbol_exports.hpp" 8 | 9 | namespace trajopt { 10 | 11 | TRAJOPT_DLLEXPORT std::atomic& get_cancellation_flag(); 12 | 13 | } // namespace trajopt 14 | -------------------------------------------------------------------------------- /src/assets/GenerateInProgress.tsx: -------------------------------------------------------------------------------- 1 | import { ShapeLine } from "@mui/icons-material"; 2 | import IconInProgress from "./IconInProgress"; 3 | 4 | function GenerateInProgress(props: any) { 5 | return ( 6 | }> 7 | ); 8 | } 9 | export default GenerateInProgress; 10 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.check.overrideCommand": [ 3 | "cargo", "clippy", "--target-dir", "./target/clippy", "--message-format=json" 4 | ], 5 | "files.associations": { 6 | "*.py": "python", 7 | "*.traj": "json" 8 | }, 9 | "java.configuration.updateBuildConfiguration": "interactive" 10 | } 11 | -------------------------------------------------------------------------------- /src-tauri/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | built::write_built_file().expect("Failed to acquire build-time information"); 3 | unsafe { 4 | std::env::set_var("TAURI_CONFIG", "{ \"bundle\": { \"externalBin\": null } }"); 5 | } 6 | tauri_build::build(); 7 | unsafe { 8 | std::env::set_var("TAURI_CONFIG", "{}"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Choreo 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /trajoptlib/cmake/SubdirList.cmake: -------------------------------------------------------------------------------- 1 | macro(subdir_list result curdir) 2 | file(GLOB children RELATIVE ${curdir} ${curdir}/*) 3 | set(dirlist "") 4 | foreach(child ${children}) 5 | if(IS_DIRECTORY ${curdir}/${child}) 6 | list(APPEND dirlist ${child}) 7 | endif() 8 | endforeach() 9 | set(${result} ${dirlist}) 10 | endmacro() 11 | -------------------------------------------------------------------------------- /choreolib/src/main/native/include/choreo/util/FieldDimensions.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Choreo contributors 2 | 3 | // Auto-generated by update_field_dimensions.py 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | namespace choreo::util { 10 | static constexpr units::meter_t fieldLength = 17.548_m; 11 | static constexpr units::meter_t fieldWidth = 8.052_m; 12 | } // namespace choreo::util 13 | -------------------------------------------------------------------------------- /.github/workflows/pr-labeler.yml: -------------------------------------------------------------------------------- 1 | name: "Pull Request Labeler" 2 | on: 3 | - pull_request_target 4 | 5 | jobs: 6 | labeler: 7 | permissions: 8 | contents: read 9 | pull-requests: write 10 | runs-on: ubuntu-slim 11 | steps: 12 | - name: Checkout repo 13 | uses: actions/checkout@v5 14 | 15 | - uses: actions/labeler@v5 16 | with: 17 | sync-labels: true 18 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | { pkgs ? import { } }: 2 | 3 | pkgs.mkShell { 4 | name = "Choreo"; 5 | 6 | packages = with pkgs; [ 7 | cmake 8 | gcc 9 | git 10 | nodejs 11 | pnpm 12 | rustup 13 | 14 | cacert 15 | gnome.libsoup 16 | librsvg 17 | pkg-config 18 | webkitgtk 19 | ]; 20 | 21 | shellHook = '' 22 | export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PWD/src-tauri 23 | ''; 24 | } 25 | -------------------------------------------------------------------------------- /src/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: 4 | -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", 5 | "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: 12 | source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace; 13 | } 14 | -------------------------------------------------------------------------------- /docs/js/mathjax.js: -------------------------------------------------------------------------------- 1 | window.MathJax = { 2 | tex: { 3 | inlineMath: [["\\(", "\\)"]], 4 | displayMath: [["\\[", "\\]"]], 5 | processEscapes: true, 6 | processEnvironments: true 7 | }, 8 | options: { 9 | ignoreHtmlClass: ".*|", 10 | processHtmlClass: "arithmatex" 11 | } 12 | }; 13 | 14 | document$.subscribe(() => { 15 | MathJax.startup.output.clearCache() 16 | MathJax.typesetClear() 17 | MathJax.texReset() 18 | MathJax.typesetPromise() 19 | }) 20 | -------------------------------------------------------------------------------- /docs/document-settings.md: -------------------------------------------------------------------------------- 1 | # Document Settings 2 | 3 | The Document Settings panel allows you to configure several document-wide settings. Access the Document Settings panel 4 | by clicking "Document Settings" in Choreo's main menu. 5 | 6 | ![Document Settings option in the main menu](./media/document-settings-menu.png) 7 | 8 | See the page below for an explanation of how to use the Document Settings panel. 9 | 10 | [Robot Configuration](./document-settings/robot-configuration.md) 11 | -------------------------------------------------------------------------------- /choreolib/src/main/java/choreo/util/TrajSchemaVersion.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) Choreo contributors 2 | 3 | // Auto-generated by update_traj_schema.py 4 | 5 | package choreo.util; 6 | 7 | /** Internal autogenerated class for storing the current trajectory schema version. */ 8 | public class TrajSchemaVersion { 9 | /** The current trajectory schema version. */ 10 | public static final int TRAJ_SCHEMA_VERSION = 2; 11 | 12 | /** Utility class. */ 13 | private TrajSchemaVersion() {} 14 | } 15 | -------------------------------------------------------------------------------- /src-cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "choreo-cli" 3 | version = "2026.0.0-beta.1" 4 | edition = "2024" 5 | homepage = "https://github.com/SleipnirGroup/Choreo" 6 | repository = "https://github.com/SleipnirGroup/Choreo.git" 7 | authors = ["Sleipnir Group"] 8 | license = "BSD-3-Clause" 9 | 10 | [dependencies] 11 | choreo-core = { path = "../src-core" } 12 | clap = { version = "4.5.53", features = ["derive"] } 13 | tracing = "0.1.43" 14 | tracing-subscriber = { version = "0.3.22", features = ["ansi", "fmt", "std", "json"] } 15 | -------------------------------------------------------------------------------- /make-docs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | rm -rf site 6 | 7 | pushd choreolib 8 | mkdir -p build/docs 9 | ./gradlew javadoc 10 | doxygen docs/Doxyfile 11 | popd 12 | 13 | pushd trajoptlib 14 | mkdir -p build/docs 15 | doxygen docs/Doxyfile 16 | popd 17 | 18 | mkdir -p site/api/{choreolib,trajoptlib} 19 | cp -r choreolib/build/docs/javadoc site/api/choreolib/java 20 | cp -r choreolib/build/docs/cpp/html site/api/choreolib/cpp 21 | cp -r trajoptlib/build/docs/html site/api/trajoptlib/cpp 22 | cp -r choreolib/vendor_jsons site/lib 23 | mkdocs build --dirty 24 | -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | import ReactDOM from "react-dom/client"; 2 | import App from "./App"; 3 | import "./styles.css"; 4 | import { ToastContainer } from "react-toastify"; 5 | 6 | ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( 7 | <> 8 | 9 | 21 | 22 | ); 23 | -------------------------------------------------------------------------------- /trajoptlib/test/src/util/trajopt_util_test.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) TrajoptLib contributors 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | TEST_CASE("TrajoptUtil - get_index()", "[TrajoptUtil]") { 9 | CHECK(trajopt::get_index({2, 3}, 0, 0) == 0); 10 | CHECK(trajopt::get_index({2, 3}, 1, 1) == 3); 11 | CHECK(trajopt::get_index({2, 3}, 1, 2) == 4); 12 | CHECK(trajopt::get_index({2, 3}, 2, 0) == 5); 13 | } 14 | 15 | TEST_CASE("TrajoptUtil - linspace()", "[TrajoptUtil]") { 16 | CHECK(trajopt::linspace(0.0, 2.0, 2) == std::vector{1.0, 2.0}); 17 | } 18 | -------------------------------------------------------------------------------- /trajoptlib/docs/doxygen-awesome-css/doxygen-awesome-sidebar-only-darkmode-toggle.css: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: MIT */ 2 | /** 3 | 4 | Doxygen Awesome 5 | https://github.com/jothepro/doxygen-awesome-css 6 | 7 | Copyright (c) 2021 - 2025 jothepro 8 | 9 | */ 10 | 11 | @media screen and (min-width: 768px) { 12 | 13 | #MSearchBox { 14 | width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)) - var(--searchbar-height) - 1px); 15 | } 16 | 17 | #MSearchField { 18 | width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)) - 66px - var(--searchbar-height)); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "allowJs": false, 7 | "skipLibCheck": true, 8 | "esModuleInterop": false, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "ESNext", 13 | "moduleResolution": "Node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx" 18 | }, 19 | "include": ["src"], 20 | "references": [{ "path": "./tsconfig.node.json" }] 21 | } 22 | -------------------------------------------------------------------------------- /docs/contributing/building-docs.md: -------------------------------------------------------------------------------- 1 | # Building Documentation 2 | 3 | ## Dependencies 4 | 5 | - [Python](https://www.python.org/downloads/) 6 | - [Doxygen](https://www.doxygen.nl/manual/install.html) 7 | 8 | ## Run MkDocs Development Server 9 | 10 | Install MkDocs 11 | ```console 12 | pip install -r docs/requirements.txt 13 | ``` 14 | 15 | Run the development server 16 | ```console 17 | mkdocs serve 18 | ``` 19 | 20 | ## Build MkDocs Documentation 21 | 22 | Install MkDocs 23 | ```console 24 | pip install -r docs/requirements.txt 25 | ``` 26 | 27 | Build 28 | ```console 29 | mkdocs build 30 | ``` 31 | 32 | ## Build API Documentation 33 | 34 | ```console 35 | ./make-docs.sh 36 | ``` 37 | -------------------------------------------------------------------------------- /trajoptlib/cmake/CompilerFlags.cmake: -------------------------------------------------------------------------------- 1 | macro(compiler_flags target) 2 | if(NOT MSVC) 3 | target_compile_options(${target} PRIVATE -Wall -Wextra -pedantic) 4 | else() 5 | # Suppress the following warnings: 6 | # * C4244: lossy conversion 7 | # * C4251: missing dllexport/dllimport attribute on data member 8 | target_compile_options(${target} PRIVATE /wd4244 /wd4251) 9 | endif() 10 | set_property(TARGET ${target} PROPERTY COMPILE_WARNING_AS_ERROR ON) 11 | 12 | target_compile_features(${target} PUBLIC cxx_std_23) 13 | if(MSVC) 14 | target_compile_options(${target} PUBLIC /MP /Zf /utf-8 /bigobj) 15 | endif() 16 | endmacro() 17 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.chor text eol=lf 2 | *.cjs text eol=lf 3 | *.clang-format text eol=lf 4 | *.clang-tidy text eol=lf 5 | *.cmake text eol=lf 6 | *.cpp text eol=lf 7 | *.css text eol=lf 8 | *.gradle text eol=lf 9 | *.h text eol=lf 10 | *.hpp text eol=lf 11 | *.html text eol=lf 12 | *.in text eol=lf 13 | *.java text eol=lf 14 | *.js text eol=lf 15 | *.json text eol=lf 16 | *.lock text eol=lf 17 | *.md text eol=lf 18 | *.py text eol=lf 19 | *.rs text eol=lf 20 | *.sh text eol=lf 21 | *.toml text eol=lf 22 | *.traj text eol=lf 23 | *.ts text eol=lf 24 | *.tsx text eol=lf 25 | *.txt text eol=lf 26 | *.wpiformat text eol=lf 27 | *.wpiformat-license text eol=lf 28 | *.yml text eol=lf 29 | Doxyfile text eol=lf 30 | -------------------------------------------------------------------------------- /src/components/input/ExpressionInputList.tsx: -------------------------------------------------------------------------------- 1 | import { observer } from "mobx-react"; 2 | import React, { Component, PropsWithChildren } from "react"; 3 | import styles from "./InputList.module.css"; 4 | 5 | type Props = { 6 | rowGap?: number; 7 | style?: React.CSSProperties; 8 | }; 9 | 10 | type State = object; 11 | 12 | class InputList extends Component, State> { 13 | state = {}; 14 | render() { 15 | const className = styles.InputList + " " + styles.Expression; 16 | const rowGap = this.props.rowGap ?? 0; 17 | return ( 18 |
19 | {this.props.children} 20 |
21 | ); 22 | } 23 | } 24 | export default observer(InputList); 25 | -------------------------------------------------------------------------------- /src-tauri/capabilities/main.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./schemas/desktop-schema.json", 3 | "identifier": "main-capability", 4 | "description": "Capability for the main window", 5 | "windows": ["main"], 6 | "permissions": [ 7 | "core:app:allow-version", 8 | "core:event:allow-emit", 9 | "core:event:allow-listen", 10 | "core:event:allow-unlisten", 11 | "core:path:allow-basename", 12 | "core:path:allow-dirname", 13 | "core:path:allow-join", 14 | "core:webview:allow-internal-toggle-devtools", 15 | "core:window:allow-close", 16 | "core:window:allow-destroy", 17 | "core:window:allow-set-title", 18 | "dialog:allow-ask", 19 | "dialog:allow-confirm", 20 | "dialog:allow-open", 21 | "dialog:allow-save" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /src/components/field/svg/FieldAxisLines.tsx: -------------------------------------------------------------------------------- 1 | import { observer } from "mobx-react"; 2 | 3 | const DRAW_BOUND = 100; 4 | const GRID_STROKE = 0.01; 5 | 6 | function FieldGrid() { 7 | return ( 8 | <> 9 | 18 | 27 | 28 | ); 29 | } 30 | export default observer(FieldGrid); 31 | -------------------------------------------------------------------------------- /src-core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "choreo-core" 3 | version = "2026.0.0-beta.1" 4 | edition = "2024" 5 | homepage = "https://github.com/SleipnirGroup/Choreo" 6 | repository = "https://github.com/SleipnirGroup/Choreo.git" 7 | authors = ["Sleipnir Group"] 8 | license = "BSD-3-Clause" 9 | 10 | [dependencies] 11 | dashmap = "6.1.0" 12 | fastrand = "2.3.0" 13 | futures-util = "0.3.31" 14 | ipc-channel = { version = "0.20.2", features = ["async"] } 15 | serde = { version = "1.0", features = ["derive"] } 16 | serde_json = "1.0" 17 | tempfile = "3.23.0" 18 | thiserror = "2.0.17" 19 | tokio = { version = "1", features = ["process", "macros", "rt", "fs", "sync", "io-util"] } 20 | tracing = "0.1.43" 21 | trajoptlib = { path = "../trajoptlib" } 22 | zip = { version = "6.0.0", default-features = false } 23 | -------------------------------------------------------------------------------- /src/assets/KeepInLane.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { SvgIcon as MuiSvgIcon, SvgIconProps, styled } from "@mui/material"; 3 | const SvgIcon = styled(MuiSvgIcon, { 4 | name: "KeepInLaneIcon", 5 | shouldForwardProp: (prop) => prop !== "fill" 6 | })(() => ({ 7 | fill: "currentColor", 8 | stroke: "none" 9 | })); 10 | 11 | const KeepInLane: React.FunctionComponent = (props) => { 12 | return ( 13 | 21 | ); 22 | }; 23 | export default KeepInLane; 24 | -------------------------------------------------------------------------------- /src/util/BuildInfo.ts: -------------------------------------------------------------------------------- 1 | import { invoke } from "@tauri-apps/api/core"; 2 | 3 | export interface BuildInfo { 4 | ciPlatform: string | null; 5 | pkgName: string; 6 | pkgVersion: string; 7 | pkgVersionMajor: string; 8 | pkgVersionMinor: string; 9 | pkgVersionPatch: string; 10 | pkgVersionPre: string; 11 | target: string; 12 | host: string; 13 | profile: string; 14 | rustc: string; 15 | optLevel: string; 16 | debug: boolean; 17 | features: string[]; 18 | rustcVersion: string; 19 | arch: string; 20 | endian: string; 21 | toolchain_env: string; 22 | osFamily: string; 23 | os: string; 24 | buildTime: string; 25 | gitHash: string | null; 26 | gitBranch: string | null; 27 | } 28 | 29 | export function getBuildInfo(): Promise { 30 | return invoke("build_info"); 31 | } 32 | -------------------------------------------------------------------------------- /trajoptlib/test/src/util/generate_linear_initial_guess_test.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) TrajoptLib contributors 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | TEST_CASE("generate_linear_initial_guess - Linear initial guess", 10 | "[TrajoptUtil]") { 11 | std::vector> initial_guess_points{ 12 | {{1, 0, 0}}, {{2, 0, 0}, {3, 0, 0}}, {{6, 0, 0}}}; 13 | std::vector control_interval_counts{2, 3}; 14 | std::vector expected_x{1, 2, 3, 4, 5, 6}; 15 | auto result = trajopt::generate_linear_initial_guess( 16 | initial_guess_points, control_interval_counts); 17 | CHECK(expected_x == result.x); 18 | } 19 | -------------------------------------------------------------------------------- /src/util/UnitConversions.ts: -------------------------------------------------------------------------------- 1 | export const MassUnit = function (imperial: boolean) { 2 | return imperial ? "lb" : "kg"; 3 | }; 4 | export const KG_TO_LBS = 2.02462; 5 | export const KgToLbs = (mass: number) => mass * KG_TO_LBS; 6 | export const LbsToKg = (mass: number) => mass / KG_TO_LBS; 7 | 8 | export const MetersOrInches = function (imperial: boolean) { 9 | return imperial ? "in" : "m"; 10 | }; 11 | export const M_TO_IN = 39.3701; 12 | export const MToIn = (length: number) => length * M_TO_IN; 13 | export const InToM = (length: number) => length / M_TO_IN; 14 | 15 | export const MetersOrFeet = function (imperial: boolean) { 16 | return imperial ? "ft" : "m"; 17 | }; 18 | export const M_TO_FT = M_TO_IN / 12; 19 | export const MToFt = (length: number) => length * M_TO_FT; 20 | export const FtToM = (length: number) => length / M_TO_FT; 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | !.vscode/settings.json 19 | .idea 20 | .DS_Store 21 | *.suo 22 | *.ntvs* 23 | *.njsproj 24 | *.sln 25 | *.sw? 26 | 27 | # Choreo 28 | src-tauri/gen 29 | src-tauri/target 30 | target 31 | 32 | # TrajoptLib 33 | trajoptlib/build* 34 | trajoptlib/target 35 | trajoptlib/Cargo.lock 36 | 37 | # Python 38 | *.egg-info/ 39 | .eggs/ 40 | .pytest_cache/ 41 | __pycache__/ 42 | dist/ 43 | 44 | # clangd 45 | .cache 46 | compile_commands.json 47 | 48 | # mkdocs 49 | site/ 50 | 51 | # cmake user presets 52 | CMakeUserPresets.json 53 | 54 | build/ 55 | cli/ 56 | test-tmp*/ 57 | -------------------------------------------------------------------------------- /choreolib/py/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "sleipnirgroup-choreolib" 3 | description = "Robot-side library for parsing and following Choreo paths" 4 | dynamic = ["version"] 5 | readme = "README.md" 6 | license = "BSD-3-Clause" 7 | license-files = ["LICENSE.txt"] 8 | requires-python = ">=3.9" 9 | dependencies = ["numpy", "scipy", "wpilib"] 10 | 11 | [[project.authors]] 12 | name = "Choreo Development Team" 13 | 14 | [project.urls] 15 | Documentation = "https://choreo.autos/" 16 | 17 | [build-system] 18 | requires = [ 19 | "setuptools>=61.0", 20 | "setuptools-git-versioning", 21 | ] 22 | build-backend = "setuptools.build_meta" 23 | 24 | [tool.setuptools-git-versioning] 25 | enabled = true 26 | version_callback = "_choreo_version:get_version" 27 | 28 | [tool.pytest.ini_options] 29 | minversion = "6.0" 30 | testpaths = ["choreo/test"] 31 | -------------------------------------------------------------------------------- /src/components/input/InputList.tsx: -------------------------------------------------------------------------------- 1 | import { observer } from "mobx-react"; 2 | import React, { Component, PropsWithChildren } from "react"; 3 | import styles from "./InputList.module.css"; 4 | 5 | type Props = { 6 | noCheckbox?: boolean; 7 | rowGap?: number; 8 | style?: React.CSSProperties; 9 | }; 10 | 11 | type State = object; 12 | 13 | class InputList extends Component, State> { 14 | state = {}; 15 | render() { 16 | const className = 17 | styles.InputList + 18 | " " + 19 | ((this.props.noCheckbox ?? false) ? styles.NoCheckbox : ""); 20 | const rowGap = this.props.rowGap ?? 0; 21 | return ( 22 |
23 | {this.props.children} 24 |
25 | ); 26 | } 27 | } 28 | export default observer(InputList); 29 | -------------------------------------------------------------------------------- /src/components/field/svg/FieldPathLines.tsx: -------------------------------------------------------------------------------- 1 | import { Component } from "react"; 2 | import { doc } from "../../../document/DocumentManager"; 3 | 4 | import { observer } from "mobx-react"; 5 | 6 | type Props = object; 7 | 8 | type State = object; 9 | 10 | class FieldPathLines extends Component { 11 | state = {}; 12 | 13 | render() { 14 | let pathString = ""; 15 | doc.pathlist.activePath.params.waypoints.forEach((point, _index) => { 16 | pathString += `${point.x.value}, ${point.y.value} `; 17 | }); 18 | return ( 19 | <> 20 | 27 | 28 | ); 29 | } 30 | } 31 | export default observer(FieldPathLines); 32 | -------------------------------------------------------------------------------- /choreolib/test_deploy/test.chor: -------------------------------------------------------------------------------- 1 | { 2 | "name":"idk", 3 | "version":"v2025.0.0", 4 | "type":"Swerve", 5 | "variables":{ 6 | "expressions":{}, 7 | "poses":{} 8 | }, 9 | "config":{ 10 | "modules":[ 11 | {"x":["11 in",0.2794], "y":["11 in",0.2794]}, 12 | {"x":["-11 in",-0.2794], "y":["11 in",0.2794]}, 13 | {"x":["-11 in",-0.2794], "y":["-11 in",-0.2794]}, 14 | {"x":["11 in",0.2794], "y":["-11 in",-0.2794]}], 15 | "mass":["150 lbs",68.0388555], 16 | "inertia":["6 kg m ^ 2",6.0], 17 | "gearing":["6.5",6.5], 18 | "radius":["2 in",0.0508], 19 | "vmax":["6000 RPM",628.3185307179587], 20 | "tmax":["1.2 N * m",1.2], 21 | "bumper":{ 22 | "front":["16 in",0.4064], 23 | "left":["16 in",0.4064], 24 | "back":["16 in",0.4064], 25 | "right":["16 in",0.4064] 26 | }, 27 | "differentialTrackWidth":["24 in",0.6096], 28 | }, 29 | "generationFeatures":[] 30 | } 31 | -------------------------------------------------------------------------------- /docs/contributing/building-choreolib.md: -------------------------------------------------------------------------------- 1 | # Building ChoreoLib 2 | 3 | Maven artifacts for ChoreoLib can be built using `./gradlew publish` or `./gradlew publishToMavenLocal` for local library access. 4 | 5 | The built library will be located in the respective operating system's m2 folder. By default, Maven local repository is defaulted to the `${user.home}/.m2/repository` folder: 6 | 7 | === "Windows" 8 | 9 | ``` 10 | %HOMEPATH%\.m2\repository 11 | ``` 12 | 13 | === "macOS/UNIX" 14 | 15 | ``` 16 | ~/.m2/repository 17 | ``` 18 | 19 | To use your build, update `vendordeps/ChoreoLib.json` to point to the local repository and version. 20 | 21 | !!! danger 22 | If you attempt to work with this project in VSCode with WPILib plugins, it will ask you if you want to import the project. Click no. This will change the project into a robot code project and break everything. 23 | -------------------------------------------------------------------------------- /docs/installation.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | Start by downloading Choreo from **[Releases](https://github.com/SleipnirGroup/Choreo/releases)** 4 | 5 | ## Supported Systems 6 | 7 | | Operating System | Supported? | 8 | | ------------------------------------ | ---------- | 9 | | Windows x86\_64 | ✅ | 10 | | Windows aarch64 | ✅ | 11 | | macOS x86\_64 | ✅ | 12 | | macOS arm64 | ✅ | 13 | | Linux x86\_64 | ✅ | 14 | | Linux aarch64 | ❌ | 15 | 16 | Checkout [Building Choreo](./contributing/building-choreo.md) for instruction on how to build Choreo as well as the tech stack. 17 | 18 | Once installed, head to [Robot Config](./document-settings/robot-configuration.md) to configure Choreo for your robot. 19 | -------------------------------------------------------------------------------- /src/assets/InitialGuessPoint.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { SvgIcon as MuiSvgIcon, SvgIconProps, styled } from "@mui/material"; 3 | const SvgIcon = styled(MuiSvgIcon, { 4 | name: "InitialGuessPoint", 5 | shouldForwardProp: (prop) => prop !== "fill" 6 | })(() => ({ 7 | fill: "none", 8 | stroke: "currentColor", 9 | strokeLinecap: "round", 10 | strokeLinejoin: "round", 11 | strokeWidth: "2.25px" 12 | })); 13 | 14 | const Waypoint: React.FunctionComponent = (props) => { 15 | return ( 16 | 30 | ); 31 | }; 32 | export default Waypoint; 33 | -------------------------------------------------------------------------------- /src-core/src/generation/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod generate; 2 | pub mod heading; 3 | pub mod intervals; 4 | pub mod remote; 5 | 6 | /// A port of `WPILib`'s MathUtil.inputModulus 7 | #[must_use] 8 | pub fn input_modulus(input: f64, maximum_input: f64, minimum_input: f64) -> f64 { 9 | let mut val = input; 10 | let modulus = maximum_input - minimum_input; 11 | 12 | // Wrap input if it's above the maximum input 13 | let num_max = ((val - minimum_input) / modulus).trunc(); 14 | val -= num_max * modulus; 15 | 16 | // Wrap input if it's below the minimum input 17 | let num_min = ((val - maximum_input) / modulus).trunc(); 18 | val -= num_min * modulus; 19 | 20 | val 21 | } 22 | 23 | /// A port of `WPILib`'s MathUtil.angleModulus 24 | #[must_use] 25 | pub fn angle_modulus(input: f64) -> f64 { 26 | use std::f64::consts::PI; 27 | input_modulus(input, PI, -PI) 28 | } 29 | pub mod transformers; 30 | -------------------------------------------------------------------------------- /src/assets/Waypoint.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { SvgIcon as MuiSvgIcon, SvgIconProps, styled } from "@mui/material"; 3 | const SvgIcon = styled(MuiSvgIcon, { 4 | name: "MopeimIcon", 5 | shouldForwardProp: (prop) => prop !== "fill" 6 | })(() => ({ 7 | fill: "none", 8 | stroke: "currentColor", 9 | strokeLinecap: "round", 10 | strokeLinejoin: "round", 11 | strokeWidth: "2.25px" 12 | })); 13 | 14 | const Waypoint: React.FunctionComponent = (props) => { 15 | return ( 16 | 26 | ); 27 | }; 28 | export default Waypoint; 29 | -------------------------------------------------------------------------------- /choreolib/src/main/native/cpp/choreo/trajectory/EventMarker.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Choreo contributors 2 | 3 | #include "choreo/trajectory/EventMarker.h" 4 | 5 | #include 6 | 7 | #include 8 | 9 | void choreo::to_json(wpi::json& json, const EventMarker& event) { 10 | json = wpi::json{{"data", wpi::json{{"t", event.timestamp.value()}}}, 11 | {"event", wpi::json{{"name", event.event}}}}; 12 | } 13 | 14 | void choreo::from_json(const wpi::json& json, EventMarker& event) { 15 | auto targetTimestamp = json.at("from").at("targetTimestamp"); 16 | if (!targetTimestamp.is_number()) { 17 | event.timestamp = units::second_t{-1}; 18 | event.event = ""; 19 | } else { 20 | event.timestamp = 21 | units::second_t{json.at("from").at("offset").at("val").get() + 22 | targetTimestamp.get()}; 23 | event.event = json.at("name").get(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /trajoptlib/docs/footer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | 15 | 16 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /copy-sidecar.cjs: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const { exit } = require("process"); 3 | 4 | const [name, newPathRoot] = process.argv.slice(2); 5 | 6 | const extension = process.env.TAURI_ENV_FAMILY === "windows" ? ".exe" : ""; 7 | const targetTriple = process.env.TAURI_ENV_TARGET_TRIPLE; 8 | 9 | const oldPathNative = `./target/release/${name}${extension}`; 10 | const oldPath = `./target/${targetTriple}/release/${name}${extension}`; 11 | const newPath = `${newPathRoot}/${name}-${targetTriple}${extension}`; 12 | 13 | let path; 14 | if (fs.existsSync(oldPathNative)) { 15 | path = oldPathNative; 16 | } else if (fs.existsSync(oldPath)) { 17 | path = oldPath; 18 | } else { 19 | console.log(`No file found at ${oldPathNative} or ${oldPath}`); 20 | exit(1); 21 | } 22 | 23 | fs.copyFile(path, newPath, (err) => { 24 | if (err) { 25 | console.error(`Error copying ${path} to ${newPath}: ${err}`); 26 | exit(1); 27 | } 28 | console.log(`Copied ${path} to ${newPath}`); 29 | }); 30 | -------------------------------------------------------------------------------- /choreolib/src/main/native/include/choreo/trajectory/struct/SwerveSampleStruct.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Choreo contributors 2 | 3 | #pragma once 4 | 5 | #include 6 | #include 7 | 8 | #include "choreo/trajectory/SwerveSample.h" 9 | 10 | template <> 11 | struct wpi::Struct { 12 | static constexpr std::string_view GetTypeName() { return "SwerveSample"; } 13 | static constexpr size_t GetSize() { return 144; } 14 | static constexpr std::string_view GetSchema() { 15 | return "double timestamp;double x;double y;double heading;double vx;double " 16 | "vy;double omega;double ax;double ay;double alpha;double " 17 | "moduleForcesX[4];double moduleForcesY[4];"; 18 | } 19 | 20 | static choreo::SwerveSample Unpack(std::span data); 21 | static void Pack(std::span data, const choreo::SwerveSample& value); 22 | }; 23 | 24 | static_assert(wpi::StructSerializable); 25 | -------------------------------------------------------------------------------- /src/components/field/svg/FieldGrid.tsx: -------------------------------------------------------------------------------- 1 | import { observer } from "mobx-react"; 2 | import { Component } from "react"; 3 | 4 | type Props = object; 5 | 6 | type State = object; 7 | 8 | const DRAW_BOUND = 100; 9 | const GRID_STROKE = 0.01; 10 | 11 | class FieldGrid extends Component { 12 | state = {}; 13 | 14 | render() { 15 | return ( 16 | <> 17 | 18 | 19 | 25 | 26 | 27 | 34 | 35 | ); 36 | } 37 | } 38 | export default observer(FieldGrid); 39 | -------------------------------------------------------------------------------- /src/components/config/robotconfig/DifferentialConfigPanel.tsx: -------------------------------------------------------------------------------- 1 | import { observer } from "mobx-react"; 2 | import { Component } from "react"; 3 | import { doc } from "../../../document/DocumentManager"; 4 | import ExpressionInput from "../../input/ExpressionInput"; 5 | import ExpressionInputList from "../../input/ExpressionInputList"; 6 | 7 | type Props = { rowGap: number }; 8 | 9 | type State = object; 10 | 11 | class SwerveConfigPanel extends Component { 12 | render() { 13 | const config = doc.robotConfig; 14 | return ( 15 | 16 | 24 | 25 | ); 26 | } 27 | } 28 | export default observer(SwerveConfigPanel); 29 | -------------------------------------------------------------------------------- /trajoptlib/test/src/differential_path_builder_test.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) TrajoptLib contributors 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | TEST_CASE("DifferentialPathBuilder - Linear initial guess", 9 | "[DifferentialPathBuilder]") { 10 | using namespace trajopt; 11 | 12 | trajopt::DifferentialPathBuilder path; 13 | path.wpt_initial_guess_point(0, Pose2d{0.0, 0.0, 0.0}); // at 0 14 | 15 | path.sgmt_initial_guess_points( 16 | 0, {Pose2d{1.0, 0.0, 0.0}, Pose2d{2.0, 0.0, 0.0}}); // from 0 to 1 17 | path.wpt_initial_guess_point(1, Pose2d{1.0, 0.0, 0.0}); // at 1 18 | 19 | path.wpt_initial_guess_point(2, Pose2d{5.0, 0.0, 0.0}); // at 2 20 | 21 | path.set_control_interval_counts({3, 2}); 22 | 23 | std::vector result = path.calculate_linear_initial_guess().x; 24 | std::vector expected = {0.0, 1.0, 2.0, 1.0, 3.0, 5.0}; 25 | 26 | CHECK(result == expected); 27 | } 28 | -------------------------------------------------------------------------------- /choreolib/src/main/native/include/choreo/trajectory/struct/DifferentialSampleStruct.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Choreo contributors 2 | 3 | #pragma once 4 | 5 | #include 6 | #include 7 | 8 | #include "choreo/trajectory/DifferentialSample.h" 9 | 10 | template <> 11 | struct wpi::Struct { 12 | static constexpr std::string_view GetTypeName() { 13 | return "DifferentialSample"; 14 | } 15 | static constexpr size_t GetSize() { return 96; } 16 | static constexpr std::string_view GetSchema() { 17 | return "double timestamp;double x;double y;double heading;double vl;double " 18 | "vr;double omega;double al;double ar;double alpha;double fl;double " 19 | "fr;"; 20 | } 21 | 22 | static choreo::DifferentialSample Unpack(std::span data); 23 | static void Pack(std::span data, 24 | const choreo::DifferentialSample& value); 25 | }; 26 | 27 | static_assert(wpi::StructSerializable); 28 | -------------------------------------------------------------------------------- /src/components/field/Field.module.css: -------------------------------------------------------------------------------- 1 | .Container { 2 | background-color: #15171b; 3 | width: 100%; 4 | height: 100%; 5 | position: relative; 6 | } 7 | 8 | .FieldBackground { 9 | background-position: center; 10 | background-size: contain; 11 | background-repeat: no-repeat; 12 | position: relative; 13 | top: 50%; 14 | transform: translateY(-50%); 15 | } 16 | 17 | .Overlay { 18 | background-repeat: repeat; 19 | background-size: calc(100% / 16.4592) calc(100% / 8.2296); /* 1 meter in pixels*/ 20 | background-position-y: calc((0.81) * 100% / 8.2296); 21 | background-position-x: calc((0.52) * 100% / 16.4592); 22 | background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoBAMAAAB+0KVeAAAAHlBMVEUAAABkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGRkZGSH0mEbAAAACnRSTlMAzDPDPPPYnGMw2CgMzQAAAChJREFUKM9jgAPOAgZMwGIwKkhXQSUY0BCCMxkEYUAsEM4cjI4fwYIAf2QMNbUsZjcAAAAASUVORK5CYII="); 23 | } 24 | 25 | .Container #field-svg-container { 26 | position: absolute; 27 | top: 0; 28 | left: 0; 29 | width: 100%; 30 | height: 100%; 31 | } 32 | -------------------------------------------------------------------------------- /.github/labeler.yml: -------------------------------------------------------------------------------- 1 | "component: backend": 2 | - changed-files: 3 | - any-glob-to-any-file: ["src-core/**", "src-tauri/**"] 4 | 5 | "component: cli": 6 | - changed-files: 7 | - any-glob-to-any-file: ["src-cli/**"] 8 | 9 | "component: choreolib-cpp": 10 | - changed-files: 11 | - any-glob-to-any-file: 12 | ["choreolib/src/main/native/**", "choreolib/src/test/native/**"] 13 | 14 | "component: choreolib-java": 15 | - changed-files: 16 | - any-glob-to-any-file: 17 | ["choreolib/src/main/java/**", "choreolib/src/test/java/**"] 18 | 19 | "component: choreolib-py": 20 | - changed-files: 21 | - any-glob-to-any-file: ["choreolib/py/**"] 22 | 23 | "component: documentation": 24 | - changed-files: 25 | - any-glob-to-any-file: ["docs/**"] 26 | 27 | "component: trajoptlib": 28 | - changed-files: 29 | - any-glob-to-any-file: ["trajoptlib/**"] 30 | 31 | "component: ui": 32 | - changed-files: 33 | - any-glob-to-any-file: ["src/**"] 34 | 35 | "type: build": 36 | - changed-files: 37 | - any-glob-to-any-file: [".github/**"] 38 | -------------------------------------------------------------------------------- /src/components/field/svg/FieldSamples.tsx: -------------------------------------------------------------------------------- 1 | import { Component } from "react"; 2 | import { doc } from "../../../document/DocumentManager"; 3 | 4 | import { observer } from "mobx-react"; 5 | 6 | type Props = object; 7 | 8 | type State = object; 9 | 10 | class FieldSamples extends Component { 11 | state = {}; 12 | LINE_LENGTH = 0.15; 13 | render() { 14 | const path = doc.pathlist.activePath; 15 | const trajectory = path.ui.generating 16 | ? path.ui.generationProgress 17 | : path.trajectory.fullTrajectory; 18 | // preserve the access of generationIterationNumber 19 | // to trigger rerenders when mutating the in-progress trajectory in place 20 | const _ = path.ui.generationIterationNumber; 21 | return ( 22 | <> 23 | {trajectory.map((point, idx) => ( 24 | 31 | ))} 32 | 33 | ); 34 | } 35 | } 36 | export default observer(FieldSamples); 37 | -------------------------------------------------------------------------------- /choreolib/vendordeps/WPILibNewCommands.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileName": "WPILibNewCommands.json", 3 | "name": "WPILib-New-Commands", 4 | "version": "2026.+", 5 | "uuid": "111e20f7-815e-48f8-9dd6-e675ce75b266", 6 | "mavenUrls": [], 7 | "jsonUrl": "", 8 | "javaDependencies": [ 9 | { 10 | "groupId": "edu.wpi.first.wpilibNewCommands", 11 | "artifactId": "wpilibNewCommands-java", 12 | "version": "wpilib" 13 | } 14 | ], 15 | "jniDependencies": [], 16 | "cppDependencies": [ 17 | { 18 | "groupId": "edu.wpi.first.wpilibNewCommands", 19 | "artifactId": "wpilibNewCommands-cpp", 20 | "version": "2026.+", 21 | "libName": "wpilibNewCommands", 22 | "headerClassifier": "headers", 23 | "sourcesClassifier": "sources", 24 | "sharedLibrary": true, 25 | "skipInvalidPlatforms": true, 26 | "binaryPlatforms": [ 27 | "linuxathena", 28 | "linuxarm32", 29 | "linuxarm64", 30 | "windowsx86-64", 31 | "windowsx86", 32 | "linuxx86-64", 33 | "osxuniversal" 34 | ] 35 | } 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /src/assets/IconInProgress.tsx: -------------------------------------------------------------------------------- 1 | import { Box, CircularProgress, SxProps } from "@mui/material"; 2 | import React, { JSXElementConstructor, ReactElement } from "react"; 3 | 4 | function IconInProgress(props: { 5 | icon: ReactElement>; 6 | sx?: SxProps; 7 | }) { 8 | return ( 9 | 18 | 19 | 31 | {React.cloneElement(props.icon, { 32 | htmlColor: "inherit", 33 | fontSize: "83.333%" 34 | })} 35 | 36 | 37 | ); 38 | } 39 | export default IconInProgress; 40 | -------------------------------------------------------------------------------- /src/components/config/WaypointConfigPanel.module.css: -------------------------------------------------------------------------------- 1 | .WaypointPanel { 2 | overflow-x: hidden; 3 | position: absolute; 4 | top: 0; 5 | padding: 8px; 6 | width: max-content; 7 | background-color: var(--background-light-gray); 8 | color: white; 9 | display: flex; 10 | flex-direction: column; 11 | justify-content: flex-start; 12 | border-radius: 0 0 10px 0; 13 | } 14 | .ViewOptionsPanel { 15 | overflow-x: visible; 16 | position: absolute; 17 | top: 0; 18 | right: 0; 19 | padding: 8px; 20 | width: fit-content; 21 | background-color: var(--background-light-gray); 22 | color: white; 23 | display: flex; 24 | flex-direction: column; 25 | justify-content: flex-start; 26 | align-items: flex-end; 27 | border-radius: 0 0 0 10px; 28 | } 29 | .WaypointVisibilityPanel { 30 | overflow-x: hidden; 31 | position: absolute; 32 | bottom: 0; 33 | padding: 8px; 34 | width: min-content; 35 | background-color: var(--background-light-gray); 36 | color: white; 37 | display: flex; 38 | flex-direction: column; 39 | justify-content: flex-start; 40 | border-radius: 0px 10px 0px 0; 41 | } 42 | -------------------------------------------------------------------------------- /src/assets/EventMarker.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { SvgIcon as MuiSvgIcon, SvgIconProps, styled } from "@mui/material"; 3 | const SvgIcon = styled(MuiSvgIcon, { 4 | name: "EventMarker", 5 | shouldForwardProp: (prop) => prop !== "fill" 6 | })(() => ({ 7 | fill: "none", 8 | stroke: "currentColor", 9 | strokeLinecap: "round", 10 | strokeLinejoin: "round", 11 | strokeWidth: "2.25px" 12 | })); 13 | 14 | const Waypoint: React.FunctionComponent = (props) => { 15 | return ( 16 | 35 | ); 36 | }; 37 | export default Waypoint; 38 | -------------------------------------------------------------------------------- /src-tauri/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "choreo" 3 | version = "2026.0.0-beta.1" 4 | edition = "2024" 5 | description = """\ 6 | A graphical tool for planning time-optimized trajectories for autonomous \ 7 | mobile robots in the FIRST Robotics Competition.\ 8 | """ 9 | readme = "../README.md" 10 | homepage = "https://github.com/SleipnirGroup/Choreo" 11 | repository = "https://github.com/SleipnirGroup/Choreo.git" 12 | authors = ["Sleipnir Group"] 13 | license = "BSD-3-Clause" 14 | 15 | [build-dependencies] 16 | built = { version = "0.8.0", features = ["chrono"] } 17 | tauri-build = { version = "2.5.3", features = [] } 18 | 19 | [dependencies] 20 | choreo-core = { path = "../src-core" } 21 | current_platform = "0.2.0" 22 | dirs = "6.0.0" 23 | nu-ansi-term = "0.50.3" 24 | open = "5.3.3" 25 | regex = "1.12.2" 26 | serde = "1.0" 27 | serde_json = "1.0" 28 | tauri = { version = "2.9.5", features = [ "devtools"] } 29 | tauri-plugin-dialog = "2.4.2" 30 | tauri-plugin-fs = "2.4.4" 31 | tracing = "0.1.43" 32 | tracing-appender = "0.2.4" 33 | tracing-subscriber = { version = "0.3.22", features = ["ansi", "fmt", "std"] } 34 | 35 | [features] 36 | custom-protocol = ["tauri/custom-protocol"] 37 | -------------------------------------------------------------------------------- /choreolib/src/main/native/include/choreo/trajectory/EventMarker.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Choreo contributors 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | namespace choreo { 11 | 12 | /// A marker for an event in a trajectory. 13 | struct EventMarker { 14 | /// The timestamp of the event. 15 | units::second_t timestamp; 16 | 17 | /// The event. 18 | std::string event; 19 | 20 | /// Returns a new EventMarker with the timestamp offset by the specified 21 | /// amount. 22 | /// 23 | /// @param timestampOffset The amount to offset the timestamp by. 24 | /// @return A new EventMarker with the timestamp offset by the specified 25 | /// amount. 26 | EventMarker OffsetBy(units::second_t timestampOffset) const { 27 | return EventMarker{timestamp + timestampOffset, event}; 28 | } 29 | 30 | /// EventMarker equality operator. 31 | /// 32 | /// @return True for equality. 33 | bool operator==(const EventMarker&) const = default; 34 | }; 35 | 36 | void to_json(wpi::json& json, const EventMarker& event); 37 | void from_json(const wpi::json& json, EventMarker& event); 38 | 39 | } // namespace choreo 40 | -------------------------------------------------------------------------------- /choreolib/ChoreoLib2025.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileName": "ChoreoLib2025.json", 3 | "name": "ChoreoLib", 4 | "version": "2025.0.3", 5 | "uuid": "b5e23f0a-dac9-4ad2-8dd6-02767c520aca", 6 | "frcYear": "2025", 7 | "mavenUrls": [ 8 | "https://lib.choreo.autos/dep", 9 | "https://repo1.maven.org/maven2" 10 | ], 11 | "jsonUrl": "https://lib.choreo.autos/dep/ChoreoLib2025.json", 12 | "javaDependencies": [ 13 | { 14 | "groupId": "choreo", 15 | "artifactId": "ChoreoLib-java", 16 | "version": "2025.0.3" 17 | }, 18 | { 19 | "groupId": "com.google.code.gson", 20 | "artifactId": "gson", 21 | "version": "2.11.0" 22 | } 23 | ], 24 | "jniDependencies": [], 25 | "cppDependencies": [ 26 | { 27 | "groupId": "choreo", 28 | "artifactId": "ChoreoLib-cpp", 29 | "version": "2025.0.3", 30 | "libName": "ChoreoLib", 31 | "headerClassifier": "headers", 32 | "sharedLibrary": false, 33 | "skipInvalidPlatforms": true, 34 | "binaryPlatforms": [ 35 | "windowsx86-64", 36 | "linuxx86-64", 37 | "osxuniversal", 38 | "linuxathena", 39 | "linuxarm32", 40 | "linuxarm64" 41 | ] 42 | } 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /src/assets/MoI.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { SvgIcon as MuiSvgIcon, SvgIconProps, styled } from "@mui/material"; 3 | const SvgIcon = styled(MuiSvgIcon, { 4 | name: "MopeimIcon", 5 | shouldForwardProp: (prop) => prop !== "fill" 6 | })(() => ({ 7 | fill: "currentColor", 8 | stroke: "currentColor", 9 | strokeLinecap: "round", 10 | strokeLinejoin: "round", 11 | strokeWidth: "2.25px" 12 | })); 13 | 14 | const MoI: React.FunctionComponent = (props) => { 15 | return ( 16 | 31 | ); 32 | }; 33 | export default MoI; 34 | -------------------------------------------------------------------------------- /choreolib/ChoreoLib2025Beta.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileName": "ChoreoLib2025Beta.json", 3 | "name": "ChoreoLib", 4 | "version": "2025.0.0-beta-9", 5 | "uuid": "b5e23f0a-dac9-4ad2-8dd6-02767c520aca", 6 | "frcYear": "2025", 7 | "mavenUrls": [ 8 | "https://lib.choreo.autos/dep", 9 | "https://repo1.maven.org/maven2" 10 | ], 11 | "jsonUrl": "https://lib.choreo.autos/dep/ChoreoLib2025Beta.json", 12 | "javaDependencies": [ 13 | { 14 | "groupId": "choreo", 15 | "artifactId": "ChoreoLib-java", 16 | "version": "2025.0.0-beta-9" 17 | }, 18 | { 19 | "groupId": "com.google.code.gson", 20 | "artifactId": "gson", 21 | "version": "2.11.0" 22 | } 23 | ], 24 | "jniDependencies": [], 25 | "cppDependencies": [ 26 | { 27 | "groupId": "choreo", 28 | "artifactId": "ChoreoLib-cpp", 29 | "version": "2025.0.0-beta-9", 30 | "libName": "ChoreoLib", 31 | "headerClassifier": "headers", 32 | "sharedLibrary": false, 33 | "skipInvalidPlatforms": true, 34 | "binaryPlatforms": [ 35 | "windowsx86-64", 36 | "linuxx86-64", 37 | "osxuniversal", 38 | "linuxathena", 39 | "linuxarm32", 40 | "linuxarm64" 41 | ] 42 | } 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /choreolib/vendor_jsons/ChoreoLib2026Beta.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileName": "ChoreoLib2026Beta.json", 3 | "name": "ChoreoLib", 4 | "version": "2026.0.0-beta-1", 5 | "uuid": "b5e23f0a-dac9-4ad2-8dd6-02767c520aca", 6 | "frcYear": "2026beta", 7 | "mavenUrls": [ 8 | "https://frcmaven.wpi.edu/artifactory/sleipnirgroup-mvn-release/" 9 | ], 10 | "jsonUrl": "https://choreo.autos/lib/ChoreoLib2026Beta.json", 11 | "javaDependencies": [ 12 | { 13 | "groupId": "choreo", 14 | "artifactId": "ChoreoLib-java", 15 | "version": "2026.0.0-beta-1" 16 | }, 17 | { 18 | "groupId": "com.google.code.gson", 19 | "artifactId": "gson", 20 | "version": "2.11.0" 21 | } 22 | ], 23 | "jniDependencies": [], 24 | "cppDependencies": [ 25 | { 26 | "groupId": "choreo", 27 | "artifactId": "ChoreoLib-cpp", 28 | "version": "2026.0.0-beta-1", 29 | "libName": "ChoreoLib", 30 | "headerClassifier": "headers", 31 | "sharedLibrary": false, 32 | "skipInvalidPlatforms": true, 33 | "binaryPlatforms": [ 34 | "windowsx86-64", 35 | "linuxx86-64", 36 | "osxuniversal", 37 | "linuxathena", 38 | "linuxarm32", 39 | "linuxarm64" 40 | ] 41 | } 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /choreolib/vendor_jsons/ChoreoLib2027Alpha.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileName": "ChoreoLib2027Alpha.json", 3 | "name": "ChoreoLib", 4 | "version": "2027.0.0-alpha-1", 5 | "uuid": "b5e23f0a-dac9-4ad2-8dd6-02767c520aca", 6 | "frcYear": "2027_alpha1", 7 | "mavenUrls": [ 8 | "https://frcmaven.wpi.edu/artifactory/sleipnirgroup-mvn-release/" 9 | ], 10 | "jsonUrl": "https://choreo.autos/lib/ChoreoLib2027Alpha.json", 11 | "javaDependencies": [ 12 | { 13 | "groupId": "choreo", 14 | "artifactId": "ChoreoLib-java", 15 | "version": "2027.0.0-alpha-1" 16 | }, 17 | { 18 | "groupId": "com.google.code.gson", 19 | "artifactId": "gson", 20 | "version": "2.11.0" 21 | } 22 | ], 23 | "jniDependencies": [], 24 | "cppDependencies": [ 25 | { 26 | "groupId": "choreo", 27 | "artifactId": "ChoreoLib-cpp", 28 | "version": "2027.0.0-alpha-1", 29 | "libName": "ChoreoLib", 30 | "headerClassifier": "headers", 31 | "sharedLibrary": false, 32 | "skipInvalidPlatforms": true, 33 | "binaryPlatforms": [ 34 | "windowsx86-64", 35 | "linuxx86-64", 36 | "osxuniversal", 37 | "linuxarm32", 38 | "linuxarm64", 39 | "linuxsystemcore" 40 | ] 41 | } 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /src-tauri/tauri.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "build": { 3 | "beforeDevCommand": "vite", 4 | "beforeBuildCommand": "vite build", 5 | "beforeBundleCommand": "node copy-sidecar.cjs choreo-cli ./target", 6 | "frontendDist": "../dist", 7 | "devUrl": "http://localhost:1420" 8 | }, 9 | "bundle": { 10 | "active": true, 11 | "targets": ["appimage", "deb", "dmg", "nsis", "rpm"], 12 | "macOS": { 13 | "minimumSystemVersion": "14.5", 14 | "signingIdentity": "-" 15 | }, 16 | "shortDescription": "Choreo", 17 | "icon": [ 18 | "icons/32x32.png", 19 | "icons/128x128.png", 20 | "icons/128x128@2x.png", 21 | "icons/icon.icns", 22 | "icons/icon.ico" 23 | ], 24 | "externalBin": ["../target/choreo-cli"] 25 | }, 26 | "productName": "Choreo", 27 | "version": "2026.0.0-beta-1", 28 | "identifier": "org.sleipnirgroup", 29 | "plugins": {}, 30 | "app": { 31 | "withGlobalTauri": false, 32 | "windows": [ 33 | { 34 | "fullscreen": false, 35 | "resizable": true, 36 | "title": "Choreo v2026.0.0-beta-1", 37 | "width": 1200, 38 | "height": 800 39 | } 40 | ], 41 | "security": { 42 | "capabilities": ["main-capability"], 43 | "csp": null 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /docs/contributing/sample-flipping.md: -------------------------------------------------------------------------------- 1 | # A Derivation of Sample Flipping 2 | 3 | The logic for flipping both swerve and differential samples is derived below. 4 | 5 | There are two kinds of flipping. One ("rotate around") is for rotationally symmetric fields, while the other ("mirror") reflects the field across the centerline parallel to both alliance walls. Mirroring can also be thought of as performing a "rotate" and then reflecting across the centerline perpendicular to both alliance walls. 6 | 7 | Let FIELD_LENGTH be the distance between alliance walls, and FIELD_WIDTH be the distance between the long side walls. 8 | 9 | We must preserve the following test points: 10 | 11 | Original | Rotated | Mirrored | Notes 12 | ---------|--------|-|- 13 | x = FIELD_LENGTH/2, y = FIELD_WIDTH/2 | unchanged | unchanged 14 | x = 0, y = 0 | x = FIELD_LENGTH, y = FIELD_WIDTH | x = FIELD_LENGTH, y = 0 15 | θ = 0 | π | π 16 | θ = π/2 | 3π/2 | π/2 17 | vx = 1 | vx = -1 | vx = -1 18 | vy = 1 | vy = -1 | vy = 1 19 | ω,α > 0 | ω,α > 0 | ω,α < 0 | Mirroring a CCW-spinning robot gives a CW-spinning robot. 20 | vl = -1, vr = 1 | unchanged | vl = 1, vr = -1 | Derived from above. 21 | fx, fy | both negated | x negated 22 | force left side | unchanged | flips to right side (FR, BR) | and vice versa left side, right side 23 | -------------------------------------------------------------------------------- /trajoptlib/include/trajopt/path/path.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) TrajoptLib contributors 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #include "trajopt/constraint/constraint.hpp" 11 | #include "trajopt/util/symbol_exports.hpp" 12 | 13 | namespace trajopt { 14 | 15 | /// A path waypoint. 16 | struct TRAJOPT_DLLEXPORT Waypoint { 17 | /// Instantaneous constraints at the waypoint. 18 | std::vector waypoint_constraints; 19 | 20 | /// Continuous constraints along the segment. 21 | std::vector segment_constraints; 22 | }; 23 | 24 | /// A path. 25 | /// 26 | /// @tparam Drivetrain The drivetrain type (e.g., swerve, differential). 27 | /// @tparam Solution The solution type (e.g., swerve, differential). 28 | template 29 | struct TRAJOPT_DLLEXPORT Path { 30 | /// Waypoints along the path. 31 | std::vector waypoints; 32 | 33 | /// Drivetrain of the robot. 34 | Drivetrain drivetrain; 35 | 36 | /// A vector of callbacks to be called with the intermediate solution and a 37 | /// user-specified handle at every iteration of the solver. 38 | std::vector> 39 | callbacks; 40 | }; 41 | 42 | } // namespace trajopt 43 | -------------------------------------------------------------------------------- /trajoptlib/examples/differential.rs: -------------------------------------------------------------------------------- 1 | use std::f64; 2 | 3 | use trajoptlib::{DifferentialDrivetrain, DifferentialTrajectoryGenerator}; 4 | 5 | fn main() { 6 | let drivetrain = DifferentialDrivetrain { 7 | // kg 8 | mass: 45.0, 9 | // kg-m² 10 | moi: 6.0, 11 | // m 12 | wheel_radius: 0.05, 13 | // motor rpm / 60 * 2pi / gear ratio 14 | wheel_max_angular_velocity: 6000.0 * 2.0 * f64::consts::PI / (60.0 * 6.5), 15 | // N-m 16 | wheel_max_torque: 0.9, 17 | // unitless 18 | wheel_cof: 1.5, 19 | // m 20 | trackwidth: 0.5588, 21 | }; 22 | 23 | let mut generator = DifferentialTrajectoryGenerator::new(); 24 | 25 | generator.add_callback(|trajectory, handle| println!("{:?}: handle {}", trajectory, handle)); 26 | generator.set_drivetrain(&drivetrain); 27 | generator.set_bumpers(0.65, 0.65, 0.65, 0.65); 28 | 29 | generator.pose_wpt(0, 0.0, 0.0, 0.0); 30 | generator.pose_wpt(1, 1.0, 0.0, 0.0); 31 | 32 | generator.wpt_angular_velocity_max_magnitude(0, 0.0); 33 | generator.wpt_angular_velocity_max_magnitude(1, 0.0); 34 | // generator.sgmt_keep_out_circle(0, 1, 0.5, 0.1, 0.2); 35 | 36 | generator.set_control_interval_counts(vec![40]); 37 | 38 | println!("{:?}", generator.generate(true, 0)); 39 | } 40 | -------------------------------------------------------------------------------- /.github/workflows/cache-cleanup.yml: -------------------------------------------------------------------------------- 1 | # Modifed from https://github.com/actions/cache/blob/main/tips-and-workarounds.md#force-deletion-of-caches-overriding-default-cache-eviction-policy 2 | name: Clean up unused caches 3 | on: 4 | pull_request_target: 5 | types: 6 | - closed 7 | 8 | jobs: 9 | cleanup: 10 | runs-on: ubuntu-slim 11 | permissions: 12 | # `actions:write` permission is required to delete caches 13 | # See also: https://docs.github.com/en/rest/actions/cache?apiVersion=2022-11-28#delete-a-github-actions-cache-for-a-repository-using-a-cache-id 14 | actions: write 15 | contents: read 16 | steps: 17 | - name: Clean up caches 18 | run: | 19 | ## Setting this to not fail the workflow while deleting cache keys. 20 | set +e 21 | for i in {1..20} 22 | do 23 | cacheIdsForPR=$(gh cache list -R $REPO -r $BRANCH -L 100 --sort size_in_bytes | cut -f 1 ) 24 | for cacheId in $cacheIdsForPR 25 | do 26 | gh cache delete $cacheId -R $REPO 27 | sleep 0.1 28 | done 29 | done 30 | env: 31 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 32 | REPO: ${{ github.repository }} 33 | BRANCH: refs/pull/${{ github.event.pull_request.number }}/merge 34 | -------------------------------------------------------------------------------- /.github/workflows/fix_compile_commands.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | import json 5 | 6 | 7 | def main(): 8 | parser = argparse.ArgumentParser( 9 | description="Fix compile_commands.json generated by Gradle" 10 | ) 11 | parser.add_argument("filename", help="compile_commands.json location") 12 | cmd_args = parser.parse_args() 13 | 14 | # Read JSON 15 | with open(cmd_args.filename) as f: 16 | data = json.load(f) 17 | 18 | for obj in data: 19 | out_args = [] 20 | 21 | # Filter out -isystem flags that cause false positives 22 | iter_args = iter(obj["arguments"]) 23 | for arg in iter_args: 24 | if arg == "-isystem": 25 | next_arg = next(iter_args) 26 | 27 | # /usr/lib/gcc/x86_64-pc-linux-gnu/13.2.1/include/xmmintrin.h:54:1: 28 | # error: conflicting types for '_mm_prefetch' [clang-diagnostic-error] 29 | if not next_arg.startswith("/usr/lib/gcc/"): 30 | out_args += ["-isystem", next_arg] 31 | else: 32 | out_args.append(arg) 33 | 34 | obj["arguments"] = out_args 35 | 36 | # Write JSON 37 | with open(cmd_args.filename, "w") as f: 38 | json.dump(data, f, indent=2) 39 | 40 | 41 | if __name__ == "__main__": 42 | main() 43 | -------------------------------------------------------------------------------- /src/assets/Angle.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { SvgIcon as MuiSvgIcon, SvgIconProps, styled } from "@mui/material"; 3 | const SvgIcon = styled(MuiSvgIcon, { 4 | name: "MopeimIcon", 5 | shouldForwardProp: (prop) => prop !== "fill" 6 | })(() => ({ 7 | fill: "none", 8 | stroke: "currentColor", 9 | strokeLinecap: "round", 10 | strokeLinejoin: "round", 11 | strokeWidth: "2.25px" 12 | })); 13 | 14 | const Angle: React.FunctionComponent = (props) => { 15 | const cornerX = 4; 16 | const cornerY = 18; 17 | const endX = 20; 18 | const length = endX - cornerX; 19 | const angle = Math.PI / 3; 20 | const r = 10; 21 | return ( 22 | 42 | ); 43 | }; 44 | export default Angle; 45 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import react from "@vitejs/plugin-react"; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig(async () => ({ 6 | plugins: [react()], 7 | 8 | // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build` 9 | // prevent vite from obscuring rust errors 10 | clearScreen: false, 11 | // tauri expects a fixed port, fail if that port is not available 12 | server: { 13 | port: 1420, 14 | strictPort: true, 15 | watch: { 16 | ignored: ["target/**"] 17 | } 18 | }, 19 | // to make use of `TAURI_DEBUG` and other env variables 20 | // https://tauri.studio/v1/api/config#buildconfig.beforedevcommand 21 | envPrefix: ["VITE_", "TAURI_"], 22 | build: { 23 | // Tauri supports es2021 24 | target: process.env.TAURI_PLATFORM == "windows" ? "chrome105" : "safari14", 25 | // don't minify for debug builds 26 | minify: !process.env.TAURI_DEBUG ? "esbuild" : false, 27 | // produce sourcemaps for debug builds 28 | sourcemap: !!process.env.TAURI_DEBUG, 29 | rollupOptions: { 30 | onwarn(warning, defaultHandler) { 31 | if ( 32 | warning.code === "MODULE_LEVEL_DIRECTIVE" && 33 | warning.message.includes("use client") 34 | ) { 35 | return; 36 | } 37 | } 38 | } 39 | } 40 | })); 41 | -------------------------------------------------------------------------------- /src/components/field/svg/FieldEventMarkerAddLayer.tsx: -------------------------------------------------------------------------------- 1 | import { Component } from "react"; 2 | import { doc } from "../../../document/DocumentManager"; 3 | 4 | import { observer } from "mobx-react"; 5 | 6 | type Props = object; 7 | 8 | type State = object; 9 | 10 | class FieldConstraintsAddLayer extends Component { 11 | state = {}; 12 | 13 | render() { 14 | const activePath = doc.pathlist.activePath; 15 | const waypoints = activePath.params.waypoints; 16 | return ( 17 | <> 18 | {/* Draw circles on each waypoint */} 19 | {waypoints.map((point, index) => { 20 | return ( 21 | { 31 | const newMarker = activePath.addEventMarker(); 32 | 33 | newMarker.from.setTarget({ uuid: point.uuid }); 34 | // TODO set direct timestamp if trajectory not stale 35 | doc.setSelectedSidebarItem(newMarker); 36 | }} 37 | > 38 | ); 39 | })} 40 | 41 | ); 42 | } 43 | } 44 | export default observer(FieldConstraintsAddLayer); 45 | -------------------------------------------------------------------------------- /src/components/config/robotconfig/MotorCurves.tsx: -------------------------------------------------------------------------------- 1 | export type MotorCurve = { name: string; vmax: number; kt: number }; 2 | 3 | // Data here is from Recalc's motor data at 4 | // https://github.com/tervay/recalc/blob/main/src/common/models/data/motors.json 5 | // kt = stallTorque/stallCurrent 6 | 7 | // 1 rpm in rad/s 8 | const rpm = (2 * Math.PI) / 60; 9 | export const MotorCurves: Record< 10 | | "Falcon500" 11 | | "FalconFOC" 12 | | "NEO" 13 | | "NEOVortex" 14 | | "KrakenX60" 15 | | "KrakenFOC" 16 | | "CIM", 17 | MotorCurve 18 | > = { 19 | Falcon500: { 20 | name: "Falcon 500", 21 | vmax: 6380 * rpm, 22 | kt: 4.69 / 257.0 23 | }, 24 | FalconFOC: { 25 | name: "Falcon with FOC", 26 | vmax: 6080 * rpm, 27 | kt: 5.84 / 304 28 | }, 29 | NEO: { 30 | name: "NEO", 31 | vmax: 5880 * rpm, 32 | kt: 3.28 / 181 33 | }, 34 | NEOVortex: { 35 | name: "NEO Vortex", 36 | vmax: 6784 * rpm, 37 | kt: 3.6 / 211 38 | }, 39 | KrakenX60: { 40 | name: "Kraken X60", 41 | vmax: 6000 * rpm, 42 | kt: 7.09 / 366 43 | }, 44 | KrakenFOC: { 45 | name: "Kraken with FOC", 46 | vmax: 5800 * rpm, 47 | kt: 9.37 / 483 48 | }, 49 | CIM: { 50 | name: "CIM", 51 | vmax: 5330 * rpm, 52 | kt: 2.41 / 131 53 | } 54 | }; 55 | 56 | export function maxTorqueCurrentLimited(kt: number, limitAmps: number) { 57 | return kt * limitAmps; 58 | } 59 | -------------------------------------------------------------------------------- /choreolib/src/main/native/include/choreo/trajectory/TrajectorySample.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Choreo contributors 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | namespace choreo { 12 | 13 | /// Enforce equality operators on trajectory sample types. 14 | template 15 | concept EqualityComparable = requires(const T& a, const T& b) { 16 | { a == b } -> std::convertible_to; 17 | { a != b } -> std::convertible_to; 18 | }; 19 | 20 | /// A concept representing a single robot sample in a Trajectory. 21 | template 22 | concept TrajectorySample = 23 | EqualityComparable && 24 | requires(T t, units::second_t time, T tother, int year) { 25 | { t.GetTimestamp() } -> std::same_as; 26 | { t.GetPose() } -> std::same_as; 27 | { t.GetChassisSpeeds() } -> std::same_as; 28 | { t.OffsetBy(time) } -> std::same_as; 29 | { t.Interpolate(tother, time) } -> std::same_as; 30 | // FIXME: This works around a roboRIO GCC internal compiler error; it 31 | // can't be fully generic 32 | { t.template Flipped<2022>() } -> std::same_as; 33 | { t.template Flipped<2023>() } -> std::same_as; 34 | { t.template Flipped<2024>() } -> std::same_as; 35 | }; 36 | 37 | } // namespace choreo 38 | -------------------------------------------------------------------------------- /trajoptlib/test/src/geometry/pose2d_test.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) TrajoptLib contributors 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | using Catch::Matchers::WithinAbs; 10 | 11 | inline constexpr double deg2rad(double deg) { 12 | return deg * std::numbers::pi / 180.0; 13 | } 14 | 15 | TEST_CASE("Pose2d - rotate_by", "[Pose2d]") { 16 | constexpr double x = 1.0; 17 | constexpr double y = 2.0; 18 | const trajopt::Pose2d initial{x, y, std::numbers::pi / 4}; 19 | 20 | const trajopt::Rotation2d rotation{5.0 * std::numbers::pi / 180.0}; 21 | const auto rotated = initial.rotate_by(rotation); 22 | 23 | // Translation is rotated by CCW rotation matrix 24 | double c = rotation.cos(); 25 | double s = rotation.sin(); 26 | CHECK(rotated.x() == c * x - s * y); 27 | CHECK(rotated.y() == s * x + c * y); 28 | CHECK_THAT( 29 | rotated.rotation().degrees(), 30 | WithinAbs(initial.rotation().degrees() + rotation.degrees(), 1e-9)); 31 | } 32 | 33 | TEST_CASE("Pose2d - Constexpr", "[Pose2d]") { 34 | constexpr trajopt::Pose2d default_constructed; 35 | constexpr trajopt::Pose2d translation_rotation{ 36 | trajopt::Translation2d{0.0, 1.0}, trajopt::Rotation2d{}}; 37 | 38 | static_assert(default_constructed.x() == 0.0); 39 | static_assert(translation_rotation.y() == 1.0); 40 | } 41 | -------------------------------------------------------------------------------- /trajoptlib/examples/swerve.rs: -------------------------------------------------------------------------------- 1 | use trajoptlib::{SwerveDrivetrain, SwerveTrajectoryGenerator, Translation2d}; 2 | 3 | fn main() { 4 | let drivetrain = SwerveDrivetrain { 5 | // kg 6 | mass: 45.0, 7 | // kg-m² 8 | moi: 6.0, 9 | // m 10 | wheel_radius: 0.04, 11 | // rad/s 12 | wheel_max_angular_velocity: 70.0, 13 | // N-m 14 | wheel_max_torque: 2.0, 15 | // unitless 16 | wheel_cof: 1.5, 17 | // m 18 | modules: vec![ 19 | Translation2d { x: 0.6, y: 0.6 }, 20 | Translation2d { x: 0.6, y: -0.6 }, 21 | Translation2d { x: -0.6, y: 0.6 }, 22 | Translation2d { x: -0.6, y: -0.6 }, 23 | ], 24 | }; 25 | 26 | let mut generator = SwerveTrajectoryGenerator::new(); 27 | 28 | generator.add_callback(|trajectory, handle| println!("{:?}: handle {}", trajectory, handle)); 29 | generator.set_drivetrain(&drivetrain); 30 | generator.set_bumpers(0.65, 0.65, 0.65, 0.65); 31 | 32 | generator.pose_wpt(0, 0.0, 0.0, 0.0); 33 | generator.pose_wpt(1, 1.0, 0.0, 0.0); 34 | 35 | generator.wpt_angular_velocity_max_magnitude(0, 0.0); 36 | generator.wpt_angular_velocity_max_magnitude(1, 0.0); 37 | generator.sgmt_keep_out_circle(0, 1, 0.5, 0.1, 0.2); 38 | 39 | generator.set_control_interval_counts(vec![40]); 40 | 41 | println!("{:?}", generator.generate(true, 0)); 42 | } 43 | -------------------------------------------------------------------------------- /trajoptlib/CMakePresets.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "cmakeMinimumRequired": { 4 | "major": 3, 5 | "minor": 21, 6 | "patch": 0 7 | }, 8 | "configurePresets": [ 9 | { 10 | "name": "default", 11 | "displayName": "Default Config", 12 | "description": "Default build using Ninja generator", 13 | "binaryDir": "build", 14 | "generator": "Ninja", 15 | "cacheVariables": {} 16 | }, 17 | { 18 | "name": "with-sccache", 19 | "displayName": "", 20 | "description": "Ninja config with sccache", 21 | "generator": "Ninja", 22 | "binaryDir": "build", 23 | "cacheVariables": { 24 | "CMAKE_C_COMPILER_LAUNCHER": "sccache", 25 | "CMAKE_CXX_COMPILER_LAUNCHER": "sccache" 26 | } 27 | }, 28 | { 29 | "name": "with-examples", 30 | "displayName": "Default config to build examples", 31 | "description": "Default examples build using Ninja generator", 32 | "binaryDir": "build", 33 | "generator": "Ninja", 34 | "cacheVariables": { 35 | "BUILD_EXAMPLES": "ON" 36 | } 37 | }, 38 | { 39 | "name": "with-examples-and-sccache", 40 | "displayName": "", 41 | "description": "Ninja config with sccache", 42 | "generator": "Ninja", 43 | "binaryDir": "build", 44 | "inherits": "with-sccache", 45 | "cacheVariables": { 46 | "BUILD_EXAMPLES": "ON" 47 | } 48 | } 49 | ] 50 | } 51 | -------------------------------------------------------------------------------- /choreolib/py/_choreo_version.py: -------------------------------------------------------------------------------- 1 | import re 2 | import subprocess 3 | 4 | 5 | def get_version(): 6 | proc = subprocess.run( 7 | [ 8 | "git", 9 | "describe", 10 | "--tags", 11 | ], 12 | check=True, 13 | encoding="utf-8", 14 | stdout=subprocess.PIPE, 15 | ) 16 | # If there are no tags, default to 0.0.0 17 | if proc.returncode: 18 | return "0.0.0" 19 | else: 20 | match = re.search( 21 | r""" 22 | ^v 23 | ( [0-9]+\.[0-9]+\.[0-9]+ ) # group 1 semver 24 | ( -([0-9]+) )? # group 3 commits since last tag 25 | ( -([a-z\d]+) )? # group 5 commit hash (if group 2) or alpha or beta (if not group 2) 26 | ( -([0-9]+) )? # group 7 alpha or beta number 27 | """, 28 | proc.stdout.rstrip(), 29 | re.X, 30 | ) 31 | 32 | version = match.group(1) 33 | 34 | # Choreo uses tags like v2025.0.0-alpha-1. We turn that into 2025.0.0a1 35 | # to comply with https://peps.python.org/pep-0440/#public-version-identifiers. 36 | # For dev: version number: .dev<# commits since tag> 37 | if match.group(2): 38 | version += f".dev{match.group(3)}" 39 | elif match.group(4): 40 | version += match.group(5).replace("alpha", "a").replace( 41 | "beta", "b" 42 | ) + match.group(7) 43 | return version 44 | -------------------------------------------------------------------------------- /src-tauri/src/main.rs: -------------------------------------------------------------------------------- 1 | //! Choreo (_Constraint-Honoring Omnidirectional Route Editor and Optimizer_, 2 | //! pronounced like choreography) is a graphical tool for planning 3 | //! time-optimized trajectories for autonomous mobile robots in the FIRST 4 | //! Robotics Competition. 5 | 6 | #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] 7 | 8 | mod api; 9 | mod built; 10 | mod logging; 11 | mod tauri; 12 | 13 | use std::fs; 14 | 15 | use choreo_core::generation::remote::{RemoteArgs, remote_generate_child}; 16 | 17 | fn main() { 18 | let args = std::env::args().collect::>(); 19 | if args.len() > 2 { 20 | panic!("Unsupoorted arguments: {args:?}"); 21 | } 22 | 23 | if let Some(arg) = args.get(1) { 24 | if let Ok(remote_args) = RemoteArgs::from_content(arg) { 25 | tracing_subscriber::fmt() 26 | .with_max_level(tracing::Level::ERROR) 27 | .event_format(logging::CompactFormatter { ansicolor: false }) 28 | .init(); 29 | remote_generate_child(remote_args); 30 | } else { 31 | match fs::canonicalize(arg) { 32 | Ok(path) => { 33 | tauri::run_tauri(Some(path)); 34 | } 35 | Err(e) => { 36 | panic!("Failed to canonicalize {arg} : {e:?}"); 37 | } 38 | } 39 | } 40 | } else { 41 | tauri::run_tauri(None); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) Choreo contributors 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | 5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 | 7 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | 9 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 10 | 11 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 12 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import typescriptEslint from "@typescript-eslint/eslint-plugin"; 2 | import globals from "globals"; 3 | import tsParser from "@typescript-eslint/parser"; 4 | import path from "node:path"; 5 | import { fileURLToPath } from "node:url"; 6 | import js from "@eslint/js"; 7 | import { FlatCompat } from "@eslint/eslintrc"; 8 | 9 | const __filename = fileURLToPath(import.meta.url); 10 | const __dirname = path.dirname(__filename); 11 | const compat = new FlatCompat({ 12 | baseDirectory: __dirname, 13 | recommendedConfig: js.configs.recommended, 14 | allConfig: js.configs.all 15 | }); 16 | 17 | export default [ 18 | ...compat.extends( 19 | "eslint:recommended", 20 | "plugin:@typescript-eslint/recommended" 21 | ), 22 | { 23 | plugins: { 24 | "@typescript-eslint": typescriptEslint 25 | }, 26 | 27 | languageOptions: { 28 | globals: { 29 | ...globals.node 30 | }, 31 | 32 | parser: tsParser, 33 | ecmaVersion: "latest", 34 | sourceType: "module" 35 | }, 36 | 37 | rules: { 38 | "@typescript-eslint/no-unused-vars": [ 39 | "error", 40 | { 41 | argsIgnorePattern: ".*?", 42 | varsIgnorePattern: "^_" 43 | } 44 | ], 45 | 46 | "@typescript-eslint/no-explicit-any": "off", 47 | "linebreak-style": ["error", "unix"], 48 | "no-unused-vars": "off", 49 | "prefer-const": ["error"], 50 | semi: ["error", "always"] 51 | } 52 | } 53 | ]; 54 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Docs 2 | 3 | on: [pull_request, push] 4 | 5 | permissions: 6 | contents: write 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-slim 11 | steps: 12 | - uses: actions/checkout@v5 13 | 14 | - name: Configure Git Credentials 15 | run: | 16 | git config user.name github-actions[bot] 17 | git config user.email 41898282+github-actions[bot]@users.noreply.github.com 18 | 19 | - uses: actions/setup-python@v6 20 | with: 21 | python-version: 3.14 22 | 23 | - run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV 24 | 25 | - uses: actions/cache@v3 26 | with: 27 | key: mkdocs-material-${{ env.cache_id }} 28 | path: .cache 29 | restore-keys: | 30 | mkdocs-material- 31 | 32 | - run: pip install -r docs/requirements.txt 33 | 34 | - name: Install Doxygen 35 | run: | 36 | sudo apt-get update -q 37 | sudo apt-get install -y doxygen 38 | 39 | - uses: actions/setup-java@v4 40 | with: 41 | distribution: "temurin" 42 | java-version: 17 43 | 44 | - name: Copy API docs 45 | run: ./make-docs.sh 46 | 47 | - run: mkdocs build --dirty 48 | if: github.ref != 'refs/heads/main' && !startsWith(github.ref, 'refs/tags/v') 49 | 50 | - run: mkdocs gh-deploy --dirty --force 51 | if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') 52 | -------------------------------------------------------------------------------- /src/Body.tsx: -------------------------------------------------------------------------------- 1 | import { Component } from "react"; 2 | import { observer } from "mobx-react"; 3 | import Navbar from "./components/navbar/Navbar"; 4 | import Field from "./components/field/Field"; 5 | import Sidebar from "./components/sidebar/Sidebar"; 6 | import AppMenu from "./AppMenu"; 7 | import PathAnimationPanel from "./components/field/PathAnimationPanel"; 8 | 9 | type Props = object; 10 | 11 | type State = object; 12 | 13 | class Body extends Component { 14 | state = {}; 15 | 16 | render() { 17 | return ( 18 | <> 19 |
20 |
21 | 22 | 31 | 32 | 40 | 41 | 42 | 43 | 44 | 45 |
46 |
47 | 48 | ); 49 | } 50 | } 51 | export default observer(Body); 52 | -------------------------------------------------------------------------------- /src/components/navbar/Navbar.module.css: -------------------------------------------------------------------------------- 1 | .Container { 2 | background-color: var(--background-dark-gray); 3 | color: white; 4 | display: flex; 5 | padding-right: 0; 6 | font-size: 1rem; 7 | width: 100%; 8 | border: 0; 9 | align-items: center; 10 | flex-direction: row; 11 | justify-content: flex-start; 12 | min-height: var(--top-nav-height); 13 | height: var(--top-nav-height); 14 | border-bottom: thin solid var(--divider-gray); 15 | overflow-x: scroll; 16 | overflow-y: hidden; 17 | } 18 | 19 | .PathChooserContainer { 20 | display: flex; 21 | align-items: center; 22 | font-size: 1rem; 23 | border: 0; 24 | background: "var(--background-dark-gray)"; 25 | border-radius: 0 10px 0 0; 26 | } 27 | 28 | .PathSelectControl { 29 | background-color: #7d73e7 !important; 30 | border-width: 0 !important; 31 | border-radius: 10 !important; 32 | width: 200px !important; 33 | height: 20px !important; 34 | } 35 | 36 | .PathOptionContainer { 37 | background-color: black !important; 38 | color: "white"; 39 | margin-left: 4; 40 | margin-right: 0; 41 | margin-bottom: 4; 42 | width: 192; 43 | min-height: 20px !important; 44 | height: 20px !important; 45 | border-radius: 6px; 46 | cursor: default; 47 | } 48 | 49 | .PathOptionRenameInput { 50 | background-color: transparent; 51 | border: none; 52 | color: white; 53 | align-self: center; 54 | border-bottom: solid white 2px; 55 | z-index: top; 56 | } 57 | 58 | .ToggleGroup { 59 | margin-inline: 0.3rem; 60 | } 61 | -------------------------------------------------------------------------------- /trajoptlib/include/trajopt/constraint/detail/line_point_squared_distance.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) TrajoptLib contributors 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | #include "trajopt/geometry/translation2.hpp" 8 | 9 | namespace trajopt::detail { 10 | 11 | // https://www.desmos.com/calculator/cqmc1tjtsv 12 | template 13 | decltype(auto) line_point_squared_distance(const Translation2& line_start, 14 | const Translation2& line_end, 15 | const Translation2& point) { 16 | using R = decltype(std::declval() + std::declval()); 17 | 18 | auto max = [](R a, R b) { 19 | return +0.5 * (1 + slp::sign(b - a)) * (b - a) + a; 20 | }; 21 | auto min = [](R a, R b) { 22 | return -0.5 * (1 + slp::sign(b - a)) * (b - a) + b; 23 | }; 24 | auto lerp = [](R a, R b, R t) { return a + t * (b - a); }; 25 | 26 | Translation2 l{line_end.x() - line_start.x(), 27 | line_end.y() - line_start.y()}; 28 | Translation2 v{point.x() - line_start.x(), point.y() - line_start.y()}; 29 | 30 | auto t = v.dot(l) / l.squared_norm(); 31 | auto t_bounded = max(min(t, 1), 0); // NOLINT(build/include_what_you_use) 32 | 33 | Translation2 i{lerp(line_start.x(), line_end.x(), t_bounded), 34 | lerp(line_start.y(), line_end.y(), t_bounded)}; 35 | return (i - point).squared_norm(); 36 | } 37 | 38 | } // namespace trajopt::detail 39 | -------------------------------------------------------------------------------- /choreolib/src/main/java/choreo/util/ChoreoArrayUtil.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) Choreo contributors 2 | 3 | package choreo.util; 4 | 5 | import java.util.function.BiFunction; 6 | 7 | /** A Choreo Internal utility class for array operations. */ 8 | public class ChoreoArrayUtil { 9 | /** 10 | * Checks two double arrays for equality with the given function. This returns true 11 | * if: 12 | * 13 | *
    14 | *
  • Either both arrays are null, or 15 | *
  • Neither is null, the arrays are the same length, and the given function returns true for 16 | * all same-index pairs of elements in the arrays. 17 | *
18 | * 19 | * @param arr1 The first array 20 | * @param arr2 The second array 21 | * @param check The function to compare elements. 22 | * @return Whether the arrays are equal. 23 | */ 24 | public static boolean zipEquals( 25 | double[] arr1, double[] arr2, BiFunction check) { 26 | if (arr1 == null && arr2 == null) { 27 | return true; 28 | } 29 | if (arr1 != null && arr2 == null) { 30 | return false; 31 | } 32 | if (arr1 == null && arr2 != null) { 33 | return false; 34 | } 35 | // arr1 and arr2 both not null 36 | if (arr1.length != arr2.length) { 37 | return false; 38 | } 39 | for (int i = 0; i < arr1.length; ++i) { 40 | if (!check.apply(arr1[i], arr2[i])) { 41 | return false; 42 | } 43 | } 44 | return true; 45 | } 46 | 47 | /** Factory class. */ 48 | private ChoreoArrayUtil() {} 49 | } 50 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Trigger a Release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | tag: 7 | description: "Tag Name" 8 | required: true 9 | default: "v0.0.0-test" 10 | 11 | jobs: 12 | update-version: 13 | name: "Update Version" 14 | runs-on: ubuntu-24.04 15 | container: ${{ matrix.container }} 16 | steps: 17 | - name: Generate a token 18 | id: generate-token 19 | uses: actions/create-github-app-token@v1 20 | with: 21 | app-id: ${{ secrets.CHOREOLIB_RELEASE_APP_ID }} 22 | private-key: ${{ secrets.CHOREOLIB_RELEASE_APP_PRIVATE_KEY }} 23 | 24 | - uses: actions/checkout@v5 25 | with: 26 | submodules: true 27 | fetch-depth: 0 28 | token: ${{ steps.generate-token.outputs.token }} 29 | 30 | - name: Install Python dependencies 31 | run: pip install tomlkit 32 | 33 | - uses: pnpm/action-setup@v4 34 | with: 35 | version: 10 36 | 37 | - uses: actions/setup-node@v4 38 | with: 39 | node-version: 18.x 40 | cache: "pnpm" 41 | 42 | - run: pnpm install prettier@3.7.4 43 | - name: Update version 44 | run: | 45 | ./update_version.py ${{ github.event.inputs.tag }} 46 | pnpm run fmtJs 47 | cargo update -w 48 | - uses: stefanzweifel/git-auto-commit-action@v5 49 | with: 50 | commit_message: Update version numbers to ${{ github.event.inputs.tag }} 51 | tagging_message: ${{ github.event.inputs.tag }} 52 | -------------------------------------------------------------------------------- /docs/contributing/release-process.md: -------------------------------------------------------------------------------- 1 | # Release Process 2 | 3 | Let TAG be the new tag name (e.g., `v2025.0.0-beta-1`) and VERSION be the 4 | version (e.g., `2025.0.0-beta-1`). 5 | 6 | ## Dependencies 7 | 8 | * Python 9 | * Git 10 | * GitHub CLI 11 | 12 | ## Manual setup 13 | 14 | ```bash 15 | git clone git@github.com:SleipnirGroup/Choreo 16 | git clone git@github.com:SleipnirGroup/ChoreoLib 17 | ``` 18 | 19 | Update the vendordep JSON in ChoreoLib/dep. 20 | 21 | 1. Update the Choreo version keys to VERSION 22 | 2. Update the frcYear key if needed 23 | 24 | ## Choreo Release 25 | 26 | ```bash 27 | pushd Choreo 28 | 29 | # Bump versions 30 | ./update_version.py TAG 31 | 32 | # Format .json files updated by update_version.py 33 | pnpm run fmtJs 34 | 35 | # Update Cargo.lock 36 | cargo update -w 37 | 38 | # Commit and push 39 | git commit -a -m "Bump version to TAG" 40 | git push 41 | 42 | # Tag release 43 | git tag TAG 44 | git push origin TAG 45 | 46 | popd 47 | ``` 48 | 49 | Wait for the Choreo release CI to complete before continuing. 50 | 51 | ## ChoreoLib Release 52 | 53 | `[]` in the following script should be the ChoreoLib workflow run ID 54 | from the previous Choreo tag push. 55 | 56 | ```bash 57 | pushd Choreo 58 | 59 | # Download ChoreoLib maven artifacts 60 | gh run download [] -n ChoreoLib-Maven 61 | 62 | # Place maven artifacts 63 | cp -r maven/development/choreo/* ../ChoreoLib/dep/choreo 64 | rm -r maven 65 | 66 | popd 67 | pushd ChoreoLib 68 | 69 | # Commit and push 70 | git commit -a -m "Add ChoreoLib VERSION" 71 | git push 72 | 73 | popd 74 | ``` 75 | -------------------------------------------------------------------------------- /choreolib/src/main/java/choreo/trajectory/TrajectorySample.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) Choreo contributors 2 | 3 | package choreo.trajectory; 4 | 5 | import edu.wpi.first.math.geometry.Pose2d; 6 | import edu.wpi.first.math.interpolation.Interpolatable; 7 | import edu.wpi.first.math.kinematics.ChassisSpeeds; 8 | import edu.wpi.first.util.struct.StructSerializable; 9 | 10 | /** 11 | * The generic interface for a sample in a trajectory. 12 | * 13 | * @param Derived sample type. 14 | */ 15 | public interface TrajectorySample> 16 | extends Interpolatable, StructSerializable { 17 | /** 18 | * Returns the timestamp of this sample. 19 | * 20 | * @return the timestamp of this sample. 21 | */ 22 | double getTimestamp(); 23 | 24 | /** 25 | * Returns the pose at this sample. 26 | * 27 | * @return the pose at this sample. 28 | */ 29 | Pose2d getPose(); 30 | 31 | /** 32 | * Returns the field-relative chassis speeds of this sample. 33 | * 34 | * @return the field-relative chassis speeds of this sample. 35 | */ 36 | ChassisSpeeds getChassisSpeeds(); 37 | 38 | /** 39 | * Returns this sample, mirrored across the field midline. 40 | * 41 | * @return this sample, mirrored across the field midline. 42 | */ 43 | Self flipped(); 44 | 45 | /** 46 | * Returns this sample, offset by the given timestamp. 47 | * 48 | * @param timestampOffset the offset to apply to the timestamp. 49 | * @return this sample, offset by the given timestamp. 50 | */ 51 | Self offsetBy(double timestampOffset); 52 | } 53 | -------------------------------------------------------------------------------- /choreolib/src/main/native/include/choreo/util/Map.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) Choreo contributors 2 | 3 | #pragma once 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace choreo::util { 11 | 12 | /// A compile-time associative container. 13 | /// 14 | /// @tparam Key The key type. 15 | /// @tparam Value The value type. 16 | /// @tparam Size The number of elements in the container. 17 | template 18 | class Map { 19 | public: 20 | /// Construct the map from an array of key-value pairs. 21 | /// 22 | /// @param data The key-value pairs. 23 | explicit constexpr Map(const std::array, Size>& data) 24 | : data{data} {} 25 | 26 | /// Returns the value associated with the given key. 27 | /// 28 | /// @param key The key. 29 | /// @return The value. 30 | /// @throws std::range_error if the key wasn't found. 31 | [[nodiscard]] 32 | constexpr const Value& at(const Key& key) const { 33 | if (const auto it = 34 | std::find_if(std::begin(data), std::end(data), 35 | [&key](const auto& v) { return v.first == key; }); 36 | it != std::end(data)) { 37 | return it->second; 38 | } else { 39 | throw std::range_error("Key not found"); 40 | } 41 | } 42 | 43 | private: 44 | std::array, Size> data; 45 | }; 46 | 47 | template 48 | Map(const std::array, Size>&) -> Map; 49 | 50 | } // namespace choreo::util 51 | -------------------------------------------------------------------------------- /src/assets/Mass.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { SvgIcon as MuiSvgIcon, SvgIconProps, styled } from "@mui/material"; 3 | const SvgIcon = styled(MuiSvgIcon, { 4 | name: "MopeimIcon", 5 | shouldForwardProp: (prop) => prop !== "fill" 6 | })(() => ({ 7 | fill: "currentColor", 8 | stroke: "currentColor", 9 | strokeLinecap: "round", 10 | strokeLinejoin: "round", 11 | strokeWidth: "2.25px" 12 | })); 13 | 14 | const Mass: React.FunctionComponent = (props) => { 15 | return ( 16 | 28 | ); 29 | }; 30 | export default Mass; 31 | -------------------------------------------------------------------------------- /src/components/config/variables/ExpressionsConfigPanel.tsx: -------------------------------------------------------------------------------- 1 | import { Divider } from "@mui/material"; 2 | import { observer } from "mobx-react"; 3 | import { Component } from "react"; 4 | import { doc } from "../../../document/DocumentManager"; 5 | import PoseVariablesConfigPanel from "./PoseVariablesConfigPanel"; 6 | import VariablesConfigPanel, { 7 | GeneralVariableAddPanel 8 | } from "./VariablesConfigPanel"; 9 | 10 | type Props = object; 11 | 12 | type State = object; 13 | 14 | class ExpressionsConfigPanel extends Component { 15 | rowGap = 4; 16 | 17 | render() { 18 | doc.variables.expressions.keys(); 19 | return ( 20 |
32 | 33 | {doc.variables.poses.size > 0 && ( 34 | 35 | POSE VARIABLES 36 | 37 | )} 38 | 39 | 40 | ADD NEW VARIABLE 41 | 42 | 43 |
44 | ); 45 | } 46 | } 47 | export default observer(ExpressionsConfigPanel); 48 | -------------------------------------------------------------------------------- /src/components/config/variables/VariableRenamingInput.tsx: -------------------------------------------------------------------------------- 1 | import { Input } from "@mui/material"; 2 | import { observer } from "mobx-react"; 3 | import { useState } from "react"; 4 | import styles from "../../input/InputList.module.css"; 5 | 6 | type Props = { 7 | name: string; 8 | setName: (name: string) => void; 9 | validateName: (name: string) => boolean; 10 | width?: string; 11 | }; 12 | function VariableRenamingInput(props: Props) { 13 | // default usage is that the props.name already exists as a variable 14 | function submit(name: string) { 15 | if (name !== null && props.validateName(name)) { 16 | props.setName(name); 17 | } else { 18 | setNewName(props.name); 19 | setValid(props.validateName(props.name)); 20 | } 21 | } 22 | const [newName, setNewName] = useState(props.name); 23 | const [valid, setValid] = useState(props.validateName(props.name)); 24 | return ( 25 | { 32 | setNewName(e.currentTarget.value); 33 | setValid(props.validateName(e.currentTarget.value)); 34 | }} 35 | error={!valid} 36 | onKeyDown={(e) => { 37 | if (e.key == "Enter") { 38 | submit(e.currentTarget.value); 39 | e.currentTarget.blur(); 40 | } 41 | }} 42 | onBlur={(e) => submit(e.currentTarget.value)} 43 | > 44 | ); 45 | } 46 | export default observer(VariableRenamingInput); 47 | -------------------------------------------------------------------------------- /trajoptlib/LICENSE.txt: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2022, Justin Babilino 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /test-jsons/project/0/swerve.chor: -------------------------------------------------------------------------------- 1 | { 2 | "name":"swerve", 3 | "version":0, 4 | "type":"Swerve", 5 | "variables":{ 6 | "expressions":{ 7 | "num":{ 8 | "dimension":"Number", 9 | "var":{ 10 | "exp":"0", 11 | "val":0.0 12 | } 13 | } 14 | }, 15 | "poses":{ 16 | "pose":{ 17 | "x":{ 18 | "exp":"0 m", 19 | "val":0.0 20 | }, 21 | "y":{ 22 | "exp":"0 m", 23 | "val":0.0 24 | }, 25 | "heading":{ 26 | "exp":"0 rad", 27 | "val":0.0 28 | } 29 | } 30 | } 31 | }, 32 | "config":{ 33 | "frontLeft":{ 34 | "x":{ 35 | "exp":"11 in", 36 | "val":0.2794 37 | }, 38 | "y":{ 39 | "exp":"11 in", 40 | "val":0.2794 41 | } 42 | }, 43 | "backLeft":{ 44 | "x":{ 45 | "exp":"-11 in", 46 | "val":-0.2794 47 | }, 48 | "y":{ 49 | "exp":"11 in", 50 | "val":0.2794 51 | } 52 | }, 53 | "mass":{ 54 | "exp":"150 lbs", 55 | "val":68.0388555 56 | }, 57 | "inertia":{ 58 | "exp":"6 kg m ^ 2", 59 | "val":6.0 60 | }, 61 | "gearing":{ 62 | "exp":"6.5", 63 | "val":6.5 64 | }, 65 | "radius":{ 66 | "exp":"2 in", 67 | "val":0.0508 68 | }, 69 | "vmax":{ 70 | "exp":"6000 RPM", 71 | "val":628.3185307179587 72 | }, 73 | "tmax":{ 74 | "exp":"1.2 N * m", 75 | "val":1.2 76 | }, 77 | "bumper":{ 78 | "front":{ 79 | "exp":"16 in", 80 | "val":0.4064 81 | }, 82 | "side":{ 83 | "exp":"16 in", 84 | "val":0.4064 85 | }, 86 | "back":{ 87 | "exp":"16 in", 88 | "val":0.4064 89 | } 90 | }, 91 | "differentialTrackWidth":{ 92 | "exp":"22 in", 93 | "val":0.5588 94 | } 95 | }, 96 | "generationFeatures":[] 97 | } 98 | -------------------------------------------------------------------------------- /choreolib/docs/doxygen-awesome-css/doxygen-awesome-sidebar-only-darkmode-toggle.css: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | 4 | Doxygen Awesome 5 | https://github.com/jothepro/doxygen-awesome-css 6 | 7 | MIT License 8 | 9 | Copyright (c) 2021 - 2023 jothepro 10 | 11 | Permission is hereby granted, free of charge, to any person obtaining a copy 12 | of this software and associated documentation files (the "Software"), to deal 13 | in the Software without restriction, including without limitation the rights 14 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | copies of the Software, and to permit persons to whom the Software is 16 | furnished to do so, subject to the following conditions: 17 | 18 | The above copyright notice and this permission notice shall be included in all 19 | copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | SOFTWARE. 28 | 29 | */ 30 | 31 | @media screen and (min-width: 768px) { 32 | 33 | #MSearchBox { 34 | width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)) - var(--searchbar-height) - 1px); 35 | } 36 | 37 | #MSearchField { 38 | width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)) - 66px - var(--searchbar-height)); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Choreo 2 | 3 | [![Discord](https://img.shields.io/discord/975739302933856277?color=%23738ADB&label=Join%20our%20Discord&logo=discord&logoColor=white)](https://discord.gg/ad2EEZZwsS) 4 | 5 | ![A screenshot of Choreo with an example path](./docs/media/readmeScreenshot.png) 6 | 7 | Choreo (_Constraint-Honoring Omnidirectional Route Editor and Optimizer_, pronounced like choreography) is a time-optimal drivetrain trajectory planner for the FIRST Robotics Competition. 8 | 9 | ## Download and Install 10 | 11 | Grab the latest release for your platform on the [releases](https://github.com/SleipnirGroup/Choreo/releases) page. 12 | 13 | ## Usage and Documentation 14 | 15 | Check out the [docs](https://choreo.autos), which covers installation, usage, building, and robot code integration with ChoreoLib. 16 | 17 | ## Repository directory structure 18 | 19 | - [src](src) - Choreo frontend 20 | - [src-tauri](src-tauri) - Choreo backend 21 | - [src-cli](src-cli) - Choreo command-line generation tool 22 | - [src-core](src-core) - Rust code shared between the app and the CLI 23 | - [choreolib](choreolib) - ChoreoLib: robot-side library for loading and following Choreo paths 24 | - [trajoptlib](trajoptlib) - TrajoptLib: library used by Choreo to generate time-optimal trajectories for FRC robots 25 | - [docs](docs) - Source for the [documentation](https://choreo.autos) 26 | 27 | ## Authors 28 | 29 | 30 | 31 | 32 | 33 | The 2024 field background was traced from the field renders provided by [MikLast](https://www.chiefdelphi.com/t/2024-crescendo-top-down-field-renders/447764). 34 | -------------------------------------------------------------------------------- /test-jsons/project/beta-6/swerve.chor: -------------------------------------------------------------------------------- 1 | { 2 | "name":"swerve", 3 | "version":"v2025.0.0", 4 | "type":"Swerve", 5 | "variables":{ 6 | "expressions":{ 7 | "num":{ 8 | "dimension":"Number", 9 | "var":{ 10 | "exp":"0", 11 | "val":0.0 12 | } 13 | } 14 | }, 15 | "poses":{ 16 | "pose":{ 17 | "x":{ 18 | "exp":"0 m", 19 | "val":0.0 20 | }, 21 | "y":{ 22 | "exp":"0 m", 23 | "val":0.0 24 | }, 25 | "heading":{ 26 | "exp":"0 rad", 27 | "val":0.0 28 | } 29 | } 30 | } 31 | }, 32 | "config":{ 33 | "frontLeft":{ 34 | "x":{ 35 | "exp":"11 in", 36 | "val":0.2794 37 | }, 38 | "y":{ 39 | "exp":"11 in", 40 | "val":0.2794 41 | } 42 | }, 43 | "backLeft":{ 44 | "x":{ 45 | "exp":"-11 in", 46 | "val":-0.2794 47 | }, 48 | "y":{ 49 | "exp":"11 in", 50 | "val":0.2794 51 | } 52 | }, 53 | "mass":{ 54 | "exp":"150 lbs", 55 | "val":68.0388555 56 | }, 57 | "inertia":{ 58 | "exp":"6 kg m ^ 2", 59 | "val":6.0 60 | }, 61 | "gearing":{ 62 | "exp":"6.5", 63 | "val":6.5 64 | }, 65 | "radius":{ 66 | "exp":"2 in", 67 | "val":0.0508 68 | }, 69 | "vmax":{ 70 | "exp":"6000 RPM", 71 | "val":628.3185307179587 72 | }, 73 | "tmax":{ 74 | "exp":"1.2 N * m", 75 | "val":1.2 76 | }, 77 | "bumper":{ 78 | "front":{ 79 | "exp":"16 in", 80 | "val":0.4064 81 | }, 82 | "side":{ 83 | "exp":"16 in", 84 | "val":0.4064 85 | }, 86 | "back":{ 87 | "exp":"16 in", 88 | "val":0.4064 89 | } 90 | }, 91 | "differentialTrackWidth":{ 92 | "exp":"22 in", 93 | "val":0.5588 94 | } 95 | }, 96 | "generationFeatures":[] 97 | } 98 | -------------------------------------------------------------------------------- /test-jsons/project/0/differential.chor: -------------------------------------------------------------------------------- 1 | { 2 | "name":"differential", 3 | "version":0, 4 | "type":"Differential", 5 | "variables":{ 6 | "expressions":{ 7 | "num":{ 8 | "dimension":"Number", 9 | "var":{ 10 | "exp":"0", 11 | "val":0.0 12 | } 13 | } 14 | }, 15 | "poses":{ 16 | "pose":{ 17 | "x":{ 18 | "exp":"0 m", 19 | "val":0.0 20 | }, 21 | "y":{ 22 | "exp":"0 m", 23 | "val":0.0 24 | }, 25 | "heading":{ 26 | "exp":"0 rad", 27 | "val":0.0 28 | } 29 | } 30 | } 31 | }, 32 | "config":{ 33 | "frontLeft":{ 34 | "x":{ 35 | "exp":"11 in", 36 | "val":0.2794 37 | }, 38 | "y":{ 39 | "exp":"11 in", 40 | "val":0.2794 41 | } 42 | }, 43 | "backLeft":{ 44 | "x":{ 45 | "exp":"-11 in", 46 | "val":-0.2794 47 | }, 48 | "y":{ 49 | "exp":"11 in", 50 | "val":0.2794 51 | } 52 | }, 53 | "mass":{ 54 | "exp":"150 lbs", 55 | "val":68.0388555 56 | }, 57 | "inertia":{ 58 | "exp":"6 kg m ^ 2", 59 | "val":6.0 60 | }, 61 | "gearing":{ 62 | "exp":"6.5", 63 | "val":6.5 64 | }, 65 | "radius":{ 66 | "exp":"2 in", 67 | "val":0.0508 68 | }, 69 | "vmax":{ 70 | "exp":"6000 RPM", 71 | "val":628.3185307179587 72 | }, 73 | "tmax":{ 74 | "exp":"1.2 N * m", 75 | "val":1.2 76 | }, 77 | "bumper":{ 78 | "front":{ 79 | "exp":"16 in", 80 | "val":0.4064 81 | }, 82 | "side":{ 83 | "exp":"16 in", 84 | "val":0.4064 85 | }, 86 | "back":{ 87 | "exp":"16 in", 88 | "val":0.4064 89 | } 90 | }, 91 | "differentialTrackWidth":{ 92 | "exp":"22 in", 93 | "val":0.5588 94 | } 95 | }, 96 | "generationFeatures":[] 97 | } 98 | -------------------------------------------------------------------------------- /test-jsons/project/beta-6/differential.chor: -------------------------------------------------------------------------------- 1 | { 2 | "name":"differential", 3 | "version":"v2025.0.0", 4 | "type":"Differential", 5 | "variables":{ 6 | "expressions":{ 7 | "num":{ 8 | "dimension":"Number", 9 | "var":{ 10 | "exp":"0", 11 | "val":0.0 12 | } 13 | } 14 | }, 15 | "poses":{ 16 | "pose":{ 17 | "x":{ 18 | "exp":"0 m", 19 | "val":0.0 20 | }, 21 | "y":{ 22 | "exp":"0 m", 23 | "val":0.0 24 | }, 25 | "heading":{ 26 | "exp":"0 rad", 27 | "val":0.0 28 | } 29 | } 30 | } 31 | }, 32 | "config":{ 33 | "frontLeft":{ 34 | "x":{ 35 | "exp":"11 in", 36 | "val":0.2794 37 | }, 38 | "y":{ 39 | "exp":"11 in", 40 | "val":0.2794 41 | } 42 | }, 43 | "backLeft":{ 44 | "x":{ 45 | "exp":"-11 in", 46 | "val":-0.2794 47 | }, 48 | "y":{ 49 | "exp":"11 in", 50 | "val":0.2794 51 | } 52 | }, 53 | "mass":{ 54 | "exp":"150 lbs", 55 | "val":68.0388555 56 | }, 57 | "inertia":{ 58 | "exp":"6 kg m ^ 2", 59 | "val":6.0 60 | }, 61 | "gearing":{ 62 | "exp":"6.5", 63 | "val":6.5 64 | }, 65 | "radius":{ 66 | "exp":"2 in", 67 | "val":0.0508 68 | }, 69 | "vmax":{ 70 | "exp":"6000 RPM", 71 | "val":628.3185307179587 72 | }, 73 | "tmax":{ 74 | "exp":"1.2 N * m", 75 | "val":1.2 76 | }, 77 | "bumper":{ 78 | "front":{ 79 | "exp":"16 in", 80 | "val":0.4064 81 | }, 82 | "side":{ 83 | "exp":"16 in", 84 | "val":0.4064 85 | }, 86 | "back":{ 87 | "exp":"16 in", 88 | "val":0.4064 89 | } 90 | }, 91 | "differentialTrackWidth":{ 92 | "exp":"22 in", 93 | "val":0.5588 94 | } 95 | }, 96 | "generationFeatures":[] 97 | } 98 | -------------------------------------------------------------------------------- /update_project_schema.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ 4 | A utility script to update the project schema in multiple files. 5 | 6 | simply run `python update_project_schema.py ` to update the version in the files. 7 | """ 8 | 9 | from collections.abc import Callable 10 | from dataclasses import dataclass 11 | from pathlib import Path 12 | 13 | 14 | @dataclass(frozen=True, slots=True) 15 | class Location: 16 | relative_path: Path 17 | # length, width 18 | template: Callable[[int], str] 19 | 20 | 21 | LOCATIONS: list[Location] = [ 22 | # Choreo UI 23 | Location( 24 | relative_path=Path("src/document/schema/ProjectSchemaVersion.ts"), 25 | template=lambda version: f"""// Auto-generated by update_project_schema.py 26 | export const PROJECT_SCHEMA_VERSION = {version};""", 27 | ), 28 | # Choreo backend 29 | Location( 30 | relative_path=Path("src-core/src/spec/project_schema_version.rs"), 31 | template=lambda version: f"""// Auto-generated by update_project_schema.py 32 | pub const PROJECT_SCHEMA_VERSION: u32 = {version};""", 33 | ), 34 | ] 35 | 36 | 37 | def update_version(version: int) -> None: 38 | 39 | for location in LOCATIONS: 40 | file_path = Path(__file__).parent / location.relative_path 41 | 42 | with open(file_path, mode="w", newline="\n") as f: 43 | f.write(location.template(version)) 44 | f.write("\n") 45 | 46 | 47 | if __name__ == "__main__": 48 | import argparse 49 | 50 | parser = argparse.ArgumentParser(description="Update version in files") 51 | parser.add_argument("version", type=int, help="Project schema version") 52 | args = parser.parse_args() 53 | update_version(args.version) 54 | -------------------------------------------------------------------------------- /trajoptlib/include/trajopt/constraint/translation_equality_constraint.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) TrajoptLib contributors 2 | 3 | #pragma once 4 | 5 | #include 6 | #include 7 | 8 | #include "trajopt/geometry/pose2.hpp" 9 | #include "trajopt/geometry/translation2.hpp" 10 | #include "trajopt/util/symbol_exports.hpp" 11 | 12 | namespace trajopt { 13 | 14 | /// Translation equality constraint. 15 | class TRAJOPT_DLLEXPORT TranslationEqualityConstraint { 16 | public: 17 | /// Constructs a TranslationEqualityConstraint. 18 | /// 19 | /// @param x The robot's x position. 20 | /// @param y The robot's y position. 21 | TranslationEqualityConstraint(double x, double y) : m_translation{x, y} {} 22 | 23 | /// Applies this constraint to the given problem. 24 | /// 25 | /// @param problem The optimization problem. 26 | /// @param pose The robot's pose. 27 | /// @param linear_velocity The robot's linear velocity. 28 | /// @param angular_velocity The robot's angular velocity. 29 | /// @param linear_acceleration The robot's linear acceleration. 30 | /// @param angular_acceleration The robot's angular acceleration. 31 | void apply( 32 | slp::Problem& problem, const Pose2v& pose, 33 | [[maybe_unused]] const Translation2v& linear_velocity, 34 | [[maybe_unused]] const slp::Variable& angular_velocity, 35 | [[maybe_unused]] const Translation2v& linear_acceleration, 36 | [[maybe_unused]] const slp::Variable& angular_acceleration) { 37 | problem.subject_to(pose.translation() == m_translation); 38 | } 39 | 40 | private: 41 | trajopt::Translation2d m_translation; 42 | }; 43 | 44 | } // namespace trajopt 45 | -------------------------------------------------------------------------------- /trajoptlib/include/trajopt/constraint/pose_equality_constraint.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) TrajoptLib contributors 2 | 3 | #pragma once 4 | 5 | #include 6 | #include 7 | 8 | #include "trajopt/geometry/pose2.hpp" 9 | #include "trajopt/geometry/translation2.hpp" 10 | #include "trajopt/util/symbol_exports.hpp" 11 | 12 | namespace trajopt { 13 | 14 | /// Pose equality constraint. 15 | class TRAJOPT_DLLEXPORT PoseEqualityConstraint { 16 | public: 17 | /// Constructs a PoseEqualityConstraint. 18 | /// 19 | /// @param x The robot's x position. 20 | /// @param y The robot's y position. 21 | /// @param heading The robot's heading. 22 | PoseEqualityConstraint(double x, double y, double heading) 23 | : m_pose{x, y, heading} {} 24 | 25 | /// Applies this constraint to the given problem. 26 | /// 27 | /// @param problem The optimization problem. 28 | /// @param pose The robot's pose. 29 | /// @param linear_velocity The robot's linear velocity. 30 | /// @param angular_velocity The robot's angular velocity. 31 | /// @param linear_acceleration The robot's linear acceleration. 32 | /// @param angular_acceleration The robot's angular acceleration. 33 | void apply( 34 | slp::Problem& problem, const Pose2v& pose, 35 | [[maybe_unused]] const Translation2v& linear_velocity, 36 | [[maybe_unused]] const slp::Variable& angular_velocity, 37 | [[maybe_unused]] const Translation2v& linear_acceleration, 38 | [[maybe_unused]] const slp::Variable& angular_acceleration) { 39 | problem.subject_to(pose == m_pose); 40 | } 41 | 42 | private: 43 | trajopt::Pose2d m_pose; 44 | }; 45 | 46 | } // namespace trajopt 47 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --accent-purple: rgb(125, 115, 231); 3 | --darker-purple: rgb(95, 85, 205); 4 | --select-yellow: #ffc52f; 5 | --background-dark-blue: #15171b; 6 | --background-dark-gray: #212326; 7 | --background-light-gray: #2f3136; 8 | --top-nav-height: 64px; 9 | --sidebar-width: 300px; 10 | --divider-gray: rgba(255, 255, 255, 0.12); 11 | user-select: none; 12 | -webkit-user-select: none; 13 | cursor: default; 14 | font: "Roboto"; 15 | } 16 | input[type="text"] { 17 | user-select: text; 18 | } 19 | html { 20 | touch-action: manipulation; 21 | overflow: hidden; 22 | } 23 | * { 24 | font-size: 1rem; 25 | } 26 | *::-webkit-scrollbar { 27 | width: 8px; 28 | height: 8px; 29 | } 30 | *::-webkit-scrollbar-track { 31 | background: transparent; 32 | border-radius: 4px; 33 | } 34 | *::-webkit-scrollbar-thumb { 35 | background: var(--select-yellow); 36 | width: 8px; 37 | height: 8px; 38 | border-radius: 4px; 39 | } 40 | h1 { 41 | font-size: 2.2rem; 42 | } 43 | h2 { 44 | font-size: 1.6rem; 45 | } 46 | 47 | h3 { 48 | font-size: 1.3rem; 49 | } 50 | .App { 51 | display: flex; 52 | flex-direction: column; 53 | height: 100vh; 54 | width: 100vw; 55 | overflow: hidden; 56 | min-width: 0; 57 | min-height: 0; 58 | position: fixed; 59 | } 60 | 61 | .Page { 62 | background: var(--background-dark-blue); 63 | width: 100%; 64 | flex-grow: 1; 65 | display: flex; 66 | flex-direction: column; 67 | flex: 1; 68 | min-height: 0; 69 | min-width: 0; 70 | } 71 | 72 | .Panel { 73 | position: absolute; 74 | background: var(--background-dark-blue); 75 | width: 100%; 76 | flex-grow: 1; 77 | display: flex; 78 | flex-direction: column; 79 | flex: 1; 80 | min-height: 0; 81 | min-width: 0; 82 | height: calc(100vh); 83 | } 84 | -------------------------------------------------------------------------------- /src/document/CodeGenStore.ts: -------------------------------------------------------------------------------- 1 | import { types } from "mobx-state-tree"; 2 | import { CodeGenConfig } from "./schema/DocumentTypes"; 3 | import { path } from "@tauri-apps/api"; 4 | import { toast } from "react-toastify"; 5 | 6 | export const CodeGenStore = types 7 | .model("CodeGenStore", { 8 | root: types.maybeNull(types.string), 9 | genVars: types.boolean, 10 | genTrajData: types.boolean, 11 | useChoreoLib: types.boolean 12 | }) 13 | .views((self) => ({ 14 | get serialize(): CodeGenConfig { 15 | return { 16 | root: self.root, 17 | genVars: self.genVars, 18 | genTrajData: self.genTrajData, 19 | useChoreoLib: self.useChoreoLib 20 | }; 21 | }, 22 | get javaPkg() { 23 | if (!self.root) { 24 | return null; 25 | } 26 | const splitPath = self.root.split(path.sep() + "java" + path.sep()); 27 | if (splitPath.length === 1) { 28 | toast.error( 29 | 'Invalid path: make sure your code generation root points to a "java" directory.' 30 | ); 31 | return null; 32 | } 33 | return splitPath[1].replaceAll("/", ".").replaceAll("\\", "."); 34 | } 35 | })) 36 | .actions((self) => ({ 37 | setRoot(root: string) { 38 | self.root = root; 39 | }, 40 | setGenVars(genVars: boolean) { 41 | self.genVars = genVars; 42 | }, 43 | setGenTrajData(genTrajData: boolean) { 44 | self.genTrajData = genTrajData; 45 | }, 46 | setUseChoreoLib(useChoreoLib: boolean) { 47 | self.useChoreoLib = useChoreoLib; 48 | }, 49 | deserialize(data: CodeGenConfig) { 50 | self.root = data.root; 51 | self.genVars = data.genVars; 52 | self.genTrajData = data.genTrajData; 53 | self.useChoreoLib = data.useChoreoLib; 54 | } 55 | })); 56 | -------------------------------------------------------------------------------- /src-core/src/generation/transformers/callback.rs: -------------------------------------------------------------------------------- 1 | use trajoptlib::{DifferentialTrajectory, SwerveTrajectory}; 2 | 3 | use crate::{ 4 | ResultExt, 5 | generation::generate::{LocalProgressUpdate, PROGRESS_SENDER_LOCK}, 6 | }; 7 | 8 | use super::{ 9 | DifferentialGenerationTransformer, FeatureLockedTransformer, GenerationContext, 10 | SwerveGenerationTransformer, 11 | }; 12 | 13 | pub struct CallbackSetter; 14 | 15 | fn swerve_status_callback(trajectory: SwerveTrajectory, handle: i64) { 16 | let tx_opt = PROGRESS_SENDER_LOCK.get(); 17 | if let Some(tx) = tx_opt { 18 | let _ = tx 19 | .send(LocalProgressUpdate::from(trajectory).handled(handle)) 20 | .trace_warn(); 21 | }; 22 | } 23 | 24 | fn differential_status_callback(trajectory: DifferentialTrajectory, handle: i64) { 25 | let tx_opt = PROGRESS_SENDER_LOCK.get(); 26 | if let Some(tx) = tx_opt { 27 | let _ = tx 28 | .send(LocalProgressUpdate::from(trajectory).handled(handle)) 29 | .trace_warn(); 30 | }; 31 | } 32 | 33 | impl SwerveGenerationTransformer for CallbackSetter { 34 | fn initialize(_: &GenerationContext) -> FeatureLockedTransformer { 35 | FeatureLockedTransformer::always(Self) 36 | } 37 | 38 | fn transform(&self, generator: &mut trajoptlib::SwerveTrajectoryGenerator) { 39 | generator.add_callback(swerve_status_callback); 40 | } 41 | } 42 | 43 | impl DifferentialGenerationTransformer for CallbackSetter { 44 | fn initialize(_: &GenerationContext) -> FeatureLockedTransformer { 45 | FeatureLockedTransformer::always(Self) 46 | } 47 | 48 | fn transform(&self, generator: &mut trajoptlib::DifferentialTrajectoryGenerator) { 49 | generator.add_callback(differential_status_callback); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src-core/src/spec/mod.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | pub mod project; 6 | pub mod project_schema_version; 7 | pub mod traj_schema_version; 8 | pub mod trajectory; 9 | pub mod upgraders; 10 | 11 | #[derive(Serialize, Deserialize, Clone, Debug)] 12 | pub struct OpenFilePayload { 13 | pub dir: String, 14 | pub name: String, 15 | } 16 | 17 | pub const PROJECT_SCHEMA_VERSION: u32 = project_schema_version::PROJECT_SCHEMA_VERSION; 18 | pub const TRAJ_SCHEMA_VERSION: u32 = traj_schema_version::TRAJ_SCHEMA_VERSION; 19 | 20 | /// A trait for types that can be snapshotted. 21 | /// This allows for the type to be converted to a f64. 22 | /// This trait is only implemented for [`f64`] and [`Expr`]. 23 | pub trait SnapshottableType: Debug + Clone { 24 | fn snapshot(&self) -> f64; 25 | 26 | fn fill_in_value(value: f64, unit: &'static str) -> Self; 27 | } 28 | 29 | impl SnapshottableType for f64 { 30 | #[inline] 31 | fn snapshot(&self) -> f64 { 32 | *self 33 | } 34 | 35 | #[inline] 36 | fn fill_in_value(value: f64, _unit: &'static str) -> Self { 37 | value 38 | } 39 | } 40 | 41 | #[derive(Debug, Serialize, Deserialize, Clone)] 42 | pub struct Expr { 43 | pub exp: String, 44 | pub val: f64, 45 | } 46 | impl Expr { 47 | #[must_use] 48 | pub fn new(name: &str, value: f64) -> Self { 49 | Self { 50 | exp: name.to_string(), 51 | val: value, 52 | } 53 | } 54 | } 55 | impl SnapshottableType for Expr { 56 | #[inline] 57 | fn snapshot(&self) -> f64 { 58 | self.val 59 | } 60 | 61 | #[inline] 62 | fn fill_in_value(val: f64, unit: &'static str) -> Self { 63 | Expr { 64 | exp: format!("{val} {unit}"), 65 | val, 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/assets/Torque.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { SvgIcon as MuiSvgIcon, SvgIconProps, styled } from "@mui/material"; 3 | const SvgIcon = styled(MuiSvgIcon, { 4 | name: "MopeimIcon", 5 | shouldForwardProp: (prop) => prop !== "fill" 6 | })(() => ({ 7 | fill: "none", 8 | stroke: "currentColor", 9 | strokeLinecap: "square", 10 | strokeLinejoin: "round", 11 | strokeWidth: "2.25px" 12 | })); 13 | 14 | const Torque: React.FunctionComponent = (props) => { 15 | const cornerX = 4; 16 | const cornerY = 18; 17 | const endX = 20; 18 | const angle = Math.PI / 2; 19 | const r = 8; 20 | return ( 21 | 53 | ); 54 | }; 55 | export default Torque; 56 | -------------------------------------------------------------------------------- /src/components/config/robotconfig/SwerveConfigPanel.tsx: -------------------------------------------------------------------------------- 1 | import { observer } from "mobx-react"; 2 | import { Component } from "react"; 3 | import { doc } from "../../../document/DocumentManager"; 4 | import ExpressionInput from "../../input/ExpressionInput"; 5 | import ExpressionInputList from "../../input/ExpressionInputList"; 6 | 7 | type Props = { rowGap: number }; 8 | 9 | type State = object; 10 | 11 | class SwerveConfigPanel extends Component { 12 | render() { 13 | const config = doc.robotConfig; 14 | return ( 15 | 16 | 24 | 25 | 33 | 41 | 42 | 50 | 51 | ); 52 | } 53 | } 54 | export default observer(SwerveConfigPanel); 55 | -------------------------------------------------------------------------------- /trajoptlib/build.rs: -------------------------------------------------------------------------------- 1 | use cmake::Config; 2 | 3 | fn main() { 4 | let mut cmake_config = Config::new("."); 5 | 6 | cmake_config 7 | .profile("Release") 8 | .define("BUILD_TESTING", "OFF"); 9 | 10 | cmake_config.define("BUILD_SHARED_LIBS", "OFF"); 11 | 12 | if cfg!(target_os = "windows") { 13 | cmake_config 14 | .generator("Visual Studio 17 2022") 15 | .cxxflag("/EHsc"); 16 | } 17 | 18 | let cmake_dest = cmake_config.build(); 19 | 20 | let mut bridge_build = cxx_build::bridge("src/lib.rs"); 21 | 22 | bridge_build 23 | .file("src/rust_ffi.cpp") 24 | .include("src") 25 | .include(format!("{}/include", cmake_dest.display())) 26 | .include(format!("{}/include/eigen3", cmake_dest.display())) 27 | .flag_if_supported("-std=c++23") 28 | .flag_if_supported("-std=c++2b") 29 | .flag_if_supported("/std:c++23preview"); 30 | 31 | if cfg!(target_os = "windows") { 32 | bridge_build.flag("/EHsc").flag("/utf-8"); 33 | } 34 | 35 | bridge_build.compile("TrajoptLibRust"); 36 | 37 | println!( 38 | "cargo:rustc-link-search=native={}/bin", 39 | cmake_dest.display() 40 | ); 41 | println!( 42 | "cargo:rustc-link-search=native={}/lib", 43 | cmake_dest.display() 44 | ); 45 | println!( 46 | "cargo:rustc-link-search=native={}/build/_deps/sleipnir-build/", 47 | cmake_dest.display() 48 | ); 49 | println!("cargo:rustc-link-lib=TrajoptLibRust"); 50 | println!("cargo:rustc-link-lib=TrajoptLib"); 51 | println!("cargo:rustc-link-lib=Sleipnir"); 52 | 53 | println!("cargo:rerun-if-changed=CMakeLists.txt"); 54 | println!("cargo:rerun-if-changed=cmake"); 55 | println!("cargo:rerun-if-changed=include"); 56 | println!("cargo:rerun-if-changed=src"); 57 | } 58 | -------------------------------------------------------------------------------- /test-jsons/project/1/swerve.chor: -------------------------------------------------------------------------------- 1 | { 2 | "name":"swerve", 3 | "version":1, 4 | "type":"Swerve", 5 | "variables":{ 6 | "expressions":{ 7 | "num":{ 8 | "dimension":"Number", 9 | "var":{ 10 | "exp":"0", 11 | "val":0.0 12 | } 13 | } 14 | }, 15 | "poses":{ 16 | "pose":{ 17 | "x":{ 18 | "exp":"0 m", 19 | "val":0.0 20 | }, 21 | "y":{ 22 | "exp":"0 m", 23 | "val":0.0 24 | }, 25 | "heading":{ 26 | "exp":"0 rad", 27 | "val":0.0 28 | } 29 | } 30 | } 31 | }, 32 | "config":{ 33 | "frontLeft":{ 34 | "x":{ 35 | "exp":"11 in", 36 | "val":0.2794 37 | }, 38 | "y":{ 39 | "exp":"11 in", 40 | "val":0.2794 41 | } 42 | }, 43 | "backLeft":{ 44 | "x":{ 45 | "exp":"-11 in", 46 | "val":-0.2794 47 | }, 48 | "y":{ 49 | "exp":"11 in", 50 | "val":0.2794 51 | } 52 | }, 53 | "mass":{ 54 | "exp":"150 lbs", 55 | "val":68.0388555 56 | }, 57 | "inertia":{ 58 | "exp":"6 kg m ^ 2", 59 | "val":6.0 60 | }, 61 | "gearing":{ 62 | "exp":"6.5", 63 | "val":6.5 64 | }, 65 | "radius":{ 66 | "exp":"2 in", 67 | "val":0.0508 68 | }, 69 | "vmax":{ 70 | "exp":"6000 RPM", 71 | "val":628.3185307179587 72 | }, 73 | "tmax":{ 74 | "exp":"1.2 N * m", 75 | "val":1.2 76 | }, 77 | "cof":{ 78 | "exp":"1.5", 79 | "val":1.5 80 | }, 81 | "bumper":{ 82 | "front":{ 83 | "exp":"16 in", 84 | "val":0.4064 85 | }, 86 | "side":{ 87 | "exp":"16 in", 88 | "val":0.4064 89 | }, 90 | "back":{ 91 | "exp":"16 in", 92 | "val":0.4064 93 | } 94 | }, 95 | "differentialTrackWidth":{ 96 | "exp":"22 in", 97 | "val":0.5588 98 | } 99 | }, 100 | "generationFeatures":[] 101 | } 102 | -------------------------------------------------------------------------------- /test-jsons/project/1/differential.chor: -------------------------------------------------------------------------------- 1 | { 2 | "name":"differential", 3 | "version":1, 4 | "type":"Differential", 5 | "variables":{ 6 | "expressions":{ 7 | "num":{ 8 | "dimension":"Number", 9 | "var":{ 10 | "exp":"0", 11 | "val":0.0 12 | } 13 | } 14 | }, 15 | "poses":{ 16 | "pose":{ 17 | "x":{ 18 | "exp":"0 m", 19 | "val":0.0 20 | }, 21 | "y":{ 22 | "exp":"0 m", 23 | "val":0.0 24 | }, 25 | "heading":{ 26 | "exp":"0 rad", 27 | "val":0.0 28 | } 29 | } 30 | } 31 | }, 32 | "config":{ 33 | "frontLeft":{ 34 | "x":{ 35 | "exp":"11 in", 36 | "val":0.2794 37 | }, 38 | "y":{ 39 | "exp":"11 in", 40 | "val":0.2794 41 | } 42 | }, 43 | "backLeft":{ 44 | "x":{ 45 | "exp":"-11 in", 46 | "val":-0.2794 47 | }, 48 | "y":{ 49 | "exp":"11 in", 50 | "val":0.2794 51 | } 52 | }, 53 | "mass":{ 54 | "exp":"150 lbs", 55 | "val":68.0388555 56 | }, 57 | "inertia":{ 58 | "exp":"6 kg m ^ 2", 59 | "val":6.0 60 | }, 61 | "gearing":{ 62 | "exp":"6.5", 63 | "val":6.5 64 | }, 65 | "radius":{ 66 | "exp":"2 in", 67 | "val":0.0508 68 | }, 69 | "vmax":{ 70 | "exp":"6000 RPM", 71 | "val":628.3185307179587 72 | }, 73 | "tmax":{ 74 | "exp":"1.2 N * m", 75 | "val":1.2 76 | }, 77 | "cof":{ 78 | "exp":"1.5", 79 | "val":1.5 80 | }, 81 | "bumper":{ 82 | "front":{ 83 | "exp":"16 in", 84 | "val":0.4064 85 | }, 86 | "side":{ 87 | "exp":"16 in", 88 | "val":0.4064 89 | }, 90 | "back":{ 91 | "exp":"16 in", 92 | "val":0.4064 93 | } 94 | }, 95 | "differentialTrackWidth":{ 96 | "exp":"22 in", 97 | "val":0.5588 98 | } 99 | }, 100 | "generationFeatures":[] 101 | } 102 | -------------------------------------------------------------------------------- /src/components/input/InputList.module.css: -------------------------------------------------------------------------------- 1 | .InputList { 2 | display: grid; 3 | column-gap: 1ch; 4 | grid-template-columns: max-content auto max-content 24px; 5 | width: auto; 6 | height: auto; 7 | color: white; 8 | --text-color: white; 9 | --disabled-color: gray; 10 | } 11 | .InputList.NoCheckbox { 12 | grid-template-columns: max-content auto max-content 0; 13 | } 14 | .InputList.Expression { 15 | grid-template-columns: max-content auto; 16 | } 17 | .InputList:not(.NoCheckbox):not(.Expression) .Checkbox { 18 | grid-column: 4; 19 | } 20 | .InputList:not(.NoCheckbox).Expression .Checkbox { 21 | grid-column: 2; 22 | } 23 | 24 | .Title { 25 | text-align: right; 26 | min-width: max-content; 27 | } 28 | .Title.Disabled { 29 | color: var(--disabled-color); 30 | } 31 | .Title.Tooltip { 32 | text-decoration-line: underline; 33 | text-decoration-style: dotted; 34 | text-underline-offset: 2px; 35 | } 36 | .Number { 37 | position: relative; 38 | font-family: "Roboto Mono Variable"; 39 | width: auto; 40 | height: 24px; 41 | background-color: transparent; 42 | border: none; 43 | align-self: center; 44 | flex-grow: 1; 45 | color: inherit; 46 | flex-shrink: 1; 47 | min-width: 5.5ch; 48 | border-bottom: solid 1px; 49 | text-align: right; 50 | } 51 | 52 | .Number:disabled { 53 | border-bottom: 1px solid var(--disabled-color); 54 | color: transparent; 55 | font-style: italic; 56 | } 57 | .Number:disabled.ShowWhenDisabled { 58 | color: var(--disabled-color); 59 | } 60 | 61 | .Number.Invalid { 62 | border-bottom: 2px solid red; 63 | } 64 | .Number.Mui { 65 | border-bottom: none; 66 | } 67 | 68 | .Suffix { 69 | min-width: max-content; 70 | text-align: left; 71 | flex-shrink: 0; 72 | } 73 | .Suffix.Disabled { 74 | color: var(--disabled-color); 75 | } 76 | .Checkbox { 77 | min-width: 10%; 78 | margin: 1px; 79 | padding: 0px; 80 | } 81 | -------------------------------------------------------------------------------- /trajoptlib/include/trajopt/spline/cubic_hermite_spline1d.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) TrajoptLib contributors 2 | 3 | #pragma once 4 | 5 | #include "trajopt/util/symbol_exports.hpp" 6 | 7 | namespace trajopt { 8 | 9 | /// 1D cubic hermite spline. 10 | class TRAJOPT_DLLEXPORT CubicHermiteSpline1d { 11 | public: 12 | /// Constructs a 1D cubic hermite spline. 13 | /// 14 | /// @param p0 The initial position. 15 | /// @param p1 The final position. 16 | /// @param v0 The initial velocity. 17 | /// @param v1 The final velocity. 18 | constexpr CubicHermiteSpline1d(double p0, double p1, double v0, double v1) 19 | : a{v0 + v1 + 2 * p0 - 2 * p1}, 20 | b{-2 * v0 - v1 - 3 * p0 + 3 * p1}, 21 | c{v0}, 22 | d{p0} {} 23 | 24 | /// Return the position at point t. 25 | /// 26 | /// @param t The point t 27 | /// @return The position at point t. 28 | constexpr double get_position(double t) const { 29 | double t2 = t * t; 30 | double t3 = t2 * t; 31 | return a * t3 + b * t2 + c * t + d; 32 | } 33 | 34 | /// Return the velocity at point t. 35 | /// 36 | /// @param t The point t 37 | /// @return The velocity at point t. 38 | constexpr double get_velocity(double t) const { 39 | return 3 * a * t * t + 2 * b * t + c; 40 | } 41 | 42 | /// Return the acceleration at point t. 43 | /// 44 | /// @param t The point t 45 | /// @return The acceleration at point t. 46 | constexpr double get_acceleration(double t) const { 47 | return 6 * a * t + 2 * b; 48 | } 49 | 50 | /// Return the jerk at point t. 51 | /// 52 | /// @param t The point t 53 | /// @return The jerk at point t. 54 | constexpr double get_jerk([[maybe_unused]] double t) const { return 6 * a; } 55 | 56 | private: 57 | // Coefficients of the cubic polynomial 58 | double a; 59 | double b; 60 | double c; 61 | double d; 62 | }; 63 | 64 | } // namespace trajopt 65 | -------------------------------------------------------------------------------- /src/components/field/svg/FieldGeneratedWaypoints.tsx: -------------------------------------------------------------------------------- 1 | import { Component } from "react"; 2 | import { doc } from "../../../document/DocumentManager"; 3 | 4 | import { observer } from "mobx-react"; 5 | 6 | type Props = object; 7 | 8 | type State = object; 9 | 10 | class FieldSamples extends Component { 11 | state = {}; 12 | 13 | render() { 14 | const path = doc.pathlist.activePath; 15 | return ( 16 | <> 17 | {path.snapshot.waypoints.map((point, idx) => { 18 | let color = "white"; 19 | if (idx === 0) { 20 | color = "green"; 21 | } else if (idx === path.snapshot.waypoints.length - 1) { 22 | color = "red"; 23 | } 24 | if (point.fixHeading) { 25 | return ( 26 | 32 | 33 | 34 | 43 | // Full 44 | ); 45 | } else if (point.fixTranslation) { 46 | return ( 47 | 48 | ); 49 | // Translation 50 | } else { 51 | return <>; 52 | // Empty 53 | } 54 | })} 55 | 56 | ); 57 | } 58 | } 59 | export default observer(FieldSamples); 60 | -------------------------------------------------------------------------------- /.github/workflows/sanitizers.yml: -------------------------------------------------------------------------------- 1 | name: Sanitizers 2 | 3 | on: [pull_request, push] 4 | 5 | concurrency: 6 | group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} 7 | cancel-in-progress: true 8 | 9 | jobs: 10 | build: 11 | timeout-minutes: 10 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | include: 16 | - sanitizer-name: asan 17 | cmake-args: "-DCMAKE_BUILD_TYPE=Asan" 18 | - sanitizer-name: tsan 19 | cmake-args: "-DCMAKE_BUILD_TYPE=Tsan" 20 | - sanitizer-name: ubsan 21 | cmake-args: "-DCMAKE_BUILD_TYPE=Ubsan" 22 | 23 | name: ${{ matrix.sanitizer-name }} 24 | runs-on: ubuntu-24.04 25 | 26 | steps: 27 | - uses: actions/checkout@v5 28 | with: 29 | persist-credentials: false 30 | token: ${{secrets.GITHUB_TOKEN}} 31 | 32 | - name: Install LLVM 21 33 | run: | 34 | wget https://apt.llvm.org/llvm.sh 35 | chmod +x llvm.sh 36 | sudo ./llvm.sh 21 all 37 | sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-21 200 38 | sudo update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-21 200 39 | echo "CC=clang" >> $GITHUB_ENV 40 | echo "CXX=clang++" >> $GITHUB_ENV 41 | echo "CXXFLAGS=-stdlib=libc++" >> $GITHUB_ENV 42 | 43 | - name: Set up sccache 44 | uses: mozilla-actions/sccache-action@v0.0.9 45 | 46 | - run: cmake --preset with-examples-and-sccache ${{ matrix.cmake-args }} 47 | working-directory: trajoptlib 48 | - run: cmake --build build --config RelWithDebInfo --parallel $(nproc) 49 | working-directory: trajoptlib 50 | env: 51 | SCCACHE_GHA_ENABLED: true 52 | - run: ctest --test-dir build -C RelWithDebInfo --output-on-failure 53 | working-directory: trajoptlib 54 | -------------------------------------------------------------------------------- /trajoptlib/src/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | // Error messages and codes from: 4 | // https://github.com/SleipnirGroup/Sleipnir/blob/4e07c8835dc589e496a66d3320e0990668fea268/include/sleipnir/optimization/solver/exit_status.hpp 5 | 6 | #[derive(Debug, Error)] 7 | pub enum TrajoptError { 8 | #[error("Too few degrees of freedom")] 9 | TooFewDOFs, 10 | #[error("Locally infeasible")] 11 | LocallyInfeasible, 12 | #[error("Globally infeasible")] 13 | GloballyInfeasible, 14 | #[error("Factorization failed")] 15 | FactorizationFailed, 16 | #[error("Line search failed")] 17 | LineSearchFailed, 18 | #[error("Nonfinite initial cost or constraints")] 19 | NonfiniteInitialCostOrConstraints, 20 | #[error("Diverging iterates")] 21 | DivergingIterates, 22 | #[error("Max iterations exceeded")] 23 | MaxIterationsExceeded, 24 | #[error("Timeout")] 25 | Timeout, 26 | #[error("Unparsable error code: {0}")] 27 | Unparsable(Box), 28 | #[error("Unknown error: {0:?}")] 29 | Unknown(i8), 30 | } 31 | 32 | impl From for TrajoptError { 33 | fn from(value: i8) -> Self { 34 | match value { 35 | -1 => Self::TooFewDOFs, 36 | -2 => Self::LocallyInfeasible, 37 | -3 => Self::GloballyInfeasible, 38 | -4 => Self::FactorizationFailed, 39 | -5 => Self::LineSearchFailed, 40 | -6 => Self::NonfiniteInitialCostOrConstraints, 41 | -7 => Self::DivergingIterates, 42 | -8 => Self::MaxIterationsExceeded, 43 | -9 => Self::Timeout, 44 | _ => Self::Unknown(value), 45 | } 46 | } 47 | } 48 | 49 | impl serde::Serialize for TrajoptError { 50 | fn serialize(&self, serializer: S) -> Result 51 | where 52 | S: serde::Serializer, 53 | { 54 | format!("{self}").serialize(serializer) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /test-jsons/project/2/swerve.chor: -------------------------------------------------------------------------------- 1 | { 2 | "name":"swerve", 3 | "version":"v2025.0.0", 4 | "type":"Swerve", 5 | "variables":{ 6 | "expressions":{ 7 | "num":{ 8 | "dimension":"Number", 9 | "var":{ 10 | "exp":"0", 11 | "val":0.0 12 | } 13 | } 14 | }, 15 | "poses":{ 16 | "pose":{ 17 | "x":{ 18 | "exp":"0 m", 19 | "val":0.0 20 | }, 21 | "y":{ 22 | "exp":"0 m", 23 | "val":0.0 24 | }, 25 | "heading":{ 26 | "exp":"0 rad", 27 | "val":0.0 28 | } 29 | } 30 | } 31 | }, 32 | "config":{ 33 | "frontLeft":{ 34 | "x":{ 35 | "exp":"11 in", 36 | "val":0.2794 37 | }, 38 | "y":{ 39 | "exp":"11 in", 40 | "val":0.2794 41 | } 42 | }, 43 | "backLeft":{ 44 | "x":{ 45 | "exp":"-11 in", 46 | "val":-0.2794 47 | }, 48 | "y":{ 49 | "exp":"11 in", 50 | "val":0.2794 51 | } 52 | }, 53 | "mass":{ 54 | "exp":"150 lbs", 55 | "val":68.0388555 56 | }, 57 | "inertia":{ 58 | "exp":"6 kg m ^ 2", 59 | "val":6.0 60 | }, 61 | "gearing":{ 62 | "exp":"6.5", 63 | "val":6.5 64 | }, 65 | "radius":{ 66 | "exp":"2 in", 67 | "val":0.0508 68 | }, 69 | "vmax":{ 70 | "exp":"6000 RPM", 71 | "val":628.3185307179587 72 | }, 73 | "tmax":{ 74 | "exp":"1.2 N * m", 75 | "val":1.2 76 | }, 77 | "bumper":{ 78 | "front":{ 79 | "exp":"16 in", 80 | "val":0.4064 81 | }, 82 | "side":{ 83 | "exp":"16 in", 84 | "val":0.4064 85 | }, 86 | "back":{ 87 | "exp":"16 in", 88 | "val":0.4064 89 | } 90 | }, 91 | "differentialTrackWidth":{ 92 | "exp":"22 in", 93 | "val":0.5588 94 | } 95 | }, 96 | "generationFeatures":[], 97 | "codegen":{ 98 | "root":"..\\..\\java\\frc\\robot\\constants", 99 | "genVars":false, 100 | "genTrajNames":false 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /choreolib/src/test/java/choreo/auto/AutoTestHelper.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) Choreo contributors 2 | 3 | package choreo.auto; 4 | 5 | import edu.wpi.first.hal.AllianceStationID; 6 | import edu.wpi.first.math.geometry.Pose2d; 7 | import edu.wpi.first.wpilibj.DriverStation; 8 | import edu.wpi.first.wpilibj.DriverStation.Alliance; 9 | import edu.wpi.first.wpilibj.simulation.DriverStationSim; 10 | import edu.wpi.first.wpilibj2.command.Subsystem; 11 | import java.util.Optional; 12 | import java.util.concurrent.atomic.AtomicReference; 13 | 14 | public class AutoTestHelper { 15 | public static AutoFactory factory( 16 | boolean useAllianceFlipping, AtomicReference robotPose) { 17 | // AtomicReference pose = new AtomicReference<>(new Pose2d()); 18 | return new AutoFactory( 19 | () -> robotPose.get(), 20 | newPose -> robotPose.set(newPose), 21 | sample -> robotPose.set(sample.getPose()), 22 | useAllianceFlipping, 23 | new Subsystem() {}, 24 | (sample, isStart) -> {}); 25 | } 26 | 27 | public static AutoFactory factory(boolean useAllianceFlipping) { 28 | AtomicReference pose = new AtomicReference<>(new Pose2d()); 29 | return factory(useAllianceFlipping, pose); 30 | } 31 | 32 | public static AutoFactory factory() { 33 | return factory(false); 34 | } 35 | 36 | public static void setAlliance(Optional alliance) { 37 | var id = 38 | alliance 39 | .map( 40 | all -> { 41 | if (all.equals(Alliance.Blue)) { 42 | return AllianceStationID.Blue1; 43 | } else { 44 | return AllianceStationID.Red1; 45 | } 46 | }) 47 | .orElse(AllianceStationID.Unknown); 48 | DriverStationSim.setAllianceStationId(id); 49 | DriverStationSim.notifyNewData(); 50 | DriverStation.refreshData(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /test-jsons/project/2/differential.chor: -------------------------------------------------------------------------------- 1 | { 2 | "name":"differential", 3 | "version":"v2025.0.0", 4 | "type":"Differential", 5 | "variables":{ 6 | "expressions":{ 7 | "num":{ 8 | "dimension":"Number", 9 | "var":{ 10 | "exp":"0", 11 | "val":0.0 12 | } 13 | } 14 | }, 15 | "poses":{ 16 | "pose":{ 17 | "x":{ 18 | "exp":"0 m", 19 | "val":0.0 20 | }, 21 | "y":{ 22 | "exp":"0 m", 23 | "val":0.0 24 | }, 25 | "heading":{ 26 | "exp":"0 rad", 27 | "val":0.0 28 | } 29 | } 30 | } 31 | }, 32 | "config":{ 33 | "frontLeft":{ 34 | "x":{ 35 | "exp":"11 in", 36 | "val":0.2794 37 | }, 38 | "y":{ 39 | "exp":"11 in", 40 | "val":0.2794 41 | } 42 | }, 43 | "backLeft":{ 44 | "x":{ 45 | "exp":"-11 in", 46 | "val":-0.2794 47 | }, 48 | "y":{ 49 | "exp":"11 in", 50 | "val":0.2794 51 | } 52 | }, 53 | "mass":{ 54 | "exp":"150 lbs", 55 | "val":68.0388555 56 | }, 57 | "inertia":{ 58 | "exp":"6 kg m ^ 2", 59 | "val":6.0 60 | }, 61 | "gearing":{ 62 | "exp":"6.5", 63 | "val":6.5 64 | }, 65 | "radius":{ 66 | "exp":"2 in", 67 | "val":0.0508 68 | }, 69 | "vmax":{ 70 | "exp":"6000 RPM", 71 | "val":628.3185307179587 72 | }, 73 | "tmax":{ 74 | "exp":"1.2 N * m", 75 | "val":1.2 76 | }, 77 | "bumper":{ 78 | "front":{ 79 | "exp":"16 in", 80 | "val":0.4064 81 | }, 82 | "side":{ 83 | "exp":"16 in", 84 | "val":0.4064 85 | }, 86 | "back":{ 87 | "exp":"16 in", 88 | "val":0.4064 89 | } 90 | }, 91 | "differentialTrackWidth":{ 92 | "exp":"22 in", 93 | "val":0.5588 94 | } 95 | }, 96 | "generationFeatures":[], 97 | "codegen":{ 98 | "root":"..\\..\\java\\frc\\robot\\constants", 99 | "genVars":false, 100 | "genTrajNames":false 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/components/input/BooleanInput.tsx: -------------------------------------------------------------------------------- 1 | import { Checkbox, Tooltip } from "@mui/material"; 2 | import { observer } from "mobx-react"; 3 | import React, { Component } from "react"; 4 | import styles from "./InputList.module.css"; 5 | 6 | type Props = { 7 | /** The text to show before the number */ 8 | title: string; 9 | /** Whether the input should be editable, or else italic and grayed out */ 10 | enabled: boolean; 11 | /** The value of the input */ 12 | value: boolean; 13 | setValue: (arg: boolean) => void; 14 | /** The tooltip for the title */ 15 | titleTooltip?: string; 16 | inputListCheckbox?: boolean; 17 | }; 18 | 19 | type State = object; 20 | 21 | class Input extends Component { 22 | inputElemRef: React.RefObject; 23 | constructor(props: Props) { 24 | super(props); 25 | this.inputElemRef = React.createRef(); 26 | } 27 | 28 | render() { 29 | return ( 30 | <> 31 | 32 | 41 | {this.props.title} 42 | 43 | 44 | e.stopPropagation()} 51 | onChange={(e) => { 52 | this.props.setValue(e.currentTarget.checked); 53 | }} 54 | > 55 | 56 | ); 57 | } 58 | } 59 | export default observer(Input); 60 | -------------------------------------------------------------------------------- /src/document/path/PathUIStore.ts: -------------------------------------------------------------------------------- 1 | import { types, getEnv, Instance } from "mobx-state-tree"; 2 | import { DifferentialSample, type SwerveSample } from "../schema/DocumentTypes"; 3 | import { Env } from "../DocumentManager"; 4 | import { SavingState } from "../UIStateStore"; 5 | 6 | export const PathUIStore = types 7 | .model("PathUIStore", { 8 | visibleWaypointsStart: types.number, 9 | visibleWaypointsEnd: types.number, 10 | generationProgress: types.frozen< 11 | Array | Array 12 | >([]), 13 | generating: false, 14 | generationIterationNumber: 0, 15 | upToDate: false, 16 | savingState: types.enumeration(Object.values(SavingState)) 17 | }) 18 | .actions((self) => ({ 19 | setSavingState(state: SavingState) { 20 | self.savingState = state; 21 | }, 22 | setUpToDate(upToDate: boolean) { 23 | getEnv(self).withoutUndo(() => { 24 | self.upToDate = upToDate; 25 | }); 26 | }, 27 | setVisibleWaypointsStart(start: number) { 28 | if (start <= self.visibleWaypointsEnd) { 29 | self.visibleWaypointsStart = start; 30 | } 31 | }, 32 | setVisibleWaypointsEnd(end: number) { 33 | if (end >= self.visibleWaypointsStart) { 34 | self.visibleWaypointsEnd = end; 35 | } 36 | }, 37 | setIterationNumber(it: number) { 38 | getEnv(self).withoutUndo(() => { 39 | self.generationIterationNumber = it; 40 | }); 41 | }, 42 | setInProgressTrajectory( 43 | trajectory: Array | Array 44 | ) { 45 | getEnv(self).withoutUndo(() => { 46 | self.generationProgress = trajectory; 47 | }); 48 | }, 49 | setGenerating(generating: boolean) { 50 | getEnv(self).withoutUndo(() => { 51 | self.generating = generating; 52 | }); 53 | } 54 | })); 55 | 56 | export type IPathUIStore = Instance; 57 | -------------------------------------------------------------------------------- /trajoptlib/include/trajopt/constraint/linear_velocity_direction_constraint.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) TrajoptLib contributors 2 | 3 | #pragma once 4 | 5 | #include 6 | #include 7 | 8 | #include "trajopt/geometry/pose2.hpp" 9 | #include "trajopt/geometry/translation2.hpp" 10 | #include "trajopt/util/symbol_exports.hpp" 11 | 12 | namespace trajopt { 13 | 14 | /// Linear velocity direction equality constraint. 15 | class TRAJOPT_DLLEXPORT LinearVelocityDirectionConstraint { 16 | public: 17 | /// Constructs a LinearVelocityDirectionConstraint. 18 | /// 19 | /// @param angle The angle (radians). 20 | explicit LinearVelocityDirectionConstraint(double angle) : m_angle{angle} {} 21 | 22 | /// Applies this constraint to the given problem. 23 | /// 24 | /// @param problem The optimization problem. 25 | /// @param pose The robot's pose. 26 | /// @param linear_velocity The robot's linear velocity. 27 | /// @param angular_velocity The robot's angular velocity. 28 | /// @param linear_acceleration The robot's linear acceleration. 29 | /// @param angular_acceleration The robot's angular acceleration. 30 | void apply( 31 | [[maybe_unused]] slp::Problem& problem, 32 | [[maybe_unused]] const Pose2v& pose, 33 | const Translation2v& linear_velocity, 34 | [[maybe_unused]] const slp::Variable& angular_velocity, 35 | [[maybe_unused]] const Translation2v& linear_acceleration, 36 | [[maybe_unused]] const slp::Variable& angular_acceleration) { 37 | // and must be parallel 38 | // 39 | // (v ⋅ u)/‖v‖ = 1 40 | // v ⋅ u = ‖v‖ 41 | // (v ⋅ u)² = ‖v‖² 42 | auto dot = linear_velocity.dot(Translation2d{m_angle.cos(), m_angle.sin()}); 43 | problem.subject_to(dot * dot == linear_velocity.squared_norm()); 44 | } 45 | 46 | private: 47 | trajopt::Rotation2d m_angle; 48 | }; 49 | 50 | } // namespace trajopt 51 | -------------------------------------------------------------------------------- /choreolib/src/main/native/cpp/choreo/trajectory/DifferentialSample.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) Choreo contributors 2 | 3 | #include "choreo/trajectory/DifferentialSample.h" 4 | 5 | #include 6 | 7 | void choreo::to_json(wpi::json& json, 8 | const DifferentialSample& trajectorySample) { 9 | json = wpi::json{{"t", trajectorySample.timestamp.value()}, 10 | {"x", trajectorySample.x.value()}, 11 | {"y", trajectorySample.y.value()}, 12 | {"heading", trajectorySample.heading.value()}, 13 | {"vl", trajectorySample.vl.value()}, 14 | {"vr", trajectorySample.vr.value()}, 15 | {"omega", trajectorySample.omega.value()}, 16 | {"al", trajectorySample.al.value()}, 17 | {"ar", trajectorySample.ar.value()}, 18 | {"fl", trajectorySample.fl.value()}, 19 | {"fr", trajectorySample.fr.value()}}; 20 | } 21 | 22 | void choreo::from_json(const wpi::json& json, 23 | DifferentialSample& trajectorySample) { 24 | trajectorySample.timestamp = units::second_t{json.at("t").get()}; 25 | trajectorySample.x = units::meter_t{json.at("x").get()}; 26 | trajectorySample.y = units::meter_t{json.at("y").get()}; 27 | trajectorySample.heading = units::radian_t{json.at("heading").get()}; 28 | trajectorySample.vl = units::meters_per_second_t{json.at("vl").get()}; 29 | trajectorySample.vr = units::meters_per_second_t{json.at("vr").get()}; 30 | trajectorySample.omega = 31 | units::radians_per_second_t{json.at("omega").get()}; 32 | trajectorySample.al = 33 | units::meters_per_second_squared_t{json.at("al").get()}; 34 | trajectorySample.ar = 35 | units::meters_per_second_squared_t{json.at("ar").get()}; 36 | trajectorySample.fl = units::newton_t{json.at("fl").get()}; 37 | trajectorySample.fr = units::newton_t{json.at("fr").get()}; 38 | } 39 | -------------------------------------------------------------------------------- /src/document/path/utils.ts: -------------------------------------------------------------------------------- 1 | import { WaypointIDX } from "../schema/DocumentTypes"; 2 | import { IWaypointScope } from "../ConstraintStore"; 3 | import { IHolonomicWaypointStore } from "../HolonomicWaypointStore"; 4 | 5 | export function findUUIDIndex(uuid: string, items: { uuid: string }[]) { 6 | return items.findIndex((wpt) => wpt.uuid === uuid); 7 | } 8 | 9 | export function waypointIdToSavedWaypointId( 10 | waypointId: IWaypointScope | undefined, 11 | points: IHolonomicWaypointStore[] 12 | ): "first" | "last" | number | undefined { 13 | if (waypointId === null || waypointId === undefined) { 14 | return undefined; 15 | } 16 | if (typeof waypointId !== "string") { 17 | const scopeIndex = findUUIDIndex(waypointId.uuid, points); 18 | if (scopeIndex == -1) { 19 | return undefined; // don't try to save this constraint 20 | } 21 | return scopeIndex; 22 | } else { 23 | return waypointId; 24 | } 25 | } 26 | export function savedWaypointIdToWaypointId( 27 | savedId: WaypointIDX | undefined, 28 | points: IHolonomicWaypointStore[] 29 | ): IWaypointScope | undefined { 30 | if (savedId === null || savedId === undefined) { 31 | return undefined; 32 | } 33 | 34 | if (savedId === "first") { 35 | return "first"; 36 | } 37 | if (savedId === "last") { 38 | return "last"; 39 | } 40 | if (savedId < 0 || savedId >= points.length) { 41 | return undefined; 42 | } 43 | if (!Number.isInteger(savedId)) { 44 | return undefined; 45 | } else { 46 | return { uuid: points[savedId]?.uuid as string }; 47 | } 48 | } 49 | export function getByWaypointID( 50 | id: IWaypointScope | undefined, 51 | points: IHolonomicWaypointStore[] 52 | ): IHolonomicWaypointStore | undefined { 53 | if (id === undefined) { 54 | return undefined; 55 | } else if (id === "first") { 56 | return points[0]; 57 | } else if (id === "last") { 58 | return points[points.length - 1]; 59 | } else if (typeof id.uuid === "string") { 60 | return points[findUUIDIndex(id.uuid, points)]; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /trajoptlib/test/src/swerve_path_builder_test.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) TrajoptLib contributors 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | using Catch::Matchers::WithinAbs; 10 | 11 | TEST_CASE("SwervePathBuilder - Linear initial guess", "[SwervePathBuilder]") { 12 | using namespace trajopt; 13 | 14 | trajopt::SwervePathBuilder path; 15 | path.wpt_initial_guess_point(0, Pose2d{0.0, 0.0, 0.0}); // at 0 16 | 17 | path.sgmt_initial_guess_points( 18 | 0, {Pose2d{1.0, 0.0, 0.0}, Pose2d{2.0, 0.0, 0.0}}); // from 0 to 1 19 | path.wpt_initial_guess_point(1, Pose2d{1.0, 0.0, 0.0}); // at 1 20 | 21 | path.wpt_initial_guess_point(2, Pose2d{5.0, 0.0, 0.0}); // at 2 22 | 23 | path.set_control_interval_counts({3, 2}); 24 | 25 | std::vector result = path.calculate_linear_initial_guess().x; 26 | std::vector expected = {0.0, 1.0, 2.0, 1.0, 3.0, 5.0}; 27 | 28 | CHECK(result == expected); 29 | } 30 | 31 | TEST_CASE("SwervePathBuilder - Spline initial guess", "[SwervePathBuilder]") { 32 | using namespace trajopt; 33 | 34 | trajopt::SwervePathBuilder path; 35 | path.wpt_initial_guess_point(0, Pose2d{0.0, 0.0, 0.0}); // at 0 36 | 37 | path.sgmt_initial_guess_points( 38 | 0, {Pose2d{1.0, 0.0, 0.0}, Pose2d{2.0, 0.0, 0.0}}); // from 0 to 1 39 | path.wpt_initial_guess_point(1, Pose2d{1.0, 0.0, 0.0}); // at 1 40 | 41 | path.sgmt_initial_guess_points(1, {Pose2d{3.0, 0.0, 0.0}}); // from 1 to 2 42 | path.wpt_initial_guess_point(2, Pose2d{5.0, 0.0, 0.0}); // at 2 43 | 44 | path.set_control_interval_counts({3, 2}); 45 | 46 | std::vector result = path.calculate_spline_initial_guess().x; 47 | std::vector expected = {0.0, 1.0, 2.0, 1.0, 3.0, 5.0}; 48 | 49 | REQUIRE(result.size() == expected.size()); 50 | for (size_t i = 0; i < expected.size(); ++i) { 51 | CHECK_THAT(result[i], WithinAbs(expected[i], 1e-15)); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /trajoptlib/test/src/geometry/rotation2d_test.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) TrajoptLib contributors 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | using Catch::Matchers::WithinAbs; 10 | 11 | inline constexpr double deg2rad(double deg) { 12 | return deg * std::numbers::pi / 180.0; 13 | } 14 | 15 | TEST_CASE("Rotation2d - Radians to degrees", "[Rotation2d]") { 16 | const trajopt::Rotation2d rot1{std::numbers::pi / 3.0}; 17 | const trajopt::Rotation2d rot2{std::numbers::pi / 4.0}; 18 | 19 | CHECK(rot1.degrees() == 60.0); 20 | CHECK_THAT(rot2.degrees(), WithinAbs(45.0, 1e-9)); 21 | } 22 | 23 | TEST_CASE("Rotation2d - Degrees to radians", "[Rotation2d]") { 24 | const auto rot1 = trajopt::Rotation2d{std::numbers::pi / 4}; 25 | const auto rot2 = trajopt::Rotation2d{std::numbers::pi / 6}; 26 | 27 | CHECK_THAT(rot1.radians(), WithinAbs(std::numbers::pi / 4.0, 1e-9)); 28 | CHECK(rot2.radians() == std::numbers::pi / 6.0); 29 | } 30 | 31 | TEST_CASE("Rotation2d - RotateByFromZero", "[Rotation2d]") { 32 | const trajopt::Rotation2d zero; 33 | auto rotated = zero + trajopt::Rotation2d{std::numbers::pi / 2}; 34 | 35 | CHECK(rotated.radians() == std::numbers::pi / 2.0); 36 | CHECK(rotated.degrees() == 90.0); 37 | } 38 | 39 | TEST_CASE("Rotation2d - RotateByNonZero", "[Rotation2d]") { 40 | auto rot = trajopt::Rotation2d{std::numbers::pi / 2}; 41 | rot = rot + trajopt::Rotation2d{std::numbers::pi / 6}; 42 | 43 | CHECK(rot.degrees() == 120.0); 44 | } 45 | 46 | TEST_CASE("Rotation2d - Minus", "[Rotation2d]") { 47 | const auto rot1 = trajopt::Rotation2d{deg2rad(70.0)}; 48 | const auto rot2 = trajopt::Rotation2d{std::numbers::pi / 6}; 49 | 50 | CHECK_THAT((rot1 - rot2).degrees(), WithinAbs(40, 1e-9)); 51 | } 52 | 53 | TEST_CASE("Rotation2d - Constexpr", "[Rotation2d]") { 54 | constexpr trajopt::Rotation2d default_ctor; 55 | 56 | static_assert(default_ctor.cos() == 1.0); 57 | static_assert(default_ctor.sin() == 0.0); 58 | } 59 | -------------------------------------------------------------------------------- /trajoptlib/include/trajopt/constraint/angular_velocity_max_magnitude_constraint.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) TrajoptLib contributors 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #include "trajopt/geometry/pose2.hpp" 11 | #include "trajopt/geometry/translation2.hpp" 12 | #include "trajopt/util/symbol_exports.hpp" 13 | 14 | namespace trajopt { 15 | 16 | /// Angular velocity max magnitude inequality constraint. 17 | class TRAJOPT_DLLEXPORT AngularVelocityMaxMagnitudeConstraint { 18 | public: 19 | /// Constructs an AngularVelocityMaxMagnitudeConstraint. 20 | /// 21 | /// @param max_magnitude The maximum angular velocity magnitude. Must be 22 | /// nonnegative. 23 | explicit AngularVelocityMaxMagnitudeConstraint(double max_magnitude) 24 | : m_max_magnitude{max_magnitude} { 25 | assert(max_magnitude >= 0.0); 26 | } 27 | 28 | /// Applies this constraint to the given problem. 29 | /// 30 | /// @param problem The optimization problem. 31 | /// @param pose The robot's pose. 32 | /// @param linear_velocity The robot's linear velocity. 33 | /// @param angular_velocity The robot's angular velocity. 34 | /// @param linear_acceleration The robot's linear acceleration. 35 | /// @param angular_acceleration The robot's angular acceleration. 36 | void apply( 37 | slp::Problem& problem, 38 | [[maybe_unused]] const Pose2v& pose, 39 | [[maybe_unused]] const Translation2v& linear_velocity, 40 | const slp::Variable& angular_velocity, 41 | [[maybe_unused]] const Translation2v& linear_acceleration, 42 | [[maybe_unused]] const slp::Variable& angular_acceleration) { 43 | if (m_max_magnitude == 0.0) { 44 | problem.subject_to(angular_velocity == 0.0); 45 | } else { 46 | problem.subject_to( 47 | slp::bounds(-m_max_magnitude, angular_velocity, m_max_magnitude)); 48 | } 49 | } 50 | 51 | private: 52 | double m_max_magnitude; 53 | }; 54 | 55 | } // namespace trajopt 56 | -------------------------------------------------------------------------------- /src/components/config/robotconfig/ModuleConfigPanel.tsx: -------------------------------------------------------------------------------- 1 | import { observer } from "mobx-react"; 2 | import { Component } from "react"; 3 | import { doc } from "../../../document/DocumentManager"; 4 | import ExpressionInput from "../../input/ExpressionInput"; 5 | import ExpressionInputList from "../../input/ExpressionInputList"; 6 | 7 | type Props = { rowGap: number }; 8 | 9 | type State = object; 10 | 11 | class RobotConfigPanel extends Component { 12 | render() { 13 | const config = doc.robotConfig; 14 | return ( 15 | 16 | 24 | 32 | 40 | 48 | 49 | 57 | 58 | ); 59 | } 60 | } 61 | export default observer(RobotConfigPanel); 62 | -------------------------------------------------------------------------------- /src/components/config/robotconfig/DimensionsConfigPanel.tsx: -------------------------------------------------------------------------------- 1 | import { observer } from "mobx-react"; 2 | import { Component } from "react"; 3 | import { doc } from "../../../document/DocumentManager"; 4 | import ExpressionInput from "../../input/ExpressionInput"; 5 | import ExpressionInputList from "../../input/ExpressionInputList"; 6 | 7 | type Props = { rowGap: number }; 8 | 9 | type State = object; 10 | 11 | class RobotConfigPanel extends Component { 12 | state = { selectedMotor: "NEO", currentLimit: 40 }; 13 | render() { 14 | const config = doc.robotConfig; 15 | return ( 16 | 17 | 24 | 25 | 32 | 33 | 41 | 42 | 50 | 51 | 59 | 60 | ); 61 | } 62 | } 63 | export default observer(RobotConfigPanel); 64 | -------------------------------------------------------------------------------- /trajoptlib/include/trajopt/constraint/linear_velocity_max_magnitude_constraint.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) TrajoptLib contributors 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #include "trajopt/geometry/pose2.hpp" 11 | #include "trajopt/geometry/translation2.hpp" 12 | #include "trajopt/util/symbol_exports.hpp" 13 | 14 | namespace trajopt { 15 | 16 | /// Linear velocity max magnitude inequality constraint. 17 | class TRAJOPT_DLLEXPORT LinearVelocityMaxMagnitudeConstraint { 18 | public: 19 | /// Constructs a LinearVelocityMaxMagnitudeConstraint. 20 | /// 21 | /// @param max_magnitude The maximum linear velocity magnitude. Must be 22 | /// nonnegative. 23 | explicit LinearVelocityMaxMagnitudeConstraint(double max_magnitude) 24 | : m_max_magnitude{max_magnitude} { 25 | assert(max_magnitude >= 0.0); 26 | } 27 | 28 | /// Applies this constraint to the given problem. 29 | /// 30 | /// @param problem The optimization problem. 31 | /// @param pose The robot's pose. 32 | /// @param linear_velocity The robot's linear velocity. 33 | /// @param angular_velocity The robot's angular velocity. 34 | /// @param linear_acceleration The robot's linear acceleration. 35 | /// @param angular_acceleration The robot's angular acceleration. 36 | void apply( 37 | slp::Problem& problem, 38 | [[maybe_unused]] const Pose2v& pose, 39 | const Translation2v& linear_velocity, 40 | [[maybe_unused]] const slp::Variable& angular_velocity, 41 | [[maybe_unused]] const Translation2v& linear_acceleration, 42 | [[maybe_unused]] const slp::Variable& angular_acceleration) { 43 | if (m_max_magnitude == 0.0) { 44 | problem.subject_to(linear_velocity.x() == 0.0); 45 | problem.subject_to(linear_velocity.y() == 0.0); 46 | } else { 47 | problem.subject_to(linear_velocity.squared_norm() <= 48 | m_max_magnitude * m_max_magnitude); 49 | } 50 | } 51 | 52 | private: 53 | double m_max_magnitude; 54 | }; 55 | 56 | } // namespace trajopt 57 | -------------------------------------------------------------------------------- /trajoptlib/include/trajopt/constraint/linear_acceleration_max_magnitude_constraint.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) TrajoptLib contributors 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | #include "trajopt/geometry/pose2.hpp" 11 | #include "trajopt/geometry/translation2.hpp" 12 | #include "trajopt/util/symbol_exports.hpp" 13 | 14 | namespace trajopt { 15 | 16 | /// Linear acceleration max magnitude inequality constraint. 17 | class TRAJOPT_DLLEXPORT LinearAccelerationMaxMagnitudeConstraint { 18 | public: 19 | /// Constructs a LinearAccelerationMaxMagnitudeConstraint. 20 | /// 21 | /// @param max_magnitude The maximum linear acceleration magnitude. Must be 22 | /// nonnegative. 23 | explicit LinearAccelerationMaxMagnitudeConstraint(double max_magnitude) 24 | : m_max_magnitude{max_magnitude} { 25 | assert(max_magnitude >= 0.0); 26 | } 27 | 28 | /// Applies this constraint to the given problem. 29 | /// 30 | /// @param problem The optimization problem. 31 | /// @param pose The robot's pose. 32 | /// @param linear_velocity The robot's linear velocity. 33 | /// @param angular_velocity The robot's angular velocity. 34 | /// @param linear_acceleration The robot's linear acceleration. 35 | /// @param angular_acceleration The robot's angular acceleration. 36 | void apply( 37 | slp::Problem& problem, 38 | [[maybe_unused]] const Pose2v& pose, 39 | [[maybe_unused]] const Translation2v& linear_velocity, 40 | [[maybe_unused]] const slp::Variable& angular_velocity, 41 | const Translation2v& linear_acceleration, 42 | [[maybe_unused]] const slp::Variable& angular_acceleration) { 43 | if (m_max_magnitude == 0.0) { 44 | problem.subject_to(linear_acceleration.x() == 0.0); 45 | problem.subject_to(linear_acceleration.y() == 0.0); 46 | } else { 47 | problem.subject_to(linear_acceleration.squared_norm() <= 48 | m_max_magnitude * m_max_magnitude); 49 | } 50 | } 51 | 52 | private: 53 | double m_max_magnitude; 54 | }; 55 | 56 | } // namespace trajopt 57 | -------------------------------------------------------------------------------- /src/components/config/ScopeSlider.tsx: -------------------------------------------------------------------------------- 1 | import { Slider, SliderProps } from "@mui/material"; 2 | import { observer } from "mobx-react"; 3 | import { Component } from "react"; 4 | import { IHolonomicWaypointStore } from "../../document/HolonomicWaypointStore"; 5 | 6 | type Props = { 7 | isRange: boolean; 8 | points: IHolonomicWaypointStore[]; 9 | startIndex: number; 10 | endIndex: number; 11 | setRange: (arg1: [number] | [number, number]) => void; 12 | sliderProps?: SliderProps; 13 | }; 14 | 15 | type State = object; 16 | 17 | class ScopeSlider extends Component { 18 | state = {}; 19 | render() { 20 | const isRange = this.props.isRange; 21 | const startIndex = this.props.startIndex; 22 | const endIndex = this.props.endIndex; 23 | const points = this.props.points; 24 | const pointcount = points.length; 25 | 26 | const sliderMarks = [ 27 | { value: 0, label: "Start" }, 28 | ...points.flatMap((_point, idx) => ({ value: idx + 1, label: idx + 1 })), 29 | { value: pointcount + 1, label: "End" } 30 | ]; 31 | return ( 32 |
33 | {" "} 34 | { 50 | let selection = []; 51 | if (typeof value === "number") { 52 | selection = [value] as [number]; 53 | } else { 54 | selection = value.slice(0, 2) as [number, number]; 55 | } 56 | this.props.setRange(selection); 57 | }} 58 | {...this.props.sliderProps} 59 | > 60 |
61 | ); 62 | } 63 | } 64 | export default observer(ScopeSlider); 65 | -------------------------------------------------------------------------------- /docs/contributing/contributing-guide.md: -------------------------------------------------------------------------------- 1 | # Contributing Guide 2 | 3 | Thank you for your interest in contributing to this project! We appreciate your help in making it better. 4 | 5 | ## Introduction 6 | 7 | This document outlines the guidelines for contributing to this project. It covers submitting issues, creating pull requests, and maintaining code quality. 8 | 9 | ## Submitting Issues 10 | 11 | If you encounter any issues or have suggestions for improvements, please follow these guidelines when submitting an issue: 12 | 13 | 1. Before submitting an issue, search the issue tracker to check if the issue has already been reported. 14 | 2. Provide a clear and descriptive title for the issue. 15 | 3. Include detailed steps to reproduce the issue. 16 | 4. Include any relevant error messages or screenshots. 17 | 5. Specify the version of the project you are using. 18 | 6. Be respectful and constructive in your communication. 19 | 20 | ## Pull Requests 21 | 22 | We welcome contributions in the form of pull requests. To submit a pull request, please follow these guidelines: 23 | 24 | 1. Fork the repository and create a new branch for your feature or bug fix. 25 | 2. Ensure your code follows the project's code style guidelines (see [Code Style](#code-style)). 26 | 3. Write clear and concise commit messages. 27 | 4. Include tests for your changes, if applicable. 28 | 5. Document any new features or changes in the project's documentation. 29 | 6. Submit the pull request and provide a detailed description of the changes made. 30 | 31 | ## Code Style 32 | 33 | To maintain a consistent codebase, we follow a set of code style guidelines. Please ensure that your code adheres to these guidelines before submitting a pull request. Some general guidelines include: 34 | 35 | - Use meaningful variable and function names. 36 | - Write clear and concise comments. 37 | - Follow the project's indentation and formatting conventions. 38 | - Keep lines of code within a reasonable length. 39 | - Remove any unnecessary code or comments. 40 | - Make sure to run `npm run fmt` before committing! 41 | 42 | ## License 43 | 44 | This project is licensed under the BSD-3-Clause License. By contributing to this project, you agree to license your contributions under the same license. 45 | -------------------------------------------------------------------------------- /choreolib/src/main/java/choreo/util/ChoreoAlert.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) Choreo contributors 2 | 3 | package choreo.util; 4 | 5 | import edu.wpi.first.wpilibj.Alert; 6 | import edu.wpi.first.wpilibj.Alert.AlertType; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | import java.util.function.Function; 10 | 11 | /** A utility class for creating alerts under the "Choreo Alerts" group. */ 12 | public class ChoreoAlert { 13 | /** 14 | * Creates an alert under the "Choreo" group. 15 | * 16 | * @param name The name of the alert 17 | * @param type The type of alert 18 | * @return an Alert published under the "Choreo" group 19 | */ 20 | public static Alert alert(String name, AlertType type) { 21 | return new Alert("Choreo Alerts", name, type); 22 | } 23 | 24 | /** 25 | * Creates a {@link MultiAlert} under the "Choreo" group. 26 | * 27 | * @param textGenerator A function that accepts a list of causes and returns an alert message 28 | * @param type The type of alert 29 | * @return a MultiAlert published under the "Choreo" group 30 | */ 31 | public static MultiAlert multiAlert( 32 | Function, String> textGenerator, AlertType type) { 33 | return new MultiAlert(textGenerator, type); 34 | } 35 | 36 | /** 37 | * An alert that can have multiple causes. Utilizes a function to generate an error message from a 38 | * list of causes. 39 | */ 40 | public static class MultiAlert extends Alert { 41 | private final Function, String> textGenerator; 42 | private final List causes = new ArrayList<>(); 43 | 44 | MultiAlert(Function, String> textGenerator, AlertType type) { 45 | super("Choreo Alerts", textGenerator.apply(List.of()), type); 46 | this.textGenerator = textGenerator; 47 | } 48 | 49 | /** 50 | * Adds an error causer to this alert, and pushes the alert to NetworkTables if it is not 51 | * already present. 52 | * 53 | * @param name The name of the error causer 54 | */ 55 | public void addCause(String name) { 56 | causes.add(name); 57 | setText(textGenerator.apply(causes)); 58 | set(true); 59 | } 60 | } 61 | 62 | /** Factory class. */ 63 | private ChoreoAlert() {} 64 | } 65 | -------------------------------------------------------------------------------- /src/components/field/svg/FieldEventMarkers.tsx: -------------------------------------------------------------------------------- 1 | import { observer } from "mobx-react"; 2 | import { Component } from "react"; 3 | import { doc } from "../../../document/DocumentManager"; 4 | 5 | import { sample } from "../../../util/MathUtil"; 6 | 7 | type MarkerProps = { 8 | x: number; 9 | y: number; 10 | selected: boolean; 11 | onSelect: () => void; 12 | }; 13 | 14 | type MarkerState = object; 15 | 16 | type Props = object; 17 | type State = object; 18 | class FieldEventMarker extends Component { 19 | render() { 20 | return ( 21 | 26 | 27 | 28 | this.props.onSelect()} 39 | /> 40 | 41 | 42 | ); 43 | } 44 | } 45 | 46 | class FieldEventMarkers extends Component { 47 | state = {}; 48 | 49 | render() { 50 | const path = doc.pathlist.activePath; 51 | const markers = path.markers; 52 | return markers.flatMap((marker) => { 53 | if (marker.from.timestamp === undefined) { 54 | return []; 55 | } 56 | const marked = sample(marker.from.timestamp, path.trajectory.samples); 57 | if (marked === undefined) { 58 | return <>; 59 | } 60 | return ( 61 | doc.setSelectedSidebarItem(marker)} 67 | > 68 | ); 69 | }); 70 | } 71 | } 72 | export default observer(FieldEventMarkers); 73 | -------------------------------------------------------------------------------- /src/components/navbar/Navbar.tsx: -------------------------------------------------------------------------------- 1 | import { Component } from "react"; 2 | import { uiState } from "../../document/DocumentManager"; 3 | import Tooltip from "@mui/material/Tooltip"; 4 | import styles from "./Navbar.module.css"; 5 | import { observer } from "mobx-react"; 6 | import { ToggleButton, ToggleButtonGroup } from "@mui/material"; 7 | import { NavbarItemData, NavbarItemSectionEnds } from "../../document/UIData"; 8 | 9 | type Props = object; 10 | 11 | type State = object; 12 | 13 | class Navbar extends Component { 14 | state = {}; 15 | 16 | render() { 17 | const { selectedNavbarItem, setSelectedNavbarItem } = uiState; 18 | return ( 19 |
20 | {NavbarItemSectionEnds.map((endSplit, sectionIdx) => ( 21 | { 26 | setSelectedNavbarItem(Number.parseInt(newSelection) ?? -1); 27 | }} 28 | key={sectionIdx} 29 | > 30 | {NavbarItemData.map( 31 | (item, index) => 32 | index <= endSplit && 33 | index > (NavbarItemSectionEnds[sectionIdx - 1] ?? -1) && ( 34 | 41 | 51 | {item.icon} 52 | 53 | 54 | ) 55 | )} 56 | 57 | ))} 58 |
59 | ); 60 | } 61 | } 62 | export default observer(Navbar); 63 | -------------------------------------------------------------------------------- /choreolib/src/test/java/choreo/auto/RoutineKillNoAllianceTest.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) Choreo contributors 2 | 3 | package choreo.auto; 4 | 5 | import static choreo.auto.AutoTestHelper.setAlliance; 6 | import static org.junit.jupiter.api.Assertions.assertEquals; 7 | import static org.junit.jupiter.api.Assertions.assertFalse; 8 | 9 | import edu.wpi.first.hal.HAL; 10 | import edu.wpi.first.wpilibj.DriverStation.Alliance; 11 | import java.util.Optional; 12 | import java.util.function.Supplier; 13 | import org.junit.jupiter.api.BeforeEach; 14 | import org.junit.jupiter.api.Test; 15 | 16 | public class RoutineKillNoAllianceTest { 17 | AutoFactory factoryFlip; 18 | AutoFactory factoryNoFlip; 19 | Supplier routineFlip = () -> factoryFlip.newRoutine("testRoutineKill"); 20 | Supplier routineNoFlip = () -> factoryNoFlip.newRoutine("testRoutineKill"); 21 | 22 | @BeforeEach 23 | void setup() { 24 | assert HAL.initialize(500, 0); 25 | factoryFlip = AutoTestHelper.factory(true); 26 | factoryNoFlip = AutoTestHelper.factory(false); 27 | } 28 | 29 | void testRoutineKill(Supplier routineSupplier, boolean expectKill) { 30 | AutoRoutine routine = routineSupplier.get(); 31 | assertFalse(routine.isKilled); 32 | routine.cmd().initialize(); 33 | // don't need to run, this should kill on schedule/initialize 34 | assertEquals(expectKill, routine.isKilled); 35 | } 36 | 37 | @Test 38 | void testUnFlippedEmpty() { 39 | setAlliance(Optional.empty()); 40 | testRoutineKill(routineNoFlip, false); 41 | } 42 | 43 | @Test 44 | void testUnFlippedBlue() { 45 | setAlliance((Optional.of(Alliance.Blue))); 46 | testRoutineKill(routineNoFlip, false); 47 | } 48 | 49 | @Test 50 | void testUnFlippedRed() { 51 | setAlliance(Optional.of(Alliance.Red)); 52 | testRoutineKill(routineNoFlip, false); 53 | } 54 | 55 | @Test 56 | void testFlippedEmpty() { 57 | setAlliance(Optional.empty()); 58 | testRoutineKill(routineFlip, true); 59 | } 60 | 61 | @Test 62 | void testFlippedBlue() { 63 | setAlliance(Optional.of(Alliance.Blue)); 64 | testRoutineKill(routineFlip, false); 65 | } 66 | 67 | @Test 68 | void testFlippedRed() { 69 | setAlliance(Optional.of(Alliance.Red)); 70 | testRoutineKill(routineFlip, false); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/components/sidebar/ProjectSaveStatusIndicator.tsx: -------------------------------------------------------------------------------- 1 | import { observer } from "mobx-react"; 2 | import React, { Component } from "react"; 3 | import { saveProject, saveProjectDialog } from "../../document/DocumentManager"; 4 | import { SavingState as SavingState } from "../../document/UIStateStore"; 5 | import { Check, FolderOff, PriorityHigh } from "@mui/icons-material"; 6 | import { IconButton, Tooltip } from "@mui/material"; 7 | import SaveInProgress from "../../assets/SaveInProgress"; 8 | 9 | type Props = { 10 | savingState: SavingState; 11 | iconClass?: string; 12 | }; 13 | 14 | type State = object; 15 | 16 | const icons = { 17 | [SavingState.ERROR]: () => , 18 | [SavingState.SAVING]: () => , 19 | [SavingState.NO_LOCATION]: () => , 20 | [SavingState.SAVED]: () => 21 | } satisfies { 22 | [K in SavingState]: () => React.JSX.Element; 23 | }; 24 | const tooltips = { 25 | [SavingState.ERROR]: "Error while Saving. Click to retry.", 26 | [SavingState.SAVING]: "Saving...", 27 | [SavingState.NO_LOCATION]: "Save Project", 28 | [SavingState.SAVED]: "Project Saved" 29 | } satisfies { 30 | [K in SavingState]: string; 31 | }; 32 | class SaveStatusIndicator extends Component { 33 | getIcon() { 34 | return icons[this.props.savingState]?.() ?? ; 35 | } 36 | getTooltip() { 37 | return tooltips[this.props.savingState] ?? ""; 38 | } 39 | isDisabled() { 40 | return this.props.savingState != SavingState.NO_LOCATION; 41 | } 42 | render() { 43 | return ( 44 | 45 | 46 | 47 | { 50 | const state = this.props.savingState; 51 | if (state == SavingState.NO_LOCATION) { 52 | saveProjectDialog(); 53 | } 54 | if (state == SavingState.ERROR) { 55 | saveProject(); 56 | } 57 | }} 58 | > 59 | {this.getIcon()} 60 | 61 | 62 | 63 | 64 | ); 65 | } 66 | } 67 | 68 | export default observer(SaveStatusIndicator); 69 | -------------------------------------------------------------------------------- /src/components/field/svg/constraintDisplay/PointAtOverlay.tsx: -------------------------------------------------------------------------------- 1 | import * as d3 from "d3"; 2 | import { observer } from "mobx-react"; 3 | import React, { Component } from "react"; 4 | import { IConstraintDataStore } from "../../../../document/ConstraintDataStore"; 5 | import { 6 | ConstraintKey, 7 | DataMap 8 | } from "../../../../document/ConstraintDefinitions"; 9 | import { doc } from "../../../../document/DocumentManager"; 10 | import { IHolonomicWaypointStore } from "../../../../document/HolonomicWaypointStore"; 11 | 12 | type Props = { 13 | data: IConstraintDataStore; 14 | start?: IHolonomicWaypointStore; 15 | end?: IHolonomicWaypointStore; 16 | lineColor: string; 17 | }; 18 | class PointAtOverlay extends Component, object> { 19 | rootRef: React.RefObject = React.createRef(); 20 | componentDidMount() { 21 | if (this.rootRef.current) { 22 | const dragHandleDrag = d3 23 | .drag() 24 | .on("drag", (event) => this.dragPointTranslate(event)) 25 | .on("start", () => { 26 | doc.history.startGroup(() => {}); 27 | }) 28 | .on("end", (_event) => doc.history.stopGroup()) 29 | .container(this.rootRef.current); 30 | d3.select(`#dragTarget-pointat`).call( 31 | dragHandleDrag 32 | ); 33 | } 34 | } 35 | dragPointTranslate(event: any) { 36 | this.props.data.x.set(event.x); 37 | this.props.data.y.set(event.y); 38 | } 39 | render() { 40 | if (this.props.start === undefined) { 41 | return <>; 42 | } 43 | const data = this.props.data.serialize as DataMap["PointAt"]; 44 | return ( 45 | 46 | 54 | 63 | 64 | ); 65 | } 66 | } 67 | export default observer(PointAtOverlay); 68 | -------------------------------------------------------------------------------- /choreolib/src/test/java/choreo/trajectory/TrajectoryTestHelper.java: -------------------------------------------------------------------------------- 1 | // Copyright (c) Choreo contributors 2 | 3 | package choreo.trajectory; 4 | 5 | import edu.wpi.first.math.geometry.Pose2d; 6 | import java.util.List; 7 | 8 | public class TrajectoryTestHelper { 9 | private static final double DT = 0.005; 10 | 11 | @SuppressWarnings("unchecked") 12 | public static > 13 | Trajectory linearTrajectory( 14 | String name, Pose2d start, Pose2d end, double duration, Class sampleType) { 15 | int sampleCount = (int) Math.floor(duration / DT); 16 | 17 | if (sampleCount < 2) { 18 | throw new IllegalArgumentException("Duration is too short for a trajectory"); 19 | } 20 | 21 | Trajectory trajectory = null; 22 | Pose2d lastPose = start; 23 | double lastVx = 0.01, lastVy = 0.01, lastOmega = 0.01; 24 | if (sampleType == SwerveSample.class) { 25 | SwerveSample[] samples = new SwerveSample[sampleCount]; 26 | for (int i = 0; i < sampleCount; i++) { 27 | double t = (i * DT) / duration; 28 | Pose2d pose = start.interpolate(end, t); 29 | 30 | double x = pose.getTranslation().getX(); 31 | double y = pose.getTranslation().getY(); 32 | double heading = pose.getRotation().getRadians(); 33 | 34 | double vx = (x - lastPose.getTranslation().getX()) / DT; 35 | double vy = (y - lastPose.getTranslation().getY()) / DT; 36 | double omega = (heading - lastPose.getRotation().getRadians()) / DT; 37 | 38 | double ax = (vx - lastVx) / DT; 39 | double ay = (vy - lastVy) / DT; 40 | double alpha = (omega - lastOmega) / DT; 41 | 42 | lastPose = pose; 43 | lastVx = vx; 44 | lastVy = vy; 45 | lastOmega = omega; 46 | 47 | samples[i] = 48 | new SwerveSample( 49 | i * DT, x, y, heading, vx, vy, omega, ax, ay, alpha, new double[4], new double[4]); 50 | } 51 | var st = new Trajectory(name, List.of(samples), List.of(), List.of()); 52 | trajectory = st; 53 | } else if (sampleType == DifferentialSample.class) { 54 | throw new IllegalArgumentException("DifferentialDrive not implemented"); 55 | } else { 56 | throw new IllegalArgumentException("Invalid sample type"); 57 | } 58 | 59 | return (Trajectory) trajectory; 60 | } 61 | } 62 | --------------------------------------------------------------------------------