Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion choreolib/py/choreo/test/choreolib_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
TRAJECTORY = """
{
"name":"New Path",
"version":1,
"version":2,
"snapshot":{
"waypoints":[
{"x":0.0, "y":0.0, "heading":0.0, "intervals":9, "split":false, "fixTranslation":true, "fixHeading":true, "overrideIntervals":false},
Expand Down
6 changes: 3 additions & 3 deletions choreolib/py/choreo/test/resources/swerve_test.traj
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
{
"name":"test",
"version":1,
"version":2,
"snapshot":{
"waypoints":[
{"x":2.6185336112976074, "y":6.034867286682129, "heading":0.0, "intervals":16, "split":false, "fixTranslation":true, "fixHeading":true, "overrideIntervals":false},
{"x":6.458227634429932, "y":4.456109523773193, "heading":0.0, "intervals":40, "split":false, "fixTranslation":true, "fixHeading":true, "overrideIntervals":false}],
"constraints":[
{"from":"first", "to":null, "data":{"type":"StopPoint", "props":{}}, "enabled":true},
{"from":"last", "to":null, "data":{"type":"StopPoint", "props":{}}, "enabled":true},
{"from":"first", "to":"last", "data":{"type":"KeepInRectangle", "props":{"x":0.0, "y":0.0, "w":16.54, "h":8.21}}, "enabled":true}],
{"from":"first", "to":"last", "data":{"type":"KeepInRectangle", "props":{"x":8.27, "y":4.105, "w":16.54, "h":8.21, "rotation":0.0}}, "enabled":true}],
"targetDt":0.05
},
"params":{
Expand All @@ -18,7 +18,7 @@
"constraints":[
{"from":"first", "to":null, "data":{"type":"StopPoint", "props":{}}, "enabled":true},
{"from":"last", "to":null, "data":{"type":"StopPoint", "props":{}}, "enabled":true},
{"from":"first", "to":"last", "data":{"type":"KeepInRectangle", "props":{"x":{"exp":"0 m", "val":0.0}, "y":{"exp":"0 m", "val":0.0}, "w":{"exp":"16.54 m", "val":16.54}, "h":{"exp":"8.21 m", "val":8.21}}}, "enabled":true}],
{"from":"first", "to":"last", "data":{"type":"KeepInRectangle", "props":{"x":{"exp":"8.27 m", "val":8.27}, "y":{"exp":"4.105 m", "val":4.105}, "w":{"exp":"16.54 m", "val":16.54}, "h":{"exp":"8.21 m", "val":8.21}, "rotation":{"exp":"0 deg", "val":0.0}}}, "enabled":true}],
"targetDt":{
"exp":"0.05 s",
"val":0.05
Expand Down
2 changes: 1 addition & 1 deletion choreolib/py/choreo/util/traj_schema_version.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# Auto-generated by update_traj_schema.py
TRAJ_SCHEMA_VERSION = 1
TRAJ_SCHEMA_VERSION = 2
2 changes: 1 addition & 1 deletion choreolib/src/main/java/choreo/util/TrajSchemaVersion.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
/** Internal autogenerated class for storing the current trajectory schema version. */
public class TrajSchemaVersion {
/** The current trajectory schema version. */
public static final int TRAJ_SCHEMA_VERSION = 1;
public static final int TRAJ_SCHEMA_VERSION = 2;

/** Utility class. */
private TrajSchemaVersion() {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
namespace choreo {

[[deprecated("Use kTrajSchemaVersion.")]]
inline constexpr uint32_t kTrajSpecVersion = 1;
inline constexpr uint32_t kTrajSchemaVersion = 1;
inline constexpr uint32_t kTrajSpecVersion = 2;
inline constexpr uint32_t kTrajSchemaVersion = 2;

} // namespace choreo
2 changes: 1 addition & 1 deletion choreolib/src/test/java/choreo/ChoreoTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public class ChoreoTests {
"""
{
"name":"New Path",
"version":1,
"version":2,
"snapshot":{
"waypoints":[
{"x":0.0, "y":0.0, "heading":0.0, "intervals":9, "split":false, "fixTranslation":true, "fixHeading":true, "overrideIntervals":false},
Expand Down
78 changes: 72 additions & 6 deletions src-core/src/generation/transformers/constraints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,36 @@ fn fix_scope(idx: usize, removed_idxs: &[usize]) -> usize {
idx - to_subtract
}

/// Rotates a point around another point in 2D space.
///
/// ```text
/// [x_new] [rot.cos, -rot.sin][x - other.x] [other.x]
/// [y_new] = [rot.sin, rot.cos][y - other.y] + [other.y]
/// ```
///
/// # Arguments
/// * `point` - The point to rotate as (x, y)
/// * `center` - The center point to rotate around as (x, y)
/// * `rotation` - The rotation angle in radians
///
/// # Returns
/// The new rotated point as (x, y)
fn rotate_around(point: (f64, f64), center: (f64, f64), rotation: f64) -> (f64, f64) {
let cos_r = rotation.cos();
let sin_r = rotation.sin();

// Translate to origin (center of rotation)
let rel_x = point.0 - center.0;
let rel_y = point.1 - center.1;

// Apply rotation
let rotated_x = rel_x * cos_r - rel_y * sin_r;
let rotated_y = rel_x * sin_r + rel_y * cos_r;

// Translate back
(center.0 + rotated_x, center.1 + rotated_y)
}

pub struct ConstraintSetter {
guess_points: Vec<usize>,
constraint_idx: Vec<ConstraintIDX<f64>>,
Expand Down Expand Up @@ -131,9 +161,27 @@ impl SwerveGenerationTransformer for ConstraintSetter {
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];
ConstraintData::KeepInRectangle { x, y, w, h, rotation } => {
// x, y now represent the center of the rectangle
let center = (x, y);

// Original corner points relative to center
let corners = vec![
(x - w / 2.0, y - h / 2.0), // bottom-left
(x + w / 2.0, y - h / 2.0), // bottom-right
(x + w / 2.0, y + h / 2.0), // top-right
(x - w / 2.0, y + h / 2.0), // top-left
];

let mut xs = Vec::new();
let mut ys = Vec::new();

for corner in corners {
let (rotated_x, rotated_y) = rotate_around(corner, center, rotation);
xs.push(rotated_x);
ys.push(rotated_y);
}

match to_opt {
None => generator.wpt_keep_in_polygon(from, xs, ys),
Some(to) => generator.sgmt_keep_in_polygon(from, to, xs, ys),
Expand Down Expand Up @@ -209,9 +257,27 @@ impl DifferentialGenerationTransformer for ConstraintSetter {
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];
ConstraintData::KeepInRectangle { x, y, w, h, rotation } => {
// x, y now represent the center of the rectangle
let center = (x, y);

// Original corner points relative to center
let corners = vec![
(x - w / 2.0, y - h / 2.0), // bottom-left
(x + w / 2.0, y - h / 2.0), // bottom-right
(x + w / 2.0, y + h / 2.0), // top-right
(x - w / 2.0, y + h / 2.0), // top-left
];

let mut xs = Vec::new();
let mut ys = Vec::new();

for corner in corners {
let (rotated_x, rotated_y) = rotate_around(corner, center, rotation);
xs.push(rotated_x);
ys.push(rotated_y);
}

match to_opt {
None => generator.wpt_keep_in_polygon(from, xs, ys),
Some(to) => generator.sgmt_keep_in_polygon(from, to, xs, ys),
Expand Down
2 changes: 1 addition & 1 deletion src-core/src/spec/traj_schema_version.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
// Auto-generated by update_traj_schema.py
pub const TRAJ_SCHEMA_VERSION: u32 = 1;
pub const TRAJ_SCHEMA_VERSION: u32 = 2;
11 changes: 9 additions & 2 deletions src-core/src/spec/trajectory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ pub enum ConstraintData<T: SnapshottableType> {
/// A constraint to contain the bumpers within a circlular region of the field
KeepInCircle { x: T, y: T, r: T },
/// A constraint to contain the bumpers within a rectangular region of the field
KeepInRectangle { x: T, y: T, w: T, h: T },
KeepInRectangle { x: T, y: T, w: T, h: T, rotation: T },
/// A constraint to contain the bumpers within two line
KeepInLane { tolerance: T },
/// A constraint to contain the bumpers outside a circlular region of the field
Expand Down Expand Up @@ -192,11 +192,18 @@ impl<T: SnapshottableType> ConstraintData<T> {
y: y.snapshot(),
r: r.snapshot(),
},
ConstraintData::KeepInRectangle { x, y, w, h } => ConstraintData::KeepInRectangle {
ConstraintData::KeepInRectangle {
x,
y,
w,
h,
rotation,
} => ConstraintData::KeepInRectangle {
x: x.snapshot(),
y: y.snapshot(),
w: w.snapshot(),
h: h.snapshot(),
rotation: rotation.snapshot(),
},
ConstraintData::KeepInLane { tolerance } => ConstraintData::KeepInLane {
tolerance: tolerance.snapshot(),
Expand Down
134 changes: 134 additions & 0 deletions src-core/src/spec/upgraders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ mod traj_file {
fn make_upgrader() -> Upgrader {
let mut upgrader = Upgrader::new(TRAJ_SCHEMA_VERSION);
upgrader.add_version_action(up_0_1);
upgrader.add_version_action(up_1_2);
// Ensure the new upgrader is added here
upgrader
}
Expand All @@ -34,6 +35,139 @@ mod traj_file {
)
}

fn up_1_2(editor: &mut Editor) -> ChoreoResult<()> {
use crate::spec::Expr;
use serde_json::Value as JsonValue;

// Add rotation field to all KeepInRectangle constraints in both snapshot and params

// Handle snapshot constraints
if editor.has_path("snapshot.constraints") {
let snapshot_constraints: Vec<JsonValue> = editor.get_path("snapshot.constraints")?;
let mut updated_constraints = Vec::new();

for mut constraint in snapshot_constraints {
if let Some(data_type) = constraint["data"]["type"].as_str() {
if data_type == "KeepInRectangle" {
// Add rotation field and convert coordinates from bottom-left to center
if let Some(props) = constraint["data"]["props"].as_object_mut() {
// Get existing values
let x = props.get("x").and_then(|v| v.as_f64()).unwrap_or(0.0);
let y = props.get("y").and_then(|v| v.as_f64()).unwrap_or(0.0);
let w = props.get("w").and_then(|v| v.as_f64()).unwrap_or(1.0);
let h = props.get("h").and_then(|v| v.as_f64()).unwrap_or(1.0);

// Convert from bottom-left to center coordinates
let center_x = x + w / 2.0;
let center_y = y + h / 2.0;

// Update x,y to be center coordinates
props.insert(
"x".to_string(),
JsonValue::Number(serde_json::Number::from_f64(center_x).unwrap()),
);
props.insert(
"y".to_string(),
JsonValue::Number(serde_json::Number::from_f64(center_y).unwrap()),
);

// Add rotation field
props.insert(
"rotation".to_string(),
JsonValue::Number(serde_json::Number::from_f64(0.0).unwrap()),
);
}
}
}
updated_constraints.push(constraint);
}

editor.set_path_serialize("snapshot.constraints", updated_constraints)?;
}

// Handle params constraints
if editor.has_path("params.constraints") {
let params_constraints: Vec<JsonValue> = editor.get_path("params.constraints")?;
let mut updated_constraints = Vec::new();

for mut constraint in params_constraints {
if let Some(data_type) = constraint["data"]["type"].as_str() {
if data_type == "KeepInRectangle" {
// Add rotation field and convert coordinates from bottom-left to center
if let Some(props) = constraint["data"]["props"].as_object_mut() {
// Get existing values from Expr objects
let x_val = props
.get("x")
.and_then(|v| v.get("val"))
.and_then(|v| v.as_f64())
.unwrap_or(0.0);
let y_val = props
.get("y")
.and_then(|v| v.get("val"))
.and_then(|v| v.as_f64())
.unwrap_or(0.0);
let w_val = props
.get("w")
.and_then(|v| v.get("val"))
.and_then(|v| v.as_f64())
.unwrap_or(1.0);
let h_val = props
.get("h")
.and_then(|v| v.get("val"))
.and_then(|v| v.as_f64())
.unwrap_or(1.0);

// Convert from bottom-left to center coordinates
let center_x = x_val + w_val / 2.0;
let center_y = y_val + h_val / 2.0;

// Update x,y to be center coordinates (preserve expression strings but update values)
if let Some(x_expr) = props.get_mut("x") {
if let Some(x_obj) = x_expr.as_object_mut() {
x_obj.insert(
"val".to_string(),
JsonValue::Number(
serde_json::Number::from_f64(center_x).unwrap(),
),
);
x_obj.insert(
"exp".to_string(),
JsonValue::String(format!("{} m", center_x)),
);
}
}
if let Some(y_expr) = props.get_mut("y") {
if let Some(y_obj) = y_expr.as_object_mut() {
y_obj.insert(
"val".to_string(),
JsonValue::Number(
serde_json::Number::from_f64(center_y).unwrap(),
),
);
y_obj.insert(
"exp".to_string(),
JsonValue::String(format!("{} m", center_y)),
);
}
}

// Add rotation field
props.insert(
"rotation".to_string(),
serde_json::to_value(Expr::new("0 deg", 0.0))?,
);
}
}
}
updated_constraints.push(constraint);
}

editor.set_path_serialize("params.constraints", updated_constraints)?;
}

Ok(())
}

#[cfg(test)]
mod tests {
use crate::spec::upgraders::testing_shared::{get_contents, FileType};
Expand Down
Loading
Loading