Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
780c932
init: mecanum trajectory builder
polargh Apr 25, 2025
d8178ab
Merge pull request #1 from Null-Robotics/main
polargh Jun 11, 2025
9080311
feat: untested mecanum generator
Veratay Jul 4, 2025
955f297
feat: mecanum generation added to ui, still a bit buggy
Veratay Jul 17, 2025
828bc45
Add support for mecanum trajectory generation
Veratay Oct 25, 2025
ec43e9b
Update UI to include FTC field and drivetrain changes
Veratay Oct 25, 2025
a950d3f
fix: std::ziptransform in mecanum trajectory generation
polargh Nov 8, 2025
f899017
field configuration ui looks nice now
polargh Nov 8, 2025
e1372a3
Merge branch 'main' into dev/mecanum
polargh Nov 8, 2025
05b6234
fix[rs]: variable name
polargh Nov 8, 2025
c4c987a
Merge branch 'dev/mecanum' of github.com:NullFTC/choreo into dev/mecanum
polargh Nov 8, 2025
26b8ff0
feat(mecanum): strafing efficiency + static friction
polargh Nov 8, 2025
9396158
fix[docs]: comments
polargh Nov 8, 2025
7cf9be2
latest, pretty good model?
polargh Nov 8, 2025
a0999bd
feat(semantics): objective ~> cost
polargh Nov 11, 2025
46af5dd
fix: comment uses unicodeit
polargh Nov 11, 2025
df182c3
Merge branch 'main' into dev/mecanum
polargh Nov 11, 2025
9e5958e
Update trajoptlib/src/mecanum_trajectory_generator.cpp
polargh Nov 12, 2025
0891b1e
Update trajoptlib/src/mecanum_trajectory_generator.cpp
polargh Nov 12, 2025
f11691b
Update trajoptlib/src/mecanum_trajectory_generator.cpp
polargh Nov 12, 2025
d2b2194
Update trajoptlib/src/mecanum_trajectory_generator.cpp
polargh Nov 12, 2025
5ef86d5
Update trajoptlib/src/mecanum_trajectory_generator.cpp
polargh Nov 12, 2025
0660416
Update trajoptlib/src/mecanum_trajectory_generator.cpp
polargh Nov 12, 2025
af6e0c1
Update trajoptlib/src/mecanum_trajectory_generator.cpp
polargh Nov 12, 2025
036d6fa
Update trajoptlib/src/mecanum_trajectory_generator.cpp
polargh Nov 12, 2025
b408e5e
Update trajoptlib/src/mecanum_trajectory_generator.cpp
polargh Nov 12, 2025
aa5e7a1
Update trajoptlib/src/mecanum_trajectory_generator.cpp
polargh Nov 12, 2025
ba2c1b6
Update trajoptlib/src/mecanum_trajectory_generator.cpp
polargh Nov 12, 2025
9af2e7c
Remove multiple C++ file associations from settings
polargh Nov 12, 2025
5729810
fix(refactor): quick fix hardcoded numbers
polargh Nov 12, 2025
1f2ce91
fix: rem clangd + bun.lock
polargh Nov 12, 2025
1692119
fix: m/s^2
polargh Nov 12, 2025
722df71
field properly centered
polargh Nov 25, 2025
bd1f616
Merge branch 'main' into dev/mecanum
calcmogul Dec 11, 2025
c074e24
Run cargo fmt
calcmogul Dec 11, 2025
f8c2781
Run wpiformat and fix formatting
calcmogul Dec 11, 2025
6418022
Fix more formatting
calcmogul Dec 11, 2025
3818c92
Fix Doxygen error
calcmogul Dec 11, 2025
a9cf47d
Fix TypeScript compiler errors
calcmogul Dec 11, 2025
bc88d26
Fix eslint errors
calcmogul Dec 11, 2025
f095bf5
Fix clippy warning
calcmogul Dec 11, 2025
2ddd14f
Use std::numbers::sqrt2
calcmogul Dec 11, 2025
08c2467
Remove gravity member
calcmogul Dec 11, 2025
6127b3a
Remove unused penalties
calcmogul Dec 11, 2025
e498689
Clarify which forces are in body or world frame
calcmogul Dec 11, 2025
6afa297
Merge pull request #2 from calcmogul/dev/mecanum
polargh Dec 12, 2025
b1f0bc2
Merge branch 'dev/mecanum' into main
polargh Sep 9, 2026
733cf47
Merge pull request #4 from NullFTC/main
polargh Sep 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion src-core/src/generation/generate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use std::sync::OnceLock;
use std::sync::mpsc::{Receiver, Sender, channel};

