fix first waypoint into preview

add dinamic movement for target
This commit is contained in:
VALLONGOL 2025-10-14 10:39:32 +02:00
parent e62b584981
commit b20bd40b37
6 changed files with 436 additions and 184 deletions

View File

@ -1,101 +1,100 @@
x_nm,y_nm x_nm,y_nm
0.17452406437283513,9.998476951563912 1.7364817766693033,9.84807753012208
0.2859422147817867,10.279638104623308 1.8196915585801245,9.997286852043835
0.4421393353133215,10.609933583832394 1.909698355298528,10.164705993254746
0.636432547474389,10.985187585712282 2.006314644389864,10.349354914685591
0.8621389727719386,11.401224306784082 2.1093529034194827,10.550253577267155
1.1125757327129193,11.853867943568899 2.218625609952734,10.766421941930222
1.3810599488042814,12.338942692587844 2.3339452415549684,10.99687996960557
1.6609087425529734,12.852272750362022 2.4551242757915364,11.240647621223985
1.9454392354659444,13.389682313412546 2.581975190227787,11.496744857716246
2.227968549050145,13.946995578260525 2.714310462429072,11.764191640013136
2.5018138048125236,14.520036741427065 2.851942569960741,12.04200792904544
2.7602921242600305,15.104629999433275 2.9946839903881437,12.329213685743936
2.996720628899614,15.696599548800267 3.14234720127663,12.624828871039403
3.2044164402382243,16.291769586049142 3.2947446801915516,12.927873445862636
3.3766966797828104,16.885964307701016 3.4516889046982575,13.237367371144403
3.5068784690403216,17.475007910277 3.612992352362098,13.552330607815493
3.588278929517707,18.054724590298196 3.7784675007484245,13.871783116806688
3.6142151827219164,18.620938544285714 3.947926827422586,14.19474485904877
3.578004350159902,19.169473968760666 4.121182809949932,14.520235795472518
3.472963553338606,19.696155060244156 4.298047925895815,14.847275887008719
3.4729635533386065,19.69615506024416 4.478334652825583,15.17488509458815
3.272762644392875,20.217383756990923 4.661855468304586,15.502083379141595
2.964843068011936,20.750866222507394 4.848422849898178,15.827890701599843
2.5631793345343286,21.294382991192247 5.037849275171705,16.151327022893664
2.081745954298589,21.845714597444157 5.2299472216905185,16.471412303953848
1.5345174376432555,22.402641575661793 5.424529167019969,16.787166505711173
0.9354682949068648,22.962944460243826 5.621407588725407,17.09760958909643
0.2985730364279542,23.52440378558893 5.8203949643721815,17.401761515040384
-0.36219382745493856,24.084800086095772 6.021303771525644,17.698642244473834
-1.032857786403277,24.641913896163032 6.223946487751144,17.987271738327554
-1.699444330078522,25.193525750189377 6.4281355906140325,18.26666995753233
-2.347978948142139,25.737416182573483 6.6336835576796584,18.535856863018942
-2.964487130255587,26.271365727714013 6.840402866513372,18.79385241571817
-3.5349943660803307,26.793154920009652 6.840402866513374,18.79385241571817
-4.045526145277833,27.30056429385906 7.064412663195302,19.043619595904882
-4.482107957509558,27.79137438366092 7.319986102939524,19.28896147814137
-4.830765292436963,28.263365723813894 7.604166677012322,19.530158164789555
-5.077523639721516,28.714318848716662 7.913997876679983,19.767489758211376
-5.208408489024677,29.14201429276789 8.246523193208787,20.001236360768743
-5.2094453300079095,29.544232590366253 8.598786117865023,20.231678074823588
-5.2094453300079095,29.544232590366242 8.96783014191497,20.459095002737843
-5.087215806911271,29.93160758132983 9.350698756624908,20.683767246873423
-4.8634013589893685,30.31504050771131 9.744435453261131,20.90597490959226
-4.54667716842549,30.69271300726126 10.146083723089918,21.12599809325628
-4.145718417402923,31.062806717730222 10.552687057377549,21.3441169002274
-3.6692002881049564,31.42350327686877 10.961288947390312,21.560611432867567
-3.1257979627148766,31.772984322427458 11.368932884394491,21.775761793538678
-2.5241866234159707,32.10943149215684 11.772662359656369,21.989848084602684
-1.873041452391528,32.43102642380748 12.169520864442228,22.203150408421493
-1.1810376318248348,32.735950755129934 12.556551890018351,22.41594886735704
-0.4568503438991798,33.02238612387475 12.930798927651024,22.62852356377125
0.29084522920215017,33.28851416779252 13.289305468606532,22.84115460002604
1.0533739052958677,33.53251652463377 13.629115004151155,23.054122078483353
1.822060502198683,33.75257483214907 13.947271025551181,23.267706101505095
2.5882298377273103,33.94687072808898 14.24081702407289,23.482186771453204
3.343206729698462,34.113585850204075 14.506796490982568,23.6978441906896
4.078315995928851,34.25090183624488 14.742252917546494,23.91495846157622
4.784882454235189,34.35700032396197 14.944229795030958,24.133809686474972
5.454230922434187,34.430062951105924 15.109770614702246,24.354677967747794
6.077686218342558,34.46827135542727 15.23591886782663,24.57784340775661
6.077686218342562,34.468271355427284 15.319718045670406,24.80358610886334
6.6950615437258465,34.47971290071672 15.358211639499848,25.032186173429917
7.3476044195818755,34.472832854810775 15.348443140581244,25.26392370381826
8.028791142575807,34.44634971152879 15.28745604018088,25.499078802390304
8.732098009372796,34.3989819646901 15.172293829565035,25.737931571507964
9.451001316637997,34.32944810811404 15.0,25.980762113533167
10.178977361036566,34.23646663561996 14.999999999999998,25.98076211353316
10.909502439233654,34.11875604102719 14.76007873005941,26.230939963143115
11.636052847894423,33.97503481815507 14.447076186168827,26.491050501357915
12.352104883684024,33.80402146082294 14.065598841372388,26.760197600053854
13.051134843267612,33.60443446285013 13.62025316871424,27.037485131107207
13.726619023310347,33.37499231805599 13.11564564123853,27.322016966394273
14.372033720477376,33.114413520259845 12.556382731989396,27.612896977791323
14.98085523143386,32.82141656328105 11.947070914010986,27.909229037174644
15.546559852844958,32.49471994093893 11.292316660347446,28.210117016420522
16.062623881375814,32.13304214705283 10.596726444042913,28.514664787405238
16.522523613691593,31.735101675442085 9.86490673814154,28.82197622200508
16.919735346457447,31.29961701992603 9.101464015687464,29.13115519209633
17.247735376338525,30.825306674324008 8.311004749724834,29.441305569555276
17.499999999999996,30.310889132455358 7.498135413297791,29.751531226258194
17.499999999999996,30.310889132455355 6.66746247945048,30.060936034081376
17.667951456825268,29.722378176057024 5.823592421227048,30.368623864901103
17.752649951768674,29.034858901953363 4.971131711671633,30.67369859059366
17.76202872985173,28.26065677659477 4.114686823828384,30.97526408303533
17.704021036095945,27.412097266431687 3.2588642307414455,31.272424214102394
17.586560115522854,26.501505837914515 2.4082704054549575,31.564282855671145
17.417579213153953,25.541207957493693 1.5675118210130687,31.84994387961786
17.205011574010783,24.54352909161962 0.7411949504599216,32.12851115781883
16.956790443114837,23.52079470674273 -0.06607373316034426,32.39908856215034
16.68084906548765,22.485330269313444 -0.8496877568035757,32.660779964488654
16.385120686150735,21.449461245782175 -1.6050406474256338,32.91268923671007
16.0775385501256,20.425513102599343 -2.3275259319823753,33.15392025069089
15.766035902433776,19.42581130621538 -3.0125371374296517,33.38357687830738
15.45854598809677,18.462681323080695 -3.6554677907233284,33.600762991435815
15.1630020521361,17.5484486196457 -4.2517114188192515,33.80458246195249
14.88733733957329,16.69543866236083 -4.796661548673278,33.99413916173369
14.639485095429851,15.9159769176765 -5.285711707241269,34.1685369626557
14.427378564727302,15.222388852043133 -5.714255421479081,34.326879736594805
14.258950992487165,14.626999931911136 -6.077686218342556,34.46827135542728
14.142135623730947,14.142135623730947

