@@ -59,17 +59,21 @@ class Point {
5959 this . x = x ;
6060 this . y = y ;
6161 }
62+
63+ toJSON ( ) { // Save point as [x, y] instead of {x: x, y: y}
64+ return [ this . x , this . y ] ;
65+ }
6266}
6367
6468class Run {
6569 name : string ;
6670 points : Point [ ] ;
6771 actions : Action [ ] ;
6872
69- constructor ( name : string , points : Point [ ] , actions : Action [ ] = [ ] ) {
73+ constructor ( name : string , points : [ number , number ] [ ] , actions : { point : [ number , number ] , function : string , args : any [ ] } [ ] = [ ] ) {
7074 this . name = name ;
71- this . points = points ;
72- this . actions = actions ;
75+ this . points = points . map ( ( point ) => new Point ( point [ 0 ] , point [ 1 ] ) ) ;
76+ this . actions = actions . map ( ( action ) => new Action ( new Point ( action . point [ 0 ] , action . point [ 1 ] ) , action . function , action . args ) ) ;
7377 }
7478}
7579
@@ -83,28 +87,10 @@ class SplanContent {
8387 this . drive_base = new DriveBase ( data . drive_base . left_motor , data . drive_base . right_motor , data . drive_base . wheel_diameter , data . drive_base . axle_track ) ;
8488 this . runs = data . runs . map ( ( run : any ) => new Run ( run . name , run . points , run . actions ) ) ;
8589 }
86-
87- save_file ( ) {
88- const data = JSON . stringify ( this ) ;
89- const blob = new Blob ( [ data ] , { type : "application/json" } ) ;
90- const url = URL . createObjectURL ( blob ) ;
91- const link = document . createElement ( "a" ) ;
92- link . href = url ;
93- link . download = "py_splan.json" ;
94- link . click ( ) ;
95- URL . revokeObjectURL ( url ) ;
96- }
97-
98- async generate_code ( ) {
99- const github_url = "https://raw.githubusercontent.com/PySplanner/PySplanner/refs/heads/main/pysplanner.py"
100- const response = await fetch ( github_url ) ;
101- const code = await response . text ( ) ;
102- // TODO: Add the stuff to the code
103- }
10490}
10591
10692// Custom PySplanner B-Spline algorithm
107- const getCurvePoints = ( pts : number [ ] , tension = 0.5 , isClosed = false , numOfSegments = 16 ) => {
93+ const GetCurvePoints = ( pts : number [ ] , tension = 0.5 , isClosed = false , numOfSegments = 16 ) => {
10894 let _pts = pts . slice ( 0 ) ; // Copy the array of points
10995 let res = [ ] , x , y , t1x , t2x , t1y , t2y , c1 , c2 , c3 , c4 , st , t ;
11096
@@ -149,10 +135,8 @@ export default function App() {
149135 const mat_img = `./game_board_${ theme ? theme : "dark" } .png`
150136 const [ settings_active , SetSettingsActive ] = useState ( false )
151137 const [ spike_server , SetSpikeServer ] = useState < BluetoothRemoteGATTServer | null > ( null )
152- const [ pysplan_handloer , SetPySplanHandler ] = useState < SplanContent | null > ( null )
153- const [ points , setPoints ] = useState < Point [ ] > ( [ ] ) ;
154- const [ history , setHistory ] = useState < Point [ ] [ ] > ( [ [ ] ] ) ;
155- const [ currentIndex , setCurrentIndex ] = useState ( 0 ) ;
138+ const [ pysplan_handler , SetPySplanHandler ] = useState < SplanContent | null > ( null )
139+ const [ run_index , SetRunIndex ] = useState ( 0 )
156140
157141 const HandleLoadSplan = ( ) => {
158142 const input = document . createElement ( 'input' ) ;
@@ -164,21 +148,24 @@ export default function App() {
164148 const reader = new FileReader ( ) ;
165149 reader . onload = async ( ) => {
166150 const data = JSON . parse ( reader . result as string ) ;
167- try { const splan = new SplanContent ( data ) ; SetPySplanHandler ( splan ) ; } catch ( e ) { toast . error ( "Failed to load Splan file, check the console for more info" , { duration : 5000 } ) ; console . error ( e ) ; }
151+ try {
152+ const splan = new SplanContent ( data )
153+ SetPySplanHandler ( splan ) ;
154+ } catch ( e ) { toast . error ( "Failed to load Splan file, check the console for more info" , { duration : 5000 } ) ; console . error ( e ) ; }
168155 }
169156 reader . readAsText ( file ) ;
170157 }
171158 input . click ( ) ;
172159 }
173160
174161 const HandleSaveSplan = ( ) => {
175- if ( ! pysplan_handloer ) { toast . error ( "No Splan to save" , { duration : 5000 } ) ; return ; }
176- const data = JSON . stringify ( pysplan_handloer ) ;
162+ if ( ! pysplan_handler ) { toast . error ( "No Splan to save" , { duration : 5000 } ) ; return ; }
163+ const data = JSON . stringify ( pysplan_handler ) ;
177164 const blob = new Blob ( [ data ] , { type : "application/json" } ) ;
178165 const url = URL . createObjectURL ( blob ) ;
179166 const link = document . createElement ( "a" ) ;
180167 link . href = url ;
181- link . download = `${ pysplan_handloer . name } .pysplan` ;
168+ link . download = `${ pysplan_handler . name } .pysplan` ;
182169 link . click ( ) ;
183170 URL . revokeObjectURL ( url ) ;
184171 }
@@ -190,40 +177,25 @@ export default function App() {
190177 // TODO: Add the stuff to the code
191178 }
192179
193- const addPoint = ( e : React . MouseEvent ) => {
180+ const AddPoint = ( e : React . MouseEvent ) => {
181+ if ( ! pysplan_handler ) { toast . error ( "No run selected" , { duration : 5000 } ) ; return ; }
194182 const rect = e . currentTarget . getBoundingClientRect ( ) ;
195- const newPoint = { x : e . clientX - rect . left , y : e . clientY - rect . top } ;
196- const newPoints = [ ...points , newPoint ] ;
183+ const new_point = new Point ( e . clientX - rect . left , e . clientY - rect . top ) ;
184+ const new_points = [ ...pysplan_handler . runs [ run_index ] . points , new_point ] ;
197185
198- if ( newPoints . length === 25 ) {
186+ if ( new_points . length === 25 ) {
199187 toast . warning ( "WARNING: Exceeding 25 points may cause lagging/crashing of the Spike or EV3 robot." , { duration : 10000 } ) ;
200- } else if ( newPoints . length === 50 ) {
188+ } else if ( new_points . length === 50 ) {
201189 toast . error ( "You have reached the maximum number of points, which is 50." , { duration : 10000 } ) ;
202190 return
203191 }
204192
205- setPoints ( newPoints ) ;
206- setHistory ( history . slice ( 0 , currentIndex + 1 ) . concat ( [ newPoints ] ) ) ;
207- setCurrentIndex ( currentIndex + 1 ) ;
193+ pysplan_handler . runs [ run_index ] . points = new_points ;
194+ SetPySplanHandler ( pysplan_handler ) ;
208195 } ;
209196
210- const handleKeyDown = ( e : KeyboardEvent ) => {
211- if ( e . ctrlKey && e . key === 'z' && currentIndex > 0 ) {
212- setCurrentIndex ( currentIndex - 1 ) ;
213- setPoints ( history [ currentIndex - 1 ] ) ;
214- } else if ( e . ctrlKey && e . key === 'y' && currentIndex < history . length - 1 ) {
215- setCurrentIndex ( currentIndex + 1 ) ;
216- setPoints ( history [ currentIndex + 1 ] ) ;
217- }
218- } ;
219-
220- useEffect ( ( ) => {
221- window . addEventListener ( 'keydown' , handleKeyDown ) ;
222- return ( ) => window . removeEventListener ( 'keydown' , handleKeyDown ) ;
223- } , [ points , history , currentIndex ] ) ;
224-
225- const flatPoints = points . flatMap ( p => [ p . x , p . y ] ) ;
226- const splinePoints = getCurvePoints ( flatPoints ) ;
197+ const flat_points = pysplan_handler ?. runs [ run_index ] . points . flatMap ( p => [ p . x , p . y ] )
198+ const spline_points = GetCurvePoints ( flat_points ?? [ ] ) ;
227199
228200 const GetSpikeServer = async ( ) => {
229201 toast . promise (
@@ -280,11 +252,25 @@ export default function App() {
280252 </ Accordion >
281253 < Button variant = "secondary" className = "w-full" onClick = { ( ) => SetSettingsActive ( true ) } > Settings</ Button >
282254 </ div >
283- < Separator orientation = "horizontal" className = "bg-zinc-600 w-[calc(100%-30px)] ml-[15px] my -4" />
255+ < Separator orientation = "horizontal" className = "bg-zinc-600 w-[calc(100%-30px)] ml-[15px] mt -4" />
284256 </ div >
285257 < div className = "flex flex-col flex-grow overflow-y-auto" >
286- < p className = "text-center mt-4 w-[calc(100%-30px)] ml-[15px] font-bold text-lg" > Under Construction</ p >
287- < p className = "text-center mt-1 w-[calc(100%-30px)] ml-[15px] text-sm text-zinc-500" > This path planner is currently being developed</ p >
258+ { pysplan_handler ? (
259+ < div className = "flex flex-col gap-2 w-[calc(100%-30px)] ml-[15px]" >
260+ { pysplan_handler . runs . map ( ( run , idx ) => (
261+ < Button key = { idx } variant = { run_index === idx ? "secondary" : "outline" } className = "w-full" onClick = { ( ) => SetRunIndex ( idx ) } > { run . name } </ Button >
262+ ) ) }
263+ </ div >
264+ ) : (
265+ < div >
266+ < p className = "text-center mt-4 w-[calc(100%-30px)] ml-[15px] font-bold text-lg" >
267+ Under Construction
268+ </ p >
269+ < p className = "text-center mt-1 w-[calc(100%-30px)] ml-[15px] text-sm text-zinc-500" >
270+ This path planner is currently being developed
271+ </ p >
272+ </ div >
273+ ) }
288274 </ div >
289275 </ Card >
290276 ) ;
@@ -293,16 +279,16 @@ export default function App() {
293279 const Home = ( ) => {
294280 return (
295281 < div className = "flex items-center justify-center w-full h-full" >
296- { pysplan_handloer ? (
282+ { pysplan_handler ? (
297283 < div className = "relative flex items-center justify-center w-full h-full border rounded-lg ml-4" >
298284 < div className = "relative" >
299285 < img src = { mat_img } className = "w-auto h-auto max-h-[85vh] max-w-[85vw] object-contain" />
300286 < div className = "absolute inset-0 flex items-center justify-center" >
301- < div className = "w-full h-full relative" onClick = { addPoint } >
302- { points . map ( ( p , idx ) => (
287+ < div className = "w-full h-full relative" onClick = { AddPoint } >
288+ { pysplan_handler . runs [ run_index ] . points . map ( ( p , idx ) => (
303289 < div key = { idx } className = "absolute bg-green-500 w-2 h-2 rounded-full" style = { { left : `${ p . x } px` , top : `${ p . y } px` } } />
304290 ) ) }
305- { splinePoints . map ( ( p , idx ) => (
291+ { spline_points . map ( ( p , idx ) => (
306292 < div key = { idx } className = "absolute bg-green-400 w-1 h-1 rounded-full" style = { { left : `${ p . x } px` , top : `${ p . y } px` } } />
307293 ) ) }
308294 </ div >
0 commit comments