use trajoptlib::{DifferentialTrajectory, SwerveTrajectory};
use trajoptlib::{DifferentialTrajectory, MecanumTrajectory, SwerveTrajectory};

use super::transformers::{
CallbackSetter, ConstraintSetter, DrivetrainAndBumpersSetter, IntervalCountSetter,
Expand All @@ -30,6 +30,9 @@ pub enum LocalProgressUpdate {
// Diff variant
update: Vec<Sample>,
},
MecanumTrajectory {
update: Vec<Sample>,
},
DiagnosticText {
update: String,
},
Expand Down Expand Up @@ -68,6 +71,14 @@ impl From<Vec<usize>> for LocalProgressUpdate {
}
}

impl From<MecanumTrajectory> for LocalProgressUpdate {
fn from(trajectory: MecanumTrajectory) -> Self {
LocalProgressUpdate::MecanumTrajectory {
update: trajectory.samples.iter().map(Sample::from).collect(),
}
}
}

pub struct HandledLocalProgressUpdate {
pub handle: i64,
pub update: LocalProgressUpdate,
Expand Down
15 changes: 15 additions & 0 deletions src-core/src/generation/remote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ pub enum RemoteProgressUpdate {
IncompleteTankTrajectory(Vec<Sample>),
IntervalCounts(Vec<usize>),
CompleteTrajectory(TrajectoryFile),
// Mecanum variant
IncompleteMecanumTrajectory(Vec<Sample>),
CompleteTrajectory(Trajectory),
Error(ChoreoError),
}

Expand Down Expand Up @@ -123,6 +126,11 @@ pub fn remote_generate_child(args: RemoteArgs) {
update: LocalProgressUpdate::IntervalCounts { update },
..
} => serde_json::to_string(&RemoteProgressUpdate::IntervalCounts(update)),
update: LocalProgressUpdate::MecanumTrajectory { update },
..
} => serde_json::to_string(&RemoteProgressUpdate::IncompleteMecanumTrajectory(
update,
)),
_ => continue,
}
.expect("Failed to serialize progress update");
Expand Down Expand Up @@ -317,6 +325,13 @@ pub async fn remote_generate_parent(
}.handled(handle)
);
},
Ok(RemoteProgressUpdate::IncompleteMecanumTrajectory(trajectory)) => {
remote_resources.emit_progress(
LocalProgressUpdate::MecanumTrajectory {
update: trajectory
}.handled(handle)
);
},
Ok(RemoteProgressUpdate::CompleteTrajectory(trajectory)) => {
break Ok(trajectory);
},
Expand Down
23 changes: 21 additions & 2 deletions src-core/src/generation/transformers/callback.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use trajoptlib::{DifferentialTrajectory, SwerveTrajectory};
use trajoptlib::{DifferentialTrajectory, MecanumTrajectory, SwerveTrajectory};

use crate::{
ResultExt,
Expand All @@ -7,7 +7,7 @@ use crate::{

use super::{
DifferentialGenerationTransformer, FeatureLockedTransformer, GenerationContext,
SwerveGenerationTransformer,
MecanumGenerationTransformer, SwerveGenerationTransformer,
};

pub struct CallbackSetter;
Expand All @@ -30,6 +30,15 @@ fn differential_status_callback(trajectory: DifferentialTrajectory, handle: i64)
};
}

fn mecanum_status_callback(trajectory: MecanumTrajectory, handle: i64) {
let tx_opt = PROGRESS_SENDER_LOCK.get();
if let Some(tx) = tx_opt {
let _ = tx
.send(LocalProgressUpdate::from(trajectory).handled(handle))
.trace_warn();
};
}