1 x_nm y_nm
2 0.17452406437283513 1.7364817766693033 9.998476951563912 9.84807753012208
3 0.2859422147817867 1.8196915585801245 10.279638104623308 9.997286852043835
4 0.4421393353133215 1.909698355298528 10.609933583832394 10.164705993254746
5 0.636432547474389 2.006314644389864 10.985187585712282 10.349354914685591
6 0.8621389727719386 2.1093529034194827 11.401224306784082 10.550253577267155
7 1.1125757327129193 2.218625609952734 11.853867943568899 10.766421941930222
8 1.3810599488042814 2.3339452415549684 12.338942692587844 10.99687996960557
9 1.6609087425529734 2.4551242757915364 12.852272750362022 11.240647621223985
10 1.9454392354659444 2.581975190227787 13.389682313412546 11.496744857716246
11 2.227968549050145 2.714310462429072 13.946995578260525 11.764191640013136
12 2.5018138048125236 2.851942569960741 14.520036741427065 12.04200792904544
13 2.7602921242600305 2.9946839903881437 15.104629999433275 12.329213685743936
14 2.996720628899614 3.14234720127663 15.696599548800267 12.624828871039403
15 3.2044164402382243 3.2947446801915516 16.291769586049142 12.927873445862636
16 3.3766966797828104 3.4516889046982575 16.885964307701016 13.237367371144403
17 3.5068784690403216 3.612992352362098 17.475007910277 13.552330607815493
18 3.588278929517707 3.7784675007484245 18.054724590298196 13.871783116806688
19 3.6142151827219164 3.947926827422586 18.620938544285714 14.19474485904877
20 3.578004350159902 4.121182809949932 19.169473968760666 14.520235795472518
21 3.472963553338606 4.298047925895815 19.696155060244156 14.847275887008719
22 3.4729635533386065 4.478334652825583 19.69615506024416 15.17488509458815
23 3.272762644392875 4.661855468304586 20.217383756990923 15.502083379141595
24 2.964843068011936 4.848422849898178 20.750866222507394 15.827890701599843
25 2.5631793345343286 5.037849275171705 21.294382991192247 16.151327022893664
26 2.081745954298589 5.2299472216905185 21.845714597444157 16.471412303953848
27 1.5345174376432555 5.424529167019969 22.402641575661793 16.787166505711173
28 0.9354682949068648 5.621407588725407 22.962944460243826 17.09760958909643
29 0.2985730364279542 5.8203949643721815 23.52440378558893 17.401761515040384
30 -0.36219382745493856 6.021303771525644 24.084800086095772 17.698642244473834
31 -1.032857786403277 6.223946487751144 24.641913896163032 17.987271738327554
32 -1.699444330078522 6.4281355906140325 25.193525750189377 18.26666995753233
33 -2.347978948142139 6.6336835576796584 25.737416182573483 18.535856863018942
34 -2.964487130255587 6.840402866513372 26.271365727714013 18.79385241571817
35 -3.5349943660803307 6.840402866513374 26.793154920009652 18.79385241571817
36 -4.045526145277833 7.064412663195302 27.30056429385906 19.043619595904882
37 -4.482107957509558 7.319986102939524 27.79137438366092 19.28896147814137
38 -4.830765292436963 7.604166677012322 28.263365723813894 19.530158164789555
39 -5.077523639721516 7.913997876679983 28.714318848716662 19.767489758211376
40 -5.208408489024677 8.246523193208787 29.14201429276789 20.001236360768743
41 -5.2094453300079095 8.598786117865023 29.544232590366253 20.231678074823588
42 -5.2094453300079095 8.96783014191497 29.544232590366242 20.459095002737843
43 -5.087215806911271 9.350698756624908 29.93160758132983 20.683767246873423
44 -4.8634013589893685 9.744435453261131 30.31504050771131 20.90597490959226
45 -4.54667716842549 10.146083723089918 30.69271300726126 21.12599809325628
46 -4.145718417402923 10.552687057377549 31.062806717730222 21.3441169002274
47 -3.6692002881049564 10.961288947390312 31.42350327686877 21.560611432867567
48 -3.1257979627148766 11.368932884394491 31.772984322427458 21.775761793538678
49 -2.5241866234159707 11.772662359656369 32.10943149215684 21.989848084602684
50 -1.873041452391528 12.169520864442228 32.43102642380748 22.203150408421493
51 -1.1810376318248348 12.556551890018351 32.735950755129934 22.41594886735704
52 -0.4568503438991798 12.930798927651024 33.02238612387475 22.62852356377125
53 0.29084522920215017 13.289305468606532 33.28851416779252 22.84115460002604
54 1.0533739052958677 13.629115004151155 33.53251652463377 23.054122078483353
55 1.822060502198683 13.947271025551181 33.75257483214907 23.267706101505095
56 2.5882298377273103 14.24081702407289 33.94687072808898 23.482186771453204
57 3.343206729698462 14.506796490982568 34.113585850204075 23.6978441906896
58 4.078315995928851 14.742252917546494 34.25090183624488 23.91495846157622
59 4.784882454235189 14.944229795030958 34.35700032396197 24.133809686474972
60 5.454230922434187 15.109770614702246 34.430062951105924 24.354677967747794
61 6.077686218342558 15.23591886782663 34.46827135542727 24.57784340775661
62 6.077686218342562 15.319718045670406 34.468271355427284 24.80358610886334
63 6.6950615437258465 15.358211639499848 34.47971290071672 25.032186173429917
64 7.3476044195818755 15.348443140581244 34.472832854810775 25.26392370381826
65 8.028791142575807 15.28745604018088 34.44634971152879 25.499078802390304
66 8.732098009372796 15.172293829565035 34.3989819646901 25.737931571507964
67 9.451001316637997 15.0 34.32944810811404 25.980762113533167
68 10.178977361036566 14.999999999999998 34.23646663561996 25.98076211353316
69 10.909502439233654 14.76007873005941 34.11875604102719 26.230939963143115
70 11.636052847894423 14.447076186168827 33.97503481815507 26.491050501357915
71 12.352104883684024 14.065598841372388 33.80402146082294 26.760197600053854
72 13.051134843267612 13.62025316871424 33.60443446285013 27.037485131107207
73 13.726619023310347 13.11564564123853 33.37499231805599 27.322016966394273
74 14.372033720477376 12.556382731989396 33.114413520259845 27.612896977791323
75 14.98085523143386 11.947070914010986 32.82141656328105 27.909229037174644
76 15.546559852844958 11.292316660347446 32.49471994093893 28.210117016420522
77 16.062623881375814 10.596726444042913 32.13304214705283 28.514664787405238
78 16.522523613691593 9.86490673814154 31.735101675442085 28.82197622200508
79 16.919735346457447 9.101464015687464 31.29961701992603 29.13115519209633
80 17.247735376338525 8.311004749724834 30.825306674324008 29.441305569555276
81 17.499999999999996 7.498135413297791 30.310889132455358 29.751531226258194
82 17.499999999999996 6.66746247945048 30.310889132455355 30.060936034081376
83 17.667951456825268 5.823592421227048 29.722378176057024 30.368623864901103
84 17.752649951768674 4.971131711671633 29.034858901953363 30.67369859059366
85 17.76202872985173 4.114686823828384 28.26065677659477 30.97526408303533
86 17.704021036095945 3.2588642307414455 27.412097266431687 31.272424214102394
87 17.586560115522854 2.4082704054549575 26.501505837914515 31.564282855671145
88 17.417579213153953 1.5675118210130687 25.541207957493693 31.84994387961786
89 17.205011574010783 0.7411949504599216 24.54352909161962 32.12851115781883
90 16.956790443114837 -0.06607373316034426 23.52079470674273 32.39908856215034
91 16.68084906548765 -0.8496877568035757 22.485330269313444 32.660779964488654
92 16.385120686150735 -1.6050406474256338 21.449461245782175 32.91268923671007
93 16.0775385501256 -2.3275259319823753 20.425513102599343 33.15392025069089
94 15.766035902433776 -3.0125371374296517 19.42581130621538 33.38357687830738
95 15.45854598809677 -3.6554677907233284 18.462681323080695 33.600762991435815
96 15.1630020521361 -4.2517114188192515 17.5484486196457 33.80458246195249
97 14.88733733957329 -4.796661548673278 16.69543866236083 33.99413916173369
98 14.639485095429851 -5.285711707241269 15.9159769176765 34.1685369626557
99 14.427378564727302 -5.714255421479081 15.222388852043133 34.326879736594805
100 14.258950992487165 -6.077686218342556 14.626999931911136 34.46827135542728
14.142135623730947 14.142135623730947

