Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
1839a19
added plumbing for control interval count setting
LewisSeiden Oct 29, 2023
1647cc9
added function to optimize control interval counts
LewisSeiden Oct 29, 2023
bb76ec4
added control interval count heuristic
LewisSeiden Oct 30, 2023
abffde7
fixed animation bar
LewisSeiden Oct 30, 2023
d61cf2f
added path settings menu
LewisSeiden Nov 1, 2023
ade32d5
fixed initial guess points with culling
LewisSeiden Nov 1, 2023
c8b1fca
added generator settings to save file
LewisSeiden Nov 2, 2023
dbab02c
removed unusued settings button
LewisSeiden Nov 5, 2023
a08de9a
started obstacles work
LewisSeiden Nov 11, 2023
41ace4b
got hardcoded obstacles working convergence is bad
LewisSeiden Nov 12, 2023
b4bc09c
switched trajoptlib to github build instead of local build
LewisSeiden Dec 5, 2023
5ebb141
merged main into obstacles
LewisSeiden Dec 5, 2023
26eb681
plumbed save file circle obstacles to solver
LewisSeiden Dec 12, 2023
0d88061
added obstacles to field overlay
LewisSeiden Dec 13, 2023
5f617e5
obstacles appear on field, in sidebar, can be added
LewisSeiden Dec 23, 2023
01115de
circle obstacles are draggable
LewisSeiden Dec 24, 2023
b7de57b
properly load obstacles from saved file
LewisSeiden Dec 24, 2023
d74d812
Merge branch 'main' into feature/obstacles
LewisSeiden Dec 24, 2023
c820489
ran formatter
LewisSeiden Dec 24, 2023
c57fc98
made obstacles pretty
LewisSeiden Dec 27, 2023
bc01b79
fixed obstacle drag
LewisSeiden Dec 27, 2023
44a9f5c
resolved pr feedback
LewisSeiden Dec 27, 2023
3778cce
obstacles in sidebar index from 1
LewisSeiden Dec 27, 2023
3a4ff81
ran formatter
LewisSeiden Dec 28, 2023
0fcb168
fix selection layers, delete hotkey
LewisSeiden Dec 28, 2023
10dd59a
Merge branch 'main' into feature/obstacles
LewisSeiden Dec 28, 2023
9079e2d
ran formatter
LewisSeiden Dec 29, 2023
d20f297
updated trajoptlib version
LewisSeiden Dec 29, 2023
69b4b01
merged main into feature/obstacles
LewisSeiden Dec 29, 2023
79dd13c
updated cargo.toml
LewisSeiden Dec 29, 2023
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
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 2 additions & 3 deletions src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,10 @@ license = "BSD-3-Clause"
tauri-build = { version = "1.4", features = [] }