impl SwerveGenerationTransformer for CallbackSetter {
fn initialize(_: &GenerationContext) -> FeatureLockedTransformer<Self> {
FeatureLockedTransformer::always(Self)
Expand All @@ -49,3 +58,13 @@ impl DifferentialGenerationTransformer for CallbackSetter {
generator.add_callback(differential_status_callback);
}
}

impl MecanumGenerationTransformer for CallbackSetter {
fn initialize(_: &GenerationContext) -> FeatureLockedTransformer<Self> {
FeatureLockedTransformer::always(Self)
}

fn transform(&self, generator: &mut trajoptlib::MecanumTrajectoryGenerator) {
generator.add_callback(mecanum_status_callback);
}
}
70 changes: 69 additions & 1 deletion src-core/src/generation/transformers/constraints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::spec::trajectory::{ConstraintData, ConstraintIDX, ConstraintScope, Wa

use super::{
DifferentialGenerationTransformer, FeatureLockedTransformer, GenerationContext,
SwerveGenerationTransformer,
MecanumGenerationTransformer, SwerveGenerationTransformer,
};

fn fix_scope(idx: usize, removed_idxs: &[usize]) -> usize {
Expand Down Expand Up @@ -228,3 +228,71 @@ impl DifferentialGenerationTransformer for ConstraintSetter {
}
}
}

impl MecanumGenerationTransformer for ConstraintSetter {
fn initialize(context: &GenerationContext) -> FeatureLockedTransformer<Self> {
Self::initialize(context)
}

fn transform(&self, generator: &mut trajoptlib::MecanumTrajectoryGenerator) {
for constraint in &self.constraint_idx {
let from = fix_scope(constraint.from, &self.guess_points);
let to_opt = constraint.to.map(|idx| fix_scope(idx, &self.guess_points));
match constraint.data {
ConstraintData::PointAt {
x,
y,
tolerance,
flip,
} => match to_opt {
None => generator.wpt_point_at(from, x, y, tolerance, flip),
Some(to) => generator.sgmt_point_at(from, to, x, y, tolerance, flip),
},
ConstraintData::MaxVelocity { max } => match to_opt {
None => generator.wpt_linear_velocity_max_magnitude(from, max),
Some(to) => generator.sgmt_linear_velocity_max_magnitude(from, to, max),
},
ConstraintData::MaxAcceleration { max } => match to_opt {
None => generator.wpt_linear_acceleration_max_magnitude(from, max),
Some(to) => generator.sgmt_linear_acceleration_max_magnitude(from, to, max),
},
ConstraintData::MaxAngularVelocity { max } => match to_opt {
None => generator.wpt_angular_velocity_max_magnitude(from, max),
Some(to) => generator.sgmt_angular_velocity_max_magnitude(from, to, max),
},
ConstraintData::StopPoint {} => {
if to_opt.is_none() {
generator.wpt_linear_velocity_max_magnitude(from, 0.0f64);
generator.wpt_angular_velocity_max_magnitude(from, 0.0f64);
}
}
ConstraintData::KeepInCircle { x, y, r } => match to_opt {
None => generator.wpt_keep_in_circle(from, x, y, r),
Some(to) => generator.sgmt_keep_in_circle(from, to, x, y, r),
},
ConstraintData::KeepInRectangle { x, y, w, h } => {
let xs = vec![x, x + w, x + w, x];
let ys = vec![y, y, y + h, y + h];
match to_opt {
None => generator.wpt_keep_in_polygon(from, xs, ys),
Some(to) => generator.sgmt_keep_in_polygon(from, to, xs, ys),
}
}
ConstraintData::KeepInLane { tolerance } => {
if let Some(idx_to) = to_opt
&& let Some(wpt_from) = self.waypoint_idx.get(from)
&& let Some(wpt_to) = self.waypoint_idx.get(idx_to)
{
generator.sgmt_keep_in_lane(
from, idx_to, wpt_from.x, wpt_from.y, wpt_to.x, wpt_to.y, tolerance,
);
}
}
ConstraintData::KeepOutCircle { x, y, r } => match to_opt {
None => generator.wpt_keep_out_circle(from, x, y, r),
Some(to) => generator.sgmt_keep_out_circle(from, to, x, y, r),
},
};
}
}
}
36 changes: 34 additions & 2 deletions src-core/src/generation/transformers/drivetrain_and_bumpers.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use trajoptlib::{DifferentialDrivetrain, SwerveDrivetrain};
use trajoptlib::{DifferentialDrivetrain, MecanumDrivetrain, SwerveDrivetrain};

use crate::spec::project::RobotConfig;

use super::{
DifferentialGenerationTransformer, FeatureLockedTransformer, GenerationContext,
SwerveGenerationTransformer,
MecanumGenerationTransformer, SwerveGenerationTransformer,
};

pub struct DrivetrainAndBumpersSetter {
Expand Down Expand Up @@ -70,3 +70,35 @@ impl DifferentialGenerationTransformer for DrivetrainAndBumpersSetter {
);
}
}