View File

@ -3,7 +3,7 @@
"scan_limit": 60, "scan_limit": 60,
"max_range": 100, "max_range": 100,
"geometry": "1200x1024+463+195", "geometry": "1200x1024+463+195",
"last_selected_scenario": "scenario2", "last_selected_scenario": "scenario3",
"connection": { "connection": {
"target": { "target": {
"type": "tftp", "type": "tftp",
@ -114,7 +114,7 @@
"target_azimuth_deg": 45.0 "target_azimuth_deg": 45.0
} }
], ],
"use_spline": false "use_spline": true
}, },
{ {
"target_id": 1, "target_id": 1,
@ -150,6 +150,57 @@
"target_azimuth_deg": -10.0 "target_azimuth_deg": -10.0
} }
], ],
"use_spline": true
},
{
"target_id": 2,
"active": true,
"traceable": true,
"trajectory": [
{
"maneuver_type": "Fly to Point",
"duration_s": 1.0,
"target_altitude_ft": 10000.0,
"target_range_nm": 40.0,
"target_azimuth_deg": 0.0
},
{
"maneuver_type": "Fly for Duration",
"target_velocity_fps": 506.343,
"target_heading_deg": -180.0,
"duration_s": 10.0,
"target_altitude_ft": 10000.0
},
{
"maneuver_type": "Dynamic Maneuver",
"duration_s": 10.0,
"target_altitude_ft": 10000.0,
"dynamic_velocity_fps": 506.343,
"lateral_g": 9.0,
"longitudinal_g": 0.0,
"vertical_g": 0.0
}
],
"use_spline": false
}
]
},
"scenario3": {
"name": "scenario3",
"targets": [
{
"target_id": 0,
"active": true,
"traceable": true,
"trajectory": [
{
"maneuver_type": "Fly to Point",
"duration_s": 10.0,
"target_altitude_ft": 10000.0,
"target_range_nm": 20.0,
"target_azimuth_deg": 0.0
}
],
"use_spline": false "use_spline": false
} }
] ]

View File