[dependencies]
tauri = { version = "1.4", features = [ "window-set-title", "path-all", "dialog-confirm", "dialog-save", "dialog-open", "fs-all", "shell-open", "devtools"] }
tauri = { version = "1.4", features = [ "dialog-confirm", "dialog-save", "dialog-open", "dialog-save", "fs-all", "shell-open", "devtools" ] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
trajoptlib = { git = "https://github.com/SleipnirGroup/TrajoptLib.git", rev = "20bb8c2ef016ceedab0dc5c0271ad9b69048039a" }

trajoptlib = { git = "https://github.com/SleipnirGroup/TrajoptLib.git", rev = "c9f8140e92dff07b2edb9c7034fea4d18dc46c9a" }

[features]
# this feature is used for production builds or when `devPath` points to the filesystem
Expand Down
41 changes: 37 additions & 4 deletions src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ struct ChoreoSegmentScope {
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
enum ChoreoConstraintScope {

Segment([usize;2]),
Waypoint([usize;1])
}
Expand All @@ -73,6 +72,25 @@ enum Constraints {
// Also add the constraint type here
//define_enum_macro!(BoundsZeroVelocity, WptVelocityDirection, WptZeroVelocity);

#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[allow(non_snake_case)]
struct CircleObstacle {
x: f64, y: f64, radius: f64
}

#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[allow(non_snake_case)]
struct PolygonObstacle {
x: Vec<f64>, y: Vec<f64>, radius: f64
}

#[derive(Debug, serde::Serialize, serde::Deserialize)]
#[allow(non_snake_case)]
enum Obstacle {
Circle(CircleObstacle),
Polygon(PolygonObstacle)
}

fn fix_scope(idx: usize, removed_idxs: &Vec<usize>) -> usize {
let mut to_subtract: usize = 0;
for removed in removed_idxs {
Expand All @@ -89,8 +107,15 @@ async fn cancel() {
builder.cancel_all();
}

#[allow(non_snake_case)]
#[tauri::command]
async fn generate_trajectory(path: Vec<ChoreoWaypoint>, config: ChoreoRobotConfig, constraints: Vec<Constraints>) -> Result<HolonomicTrajectory, String> {
async fn generate_trajectory(
path: Vec<ChoreoWaypoint>,
config: ChoreoRobotConfig,
constraints: Vec<Constraints>,
circleObstacles: Vec<CircleObstacle>,
polygonObstacles: Vec<PolygonObstacle>
) -> Result<HolonomicTrajectory, String> {

let mut path_builder = SwervePathBuilder::new();
let mut wpt_cnt : usize = 0;
Expand Down Expand Up @@ -239,8 +264,16 @@ async fn generate_trajectory(path: Vec<ChoreoWaypoint>, config: ChoreoRobotConfi
}
]
};
//path_builder.set_bumpers(config.bumperLength, config.bumperWidth);
path_builder.sgmt_circle_obstacle(0, path.len()-1, 3.0, 3.0, 1.0);

path_builder.set_bumpers(config.bumperLength, config.bumperWidth);

for o in circleObstacles {
path_builder.sgmt_circle_obstacle(0, wpt_cnt - 1, o.x, o.y, o.radius);
}

for o in polygonObstacles {
path_builder.sgmt_polygon_obstacle(0, wpt_cnt - 1, o.x, o.y, o.radius);
}
path_builder.set_drivetrain(&drivetrain);
path_builder.generate()
}
Expand Down
61 changes: 61 additions & 0 deletions src/components/config/CircularObstacleConfigPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { observer } from "mobx-react";
import React, { Component } from "react";
import DocumentManagerContext from "../../document/DocumentManager";
import styles from "./WaypointConfigPanel.module.css";
import InputList from "../input/InputList";
import Input from "../input/Input";
import { ICircularObstacleStore } from "../../document/CircularObstacleStore";

type Props = { obstacle: ICircularObstacleStore | null };

type State = {};

class CircularObstacleConfigPanel extends Component<Props, State> {
static contextType = DocumentManagerContext;
declare context: React.ContextType<typeof DocumentManagerContext>;
state = {};
render() {
let { obstacle } = this.props;
if (obstacle !== null) {
return (
<div className={styles.WaypointPanel}>
<InputList noCheckbox>
<Input
title="x"
suffix="m"
enabled={true}
setEnabled={(a) => null}
number={obstacle.x}
setNumber={(x) => obstacle!.setX(x)}
showCheckbox={false}
titleTooltip={"Obstacle Center X"}
/>

<Input
title="y"
suffix="m"
enabled={true}
setEnabled={(a) => null}
number={obstacle.y}
setNumber={(y) => obstacle!.setY(y)}
showCheckbox={false}
titleTooltip={"Obstacle Center Y"}
/>

<Input
title="r"
suffix="m"
enabled={true}
setEnabled={(a) => null}
number={obstacle.radius}
setNumber={(r) => obstacle!.setRadius(r)}
showCheckbox={false}
titleTooltip={"Obstacle Radius"}
/>
</InputList>
</div>
);
}
}
}
export default observer(CircularObstacleConfigPanel);
12 changes: 12 additions & 0 deletions src/components/field/Field.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import "react-toastify/dist/ReactToastify.min.css";
import { ToastContainer, toast } from "react-toastify";
import { invoke } from "@tauri-apps/api";
import { Close } from "@mui/icons-material";
import { ICircularObstacleStore } from "../../document/CircularObstacleStore";
import CircularObstacleConfigPanel from "../config/CircularObstacleConfigPanel";

type Props = {};

Expand Down Expand Up @@ -66,6 +68,16 @@ export class Field extends Component<Props, State> {
constraint={selectedSidebar as IConstraintStore}
></ConstraintsConfigPanel>
)}
{selectedSidebar !== undefined &&
"radius" in selectedSidebar &&
activePath.obstacles.find(
(obstacle) =>
obstacle.uuid == (selectedSidebar as ICircularObstacleStore)!.uuid
) && (
<CircularObstacleConfigPanel
obstacle={selectedSidebar as ICircularObstacleStore}
></CircularObstacleConfigPanel>
)}
{robotConfigOpen && (
<div className={styles.WaypointPanel}>
<RobotConfigPanel></RobotConfigPanel>
Expand Down
112 changes: 112 additions & 0 deletions src/components/field/svg/FieldObstacles.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import React, { Component } from "react";
import DocumentManagerContext from "../../../document/DocumentManager";
import { observer } from "mobx-react";
import { Circle } from "@mui/icons-material";
import { Box } from "@mui/material";
import * as d3 from "d3";
import { ICircularObstacleStore } from "../../../document/CircularObstacleStore";

type Props = { obstacle: ICircularObstacleStore; index: number };

type State = {};
const STROKE = 0.1;
const DOT = 0.1;

class FieldGrid extends Component<Props, State> {
static contextType = DocumentManagerContext;
context!: React.ContextType<typeof DocumentManagerContext>;
state = {};
rootRef: React.RefObject<SVGElement> = React.createRef<SVGElement>();

appendIndexID(id: string): string {
return `${id}${this.props.index}`;
}

dragPointTranslate(event: any) {
this.props.obstacle.setX(this.props.obstacle.x + event.dx);
this.props.obstacle.setY(this.props.obstacle.y + event.dy);
}

dragPointRadius(event: any) {
let dx = event.x - this.props.obstacle.x;
let dy = event.y - this.props.obstacle.y;
let r = Math.sqrt(dx * dx + dy * dy);

this.props.obstacle.setRadius(r);
}

componentDidMount(): void {
if (this.rootRef.current) {
var dragHandleDrag = d3
.drag<SVGCircleElement, undefined>()
.on("drag", (event) => this.dragPointTranslate(event))
.on("start", () => {
this.context.model.select(this.props.obstacle);
this.context.history.startGroup(() => {});
})
.on("end", (event) => this.context.history.stopGroup())
.container(this.rootRef.current);
d3.select<SVGCircleElement, undefined>(
`#oDragTarget${this.props.index}`
).call(dragHandleDrag);
d3.select<SVGCircleElement, undefined>(
`#oCenterDragTarget${this.props.index}`
).call(dragHandleDrag);

var radiusHandleDrag = d3
.drag<SVGCircleElement, undefined>()
.on("drag", (event) => this.dragPointRadius(event))
.on("start", () => {
this.context.model.select(this.props.obstacle);
this.context.history.startGroup(() => {});
})
.on("end", (event) => this.context.history.stopGroup())
.container(this.rootRef.current);
d3.select<SVGCircleElement, undefined>(
`#oRadiusDragTarget${this.props.index}`
).call(radiusHandleDrag);
}
}

render() {
let o = this.props.obstacle;
return (
<g ref={this.rootRef}>
{/* Main Circle */}
<circle
cx={o.x}
cy={o.y}
r={o.radius - STROKE / 2}
fill={"red"}
fillOpacity={0.1}
onClick={() => this.context.model.select(o)}
id={this.appendIndexID("oDragTarget")}
></circle>
{/* Center Dot */}
<circle
cx={o.x}
cy={o.y}
r={o.radius < DOT * 2 ? 0.0 : DOT}
fill={o.selected ? "var(--select-yellow)" : "red"}
fillOpacity={o.selected ? 1.0 : 0.8}
onClick={() => this.context.model.select(o)}
id={this.appendIndexID("oCenterDragTarget")}
></circle>
{/* Radius Handle */}
<circle
cx={o.x}
cy={o.y}
r={o.radius - STROKE / 2}
fill={"transparent"}
pointerEvents={"visibleStroke"}
stroke={o.selected ? "var(--select-yellow)" : "red"}
strokeWidth={STROKE}
strokeOpacity={o.selected ? 1.0 : 0.8}
onClick={() => this.context.model.select(o)}
id={this.appendIndexID("oRadiusDragTarget")}
></circle>
</g>
);
}
}
export default observer(FieldGrid);
56 changes: 51 additions & 5 deletions src/components/field/svg/FieldOverlayRoot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ import { NavbarLabels, ViewLayers } from "../../../document/UIStateStore";
import FieldGeneratedLines from "./FieldGeneratedLines";
import FieldAxisLines from "./FieldAxisLines";
import FieldConstraintsAddLayer from "./FieldConstraintsAddLayer";
import FieldObstacle from "./FieldObstacles";
import { Box } from "@mui/material";
import { Circle } from "@mui/icons-material";
import { v4 as uuidv4 } from "uuid";
import { CircularObstacleStore } from "../../../document/CircularObstacleStore";

type Props = {};

Expand Down Expand Up @@ -130,11 +135,7 @@ class FieldOverlayRoot extends Component<Props, State> {
)}
{layers[ViewLayers.Grid] && <FieldGrid></FieldGrid>}
<FieldAxisLines></FieldAxisLines>
{/* Line paths */}
{layers[ViewLayers.Waypoints] && <FieldPathLines></FieldPathLines>}
{layers[ViewLayers.Trajectory] && (
<FieldGeneratedLines></FieldGeneratedLines>
)}
{/* Obstacle and waypoint mouse capture*/}
{layers[ViewLayers.Waypoints] &&
this.context.model.uiState.isNavbarWaypointSelected() && (
<circle
Expand All @@ -145,6 +146,30 @@ class FieldOverlayRoot extends Component<Props, State> {
onClick={(e) => this.createWaypoint(e)}
></circle>
)}
{layers[ViewLayers.Obstacles] &&
this.context.model.uiState.isNavbarObstacleSelected() && (
<circle
cx={0}
cy={0}
r={10000}
style={{ fill: "transparent" }}
onClick={(e) => this.createObstacle(e)}
></circle>
)}
{layers[ViewLayers.Obstacles] &&
this.context.model.document.pathlist.activePath.obstacles.map(
(obstacle, index) => (
<FieldObstacle
obstacle={obstacle}
index={index}
></FieldObstacle>
)
)}
{/* Line paths */}
{layers[ViewLayers.Waypoints] && <FieldPathLines></FieldPathLines>}
{layers[ViewLayers.Trajectory] && (
<FieldGeneratedLines></FieldGeneratedLines>
)}
{layers[ViewLayers.Waypoints] &&
this.context.model.document.pathlist.activePath.waypoints.map(
(point, index) => (
Expand Down Expand Up @@ -196,5 +221,26 @@ class FieldOverlayRoot extends Component<Props, State> {
this.context.history.stopGroup();
}
}
createObstacle(e: React.MouseEvent<SVGCircleElement, MouseEvent>): void {
if (e.currentTarget === e.target) {
var coords = this.screenSpaceToFieldSpace(this.svgRef?.current, {
x: e.clientX,
y: e.clientY,
});
this.context.history.startGroup(() => {
var newObstacle =
this.context.model.document.pathlist.activePath.addObstacle(
CircularObstacleStore.create({
x: coords.x,
y: coords.y,
radius: 0.5,
uuid: uuidv4(),
})
);
// const selectedItem = this.context.model.uiState.selectedNavbarItem;
});
this.context.history.stopGroup();
}
}
}
export default observer(FieldOverlayRoot);
Loading