impl MecanumGenerationTransformer for DrivetrainAndBumpersSetter {
fn initialize(context: &GenerationContext) -> FeatureLockedTransformer<Self> {
FeatureLockedTransformer::always(Self {
config: context.project.config.snapshot(),
})
}

fn transform(&self, generator: &mut trajoptlib::MecanumTrajectoryGenerator) {
let config = &self.config;
let drivetrain = MecanumDrivetrain {
mass: config.mass,
moi: config.inertia,
wheel_radius: config.radius,
// rad per sec
wheel_max_angular_velocity: config.vmax / config.gearing,
wheel_max_torque: config.tmax * config.gearing,
wheel_cof: config.cof,
static_friction_coefficient: 0.0,
strafe_efficiency: 0.75,
modules: config.module_translations(),
};

generator.set_drivetrain(&drivetrain);
generator.set_bumpers(
config.bumper.front,
config.bumper.side,
config.bumper.side,
config.bumper.back,
);
}
}
55 changes: 54 additions & 1 deletion src-core/src/generation/transformers/interval_count.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::spec::trajectory::Waypoint;

use super::{
DifferentialGenerationTransformer, FeatureLockedTransformer, GenerationContext,
SwerveGenerationTransformer,
MecanumGenerationTransformer, SwerveGenerationTransformer,
};

pub struct IntervalCountSetter {
Expand Down Expand Up @@ -108,3 +108,56 @@ impl DifferentialGenerationTransformer for IntervalCountSetter {
generator.set_control_interval_counts(control_interval_counts);
}
}

impl MecanumGenerationTransformer for IntervalCountSetter {
fn initialize(context: &GenerationContext) -> FeatureLockedTransformer<Self> {
FeatureLockedTransformer::always(Self {
counts: guess_control_interval_counts(
&context.project.config.snapshot(),
&context.params,
)
.unwrap_or_default(),
waypoints: context.params.waypoints.clone(),
})
}

fn transform(&self, generator: &mut trajoptlib::MecanumTrajectoryGenerator) {
let waypoints = &self.waypoints;
let mut guess_points_after_waypoint = Vec::new();
let mut control_interval_counts = Vec::new();
let mut wpt_cnt = 0;
for i in 0..waypoints.len() {
let wpt = &waypoints[i];
// add initial guess points (actually unconstrained empty wpts in Choreo terms)
if wpt.is_initial_guess && !wpt.fix_heading && !wpt.fix_translation {
let guess_point = Pose2d {
x: wpt.x,
y: wpt.y,
heading: wpt.heading,
};
guess_points_after_waypoint.push(guess_point);
if let Some(last) = control_interval_counts.last_mut() {
*last += self.counts[i];
}
} else {
if wpt_cnt > 0 {
generator.sgmt_initial_guess_points(wpt_cnt - 1, &guess_points_after_waypoint);
}
guess_points_after_waypoint.clear();
if wpt.fix_heading && wpt.fix_translation {
generator.pose_wpt(wpt_cnt, wpt.x, wpt.y, wpt.heading);
} else if wpt.fix_translation {
generator.translation_wpt(wpt_cnt, wpt.x, wpt.y, wpt.heading);
} else {
generator.empty_wpt(wpt_cnt, wpt.x, wpt.y, wpt.heading);
}
wpt_cnt += 1;
if i != waypoints.len() - 1 {
control_interval_counts.push(self.counts[i]);
}
}
}

generator.set_control_interval_counts(control_interval_counts);
}
}
Loading
Loading