@ -19,24 +19,27 @@ NM_TO_FT = 6076.12
class ManeuverType(Enum): class ManeuverType(Enum):
FLY_TO_POINT = "Fly to Point" FLY_TO_POINT = "Fly to Point"
FLY_FOR_DURATION = "Fly for Duration" FLY_FOR_DURATION = "Fly for Duration"
DYNAMIC_MANEUVER = "Dynamic Maneuver"
@dataclass @dataclass
class Waypoint: class Waypoint:
"""Represents a segment of a target's trajectory, defining a maneuver.""" """Represents a segment of a target's trajectory, defining a maneuver."""
maneuver_type: ManeuverType = ManeuverType.FLY_FOR_DURATION maneuver_type: ManeuverType = ManeuverType.FLY_FOR_DURATION
# Parameters used by FLY_FOR_DURATION and as final state for FLY_TO_POINT # Parameters for FLY_FOR_DURATION and FLY_TO_POINT
target_velocity_fps: Optional[float] = None target_velocity_fps: Optional[float] = None
target_heading_deg: Optional[float] = None target_heading_deg: Optional[float] = None
# Parameters used by both
duration_s: Optional[float] = 10.0 duration_s: Optional[float] = 10.0
target_altitude_ft: Optional[float] = 10000.0 target_altitude_ft: Optional[float] = 10000.0
# Parameters used by FLY_TO_POINT
target_range_nm: Optional[float] = None target_range_nm: Optional[float] = None
target_azimuth_deg: Optional[float] = None target_azimuth_deg: Optional[float] = None
# Parameters for DYNAMIC_MANEUVER (all obbligatori, velocity non opzionale)
dynamic_velocity_fps: Optional[float] = None # velocità durante la manovra
lateral_g: Optional[float] = None # G laterale (virata)
longitudinal_g: Optional[float] = None # G longitudinale (accelerazione)
vertical_g: Optional[float] = None # G verticale (salita/discesa)
# Internal state for interpolation, set when waypoint becomes active # Internal state for interpolation, set when waypoint becomes active
_start_velocity_fps: float = field(init=False, repr=False, default=0.0) _start_velocity_fps: float = field(init=False, repr=False, default=0.0)
_start_heading_deg: float = field(init=False, repr=False, default=0.0) _start_heading_deg: float = field(init=False, repr=False, default=0.0)
@ -45,7 +48,6 @@ class Waypoint:
def to_dict(self) -> Dict: def to_dict(self) -> Dict:
data = {"maneuver_type": self.maneuver_type.value} data = {"maneuver_type": self.maneuver_type.value}
# Use dataclasses.fields to iterate reliably
for f in fields(self): for f in fields(self):
if not f.name.startswith('_') and f.name != "maneuver_type": if not f.name.startswith('_') and f.name != "maneuver_type":
val = getattr(self, f.name) val = getattr(self, f.name)
@ -194,54 +196,9 @@ class Target:
return return
if self.use_spline and self._spline_path: if self.use_spline and self._spline_path:
self._sim_time_s += delta_time_s # ...existing code...
pass
total_duration = self._spline_point_times[-1]
if total_duration <= 0 or self._sim_time_s >= total_duration:
# Simulation finished, stay at the last point
self._spline_index = len(self._spline_path) - 1
self.active = False
self.current_velocity_fps = 0.0
else:
# Find the current index in the spline path based on time
progress = self._sim_time_s / total_duration
self._spline_index = int(progress * (len(self._spline_path) - 1))
pt = self._spline_path[self._spline_index]
# Determine previous point for calculating heading/pitch
prev_pt = self._spline_path[self._spline_index - 1] if self._spline_index > 0 else pt
# Update position (path is already in feet)
self._pos_x_ft, self._pos_y_ft, self._pos_z_ft = pt[0], pt[1], pt[2]
# Update kinematics
dx = self._pos_x_ft - prev_pt[0]
dy = self._pos_y_ft - prev_pt[1]
dz = self._pos_z_ft - prev_pt[2]
dist_moved_since_last_tick = math.sqrt(dx**2 + dy**2 + dz**2)
dist_2d = math.sqrt(dx**2 + dy**2)
if dist_2d > 0.1: # Avoid division by zero or noise
self.current_heading_deg = math.degrees(math.atan2(dx, dy)) % 360
self.current_pitch_deg = math.degrees(math.atan2(dz, dist_2d))
if delta_time_s > 0:
self.current_velocity_fps = dist_moved_since_last_tick / delta_time_s
self._update_current_polar_coords()
# --- DEBUG LOG ---
#import logging
#logging.getLogger("target_simulator.spline_debug").info(
# f"SplinePos: t={self._sim_time_s:.2f}s idx={self._spline_index} x={self._pos_x_ft:.2f} y={self._pos_y_ft:.2f} z={self._pos_z_ft:.2f}")
# --- DEBUG LOG ---
#import logging
#logging.getLogger("target_simulator.spline_debug").info(
# f"SplinePos: t={self._sim_time_s:.2f}s idx={self._spline_index} x={self._pos_x_ft:.2f} y={self._pos_y_ft:.2f} z={self._pos_z_ft:.2f}")
else: else:
# Logica classica
self._sim_time_s += delta_time_s self._sim_time_s += delta_time_s
self._time_in_waypoint_s += delta_time_s self._time_in_waypoint_s += delta_time_s
if self._current_waypoint_index == -1: if self._current_waypoint_index == -1:
@ -253,27 +210,56 @@ class Target:
if wp.maneuver_type == ManeuverType.FLY_FOR_DURATION: if wp.maneuver_type == ManeuverType.FLY_FOR_DURATION:
self.current_velocity_fps = wp.target_velocity_fps or self.current_velocity_fps self.current_velocity_fps = wp.target_velocity_fps or self.current_velocity_fps
self.current_heading_deg = wp.target_heading_deg or self.current_heading_deg self.current_heading_deg = wp.target_heading_deg or self.current_heading_deg
elif wp.maneuver_type == ManeuverType.DYNAMIC_MANEUVER:
self.current_velocity_fps = wp.dynamic_velocity_fps or self.current_velocity_fps
self.current_altitude_ft = wp.target_altitude_ft or self.current_altitude_ft self.current_altitude_ft = wp.target_altitude_ft or self.current_altitude_ft
self._activate_next_waypoint() self._activate_next_waypoint()
if self._current_waypoint_index == -1: if self._current_waypoint_index == -1:
return return
wp = self.trajectory[self._current_waypoint_index] # Get new active waypoint wp = self.trajectory[self._current_waypoint_index]
duration = wp._calculated_duration_s if wp._calculated_duration_s is not None else wp.duration_s or 0.0 duration = wp._calculated_duration_s if wp._calculated_duration_s is not None else wp.duration_s or 0.0
progress = self._time_in_waypoint_s / duration if duration > 0 else 1.0 progress = self._time_in_waypoint_s / duration if duration > 0 else 1.0
if wp.maneuver_type == ManeuverType.FLY_FOR_DURATION: if wp.maneuver_type == ManeuverType.FLY_FOR_DURATION:
# Interpolate kinematics during the maneuver # ...existing code...
self.current_velocity_fps = wp._start_velocity_fps + ((wp.target_velocity_fps or wp._start_velocity_fps) - wp._start_velocity_fps) * progress self.current_velocity_fps = wp._start_velocity_fps + ((wp.target_velocity_fps or wp._start_velocity_fps) - wp._start_velocity_fps) * progress
self.current_altitude_ft = wp._start_altitude_ft + ((wp.target_altitude_ft or wp._start_altitude_ft) - wp._start_altitude_ft) * progress self.current_altitude_ft = wp._start_altitude_ft + ((wp.target_altitude_ft or wp._start_altitude_ft) - wp._start_altitude_ft) * progress
delta_heading = ((wp.target_heading_deg or wp._start_heading_deg) - wp._start_heading_deg + 180) % 360 - 180 delta_heading = ((wp.target_heading_deg or wp._start_heading_deg) - wp._start_heading_deg + 180) % 360 - 180
self.current_heading_deg = (wp._start_heading_deg + delta_heading * progress) % 360 self.current_heading_deg = (wp._start_heading_deg + delta_heading * progress) % 360
# Update position using current 3D vector elif wp.maneuver_type == ManeuverType.DYNAMIC_MANEUVER:
heading_rad = math.radians(self.current_heading_deg) # Logica dinamica: integra accelerazioni e heading
pitch_rad = math.radians(self.current_pitch_deg) # Parametri obbligatori: dynamic_velocity_fps, lateral_g, duration_s
dist_moved = self.current_velocity_fps * delta_time_s velocity = wp.dynamic_velocity_fps
dist_2d = dist_moved * math.cos(pitch_rad) lateral_g = wp.lateral_g or 0.0
self._pos_x_ft += dist_2d * math.sin(heading_rad) longitudinal_g = wp.longitudinal_g or 0.0
self._pos_y_ft += dist_2d * math.cos(heading_rad) vertical_g = wp.vertical_g or 0.0
self._pos_z_ft += dist_moved * math.sin(pitch_rad) # Conversione G in ft/s^2
G_FT_S2 = 32.174
lateral_acc = lateral_g * G_FT_S2
long_acc = longitudinal_g * G_FT_S2
vert_acc = vertical_g * G_FT_S2
# Aggiorna heading (virata): rateo di virata = a_laterale / velocità
# LV turn_rate_rad_s = lateral_acc / max(velocity, 1e-3)
R = (velocity* velocity)/lateral_acc
omega = velocity/R if R!=0 else 0
turn_rate_rad_s = omega
self.current_heading_deg = (self.current_heading_deg + math.degrees(turn_rate_rad_s * delta_time_s)) % 360
# Aggiorna velocità (accelerazione longitudinale)
self.current_velocity_fps = velocity + long_acc * delta_time_s
# Aggiorna posizione
heading_rad = math.radians(self.current_heading_deg)
pitch_rad = math.radians(self.current_pitch_deg)
dist_moved = self.current_velocity_fps * delta_time_s
dist_2d = dist_moved * math.cos(pitch_rad)
self._pos_x_ft += dist_2d * math.sin(heading_rad)
self._pos_y_ft += dist_2d * math.cos(heading_rad)
self._pos_z_ft += dist_moved * math.sin(pitch_rad) + vert_acc * delta_time_s
# ...existing code...
self._update_current_polar_coords() self._update_current_polar_coords()
def _update_current_polar_coords(self): def _update_current_polar_coords(self):

View File

@ -160,15 +160,122 @@ class PPIDisplay(ttk.Frame):
Simulates and draws a trajectory preview. Simulates and draws a trajectory preview.
Accepts either a list of Waypoint or a Target object. Accepts either a list of Waypoint or a Target object.
""" """
# Pulisci tutto prima di ridisegnare
self.clear_previews() self.clear_previews()
self._waypoints_plot.set_data([], []) self._waypoints_plot.set_data([], [])
self._start_plot.set_data([], []) self._start_plot.set_data([], [])
if hasattr(self, '_preview_artist'): if hasattr(self, '_preview_artist'):
self._preview_artist.set_data([], []) self._preview_artist.set_data([], [])
# Forza la pulizia della canvas
self.canvas.draw() # --- Simula la traiettoria anche per manovre dinamiche ---
self._on_range_selected() waypoints = trajectory if isinstance(trajectory, list) else getattr(trajectory, 'trajectory', [])
preview_points = []
# Stato iniziale
pos_x, pos_y, pos_z = 0.0, 0.0, 0.0
heading_deg = 0.0
velocity_fps = 0.0
altitude_ft = 0.0
# Usa il primo waypoint come stato iniziale se disponibile
if waypoints:
wp0 = waypoints[0]
if getattr(wp0, 'target_range_nm', None) is not None and getattr(wp0, 'target_azimuth_deg', None) is not None:
r0 = wp0.target_range_nm
th0 = math.radians(wp0.target_azimuth_deg)
pos_x = r0 * 6076.12 * math.sin(th0)
pos_y = r0 * 6076.12 * math.cos(th0)
pos_z = wp0.target_altitude_ft or 0.0
heading_deg = wp0.target_heading_deg or 0.0
velocity_fps = wp0.target_velocity_fps or 0.0
altitude_ft = pos_z
preview_points.append((pos_x, pos_y))
# Simula ogni waypoint
for wp in waypoints:
if wp.maneuver_type == ManeuverType.FLY_TO_POINT:
if wp.target_range_nm is not None and wp.target_azimuth_deg is not None:
r = wp.target_range_nm
th = math.radians(wp.target_azimuth_deg)
pos_x = r * 6076.12 * math.sin(th)
pos_y = r * 6076.12 * math.cos(th)
pos_z = wp.target_altitude_ft or pos_z
heading_deg = wp.target_heading_deg or heading_deg
velocity_fps = wp.target_velocity_fps or velocity_fps
preview_points.append((pos_x, pos_y))
elif wp.maneuver_type == ManeuverType.FLY_FOR_DURATION:
# Interpolazione lineare
dt = wp.duration_s or 1.0
steps = max(10, int(dt))
for i in range(steps):
frac = (i + 1) / steps
heading_rad = math.radians(heading_deg)
dist = (wp.target_velocity_fps or velocity_fps) * (dt / steps)
pos_x += dist * math.sin(heading_rad)
pos_y += dist * math.cos(heading_rad)
preview_points.append((pos_x, pos_y))
heading_deg = wp.target_heading_deg or heading_deg
velocity_fps = wp.target_velocity_fps or velocity_fps
elif wp.maneuver_type == ManeuverType.DYNAMIC_MANEUVER:
# Simula la curva: integra accelerazione laterale
dt = wp.duration_s or 1.0
steps = max(20, int(dt * 2))
velocity = wp.dynamic_velocity_fps or velocity_fps
heading = heading_deg
lateral_g = wp.lateral_g or 0.0
long_g = wp.longitudinal_g or 0.0
vert_g = wp.vertical_g or 0.0
G_FT_S2 = 32.174
lateral_acc = lateral_g * G_FT_S2
long_acc = long_g * G_FT_S2
vert_acc = vert_g * G_FT_S2
for i in range(steps):
dts = dt / steps
# Aggiorna heading (virata)
turn_rate_rad_s = lateral_acc / max(velocity, 1e-3)
heading += math.degrees(turn_rate_rad_s * dts)
heading_rad = math.radians(heading)
# Aggiorna velocità
velocity += long_acc * dts
# Aggiorna posizione
pos_x += velocity * math.sin(heading_rad) * dts
pos_y += velocity * math.cos(heading_rad) * dts
pos_z += vert_acc * dts
preview_points.append((pos_x, pos_y))
heading_deg = heading
velocity_fps = velocity
# Converti i punti in polari per la PPI
thetas = []
rs = []
for x, y in preview_points:
r = math.sqrt(x**2 + y**2) / 6076.12
th = math.atan2(x, y)
rs.append(r)
thetas.append(th)
# Mostra il punto iniziale anche se c'è solo un waypoint
if len(thetas) == 1:
self._preview_artist.set_data([], [])
self._waypoints_plot.set_data(thetas, rs)
self._start_plot.set_data([thetas[0]], [rs[0]])
heading_len = 3
heading_theta = thetas[0]
heading_r2 = rs[0] + heading_len
if hasattr(self, '_heading_artist'):
self._heading_artist.remove()
self._heading_artist, = self.ax.plot([heading_theta, heading_theta], [rs[0], heading_r2], color='red', linewidth=1, alpha=0.8)
self.canvas.draw()
self._on_range_selected()
return
elif len(thetas) >= 2:
self._preview_artist.set_data(thetas, rs)
self._waypoints_plot.set_data(thetas, rs)
self._start_plot.set_data([thetas[0]], [rs[0]])
heading_len = 3
heading_theta = thetas[0]
heading_r2 = rs[0] + heading_len
if hasattr(self, '_heading_artist'):
self._heading_artist.remove()
self._heading_artist, = self.ax.plot([heading_theta, heading_theta], [rs[0], heading_r2], color='red', linewidth=1, alpha=0.8)
self.canvas.draw()
self._on_range_selected()
return
# Determine input type # Determine input type
if isinstance(trajectory, list): if isinstance(trajectory, list):
@ -182,8 +289,15 @@ class PPIDisplay(ttk.Frame):
return return
# Classic preview (polyline) SOLO se spline non attiva # Classic preview (polyline) SOLO se spline non attiva
# (la preview spline cancella la classica) # (la preview spline cancella la classica)
thetas = [math.radians(getattr(wp, 'target_azimuth_deg', 0)) for wp in waypoints] # Solo waypoint con azimuth e range (escludi manovre dinamiche)
rs = [getattr(wp, 'target_range_nm', 0) for wp in waypoints] thetas = []
rs = []
for wp in waypoints:
az = getattr(wp, 'target_azimuth_deg', None)
rng = getattr(wp, 'target_range_nm', None)
if az is not None and rng is not None:
thetas.append(math.radians(az))
rs.append(rng)
if len(thetas) < 2: if len(thetas) < 2:
self.canvas.draw() self.canvas.draw()
return return

View File

@ -2,6 +2,7 @@ import tkinter as tk
from tkinter import ttk, messagebox from tkinter import ttk, messagebox
from typing import List, Optional from typing import List, Optional
import copy import copy
import math
from queue import Queue, Empty from queue import Queue, Empty
# Use absolute imports # Use absolute imports
@ -40,7 +41,7 @@ class TrajectoryEditorWindow(tk.Toplevel):
scenario_name = f"Trajectory_{self.target_id}" scenario_name = f"Trajectory_{self.target_id}"
self.title(f"Trajectory Editor - {scenario_name} - Target {self.target_id}") self.title(f"Trajectory Editor - {scenario_name} - Target {self.target_id}")
self.geometry("1100x700") self.geometry("1100x800")
self.gui_update_queue = Queue() self.gui_update_queue = Queue()
self.preview_engine: Optional[SimulationEngine] = None self.preview_engine: Optional[SimulationEngine] = None
@ -83,6 +84,11 @@ class TrajectoryEditorWindow(tk.Toplevel):
self.ppi_preview = PPIDisplay(preview_frame, max_range_nm=self.initial_max_range) self.ppi_preview = PPIDisplay(preview_frame, max_range_nm=self.initial_max_range)
self.ppi_preview.pack(fill=tk.BOTH, expand=True) self.ppi_preview.pack(fill=tk.BOTH, expand=True)
# --- Parametri fisici manovra dinamica ---
self.phys_params_label = ttk.Label(preview_frame, text="", font=("Consolas", 10), foreground="#00FF99")
self.phys_params_label.pack(fill=tk.X, padx=5, pady=(0, 5))
# --- Controlli di simulazione ---
preview_controls = ttk.Frame(preview_frame) preview_controls = ttk.Frame(preview_frame)
preview_controls.pack(fill=tk.X, pady=5) preview_controls.pack(fill=tk.X, pady=5)
self.play_button = ttk.Button(preview_controls, text="▶ Play", command=self._on_preview_play) self.play_button = ttk.Button(preview_controls, text="▶ Play", command=self._on_preview_play)
@ -93,7 +99,6 @@ class TrajectoryEditorWindow(tk.Toplevel):
self.stop_button.pack(side=tk.LEFT, padx=5) self.stop_button.pack(side=tk.LEFT, padx=5)
self.reset_button = ttk.Button(preview_controls, text="⟲ Reset", command=self._on_preview_reset) self.reset_button = ttk.Button(preview_controls, text="⟲ Reset", command=self._on_preview_reset)
self.reset_button.pack(side=tk.LEFT, padx=5) self.reset_button.pack(side=tk.LEFT, padx=5)
# ...existing code...
ttk.Label(preview_controls, text="Speed:").pack(side=tk.LEFT, padx=(10, 2), pady=5) ttk.Label(preview_controls, text="Speed:").pack(side=tk.LEFT, padx=(10, 2), pady=5)
self.time_multiplier_var = tk.StringVar(value="1x") self.time_multiplier_var = tk.StringVar(value="1x")
self.multiplier_combo = ttk.Combobox(preview_controls, textvariable=self.time_multiplier_var, values=["1x", "2x", "4x", "10x"], state="readonly", width=4) self.multiplier_combo = ttk.Combobox(preview_controls, textvariable=self.time_multiplier_var, values=["1x", "2x", "4x", "10x"], state="readonly", width=4)
@ -108,11 +113,66 @@ class TrajectoryEditorWindow(tk.Toplevel):
self.sim_progress.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5) self.sim_progress.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
self.sim_time_label = ttk.Label(progress_frame, text="0.0s / 0.0s") self.sim_time_label = ttk.Label(progress_frame, text="0.0s / 0.0s")
self.sim_time_label.pack(side=tk.RIGHT, padx=5) self.sim_time_label.pack(side=tk.RIGHT, padx=5)
# --- Pulsanti OK e Close ---
button_frame = ttk.Frame(self) button_frame = ttk.Frame(self)
button_frame.pack(fill=tk.X, padx=10, pady=(0, 10), side=tk.BOTTOM) button_frame.pack(side=tk.BOTTOM, fill=tk.X, padx=10, pady=10)
ttk.Button(button_frame, text="Cancel", command=self._on_cancel).pack(side=tk.RIGHT) self.ok_button = ttk.Button(button_frame, text="OK", command=self._on_ok)
ttk.Button(button_frame, text="OK", command=self._on_ok).pack(side=tk.RIGHT, padx=5) self.ok_button.pack(side=tk.RIGHT, padx=5)
self.close_button = ttk.Button(button_frame, text="Close", command=self._on_cancel)
self.close_button.pack(side=tk.RIGHT, padx=5)
def _show_physical_params(self):
selected = self.wp_tree.focus()
if not selected:
self.phys_params_label.config(text="")
return
try:
idx = int(selected)
wp = self.waypoints[idx]
if wp.maneuver_type == ManeuverType.DYNAMIC_MANEUVER:
vel_kn = (wp.dynamic_velocity_fps or 0.0) * FPS_TO_KNOTS
vel_mps = vel_kn * 0.514444
g = wp.lateral_g or 0.0
g_acc = 9.81
if g != 0 and vel_mps > 0:
radius_m = vel_mps ** 2 / (g_acc * abs(g))
turn_rate_deg_s = (g_acc * abs(g)) / vel_mps * (180 / math.pi)
self.phys_params_label.config(
text=f"Raggio curvatura: {radius_m:,.0f} m | Rateo virata: {turn_rate_deg_s:.1f}°/s | Velocità: {vel_kn:.1f} kn | G lat: {g:.2f}"
)
else:
self.phys_params_label.config(text="(Parametri insufficienti per calcolo fisico)")
else:
self.phys_params_label.config(text="")
except Exception:
self.phys_params_label.config(text="")
def _show_physical_params(self):
selected = self.wp_tree.focus()
if not selected:
self.phys_params_label.config(text="")
return
try:
idx = int(selected)
wp = self.waypoints[idx]
if wp.maneuver_type == ManeuverType.DYNAMIC_MANEUVER:
vel_kn = (wp.dynamic_velocity_fps or 0.0) * FPS_TO_KNOTS
vel_mps = vel_kn * 0.514444
g = wp.lateral_g or 0.0
g_acc = 9.81
if g != 0 and vel_mps > 0:
radius_m = vel_mps ** 2 / (g_acc * abs(g))
turn_rate_deg_s = (g_acc * abs(g)) / vel_mps * (180 / math.pi)
self.phys_params_label.config(
text=f"Raggio curvatura: {radius_m:,.0f} m | Rateo virata: {turn_rate_deg_s:.1f}°/s | Velocità: {vel_kn:.1f} kn | G lat: {g:.2f}"
)
else:
self.phys_params_label.config(text="(Parametri insufficienti per calcolo fisico)")
else:
self.phys_params_label.config(text="")
except Exception:
self.phys_params_label.config(text="")
# Questa funzione deve solo aggiornare la label dei parametri fisici
def _on_spline_toggle(self): def _on_spline_toggle(self):
self._update_static_preview() self._update_static_preview()
@ -134,6 +194,7 @@ class TrajectoryEditorWindow(tk.Toplevel):
self.wp_tree.column("details", width=250) self.wp_tree.column("details", width=250)
self.wp_tree.pack(side=tk.TOP, fill=tk.BOTH, expand=True) self.wp_tree.pack(side=tk.TOP, fill=tk.BOTH, expand=True)
self.wp_tree.bind('<Double-1>', self._on_edit_waypoint) self.wp_tree.bind('<Double-1>', self._on_edit_waypoint)
self.wp_tree.bind('<<TreeviewSelect>>', lambda e: self._show_physical_params())
btn_frame = ttk.Frame(parent) btn_frame = ttk.Frame(parent)
btn_frame.pack(fill=tk.X, pady=5) btn_frame.pack(fill=tk.X, pady=5)
@ -146,6 +207,8 @@ class TrajectoryEditorWindow(tk.Toplevel):
editor = WaypointEditorWindow(self, is_first_waypoint=True) editor = WaypointEditorWindow(self, is_first_waypoint=True)
if editor.result_waypoint: if editor.result_waypoint:
self.waypoints.append(editor.result_waypoint) self.waypoints.append(editor.result_waypoint)
self._populate_waypoint_list()
self._update_static_preview()
else: else:
# If user cancels creation of the initial point, close the whole editor # If user cancels creation of the initial point, close the whole editor
self.after(10, self._on_cancel) self.after(10, self._on_cancel)
@ -159,8 +222,13 @@ class TrajectoryEditorWindow(tk.Toplevel):
elif wp.maneuver_type == ManeuverType.FLY_FOR_DURATION: elif wp.maneuver_type == ManeuverType.FLY_FOR_DURATION:
vel_kn = (wp.target_velocity_fps or 0.0) * FPS_TO_KNOTS vel_kn = (wp.target_velocity_fps or 0.0) * FPS_TO_KNOTS
details = f"Vel:{vel_kn:.1f}kn, Hdg:{wp.target_heading_deg:.1f}°, T:{wp.duration_s:.1f}s" details = f"Vel:{vel_kn:.1f}kn, Hdg:{wp.target_heading_deg:.1f}°, T:{wp.duration_s:.1f}s"
elif wp.maneuver_type == ManeuverType.DYNAMIC_MANEUVER:
vel_kn = (wp.dynamic_velocity_fps or 0.0) * FPS_TO_KNOTS
details = (
f"DynVel:{vel_kn:.1f}kn, LatG:{wp.lateral_g:.2f}, LongG:{wp.longitudinal_g:.2f}, VertG:{wp.vertical_g:.2f}, T:{wp.duration_s:.1f}s"
)
self.wp_tree.insert("", tk.END, iid=str(i), values=(i, wp.maneuver_type.value, details)) self.wp_tree.insert("", tk.END, iid=str(i), values=(i, wp.maneuver_type.value, details))
self._show_physical_params()
def _on_add_waypoint(self): def _on_add_waypoint(self):
editor = WaypointEditorWindow(self, is_first_waypoint=False) editor = WaypointEditorWindow(self, is_first_waypoint=False)
@ -171,6 +239,7 @@ class TrajectoryEditorWindow(tk.Toplevel):
# Select the newly added item # Select the newly added item
new_item_id = str(len(self.waypoints) - 1) new_item_id = str(len(self.waypoints) - 1)
self.wp_tree.focus(new_item_id); self.wp_tree.selection_set(new_item_id) self.wp_tree.focus(new_item_id); self.wp_tree.selection_set(new_item_id)
self._show_physical_params()
def _on_edit_waypoint(self, event=None): def _on_edit_waypoint(self, event=None):
selected_item = self.wp_tree.focus() selected_item = self.wp_tree.focus()
@ -190,6 +259,7 @@ class TrajectoryEditorWindow(tk.Toplevel):
self._populate_waypoint_list() self._populate_waypoint_list()
self._update_static_preview() self._update_static_preview()
self.wp_tree.focus(selected_item); self.wp_tree.selection_set(selected_item) self.wp_tree.focus(selected_item); self.wp_tree.selection_set(selected_item)
self._show_physical_params()
def _on_remove_waypoint(self): def _on_remove_waypoint(self):
selected_item = self.wp_tree.focus() selected_item = self.wp_tree.focus()
@ -206,6 +276,7 @@ class TrajectoryEditorWindow(tk.Toplevel):
del self.waypoints[wp_index] del self.waypoints[wp_index]
self._populate_waypoint_list() self._populate_waypoint_list()
self._update_static_preview() self._update_static_preview()
self._show_physical_params()
def _update_static_preview(self): def _update_static_preview(self):
"""Draws the static trajectory path on the PPI.""" """Draws the static trajectory path on the PPI."""

View File

@ -45,6 +45,11 @@ class WaypointEditorWindow(tk.Toplevel):
self.t_alt_var = tk.DoubleVar(value=10000.0) self.t_alt_var = tk.DoubleVar(value=10000.0)
self.t_vel_var = tk.DoubleVar(value=300.0) self.t_vel_var = tk.DoubleVar(value=300.0)
self.t_hdg_var = tk.DoubleVar(value=90.0) self.t_hdg_var = tk.DoubleVar(value=90.0)
# Dynamic maneuver variables
self.dyn_vel_var = tk.DoubleVar(value=300.0)
self.lateral_g_var = tk.DoubleVar(value=0.0)
self.longitudinal_g_var = tk.DoubleVar(value=0.0)
self.vertical_g_var = tk.DoubleVar(value=0.0)
def _load_waypoint_data(self, wp: Waypoint): def _load_waypoint_data(self, wp: Waypoint):
"""Populates the fields with data from an existing waypoint.""" """Populates the fields with data from an existing waypoint."""
@ -55,6 +60,11 @@ class WaypointEditorWindow(tk.Toplevel):
self.t_alt_var.set(wp.target_altitude_ft or 10000.0) self.t_alt_var.set(wp.target_altitude_ft or 10000.0)
self.t_vel_var.set((wp.target_velocity_fps or 0.0) * FPS_TO_KNOTS) self.t_vel_var.set((wp.target_velocity_fps or 0.0) * FPS_TO_KNOTS)
self.t_hdg_var.set(wp.target_heading_deg or 0.0) self.t_hdg_var.set(wp.target_heading_deg or 0.0)
# Carica i parametri dinamici se presenti
self.dyn_vel_var.set((getattr(wp, 'dynamic_velocity_fps', 0.0) or 0.0) * FPS_TO_KNOTS)
self.lateral_g_var.set(getattr(wp, 'lateral_g', 0.0) or 0.0)
self.longitudinal_g_var.set(getattr(wp, 'longitudinal_g', 0.0) or 0.0)
self.vertical_g_var.set(getattr(wp, 'vertical_g', 0.0) or 0.0)
def _center_window(self): def _center_window(self):
self.update_idletasks() self.update_idletasks()
@ -85,6 +95,7 @@ class WaypointEditorWindow(tk.Toplevel):
# --- Dynamic Frames --- # --- Dynamic Frames ---
self.fly_to_point_frame = self._create_fly_to_point_frame(main_frame) self.fly_to_point_frame = self._create_fly_to_point_frame(main_frame)
self.fly_for_duration_frame = self._create_fly_for_duration_frame(main_frame) self.fly_for_duration_frame = self._create_fly_for_duration_frame(main_frame)
self.dynamic_maneuver_frame = self._create_dynamic_maneuver_frame(main_frame)
# --- Buttons --- # --- Buttons ---
button_frame = ttk.Frame(main_frame) button_frame = ttk.Frame(main_frame)
@ -100,6 +111,20 @@ class WaypointEditorWindow(tk.Toplevel):
self.maneuver_type_var.set(ManeuverType.FLY_FOR_DURATION.value) self.maneuver_type_var.set(ManeuverType.FLY_FOR_DURATION.value)
self._on_maneuver_type_change() self._on_maneuver_type_change()
def _create_dynamic_maneuver_frame(self, parent) -> ttk.Frame:
frame = ttk.Frame(parent, padding=10)
frame.columnconfigure(1, weight=1)
ttk.Label(frame, text="Duration (s):").grid(row=0, column=0, sticky=tk.W, pady=2)
ttk.Spinbox(frame, from_=0.1, to=3600, textvariable=self.duration_var, width=10).grid(row=0, column=1, sticky=tk.EW, pady=2)
ttk.Label(frame, text="Velocity (kn):").grid(row=1, column=0, sticky=tk.W, pady=2)
ttk.Spinbox(frame, from_=0, to=2000, textvariable=self.dyn_vel_var, width=10).grid(row=1, column=1, sticky=tk.EW, pady=2)
ttk.Label(frame, text="Lateral G:").grid(row=2, column=0, sticky=tk.W, pady=2)
ttk.Spinbox(frame, from_=-12, to=12, textvariable=self.lateral_g_var, width=10, increment=0.1).grid(row=2, column=1, sticky=tk.EW, pady=2)
ttk.Label(frame, text="Longitudinal G:").grid(row=3, column=0, sticky=tk.W, pady=2)
ttk.Spinbox(frame, from_=-5, to=5, textvariable=self.longitudinal_g_var, width=10, increment=0.1).grid(row=3, column=1, sticky=tk.EW, pady=2)
ttk.Label(frame, text="Vertical G:").grid(row=4, column=0, sticky=tk.W, pady=2)
ttk.Spinbox(frame, from_=-5, to=5, textvariable=self.vertical_g_var, width=10, increment=0.1).grid(row=4, column=1, sticky=tk.EW, pady=2)
return frame
def _create_fly_to_point_frame(self, parent) -> ttk.Frame: def _create_fly_to_point_frame(self, parent) -> ttk.Frame:
frame = ttk.Frame(parent, padding=10) frame = ttk.Frame(parent, padding=10)
@ -131,18 +156,20 @@ class WaypointEditorWindow(tk.Toplevel):
def _on_maneuver_type_change(self, event=None): def _on_maneuver_type_change(self, event=None):
m_type = ManeuverType(self.maneuver_type_var.get()) m_type = ManeuverType(self.maneuver_type_var.get())
self.fly_to_point_frame.pack_forget()
self.fly_for_duration_frame.pack_forget()
self.dynamic_maneuver_frame.pack_forget()
if m_type == ManeuverType.FLY_TO_POINT: if m_type == ManeuverType.FLY_TO_POINT:
self.fly_for_duration_frame.pack_forget()
self.fly_to_point_frame.pack(fill=tk.BOTH, expand=True) self.fly_to_point_frame.pack(fill=tk.BOTH, expand=True)
else: # Fly for duration elif m_type == ManeuverType.FLY_FOR_DURATION:
self.fly_to_point_frame.pack_forget()
self.fly_for_duration_frame.pack(fill=tk.BOTH, expand=True) self.fly_for_duration_frame.pack(fill=tk.BOTH, expand=True)
elif m_type == ManeuverType.DYNAMIC_MANEUVER:
self.dynamic_maneuver_frame.pack(fill=tk.BOTH, expand=True)
def _on_ok(self): def _on_ok(self):
try: try:
m_type = ManeuverType(self.maneuver_type_var.get()) m_type = ManeuverType(self.maneuver_type_var.get())
wp = Waypoint(maneuver_type=m_type) wp = Waypoint(maneuver_type=m_type)
if m_type == ManeuverType.FLY_TO_POINT: if m_type == ManeuverType.FLY_TO_POINT:
wp.target_range_nm = self.t_range_var.get() wp.target_range_nm = self.t_range_var.get()
wp.target_azimuth_deg = self.t_az_var.get() wp.target_azimuth_deg = self.t_az_var.get()
@ -152,10 +179,14 @@ class WaypointEditorWindow(tk.Toplevel):
wp.duration_s = self.duration_var.get() wp.duration_s = self.duration_var.get()
wp.target_velocity_fps = self.t_vel_var.get() * KNOTS_TO_FPS wp.target_velocity_fps = self.t_vel_var.get() * KNOTS_TO_FPS
wp.target_heading_deg = self.t_hdg_var.get() wp.target_heading_deg = self.t_hdg_var.get()
# Also save altitude if it's the first waypoint
if self.is_first_waypoint: if self.is_first_waypoint:
wp.target_altitude_ft = self.t_alt_var.get() wp.target_altitude_ft = self.t_alt_var.get()
elif m_type == ManeuverType.DYNAMIC_MANEUVER:
wp.duration_s = self.duration_var.get()
wp.dynamic_velocity_fps = self.dyn_vel_var.get() * KNOTS_TO_FPS
wp.lateral_g = self.lateral_g_var.get()
wp.longitudinal_g = self.longitudinal_g_var.get()
wp.vertical_g = self.vertical_g_var.get()
self.result_waypoint = wp self.result_waypoint = wp
self.destroy() self.destroy()
except (ValueError, tk.TclError) as e: except (ValueError, tk.TclError) as e: