StarPilot

This commit is contained in:
firestar5683
2026-03-12 01:49:47 -05:00
parent 0e9ef526f7
commit d0e1db6766
2171 changed files with 590688 additions and 247754 deletions
+125
View File
@@ -0,0 +1,125 @@
info face="Inter-Black" size=-200 bold=0 italic=0 charset="" unicode=1 stretchH=100 smooth=0 aa=1 padding=0,0,0,0 spacing=0,0 outline=0
common lineHeight=200 base=200 scaleW=2048 scaleH=2048 pages=1 packed=0 alphaChnl=0 redChnl=4 greenChnl=4 blueChnl=4
page id=0 file="Inter-Black.png"
chars count=121
char id=32 x=6 y=6 width=32 height=200 xoffset=0 yoffset=0 xadvance=32 page=0 chnl=15
char id=33 x=50 y=6 width=34 height=123 xoffset=12 yoffset=39 xadvance=58 page=0 chnl=15
char id=34 x=96 y=6 width=53 height=50 xoffset=5 yoffset=39 xadvance=63 page=0 chnl=15
char id=35 x=161 y=6 width=109 height=121 xoffset=0 yoffset=39 xadvance=109 page=0 chnl=15
char id=36 x=282 y=6 width=102 height=152 xoffset=4 yoffset=24 xadvance=110 page=0 chnl=15
char id=37 x=396 y=6 width=121 height=133 xoffset=13 yoffset=33 xadvance=147 page=0 chnl=15
char id=38 x=529 y=6 width=112 height=125 xoffset=5 yoffset=37 xadvance=115 page=0 chnl=15
char id=39 x=653 y=6 width=22 height=50 xoffset=5 yoffset=39 xadvance=33 page=0 chnl=15
char id=40 x=687 y=6 width=57 height=149 xoffset=14 yoffset=39 xadvance=72 page=0 chnl=15
char id=41 x=756 y=6 width=57 height=149 xoffset=2 yoffset=39 xadvance=72 page=0 chnl=15
char id=42 x=825 y=6 width=69 height=69 xoffset=16 yoffset=39 xadvance=100 page=0 chnl=15
char id=43 x=906 y=6 width=88 height=88 xoffset=13 yoffset=67 xadvance=114 page=0 chnl=15
char id=44 x=1006 y=6 width=33 height=47 xoffset=7 yoffset=143 xadvance=52 page=0 chnl=15
char id=45 x=1051 y=6 width=57 height=26 xoffset=11 yoffset=99 xadvance=78 page=0 chnl=15
char id=46 x=1120 y=6 width=34 height=33 xoffset=9 yoffset=129 xadvance=51 page=0 chnl=15
char id=47 x=1166 y=6 width=68 height=145 xoffset=0 yoffset=34 xadvance=68 page=0 chnl=15
char id=48 x=1246 y=6 width=107 height=126 xoffset=7 yoffset=38 xadvance=120 page=0 chnl=15
char id=49 x=1365 y=6 width=60 height=121 xoffset=7 yoffset=39 xadvance=83 page=0 chnl=15
char id=50 x=1437 y=6 width=96 height=122 xoffset=5 yoffset=38 xadvance=106 page=0 chnl=15
char id=51 x=1545 y=6 width=100 height=124 xoffset=6 yoffset=38 xadvance=111 page=0 chnl=15
char id=52 x=1657 y=6 width=105 height=121 xoffset=6 yoffset=39 xadvance=115 page=0 chnl=15
char id=53 x=1774 y=6 width=97 height=123 xoffset=8 yoffset=39 xadvance=110 page=0 chnl=15
char id=54 x=1883 y=6 width=101 height=125 xoffset=6 yoffset=37 xadvance=113 page=0 chnl=15
char id=55 x=6 y=218 width=90 height=121 xoffset=5 yoffset=39 xadvance=100 page=0 chnl=15
char id=56 x=108 y=218 width=102 height=124 xoffset=6 yoffset=38 xadvance=114 page=0 chnl=15
char id=57 x=222 y=218 width=102 height=125 xoffset=6 yoffset=37 xadvance=113 page=0 chnl=15
char id=58 x=336 y=218 width=34 height=94 xoffset=9 yoffset=68 xadvance=51 page=0 chnl=15
char id=59 x=382 y=218 width=38 height=122 xoffset=7 yoffset=68 xadvance=52 page=0 chnl=15
char id=60 x=432 y=218 width=88 height=99 xoffset=13 yoffset=62 xadvance=114 page=0 chnl=15
char id=61 x=532 y=218 width=88 height=69 xoffset=13 yoffset=77 xadvance=114 page=0 chnl=15
char id=62 x=632 y=218 width=88 height=99 xoffset=13 yoffset=62 xadvance=114 page=0 chnl=15
char id=63 x=732 y=218 width=86 height=124 xoffset=6 yoffset=38 xadvance=99 page=0 chnl=15
char id=64 x=830 y=218 width=164 height=160 xoffset=11 yoffset=37 xadvance=180 page=0 chnl=15
char id=65 x=1006 y=218 width=125 height=121 xoffset=3 yoffset=39 xadvance=131 page=0 chnl=15
char id=66 x=1143 y=218 width=100 height=121 xoffset=7 yoffset=39 xadvance=110 page=0 chnl=15
char id=67 x=1255 y=218 width=113 height=124 xoffset=7 yoffset=38 xadvance=126 page=0 chnl=15
char id=68 x=1380 y=218 width=107 height=121 xoffset=7 yoffset=39 xadvance=121 page=0 chnl=15
char id=69 x=1499 y=218 width=88 height=121 xoffset=7 yoffset=39 xadvance=102 page=0 chnl=15
char id=70 x=1599 y=218 width=86 height=121 xoffset=7 yoffset=39 xadvance=96 page=0 chnl=15
char id=71 x=1697 y=218 width=114 height=124 xoffset=7 yoffset=38 xadvance=127 page=0 chnl=15
char id=72 x=1823 y=218 width=110 height=121 xoffset=7 yoffset=39 xadvance=123 page=0 chnl=15
char id=73 x=1945 y=218 width=34 height=121 xoffset=7 yoffset=39 xadvance=48 page=0 chnl=15
char id=74 x=6 y=430 width=89 height=123 xoffset=1 yoffset=39 xadvance=97 page=0 chnl=15
char id=75 x=107 y=430 width=111 height=121 xoffset=7 yoffset=39 xadvance=118 page=0 chnl=15
char id=76 x=230 y=430 width=82 height=121 xoffset=7 yoffset=39 xadvance=94 page=0 chnl=15
char id=77 x=324 y=430 width=140 height=121 xoffset=7 yoffset=39 xadvance=153 page=0 chnl=15
char id=78 x=476 y=430 width=105 height=121 xoffset=7 yoffset=39 xadvance=119 page=0 chnl=15
char id=79 x=593 y=430 width=118 height=124 xoffset=7 yoffset=38 xadvance=131 page=0 chnl=15
char id=80 x=723 y=430 width=98 height=121 xoffset=7 yoffset=39 xadvance=108 page=0 chnl=15
char id=81 x=833 y=430 width=118 height=131 xoffset=7 yoffset=38 xadvance=131 page=0 chnl=15
char id=82 x=963 y=430 width=102 height=121 xoffset=7 yoffset=39 xadvance=110 page=0 chnl=15
char id=83 x=1077 y=430 width=102 height=124 xoffset=4 yoffset=38 xadvance=110 page=0 chnl=15
char id=84 x=1191 y=430 width=105 height=121 xoffset=4 yoffset=39 xadvance=113 page=0 chnl=15
char id=85 x=1308 y=430 width=105 height=123 xoffset=7 yoffset=39 xadvance=119 page=0 chnl=15
char id=86 x=1425 y=430 width=125 height=121 xoffset=3 yoffset=39 xadvance=131 page=0 chnl=15
char id=87 x=1562 y=430 width=176 height=121 xoffset=2 yoffset=39 xadvance=180 page=0 chnl=15
char id=88 x=1750 y=430 width=118 height=121 xoffset=4 yoffset=39 xadvance=126 page=0 chnl=15
char id=89 x=1880 y=430 width=122 height=121 xoffset=2 yoffset=39 xadvance=126 page=0 chnl=15
char id=90 x=6 y=642 width=99 height=121 xoffset=8 yoffset=39 xadvance=114 page=0 chnl=15
char id=91 x=117 y=642 width=55 height=149 xoffset=16 yoffset=39 xadvance=72 page=0 chnl=15
char id=92 x=184 y=642 width=68 height=145 xoffset=0 yoffset=34 xadvance=68 page=0 chnl=15
char id=93 x=264 y=642 width=55 height=149 xoffset=2 yoffset=39 xadvance=72 page=0 chnl=15
char id=94 x=331 y=642 width=74 height=54 xoffset=4 yoffset=41 xadvance=82 page=0 chnl=15
char id=95 x=417 y=642 width=82 height=24 xoffset=0 yoffset=160 xadvance=81 page=0 chnl=15
char id=96 x=511 y=642 width=40 height=27 xoffset=21 yoffset=32 xadvance=82 page=0 chnl=15
char id=97 x=563 y=642 width=88 height=94 xoffset=3 yoffset=68 xadvance=97 page=0 chnl=15
char id=98 x=663 y=642 width=95 height=123 xoffset=7 yoffset=39 xadvance=106 page=0 chnl=15
char id=99 x=770 y=642 width=92 height=94 xoffset=4 yoffset=68 xadvance=100 page=0 chnl=15
char id=100 x=874 y=642 width=94 height=123 xoffset=5 yoffset=39 xadvance=106 page=0 chnl=15
char id=101 x=980 y=642 width=92 height=94 xoffset=4 yoffset=68 xadvance=100 page=0 chnl=15
char id=102 x=1084 y=642 width=66 height=126 xoffset=1 yoffset=34 xadvance=66 page=0 chnl=15
char id=103 x=1162 y=642 width=95 height=127 xoffset=5 yoffset=68 xadvance=107 page=0 chnl=15
char id=104 x=1269 y=642 width=92 height=121 xoffset=8 yoffset=39 xadvance=106 page=0 chnl=15
char id=105 x=1373 y=642 width=33 height=130 xoffset=8 yoffset=30 xadvance=48 page=0 chnl=15
char id=106 x=1418 y=642 width=47 height=164 xoffset=-6 yoffset=30 xadvance=48 page=0 chnl=15
char id=107 x=1477 y=642 width=93 height=121 xoffset=8 yoffset=39 xadvance=100 page=0 chnl=15
char id=108 x=1582 y=642 width=33 height=121 xoffset=8 yoffset=39 xadvance=48 page=0 chnl=15
char id=109 x=1627 y=642 width=140 height=92 xoffset=8 yoffset=68 xadvance=155 page=0 chnl=15
char id=110 x=1779 y=642 width=92 height=92 xoffset=8 yoffset=68 xadvance=106 page=0 chnl=15
char id=111 x=1883 y=642 width=95 height=94 xoffset=4 yoffset=68 xadvance=103 page=0 chnl=15
char id=112 x=6 y=854 width=94 height=126 xoffset=8 yoffset=68 xadvance=107 page=0 chnl=15
char id=113 x=112 y=854 width=94 height=126 xoffset=5 yoffset=68 xadvance=107 page=0 chnl=15
char id=114 x=218 y=854 width=62 height=92 xoffset=8 yoffset=68 xadvance=71 page=0 chnl=15
char id=115 x=292 y=854 width=89 height=94 xoffset=4 yoffset=68 xadvance=97 page=0 chnl=15
char id=116 x=393 y=854 width=62 height=114 xoffset=2 yoffset=48 xadvance=66 page=0 chnl=15
char id=117 x=467 y=854 width=92 height=93 xoffset=8 yoffset=69 xadvance=107 page=0 chnl=15
char id=118 x=571 y=854 width=100 height=91 xoffset=0 yoffset=69 xadvance=100 page=0 chnl=15
char id=119 x=683 y=854 width=142 height=91 xoffset=1 yoffset=69 xadvance=144 page=0 chnl=15
char id=120 x=837 y=854 width=95 height=91 xoffset=2 yoffset=69 xadvance=98 page=0 chnl=15
char id=121 x=944 y=854 width=100 height=125 xoffset=0 yoffset=69 xadvance=100 page=0 chnl=15
char id=122 x=1056 y=854 width=80 height=91 xoffset=9 yoffset=69 xadvance=97 page=0 chnl=15
char id=123 x=1148 y=854 width=67 height=149 xoffset=4 yoffset=39 xadvance=72 page=0 chnl=15
char id=124 x=1227 y=854 width=33 height=198 xoffset=17 yoffset=1 xadvance=66 page=0 chnl=15
char id=125 x=1272 y=854 width=67 height=149 xoffset=2 yoffset=39 xadvance=72 page=0 chnl=15
char id=126 x=1351 y=854 width=93 height=43 xoffset=11 yoffset=90 xadvance=114 page=0 chnl=15
char id=163 x=1456 y=854 width=94 height=122 xoffset=11 yoffset=38 xadvance=115 page=0 chnl=15
char id=165 x=1562 y=854 width=93 height=121 xoffset=2 yoffset=39 xadvance=97 page=0 chnl=15
char id=167 x=1667 y=854 width=85 height=147 xoffset=9 yoffset=38 xadvance=102 page=0 chnl=15
char id=176 x=1764 y=854 width=61 height=61 xoffset=8 yoffset=39 xadvance=76 page=0 chnl=15
char id=178 x=1837 y=854 width=62 height=74 xoffset=4 yoffset=26 xadvance=70 page=0 chnl=15
char id=215 x=1911 y=854 width=93 height=94 xoffset=11 yoffset=64 xadvance=114 page=0 chnl=15
char id=231 x=6 y=1066 width=92 height=132 xoffset=4 yoffset=68 xadvance=100 page=0 chnl=15
char id=234 x=110 y=1066 width=92 height=131 xoffset=4 yoffset=31 xadvance=100 page=0 chnl=15
char id=241 x=214 y=1066 width=92 height=127 xoffset=8 yoffset=33 xadvance=106 page=0 chnl=15
char id=252 x=318 y=1066 width=92 height=130 xoffset=8 yoffset=32 xadvance=107 page=0 chnl=15
char id=8209 x=422 y=1066 width=57 height=26 xoffset=18 yoffset=99 xadvance=88 page=0 chnl=15
char id=8211 x=491 y=1066 width=83 height=26 xoffset=0 yoffset=99 xadvance=82 page=0 chnl=15
char id=8226 x=586 y=1066 width=52 height=52 xoffset=7 yoffset=86 xadvance=65 page=0 chnl=15
char id=8364 x=650 y=1066 width=103 height=124 xoffset=5 yoffset=38 xadvance=115 page=0 chnl=15
char id=8627 x=765 y=1066 width=139 height=112 xoffset=16 yoffset=39 xadvance=170 page=0 chnl=15
char id=8679 x=916 y=1066 width=170 height=130 xoffset=11 yoffset=30 xadvance=191 page=0 chnl=15
char id=9003 x=1098 y=1066 width=173 height=121 xoffset=13 yoffset=39 xadvance=200 page=0 chnl=15
char id=9251 x=1283 y=1066 width=110 height=41 xoffset=10 yoffset=148 xadvance=130 page=0 chnl=15
char id=9654 x=1405 y=1066 width=135 height=152 xoffset=18 yoffset=24 xadvance=162 page=0 chnl=15
char id=9664 x=1552 y=1066 width=135 height=152 xoffset=9 yoffset=24 xadvance=162 page=0 chnl=15
char id=9675 x=1699 y=1066 width=125 height=125 xoffset=13 yoffset=37 xadvance=151 page=0 chnl=15
char id=9679 x=1836 y=1066 width=125 height=125 xoffset=13 yoffset=37 xadvance=151 page=0 chnl=15
char id=9881 x=1973 y=1066 width=0 height=0 xoffset=0 yoffset=0 xadvance=0 page=0 chnl=15
char id=10003 x=6 y=1278 width=127 height=101 xoffset=14 yoffset=57 xadvance=145 page=0 chnl=15
char id=10004 x=145 y=1278 width=0 height=0 xoffset=0 yoffset=0 xadvance=0 page=0 chnl=15
char id=10005 x=157 y=1278 width=0 height=0 xoffset=0 yoffset=0 xadvance=0 page=0 chnl=15
Binary file not shown.

After

Width:  |  Height:  |  Size: 211 KiB

+125
View File
@@ -0,0 +1,125 @@
info face="Inter-Bold" size=-200 bold=0 italic=0 charset="" unicode=1 stretchH=100 smooth=0 aa=1 padding=0,0,0,0 spacing=0,0 outline=0
common lineHeight=200 base=200 scaleW=2048 scaleH=2048 pages=1 packed=0 alphaChnl=0 redChnl=4 greenChnl=4 blueChnl=4
page id=0 file="Inter-Bold.png"
chars count=121
char id=32 x=6 y=6 width=38 height=200 xoffset=0 yoffset=0 xadvance=38 page=0 chnl=15
char id=33 x=56 y=6 width=29 height=123 xoffset=12 yoffset=39 xadvance=53 page=0 chnl=15
char id=34 x=97 y=6 width=49 height=47 xoffset=8 yoffset=39 xadvance=64 page=0 chnl=15
char id=35 x=158 y=6 width=105 height=121 xoffset=1 yoffset=39 xadvance=107 page=0 chnl=15
char id=36 x=275 y=6 width=96 height=152 xoffset=6 yoffset=24 xadvance=108 page=0 chnl=15
char id=37 x=383 y=6 width=116 height=130 xoffset=13 yoffset=35 xadvance=142 page=0 chnl=15
char id=38 x=511 y=6 width=105 height=124 xoffset=6 yoffset=38 xadvance=111 page=0 chnl=15
char id=39 x=628 y=6 width=19 height=47 xoffset=8 yoffset=39 xadvance=34 page=0 chnl=15
char id=40 x=659 y=6 width=49 height=150 xoffset=15 yoffset=37 xadvance=67 page=0 chnl=15
char id=41 x=720 y=6 width=49 height=150 xoffset=4 yoffset=37 xadvance=67 page=0 chnl=15
char id=42 x=781 y=6 width=67 height=69 xoffset=13 yoffset=39 xadvance=93 page=0 chnl=15
char id=43 x=860 y=6 width=84 height=84 xoffset=14 yoffset=70 xadvance=112 page=0 chnl=15
char id=44 x=956 y=6 width=29 height=47 xoffset=9 yoffset=143 xadvance=50 page=0 chnl=15
char id=45 x=997 y=6 width=56 height=21 xoffset=11 yoffset=100 xadvance=77 page=0 chnl=15
char id=46 x=1065 y=6 width=29 height=29 xoffset=10 yoffset=133 xadvance=49 page=0 chnl=15
char id=47 x=1106 y=6 width=61 height=145 xoffset=2 yoffset=34 xadvance=64 page=0 chnl=15
char id=48 x=1179 y=6 width=98 height=125 xoffset=8 yoffset=38 xadvance=113 page=0 chnl=15
char id=49 x=1289 y=6 width=55 height=121 xoffset=8 yoffset=39 xadvance=80 page=0 chnl=15
char id=50 x=1356 y=6 width=88 height=122 xoffset=8 yoffset=38 xadvance=104 page=0 chnl=15
char id=51 x=1456 y=6 width=93 height=124 xoffset=8 yoffset=38 xadvance=108 page=0 chnl=15
char id=52 x=1561 y=6 width=98 height=121 xoffset=7 yoffset=39 xadvance=112 page=0 chnl=15
char id=53 x=1671 y=6 width=89 height=123 xoffset=10 yoffset=39 xadvance=106 page=0 chnl=15
char id=54 x=1772 y=6 width=94 height=125 xoffset=7 yoffset=37 xadvance=109 page=0 chnl=15
char id=55 x=1878 y=6 width=85 height=121 xoffset=6 yoffset=39 xadvance=98 page=0 chnl=15
char id=56 x=6 y=218 width=95 height=124 xoffset=7 yoffset=38 xadvance=109 page=0 chnl=15
char id=57 x=113 y=218 width=94 height=125 xoffset=8 yoffset=37 xadvance=109 page=0 chnl=15
char id=58 x=219 y=218 width=29 height=92 xoffset=10 yoffset=70 xadvance=49 page=0 chnl=15
char id=59 x=260 y=218 width=32 height=120 xoffset=9 yoffset=70 xadvance=49 page=0 chnl=15
char id=60 x=304 y=218 width=84 height=94 xoffset=14 yoffset=65 xadvance=112 page=0 chnl=15
char id=61 x=400 y=218 width=82 height=60 xoffset=15 yoffset=82 xadvance=112 page=0 chnl=15
char id=62 x=494 y=218 width=84 height=94 xoffset=14 yoffset=65 xadvance=112 page=0 chnl=15
char id=63 x=590 y=218 width=80 height=124 xoffset=6 yoffset=38 xadvance=92 page=0 chnl=15
char id=64 x=682 y=218 width=155 height=155 xoffset=9 yoffset=40 xadvance=170 page=0 chnl=15
char id=65 x=849 y=218 width=117 height=121 xoffset=3 yoffset=39 xadvance=123 page=0 chnl=15
char id=66 x=978 y=218 width=94 height=121 xoffset=10 yoffset=39 xadvance=109 page=0 chnl=15
char id=67 x=1084 y=218 width=109 height=124 xoffset=8 yoffset=38 xadvance=124 page=0 chnl=15
char id=68 x=1205 y=218 width=102 height=121 xoffset=10 yoffset=39 xadvance=120 page=0 chnl=15
char id=69 x=1319 y=218 width=82 height=121 xoffset=10 yoffset=39 xadvance=101 page=0 chnl=15
char id=70 x=1413 y=218 width=81 height=121 xoffset=10 yoffset=39 xadvance=96 page=0 chnl=15
char id=71 x=1506 y=218 width=110 height=124 xoffset=8 yoffset=38 xadvance=125 page=0 chnl=15
char id=72 x=1628 y=218 width=103 height=121 xoffset=10 yoffset=39 xadvance=123 page=0 chnl=15
char id=73 x=1743 y=218 width=26 height=121 xoffset=10 yoffset=39 xadvance=46 page=0 chnl=15
char id=74 x=1781 y=218 width=81 height=123 xoffset=3 yoffset=39 xadvance=94 page=0 chnl=15
char id=75 x=1874 y=218 width=102 height=121 xoffset=10 yoffset=39 xadvance=113 page=0 chnl=15
char id=76 x=6 y=430 width=78 height=121 xoffset=10 yoffset=39 xadvance=93 page=0 chnl=15
char id=77 x=96 y=430 width=131 height=121 xoffset=10 yoffset=39 xadvance=151 page=0 chnl=15
char id=78 x=239 y=430 width=101 height=121 xoffset=10 yoffset=39 xadvance=121 page=0 chnl=15
char id=79 x=352 y=430 width=113 height=124 xoffset=8 yoffset=38 xadvance=129 page=0 chnl=15
char id=80 x=477 y=430 width=91 height=121 xoffset=10 yoffset=39 xadvance=107 page=0 chnl=15
char id=81 x=580 y=430 width=113 height=132 xoffset=8 yoffset=38 xadvance=129 page=0 chnl=15
char id=82 x=705 y=430 width=96 height=121 xoffset=10 yoffset=39 xadvance=108 page=0 chnl=15
char id=83 x=813 y=430 width=96 height=124 xoffset=6 yoffset=38 xadvance=108 page=0 chnl=15
char id=84 x=921 y=430 width=100 height=121 xoffset=5 yoffset=39 xadvance=110 page=0 chnl=15
char id=85 x=1033 y=430 width=101 height=123 xoffset=10 yoffset=39 xadvance=120 page=0 chnl=15
char id=86 x=1146 y=430 width=117 height=121 xoffset=3 yoffset=39 xadvance=123 page=0 chnl=15
char id=87 x=1275 y=430 width=166 height=121 xoffset=3 yoffset=39 xadvance=171 page=0 chnl=15
char id=88 x=1453 y=430 width=110 height=121 xoffset=4 yoffset=39 xadvance=118 page=0 chnl=15
char id=89 x=1575 y=430 width=114 height=121 xoffset=3 yoffset=39 xadvance=119 page=0 chnl=15
char id=90 x=1701 y=430 width=93 height=121 xoffset=9 yoffset=39 xadvance=110 page=0 chnl=15
char id=91 x=1806 y=430 width=47 height=150 xoffset=17 yoffset=37 xadvance=67 page=0 chnl=15
char id=92 x=1865 y=430 width=61 height=145 xoffset=2 yoffset=34 xadvance=64 page=0 chnl=15
char id=93 x=1938 y=430 width=48 height=150 xoffset=3 yoffset=37 xadvance=67 page=0 chnl=15
char id=94 x=6 y=642 width=71 height=53 xoffset=5 yoffset=42 xadvance=80 page=0 chnl=15
char id=95 x=89 y=642 width=80 height=19 xoffset=-1 yoffset=160 xadvance=78 page=0 chnl=15
char id=96 x=181 y=642 width=36 height=28 xoffset=23 yoffset=31 xadvance=82 page=0 chnl=15
char id=97 x=229 y=642 width=82 height=94 xoffset=5 yoffset=68 xadvance=95 page=0 chnl=15
char id=98 x=323 y=642 width=89 height=123 xoffset=10 yoffset=39 xadvance=104 page=0 chnl=15
char id=99 x=424 y=642 width=85 height=94 xoffset=6 yoffset=68 xadvance=97 page=0 chnl=15
char id=100 x=521 y=642 width=89 height=123 xoffset=6 yoffset=39 xadvance=104 page=0 chnl=15
char id=101 x=622 y=642 width=87 height=94 xoffset=6 yoffset=68 xadvance=98 page=0 chnl=15
char id=102 x=721 y=642 width=61 height=126 xoffset=2 yoffset=34 xadvance=63 page=0 chnl=15
char id=103 x=794 y=642 width=89 height=128 xoffset=6 yoffset=68 xadvance=104 page=0 chnl=15
char id=104 x=895 y=642 width=85 height=121 xoffset=9 yoffset=39 xadvance=103 page=0 chnl=15
char id=105 x=992 y=642 width=28 height=128 xoffset=9 yoffset=32 xadvance=44 page=0 chnl=15
char id=106 x=1032 y=642 width=41 height=162 xoffset=-5 yoffset=32 xadvance=44 page=0 chnl=15
char id=107 x=1085 y=642 width=87 height=121 xoffset=9 yoffset=39 xadvance=96 page=0 chnl=15
char id=108 x=1184 y=642 width=26 height=121 xoffset=9 yoffset=39 xadvance=44 page=0 chnl=15
char id=109 x=1222 y=642 width=132 height=92 xoffset=9 yoffset=68 xadvance=150 page=0 chnl=15
char id=110 x=1366 y=642 width=85 height=92 xoffset=9 yoffset=68 xadvance=102 page=0 chnl=15
char id=111 x=1463 y=642 width=90 height=94 xoffset=6 yoffset=68 xadvance=101 page=0 chnl=15
char id=112 x=1565 y=642 width=89 height=126 xoffset=9 yoffset=68 xadvance=104 page=0 chnl=15
char id=113 x=1666 y=642 width=89 height=126 xoffset=6 yoffset=68 xadvance=104 page=0 chnl=15
char id=114 x=1767 y=642 width=56 height=92 xoffset=9 yoffset=68 xadvance=67 page=0 chnl=15
char id=115 x=1835 y=642 width=83 height=94 xoffset=5 yoffset=68 xadvance=92 page=0 chnl=15
char id=116 x=1930 y=642 width=57 height=114 xoffset=3 yoffset=48 xadvance=64 page=0 chnl=15
char id=117 x=6 y=854 width=84 height=93 xoffset=9 yoffset=69 xadvance=102 page=0 chnl=15
char id=118 x=102 y=854 width=93 height=91 xoffset=2 yoffset=69 xadvance=96 page=0 chnl=15
char id=119 x=207 y=854 width=135 height=91 xoffset=3 yoffset=69 xadvance=140 page=0 chnl=15
char id=120 x=354 y=854 width=87 height=91 xoffset=4 yoffset=69 xadvance=94 page=0 chnl=15
char id=121 x=453 y=854 width=93 height=125 xoffset=2 yoffset=69 xadvance=96 page=0 chnl=15
char id=122 x=558 y=854 width=77 height=91 xoffset=9 yoffset=69 xadvance=94 page=0 chnl=15
char id=123 x=647 y=854 width=59 height=150 xoffset=5 yoffset=37 xadvance=67 page=0 chnl=15
char id=124 x=718 y=854 width=24 height=198 xoffset=19 yoffset=1 xadvance=61 page=0 chnl=15
char id=125 x=754 y=854 width=60 height=150 xoffset=3 yoffset=37 xadvance=67 page=0 chnl=15
char id=126 x=826 y=854 width=89 height=38 xoffset=12 yoffset=93 xadvance=112 page=0 chnl=15
char id=163 x=927 y=854 width=89 height=122 xoffset=11 yoffset=38 xadvance=110 page=0 chnl=15
char id=165 x=1028 y=854 width=91 height=121 xoffset=2 yoffset=39 xadvance=94 page=0 chnl=15
char id=167 x=1131 y=854 width=82 height=147 xoffset=8 yoffset=38 xadvance=98 page=0 chnl=15
char id=176 x=1225 y=854 width=58 height=59 xoffset=9 yoffset=38 xadvance=75 page=0 chnl=15
char id=178 x=1295 y=854 width=60 height=74 xoffset=5 yoffset=26 xadvance=70 page=0 chnl=15
char id=215 x=1367 y=854 width=86 height=86 xoffset=13 yoffset=69 xadvance=112 page=0 chnl=15
char id=231 x=1465 y=854 width=85 height=131 xoffset=6 yoffset=68 xadvance=97 page=0 chnl=15
char id=234 x=1562 y=854 width=87 height=130 xoffset=6 yoffset=32 xadvance=98 page=0 chnl=15
char id=241 x=1661 y=854 width=85 height=125 xoffset=9 yoffset=35 xadvance=102 page=0 chnl=15
char id=252 x=1758 y=854 width=84 height=129 xoffset=9 yoffset=33 xadvance=102 page=0 chnl=15
char id=8209 x=1854 y=854 width=56 height=21 xoffset=12 yoffset=100 xadvance=76 page=0 chnl=15
char id=8211 x=1922 y=854 width=83 height=21 xoffset=0 yoffset=100 xadvance=82 page=0 chnl=15
char id=8226 x=6 y=1066 width=53 height=52 xoffset=12 yoffset=86 xadvance=76 page=0 chnl=15
char id=8364 x=71 y=1066 width=101 height=124 xoffset=4 yoffset=38 xadvance=113 page=0 chnl=15
char id=8627 x=184 y=1066 width=136 height=109 xoffset=16 yoffset=39 xadvance=167 page=0 chnl=15
char id=8679 x=332 y=1066 width=158 height=130 xoffset=12 yoffset=30 xadvance=182 page=0 chnl=15
char id=9003 x=502 y=1066 width=173 height=121 xoffset=13 yoffset=39 xadvance=200 page=0 chnl=15
char id=9251 x=687 y=1066 width=104 height=40 xoffset=11 yoffset=148 xadvance=126 page=0 chnl=15
char id=9654 x=803 y=1066 width=132 height=152 xoffset=17 yoffset=24 xadvance=157 page=0 chnl=15
char id=9664 x=947 y=1066 width=132 height=152 xoffset=9 yoffset=24 xadvance=157 page=0 chnl=15
char id=9675 x=1091 y=1066 width=125 height=125 xoffset=13 yoffset=37 xadvance=151 page=0 chnl=15
char id=9679 x=1228 y=1066 width=125 height=125 xoffset=13 yoffset=37 xadvance=151 page=0 chnl=15
char id=9881 x=1365 y=1066 width=0 height=0 xoffset=0 yoffset=0 xadvance=0 page=0 chnl=15
char id=10003 x=1377 y=1066 width=122 height=94 xoffset=15 yoffset=59 xadvance=145 page=0 chnl=15
char id=10004 x=1511 y=1066 width=0 height=0 xoffset=0 yoffset=0 xadvance=0 page=0 chnl=15
char id=10005 x=1523 y=1066 width=0 height=0 xoffset=0 yoffset=0 xadvance=0 page=0 chnl=15
Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

+125
View File
@@ -0,0 +1,125 @@
info face="Inter-ExtraBold" size=-200 bold=0 italic=0 charset="" unicode=1 stretchH=100 smooth=0 aa=1 padding=0,0,0,0 spacing=0,0 outline=0
common lineHeight=200 base=200 scaleW=2048 scaleH=2048 pages=1 packed=0 alphaChnl=0 redChnl=4 greenChnl=4 blueChnl=4
page id=0 file="Inter-ExtraBold.png"
chars count=121
char id=32 x=6 y=6 width=35 height=200 xoffset=0 yoffset=0 xadvance=35 page=0 chnl=15
char id=33 x=53 y=6 width=32 height=123 xoffset=12 yoffset=39 xadvance=56 page=0 chnl=15
char id=34 x=97 y=6 width=50 height=48 xoffset=7 yoffset=39 xadvance=64 page=0 chnl=15
char id=35 x=159 y=6 width=107 height=121 xoffset=1 yoffset=39 xadvance=108 page=0 chnl=15
char id=36 x=278 y=6 width=99 height=152 xoffset=5 yoffset=24 xadvance=109 page=0 chnl=15
char id=37 x=389 y=6 width=119 height=132 xoffset=13 yoffset=34 xadvance=144 page=0 chnl=15
char id=38 x=520 y=6 width=109 height=124 xoffset=5 yoffset=38 xadvance=113 page=0 chnl=15
char id=39 x=641 y=6 width=20 height=48 xoffset=7 yoffset=39 xadvance=33 page=0 chnl=15
char id=40 x=673 y=6 width=53 height=149 xoffset=14 yoffset=38 xadvance=70 page=0 chnl=15
char id=41 x=738 y=6 width=53 height=149 xoffset=3 yoffset=38 xadvance=70 page=0 chnl=15
char id=42 x=803 y=6 width=69 height=69 xoffset=14 yoffset=39 xadvance=96 page=0 chnl=15
char id=43 x=884 y=6 width=86 height=86 xoffset=14 yoffset=69 xadvance=113 page=0 chnl=15
char id=44 x=982 y=6 width=31 height=47 xoffset=8 yoffset=143 xadvance=51 page=0 chnl=15
char id=45 x=1025 y=6 width=56 height=23 xoffset=11 yoffset=100 xadvance=77 page=0 chnl=15
char id=46 x=1093 y=6 width=32 height=31 xoffset=9 yoffset=131 xadvance=50 page=0 chnl=15
char id=47 x=1137 y=6 width=64 height=145 xoffset=1 yoffset=34 xadvance=66 page=0 chnl=15
char id=48 x=1213 y=6 width=103 height=125 xoffset=7 yoffset=38 xadvance=117 page=0 chnl=15
char id=49 x=1328 y=6 width=58 height=121 xoffset=7 yoffset=39 xadvance=82 page=0 chnl=15
char id=50 x=1398 y=6 width=91 height=122 xoffset=7 yoffset=38 xadvance=105 page=0 chnl=15
char id=51 x=1501 y=6 width=96 height=124 xoffset=7 yoffset=38 xadvance=110 page=0 chnl=15
char id=52 x=1609 y=6 width=101 height=121 xoffset=7 yoffset=39 xadvance=113 page=0 chnl=15
char id=53 x=1722 y=6 width=93 height=123 xoffset=9 yoffset=39 xadvance=108 page=0 chnl=15
char id=54 x=1827 y=6 width=98 height=125 xoffset=6 yoffset=37 xadvance=111 page=0 chnl=15
char id=55 x=1937 y=6 width=88 height=121 xoffset=5 yoffset=39 xadvance=99 page=0 chnl=15
char id=56 x=6 y=218 width=98 height=124 xoffset=7 yoffset=38 xadvance=111 page=0 chnl=15
char id=57 x=116 y=218 width=98 height=125 xoffset=7 yoffset=37 xadvance=111 page=0 chnl=15
char id=58 x=226 y=218 width=32 height=93 xoffset=9 yoffset=69 xadvance=50 page=0 chnl=15
char id=59 x=270 y=218 width=35 height=121 xoffset=8 yoffset=69 xadvance=50 page=0 chnl=15
char id=60 x=317 y=218 width=86 height=97 xoffset=14 yoffset=63 xadvance=113 page=0 chnl=15
char id=61 x=415 y=218 width=85 height=65 xoffset=14 yoffset=79 xadvance=113 page=0 chnl=15
char id=62 x=512 y=218 width=86 height=97 xoffset=14 yoffset=63 xadvance=113 page=0 chnl=15
char id=63 x=610 y=218 width=83 height=124 xoffset=6 yoffset=38 xadvance=96 page=0 chnl=15
char id=64 x=705 y=218 width=160 height=158 xoffset=10 yoffset=38 xadvance=175 page=0 chnl=15
char id=65 x=877 y=218 width=121 height=121 xoffset=3 yoffset=39 xadvance=127 page=0 chnl=15
char id=66 x=1010 y=218 width=96 height=121 xoffset=9 yoffset=39 xadvance=109 page=0 chnl=15
char id=67 x=1118 y=218 width=112 height=124 xoffset=7 yoffset=38 xadvance=125 page=0 chnl=15
char id=68 x=1242 y=218 width=104 height=121 xoffset=9 yoffset=39 xadvance=120 page=0 chnl=15
char id=69 x=1358 y=218 width=84 height=121 xoffset=9 yoffset=39 xadvance=101 page=0 chnl=15
char id=70 x=1454 y=218 width=83 height=121 xoffset=9 yoffset=39 xadvance=96 page=0 chnl=15
char id=71 x=1549 y=218 width=112 height=124 xoffset=7 yoffset=38 xadvance=126 page=0 chnl=15
char id=72 x=1673 y=218 width=106 height=121 xoffset=9 yoffset=39 xadvance=123 page=0 chnl=15
char id=73 x=1791 y=218 width=30 height=121 xoffset=9 yoffset=39 xadvance=47 page=0 chnl=15
char id=74 x=1833 y=218 width=85 height=123 xoffset=2 yoffset=39 xadvance=95 page=0 chnl=15
char id=75 x=6 y=430 width=106 height=121 xoffset=9 yoffset=39 xadvance=116 page=0 chnl=15
char id=76 x=124 y=430 width=80 height=121 xoffset=9 yoffset=39 xadvance=94 page=0 chnl=15
char id=77 x=216 y=430 width=135 height=121 xoffset=9 yoffset=39 xadvance=152 page=0 chnl=15
char id=78 x=363 y=430 width=103 height=121 xoffset=9 yoffset=39 xadvance=120 page=0 chnl=15
char id=79 x=478 y=430 width=116 height=124 xoffset=7 yoffset=38 xadvance=130 page=0 chnl=15
char id=80 x=606 y=430 width=94 height=121 xoffset=9 yoffset=39 xadvance=107 page=0 chnl=15
char id=81 x=712 y=430 width=116 height=132 xoffset=7 yoffset=38 xadvance=130 page=0 chnl=15
char id=82 x=840 y=430 width=98 height=121 xoffset=9 yoffset=39 xadvance=109 page=0 chnl=15
char id=83 x=950 y=430 width=99 height=124 xoffset=5 yoffset=38 xadvance=109 page=0 chnl=15
char id=84 x=1061 y=430 width=102 height=121 xoffset=5 yoffset=39 xadvance=111 page=0 chnl=15
char id=85 x=1175 y=430 width=102 height=123 xoffset=9 yoffset=39 xadvance=119 page=0 chnl=15
char id=86 x=1289 y=430 width=121 height=121 xoffset=3 yoffset=39 xadvance=127 page=0 chnl=15
char id=87 x=1422 y=430 width=172 height=121 xoffset=2 yoffset=39 xadvance=175 page=0 chnl=15
char id=88 x=1606 y=430 width=114 height=121 xoffset=4 yoffset=39 xadvance=122 page=0 chnl=15
char id=89 x=1732 y=430 width=117 height=121 xoffset=3 yoffset=39 xadvance=123 page=0 chnl=15
char id=90 x=1861 y=430 width=96 height=121 xoffset=8 yoffset=39 xadvance=112 page=0 chnl=15
char id=91 x=1969 y=430 width=52 height=149 xoffset=16 yoffset=38 xadvance=70 page=0 chnl=15
char id=92 x=6 y=642 width=64 height=145 xoffset=1 yoffset=34 xadvance=66 page=0 chnl=15
char id=93 x=82 y=642 width=51 height=149 xoffset=3 yoffset=38 xadvance=70 page=0 chnl=15
char id=94 x=145 y=642 width=72 height=53 xoffset=5 yoffset=42 xadvance=81 page=0 chnl=15
char id=95 x=229 y=642 width=82 height=22 xoffset=-1 yoffset=160 xadvance=80 page=0 chnl=15
char id=96 x=323 y=642 width=38 height=28 xoffset=22 yoffset=31 xadvance=82 page=0 chnl=15
char id=97 x=373 y=642 width=85 height=94 xoffset=4 yoffset=68 xadvance=96 page=0 chnl=15
char id=98 x=470 y=642 width=91 height=123 xoffset=9 yoffset=39 xadvance=105 page=0 chnl=15
char id=99 x=573 y=642 width=89 height=94 xoffset=5 yoffset=68 xadvance=98 page=0 chnl=15
char id=100 x=674 y=642 width=92 height=123 xoffset=5 yoffset=39 xadvance=105 page=0 chnl=15
char id=101 x=778 y=642 width=90 height=94 xoffset=5 yoffset=68 xadvance=99 page=0 chnl=15
char id=102 x=880 y=642 width=63 height=126 xoffset=2 yoffset=34 xadvance=65 page=0 chnl=15
char id=103 x=955 y=642 width=92 height=127 xoffset=5 yoffset=68 xadvance=105 page=0 chnl=15
char id=104 x=1059 y=642 width=88 height=121 xoffset=9 yoffset=39 xadvance=104 page=0 chnl=15
char id=105 x=1159 y=642 width=31 height=129 xoffset=8 yoffset=31 xadvance=46 page=0 chnl=15
char id=106 x=1202 y=642 width=44 height=163 xoffset=-5 yoffset=31 xadvance=46 page=0 chnl=15
char id=107 x=1258 y=642 width=90 height=121 xoffset=9 yoffset=39 xadvance=98 page=0 chnl=15
char id=108 x=1360 y=642 width=29 height=121 xoffset=9 yoffset=39 xadvance=46 page=0 chnl=15
char id=109 x=1401 y=642 width=136 height=92 xoffset=9 yoffset=68 xadvance=153 page=0 chnl=15
char id=110 x=1549 y=642 width=88 height=92 xoffset=9 yoffset=68 xadvance=104 page=0 chnl=15
char id=111 x=1649 y=642 width=92 height=94 xoffset=5 yoffset=68 xadvance=102 page=0 chnl=15
char id=112 x=1753 y=642 width=91 height=126 xoffset=9 yoffset=68 xadvance=105 page=0 chnl=15
char id=113 x=1856 y=642 width=92 height=126 xoffset=5 yoffset=68 xadvance=105 page=0 chnl=15
char id=114 x=1960 y=642 width=58 height=92 xoffset=9 yoffset=68 xadvance=69 page=0 chnl=15
char id=115 x=6 y=854 width=86 height=94 xoffset=5 yoffset=68 xadvance=95 page=0 chnl=15
char id=116 x=104 y=854 width=59 height=114 xoffset=3 yoffset=48 xadvance=65 page=0 chnl=15
char id=117 x=175 y=854 width=87 height=93 xoffset=9 yoffset=69 xadvance=105 page=0 chnl=15
char id=118 x=274 y=854 width=96 height=91 xoffset=1 yoffset=69 xadvance=98 page=0 chnl=15
char id=119 x=382 y=854 width=139 height=91 xoffset=2 yoffset=69 xadvance=142 page=0 chnl=15
char id=120 x=533 y=854 width=91 height=91 xoffset=3 yoffset=69 xadvance=96 page=0 chnl=15
char id=121 x=636 y=854 width=96 height=125 xoffset=1 yoffset=69 xadvance=98 page=0 chnl=15
char id=122 x=744 y=854 width=78 height=91 xoffset=9 yoffset=69 xadvance=96 page=0 chnl=15
char id=123 x=834 y=854 width=64 height=149 xoffset=4 yoffset=38 xadvance=70 page=0 chnl=15
char id=124 x=910 y=854 width=29 height=198 xoffset=18 yoffset=1 xadvance=64 page=0 chnl=15
char id=125 x=951 y=854 width=63 height=149 xoffset=3 yoffset=38 xadvance=70 page=0 chnl=15
char id=126 x=1026 y=854 width=91 height=41 xoffset=11 yoffset=91 xadvance=113 page=0 chnl=15
char id=163 x=1129 y=854 width=91 height=122 xoffset=11 yoffset=38 xadvance=113 page=0 chnl=15
char id=165 x=1232 y=854 width=92 height=121 xoffset=2 yoffset=39 xadvance=95 page=0 chnl=15
char id=167 x=1336 y=854 width=83 height=147 xoffset=9 yoffset=38 xadvance=100 page=0 chnl=15
char id=176 x=1431 y=854 width=60 height=59 xoffset=8 yoffset=39 xadvance=76 page=0 chnl=15
char id=178 x=1503 y=854 width=61 height=74 xoffset=5 yoffset=26 xadvance=70 page=0 chnl=15
char id=215 x=1576 y=854 width=90 height=90 xoffset=12 yoffset=67 xadvance=113 page=0 chnl=15
char id=231 x=1678 y=854 width=89 height=131 xoffset=5 yoffset=68 xadvance=98 page=0 chnl=15
char id=234 x=1779 y=854 width=90 height=130 xoffset=5 yoffset=32 xadvance=99 page=0 chnl=15
char id=241 x=1881 y=854 width=88 height=126 xoffset=9 yoffset=34 xadvance=104 page=0 chnl=15
char id=252 x=6 y=1066 width=87 height=130 xoffset=9 yoffset=32 xadvance=105 page=0 chnl=15
char id=8209 x=105 y=1066 width=57 height=23 xoffset=15 yoffset=100 xadvance=82 page=0 chnl=15
char id=8211 x=174 y=1066 width=83 height=23 xoffset=0 yoffset=100 xadvance=82 page=0 chnl=15
char id=8226 x=269 y=1066 width=53 height=52 xoffset=9 yoffset=86 xadvance=71 page=0 chnl=15
char id=8364 x=334 y=1066 width=102 height=124 xoffset=4 yoffset=38 xadvance=114 page=0 chnl=15
char id=8627 x=448 y=1066 width=137 height=110 xoffset=16 yoffset=39 xadvance=169 page=0 chnl=15
char id=8679 x=597 y=1066 width=163 height=130 xoffset=12 yoffset=30 xadvance=187 page=0 chnl=15
char id=9003 x=772 y=1066 width=173 height=121 xoffset=13 yoffset=39 xadvance=200 page=0 chnl=15
char id=9251 x=957 y=1066 width=107 height=41 xoffset=11 yoffset=148 xadvance=128 page=0 chnl=15
char id=9654 x=1076 y=1066 width=134 height=152 xoffset=17 yoffset=24 xadvance=160 page=0 chnl=15
char id=9664 x=1222 y=1066 width=134 height=152 xoffset=9 yoffset=24 xadvance=160 page=0 chnl=15
char id=9675 x=1368 y=1066 width=125 height=125 xoffset=13 yoffset=37 xadvance=151 page=0 chnl=15
char id=9679 x=1505 y=1066 width=125 height=125 xoffset=13 yoffset=37 xadvance=151 page=0 chnl=15
char id=9881 x=1642 y=1066 width=0 height=0 xoffset=0 yoffset=0 xadvance=0 page=0 chnl=15
char id=10003 x=1654 y=1066 width=124 height=98 xoffset=15 yoffset=58 xadvance=145 page=0 chnl=15
char id=10004 x=1790 y=1066 width=0 height=0 xoffset=0 yoffset=0 xadvance=0 page=0 chnl=15
char id=10005 x=1802 y=1066 width=0 height=0 xoffset=0 yoffset=0 xadvance=0 page=0 chnl=15
Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

+125
View File
@@ -0,0 +1,125 @@
info face="Inter-ExtraLight" size=-200 bold=0 italic=0 charset="" unicode=1 stretchH=100 smooth=0 aa=1 padding=0,0,0,0 spacing=0,0 outline=0
common lineHeight=200 base=200 scaleW=2048 scaleH=2048 pages=1 packed=0 alphaChnl=0 redChnl=4 greenChnl=4 blueChnl=4
page id=0 file="Inter-ExtraLight.png"
chars count=121
char id=32 x=6 y=6 width=46 height=200 xoffset=0 yoffset=0 xadvance=46 page=0 chnl=15
char id=33 x=64 y=6 width=14 height=122 xoffset=15 yoffset=39 xadvance=44 page=0 chnl=15
char id=34 x=90 y=6 width=33 height=40 xoffset=13 yoffset=39 xadvance=59 page=0 chnl=15
char id=35 x=135 y=6 width=99 height=121 xoffset=3 yoffset=39 xadvance=104 page=0 chnl=15
char id=36 x=246 y=6 width=81 height=152 xoffset=11 yoffset=24 xadvance=102 page=0 chnl=15
char id=37 x=339 y=6 width=104 height=124 xoffset=13 yoffset=38 xadvance=129 page=0 chnl=15
char id=38 x=455 y=6 width=88 height=124 xoffset=9 yoffset=38 xadvance=99 page=0 chnl=15
char id=39 x=555 y=6 width=8 height=40 xoffset=13 yoffset=39 xadvance=34 page=0 chnl=15
char id=40 x=575 y=6 width=27 height=151 xoffset=18 yoffset=34 xadvance=54 page=0 chnl=15
char id=41 x=614 y=6 width=26 height=151 xoffset=10 yoffset=34 xadvance=54 page=0 chnl=15
char id=42 x=652 y=6 width=63 height=69 xoffset=10 yoffset=39 xadvance=82 page=0 chnl=15
char id=43 x=727 y=6 width=72 height=72 xoffset=17 yoffset=77 xadvance=106 page=0 chnl=15
char id=44 x=811 y=6 width=16 height=47 xoffset=13 yoffset=143 xadvance=43 page=0 chnl=15
char id=45 x=839 y=6 width=48 height=7 xoffset=13 yoffset=105 xadvance=73 page=0 chnl=15
char id=46 x=899 y=6 width=13 height=13 xoffset=15 yoffset=148 xadvance=43 page=0 chnl=15
char id=47 x=924 y=6 width=47 height=145 xoffset=4 yoffset=34 xadvance=55 page=0 chnl=15
char id=48 x=983 y=6 width=78 height=124 xoffset=11 yoffset=38 xadvance=99 page=0 chnl=15
char id=49 x=1073 y=6 width=36 height=121 xoffset=11 yoffset=39 xadvance=70 page=0 chnl=15
char id=50 x=1121 y=6 width=74 height=122 xoffset=14 yoffset=38 xadvance=101 page=0 chnl=15
char id=51 x=1207 y=6 width=76 height=124 xoffset=14 yoffset=38 xadvance=102 page=0 chnl=15
char id=52 x=1295 y=6 width=79 height=121 xoffset=11 yoffset=39 xadvance=100 page=0 chnl=15
char id=53 x=1386 y=6 width=72 height=123 xoffset=13 yoffset=39 xadvance=96 page=0 chnl=15
char id=54 x=1470 y=6 width=75 height=124 xoffset=12 yoffset=38 xadvance=98 page=0 chnl=15
char id=55 x=1557 y=6 width=73 height=121 xoffset=9 yoffset=39 xadvance=91 page=0 chnl=15
char id=56 x=1642 y=6 width=78 height=124 xoffset=11 yoffset=38 xadvance=100 page=0 chnl=15
char id=57 x=1732 y=6 width=75 height=125 xoffset=12 yoffset=37 xadvance=98 page=0 chnl=15
char id=58 x=1819 y=6 width=13 height=77 xoffset=15 yoffset=78 xadvance=43 page=0 chnl=15
char id=59 x=1844 y=6 width=17 height=112 xoffset=13 yoffset=78 xadvance=43 page=0 chnl=15
char id=60 x=1873 y=6 width=69 height=88 xoffset=19 yoffset=69 xadvance=106 page=0 chnl=15
char id=61 x=1954 y=6 width=62 height=40 xoffset=22 yoffset=93 xadvance=106 page=0 chnl=15
char id=62 x=6 y=218 width=69 height=88 xoffset=19 yoffset=69 xadvance=106 page=0 chnl=15
char id=63 x=87 y=218 width=65 height=123 xoffset=7 yoffset=38 xadvance=80 page=0 chnl=15
char id=64 x=164 y=218 width=139 height=148 xoffset=8 yoffset=44 xadvance=154 page=0 chnl=15
char id=65 x=315 y=218 width=97 height=121 xoffset=4 yoffset=39 xadvance=105 page=0 chnl=15
char id=66 x=424 y=218 width=78 height=121 xoffset=17 yoffset=39 xadvance=106 page=0 chnl=15
char id=67 x=514 y=218 width=98 height=124 xoffset=11 yoffset=38 xadvance=119 page=0 chnl=15
char id=68 x=624 y=218 width=91 height=121 xoffset=17 yoffset=39 xadvance=118 page=0 chnl=15
char id=69 x=727 y=218 width=69 height=121 xoffset=17 yoffset=39 xadvance=97 page=0 chnl=15
char id=70 x=808 y=218 width=68 height=121 xoffset=17 yoffset=39 xadvance=96 page=0 chnl=15
char id=71 x=888 y=218 width=99 height=124 xoffset=11 yoffset=38 xadvance=122 page=0 chnl=15
char id=72 x=999 y=218 width=88 height=121 xoffset=17 yoffset=39 xadvance=121 page=0 chnl=15
char id=73 x=1099 y=218 width=8 height=121 xoffset=17 yoffset=39 xadvance=42 page=0 chnl=15
char id=74 x=1119 y=218 width=62 height=123 xoffset=8 yoffset=39 xadvance=87 page=0 chnl=15
char id=75 x=1193 y=218 width=82 height=121 xoffset=17 yoffset=39 xadvance=103 page=0 chnl=15
char id=76 x=1287 y=218 width=67 height=121 xoffset=17 yoffset=39 xadvance=92 page=0 chnl=15
char id=77 x=1366 y=218 width=110 height=121 xoffset=17 yoffset=39 xadvance=144 page=0 chnl=15
char id=78 x=1488 y=218 width=93 height=121 xoffset=17 yoffset=39 xadvance=126 page=0 chnl=15
char id=79 x=1593 y=218 width=102 height=124 xoffset=11 yoffset=38 xadvance=124 page=0 chnl=15
char id=80 x=1707 y=218 width=75 height=121 xoffset=17 yoffset=39 xadvance=101 page=0 chnl=15
char id=81 x=1794 y=218 width=102 height=134 xoffset=11 yoffset=38 xadvance=124 page=0 chnl=15
char id=82 x=1908 y=218 width=80 height=121 xoffset=17 yoffset=39 xadvance=102 page=0 chnl=15
char id=83 x=6 y=430 width=81 height=125 xoffset=11 yoffset=37 xadvance=102 page=0 chnl=15
char id=84 x=99 y=430 width=85 height=121 xoffset=9 yoffset=39 xadvance=102 page=0 chnl=15
char id=85 x=196 y=430 width=90 height=123 xoffset=17 yoffset=39 xadvance=123 page=0 chnl=15
char id=86 x=298 y=430 width=97 height=121 xoffset=4 yoffset=39 xadvance=105 page=0 chnl=15
char id=87 x=407 y=430 width=143 height=121 xoffset=4 yoffset=39 xadvance=150 page=0 chnl=15
char id=88 x=562 y=430 width=90 height=121 xoffset=5 yoffset=39 xadvance=99 page=0 chnl=15
char id=89 x=664 y=430 width=95 height=121 xoffset=5 yoffset=39 xadvance=104 page=0 chnl=15
char id=90 x=771 y=430 width=79 height=121 xoffset=11 yoffset=39 xadvance=101 page=0 chnl=15
char id=91 x=862 y=430 width=26 height=151 xoffset=20 yoffset=34 xadvance=54 page=0 chnl=15
char id=92 x=900 y=430 width=47 height=145 xoffset=4 yoffset=34 xadvance=55 page=0 chnl=15
char id=93 x=959 y=430 width=26 height=151 xoffset=9 yoffset=34 xadvance=54 page=0 chnl=15
char id=94 x=997 y=430 width=56 height=52 xoffset=7 yoffset=43 xadvance=69 page=0 chnl=15
char id=95 x=1065 y=430 width=74 height=7 xoffset=-1 yoffset=160 xadvance=71 page=0 chnl=15
char id=96 x=1151 y=430 width=25 height=27 xoffset=28 yoffset=31 xadvance=82 page=0 chnl=15
char id=97 x=1188 y=430 width=66 height=94 xoffset=10 yoffset=68 xadvance=90 page=0 chnl=15
char id=98 x=1266 y=430 width=74 height=123 xoffset=17 yoffset=39 xadvance=100 page=0 chnl=15
char id=99 x=1352 y=430 width=74 height=94 xoffset=9 yoffset=68 xadvance=91 page=0 chnl=15
char id=100 x=1438 y=430 width=74 height=123 xoffset=9 yoffset=39 xadvance=100 page=0 chnl=15
char id=101 x=1524 y=430 width=77 height=94 xoffset=9 yoffset=68 xadvance=95 page=0 chnl=15
char id=102 x=1613 y=430 width=46 height=125 xoffset=8 yoffset=35 xadvance=56 page=0 chnl=15
char id=103 x=1671 y=430 width=74 height=128 xoffset=9 yoffset=68 xadvance=97 page=0 chnl=15
char id=104 x=1757 y=430 width=67 height=121 xoffset=14 yoffset=39 xadvance=95 page=0 chnl=15
char id=105 x=1836 y=430 width=13 height=118 xoffset=12 yoffset=42 xadvance=37 page=0 chnl=15
char id=106 x=1861 y=430 width=28 height=152 xoffset=-3 yoffset=42 xadvance=37 page=0 chnl=15
char id=107 x=1901 y=430 width=66 height=121 xoffset=14 yoffset=39 xadvance=83 page=0 chnl=15
char id=108 x=1979 y=430 width=9 height=121 xoffset=14 yoffset=39 xadvance=36 page=0 chnl=15
char id=109 x=6 y=642 width=112 height=92 xoffset=14 yoffset=68 xadvance=139 page=0 chnl=15
char id=110 x=130 y=642 width=66 height=92 xoffset=14 yoffset=68 xadvance=94 page=0 chnl=15
char id=111 x=208 y=642 width=78 height=94 xoffset=9 yoffset=68 xadvance=96 page=0 chnl=15
char id=112 x=298 y=642 width=74 height=126 xoffset=14 yoffset=68 xadvance=97 page=0 chnl=15
char id=113 x=384 y=642 width=74 height=126 xoffset=9 yoffset=68 xadvance=97 page=0 chnl=15
char id=114 x=470 y=642 width=38 height=92 xoffset=14 yoffset=68 xadvance=56 page=0 chnl=15
char id=115 x=520 y=642 width=64 height=94 xoffset=10 yoffset=68 xadvance=83 page=0 chnl=15
char id=116 x=596 y=642 width=44 height=114 xoffset=7 yoffset=48 xadvance=58 page=0 chnl=15
char id=117 x=652 y=642 width=66 height=93 xoffset=14 yoffset=69 xadvance=94 page=0 chnl=15
char id=118 x=730 y=642 width=74 height=91 xoffset=7 yoffset=69 xadvance=88 page=0 chnl=15
char id=119 x=816 y=642 width=118 height=91 xoffset=7 yoffset=69 xadvance=131 page=0 chnl=15
char id=120 x=946 y=642 width=67 height=91 xoffset=9 yoffset=69 xadvance=84 page=0 chnl=15
char id=121 x=1025 y=642 width=74 height=125 xoffset=7 yoffset=69 xadvance=88 page=0 chnl=15
char id=122 x=1111 y=642 width=66 height=91 xoffset=11 yoffset=69 xadvance=85 page=0 chnl=15
char id=123 x=1189 y=642 width=43 height=151 xoffset=9 yoffset=34 xadvance=54 page=0 chnl=15
char id=124 x=1244 y=642 width=8 height=198 xoffset=20 yoffset=1 xadvance=48 page=0 chnl=15
char id=125 x=1264 y=642 width=43 height=151 xoffset=3 yoffset=34 xadvance=54 page=0 chnl=15
char id=126 x=1319 y=642 width=78 height=23 xoffset=14 yoffset=101 xadvance=106 page=0 chnl=15
char id=163 x=1409 y=642 width=79 height=122 xoffset=12 yoffset=38 xadvance=104 page=0 chnl=15
char id=165 x=1500 y=642 width=82 height=121 xoffset=3 yoffset=39 xadvance=87 page=0 chnl=15
char id=167 x=1594 y=642 width=69 height=147 xoffset=10 yoffset=38 xadvance=88 page=0 chnl=15
char id=176 x=1675 y=642 width=51 height=51 xoffset=12 yoffset=38 xadvance=75 page=0 chnl=15
char id=178 x=1738 y=642 width=51 height=74 xoffset=9 yoffset=26 xadvance=68 page=0 chnl=15
char id=215 x=1801 y=642 width=72 height=72 xoffset=17 yoffset=77 xadvance=106 page=0 chnl=15
char id=231 x=1885 y=642 width=74 height=124 xoffset=9 yoffset=68 xadvance=91 page=0 chnl=15
char id=234 x=6 y=854 width=77 height=126 xoffset=9 yoffset=36 xadvance=95 page=0 chnl=15
char id=241 x=95 y=854 width=66 height=120 xoffset=14 yoffset=40 xadvance=94 page=0 chnl=15
char id=252 x=173 y=854 width=66 height=120 xoffset=14 yoffset=42 xadvance=94 page=0 chnl=15
char id=8209 x=251 y=854 width=48 height=7 xoffset=-2 yoffset=105 xadvance=47 page=0 chnl=15
char id=8211 x=311 y=854 width=83 height=7 xoffset=0 yoffset=105 xadvance=82 page=0 chnl=15
char id=8226 x=406 y=854 width=39 height=38 xoffset=27 yoffset=93 xadvance=92 page=0 chnl=15
char id=8364 x=457 y=854 width=95 height=124 xoffset=5 yoffset=38 xadvance=111 page=0 chnl=15
char id=8627 x=564 y=854 width=125 height=100 xoffset=16 yoffset=39 xadvance=156 page=0 chnl=15
char id=8679 x=701 y=854 width=122 height=130 xoffset=15 yoffset=30 xadvance=151 page=0 chnl=15
char id=9003 x=835 y=854 width=173 height=121 xoffset=13 yoffset=39 xadvance=200 page=0 chnl=15
char id=9251 x=1020 y=854 width=36 height=34 xoffset=39 yoffset=148 xadvance=114 page=0 chnl=15
char id=9654 x=1068 y=854 width=126 height=152 xoffset=14 yoffset=24 xadvance=146 page=0 chnl=15
char id=9664 x=1206 y=854 width=126 height=152 xoffset=7 yoffset=24 xadvance=146 page=0 chnl=15
char id=9675 x=1344 y=854 width=125 height=125 xoffset=13 yoffset=37 xadvance=151 page=0 chnl=15
char id=9679 x=1481 y=854 width=125 height=125 xoffset=13 yoffset=37 xadvance=151 page=0 chnl=15
char id=9881 x=1618 y=854 width=0 height=0 xoffset=0 yoffset=0 xadvance=0 page=0 chnl=15
char id=10003 x=1630 y=854 width=110 height=77 xoffset=18 yoffset=65 xadvance=145 page=0 chnl=15
char id=10004 x=1752 y=854 width=0 height=0 xoffset=0 yoffset=0 xadvance=0 page=0 chnl=15
char id=10005 x=1764 y=854 width=0 height=0 xoffset=0 yoffset=0 xadvance=0 page=0 chnl=15
Binary file not shown.

After

Width:  |  Height:  |  Size: 219 KiB

+125
View File
@@ -0,0 +1,125 @@
info face="Inter-Light" size=-200 bold=0 italic=0 charset="" unicode=1 stretchH=100 smooth=0 aa=1 padding=0,0,0,0 spacing=0,0 outline=0
common lineHeight=200 base=200 scaleW=2048 scaleH=2048 pages=1 packed=0 alphaChnl=0 redChnl=4 greenChnl=4 blueChnl=4
page id=0 file="Inter-Light.png"
chars count=121
char id=32 x=6 y=6 width=46 height=200 xoffset=0 yoffset=0 xadvance=46 page=0 chnl=15
char id=33 x=64 y=6 width=17 height=122 xoffset=14 yoffset=39 xadvance=45 page=0 chnl=15
char id=34 x=93 y=6 width=39 height=41 xoffset=12 yoffset=39 xadvance=63 page=0 chnl=15
char id=35 x=144 y=6 width=99 height=121 xoffset=3 yoffset=39 xadvance=104 page=0 chnl=15
char id=36 x=255 y=6 width=84 height=152 xoffset=10 yoffset=24 xadvance=103 page=0 chnl=15
char id=37 x=351 y=6 width=106 height=124 xoffset=13 yoffset=38 xadvance=132 page=0 chnl=15
char id=38 x=469 y=6 width=91 height=124 xoffset=9 yoffset=38 xadvance=102 page=0 chnl=15
char id=39 x=572 y=6 width=11 height=41 xoffset=12 yoffset=39 xadvance=35 page=0 chnl=15
char id=40 x=595 y=6 width=31 height=151 xoffset=18 yoffset=34 xadvance=57 page=0 chnl=15
char id=41 x=638 y=6 width=32 height=151 xoffset=8 yoffset=34 xadvance=57 page=0 chnl=15
char id=42 x=682 y=6 width=64 height=69 xoffset=9 yoffset=39 xadvance=82 page=0 chnl=15
char id=43 x=758 y=6 width=76 height=76 xoffset=16 yoffset=75 xadvance=107 page=0 chnl=15
char id=44 x=846 y=6 width=20 height=47 xoffset=12 yoffset=143 xadvance=44 page=0 chnl=15
char id=45 x=878 y=6 width=51 height=11 xoffset=12 yoffset=103 xadvance=74 page=0 chnl=15
char id=46 x=941 y=6 width=18 height=18 xoffset=13 yoffset=143 xadvance=44 page=0 chnl=15
char id=47 x=971 y=6 width=49 height=145 xoffset=4 yoffset=34 xadvance=57 page=0 chnl=15
char id=48 x=1032 y=6 width=81 height=124 xoffset=10 yoffset=38 xadvance=101 page=0 chnl=15
char id=49 x=1125 y=6 width=41 height=121 xoffset=10 yoffset=39 xadvance=73 page=0 chnl=15
char id=50 x=1178 y=6 width=76 height=122 xoffset=13 yoffset=38 xadvance=100 page=0 chnl=15
char id=51 x=1266 y=6 width=79 height=124 xoffset=13 yoffset=38 xadvance=103 page=0 chnl=15
char id=52 x=1357 y=6 width=84 height=121 xoffset=10 yoffset=39 xadvance=103 page=0 chnl=15
char id=53 x=1453 y=6 width=74 height=123 xoffset=13 yoffset=39 xadvance=98 page=0 chnl=15
char id=54 x=1539 y=6 width=79 height=125 xoffset=11 yoffset=37 xadvance=100 page=0 chnl=15
char id=55 x=1630 y=6 width=76 height=121 xoffset=8 yoffset=39 xadvance=93 page=0 chnl=15
char id=56 x=1718 y=6 width=81 height=124 xoffset=10 yoffset=38 xadvance=101 page=0 chnl=15
char id=57 x=1811 y=6 width=79 height=125 xoffset=11 yoffset=37 xadvance=100 page=0 chnl=15
char id=58 x=1902 y=6 width=18 height=83 xoffset=13 yoffset=75 xadvance=44 page=0 chnl=15
char id=59 x=1932 y=6 width=21 height=115 xoffset=12 yoffset=75 xadvance=45 page=0 chnl=15
char id=60 x=6 y=218 width=73 height=88 xoffset=17 yoffset=69 xadvance=107 page=0 chnl=15
char id=61 x=91 y=218 width=68 height=44 xoffset=20 yoffset=91 xadvance=107 page=0 chnl=15
char id=62 x=171 y=218 width=73 height=88 xoffset=17 yoffset=69 xadvance=107 page=0 chnl=15
char id=63 x=256 y=218 width=68 height=123 xoffset=6 yoffset=38 xadvance=82 page=0 chnl=15
char id=64 x=336 y=218 width=139 height=148 xoffset=8 yoffset=44 xadvance=154 page=0 chnl=15
char id=65 x=487 y=218 width=100 height=121 xoffset=4 yoffset=39 xadvance=108 page=0 chnl=15
char id=66 x=599 y=218 width=82 height=121 xoffset=15 yoffset=39 xadvance=106 page=0 chnl=15
char id=67 x=693 y=218 width=101 height=124 xoffset=10 yoffset=38 xadvance=119 page=0 chnl=15
char id=68 x=806 y=218 width=93 height=121 xoffset=15 yoffset=39 xadvance=118 page=0 chnl=15
char id=69 x=911 y=218 width=72 height=121 xoffset=15 yoffset=39 xadvance=98 page=0 chnl=15
char id=70 x=995 y=218 width=71 height=121 xoffset=15 yoffset=39 xadvance=96 page=0 chnl=15
char id=71 x=1078 y=218 width=102 height=124 xoffset=10 yoffset=38 xadvance=122 page=0 chnl=15
char id=72 x=1192 y=218 width=92 height=121 xoffset=15 yoffset=39 xadvance=122 page=0 chnl=15
char id=73 x=1296 y=218 width=12 height=121 xoffset=15 yoffset=39 xadvance=42 page=0 chnl=15
char id=74 x=1320 y=218 width=66 height=123 xoffset=7 yoffset=39 xadvance=88 page=0 chnl=15
char id=75 x=1398 y=218 width=87 height=121 xoffset=15 yoffset=39 xadvance=105 page=0 chnl=15
char id=76 x=1497 y=218 width=70 height=121 xoffset=15 yoffset=39 xadvance=92 page=0 chnl=15
char id=77 x=1579 y=218 width=115 height=121 xoffset=15 yoffset=39 xadvance=145 page=0 chnl=15
char id=78 x=1706 y=218 width=95 height=121 xoffset=15 yoffset=39 xadvance=125 page=0 chnl=15
char id=79 x=1813 y=218 width=105 height=124 xoffset=10 yoffset=38 xadvance=125 page=0 chnl=15
char id=80 x=1930 y=218 width=79 height=121 xoffset=15 yoffset=39 xadvance=103 page=0 chnl=15
char id=81 x=6 y=430 width=105 height=134 xoffset=10 yoffset=38 xadvance=125 page=0 chnl=15
char id=82 x=123 y=430 width=84 height=121 xoffset=15 yoffset=39 xadvance=104 page=0 chnl=15
char id=83 x=219 y=430 width=84 height=125 xoffset=10 yoffset=38 xadvance=103 page=0 chnl=15
char id=84 x=315 y=430 width=88 height=121 xoffset=8 yoffset=39 xadvance=104 page=0 chnl=15
char id=85 x=415 y=430 width=93 height=124 xoffset=15 yoffset=39 xadvance=123 page=0 chnl=15
char id=86 x=520 y=430 width=100 height=121 xoffset=4 yoffset=39 xadvance=108 page=0 chnl=15
char id=87 x=632 y=430 width=147 height=121 xoffset=3 yoffset=39 xadvance=153 page=0 chnl=15
char id=88 x=791 y=430 width=95 height=121 xoffset=4 yoffset=39 xadvance=102 page=0 chnl=15
char id=89 x=898 y=430 width=99 height=121 xoffset=4 yoffset=39 xadvance=107 page=0 chnl=15
char id=90 x=1009 y=430 width=82 height=121 xoffset=10 yoffset=39 xadvance=102 page=0 chnl=15
char id=91 x=1103 y=430 width=31 height=151 xoffset=19 yoffset=34 xadvance=57 page=0 chnl=15
char id=92 x=1146 y=430 width=49 height=145 xoffset=4 yoffset=34 xadvance=57 page=0 chnl=15
char id=93 x=1207 y=430 width=31 height=151 xoffset=7 yoffset=34 xadvance=57 page=0 chnl=15
char id=94 x=1250 y=430 width=61 height=52 xoffset=6 yoffset=43 xadvance=73 page=0 chnl=15
char id=95 x=1323 y=430 width=75 height=10 xoffset=-1 yoffset=160 xadvance=73 page=0 chnl=15
char id=96 x=1410 y=430 width=28 height=28 xoffset=27 yoffset=30 xadvance=82 page=0 chnl=15
char id=97 x=1450 y=430 width=69 height=95 xoffset=9 yoffset=68 xadvance=91 page=0 chnl=15
char id=98 x=1531 y=430 width=78 height=123 xoffset=15 yoffset=39 xadvance=101 page=0 chnl=15
char id=99 x=1621 y=430 width=75 height=94 xoffset=9 yoffset=68 xadvance=91 page=0 chnl=15
char id=100 x=1708 y=430 width=77 height=123 xoffset=9 yoffset=39 xadvance=101 page=0 chnl=15
char id=101 x=1797 y=430 width=78 height=94 xoffset=9 yoffset=68 xadvance=95 page=0 chnl=15
char id=102 x=1887 y=430 width=50 height=126 xoffset=6 yoffset=34 xadvance=57 page=0 chnl=15
char id=103 x=1949 y=430 width=77 height=128 xoffset=9 yoffset=68 xadvance=99 page=0 chnl=15
char id=104 x=6 y=642 width=70 height=121 xoffset=13 yoffset=39 xadvance=96 page=0 chnl=15
char id=105 x=88 y=642 width=17 height=121 xoffset=11 yoffset=39 xadvance=38 page=0 chnl=15
char id=106 x=117 y=642 width=30 height=155 xoffset=-3 yoffset=39 xadvance=38 page=0 chnl=15
char id=107 x=159 y=642 width=71 height=121 xoffset=13 yoffset=39 xadvance=86 page=0 chnl=15
char id=108 x=242 y=642 width=12 height=121 xoffset=13 yoffset=39 xadvance=38 page=0 chnl=15
char id=109 x=266 y=642 width=115 height=92 xoffset=13 yoffset=68 xadvance=141 page=0 chnl=15
char id=110 x=393 y=642 width=69 height=92 xoffset=13 yoffset=68 xadvance=95 page=0 chnl=15
char id=111 x=474 y=642 width=80 height=94 xoffset=9 yoffset=68 xadvance=97 page=0 chnl=15
char id=112 x=566 y=642 width=77 height=126 xoffset=13 yoffset=68 xadvance=99 page=0 chnl=15
char id=113 x=655 y=642 width=77 height=126 xoffset=9 yoffset=68 xadvance=99 page=0 chnl=15
char id=114 x=744 y=642 width=42 height=92 xoffset=13 yoffset=68 xadvance=58 page=0 chnl=15
char id=115 x=798 y=642 width=67 height=94 xoffset=9 yoffset=68 xadvance=84 page=0 chnl=15
char id=116 x=877 y=642 width=47 height=114 xoffset=6 yoffset=48 xadvance=59 page=0 chnl=15
char id=117 x=936 y=642 width=69 height=93 xoffset=13 yoffset=69 xadvance=95 page=0 chnl=15
char id=118 x=1017 y=642 width=78 height=91 xoffset=6 yoffset=69 xadvance=90 page=0 chnl=15
char id=119 x=1107 y=642 width=121 height=91 xoffset=6 yoffset=69 xadvance=133 page=0 chnl=15
char id=120 x=1240 y=642 width=71 height=91 xoffset=8 yoffset=69 xadvance=87 page=0 chnl=15
char id=121 x=1323 y=642 width=78 height=125 xoffset=6 yoffset=69 xadvance=90 page=0 chnl=15
char id=122 x=1413 y=642 width=69 height=91 xoffset=10 yoffset=69 xadvance=87 page=0 chnl=15
char id=123 x=1494 y=642 width=46 height=151 xoffset=7 yoffset=34 xadvance=57 page=0 chnl=15
char id=124 x=1552 y=642 width=11 height=198 xoffset=20 yoffset=1 xadvance=50 page=0 chnl=15
char id=125 x=1575 y=642 width=46 height=151 xoffset=4 yoffset=34 xadvance=57 page=0 chnl=15
char id=126 x=1633 y=642 width=80 height=27 xoffset=14 yoffset=99 xadvance=107 page=0 chnl=15
char id=163 x=1725 y=642 width=82 height=122 xoffset=11 yoffset=38 xadvance=104 page=0 chnl=15
char id=165 x=1819 y=642 width=85 height=121 xoffset=2 yoffset=39 xadvance=89 page=0 chnl=15
char id=167 x=1916 y=642 width=72 height=147 xoffset=9 yoffset=38 xadvance=89 page=0 chnl=15
char id=176 x=6 y=854 width=53 height=53 xoffset=11 yoffset=38 xadvance=75 page=0 chnl=15
char id=178 x=71 y=854 width=54 height=74 xoffset=8 yoffset=26 xadvance=69 page=0 chnl=15
char id=215 x=137 y=854 width=74 height=74 xoffset=17 yoffset=76 xadvance=107 page=0 chnl=15
char id=231 x=223 y=854 width=75 height=126 xoffset=9 yoffset=68 xadvance=91 page=0 chnl=15
char id=234 x=310 y=854 width=78 height=127 xoffset=9 yoffset=35 xadvance=95 page=0 chnl=15
char id=241 x=400 y=854 width=69 height=122 xoffset=13 yoffset=38 xadvance=95 page=0 chnl=15
char id=252 x=481 y=854 width=69 height=123 xoffset=13 yoffset=39 xadvance=95 page=0 chnl=15
char id=8209 x=562 y=854 width=51 height=11 xoffset=0 yoffset=103 xadvance=53 page=0 chnl=15
char id=8211 x=625 y=854 width=83 height=11 xoffset=0 yoffset=103 xadvance=82 page=0 chnl=15
char id=8226 x=720 y=854 width=45 height=46 xoffset=24 yoffset=89 xadvance=92 page=0 chnl=15
char id=8364 x=777 y=854 width=96 height=124 xoffset=4 yoffset=38 xadvance=110 page=0 chnl=15
char id=8627 x=885 y=854 width=128 height=102 xoffset=16 yoffset=39 xadvance=159 page=0 chnl=15
char id=8679 x=1025 y=854 width=131 height=130 xoffset=15 yoffset=30 xadvance=160 page=0 chnl=15
char id=9003 x=1168 y=854 width=173 height=121 xoffset=13 yoffset=39 xadvance=200 page=0 chnl=15
char id=9251 x=1353 y=854 width=38 height=36 xoffset=39 yoffset=148 xadvance=116 page=0 chnl=15
char id=9654 x=1403 y=854 width=128 height=152 xoffset=14 yoffset=24 xadvance=148 page=0 chnl=15
char id=9664 x=1543 y=854 width=128 height=152 xoffset=7 yoffset=24 xadvance=148 page=0 chnl=15
char id=9675 x=1683 y=854 width=125 height=125 xoffset=13 yoffset=37 xadvance=151 page=0 chnl=15
char id=9679 x=1820 y=854 width=125 height=125 xoffset=13 yoffset=37 xadvance=151 page=0 chnl=15
char id=9881 x=1957 y=854 width=0 height=0 xoffset=0 yoffset=0 xadvance=0 page=0 chnl=15
char id=10003 x=6 y=1066 width=112 height=80 xoffset=18 yoffset=64 xadvance=145 page=0 chnl=15
char id=10004 x=130 y=1066 width=0 height=0 xoffset=0 yoffset=0 xadvance=0 page=0 chnl=15
char id=10005 x=142 y=1066 width=0 height=0 xoffset=0 yoffset=0 xadvance=0 page=0 chnl=15
Binary file not shown.

After

Width:  |  Height:  |  Size: 222 KiB

+125
View File
@@ -0,0 +1,125 @@
info face="Inter-Medium" size=-200 bold=0 italic=0 charset="" unicode=1 stretchH=100 smooth=0 aa=1 padding=0,0,0,0 spacing=0,0 outline=0
common lineHeight=200 base=200 scaleW=2048 scaleH=2048 pages=1 packed=0 alphaChnl=0 redChnl=4 greenChnl=4 blueChnl=4
page id=0 file="Inter-Medium.png"
chars count=121
char id=32 x=6 y=6 width=43 height=200 xoffset=0 yoffset=0 xadvance=43 page=0 chnl=15
char id=33 x=61 y=6 width=25 height=123 xoffset=12 yoffset=39 xadvance=48 page=0 chnl=15
char id=34 x=98 y=6 width=46 height=44 xoffset=10 yoffset=39 xadvance=66 page=0 chnl=15
char id=35 x=156 y=6 width=101 height=121 xoffset=2 yoffset=39 xadvance=105 page=0 chnl=15
char id=36 x=269 y=6 width=90 height=152 xoffset=8 yoffset=24 xadvance=106 page=0 chnl=15
char id=37 x=371 y=6 width=111 height=126 xoffset=13 yoffset=37 xadvance=136 page=0 chnl=15
char id=38 x=494 y=6 width=99 height=124 xoffset=7 yoffset=38 xadvance=107 page=0 chnl=15
char id=39 x=605 y=6 width=16 height=44 xoffset=10 yoffset=39 xadvance=35 page=0 chnl=15
char id=40 x=633 y=6 width=40 height=150 xoffset=16 yoffset=35 xadvance=62 page=0 chnl=15
char id=41 x=685 y=6 width=40 height=150 xoffset=6 yoffset=35 xadvance=62 page=0 chnl=15
char id=42 x=737 y=6 width=66 height=69 xoffset=10 yoffset=39 xadvance=86 page=0 chnl=15
char id=43 x=815 y=6 width=80 height=80 xoffset=15 yoffset=73 xadvance=110 page=0 chnl=15
char id=44 x=907 y=6 width=25 height=47 xoffset=10 yoffset=143 xadvance=47 page=0 chnl=15
char id=45 x=944 y=6 width=54 height=16 xoffset=11 yoffset=101 xadvance=76 page=0 chnl=15
char id=46 x=1010 y=6 width=25 height=25 xoffset=11 yoffset=137 xadvance=46 page=0 chnl=15
char id=47 x=1047 y=6 width=55 height=145 xoffset=3 yoffset=34 xadvance=60 page=0 chnl=15
char id=48 x=1114 y=6 width=89 height=124 xoffset=9 yoffset=38 xadvance=106 page=0 chnl=15
char id=49 x=1215 y=6 width=49 height=121 xoffset=9 yoffset=39 xadvance=78 page=0 chnl=15
char id=50 x=1276 y=6 width=80 height=122 xoffset=11 yoffset=38 xadvance=101 page=0 chnl=15
char id=51 x=1368 y=6 width=86 height=124 xoffset=10 yoffset=38 xadvance=106 page=0 chnl=15
char id=52 x=1466 y=6 width=91 height=121 xoffset=9 yoffset=39 xadvance=108 page=0 chnl=15
char id=53 x=1569 y=6 width=82 height=123 xoffset=11 yoffset=39 xadvance=102 page=0 chnl=15
char id=54 x=1663 y=6 width=87 height=125 xoffset=9 yoffset=37 xadvance=105 page=0 chnl=15
char id=55 x=1762 y=6 width=80 height=121 xoffset=7 yoffset=39 xadvance=95 page=0 chnl=15
char id=56 x=1854 y=6 width=86 height=124 xoffset=9 yoffset=38 xadvance=104 page=0 chnl=15
char id=57 x=6 y=218 width=87 height=125 xoffset=9 yoffset=37 xadvance=105 page=0 chnl=15
char id=58 x=105 y=218 width=25 height=90 xoffset=11 yoffset=72 xadvance=46 page=0 chnl=15
char id=59 x=142 y=218 width=28 height=118 xoffset=10 yoffset=72 xadvance=47 page=0 chnl=15
char id=60 x=182 y=218 width=79 height=90 xoffset=16 yoffset=68 xadvance=110 page=0 chnl=15
char id=61 x=273 y=218 width=76 height=53 xoffset=17 yoffset=86 xadvance=110 page=0 chnl=15
char id=62 x=361 y=218 width=79 height=90 xoffset=16 yoffset=68 xadvance=110 page=0 chnl=15
char id=63 x=452 y=218 width=73 height=124 xoffset=6 yoffset=38 xadvance=86 page=0 chnl=15
char id=64 x=537 y=218 width=145 height=151 xoffset=8 yoffset=42 xadvance=159 page=0 chnl=15
char id=65 x=694 y=218 width=108 height=121 xoffset=4 yoffset=39 xadvance=115 page=0 chnl=15
char id=66 x=814 y=218 width=87 height=121 xoffset=13 yoffset=39 xadvance=108 page=0 chnl=15
char id=67 x=913 y=218 width=105 height=124 xoffset=9 yoffset=38 xadvance=121 page=0 chnl=15
char id=68 x=1030 y=218 width=97 height=121 xoffset=13 yoffset=39 xadvance=119 page=0 chnl=15
char id=69 x=1139 y=218 width=77 height=121 xoffset=13 yoffset=39 xadvance=99 page=0 chnl=15
char id=70 x=1228 y=218 width=75 height=121 xoffset=13 yoffset=39 xadvance=96 page=0 chnl=15
char id=71 x=1315 y=218 width=105 height=124 xoffset=9 yoffset=38 xadvance=123 page=0 chnl=15
char id=72 x=1432 y=218 width=97 height=121 xoffset=13 yoffset=39 xadvance=122 page=0 chnl=15
char id=73 x=1541 y=218 width=19 height=121 xoffset=13 yoffset=39 xadvance=44 page=0 chnl=15
char id=74 x=1572 y=218 width=73 height=123 xoffset=5 yoffset=39 xadvance=91 page=0 chnl=15
char id=75 x=1657 y=218 width=94 height=121 xoffset=13 yoffset=39 xadvance=109 page=0 chnl=15
char id=76 x=1763 y=218 width=73 height=121 xoffset=13 yoffset=39 xadvance=93 page=0 chnl=15
char id=77 x=1848 y=218 width=123 height=121 xoffset=13 yoffset=39 xadvance=148 page=0 chnl=15
char id=78 x=6 y=430 width=98 height=121 xoffset=13 yoffset=39 xadvance=123 page=0 chnl=15
char id=79 x=116 y=430 width=109 height=124 xoffset=9 yoffset=38 xadvance=126 page=0 chnl=15
char id=80 x=237 y=430 width=85 height=121 xoffset=13 yoffset=39 xadvance=105 page=0 chnl=15
char id=81 x=334 y=430 width=109 height=133 xoffset=9 yoffset=38 xadvance=126 page=0 chnl=15
char id=82 x=455 y=430 width=90 height=121 xoffset=13 yoffset=39 xadvance=106 page=0 chnl=15
char id=83 x=557 y=430 width=90 height=124 xoffset=8 yoffset=38 xadvance=106 page=0 chnl=15
char id=84 x=659 y=430 width=94 height=121 xoffset=7 yoffset=39 xadvance=107 page=0 chnl=15
char id=85 x=765 y=430 width=96 height=123 xoffset=13 yoffset=39 xadvance=121 page=0 chnl=15
char id=86 x=873 y=430 width=108 height=121 xoffset=4 yoffset=39 xadvance=115 page=0 chnl=15
char id=87 x=993 y=430 width=156 height=121 xoffset=3 yoffset=39 xadvance=161 page=0 chnl=15
char id=88 x=1161 y=430 width=102 height=121 xoffset=4 yoffset=39 xadvance=110 page=0 chnl=15
char id=89 x=1275 y=430 width=107 height=121 xoffset=3 yoffset=39 xadvance=113 page=0 chnl=15
char id=90 x=1394 y=430 width=88 height=121 xoffset=9 yoffset=39 xadvance=105 page=0 chnl=15
char id=91 x=1494 y=430 width=40 height=150 xoffset=18 yoffset=35 xadvance=62 page=0 chnl=15
char id=92 x=1546 y=430 width=55 height=145 xoffset=3 yoffset=34 xadvance=60 page=0 chnl=15
char id=93 x=1613 y=430 width=39 height=150 xoffset=5 yoffset=35 xadvance=62 page=0 chnl=15
char id=94 x=1664 y=430 width=67 height=52 xoffset=6 yoffset=43 xadvance=78 page=0 chnl=15
char id=95 x=1743 y=430 width=78 height=15 xoffset=-1 yoffset=160 xadvance=75 page=0 chnl=15
char id=96 x=1833 y=430 width=33 height=28 xoffset=25 yoffset=30 xadvance=82 page=0 chnl=15
char id=97 x=1878 y=430 width=76 height=94 xoffset=7 yoffset=68 xadvance=94 page=0 chnl=15
char id=98 x=6 y=642 width=83 height=123 xoffset=13 yoffset=39 xadvance=103 page=0 chnl=15
char id=99 x=101 y=642 width=80 height=94 xoffset=7 yoffset=68 xadvance=93 page=0 chnl=15
char id=100 x=193 y=642 width=84 height=123 xoffset=7 yoffset=39 xadvance=103 page=0 chnl=15
char id=101 x=289 y=642 width=83 height=94 xoffset=7 yoffset=68 xadvance=97 page=0 chnl=15
char id=102 x=384 y=642 width=57 height=126 xoffset=3 yoffset=34 xadvance=60 page=0 chnl=15
char id=103 x=453 y=642 width=84 height=128 xoffset=7 yoffset=68 xadvance=101 page=0 chnl=15
char id=104 x=549 y=642 width=77 height=121 xoffset=11 yoffset=39 xadvance=99 page=0 chnl=15
char id=105 x=638 y=642 width=23 height=125 xoffset=9 yoffset=35 xadvance=41 page=0 chnl=15
char id=106 x=673 y=642 width=35 height=159 xoffset=-3 yoffset=35 xadvance=41 page=0 chnl=15
char id=107 x=720 y=642 width=80 height=121 xoffset=11 yoffset=39 xadvance=91 page=0 chnl=15
char id=108 x=812 y=642 width=19 height=121 xoffset=11 yoffset=39 xadvance=41 page=0 chnl=15
char id=109 x=843 y=642 width=124 height=92 xoffset=11 yoffset=68 xadvance=146 page=0 chnl=15
char id=110 x=979 y=642 width=77 height=92 xoffset=11 yoffset=68 xadvance=98 page=0 chnl=15
char id=111 x=1068 y=642 width=85 height=94 xoffset=7 yoffset=68 xadvance=99 page=0 chnl=15
char id=112 x=1165 y=642 width=84 height=126 xoffset=11 yoffset=68 xadvance=101 page=0 chnl=15
char id=113 x=1261 y=642 width=84 height=126 xoffset=7 yoffset=68 xadvance=101 page=0 chnl=15
char id=114 x=1357 y=642 width=50 height=92 xoffset=11 yoffset=68 xadvance=63 page=0 chnl=15
char id=115 x=1419 y=642 width=75 height=94 xoffset=7 yoffset=68 xadvance=88 page=0 chnl=15
char id=116 x=1506 y=642 width=52 height=114 xoffset=4 yoffset=48 xadvance=61 page=0 chnl=15
char id=117 x=1570 y=642 width=76 height=93 xoffset=11 yoffset=69 xadvance=98 page=0 chnl=15
char id=118 x=1658 y=642 width=85 height=91 xoffset=4 yoffset=69 xadvance=93 page=0 chnl=15
char id=119 x=1755 y=642 width=128 height=91 xoffset=4 yoffset=69 xadvance=136 page=0 chnl=15
char id=120 x=1895 y=642 width=80 height=91 xoffset=6 yoffset=69 xadvance=91 page=0 chnl=15
char id=121 x=6 y=854 width=86 height=125 xoffset=4 yoffset=69 xadvance=93 page=0 chnl=15
char id=122 x=104 y=854 width=74 height=91 xoffset=9 yoffset=69 xadvance=91 page=0 chnl=15
char id=123 x=190 y=854 width=53 height=150 xoffset=5 yoffset=35 xadvance=62 page=0 chnl=15
char id=124 x=255 y=854 width=17 height=198 xoffset=20 yoffset=1 xadvance=56 page=0 chnl=15
char id=125 x=284 y=854 width=52 height=150 xoffset=5 yoffset=35 xadvance=62 page=0 chnl=15
char id=126 x=348 y=854 width=85 height=34 xoffset=13 yoffset=95 xadvance=110 page=0 chnl=15
char id=163 x=445 y=854 width=86 height=122 xoffset=10 yoffset=38 xadvance=106 page=0 chnl=15
char id=165 x=543 y=854 width=88 height=121 xoffset=2 yoffset=39 xadvance=91 page=0 chnl=15
char id=167 x=643 y=854 width=77 height=147 xoffset=8 yoffset=38 xadvance=93 page=0 chnl=15
char id=176 x=732 y=854 width=56 height=57 xoffset=10 yoffset=38 xadvance=75 page=0 chnl=15
char id=178 x=800 y=854 width=57 height=74 xoffset=7 yoffset=26 xadvance=69 page=0 chnl=15
char id=215 x=869 y=854 width=80 height=79 xoffset=15 yoffset=73 xadvance=110 page=0 chnl=15
char id=231 x=961 y=854 width=80 height=129 xoffset=7 yoffset=68 xadvance=93 page=0 chnl=15
char id=234 x=1053 y=854 width=83 height=129 xoffset=7 yoffset=33 xadvance=97 page=0 chnl=15
char id=241 x=1148 y=854 width=77 height=124 xoffset=11 yoffset=36 xadvance=98 page=0 chnl=15
char id=252 x=1237 y=854 width=76 height=127 xoffset=11 yoffset=35 xadvance=98 page=0 chnl=15
char id=8209 x=1325 y=854 width=54 height=16 xoffset=6 yoffset=101 xadvance=65 page=0 chnl=15
char id=8211 x=1391 y=854 width=83 height=16 xoffset=0 yoffset=101 xadvance=82 page=0 chnl=15
char id=8226 x=1486 y=854 width=53 height=52 xoffset=17 yoffset=86 xadvance=87 page=0 chnl=15
char id=8364 x=1551 y=854 width=98 height=124 xoffset=4 yoffset=38 xadvance=111 page=0 chnl=15
char id=8627 x=1661 y=854 width=133 height=105 xoffset=16 yoffset=39 xadvance=164 page=0 chnl=15
char id=8679 x=1806 y=854 width=146 height=130 xoffset=14 yoffset=30 xadvance=173 page=0 chnl=15
char id=9003 x=6 y=1066 width=173 height=121 xoffset=13 yoffset=39 xadvance=200 page=0 chnl=15
char id=9251 x=191 y=1066 width=99 height=39 xoffset=11 yoffset=148 xadvance=121 page=0 chnl=15
char id=9654 x=302 y=1066 width=130 height=152 xoffset=16 yoffset=24 xadvance=153 page=0 chnl=15
char id=9664 x=444 y=1066 width=130 height=152 xoffset=8 yoffset=24 xadvance=153 page=0 chnl=15
char id=9675 x=586 y=1066 width=125 height=125 xoffset=13 yoffset=37 xadvance=151 page=0 chnl=15
char id=9679 x=723 y=1066 width=125 height=125 xoffset=13 yoffset=37 xadvance=151 page=0 chnl=15
char id=9881 x=860 y=1066 width=0 height=0 xoffset=0 yoffset=0 xadvance=0 page=0 chnl=15
char id=10003 x=872 y=1066 width=116 height=86 xoffset=17 yoffset=62 xadvance=145 page=0 chnl=15
char id=10004 x=1000 y=1066 width=0 height=0 xoffset=0 yoffset=0 xadvance=0 page=0 chnl=15
char id=10005 x=1012 y=1066 width=0 height=0 xoffset=0 yoffset=0 xadvance=0 page=0 chnl=15
Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

+125
View File
@@ -0,0 +1,125 @@
info face="Inter-Regular" size=-200 bold=0 italic=0 charset="" unicode=1 stretchH=100 smooth=0 aa=1 padding=0,0,0,0 spacing=0,0 outline=0
common lineHeight=200 base=200 scaleW=2048 scaleH=2048 pages=1 packed=0 alphaChnl=0 redChnl=4 greenChnl=4 blueChnl=4
page id=0 file="Inter-Regular.png"
chars count=121
char id=32 x=6 y=6 width=46 height=200 xoffset=0 yoffset=0 xadvance=46 page=0 chnl=15
char id=33 x=64 y=6 width=22 height=122 xoffset=12 yoffset=39 xadvance=46 page=0 chnl=15
char id=34 x=98 y=6 width=44 height=43 xoffset=11 yoffset=39 xadvance=66 page=0 chnl=15
char id=35 x=154 y=6 width=100 height=121 xoffset=2 yoffset=39 xadvance=104 page=0 chnl=15
char id=36 x=266 y=6 width=88 height=152 xoffset=9 yoffset=24 xadvance=105 page=0 chnl=15
char id=37 x=366 y=6 width=108 height=124 xoffset=13 yoffset=38 xadvance=134 page=0 chnl=15
char id=38 x=486 y=6 width=96 height=124 xoffset=8 yoffset=38 xadvance=105 page=0 chnl=15
char id=39 x=594 y=6 width=14 height=43 xoffset=11 yoffset=39 xadvance=36 page=0 chnl=15
char id=40 x=620 y=6 width=36 height=151 xoffset=17 yoffset=34 xadvance=59 page=0 chnl=15
char id=41 x=668 y=6 width=36 height=151 xoffset=7 yoffset=34 xadvance=59 page=0 chnl=15
char id=42 x=716 y=6 width=65 height=69 xoffset=9 yoffset=39 xadvance=82 page=0 chnl=15
char id=43 x=793 y=6 width=79 height=78 xoffset=15 yoffset=74 xadvance=108 page=0 chnl=15
char id=44 x=884 y=6 width=23 height=47 xoffset=11 yoffset=143 xadvance=46 page=0 chnl=15
char id=45 x=919 y=6 width=54 height=13 xoffset=11 yoffset=102 xadvance=76 page=0 chnl=15
char id=46 x=985 y=6 width=22 height=22 xoffset=12 yoffset=139 xadvance=45 page=0 chnl=15
char id=47 x=1019 y=6 width=53 height=145 xoffset=3 yoffset=34 xadvance=58 page=0 chnl=15
char id=48 x=1084 y=6 width=85 height=124 xoffset=9 yoffset=38 xadvance=103 page=0 chnl=15
char id=49 x=1181 y=6 width=45 height=121 xoffset=10 yoffset=39 xadvance=76 page=0 chnl=15
char id=50 x=1238 y=6 width=77 height=122 xoffset=12 yoffset=38 xadvance=100 page=0 chnl=15
char id=51 x=1327 y=6 width=82 height=124 xoffset=12 yoffset=38 xadvance=105 page=0 chnl=15
char id=52 x=1421 y=6 width=88 height=121 xoffset=9 yoffset=39 xadvance=106 page=0 chnl=15
char id=53 x=1521 y=6 width=78 height=123 xoffset=12 yoffset=39 xadvance=100 page=0 chnl=15
char id=54 x=1611 y=6 width=83 height=125 xoffset=10 yoffset=37 xadvance=103 page=0 chnl=15
char id=55 x=1706 y=6 width=78 height=121 xoffset=7 yoffset=39 xadvance=94 page=0 chnl=15
char id=56 x=1796 y=6 width=82 height=124 xoffset=10 yoffset=38 xadvance=101 page=0 chnl=15
char id=57 x=1890 y=6 width=83 height=125 xoffset=10 yoffset=37 xadvance=103 page=0 chnl=15
char id=58 x=1985 y=6 width=22 height=89 xoffset=12 yoffset=72 xadvance=45 page=0 chnl=15
char id=59 x=6 y=218 width=25 height=118 xoffset=11 yoffset=72 xadvance=46 page=0 chnl=15
char id=60 x=43 y=218 width=77 height=88 xoffset=16 yoffset=69 xadvance=108 page=0 chnl=15
char id=61 x=132 y=218 width=73 height=48 xoffset=18 yoffset=89 xadvance=108 page=0 chnl=15
char id=62 x=217 y=218 width=77 height=88 xoffset=16 yoffset=69 xadvance=108 page=0 chnl=15
char id=63 x=306 y=218 width=70 height=123 xoffset=6 yoffset=38 xadvance=83 page=0 chnl=15
char id=64 x=388 y=218 width=140 height=148 xoffset=7 yoffset=44 xadvance=154 page=0 chnl=15
char id=65 x=540 y=218 width=104 height=121 xoffset=4 yoffset=39 xadvance=111 page=0 chnl=15
char id=66 x=656 y=218 width=85 height=121 xoffset=14 yoffset=39 xadvance=107 page=0 chnl=15
char id=67 x=753 y=218 width=103 height=124 xoffset=9 yoffset=38 xadvance=120 page=0 chnl=15
char id=68 x=868 y=218 width=95 height=121 xoffset=14 yoffset=39 xadvance=118 page=0 chnl=15
char id=69 x=975 y=218 width=75 height=121 xoffset=14 yoffset=39 xadvance=98 page=0 chnl=15
char id=70 x=1062 y=218 width=73 height=121 xoffset=14 yoffset=39 xadvance=96 page=0 chnl=15
char id=71 x=1147 y=218 width=104 height=124 xoffset=9 yoffset=38 xadvance=122 page=0 chnl=15
char id=72 x=1263 y=218 width=94 height=121 xoffset=14 yoffset=39 xadvance=122 page=0 chnl=15
char id=73 x=1369 y=218 width=16 height=121 xoffset=14 yoffset=39 xadvance=43 page=0 chnl=15
char id=74 x=1397 y=218 width=70 height=123 xoffset=6 yoffset=39 xadvance=89 page=0 chnl=15
char id=75 x=1479 y=218 width=90 height=121 xoffset=14 yoffset=39 xadvance=107 page=0 chnl=15
char id=76 x=1581 y=218 width=71 height=121 xoffset=14 yoffset=39 xadvance=92 page=0 chnl=15
char id=77 x=1664 y=218 width=119 height=121 xoffset=14 yoffset=39 xadvance=146 page=0 chnl=15
char id=78 x=1795 y=218 width=96 height=121 xoffset=14 yoffset=39 xadvance=124 page=0 chnl=15
char id=79 x=1903 y=218 width=107 height=124 xoffset=9 yoffset=38 xadvance=125 page=0 chnl=15
char id=80 x=6 y=430 width=83 height=121 xoffset=14 yoffset=39 xadvance=104 page=0 chnl=15
char id=81 x=101 y=430 width=107 height=134 xoffset=9 yoffset=38 xadvance=125 page=0 chnl=15
char id=82 x=220 y=430 width=87 height=121 xoffset=14 yoffset=39 xadvance=105 page=0 chnl=15
char id=83 x=319 y=430 width=88 height=125 xoffset=9 yoffset=38 xadvance=105 page=0 chnl=15
char id=84 x=419 y=430 width=92 height=121 xoffset=7 yoffset=39 xadvance=106 page=0 chnl=15
char id=85 x=523 y=430 width=94 height=124 xoffset=14 yoffset=39 xadvance=122 page=0 chnl=15
char id=86 x=629 y=430 width=104 height=121 xoffset=4 yoffset=39 xadvance=111 page=0 chnl=15
char id=87 x=745 y=430 width=151 height=121 xoffset=3 yoffset=39 xadvance=156 page=0 chnl=15
char id=88 x=908 y=430 width=98 height=121 xoffset=4 yoffset=39 xadvance=106 page=0 chnl=15
char id=89 x=1018 y=430 width=102 height=121 xoffset=4 yoffset=39 xadvance=109 page=0 chnl=15
char id=90 x=1132 y=430 width=85 height=121 xoffset=9 yoffset=39 xadvance=103 page=0 chnl=15
char id=91 x=1229 y=430 width=36 height=151 xoffset=19 yoffset=34 xadvance=59 page=0 chnl=15
char id=92 x=1277 y=430 width=53 height=145 xoffset=3 yoffset=34 xadvance=58 page=0 chnl=15
char id=93 x=1342 y=430 width=36 height=151 xoffset=5 yoffset=34 xadvance=59 page=0 chnl=15
char id=94 x=1390 y=430 width=65 height=52 xoffset=6 yoffset=43 xadvance=77 page=0 chnl=15
char id=95 x=1467 y=430 width=76 height=13 xoffset=-1 yoffset=160 xadvance=74 page=0 chnl=15
char id=96 x=1555 y=430 width=32 height=28 xoffset=25 yoffset=30 xadvance=82 page=0 chnl=15
char id=97 x=1599 y=430 width=73 height=95 xoffset=8 yoffset=68 xadvance=93 page=0 chnl=15
char id=98 x=1684 y=430 width=81 height=123 xoffset=14 yoffset=39 xadvance=102 page=0 chnl=15
char id=99 x=1777 y=430 width=77 height=94 xoffset=8 yoffset=68 xadvance=92 page=0 chnl=15
char id=100 x=1866 y=430 width=81 height=123 xoffset=8 yoffset=39 xadvance=102 page=0 chnl=15
char id=101 x=6 y=642 width=80 height=94 xoffset=8 yoffset=68 xadvance=96 page=0 chnl=15
char id=102 x=98 y=642 width=54 height=126 xoffset=4 yoffset=34 xadvance=59 page=0 chnl=15
char id=103 x=164 y=642 width=81 height=128 xoffset=8 yoffset=68 xadvance=100 page=0 chnl=15
char id=104 x=257 y=642 width=73 height=121 xoffset=12 yoffset=39 xadvance=97 page=0 chnl=15
char id=105 x=342 y=642 width=21 height=124 xoffset=9 yoffset=36 xadvance=39 page=0 chnl=15
char id=106 x=375 y=642 width=33 height=158 xoffset=-3 yoffset=36 xadvance=39 page=0 chnl=15
char id=107 x=420 y=642 width=76 height=121 xoffset=12 yoffset=39 xadvance=89 page=0 chnl=15
char id=108 x=508 y=642 width=15 height=121 xoffset=12 yoffset=39 xadvance=39 page=0 chnl=15
char id=109 x=535 y=642 width=119 height=92 xoffset=12 yoffset=68 xadvance=143 page=0 chnl=15
char id=110 x=666 y=642 width=73 height=92 xoffset=12 yoffset=68 xadvance=96 page=0 chnl=15
char id=111 x=751 y=642 width=83 height=94 xoffset=8 yoffset=68 xadvance=98 page=0 chnl=15
char id=112 x=846 y=642 width=81 height=126 xoffset=12 yoffset=68 xadvance=100 page=0 chnl=15
char id=113 x=939 y=642 width=81 height=126 xoffset=8 yoffset=68 xadvance=100 page=0 chnl=15
char id=114 x=1032 y=642 width=46 height=92 xoffset=12 yoffset=68 xadvance=61 page=0 chnl=15
char id=115 x=1090 y=642 width=71 height=94 xoffset=8 yoffset=68 xadvance=86 page=0 chnl=15
char id=116 x=1173 y=642 width=49 height=114 xoffset=5 yoffset=48 xadvance=60 page=0 chnl=15
char id=117 x=1234 y=642 width=72 height=93 xoffset=12 yoffset=69 xadvance=96 page=0 chnl=15
char id=118 x=1318 y=642 width=82 height=91 xoffset=5 yoffset=69 xadvance=92 page=0 chnl=15
char id=119 x=1412 y=642 width=124 height=91 xoffset=5 yoffset=69 xadvance=134 page=0 chnl=15
char id=120 x=1548 y=642 width=76 height=91 xoffset=7 yoffset=69 xadvance=89 page=0 chnl=15
char id=121 x=1636 y=642 width=82 height=125 xoffset=5 yoffset=69 xadvance=92 page=0 chnl=15
char id=122 x=1730 y=642 width=71 height=91 xoffset=10 yoffset=69 xadvance=89 page=0 chnl=15
char id=123 x=1813 y=642 width=49 height=151 xoffset=6 yoffset=34 xadvance=59 page=0 chnl=15
char id=124 x=1874 y=642 width=12 height=198 xoffset=21 yoffset=1 xadvance=53 page=0 chnl=15
char id=125 x=1898 y=642 width=49 height=151 xoffset=5 yoffset=34 xadvance=59 page=0 chnl=15
char id=126 x=6 y=854 width=83 height=31 xoffset=13 yoffset=97 xadvance=108 page=0 chnl=15
char id=163 x=101 y=854 width=84 height=122 xoffset=10 yoffset=38 xadvance=104 page=0 chnl=15
char id=165 x=197 y=854 width=87 height=121 xoffset=2 yoffset=39 xadvance=90 page=0 chnl=15
char id=167 x=296 y=854 width=75 height=147 xoffset=8 yoffset=38 xadvance=91 page=0 chnl=15
char id=176 x=383 y=854 width=55 height=55 xoffset=10 yoffset=38 xadvance=75 page=0 chnl=15
char id=178 x=450 y=854 width=56 height=74 xoffset=7 yoffset=26 xadvance=69 page=0 chnl=15
char id=215 x=518 y=854 width=77 height=76 xoffset=16 yoffset=75 xadvance=108 page=0 chnl=15
char id=231 x=607 y=854 width=77 height=128 xoffset=8 yoffset=68 xadvance=92 page=0 chnl=15
char id=234 x=696 y=854 width=80 height=129 xoffset=8 yoffset=33 xadvance=96 page=0 chnl=15
char id=241 x=788 y=854 width=73 height=124 xoffset=12 yoffset=36 xadvance=96 page=0 chnl=15
char id=252 x=873 y=854 width=72 height=126 xoffset=12 yoffset=36 xadvance=96 page=0 chnl=15
char id=8209 x=957 y=854 width=53 height=13 xoffset=3 yoffset=102 xadvance=59 page=0 chnl=15
char id=8211 x=1022 y=854 width=83 height=13 xoffset=0 yoffset=102 xadvance=82 page=0 chnl=15
char id=8226 x=1117 y=854 width=53 height=52 xoffset=20 yoffset=86 xadvance=92 page=0 chnl=15
char id=8364 x=1182 y=854 width=96 height=124 xoffset=4 yoffset=38 xadvance=109 page=0 chnl=15
char id=8627 x=1290 y=854 width=131 height=104 xoffset=16 yoffset=39 xadvance=162 page=0 chnl=15
char id=8679 x=1433 y=854 width=139 height=130 xoffset=15 yoffset=30 xadvance=169 page=0 chnl=15
char id=9003 x=1584 y=854 width=173 height=121 xoffset=13 yoffset=39 xadvance=200 page=0 chnl=15
char id=9251 x=1769 y=854 width=96 height=38 xoffset=12 yoffset=148 xadvance=119 page=0 chnl=15
char id=9654 x=1877 y=854 width=129 height=152 xoffset=15 yoffset=24 xadvance=151 page=0 chnl=15
char id=9664 x=6 y=1066 width=129 height=152 xoffset=7 yoffset=24 xadvance=151 page=0 chnl=15
char id=9675 x=147 y=1066 width=125 height=125 xoffset=13 yoffset=37 xadvance=151 page=0 chnl=15
char id=9679 x=284 y=1066 width=125 height=125 xoffset=13 yoffset=37 xadvance=151 page=0 chnl=15
char id=9881 x=421 y=1066 width=0 height=0 xoffset=0 yoffset=0 xadvance=0 page=0 chnl=15
char id=10003 x=433 y=1066 width=114 height=83 xoffset=17 yoffset=63 xadvance=145 page=0 chnl=15
char id=10004 x=559 y=1066 width=0 height=0 xoffset=0 yoffset=0 xadvance=0 page=0 chnl=15
char id=10005 x=571 y=1066 width=0 height=0 xoffset=0 yoffset=0 xadvance=0 page=0 chnl=15
Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

+125
View File
@@ -0,0 +1,125 @@
info face="Inter-SemiBold" size=-200 bold=0 italic=0 charset="" unicode=1 stretchH=100 smooth=0 aa=1 padding=0,0,0,0 spacing=0,0 outline=0
common lineHeight=200 base=200 scaleW=2048 scaleH=2048 pages=1 packed=0 alphaChnl=0 redChnl=4 greenChnl=4 blueChnl=4
page id=0 file="Inter-SemiBold.png"
chars count=121
char id=32 x=6 y=6 width=41 height=200 xoffset=0 yoffset=0 xadvance=41 page=0 chnl=15
char id=33 x=59 y=6 width=27 height=123 xoffset=12 yoffset=39 xadvance=51 page=0 chnl=15
char id=34 x=98 y=6 width=47 height=46 xoffset=9 yoffset=39 xadvance=65 page=0 chnl=15
char id=35 x=157 y=6 width=103 height=121 xoffset=2 yoffset=39 xadvance=106 page=0 chnl=15
char id=36 x=272 y=6 width=93 height=152 xoffset=7 yoffset=24 xadvance=107 page=0 chnl=15
char id=37 x=377 y=6 width=113 height=128 xoffset=13 yoffset=36 xadvance=139 page=0 chnl=15
char id=38 x=502 y=6 width=102 height=124 xoffset=7 yoffset=38 xadvance=109 page=0 chnl=15
char id=39 x=616 y=6 width=17 height=46 xoffset=9 yoffset=39 xadvance=35 page=0 chnl=15
char id=40 x=645 y=6 width=44 height=150 xoffset=16 yoffset=36 xadvance=65 page=0 chnl=15
char id=41 x=701 y=6 width=44 height=150 xoffset=5 yoffset=36 xadvance=65 page=0 chnl=15
char id=42 x=757 y=6 width=67 height=69 xoffset=11 yoffset=39 xadvance=89 page=0 chnl=15
char id=43 x=836 y=6 width=83 height=82 xoffset=14 yoffset=71 xadvance=111 page=0 chnl=15
char id=44 x=931 y=6 width=27 height=47 xoffset=9 yoffset=143 xadvance=48 page=0 chnl=15
char id=45 x=970 y=6 width=55 height=18 xoffset=11 yoffset=101 xadvance=76 page=0 chnl=15
char id=46 x=1037 y=6 width=26 height=27 xoffset=11 yoffset=135 xadvance=48 page=0 chnl=15
char id=47 x=1075 y=6 width=58 height=145 xoffset=2 yoffset=34 xadvance=62 page=0 chnl=15
char id=48 x=1145 y=6 width=94 height=125 xoffset=8 yoffset=38 xadvance=110 page=0 chnl=15
char id=49 x=1251 y=6 width=52 height=121 xoffset=8 yoffset=39 xadvance=79 page=0 chnl=15
char id=50 x=1315 y=6 width=85 height=122 xoffset=9 yoffset=38 xadvance=102 page=0 chnl=15
char id=51 x=1412 y=6 width=90 height=124 xoffset=9 yoffset=38 xadvance=107 page=0 chnl=15
char id=52 x=1514 y=6 width=94 height=121 xoffset=8 yoffset=39 xadvance=110 page=0 chnl=15
char id=53 x=1620 y=6 width=86 height=123 xoffset=10 yoffset=39 xadvance=104 page=0 chnl=15
char id=54 x=1718 y=6 width=91 height=125 xoffset=8 yoffset=37 xadvance=107 page=0 chnl=15
char id=55 x=1821 y=6 width=83 height=121 xoffset=6 yoffset=39 xadvance=97 page=0 chnl=15
char id=56 x=1916 y=6 width=91 height=124 xoffset=8 yoffset=38 xadvance=106 page=0 chnl=15
char id=57 x=6 y=218 width=90 height=125 xoffset=9 yoffset=37 xadvance=107 page=0 chnl=15
char id=58 x=108 y=218 width=26 height=91 xoffset=11 yoffset=71 xadvance=48 page=0 chnl=15
char id=59 x=146 y=218 width=31 height=119 xoffset=9 yoffset=71 xadvance=48 page=0 chnl=15
char id=60 x=189 y=218 width=81 height=92 xoffset=15 yoffset=66 xadvance=111 page=0 chnl=15
char id=61 x=282 y=218 width=79 height=57 xoffset=16 yoffset=84 xadvance=111 page=0 chnl=15
char id=62 x=373 y=218 width=81 height=92 xoffset=15 yoffset=66 xadvance=111 page=0 chnl=15
char id=63 x=466 y=218 width=77 height=124 xoffset=6 yoffset=38 xadvance=89 page=0 chnl=15
char id=64 x=555 y=218 width=149 height=153 xoffset=9 yoffset=41 xadvance=165 page=0 chnl=15
char id=65 x=716 y=218 width=112 height=121 xoffset=4 yoffset=39 xadvance=119 page=0 chnl=15
char id=66 x=840 y=218 width=91 height=121 xoffset=11 yoffset=39 xadvance=108 page=0 chnl=15
char id=67 x=943 y=218 width=107 height=124 xoffset=8 yoffset=38 xadvance=122 page=0 chnl=15
char id=68 x=1062 y=218 width=100 height=121 xoffset=11 yoffset=39 xadvance=119 page=0 chnl=15
char id=69 x=1174 y=218 width=80 height=121 xoffset=11 yoffset=39 xadvance=100 page=0 chnl=15
char id=70 x=1266 y=218 width=78 height=121 xoffset=11 yoffset=39 xadvance=96 page=0 chnl=15
char id=71 x=1356 y=218 width=108 height=124 xoffset=8 yoffset=38 xadvance=124 page=0 chnl=15
char id=72 x=1476 y=218 width=101 height=121 xoffset=11 yoffset=39 xadvance=122 page=0 chnl=15
char id=73 x=1589 y=218 width=23 height=121 xoffset=11 yoffset=39 xadvance=45 page=0 chnl=15
char id=74 x=1624 y=218 width=77 height=123 xoffset=4 yoffset=39 xadvance=92 page=0 chnl=15
char id=75 x=1713 y=218 width=98 height=121 xoffset=11 yoffset=39 xadvance=111 page=0 chnl=15
char id=76 x=1823 y=218 width=76 height=121 xoffset=11 yoffset=39 xadvance=93 page=0 chnl=15
char id=77 x=6 y=430 width=127 height=121 xoffset=11 yoffset=39 xadvance=149 page=0 chnl=15
char id=78 x=145 y=430 width=100 height=121 xoffset=11 yoffset=39 xadvance=122 page=0 chnl=15
char id=79 x=257 y=430 width=112 height=124 xoffset=8 yoffset=38 xadvance=128 page=0 chnl=15
char id=80 x=381 y=430 width=89 height=121 xoffset=11 yoffset=39 xadvance=106 page=0 chnl=15
char id=81 x=482 y=430 width=112 height=133 xoffset=8 yoffset=38 xadvance=128 page=0 chnl=15
char id=82 x=606 y=430 width=93 height=121 xoffset=11 yoffset=39 xadvance=107 page=0 chnl=15
char id=83 x=711 y=430 width=93 height=124 xoffset=7 yoffset=38 xadvance=107 page=0 chnl=15
char id=84 x=816 y=430 width=97 height=121 xoffset=6 yoffset=39 xadvance=109 page=0 chnl=15
char id=85 x=925 y=430 width=99 height=123 xoffset=11 yoffset=39 xadvance=121 page=0 chnl=15
char id=86 x=1036 y=430 width=112 height=121 xoffset=4 yoffset=39 xadvance=119 page=0 chnl=15
char id=87 x=1160 y=430 width=161 height=121 xoffset=3 yoffset=39 xadvance=166 page=0 chnl=15
char id=88 x=1333 y=430 width=106 height=121 xoffset=4 yoffset=39 xadvance=114 page=0 chnl=15
char id=89 x=1451 y=430 width=110 height=121 xoffset=3 yoffset=39 xadvance=116 page=0 chnl=15
char id=90 x=1573 y=430 width=90 height=121 xoffset=9 yoffset=39 xadvance=107 page=0 chnl=15
char id=91 x=1675 y=430 width=43 height=150 xoffset=18 yoffset=36 xadvance=65 page=0 chnl=15
char id=92 x=1730 y=430 width=58 height=145 xoffset=2 yoffset=34 xadvance=62 page=0 chnl=15
char id=93 x=1800 y=430 width=43 height=150 xoffset=4 yoffset=36 xadvance=65 page=0 chnl=15
char id=94 x=1855 y=430 width=69 height=53 xoffset=5 yoffset=42 xadvance=79 page=0 chnl=15
char id=95 x=1936 y=430 width=79 height=17 xoffset=-1 yoffset=160 xadvance=77 page=0 chnl=15
char id=96 x=6 y=642 width=35 height=28 xoffset=24 yoffset=31 xadvance=82 page=0 chnl=15
char id=97 x=53 y=642 width=79 height=94 xoffset=6 yoffset=68 xadvance=94 page=0 chnl=15
char id=98 x=144 y=642 width=86 height=123 xoffset=11 yoffset=39 xadvance=104 page=0 chnl=15
char id=99 x=242 y=642 width=82 height=94 xoffset=7 yoffset=68 xadvance=95 page=0 chnl=15
char id=100 x=336 y=642 width=86 height=123 xoffset=7 yoffset=39 xadvance=104 page=0 chnl=15
char id=101 x=434 y=642 width=84 height=94 xoffset=7 yoffset=68 xadvance=97 page=0 chnl=15
char id=102 x=530 y=642 width=58 height=126 xoffset=3 yoffset=34 xadvance=62 page=0 chnl=15
char id=103 x=600 y=642 width=86 height=128 xoffset=7 yoffset=68 xadvance=103 page=0 chnl=15
char id=104 x=698 y=642 width=81 height=121 xoffset=10 yoffset=39 xadvance=101 page=0 chnl=15
char id=105 x=791 y=642 width=25 height=126 xoffset=9 yoffset=34 xadvance=43 page=0 chnl=15
char id=106 x=828 y=642 width=38 height=160 xoffset=-4 yoffset=34 xadvance=43 page=0 chnl=15
char id=107 x=878 y=642 width=83 height=121 xoffset=10 yoffset=39 xadvance=94 page=0 chnl=15
char id=108 x=973 y=642 width=23 height=121 xoffset=10 yoffset=39 xadvance=43 page=0 chnl=15
char id=109 x=1008 y=642 width=128 height=92 xoffset=10 yoffset=68 xadvance=148 page=0 chnl=15
char id=110 x=1148 y=642 width=81 height=92 xoffset=10 yoffset=68 xadvance=100 page=0 chnl=15
char id=111 x=1241 y=642 width=87 height=94 xoffset=7 yoffset=68 xadvance=100 page=0 chnl=15
char id=112 x=1340 y=642 width=87 height=126 xoffset=10 yoffset=68 xadvance=103 page=0 chnl=15
char id=113 x=1439 y=642 width=86 height=126 xoffset=7 yoffset=68 xadvance=103 page=0 chnl=15
char id=114 x=1537 y=642 width=53 height=92 xoffset=10 yoffset=68 xadvance=65 page=0 chnl=15
char id=115 x=1602 y=642 width=79 height=94 xoffset=6 yoffset=68 xadvance=90 page=0 chnl=15
char id=116 x=1693 y=642 width=54 height=114 xoffset=4 yoffset=48 xadvance=62 page=0 chnl=15
char id=117 x=1759 y=642 width=80 height=93 xoffset=10 yoffset=69 xadvance=100 page=0 chnl=15
char id=118 x=1851 y=642 width=89 height=91 xoffset=3 yoffset=69 xadvance=95 page=0 chnl=15
char id=119 x=6 y=854 width=131 height=91 xoffset=4 yoffset=69 xadvance=138 page=0 chnl=15
char id=120 x=149 y=854 width=83 height=91 xoffset=5 yoffset=69 xadvance=92 page=0 chnl=15
char id=121 x=244 y=854 width=89 height=125 xoffset=3 yoffset=69 xadvance=95 page=0 chnl=15
char id=122 x=345 y=854 width=75 height=91 xoffset=9 yoffset=69 xadvance=92 page=0 chnl=15
char id=123 x=432 y=854 width=56 height=150 xoffset=5 yoffset=36 xadvance=65 page=0 chnl=15
char id=124 x=500 y=854 width=21 height=198 xoffset=19 yoffset=1 xadvance=59 page=0 chnl=15
char id=125 x=533 y=854 width=56 height=150 xoffset=4 yoffset=36 xadvance=65 page=0 chnl=15
char id=126 x=601 y=854 width=87 height=36 xoffset=12 yoffset=94 xadvance=111 page=0 chnl=15
char id=163 x=700 y=854 width=88 height=122 xoffset=10 yoffset=38 xadvance=108 page=0 chnl=15
char id=165 x=800 y=854 width=89 height=121 xoffset=2 yoffset=39 xadvance=93 page=0 chnl=15
char id=167 x=901 y=854 width=80 height=147 xoffset=8 yoffset=38 xadvance=95 page=0 chnl=15
char id=176 x=993 y=854 width=58 height=58 xoffset=9 yoffset=38 xadvance=75 page=0 chnl=15
char id=178 x=1063 y=854 width=58 height=74 xoffset=6 yoffset=26 xadvance=69 page=0 chnl=15
char id=215 x=1133 y=854 width=83 height=83 xoffset=14 yoffset=71 xadvance=111 page=0 chnl=15
char id=231 x=1228 y=854 width=82 height=130 xoffset=7 yoffset=68 xadvance=95 page=0 chnl=15
char id=234 x=1322 y=854 width=84 height=130 xoffset=7 yoffset=32 xadvance=97 page=0 chnl=15
char id=241 x=1418 y=854 width=81 height=125 xoffset=10 yoffset=35 xadvance=100 page=0 chnl=15
char id=252 x=1511 y=854 width=80 height=128 xoffset=10 yoffset=34 xadvance=100 page=0 chnl=15
char id=8209 x=1603 y=854 width=55 height=18 xoffset=9 yoffset=101 xadvance=70 page=0 chnl=15
char id=8211 x=1670 y=854 width=83 height=18 xoffset=0 yoffset=101 xadvance=82 page=0 chnl=15
char id=8226 x=1765 y=854 width=52 height=52 xoffset=15 yoffset=86 xadvance=82 page=0 chnl=15
char id=8364 x=1829 y=854 width=99 height=124 xoffset=4 yoffset=38 xadvance=112 page=0 chnl=15
char id=8627 x=6 y=1066 width=134 height=107 xoffset=16 yoffset=39 xadvance=165 page=0 chnl=15
char id=8679 x=152 y=1066 width=152 height=130 xoffset=13 yoffset=30 xadvance=178 page=0 chnl=15
char id=9003 x=316 y=1066 width=173 height=121 xoffset=13 yoffset=39 xadvance=200 page=0 chnl=15
char id=9251 x=501 y=1066 width=102 height=39 xoffset=11 yoffset=148 xadvance=123 page=0 chnl=15
char id=9654 x=615 y=1066 width=131 height=152 xoffset=16 yoffset=24 xadvance=155 page=0 chnl=15
char id=9664 x=758 y=1066 width=131 height=152 xoffset=8 yoffset=24 xadvance=155 page=0 chnl=15
char id=9675 x=901 y=1066 width=125 height=125 xoffset=13 yoffset=37 xadvance=151 page=0 chnl=15
char id=9679 x=1038 y=1066 width=125 height=125 xoffset=13 yoffset=37 xadvance=151 page=0 chnl=15
char id=9881 x=1175 y=1066 width=0 height=0 xoffset=0 yoffset=0 xadvance=0 page=0 chnl=15
char id=10003 x=1187 y=1066 width=119 height=91 xoffset=16 yoffset=60 xadvance=145 page=0 chnl=15
char id=10004 x=1318 y=1066 width=0 height=0 xoffset=0 yoffset=0 xadvance=0 page=0 chnl=15
char id=10005 x=1330 y=1066 width=0 height=0 xoffset=0 yoffset=0 xadvance=0 page=0 chnl=15
Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

+125
View File
@@ -0,0 +1,125 @@
info face="Inter-Thin" size=-200 bold=0 italic=0 charset="" unicode=1 stretchH=100 smooth=0 aa=1 padding=0,0,0,0 spacing=0,0 outline=0
common lineHeight=200 base=200 scaleW=2048 scaleH=2048 pages=1 packed=0 alphaChnl=0 redChnl=4 greenChnl=4 blueChnl=4
page id=0 file="Inter-Thin.png"
chars count=121
char id=32 x=6 y=6 width=46 height=200 xoffset=0 yoffset=0 xadvance=46 page=0 chnl=15
char id=33 x=64 y=6 width=9 height=122 xoffset=17 yoffset=39 xadvance=43 page=0 chnl=15
char id=34 x=85 y=6 width=27 height=39 xoffset=15 yoffset=39 xadvance=56 page=0 chnl=15
char id=35 x=124 y=6 width=98 height=121 xoffset=3 yoffset=39 xadvance=104 page=0 chnl=15
char id=36 x=234 y=6 width=77 height=152 xoffset=12 yoffset=24 xadvance=101 page=0 chnl=15
char id=37 x=323 y=6 width=102 height=124 xoffset=13 yoffset=38 xadvance=127 page=0 chnl=15
char id=38 x=437 y=6 width=84 height=124 xoffset=10 yoffset=38 xadvance=96 page=0 chnl=15
char id=39 x=533 y=6 width=4 height=39 xoffset=15 yoffset=39 xadvance=33 page=0 chnl=15
char id=40 x=549 y=6 width=22 height=151 xoffset=19 yoffset=34 xadvance=52 page=0 chnl=15
char id=41 x=583 y=6 width=22 height=151 xoffset=11 yoffset=34 xadvance=52 page=0 chnl=15
char id=42 x=617 y=6 width=61 height=69 xoffset=11 yoffset=39 xadvance=82 page=0 chnl=15
char id=43 x=690 y=6 width=69 height=69 xoffset=18 yoffset=79 xadvance=105 page=0 chnl=15
char id=44 x=771 y=6 width=13 height=47 xoffset=14 yoffset=143 xadvance=42 page=0 chnl=15
char id=45 x=796 y=6 width=46 height=5 xoffset=13 yoffset=106 xadvance=72 page=0 chnl=15
char id=46 x=854 y=6 width=10 height=9 xoffset=16 yoffset=152 xadvance=41 page=0 chnl=15
char id=47 x=876 y=6 width=44 height=145 xoffset=5 yoffset=34 xadvance=53 page=0 chnl=15
char id=48 x=932 y=6 width=75 height=124 xoffset=11 yoffset=38 xadvance=97 page=0 chnl=15
char id=49 x=1019 y=6 width=32 height=121 xoffset=11 yoffset=39 xadvance=67 page=0 chnl=15
char id=50 x=1063 y=6 width=73 height=122 xoffset=15 yoffset=38 xadvance=102 page=0 chnl=15
char id=51 x=1148 y=6 width=72 height=124 xoffset=15 yoffset=38 xadvance=101 page=0 chnl=15
char id=52 x=1232 y=6 width=75 height=121 xoffset=12 yoffset=39 xadvance=98 page=0 chnl=15
char id=53 x=1319 y=6 width=69 height=123 xoffset=13 yoffset=39 xadvance=94 page=0 chnl=15
char id=54 x=1400 y=6 width=70 height=124 xoffset=13 yoffset=38 xadvance=96 page=0 chnl=15
char id=55 x=1482 y=6 width=71 height=121 xoffset=9 yoffset=39 xadvance=90 page=0 chnl=15
char id=56 x=1565 y=6 width=76 height=124 xoffset=12 yoffset=38 xadvance=99 page=0 chnl=15
char id=57 x=1653 y=6 width=70 height=125 xoffset=13 yoffset=37 xadvance=96 page=0 chnl=15
char id=58 x=1735 y=6 width=10 height=71 xoffset=16 yoffset=81 xadvance=41 page=0 chnl=15
char id=59 x=1757 y=6 width=14 height=109 xoffset=14 yoffset=81 xadvance=42 page=0 chnl=15
char id=60 x=1783 y=6 width=65 height=88 xoffset=20 yoffset=69 xadvance=105 page=0 chnl=15
char id=61 x=1860 y=6 width=57 height=36 xoffset=24 yoffset=95 xadvance=105 page=0 chnl=15
char id=62 x=1929 y=6 width=65 height=88 xoffset=20 yoffset=69 xadvance=105 page=0 chnl=15
char id=63 x=6 y=218 width=63 height=123 xoffset=7 yoffset=38 xadvance=79 page=0 chnl=15
char id=64 x=81 y=218 width=139 height=148 xoffset=8 yoffset=44 xadvance=154 page=0 chnl=15
char id=65 x=232 y=218 width=92 height=121 xoffset=5 yoffset=39 xadvance=101 page=0 chnl=15
char id=66 x=336 y=218 width=76 height=121 xoffset=18 yoffset=39 xadvance=105 page=0 chnl=15
char id=67 x=424 y=218 width=97 height=124 xoffset=11 yoffset=38 xadvance=119 page=0 chnl=15
char id=68 x=533 y=218 width=89 height=121 xoffset=18 yoffset=39 xadvance=118 page=0 chnl=15
char id=69 x=634 y=218 width=67 height=121 xoffset=18 yoffset=39 xadvance=96 page=0 chnl=15
char id=70 x=713 y=218 width=66 height=121 xoffset=18 yoffset=39 xadvance=96 page=0 chnl=15
char id=71 x=791 y=218 width=98 height=124 xoffset=11 yoffset=38 xadvance=122 page=0 chnl=15
char id=72 x=901 y=218 width=85 height=121 xoffset=18 yoffset=39 xadvance=121 page=0 chnl=15
char id=73 x=998 y=218 width=5 height=121 xoffset=18 yoffset=39 xadvance=41 page=0 chnl=15
char id=74 x=1015 y=218 width=60 height=123 xoffset=8 yoffset=39 xadvance=85 page=0 chnl=15
char id=75 x=1087 y=218 width=79 height=121 xoffset=18 yoffset=39 xadvance=101 page=0 chnl=15
char id=76 x=1178 y=218 width=65 height=121 xoffset=18 yoffset=39 xadvance=92 page=0 chnl=15
char id=77 x=1255 y=218 width=106 height=121 xoffset=18 yoffset=39 xadvance=142 page=0 chnl=15
char id=78 x=1373 y=218 width=91 height=121 xoffset=18 yoffset=39 xadvance=127 page=0 chnl=15
char id=79 x=1476 y=218 width=101 height=124 xoffset=11 yoffset=38 xadvance=123 page=0 chnl=15
char id=80 x=1589 y=218 width=71 height=121 xoffset=18 yoffset=39 xadvance=100 page=0 chnl=15
char id=81 x=1672 y=218 width=101 height=134 xoffset=11 yoffset=38 xadvance=123 page=0 chnl=15
char id=82 x=1785 y=218 width=77 height=121 xoffset=18 yoffset=39 xadvance=101 page=0 chnl=15
char id=83 x=1874 y=218 width=77 height=125 xoffset=12 yoffset=37 xadvance=101 page=0 chnl=15
char id=84 x=6 y=430 width=82 height=121 xoffset=10 yoffset=39 xadvance=101 page=0 chnl=15
char id=85 x=100 y=430 width=88 height=123 xoffset=18 yoffset=39 xadvance=123 page=0 chnl=15
char id=86 x=200 y=430 width=92 height=121 xoffset=5 yoffset=39 xadvance=101 page=0 chnl=15
char id=87 x=304 y=430 width=139 height=121 xoffset=4 yoffset=39 xadvance=147 page=0 chnl=15
char id=88 x=455 y=430 width=87 height=121 xoffset=5 yoffset=39 xadvance=96 page=0 chnl=15
char id=89 x=554 y=430 width=91 height=121 xoffset=5 yoffset=39 xadvance=101 page=0 chnl=15
char id=90 x=657 y=430 width=77 height=121 xoffset=12 yoffset=39 xadvance=100 page=0 chnl=15
char id=91 x=746 y=430 width=22 height=151 xoffset=20 yoffset=34 xadvance=52 page=0 chnl=15
char id=92 x=780 y=430 width=44 height=145 xoffset=5 yoffset=34 xadvance=53 page=0 chnl=15
char id=93 x=836 y=430 width=22 height=151 xoffset=10 yoffset=34 xadvance=52 page=0 chnl=15
char id=94 x=870 y=430 width=52 height=52 xoffset=7 yoffset=43 xadvance=66 page=0 chnl=15
char id=95 x=934 y=430 width=72 height=4 xoffset=-1 yoffset=160 xadvance=70 page=0 chnl=15
char id=96 x=1018 y=430 width=22 height=26 xoffset=29 yoffset=31 xadvance=82 page=0 chnl=15
char id=97 x=1052 y=430 width=63 height=94 xoffset=11 yoffset=68 xadvance=89 page=0 chnl=15
char id=98 x=1127 y=430 width=71 height=123 xoffset=18 yoffset=39 xadvance=98 page=0 chnl=15
char id=99 x=1210 y=430 width=71 height=94 xoffset=10 yoffset=68 xadvance=91 page=0 chnl=15
char id=100 x=1293 y=430 width=71 height=123 xoffset=10 yoffset=39 xadvance=98 page=0 chnl=15
char id=101 x=1376 y=430 width=75 height=94 xoffset=10 yoffset=68 xadvance=95 page=0 chnl=15
char id=102 x=1463 y=430 width=41 height=124 xoffset=11 yoffset=36 xadvance=54 page=0 chnl=15
char id=103 x=1516 y=430 width=71 height=128 xoffset=10 yoffset=68 xadvance=96 page=0 chnl=15
char id=104 x=1599 y=430 width=63 height=121 xoffset=15 yoffset=39 xadvance=93 page=0 chnl=15
char id=105 x=1674 y=430 width=9 height=115 xoffset=14 yoffset=45 xadvance=36 page=0 chnl=15
char id=106 x=1695 y=430 width=26 height=149 xoffset=-3 yoffset=45 xadvance=36 page=0 chnl=15
char id=107 x=1733 y=430 width=60 height=121 xoffset=15 yoffset=39 xadvance=80 page=0 chnl=15
char id=108 x=1805 y=430 width=5 height=121 xoffset=15 yoffset=39 xadvance=35 page=0 chnl=15
char id=109 x=1822 y=430 width=108 height=92 xoffset=15 yoffset=68 xadvance=138 page=0 chnl=15
char id=110 x=1942 y=430 width=63 height=92 xoffset=15 yoffset=68 xadvance=93 page=0 chnl=15
char id=111 x=6 y=642 width=76 height=94 xoffset=10 yoffset=68 xadvance=95 page=0 chnl=15
char id=112 x=94 y=642 width=70 height=126 xoffset=15 yoffset=68 xadvance=95 page=0 chnl=15
char id=113 x=176 y=642 width=71 height=126 xoffset=10 yoffset=68 xadvance=95 page=0 chnl=15
char id=114 x=259 y=642 width=35 height=92 xoffset=15 yoffset=68 xadvance=53 page=0 chnl=15
char id=115 x=306 y=642 width=60 height=94 xoffset=11 yoffset=68 xadvance=81 page=0 chnl=15
char id=116 x=378 y=642 width=42 height=114 xoffset=8 yoffset=48 xadvance=57 page=0 chnl=15
char id=117 x=432 y=642 width=63 height=93 xoffset=15 yoffset=69 xadvance=93 page=0 chnl=15
char id=118 x=507 y=642 width=70 height=91 xoffset=8 yoffset=69 xadvance=86 page=0 chnl=15
char id=119 x=589 y=642 width=115 height=91 xoffset=8 yoffset=69 xadvance=130 page=0 chnl=15
char id=120 x=716 y=642 width=63 height=91 xoffset=10 yoffset=69 xadvance=82 page=0 chnl=15
char id=121 x=791 y=642 width=70 height=126 xoffset=8 yoffset=69 xadvance=86 page=0 chnl=15
char id=122 x=873 y=642 width=62 height=91 xoffset=12 yoffset=69 xadvance=84 page=0 chnl=15
char id=123 x=947 y=642 width=40 height=151 xoffset=10 yoffset=34 xadvance=52 page=0 chnl=15
char id=124 x=999 y=642 width=5 height=198 xoffset=20 yoffset=1 xadvance=45 page=0 chnl=15
char id=125 x=1016 y=642 width=40 height=151 xoffset=2 yoffset=34 xadvance=52 page=0 chnl=15
char id=126 x=1068 y=642 width=75 height=18 xoffset=15 yoffset=104 xadvance=105 page=0 chnl=15
char id=163 x=1155 y=642 width=77 height=122 xoffset=13 yoffset=38 xadvance=104 page=0 chnl=15
char id=165 x=1244 y=642 width=80 height=121 xoffset=3 yoffset=39 xadvance=86 page=0 chnl=15
char id=167 x=1336 y=642 width=67 height=147 xoffset=10 yoffset=38 xadvance=87 page=0 chnl=15
char id=176 x=1415 y=642 width=49 height=49 xoffset=13 yoffset=38 xadvance=75 page=0 chnl=15
char id=178 x=1476 y=642 width=50 height=74 xoffset=9 yoffset=26 xadvance=68 page=0 chnl=15
char id=215 x=1538 y=642 width=70 height=70 xoffset=18 yoffset=78 xadvance=105 page=0 chnl=15
char id=231 x=1620 y=642 width=71 height=123 xoffset=10 yoffset=68 xadvance=91 page=0 chnl=15
char id=234 x=1703 y=642 width=75 height=125 xoffset=10 yoffset=37 xadvance=95 page=0 chnl=15
char id=241 x=1790 y=642 width=63 height=119 xoffset=15 yoffset=41 xadvance=93 page=0 chnl=15
char id=252 x=1865 y=642 width=63 height=117 xoffset=15 yoffset=45 xadvance=93 page=0 chnl=15
char id=8209 x=1940 y=642 width=46 height=5 xoffset=-5 yoffset=106 xadvance=41 page=0 chnl=15
char id=8211 x=6 y=854 width=83 height=5 xoffset=0 yoffset=106 xadvance=82 page=0 chnl=15
char id=8226 x=101 y=854 width=31 height=31 xoffset=31 yoffset=97 xadvance=92 page=0 chnl=15
char id=8364 x=144 y=854 width=95 height=124 xoffset=5 yoffset=38 xadvance=112 page=0 chnl=15
char id=8627 x=251 y=854 width=122 height=98 xoffset=16 yoffset=39 xadvance=153 page=0 chnl=15
char id=8679 x=385 y=854 width=113 height=130 xoffset=15 yoffset=30 xadvance=142 page=0 chnl=15
char id=9003 x=510 y=854 width=173 height=121 xoffset=13 yoffset=39 xadvance=200 page=0 chnl=15
char id=9251 x=695 y=854 width=87 height=36 xoffset=13 yoffset=148 xadvance=112 page=0 chnl=15
char id=9654 x=794 y=854 width=125 height=152 xoffset=13 yoffset=24 xadvance=144 page=0 chnl=15
char id=9664 x=931 y=854 width=125 height=152 xoffset=6 yoffset=24 xadvance=144 page=0 chnl=15
char id=9675 x=1068 y=854 width=125 height=125 xoffset=13 yoffset=37 xadvance=151 page=0 chnl=15
char id=9679 x=1205 y=854 width=125 height=125 xoffset=13 yoffset=37 xadvance=151 page=0 chnl=15
char id=9881 x=1342 y=854 width=0 height=0 xoffset=0 yoffset=0 xadvance=0 page=0 chnl=15
char id=10003 x=1354 y=854 width=108 height=73 xoffset=19 yoffset=66 xadvance=145 page=0 chnl=15
char id=10004 x=1474 y=854 width=0 height=0 xoffset=0 yoffset=0 xadvance=0 page=0 chnl=15
char id=10005 x=1486 y=854 width=0 height=0 xoffset=0 yoffset=0 xadvance=0 page=0 chnl=15
Binary file not shown.

After

Width:  |  Height:  |  Size: 219 KiB

@@ -0,0 +1,125 @@
info face="JetBrainsMono-Medium" size=-200 bold=0 italic=0 charset="" unicode=1 stretchH=100 smooth=0 aa=1 padding=0,0,0,0 spacing=0,0 outline=0
common lineHeight=200 base=200 scaleW=2048 scaleH=2048 pages=1 packed=0 alphaChnl=0 redChnl=4 greenChnl=4 blueChnl=4
page id=0 file="JetBrainsMono-Medium.png"
chars count=121
char id=32 x=6 y=6 width=90 height=200 xoffset=0 yoffset=0 xadvance=90 page=0 chnl=15
char id=33 x=108 y=6 width=25 height=112 xoffset=33 yoffset=43 xadvance=90 page=0 chnl=15
char id=34 x=145 y=6 width=49 height=46 xoffset=21 yoffset=43 xadvance=90 page=0 chnl=15
char id=35 x=206 y=6 width=81 height=111 xoffset=5 yoffset=43 xadvance=90 page=0 chnl=15
char id=36 x=299 y=6 width=71 height=154 xoffset=10 yoffset=22 xadvance=90 page=0 chnl=15
char id=37 x=382 y=6 width=89 height=113 xoffset=1 yoffset=42 xadvance=90 page=0 chnl=15
char id=38 x=483 y=6 width=80 height=115 xoffset=7 yoffset=41 xadvance=90 page=0 chnl=15
char id=39 x=575 y=6 width=17 height=46 xoffset=36 yoffset=43 xadvance=90 page=0 chnl=15
char id=40 x=604 y=6 width=47 height=147 xoffset=27 yoffset=26 xadvance=90 page=0 chnl=15
char id=41 x=663 y=6 width=47 height=147 xoffset=17 yoffset=26 xadvance=90 page=0 chnl=15
char id=42 x=722 y=6 width=81 height=80 xoffset=5 yoffset=61 xadvance=90 page=0 chnl=15
char id=43 x=815 y=6 width=73 height=72 xoffset=9 yoffset=68 xadvance=90 page=0 chnl=15
char id=44 x=900 y=6 width=34 height=48 xoffset=24 yoffset=131 xadvance=90 page=0 chnl=15
char id=45 x=946 y=6 width=49 height=14 xoffset=21 yoffset=97 xadvance=90 page=0 chnl=15
char id=46 x=1007 y=6 width=27 height=27 xoffset=32 yoffset=129 xadvance=90 page=0 chnl=15
char id=47 x=1046 y=6 width=71 height=143 xoffset=10 yoffset=28 xadvance=90 page=0 chnl=15
char id=48 x=1129 y=6 width=69 height=115 xoffset=11 yoffset=41 xadvance=90 page=0 chnl=15
char id=49 x=1210 y=6 width=70 height=111 xoffset=13 yoffset=43 xadvance=90 page=0 chnl=15
char id=50 x=1292 y=6 width=70 height=113 xoffset=10 yoffset=41 xadvance=90 page=0 chnl=15
char id=51 x=1374 y=6 width=69 height=113 xoffset=10 yoffset=43 xadvance=90 page=0 chnl=15
char id=52 x=1455 y=6 width=65 height=111 xoffset=10 yoffset=43 xadvance=90 page=0 chnl=15
char id=53 x=1532 y=6 width=67 height=113 xoffset=12 yoffset=43 xadvance=90 page=0 chnl=15
char id=54 x=1611 y=6 width=73 height=113 xoffset=9 yoffset=43 xadvance=90 page=0 chnl=15
char id=55 x=1696 y=6 width=73 height=111 xoffset=11 yoffset=43 xadvance=90 page=0 chnl=15
char id=56 x=1781 y=6 width=73 height=115 xoffset=9 yoffset=41 xadvance=90 page=0 chnl=15
char id=57 x=1866 y=6 width=73 height=113 xoffset=9 yoffset=41 xadvance=90 page=0 chnl=15
char id=58 x=1951 y=6 width=27 height=87 xoffset=32 yoffset=69 xadvance=90 page=0 chnl=15
char id=59 x=1990 y=6 width=34 height=110 xoffset=25 yoffset=69 xadvance=90 page=0 chnl=15
char id=60 x=6 y=218 width=67 height=82 xoffset=12 yoffset=63 xadvance=90 page=0 chnl=15
char id=61 x=85 y=218 width=67 height=50 xoffset=12 yoffset=79 xadvance=90 page=0 chnl=15
char id=62 x=164 y=218 width=67 height=82 xoffset=12 yoffset=63 xadvance=90 page=0 chnl=15
char id=63 x=243 y=218 width=56 height=112 xoffset=19 yoffset=43 xadvance=90 page=0 chnl=15
char id=64 x=311 y=218 width=80 height=141 xoffset=6 yoffset=41 xadvance=90 page=0 chnl=15
char id=65 x=403 y=218 width=78 height=111 xoffset=6 yoffset=43 xadvance=90 page=0 chnl=15
char id=66 x=493 y=218 width=69 height=111 xoffset=13 yoffset=43 xadvance=90 page=0 chnl=15
char id=67 x=574 y=218 width=68 height=115 xoffset=12 yoffset=41 xadvance=90 page=0 chnl=15
char id=68 x=654 y=218 width=66 height=111 xoffset=13 yoffset=43 xadvance=90 page=0 chnl=15
char id=69 x=732 y=218 width=66 height=111 xoffset=14 yoffset=43 xadvance=90 page=0 chnl=15
char id=70 x=810 y=218 width=67 height=111 xoffset=13 yoffset=43 xadvance=90 page=0 chnl=15
char id=71 x=889 y=218 width=68 height=115 xoffset=12 yoffset=41 xadvance=90 page=0 chnl=15
char id=72 x=969 y=218 width=65 height=111 xoffset=13 yoffset=43 xadvance=90 page=0 chnl=15
char id=73 x=1046 y=218 width=61 height=111 xoffset=15 yoffset=43 xadvance=90 page=0 chnl=15
char id=74 x=1119 y=218 width=70 height=113 xoffset=6 yoffset=43 xadvance=90 page=0 chnl=15
char id=75 x=1201 y=218 width=73 height=111 xoffset=13 yoffset=43 xadvance=90 page=0 chnl=15
char id=76 x=1286 y=218 width=65 height=111 xoffset=19 yoffset=43 xadvance=90 page=0 chnl=15
char id=77 x=1363 y=218 width=71 height=111 xoffset=10 yoffset=43 xadvance=90 page=0 chnl=15
char id=78 x=1446 y=218 width=66 height=111 xoffset=13 yoffset=43 xadvance=90 page=0 chnl=15
char id=79 x=1524 y=218 width=67 height=115 xoffset=12 yoffset=41 xadvance=90 page=0 chnl=15
char id=80 x=1603 y=218 width=71 height=111 xoffset=13 yoffset=43 xadvance=90 page=0 chnl=15
char id=81 x=1686 y=218 width=70 height=141 xoffset=11 yoffset=41 xadvance=90 page=0 chnl=15
char id=82 x=1768 y=218 width=71 height=111 xoffset=13 yoffset=43 xadvance=90 page=0 chnl=15
char id=83 x=1851 y=218 width=71 height=115 xoffset=10 yoffset=41 xadvance=90 page=0 chnl=15
char id=84 x=1934 y=218 width=75 height=111 xoffset=8 yoffset=43 xadvance=90 page=0 chnl=15
char id=85 x=6 y=430 width=66 height=113 xoffset=13 yoffset=43 xadvance=90 page=0 chnl=15
char id=86 x=84 y=430 width=78 height=111 xoffset=6 yoffset=43 xadvance=90 page=0 chnl=15
char id=87 x=174 y=430 width=85 height=111 xoffset=3 yoffset=43 xadvance=90 page=0 chnl=15
char id=88 x=271 y=430 width=81 height=111 xoffset=5 yoffset=43 xadvance=90 page=0 chnl=15
char id=89 x=364 y=430 width=83 height=111 xoffset=4 yoffset=43 xadvance=90 page=0 chnl=15
char id=90 x=459 y=430 width=67 height=111 xoffset=12 yoffset=43 xadvance=90 page=0 chnl=15
char id=91 x=538 y=430 width=39 height=143 xoffset=30 yoffset=28 xadvance=90 page=0 chnl=15
char id=92 x=589 y=430 width=71 height=143 xoffset=10 yoffset=28 xadvance=90 page=0 chnl=15
char id=93 x=672 y=430 width=39 height=143 xoffset=22 yoffset=28 xadvance=90 page=0 chnl=15
char id=94 x=723 y=430 width=69 height=60 xoffset=11 yoffset=43 xadvance=90 page=0 chnl=15
char id=95 x=804 y=430 width=73 height=14 xoffset=9 yoffset=157 xadvance=90 page=0 chnl=15
char id=96 x=889 y=430 width=34 height=22 xoffset=23 yoffset=35 xadvance=90 page=0 chnl=15
char id=97 x=935 y=430 width=69 height=87 xoffset=9 yoffset=69 xadvance=90 page=0 chnl=15
char id=98 x=1016 y=430 width=66 height=113 xoffset=13 yoffset=43 xadvance=90 page=0 chnl=15
char id=99 x=1094 y=430 width=68 height=87 xoffset=12 yoffset=69 xadvance=90 page=0 chnl=15
char id=100 x=1174 y=430 width=66 height=113 xoffset=12 yoffset=43 xadvance=90 page=0 chnl=15
char id=101 x=1252 y=430 width=67 height=87 xoffset=12 yoffset=69 xadvance=90 page=0 chnl=15
char id=102 x=1331 y=430 width=74 height=111 xoffset=8 yoffset=43 xadvance=90 page=0 chnl=15
char id=103 x=1417 y=430 width=66 height=113 xoffset=12 yoffset=69 xadvance=90 page=0 chnl=15
char id=104 x=1495 y=430 width=65 height=111 xoffset=13 yoffset=43 xadvance=90 page=0 chnl=15
char id=105 x=1572 y=430 width=73 height=119 xoffset=12 yoffset=35 xadvance=90 page=0 chnl=15
char id=106 x=1657 y=430 width=60 height=147 xoffset=10 yoffset=35 xadvance=90 page=0 chnl=15
char id=107 x=1729 y=430 width=72 height=111 xoffset=13 yoffset=43 xadvance=90 page=0 chnl=15
char id=108 x=1813 y=430 width=80 height=111 xoffset=4 yoffset=43 xadvance=90 page=0 chnl=15
char id=109 x=1905 y=430 width=75 height=85 xoffset=8 yoffset=69 xadvance=90 page=0 chnl=15
char id=110 x=6 y=642 width=65 height=85 xoffset=13 yoffset=69 xadvance=90 page=0 chnl=15
char id=111 x=83 y=642 width=67 height=87 xoffset=12 yoffset=69 xadvance=90 page=0 chnl=15
char id=112 x=162 y=642 width=66 height=113 xoffset=13 yoffset=69 xadvance=90 page=0 chnl=15
char id=113 x=240 y=642 width=66 height=113 xoffset=12 yoffset=69 xadvance=90 page=0 chnl=15
char id=114 x=318 y=642 width=67 height=85 xoffset=15 yoffset=69 xadvance=90 page=0 chnl=15
char id=115 x=397 y=642 width=68 height=87 xoffset=12 yoffset=69 xadvance=90 page=0 chnl=15
char id=116 x=477 y=642 width=73 height=107 xoffset=7 yoffset=47 xadvance=90 page=0 chnl=15
char id=117 x=562 y=642 width=65 height=86 xoffset=13 yoffset=70 xadvance=90 page=0 chnl=15
char id=118 x=639 y=642 width=79 height=84 xoffset=6 yoffset=70 xadvance=90 page=0 chnl=15
char id=119 x=730 y=642 width=83 height=84 xoffset=4 yoffset=70 xadvance=90 page=0 chnl=15
char id=120 x=825 y=642 width=78 height=84 xoffset=6 yoffset=70 xadvance=90 page=0 chnl=15
char id=121 x=915 y=642 width=79 height=112 xoffset=6 yoffset=70 xadvance=90 page=0 chnl=15
char id=122 x=1006 y=642 width=65 height=84 xoffset=13 yoffset=70 xadvance=90 page=0 chnl=15
char id=123 x=1083 y=642 width=66 height=143 xoffset=11 yoffset=28 xadvance=90 page=0 chnl=15
char id=124 x=1161 y=642 width=16 height=143 xoffset=38 yoffset=28 xadvance=90 page=0 chnl=15
char id=125 x=1189 y=642 width=66 height=143 xoffset=14 yoffset=28 xadvance=90 page=0 chnl=15
char id=126 x=1267 y=642 width=72 height=33 xoffset=10 yoffset=84 xadvance=90 page=0 chnl=15
char id=163 x=1351 y=642 width=71 height=113 xoffset=11 yoffset=41 xadvance=90 page=0 chnl=15
char id=165 x=1434 y=642 width=83 height=111 xoffset=4 yoffset=43 xadvance=90 page=0 chnl=15
char id=167 x=1529 y=642 width=70 height=137 xoffset=10 yoffset=41 xadvance=90 page=0 chnl=15
char id=176 x=1611 y=642 width=47 height=45 xoffset=22 yoffset=41 xadvance=90 page=0 chnl=15
char id=178 x=1670 y=642 width=48 height=67 xoffset=22 yoffset=26 xadvance=90 page=0 chnl=15
char id=215 x=1730 y=642 width=61 height=62 xoffset=15 yoffset=73 xadvance=90 page=0 chnl=15
char id=231 x=1803 y=642 width=68 height=117 xoffset=12 yoffset=69 xadvance=90 page=0 chnl=15
char id=234 x=1883 y=642 width=67 height=121 xoffset=12 yoffset=35 xadvance=90 page=0 chnl=15
char id=241 x=1962 y=642 width=65 height=119 xoffset=13 yoffset=35 xadvance=90 page=0 chnl=15
char id=252 x=6 y=854 width=65 height=121 xoffset=13 yoffset=35 xadvance=90 page=0 chnl=15
char id=8209 x=83 y=854 width=0 height=0 xoffset=0 yoffset=0 xadvance=0 page=0 chnl=15
char id=8211 x=95 y=854 width=68 height=14 xoffset=11 yoffset=97 xadvance=90 page=0 chnl=15
char id=8226 x=175 y=854 width=38 height=38 xoffset=26 yoffset=80 xadvance=90 page=0 chnl=15
char id=8364 x=225 y=854 width=81 height=113 xoffset=2 yoffset=43 xadvance=90 page=0 chnl=15
char id=8627 x=318 y=854 width=0 height=0 xoffset=0 yoffset=0 xadvance=0 page=0 chnl=15
char id=8679 x=330 y=854 width=87 height=115 xoffset=2 yoffset=39 xadvance=90 page=0 chnl=15
char id=9003 x=429 y=854 width=0 height=0 xoffset=0 yoffset=0 xadvance=0 page=0 chnl=15
char id=9251 x=441 y=854 width=0 height=0 xoffset=0 yoffset=0 xadvance=0 page=0 chnl=15
char id=9654 x=453 y=854 width=93 height=95 xoffset=0 yoffset=52 xadvance=90 page=0 chnl=15
char id=9664 x=558 y=854 width=93 height=95 xoffset=-2 yoffset=52 xadvance=90 page=0 chnl=15
char id=9675 x=663 y=854 width=0 height=0 xoffset=0 yoffset=0 xadvance=0 page=0 chnl=15
char id=9679 x=675 y=854 width=95 height=95 xoffset=-2 yoffset=52 xadvance=90 page=0 chnl=15
char id=9881 x=782 y=854 width=0 height=0 xoffset=0 yoffset=0 xadvance=0 page=0 chnl=15
char id=10003 x=794 y=854 width=81 height=79 xoffset=5 yoffset=59 xadvance=90 page=0 chnl=15
char id=10004 x=887 y=854 width=0 height=0 xoffset=0 yoffset=0 xadvance=0 page=0 chnl=15
char id=10005 x=899 y=854 width=73 height=73 xoffset=9 yoffset=67 xadvance=90 page=0 chnl=15
Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

+17 -13
View File
@@ -10,7 +10,7 @@ TRANSLATIONS_DIR = SELFDRIVE_DIR / "ui" / "translations"
LANGUAGES_FILE = TRANSLATIONS_DIR / "languages.json"
GLYPH_PADDING = 6
EXTRA_CHARS = "–‑✓×°§•X⚙✕◀▶✔⌫⇧␣○●↳çêüñ–‑✓×°§•€£¥"
EXTRA_CHARS = "–‑✓×°§•X⚙✕◀▶✔⌫⇧␣○●↳çêüñ–‑✓×°§•€£¥²"
UNIFONT_LANGUAGES = {"ar", "th", "zh-CHT", "zh-CHS", "ko", "ja"}
@@ -48,16 +48,18 @@ def _glyph_metrics(glyphs, rects, codepoints):
offset_y = int(round(glyph.offsetY))
min_offset_y = offset_y if min_offset_y is None else min(min_offset_y, offset_y)
max_extent = max(max_extent, offset_y + height)
entries.append({
"id": codepoint,
"x": int(round(rect.x)),
"y": int(round(rect.y)),
"width": width,
"height": height,
"xoffset": int(round(glyph.offsetX)),
"yoffset": offset_y,
"xadvance": int(round(glyph.advanceX)),
})
entries.append(
{
"id": codepoint,
"x": int(round(rect.x)),
"y": int(round(rect.y)),
"width": width,
"height": height,
"xoffset": int(round(glyph.offsetX)),
"yoffset": offset_y,
"xadvance": int(round(glyph.advanceX)),
}
)
if min_offset_y is None:
raise RuntimeError("No glyphs were generated")
@@ -80,8 +82,10 @@ def _write_bmfont(path: Path, font_size: int, face: str, atlas_name: str, line_h
]
for entry in entries:
lines.append(
("char id={id:<4} x={x:<5} y={y:<5} width={width:<5} height={height:<5} " +
"xoffset={xoffset:<5} yoffset={yoffset:<5} xadvance={xadvance:<5} page=0 chnl=15").format(**entry)
(
"char id={id:<4} x={x:<5} y={y:<5} width={width:<5} height={height:<5} "
+ "xoffset={xoffset:<5} yoffset={yoffset:<5} xadvance={xadvance:<5} page=0 chnl=15"
).format(**entry)
)
path.write_text("\n".join(lines) + "\n")
+157
View File
@@ -0,0 +1,157 @@
info face="unifont" size=-16 bold=0 italic=0 charset="" unicode=1 stretchH=100 smooth=0 aa=1 padding=0,0,0,0 spacing=0,0 outline=0
common lineHeight=16 base=16 scaleW=512 scaleH=256 pages=1 packed=0 alphaChnl=0 redChnl=4 greenChnl=4 blueChnl=4
page id=0 file="unifont.png"
chars count=153
char id=32 x=6 y=6 width=8 height=16 xoffset=0 yoffset=0 xadvance=8 page=0 chnl=15
char id=33 x=26 y=6 width=1 height=10 xoffset=4 yoffset=4 xadvance=8 page=0 chnl=15
char id=34 x=39 y=6 width=5 height=4 xoffset=2 yoffset=2 xadvance=8 page=0 chnl=15
char id=35 x=56 y=6 width=6 height=10 xoffset=1 yoffset=4 xadvance=8 page=0 chnl=15
char id=36 x=74 y=6 width=7 height=10 xoffset=1 yoffset=4 xadvance=8 page=0 chnl=15
char id=37 x=93 y=6 width=7 height=10 xoffset=1 yoffset=4 xadvance=8 page=0 chnl=15
char id=38 x=112 y=6 width=7 height=10 xoffset=1 yoffset=4 xadvance=8 page=0 chnl=15
char id=39 x=131 y=6 width=1 height=4 xoffset=4 yoffset=2 xadvance=8 page=0 chnl=15
char id=40 x=144 y=6 width=3 height=12 xoffset=3 yoffset=3 xadvance=8 page=0 chnl=15
char id=41 x=159 y=6 width=3 height=12 xoffset=2 yoffset=3 xadvance=8 page=0 chnl=15
char id=42 x=174 y=6 width=7 height=7 xoffset=1 yoffset=6 xadvance=8 page=0 chnl=15
char id=43 x=193 y=6 width=7 height=7 xoffset=1 yoffset=6 xadvance=8 page=0 chnl=15
char id=44 x=212 y=6 width=2 height=4 xoffset=3 yoffset=12 xadvance=8 page=0 chnl=15
char id=45 x=226 y=6 width=4 height=1 xoffset=2 yoffset=9 xadvance=8 page=0 chnl=15
char id=46 x=242 y=6 width=2 height=2 xoffset=3 yoffset=12 xadvance=8 page=0 chnl=15
char id=47 x=256 y=6 width=6 height=10 xoffset=1 yoffset=4 xadvance=8 page=0 chnl=15
char id=48 x=274 y=6 width=6 height=10 xoffset=1 yoffset=4 xadvance=8 page=0 chnl=15
char id=49 x=292 y=6 width=5 height=10 xoffset=2 yoffset=4 xadvance=8 page=0 chnl=15
char id=50 x=309 y=6 width=6 height=10 xoffset=1 yoffset=4 xadvance=8 page=0 chnl=15
char id=51 x=327 y=6 width=6 height=10 xoffset=1 yoffset=4 xadvance=8 page=0 chnl=15
char id=52 x=345 y=6 width=6 height=10 xoffset=1 yoffset=4 xadvance=8 page=0 chnl=15
char id=53 x=363 y=6 width=6 height=10 xoffset=1 yoffset=4 xadvance=8 page=0 chnl=15
char id=54 x=381 y=6 width=6 height=10 xoffset=1 yoffset=4 xadvance=8 page=0 chnl=15
char id=55 x=399 y=6 width=6 height=10 xoffset=1 yoffset=4 xadvance=8 page=0 chnl=15
char id=56 x=417 y=6 width=6 height=10 xoffset=1 yoffset=4 xadvance=8 page=0 chnl=15
char id=57 x=435 y=6 width=6 height=10 xoffset=1 yoffset=4 xadvance=8 page=0 chnl=15
char id=58 x=453 y=6 width=2 height=7 xoffset=3 yoffset=6 xadvance=8 page=0 chnl=15
char id=59 x=467 y=6 width=2 height=9 xoffset=3 yoffset=6 xadvance=8 page=0 chnl=15
char id=60 x=481 y=6 width=5 height=9 xoffset=2 yoffset=5 xadvance=8 page=0 chnl=15
char id=61 x=6 y=34 width=6 height=5 xoffset=1 yoffset=7 xadvance=8 page=0 chnl=15
char id=62 x=24 y=34 width=5 height=9 xoffset=1 yoffset=5 xadvance=8 page=0 chnl=15
char id=63 x=41 y=34 width=6 height=10 xoffset=1 yoffset=4 xadvance=8 page=0 chnl=15
char id=64 x=59 y=34 width=6 height=10 xoffset=1 yoffset=4 xadvance=8 page=0 chnl=15
char id=65 x=77 y=34 width=6 height=10 xoffset=1 yoffset=4 xadvance=8 page=0 chnl=15
char id=66 x=95 y=34 width=6 height=10 xoffset=1 yoffset=4 xadvance=8 page=0 chnl=15
char id=67 x=113 y=34 width=6 height=10 xoffset=1 yoffset=4 xadvance=8 page=0 chnl=15
char id=68 x=131 y=34 width=6 height=10 xoffset=1 yoffset=4 xadvance=8 page=0 chnl=15
char id=69 x=149 y=34 width=6 height=10 xoffset=1 yoffset=4 xadvance=8 page=0 chnl=15
char id=70 x=167 y=34 width=6 height=10 xoffset=1 yoffset=4 xadvance=8 page=0 chnl=15
char id=71 x=185 y=34 width=6 height=10 xoffset=1 yoffset=4 xadvance=8 page=0 chnl=15
char id=72 x=203 y=34 width=6 height=10 xoffset=1 yoffset=4 xadvance=8 page=0 chnl=15
char id=73 x=221 y=34 width=5 height=10 xoffset=2 yoffset=4 xadvance=8 page=0 chnl=15
char id=74 x=238 y=34 width=7 height=10 xoffset=1 yoffset=4 xadvance=8 page=0 chnl=15
char id=75 x=257 y=34 width=6 height=10 xoffset=1 yoffset=4 xadvance=8 page=0 chnl=15
char id=76 x=275 y=34 width=6 height=10 xoffset=1 yoffset=4 xadvance=8 page=0 chnl=15
char id=77 x=293 y=34 width=6 height=10 xoffset=1 yoffset=4 xadvance=8 page=0 chnl=15
char id=78 x=311 y=34 width=6 height=10 xoffset=1 yoffset=4 xadvance=8 page=0 chnl=15
char id=79 x=329 y=34 width=6 height=10 xoffset=1 yoffset=4 xadvance=8 page=0 chnl=15
char id=80 x=347 y=34 width=6 height=10 xoffset=1 yoffset=4 xadvance=8 page=0 chnl=15
char id=81 x=365 y=34 width=7 height=11 xoffset=1 yoffset=4 xadvance=8 page=0 chnl=15
char id=82 x=384 y=34 width=6 height=10 xoffset=1 yoffset=4 xadvance=8 page=0 chnl=15
char id=83 x=402 y=34 width=6 height=10 xoffset=1 yoffset=4 xadvance=8 page=0 chnl=15
char id=84 x=420 y=34 width=7 height=10 xoffset=1 yoffset=4 xadvance=8 page=0 chnl=15
char id=85 x=439 y=34 width=6 height=10 xoffset=1 yoffset=4 xadvance=8 page=0 chnl=15
char id=86 x=457 y=34 width=7 height=10 xoffset=1 yoffset=4 xadvance=8 page=0 chnl=15
char id=87 x=476 y=34 width=6 height=10 xoffset=1 yoffset=4 xadvance=8 page=0 chnl=15
char id=88 x=6 y=62 width=6 height=10 xoffset=1 yoffset=4 xadvance=8 page=0 chnl=15
char id=89 x=24 y=62 width=7 height=10 xoffset=1 yoffset=4 xadvance=8 page=0 chnl=15
char id=90 x=43 y=62 width=6 height=10 xoffset=1 yoffset=4 xadvance=8 page=0 chnl=15
char id=91 x=61 y=62 width=3 height=12 xoffset=4 yoffset=3 xadvance=8 page=0 chnl=15
char id=92 x=76 y=62 width=6 height=10 xoffset=1 yoffset=4 xadvance=8 page=0 chnl=15
char id=93 x=94 y=62 width=3 height=12 xoffset=1 yoffset=3 xadvance=8 page=0 chnl=15
char id=94 x=109 y=62 width=6 height=3 xoffset=1 yoffset=2 xadvance=8 page=0 chnl=15
char id=95 x=127 y=62 width=7 height=1 xoffset=1 yoffset=14 xadvance=8 page=0 chnl=15
char id=96 x=146 y=62 width=3 height=3 xoffset=2 yoffset=1 xadvance=8 page=0 chnl=15
char id=97 x=161 y=62 width=6 height=8 xoffset=1 yoffset=6 xadvance=8 page=0 chnl=15
char id=98 x=179 y=62 width=6 height=11 xoffset=1 yoffset=3 xadvance=8 page=0 chnl=15
char id=99 x=197 y=62 width=6 height=8 xoffset=1 yoffset=6 xadvance=8 page=0 chnl=15
char id=100 x=215 y=62 width=6 height=11 xoffset=1 yoffset=3 xadvance=8 page=0 chnl=15
char id=101 x=233 y=62 width=6 height=8 xoffset=1 yoffset=6 xadvance=8 page=0 chnl=15
char id=102 x=251 y=62 width=5 height=11 xoffset=1 yoffset=3 xadvance=8 page=0 chnl=15
char id=103 x=268 y=62 width=6 height=11 xoffset=1 yoffset=5 xadvance=8 page=0 chnl=15
char id=104 x=286 y=62 width=6 height=11 xoffset=1 yoffset=3 xadvance=8 page=0 chnl=15
char id=105 x=304 y=62 width=5 height=11 xoffset=2 yoffset=3 xadvance=8 page=0 chnl=15
char id=106 x=321 y=62 width=5 height=13 xoffset=1 yoffset=3 xadvance=8 page=0 chnl=15
char id=107 x=338 y=62 width=6 height=11 xoffset=1 yoffset=3 xadvance=8 page=0 chnl=15
char id=108 x=356 y=62 width=5 height=11 xoffset=2 yoffset=3 xadvance=8 page=0 chnl=15
char id=109 x=373 y=62 width=7 height=8 xoffset=1 yoffset=6 xadvance=8 page=0 chnl=15
char id=110 x=392 y=62 width=6 height=8 xoffset=1 yoffset=6 xadvance=8 page=0 chnl=15
char id=111 x=410 y=62 width=6 height=8 xoffset=1 yoffset=6 xadvance=8 page=0 chnl=15
char id=112 x=428 y=62 width=6 height=10 xoffset=1 yoffset=6 xadvance=8 page=0 chnl=15
char id=113 x=446 y=62 width=6 height=10 xoffset=1 yoffset=6 xadvance=8 page=0 chnl=15
char id=114 x=464 y=62 width=6 height=8 xoffset=1 yoffset=6 xadvance=8 page=0 chnl=15
char id=115 x=482 y=62 width=6 height=8 xoffset=1 yoffset=6 xadvance=8 page=0 chnl=15
char id=116 x=6 y=90 width=5 height=10 xoffset=1 yoffset=4 xadvance=8 page=0 chnl=15
char id=117 x=23 y=90 width=6 height=8 xoffset=1 yoffset=6 xadvance=8 page=0 chnl=15
char id=118 x=41 y=90 width=6 height=8 xoffset=1 yoffset=6 xadvance=8 page=0 chnl=15
char id=119 x=59 y=90 width=7 height=8 xoffset=1 yoffset=6 xadvance=8 page=0 chnl=15
char id=120 x=78 y=90 width=6 height=8 xoffset=1 yoffset=6 xadvance=8 page=0 chnl=15
char id=121 x=96 y=90 width=6 height=10 xoffset=1 yoffset=6 xadvance=8 page=0 chnl=15
char id=122 x=114 y=90 width=6 height=8 xoffset=1 yoffset=6 xadvance=8 page=0 chnl=15
char id=123 x=132 y=90 width=4 height=13 xoffset=2 yoffset=3 xadvance=8 page=0 chnl=15
char id=124 x=148 y=90 width=1 height=14 xoffset=4 yoffset=2 xadvance=8 page=0 chnl=15
char id=125 x=161 y=90 width=4 height=13 xoffset=2 yoffset=3 xadvance=8 page=0 chnl=15
char id=126 x=177 y=90 width=7 height=3 xoffset=1 yoffset=3 xadvance=8 page=0 chnl=15
char id=163 x=196 y=90 width=7 height=10 xoffset=1 yoffset=4 xadvance=8 page=0 chnl=15
char id=165 x=215 y=90 width=7 height=10 xoffset=1 yoffset=4 xadvance=8 page=0 chnl=15
char id=167 x=234 y=90 width=6 height=10 xoffset=1 yoffset=4 xadvance=8 page=0 chnl=15
char id=176 x=252 y=90 width=4 height=4 xoffset=2 yoffset=4 xadvance=8 page=0 chnl=15
char id=178 x=268 y=90 width=5 height=7 xoffset=1 yoffset=3 xadvance=8 page=0 chnl=15
char id=215 x=285 y=90 width=6 height=5 xoffset=1 yoffset=7 xadvance=8 page=0 chnl=15
char id=231 x=303 y=90 width=6 height=10 xoffset=1 yoffset=6 xadvance=8 page=0 chnl=15
char id=234 x=321 y=90 width=6 height=12 xoffset=1 yoffset=2 xadvance=8 page=0 chnl=15
char id=241 x=339 y=90 width=6 height=12 xoffset=1 yoffset=2 xadvance=8 page=0 chnl=15
char id=252 x=357 y=90 width=6 height=12 xoffset=1 yoffset=2 xadvance=8 page=0 chnl=15
char id=1059 x=375 y=90 width=7 height=10 xoffset=1 yoffset=4 xadvance=8 page=0 chnl=15
char id=1072 x=394 y=90 width=6 height=8 xoffset=1 yoffset=6 xadvance=8 page=0 chnl=15
char id=1082 x=412 y=90 width=6 height=8 xoffset=1 yoffset=6 xadvance=8 page=0 chnl=15
char id=1085 x=430 y=90 width=6 height=8 xoffset=1 yoffset=6 xadvance=8 page=0 chnl=15
char id=1088 x=448 y=90 width=6 height=10 xoffset=1 yoffset=6 xadvance=8 page=0 chnl=15
char id=1089 x=466 y=90 width=6 height=8 xoffset=1 yoffset=6 xadvance=8 page=0 chnl=15
char id=1100 x=484 y=90 width=6 height=8 xoffset=1 yoffset=6 xadvance=8 page=0 chnl=15
char id=1111 x=6 y=118 width=5 height=11 xoffset=2 yoffset=3 xadvance=8 page=0 chnl=15
char id=1575 x=23 y=118 width=1 height=8 xoffset=4 yoffset=3 xadvance=8 page=0 chnl=15
char id=1576 x=36 y=118 width=6 height=7 xoffset=1 yoffset=7 xadvance=8 page=0 chnl=15
char id=1577 x=54 y=118 width=4 height=7 xoffset=2 yoffset=4 xadvance=8 page=0 chnl=15
char id=1585 x=70 y=118 width=6 height=7 xoffset=1 yoffset=8 xadvance=8 page=0 chnl=15
char id=1593 x=88 y=118 width=6 height=9 xoffset=1 yoffset=7 xadvance=8 page=0 chnl=15
char id=1604 x=106 y=118 width=6 height=11 xoffset=1 yoffset=3 xadvance=8 page=0 chnl=15
char id=1610 x=124 y=118 width=6 height=8 xoffset=1 yoffset=8 xadvance=8 page=0 chnl=15
char id=3607 x=142 y=118 width=6 height=8 xoffset=1 yoffset=6 xadvance=8 page=0 chnl=15
char id=3618 x=160 y=118 width=6 height=8 xoffset=1 yoffset=6 xadvance=8 page=0 chnl=15
char id=3652 x=178 y=118 width=6 height=11 xoffset=1 yoffset=3 xadvance=8 page=0 chnl=15
char id=8209 x=196 y=118 width=4 height=1 xoffset=2 yoffset=9 xadvance=8 page=0 chnl=15
char id=8211 x=212 y=118 width=6 height=1 xoffset=1 yoffset=9 xadvance=8 page=0 chnl=15
char id=8226 x=230 y=118 width=5 height=5 xoffset=1 yoffset=7 xadvance=8 page=0 chnl=15
char id=8364 x=247 y=118 width=6 height=10 xoffset=1 yoffset=4 xadvance=8 page=0 chnl=15
char id=8627 x=265 y=118 width=5 height=12 xoffset=2 yoffset=0 xadvance=8 page=0 chnl=15
char id=8679 x=282 y=118 width=7 height=12 xoffset=1 yoffset=2 xadvance=8 page=0 chnl=15
char id=9003 x=301 y=118 width=8 height=5 xoffset=0 yoffset=7 xadvance=8 page=0 chnl=15
char id=9251 x=321 y=118 width=6 height=2 xoffset=1 yoffset=12 xadvance=8 page=0 chnl=15
char id=9654 x=339 y=118 width=6 height=6 xoffset=1 yoffset=6 xadvance=8 page=0 chnl=15
char id=9664 x=357 y=118 width=6 height=6 xoffset=1 yoffset=6 xadvance=8 page=0 chnl=15
char id=9675 x=375 y=118 width=7 height=7 xoffset=1 yoffset=5 xadvance=8 page=0 chnl=15
char id=9679 x=394 y=118 width=7 height=7 xoffset=1 yoffset=5 xadvance=8 page=0 chnl=15
char id=9881 x=413 y=118 width=13 height=13 xoffset=1 yoffset=2 xadvance=16 page=0 chnl=15
char id=10003 x=438 y=118 width=7 height=10 xoffset=1 yoffset=4 xadvance=8 page=0 chnl=15
char id=10004 x=457 y=118 width=15 height=11 xoffset=0 yoffset=4 xadvance=16 page=0 chnl=15
char id=10005 x=484 y=118 width=11 height=11 xoffset=2 yoffset=2 xadvance=16 page=0 chnl=15
char id=20013 x=6 y=146 width=11 height=16 xoffset=2 yoffset=0 xadvance=16 page=0 chnl=15
char id=20307 x=29 y=146 width=15 height=16 xoffset=0 yoffset=0 xadvance=16 page=0 chnl=15
char id=25991 x=56 y=146 width=15 height=16 xoffset=0 yoffset=0 xadvance=16 page=0 chnl=15
char id=26085 x=83 y=146 width=9 height=15 xoffset=3 yoffset=1 xadvance=16 page=0 chnl=15
char id=26412 x=104 y=146 width=15 height=16 xoffset=0 yoffset=0 xadvance=16 page=0 chnl=15
char id=31616 x=131 y=146 width=15 height=16 xoffset=0 yoffset=0 xadvance=16 page=0 chnl=15
char id=32321 x=158 y=146 width=15 height=16 xoffset=0 yoffset=0 xadvance=16 page=0 chnl=15
char id=35486 x=185 y=146 width=15 height=16 xoffset=0 yoffset=0 xadvance=16 page=0 chnl=15
char id=39636 x=212 y=146 width=15 height=16 xoffset=0 yoffset=0 xadvance=16 page=0 chnl=15
char id=44397 x=239 y=146 width=15 height=14 xoffset=1 yoffset=2 xadvance=16 page=0 chnl=15
char id=50612 x=266 y=146 width=12 height=14 xoffset=2 yoffset=2 xadvance=16 page=0 chnl=15
char id=54620 x=290 y=146 width=15 height=14 xoffset=1 yoffset=2 xadvance=16 page=0 chnl=15
char id=65288 x=317 y=146 width=5 height=14 xoffset=7 yoffset=1 xadvance=16 page=0 chnl=15
char id=65289 x=334 y=146 width=5 height=14 xoffset=3 yoffset=1 xadvance=16 page=0 chnl=15
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 565 KiB

After

Width:  |  Height:  |  Size: 418 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 418 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

+14 -3
View File
@@ -46,6 +46,7 @@ class CarSpecificEvents:
self.steering_unpressed = 0
self.low_speed_alert = False
self.gm_low_speed_alert_shown = False
self.no_steer_warning = False
self.silent_steer_warning = True
@@ -111,7 +112,17 @@ class CarSpecificEvents:
events.add(EventName.manualRestart)
elif self.CP.brand == 'gm':
events = self.create_common_events(CS, CS_prev, extra_gears=extra_gears, pcm_enable=self.CP.pcmCruise)
events = self.create_common_events(CS, CS_prev, extra_gears=extra_gears, pcm_enable=self.CP.pcmCruise,
suppress_low_speed_alert=True)
# StarPilot behavior: show the low-speed steer alert once per ignition cycle.
if (not CS.cruiseState.available and not CS_prev.cruiseState.available and
CS.vEgo < 0.1 and not CC.enabled):
self.gm_low_speed_alert_shown = False
if CS.lowSpeedAlert and not self.gm_low_speed_alert_shown:
events.add(EventName.belowSteerSpeed)
self.gm_low_speed_alert_shown = True
# Enabling at a standstill with brake is allowed
# TODO: verify 17 Volt can enable for the first time at a stop and allow for all GMs
@@ -147,7 +158,7 @@ class CarSpecificEvents:
return events
def create_common_events(self, CS: structs.CarState, CS_prev: car.CarState, extra_gears: list | None = None, pcm_enable=True,
allow_button_cancel=True):
allow_button_cancel=True, suppress_low_speed_alert=False):
events = Events()
if CS.doorOpen:
@@ -191,7 +202,7 @@ class CarSpecificEvents:
events.add(EventName.vehicleSensorsInvalid)
if CS.invalidLkasSetting:
events.add(EventName.invalidLkasSetting)
if CS.lowSpeedAlert:
if CS.lowSpeedAlert and not suppress_low_speed_alert:
events.add(EventName.belowSteerSpeed)
if CS.buttonEnable:
events.add(EventName.buttonEnable)
+2
View File
@@ -177,6 +177,8 @@ class Car:
if self.frogpilot_toggles.always_on_lateral:
self.FPCP.alternativeExperience |= ALTERNATIVE_EXPERIENCE.ALWAYS_ON_LATERAL
if getattr(self.frogpilot_toggles, "remap_cancel_to_distance", False):
self.FPCP.alternativeExperience |= ALTERNATIVE_EXPERIENCE.GM_REMAP_CANCEL_TO_DISTANCE
fpcp_bytes = self.FPCP.to_bytes()
self.params.put("FrogPilotCarParams", fpcp_bytes)
+2 -2
View File
@@ -141,12 +141,12 @@ class VCruiseHelper:
if self.CP.pcmCruise and not self.gm_cc_only:
return
initial = V_CRUISE_INITIAL_EXPERIMENTAL_MODE if experimental_mode and not frogpilot_toggles.conditional_experimental_mode else V_CRUISE_INITIAL
engage_floor_kph = max(V_CRUISE_MIN, 7.0 * CV.MPH_TO_KPH)
if (any(b.type in (ButtonType.accelCruise, ButtonType.resumeCruise) for b in CS.buttonEvents)
and self.v_cruise_initialized or (self.gm_cc_only and resume_prev_button)):
self.v_cruise_kph = self.v_cruise_kph_last
else:
self.v_cruise_kph = int(round(np.clip(CS.vEgo * CV.MS_TO_KPH, initial, V_CRUISE_MAX)))
self.v_cruise_kph = int(round(np.clip(CS.vEgo * CV.MS_TO_KPH, engage_floor_kph, V_CRUISE_MAX)))
self.v_cruise_cluster_kph = self.v_cruise_kph
+1
View File
@@ -133,6 +133,7 @@ class Controls:
# accel PID loop
pid_accel_limits = self.CI.get_pid_accel_limits(self.CP, CS.vEgo, CS.vCruise * CV.KPH_TO_MS)
self.LoC.experimental_mode = bool(self.sm['selfdriveState'].experimentalMode)
actuators.accel = float(min(self.LoC.update(CC.longActive, CS, long_plan.aTarget, long_plan.shouldStop, pid_accel_limits, self.frogpilot_toggles), self.frogpilot_toggles.max_desired_acceleration))
# Steering PID loop and lateral MPC
+4
View File
@@ -54,6 +54,10 @@ def get_accel_from_plan(speeds, accels, t_idxs, action_t=DT_MDL, vEgoStopping=0.
v_target_1sec < vEgoStopping)
return a_target, should_stop
# Backward-compatible alias used by StarPilot tinygrad_modeld.
get_accel_from_plan_tomb_raider = get_accel_from_plan
def curv_from_psis(psi_target, psi_rate, vego, action_t):
vego = np.clip(vego, MIN_SPEED, np.inf)
curv_from_psi = psi_target / (vego * action_t)
+117 -23
View File
@@ -3,11 +3,13 @@ import numpy as np
from collections import deque
from cereal import log
from opendbc.car.lateral import FRICTION_THRESHOLD, get_friction
from openpilot.common.constants import ACCELERATION_DUE_TO_GRAVITY
from opendbc.car.gm.values import CAR as GM_CAR
from opendbc.car.lateral import get_friction
from openpilot.common.constants import ACCELERATION_DUE_TO_GRAVITY, CV
from openpilot.common.filter_simple import FirstOrderFilter
from openpilot.selfdrive.controls.lib.latcontrol import LatControl
from openpilot.common.pid import PIDController
from openpilot.selfdrive.controls.lib.drive_helpers import MIN_SPEED
from openpilot.selfdrive.controls.lib.latcontrol import LatControl
# At higher speeds (25+mph) we can assume:
# Lateral acceleration achieved by a specific car correlates to
@@ -20,15 +22,46 @@ from openpilot.common.pid import PIDController
# Additionally, there is friction in the steering wheel that needs
# to be overcome to move it at all, this is compensated for too.
KP = 1.0
KI = 0.3
KD = 0.0
KP = 0.7
KI = 0.35
INTERP_SPEEDS = [1, 1.5, 2.0, 3.0, 5, 7.5, 10, 15, 30]
KP_INTERP = [250, 120, 65, 30, 11.5, 5.5, 3.5, 2.0, KP]
LOW_SPEED_X = [0, 10, 20, 30]
LOW_SPEED_Y = [12, 10.5, 8, 5]
MAX_LAT_JERK_UP = 2.5 # m/s^3
LP_FILTER_CUTOFF_HZ = 1.2
JERK_LOOKAHEAD_SECONDS = 0.19
JERK_GAIN = 0.22
LAT_ACCEL_REQUEST_BUFFER_SECONDS = 1.0
VERSION = 0
VERSION = 2
DEBUG_TORQUE_TUNE = False
FF_SCALE_BLEND_LAT_ACCEL = 0.05
DEADZONE_BOOST_LAT_ACCEL = 0.15
UNWIND_D_DES_THRESHOLD = -1.0
UNWIND_LAT_ACCEL_NEAR_ZERO = 0.3
MIN_LATERAL_CONTROL_SPEED = 0.3
BOLT_2022_2023_CARS = (
GM_CAR.CHEVROLET_BOLT_ACC_2022_2023,
GM_CAR.CHEVROLET_BOLT_ACC_2022_2023_PEDAL,
GM_CAR.CHEVROLET_BOLT_CC_2022_2023,
)
BOLT_2018_2021_CARS = (
GM_CAR.CHEVROLET_BOLT_CC_2018_2021,
)
BOLT_2017_CARS = (
GM_CAR.CHEVROLET_BOLT_CC_2017,
)
BOLT_CARS = BOLT_2022_2023_CARS + BOLT_2018_2021_CARS + BOLT_2017_CARS
def get_friction_threshold(v_ego: float) -> float:
# Keep StarPilot's speed-scaled friction threshold behavior.
return float(np.interp(v_ego, [1 * CV.MPH_TO_MS, 20 * CV.MPH_TO_MS, 75 * CV.MPH_TO_MS], [0.16, 0.19, 0.27]))
class LatControlTorque(LatControl):
def __init__(self, CP, CI, dt):
@@ -36,13 +69,41 @@ class LatControlTorque(LatControl):
self.torque_params = CP.lateralTuning.torque.as_builder()
self.torque_from_lateral_accel = CI.torque_from_lateral_accel()
self.lateral_accel_from_torque = CI.lateral_accel_from_torque()
self.pid = PIDController([INTERP_SPEEDS, KP_INTERP], KI, KD, rate=1/self.dt)
self.pid = PIDController([INTERP_SPEEDS, KP_INTERP], KI, rate=1/self.dt)
self.update_limits()
self.steering_angle_deadzone_deg = self.torque_params.steeringAngleDeadzoneDeg
self.lat_accel_request_buffer_len = int(LAT_ACCEL_REQUEST_BUFFER_SECONDS / self.dt)
self.lat_accel_request_buffer = deque([0.] * self.lat_accel_request_buffer_len , maxlen=self.lat_accel_request_buffer_len)
self.lat_accel_request_buffer = deque([0.] * self.lat_accel_request_buffer_len, maxlen=self.lat_accel_request_buffer_len)
self.lookahead_frames = int(JERK_LOOKAHEAD_SECONDS / self.dt)
self.jerk_filter = FirstOrderFilter(0.0, 1 / (2 * np.pi * LP_FILTER_CUTOFF_HZ), self.dt)
self.previous_measurement = 0.0
self.measurement_rate_filter = FirstOrderFilter(0.0, 1 / (2 * np.pi * LP_FILTER_CUTOFF_HZ), self.dt)
self.measurement_rate_filter = FirstOrderFilter(0.0, 1 / (2 * np.pi * (MAX_LAT_JERK_UP - 0.5)), self.dt)
self.low_speed_reset_threshold = max(CP.minSteerSpeed, MIN_LATERAL_CONTROL_SPEED)
self.steer_release_i_decay = 0.8
self.prev_steering_pressed = False
self.debug_counter = 0
self.prev_desired_lateral_accel = 0.0
self.is_bolt = CP.carFingerprint in BOLT_CARS
self.is_bolt_2022_2023 = CP.carFingerprint in BOLT_2022_2023_CARS
self.is_bolt_2018_2021 = CP.carFingerprint in BOLT_2018_2021_CARS
self.is_bolt_2017 = CP.carFingerprint in BOLT_2017_CARS
self.use_bolt_ff_scaling = self.is_bolt_2022_2023 or self.is_bolt_2018_2021 or self.is_bolt_2017
self.use_bolt_ki_multiplier = self.use_bolt_ff_scaling
self.torque_ff_scale_pos = 1.0
self.torque_ff_scale_neg = 1.0
self.torque_deadzone_boost = float(getattr(self.torque_params, "kfDEPRECATED", 0.0))
self.torque_ki_mult = 1.0
if self.is_bolt:
kp_scale = getattr(self.torque_params, "kp", getattr(self.torque_params, "kpDEPRECATED", 1.0))
ki_scale = getattr(self.torque_params, "ki", getattr(self.torque_params, "kiDEPRECATED", 1.0))
kd_scale = getattr(self.torque_params, "kd", getattr(self.torque_params, "kdDEPRECATED", 1.0))
self.torque_ff_scale_pos = float(kp_scale)
self.torque_ff_scale_neg = float(ki_scale)
self.torque_ki_mult = float(kd_scale)
if self.use_bolt_ki_multiplier and self.torque_ki_mult > 0.0 and self.torque_ki_mult != 1.0:
self.pid._k_i = [self.pid._k_i[0], [k * self.torque_ki_mult for k in self.pid._k_i[1]]]
def update_live_torque_params(self, latAccelFactor, latAccelOffset, friction):
self.torque_params.latAccelFactor = latAccelFactor
@@ -60,7 +121,15 @@ class LatControlTorque(LatControl):
if not active:
output_torque = 0.0
pid_log.active = False
self.pid.reset()
self.previous_measurement = 0.0
self.measurement_rate_filter.x = 0.0
self.lat_accel_request_buffer = deque([0.] * self.lat_accel_request_buffer_len, maxlen=self.lat_accel_request_buffer_len)
self.prev_desired_lateral_accel = 0.0
else:
if self.prev_steering_pressed and not CS.steeringPressed:
self.pid.i *= self.steer_release_i_decay
measured_curvature = -VM.calc_curvature(math.radians(CS.steeringAngleDeg - params.angleOffsetDeg), CS.vEgo, params.roll)
roll_compensation = params.roll * ACCELERATION_DUE_TO_GRAVITY
curvature_deadzone = abs(VM.calc_curvature(math.radians(self.steering_angle_deadzone_deg), CS.vEgo, 0.0))
@@ -68,33 +137,50 @@ class LatControlTorque(LatControl):
delay_frames = int(np.clip(lat_delay / self.dt, 1, self.lat_accel_request_buffer_len))
expected_lateral_accel = self.lat_accel_request_buffer[-delay_frames]
# TODO factor out lateral jerk from error to later replace it with delay independent alternative
future_desired_lateral_accel = desired_curvature * CS.vEgo ** 2
self.lat_accel_request_buffer.append(future_desired_lateral_accel)
raw_lateral_jerk = (future_desired_lateral_accel - expected_lateral_accel) / max(lat_delay, self.dt)
raw_lateral_jerk = np.clip(raw_lateral_jerk, -MAX_LAT_JERK_UP, MAX_LAT_JERK_UP)
desired_lateral_jerk = np.clip(self.jerk_filter.update(raw_lateral_jerk), -MAX_LAT_JERK_UP, MAX_LAT_JERK_UP)
gravity_adjusted_future_lateral_accel = future_desired_lateral_accel - roll_compensation
desired_lateral_jerk = (future_desired_lateral_accel - expected_lateral_accel) / lat_delay
setpoint = expected_lateral_accel + desired_lateral_jerk * lat_delay
desired_lateral_accel_rate = (setpoint - self.prev_desired_lateral_accel) / self.dt
unwind_detected = (desired_lateral_accel_rate < UNWIND_D_DES_THRESHOLD and
abs(setpoint) < UNWIND_LAT_ACCEL_NEAR_ZERO)
self.prev_desired_lateral_accel = setpoint
measurement = measured_curvature * CS.vEgo ** 2
measurement_rate = self.measurement_rate_filter.update((measurement - self.previous_measurement) / self.dt)
measurement_rate = np.clip(measurement_rate, -MAX_LAT_JERK_UP, MAX_LAT_JERK_UP)
self.previous_measurement = measurement
setpoint = lat_delay * desired_lateral_jerk + expected_lateral_accel
low_speed_factor = (np.interp(CS.vEgo, LOW_SPEED_X, LOW_SPEED_Y) / max(CS.vEgo, MIN_SPEED)) ** 2
current_kp = np.interp(CS.vEgo, self.pid._k_p[0], self.pid._k_p[1])
error = setpoint - measurement
error_with_lsf = error * (1 + low_speed_factor / max(current_kp, 1e-3))
# do error correction in lateral acceleration space, convert at end to handle non-linear torque responses correctly
pid_log.error = float(error)
pid_log.error = float(error_with_lsf)
ff = gravity_adjusted_future_lateral_accel
# latAccelOffset corrects roll compensation bias from device roll misalignment relative to car roll
ff -= self.torque_params.latAccelOffset
# TODO jerk is weighted by lat_delay for legacy reasons, but should be made independent of it
ff += get_friction(error, lateral_accel_deadzone, FRICTION_THRESHOLD, self.torque_params)
ff_scale = 1.0
if self.use_bolt_ff_scaling:
ff_scale = np.interp(ff, [-FF_SCALE_BLEND_LAT_ACCEL, 0.0, FF_SCALE_BLEND_LAT_ACCEL],
[self.torque_ff_scale_neg, 1.0, self.torque_ff_scale_pos])
ff *= ff_scale
ff += get_friction(error_with_lsf + JERK_GAIN * desired_lateral_jerk, lateral_accel_deadzone, get_friction_threshold(CS.vEgo), self.torque_params)
deadzone_boost_active = False
if self.torque_deadzone_boost > 0.0 and abs(gravity_adjusted_future_lateral_accel) < DEADZONE_BOOST_LAT_ACCEL:
boost_scale = np.interp(abs(gravity_adjusted_future_lateral_accel), [0.0, DEADZONE_BOOST_LAT_ACCEL], [1.0, 0.0])
ff += np.sign(gravity_adjusted_future_lateral_accel) * self.torque_deadzone_boost * boost_scale
deadzone_boost_active = True
freeze_integrator = steer_limited_by_safety or CS.steeringPressed or CS.vEgo < 5
output_lataccel = self.pid.update(pid_log.error,
-measurement_rate,
feedforward=ff,
speed=CS.vEgo,
freeze_integrator=freeze_integrator)
if CS.vEgo < self.low_speed_reset_threshold:
self.pid.reset()
freeze_integrator = (steer_limited_by_safety or CS.steeringPressed or
CS.vEgo < self.low_speed_reset_threshold or unwind_detected)
output_lataccel = self.pid.update(pid_log.error, error_rate=-measurement_rate, speed=CS.vEgo, feedforward=ff, freeze_integrator=freeze_integrator)
output_torque = self.torque_from_lateral_accel(output_lataccel, self.torque_params)
pid_log.active = True
@@ -102,11 +188,19 @@ class LatControlTorque(LatControl):
pid_log.i = float(self.pid.i)
pid_log.d = float(self.pid.d)
pid_log.f = float(self.pid.f)
pid_log.output = float(-output_torque) # TODO: log lat accel?
pid_log.output = float(-output_torque) # TODO: log lat accel?
pid_log.actualLateralAccel = float(measurement)
pid_log.desiredLateralAccel = float(setpoint)
pid_log.desiredLateralJerk = float(desired_lateral_jerk)
pid_log.saturated = bool(self._check_saturation(self.steer_max - abs(output_torque) < 1e-3, CS, steer_limited_by_safety, curvature_limited))
if DEBUG_TORQUE_TUNE and self.is_bolt:
self.debug_counter += 1
if self.debug_counter % 50 == 0:
print(f"bolt_torque ff_scale={ff_scale:.3f} pos={self.torque_ff_scale_pos:.3f} "
f"neg={self.torque_ff_scale_neg:.3f} deadzone_boost_active={deadzone_boost_active}")
self.prev_steering_pressed = CS.steeringPressed
# TODO left is positive in this convention
return -output_torque, 0.0, pid_log
@@ -1,2 +1,4 @@
acados_ocp_lat.json
c_generated_code/
c_generated_code/**
!c_generated_code/acados_ocp_solver_pyx.so
!c_generated_code/libacados_ocp_solver_lat.so
+84 -6
View File
@@ -1,9 +1,11 @@
import numpy as np
from cereal import car
from openpilot.common.realtime import DT_CTRL
from openpilot.common.filter_simple import FirstOrderFilter
from openpilot.selfdrive.controls.lib.drive_helpers import CONTROL_N
from openpilot.common.pid import PIDController
from openpilot.selfdrive.modeld.constants import ModelConstants
from opendbc.car.gm.values import CarControllerParams, GMFlags
CONTROL_N_T_IDX = ModelConstants.T_IDXS[:CONTROL_N]
@@ -12,6 +14,8 @@ LongCtrlState = car.CarControl.Actuators.LongControlState
def long_control_state_trans(CP, active, long_control_state, v_ego,
should_stop, brake_pressed, cruise_standstill, frogpilot_toggles):
# Ignore cruise standstill if car has a gas interceptor.
cruise_standstill = cruise_standstill and not CP.enableGasInterceptorDEPRECATED
stopping_condition = should_stop
starting_condition = (not should_stop and
not cruise_standstill and
@@ -48,13 +52,73 @@ class LongControl:
def __init__(self, CP):
self.CP = CP
self.long_control_state = LongCtrlState.off
self.experimental_mode = False
self.pid = PIDController((CP.longitudinalTuning.kpBP, CP.longitudinalTuning.kpV),
(CP.longitudinalTuning.kiBP, CP.longitudinalTuning.kiV),
rate=1 / DT_CTRL)
# Preserve legacy behavior when no feedforward gain is provided (default of 0.0).
kf = getattr(CP.longitudinalTuning, 'kfDEPRECATED', 0.0)
self.feedforward_gain = kf if kf != 0.0 else 1.0
self._mode_setup()
self.last_output_accel = 0.0
self.last_a_target = 0.0
self.integrator_hold_frames = 0
self.is_gm_pedal_long = bool(
CP.brand == "gm" and CP.enableGasInterceptorDEPRECATED and (CP.flags & GMFlags.PEDAL_LONG.value)
)
def update_mpc_mode(self, experimental_mode: bool):
new_mode = 'blended' if experimental_mode else 'acc'
if self.transitioning and self.prev_mode == 'blended' and self.current_mode == 'acc':
self.mode_transition_timer = 0.0
if new_mode != self.current_mode:
self.prev_mode = self.current_mode
self.transitioning = True
self.mode_transition_timer = 0.0
self.mode_transition_filter.x = self.last_output_accel
self.current_mode = new_mode
if self.transitioning:
self.mode_transition_timer += DT_CTRL
if self.mode_transition_timer >= self.mode_transition_duration:
self.transitioning = False
def _mode_setup(self):
self.prev_mode = 'acc'
self.current_mode = 'acc'
self.mode_transition_filter = FirstOrderFilter(0.0, 0.5, DT_CTRL)
self.mode_transition_timer = 0.0
self.mode_transition_duration = 1.0
self.transitioning = False
def reset(self):
self.pid.reset()
self.last_a_target = 0.0
self.integrator_hold_frames = 0
def _get_pedal_long_freeze(self, a_target, error, v_ego, accel_limits):
if not self.is_gm_pedal_long:
self.last_a_target = a_target
self.integrator_hold_frames = 0
return False
handoff_threshold = np.interp(v_ego, [0.0, 4.0, 12.0, 25.0], [0.35, 0.45, 0.55, 0.70])
if abs(a_target - self.last_a_target) > handoff_threshold:
hold_frames = int(round(np.interp(v_ego, [0.0, 4.0, 12.0, 25.0], [25.0, 20.0, 14.0, 10.0])))
self.integrator_hold_frames = max(self.integrator_hold_frames, hold_frames)
self.last_a_target = a_target
if self.integrator_hold_frames > 0:
self.integrator_hold_frames -= 1
sat_buffer = 0.03
at_neg_sat = self.last_output_accel <= (accel_limits[0] + sat_buffer)
at_pos_sat = self.last_output_accel >= (accel_limits[1] - sat_buffer)
sat_pushing_lower = at_neg_sat and error < -0.05
sat_pushing_upper = at_pos_sat and error > 0.05
return self.integrator_hold_frames > 0 or sat_pushing_lower or sat_pushing_upper
def update(self, active, CS, a_target, should_stop, accel_limits, frogpilot_toggles):
"""Update longitudinal control. This updates the state machine and runs a PID loop"""
@@ -76,16 +140,30 @@ class LongControl:
self.reset()
elif self.long_control_state == LongCtrlState.starting:
if frogpilot_toggles.human_acceleration:
output_accel = max(a_target, frogpilot_toggles.startAccel)
else:
output_accel = frogpilot_toggles.startAccel
output_accel = (a_target if frogpilot_toggles.human_acceleration else frogpilot_toggles.startAccel)
self.reset()
else: # LongCtrlState.pid
error = a_target - CS.aEgo
output_accel = self.pid.update(error, speed=CS.vEgo,
feedforward=a_target)
self.update_mpc_mode(self.experimental_mode)
freeze_integrator = self._get_pedal_long_freeze(a_target, error, CS.vEgo, accel_limits)
raw_output_accel = self.pid.update(error, speed=CS.vEgo,
feedforward=a_target * self.feedforward_gain,
freeze_integrator=freeze_integrator)
if self.transitioning and self.prev_mode == 'acc' and self.current_mode == 'blended':
if raw_output_accel < 0 and raw_output_accel < self.last_output_accel:
progress = min(1.0, self.mode_transition_timer / self.mode_transition_duration)
# Soften transition at low urgency, but keep sharp for high decel.
urgency_denom = max(1e-3, abs(CarControllerParams.ACCEL_MIN))
urgency = abs(raw_output_accel / urgency_denom)
urgency_smooth = min(1.0, urgency ** 0.4)
blend_factor = 1.0 - (1.0 - progress) * (1.0 - urgency_smooth)
output_accel = self.last_output_accel + (raw_output_accel - self.last_output_accel) * blend_factor
else:
output_accel = raw_output_accel
else:
output_accel = raw_output_accel
self.last_output_accel = np.clip(output_accel, accel_limits[0], accel_limits[1])
return self.last_output_accel
@@ -1,2 +1,4 @@
acados_ocp_long.json
c_generated_code/
c_generated_code/**
!c_generated_code/acados_ocp_solver_pyx.so
!c_generated_code/libacados_ocp_solver_long.so
@@ -3,12 +3,21 @@ import os
import time
import numpy as np
from cereal import log
from opendbc.car.interfaces import ACCEL_MIN, ACCEL_MAX
try:
from opendbc.car.interfaces import ACCEL_MIN, ACCEL_MAX
except Exception:
# Build-time fallback for generated-code steps before full python extension availability.
ACCEL_MIN = -3.5
ACCEL_MAX = 2.0
from openpilot.common.constants import CV
from openpilot.common.filter_simple import FirstOrderFilter
from openpilot.common.realtime import DT_MDL
from openpilot.common.swaglog import cloudlog
# WARNING: imports outside of constants will not trigger a rebuild
from openpilot.selfdrive.modeld.constants import index_function
from openpilot.selfdrive.controls.radard import _LEAD_ACCEL_TAU
# Keep in sync with selfdrive/controls/radard.py
_LEAD_ACCEL_TAU = 1.5
if __name__ == '__main__': # generating code
from openpilot.third_party.acados.acados_template import AcadosModel, AcadosOcp, AcadosOcpSolver
@@ -31,12 +40,25 @@ COST_E_DIM = 5
COST_DIM = COST_E_DIM + 1
CONSTR_DIM = 4
X_EGO_OBSTACLE_COST = 3.
# ===== VOACC SPEED-BASED TUNING PARAMETERS =====
# Speed ranges: [0-35, 35-55, 55-70, 70+ mph]
SPEED_BREAKPOINTS = [0, 35, 55, 70]
# [City, Urban Hwy, Rural Hwy, High Speed]
X_EGO_OBSTACLE_COSTS = [3.0, 3.0, 2.5, 2.0]
J_EGO_COSTS = [5.0, 4.75, 4.5, 4.0]
A_CHANGE_COSTS = [200.0, 195.0, 180.0, 170.0]
LEAD_FILTER_TIME_LOW = 0.8
LEAD_FILTER_TIME_HIGH = 1.2
DIST_ADAPTS = [0.04, 0.06, 0.06, 0.05]
# Current active values
X_EGO_OBSTACLE_COST = 2.75
X_EGO_COST = 0.
V_EGO_COST = 0.
A_EGO_COST = 0.
J_EGO_COST = 5.0
A_CHANGE_COST = 200.
J_EGO_COST = 5.5
A_CHANGE_COST = 250.0
DANGER_ZONE_COST = 100.
CRASH_DISTANCE = .25
LEAD_DANGER_FACTOR = 0.75
@@ -58,6 +80,11 @@ STOP_DISTANCE = 6.0
CRUISE_MIN_ACCEL = -1.2
CRUISE_MAX_ACCEL = 1.6
def get_speed_based_param(speed_mph, param_array):
return float(np.interp(speed_mph, SPEED_BREAKPOINTS, param_array))
def get_jerk_factor(aggressive_jerk_acceleration=0.5, aggressive_jerk_danger=0.5, aggressive_jerk_speed=0.5,
standard_jerk_acceleration=1.0, standard_jerk_danger=1.0, standard_jerk_speed=1.0,
relaxed_jerk_acceleration=1.0, relaxed_jerk_danger=1.0, relaxed_jerk_speed=1.0,
@@ -105,13 +132,15 @@ def get_T_FOLLOW(aggressive_follow=1.25, standard_follow=1.45, relaxed_follow=1.
def get_stopped_equivalence_factor(v_lead):
return (v_lead**2) / (2 * COMFORT_BRAKE)
def get_safe_obstacle_distance(v_ego, t_follow):
return (v_ego**2) / (2 * COMFORT_BRAKE) + t_follow * v_ego + STOP_DISTANCE
def desired_follow_distance(v_ego, v_lead, t_follow=None):
def get_safe_obstacle_distance(v_ego, t_follow, stop_distance=STOP_DISTANCE):
return (v_ego**2) / (2 * COMFORT_BRAKE) + t_follow * v_ego + stop_distance
def desired_follow_distance(v_ego, v_lead, t_follow=None, stop_distance=STOP_DISTANCE):
if t_follow is None:
t_follow = get_T_FOLLOW()
return get_safe_obstacle_distance(v_ego, t_follow) - get_stopped_equivalence_factor(v_lead)
return get_safe_obstacle_distance(v_ego, t_follow, stop_distance) - get_stopped_equivalence_factor(v_lead)
def gen_long_model():
@@ -249,9 +278,27 @@ class LongitudinalMpc:
self.mode = mode
self.dt = dt
self.solver = AcadosOcpSolverCython(MODEL_NAME, ACADOS_SOLVER_TYPE, N)
self.reset()
self.source = SOURCES[2]
self.current_filter_time = LEAD_FILTER_TIME_LOW
self.prev_filter_time = self.current_filter_time
self.lead_a_filter = FirstOrderFilter(0.0, self.current_filter_time, self.dt)
self.lead_v_filter = FirstOrderFilter(0.0, self.current_filter_time, self.dt)
self.filter_time_factor = 1.0
self.prev_filter_time_factor = 1.0
self.slew_per_sec = 1.0
self.current_x_ego_cost = X_EGO_OBSTACLE_COSTS[0]
self.current_j_ego_cost = J_EGO_COSTS[0]
self.current_a_change_cost = A_CHANGE_COSTS[0]
self.current_dist_adapt = DIST_ADAPTS[0]
self.cruise_min_a = ACCEL_MIN
self.max_a = min(ACCEL_MAX, 1.2)
self.stop_distance = STOP_DISTANCE
self.reset()
def reset(self):
# self.solver = AcadosOcpSolverCython(MODEL_NAME, ACADOS_SOLVER_TYPE, N)
self.solver.reset()
@@ -297,10 +344,56 @@ class LongitudinalMpc:
for i in range(N):
self.solver.cost_set(i, 'Zl', Zl)
def set_weights(self, acceleration_jerk=1.0, danger_jerk=1.0, speed_jerk=1.0, prev_accel_constraint=True, personality=log.LongitudinalPersonality.standard):
def set_weights(self, acceleration_jerk=1.0, danger_jerk=1.0, speed_jerk=1.0, prev_accel_constraint=True,
personality=log.LongitudinalPersonality.standard, v_ego=0.0, lead_dist=50.0,
uncertainty=0.0, accel_reengage=False, panic_bypass=False, stop_distance=STOP_DISTANCE):
self.stop_distance = max(float(stop_distance), 4.0)
speed_mph = v_ego * CV.MS_TO_MPH
self.current_x_ego_cost = get_speed_based_param(speed_mph, X_EGO_OBSTACLE_COSTS)
self.current_j_ego_cost = get_speed_based_param(speed_mph, J_EGO_COSTS)
self.current_a_change_cost = get_speed_based_param(speed_mph, A_CHANGE_COSTS)
dist_adapt_array = [0.0, DIST_ADAPTS[1], DIST_ADAPTS[2], DIST_ADAPTS[3]]
self.current_dist_adapt = get_speed_based_param(speed_mph, dist_adapt_array)
if speed_mph < 47:
self.current_filter_time = 0.0
else:
self.current_filter_time = float(np.interp(speed_mph, [47, 65], [0.0, LEAD_FILTER_TIME_HIGH]))
if abs(self.current_filter_time - self.prev_filter_time) > 0.1:
self.lead_a_filter = FirstOrderFilter(self.lead_a_filter.x, self.current_filter_time, self.dt)
self.lead_v_filter = FirstOrderFilter(self.lead_v_filter.x, self.current_filter_time, self.dt)
self.prev_filter_time = self.current_filter_time
dist_factor = 1.0 + self.current_dist_adapt * (20.0 / max(lead_dist, 5.0))
acceleration_jerk *= dist_factor
danger_jerk *= dist_factor
speed_jerk *= dist_factor
if uncertainty <= 0.45:
tgt_factor = 1.0
elif uncertainty >= 0.70:
tgt_factor = 0.0
else:
tgt_factor = float(np.interp(uncertainty, [0.45, 0.70], [1.0, 0.30]))
if accel_reengage:
tgt_factor = min(tgt_factor, 0.5)
if panic_bypass:
tgt_factor = 0.0
max_step = self.slew_per_sec * self.dt
delta = np.clip(tgt_factor - self.filter_time_factor, -max_step, max_step)
self.filter_time_factor += float(delta)
if 0.45 <= uncertainty < 0.60:
speed_jerk *= float(np.interp(uncertainty, [0.45, 0.60], [1.2, 1.5]))
if self.mode == 'acc':
a_change_cost = acceleration_jerk if prev_accel_constraint else 0
cost_weights = [X_EGO_OBSTACLE_COST, X_EGO_COST, V_EGO_COST, A_EGO_COST, a_change_cost, speed_jerk]
cost_weights = [self.current_x_ego_cost, X_EGO_COST, V_EGO_COST, A_EGO_COST, a_change_cost, speed_jerk]
constraint_cost_weights = [LIMIT_COST, LIMIT_COST, LIMIT_COST, danger_jerk]
elif self.mode == 'blended':
a_change_cost = 40.0 if prev_accel_constraint else 0
@@ -310,6 +403,12 @@ class LongitudinalMpc:
raise NotImplementedError(f'Planner mode {self.mode} not recognized in planner cost set')
self.set_cost_weights(cost_weights, constraint_cost_weights)
if abs(self.filter_time_factor - self.prev_filter_time_factor) > 0.05:
new_filter_time = self.current_filter_time * self.filter_time_factor
self.lead_a_filter = FirstOrderFilter(self.lead_a_filter.x, new_filter_time, self.dt)
self.lead_v_filter = FirstOrderFilter(self.lead_v_filter.x, new_filter_time, self.dt)
self.prev_filter_time_factor = self.filter_time_factor
def set_cur_state(self, v, a):
v_prev = self.x0[1]
self.x0[1] = v
@@ -319,22 +418,35 @@ class LongitudinalMpc:
self.solver.set(i, 'x', self.x0)
@staticmethod
def extrapolate_lead(x_lead, v_lead, a_lead, a_lead_tau):
a_lead_traj = a_lead * np.exp(-a_lead_tau * (T_IDXS**2)/2.)
v_lead_traj = np.clip(v_lead + np.cumsum(T_DIFFS * a_lead_traj), 0.0, 1e8)
x_lead_traj = x_lead + np.cumsum(T_DIFFS * v_lead_traj)
def extrapolate_lead(x_lead, v_lead, a_lead, a_lead_tau, v_ego=0.0):
speed_mph = v_ego * CV.MS_TO_MPH
exp_weight = float(np.interp(speed_mph, [0, 20, 35], [1.0, 1.0, 0.0]))
if exp_weight > 0.0:
a_lead_traj_exp = a_lead * np.exp(-a_lead_tau * (T_IDXS**2)/2.)
v_lead_traj_exp = np.clip(v_lead + np.cumsum(T_DIFFS * a_lead_traj_exp), 0.0, 1e8)
x_lead_traj_exp = x_lead + np.cumsum(T_DIFFS * v_lead_traj_exp)
else:
x_lead_traj_exp = np.zeros_like(T_IDXS)
v_lead_traj_exp = np.zeros_like(T_IDXS)
v_lead_traj_const = np.clip(v_lead + a_lead * T_IDXS, 0.0, 1e8)
x_lead_traj_const = x_lead + v_lead * T_IDXS + 0.5 * a_lead * T_IDXS**2
v_lead_traj = exp_weight * v_lead_traj_exp + (1.0 - exp_weight) * v_lead_traj_const
x_lead_traj = exp_weight * x_lead_traj_exp + (1.0 - exp_weight) * x_lead_traj_const
lead_xv = np.column_stack((x_lead_traj, v_lead_traj))
return lead_xv
def process_lead(self, lead):
def process_lead(self, lead, tracking_lead=True):
v_ego = self.x0[1]
if lead is not None and lead.status:
if lead is not None and lead.status and tracking_lead:
x_lead = lead.dRel
v_lead = lead.vLead
a_lead = lead.aLeadK
a_lead_tau = lead.aLeadTau
else:
# Fake a fast lead car, so mpc can keep running in the same mode
x_lead = 50.0
v_lead = v_ego + 10.0
a_lead = 0.0
@@ -346,15 +458,25 @@ class LongitudinalMpc:
x_lead = np.clip(x_lead, min_x_lead, 1e8)
v_lead = np.clip(v_lead, 0.0, 1e8)
a_lead = np.clip(a_lead, -10., 5.)
lead_xv = self.extrapolate_lead(x_lead, v_lead, a_lead, a_lead_tau)
self.lead_a_filter.update(a_lead)
self.lead_v_filter.update(v_lead)
lead_xv = self.extrapolate_lead(x_lead, self.lead_v_filter.x, self.lead_a_filter.x, a_lead_tau, v_ego)
return lead_xv
def update(self, radarstate, v_cruise, x, v, a, j, danger_factor, t_follow, personality=log.LongitudinalPersonality.standard):
v_ego = self.x0[1]
self.status = radarstate.leadOne.status or radarstate.leadTwo.status
def set_accel_limits(self, min_a, max_a):
self.cruise_min_a = min_a
self.max_a = max_a
lead_xv_0 = self.process_lead(radarstate.leadOne)
lead_xv_1 = self.process_lead(radarstate.leadTwo)
def update(self, radarstate, v_cruise, x, v, a, j, danger_factor, t_follow,
personality=log.LongitudinalPersonality.standard, tracking_lead=True):
v_ego = self.x0[1]
lead_one = radarstate.leadOne
lead_two = radarstate.leadTwo
self.status = (lead_one.status and tracking_lead) or lead_two.status
lead_xv_0 = self.process_lead(lead_one, tracking_lead)
lead_xv_1 = self.process_lead(lead_two, True)
# To estimate a safe distance from a moving lead, we calculate how much stopping
# distance that lead needs as a minimum. We can add that to the current distance
@@ -363,7 +485,7 @@ class LongitudinalMpc:
lead_1_obstacle = lead_xv_1[:,0] + get_stopped_equivalence_factor(lead_xv_1[:,1])
self.params[:,0] = ACCEL_MIN
self.params[:,1] = ACCEL_MAX
self.params[:,1] = max(0.0, self.max_a)
# Update in ACC mode or ACC/e2e blend
if self.mode == 'acc':
@@ -371,13 +493,13 @@ class LongitudinalMpc:
# Fake an obstacle for cruise, this ensures smooth acceleration to set speed
# when the leads are no factor.
v_lower = v_ego + (T_IDXS * CRUISE_MIN_ACCEL * 1.05)
v_lower = v_ego + (T_IDXS * self.cruise_min_a * 1.05)
# TODO does this make sense when max_a is negative?
v_upper = v_ego + (T_IDXS * CRUISE_MAX_ACCEL * 1.05)
v_upper = v_ego + (T_IDXS * self.max_a * 1.05)
v_cruise_clipped = np.clip(v_cruise * np.ones(N+1),
v_lower,
v_upper)
cruise_obstacle = np.cumsum(T_DIFFS * v_cruise_clipped) + get_safe_obstacle_distance(v_cruise_clipped, t_follow)
cruise_obstacle = np.cumsum(T_DIFFS * v_cruise_clipped) + get_safe_obstacle_distance(v_cruise_clipped, t_follow, self.stop_distance)
x_obstacles = np.column_stack([lead_0_obstacle, lead_1_obstacle, cruise_obstacle])
self.source = SOURCES[np.argmin(x_obstacles[0])]
@@ -415,7 +537,7 @@ class LongitudinalMpc:
self.run()
if (np.any(lead_xv_0[FCW_IDXS,0] - self.x_sol[FCW_IDXS,0] < CRASH_DISTANCE) and
radarstate.leadOne.modelProb > 0.9):
lead_one.modelProb > 0.9):
self.crash_cnt += 1
else:
self.crash_cnt = 0
@@ -423,9 +545,9 @@ class LongitudinalMpc:
# Check if it got within lead comfort range
# TODO This should be done cleaner
if self.mode == 'blended':
if any((lead_0_obstacle - get_safe_obstacle_distance(self.x_sol[:,1], t_follow))- self.x_sol[:,0] < 0.0):
if any((lead_0_obstacle - get_safe_obstacle_distance(self.x_sol[:,1], t_follow, self.stop_distance))- self.x_sol[:,0] < 0.0):
self.source = 'lead0'
if any((lead_1_obstacle - get_safe_obstacle_distance(self.x_sol[:,1], t_follow))- self.x_sol[:,0] < 0.0) and \
if any((lead_1_obstacle - get_safe_obstacle_distance(self.x_sol[:,1], t_follow, self.stop_distance))- self.x_sol[:,0] < 0.0) and \
(lead_1_obstacle[0] - lead_0_obstacle[0]):
self.source = 'lead1'
+148 -14
View File
@@ -1,5 +1,6 @@
#!/usr/bin/env python3
import math
import time
import numpy as np
import cereal.messaging as messaging
@@ -12,7 +13,7 @@ from openpilot.selfdrive.controls.lib.longcontrol import LongCtrlState
from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import LongitudinalMpc
from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import T_IDXS as T_IDXS_MPC
from openpilot.selfdrive.controls.lib.drive_helpers import CONTROL_N, get_accel_from_plan
from openpilot.selfdrive.car.cruise import V_CRUISE_MAX, V_CRUISE_UNSET
from openpilot.selfdrive.car.cruise import V_CRUISE_UNSET
from openpilot.common.swaglog import cloudlog
from openpilot.frogpilot.common.frogpilot_variables import MINIMUM_LATERAL_ACCELERATION
@@ -24,6 +25,10 @@ CONTROL_N_T_IDX = ModelConstants.T_IDXS[:CONTROL_N]
ALLOW_THROTTLE_THRESHOLD = 0.4
MIN_ALLOW_THROTTLE_SPEED = 2.5
# Uncertainty-based filter disable thresholds
UNCERT_SLOPE_TRIG = 0.12 # per second
UNCERT_MAG_TRIG = 0.50
# Lookup table for turns
_A_TOTAL_MAX_V = [1.7, 3.2]
_A_TOTAL_MAX_BP = [20., 40.]
@@ -58,11 +63,11 @@ class LongitudinalPlanner:
def __init__(self, CP, init_v=0.0, init_a=0.0, dt=DT_MDL):
self.CP = CP
self.mpc = LongitudinalMpc(dt=dt)
# TODO remove mpc modes when TR released
self.mpc.mode = 'acc'
self.fcw = False
self.dt = dt
self.allow_throttle = True
self.mode = 'acc'
self.generation = None
self.a_desired = init_a
self.v_desired_filter = FirstOrderFilter(init_v, 2.0, self.dt)
@@ -75,13 +80,32 @@ class LongitudinalPlanner:
self.j_desired_trajectory = np.zeros(CONTROL_N)
self.solverExecutionTime = 0.0
self.uncert_slow = FirstOrderFilter(0.0, 1.6, self.dt)
self.uncert_fast = FirstOrderFilter(0.0, 0.9, self.dt)
self.prev_lead_dist = None
self.last_big_brake_t = 0.0
self.stable_lead = False
self.lead_dist_f = None
self._uncert_last = 0.0
self._uncert_last_t = None
@property
def mlsim(self):
return self.generation in ("v8", "v10", "v11", "v12")
@staticmethod
def parse_model(model_msg, v_ego, frogpilot_toggles):
def get_model_speed_error(model_msg, v_ego):
if len(model_msg.velocity.x) == ModelConstants.IDX_N:
return float(np.clip(model_msg.velocity.x[0] - v_ego, -5.0, 5.0))
return 0.0
@staticmethod
def parse_model(model_msg, model_error, v_ego, frogpilot_toggles):
if (len(model_msg.position.x) == ModelConstants.IDX_N and
len(model_msg.velocity.x) == ModelConstants.IDX_N and
len(model_msg.acceleration.x) == ModelConstants.IDX_N):
x = np.interp(T_IDXS_MPC, ModelConstants.T_IDXS, model_msg.position.x)
v = np.interp(T_IDXS_MPC, ModelConstants.T_IDXS, model_msg.velocity.x)
x = np.interp(T_IDXS_MPC, ModelConstants.T_IDXS, model_msg.position.x) - model_error * T_IDXS_MPC
v = np.interp(T_IDXS_MPC, ModelConstants.T_IDXS, model_msg.velocity.x) - model_error
a = np.interp(T_IDXS_MPC, ModelConstants.T_IDXS, model_msg.acceleration.x)
j = np.zeros(len(T_IDXS_MPC))
else:
@@ -104,14 +128,18 @@ class LongitudinalPlanner:
return x, v, a, j, throttle_prob
def update(self, sm, frogpilot_toggles):
mode = 'blended' if sm['selfdriveState'].experimentalMode else 'acc'
self.generation = getattr(frogpilot_toggles, "model_version", None)
self.mode = 'blended' if sm['selfdriveState'].experimentalMode else 'acc'
self.mpc.mode = 'acc'
if not self.mlsim:
self.mpc.mode = self.mode
if len(sm['carControl'].orientationNED) == 3:
accel_coast = get_coast_accel(sm['carControl'].orientationNED[1])
else:
accel_coast = ACCEL_MAX
v_ego = sm['carState'].vEgo
v_ego = max(sm['carState'].vEgo, sm['carState'].vEgoCluster)
v_cruise = sm['frogpilotPlan'].vCruise
v_cruise_initialized = sm['carState'].vCruise != V_CRUISE_UNSET
@@ -126,7 +154,7 @@ class LongitudinalPlanner:
# No change cost when user is controlling the speed, or when standstill
prev_accel_constraint = not (reset_state or sm['carState'].standstill)
if mode == 'acc':
if self.mpc.mode == 'acc':
accel_clip = [sm['frogpilotPlan'].minAcceleration, sm['frogpilotPlan'].maxAcceleration]
steer_angle_without_offset = sm['carState'].steeringAngleDeg - sm['liveParameters'].angleOffsetDeg
if not sm['frogpilotPlan'].cscControllingSpeed:
@@ -141,9 +169,11 @@ class LongitudinalPlanner:
# Prevent divergence, smooth in current v_ego
self.v_desired_filter.x = max(0.0, self.v_desired_filter.update(v_ego))
x, v, a, j, throttle_prob = self.parse_model(sm['modelV2'], v_ego, frogpilot_toggles)
model_error = self.get_model_speed_error(sm['modelV2'], v_ego)
x, v, a, j, throttle_prob = self.parse_model(sm['modelV2'], model_error, v_ego, frogpilot_toggles)
# Don't clip at low speeds since throttle_prob doesn't account for creep
self.allow_throttle = throttle_prob > ALLOW_THROTTLE_THRESHOLD or v_ego <= MIN_ALLOW_THROTTLE_SPEED
self.allow_throttle &= not sm['frogpilotPlan'].disableThrottle
if not self.allow_throttle:
clipped_accel_coast = max(accel_coast, accel_clip[0])
@@ -153,9 +183,99 @@ class LongitudinalPlanner:
if force_slow_decel:
v_cruise = 0.0
self.mpc.set_weights(sm['frogpilotPlan'].accelerationJerk, sm['frogpilotPlan'].dangerJerk, sm['frogpilotPlan'].speedJerk, prev_accel_constraint, personality=sm['selfdriveState'].personality)
accel_clip[0] = min(accel_clip[0], self.a_desired + 0.05)
accel_clip[1] = max(accel_clip[1], self.a_desired - 0.05)
lead_one = sm['radarState'].leadOne
lead_dist = lead_one.dRel if lead_one.status else 50.0
alpha = max(0.02, min(0.15, 0.05 + 0.002 * v_ego))
if self.lead_dist_f is None:
self.lead_dist_f = float(lead_dist)
else:
self.lead_dist_f += alpha * (float(lead_dist) - self.lead_dist_f)
now_t = time.monotonic()
v_rel = (v_ego - lead_one.vLead) if lead_one.status else 0.0
if self.prev_lead_dist is None:
d_rel_dot = 0.0
else:
d_rel_dot = (lead_dist - self.prev_lead_dist) / max(self.dt, 1e-3)
self.prev_lead_dist = lead_dist
uncertainty = 0.0
raw_brake_max = 0.0
if hasattr(sm['modelV2'], 'meta'):
desire_entropy = 0.0
if hasattr(sm['modelV2'].meta, 'desirePrediction'):
desire_probs = sm['modelV2'].meta.desirePrediction
if len(desire_probs) > 1:
probs = np.asarray(desire_probs, dtype=float)
total = float(np.sum(probs))
if total > 1e-6:
p = probs / total
entropy = -np.sum(p * np.log(p + 1e-10))
max_entropy = np.log(len(p))
desire_entropy = float(entropy / max(max_entropy, 1e-6))
disengage_risk = 0.0
if hasattr(sm['modelV2'].meta, 'disengagePredictions'):
brake_probs = sm['modelV2'].meta.disengagePredictions.brakePressProbs
if len(brake_probs) > 0:
probs = np.asarray(brake_probs, dtype=float)
if float(np.max(probs)) < 0.015:
probs = probs * 0.5
raw_brake_max = float(np.max(probs))
t = np.arange(len(probs), dtype=float) * DT_MDL
lam = 0.6
weights = np.exp(-lam * t)
disengage_risk = float(np.max(probs * weights))
raw_uncertainty = desire_entropy + disengage_risk
self.uncert_slow.update(raw_uncertainty)
self.uncert_fast.update(raw_uncertainty)
uncertainty = self.uncert_slow.x
if raw_brake_max > 0.02:
self.last_big_brake_t = now_t
recently_braked = (now_t - self.last_big_brake_t) < 0.7
self.stable_lead = (
lead_one.status and
abs(v_rel) < 0.5 and
abs(d_rel_dot) < 0.5 and
not recently_braked
)
if self._uncert_last_t is None:
uncert_slope = 0.0
else:
dt_u = max(1e-3, now_t - self._uncert_last_t)
uncert_slope = (uncertainty - self._uncert_last) / dt_u
self._uncert_last = uncertainty
self._uncert_last_t = now_t
closing_fast = lead_one.status and (v_ego - lead_one.vLead) > 0.5
panic_bypass = closing_fast and (uncert_slope > UNCERT_SLOPE_TRIG or uncertainty >= UNCERT_MAG_TRIG)
if panic_bypass:
cloudlog.error(f"LON_SLOPE slope={uncert_slope:.3f} uncertainty={uncertainty:.3f} v_ego={v_ego:.2f}")
self.mpc.set_weights(sm['frogpilotPlan'].accelerationJerk,
sm['frogpilotPlan'].dangerJerk,
sm['frogpilotPlan'].speedJerk,
prev_accel_constraint,
personality=sm['selfdriveState'].personality,
v_ego=v_ego,
lead_dist=self.lead_dist_f if self.lead_dist_f is not None else lead_dist,
uncertainty=uncertainty,
panic_bypass=panic_bypass,
stop_distance=getattr(frogpilot_toggles, "stop_distance", 6.0))
self.mpc.set_accel_limits(accel_clip[0], accel_clip[1])
self.mpc.set_cur_state(self.v_desired_filter.x, self.a_desired)
self.mpc.update(sm['radarState'], v_cruise, x, v, a, j, sm['frogpilotPlan'].dangerFactor, sm['frogpilotPlan'].tFollow, personality=sm['selfdriveState'].personality)
tracking_lead = sm['frogpilotPlan'].desiredFollowDistance > 0
self.mpc.update(sm['radarState'], v_cruise, x, v, a, j,
sm['frogpilotPlan'].dangerFactor, sm['frogpilotPlan'].tFollow,
personality=sm['selfdriveState'].personality, tracking_lead=tracking_lead)
self.v_desired_trajectory = np.interp(CONTROL_N_T_IDX, T_IDXS_MPC, self.mpc.v_solution)
self.a_desired_trajectory = np.interp(CONTROL_N_T_IDX, T_IDXS_MPC, self.mpc.a_solution)
@@ -171,13 +291,27 @@ class LongitudinalPlanner:
self.a_desired = float(np.interp(self.dt, CONTROL_N_T_IDX, self.a_desired_trajectory))
self.v_desired_filter.x = self.v_desired_filter.x + self.dt * (self.a_desired + a_prev) / 2.0
if lead_one.status:
rel_v = max(0.0, v_ego - lead_one.vLead)
base_th = 1.6
th = base_th + 0.6 * max(0.0, uncertainty - 0.42)
desired_gap = th * v_ego
if self.lead_dist_f is not None and self.lead_dist_f < desired_gap and rel_v > 0.5:
k_rel, k_unc = 0.04, 0.20
pre_brake = k_rel * rel_v + k_unc * max(0.0, uncertainty - 0.42)
self.a_desired = float(self.a_desired - min(pre_brake, 0.06))
if -0.05 < self.a_desired < 0.05:
self.a_desired = 0.0
action_t = frogpilot_toggles.longitudinalActuatorDelay + DT_MDL
output_a_target_mpc, output_should_stop_mpc = get_accel_from_plan(self.v_desired_trajectory, self.a_desired_trajectory, CONTROL_N_T_IDX,
action_t=action_t, vEgoStopping=frogpilot_toggles.vEgoStopping)
output_a_target_e2e = sm['modelV2'].action.desiredAcceleration
output_should_stop_e2e = sm['modelV2'].action.shouldStop
if mode == 'acc':
# Keep StarPilot behavior: for tinygrad v10/v11/v12 in experimental mode, blend with model action output.
if self.mode == 'acc' or self.generation == 'v9':
output_a_target = output_a_target_mpc
self.output_should_stop = output_should_stop_mpc
else:
@@ -208,7 +342,7 @@ class LongitudinalPlanner:
longitudinalPlan.fcw = self.fcw
longitudinalPlan.aTarget = float(self.output_a_target)
longitudinalPlan.shouldStop = bool(self.output_should_stop)
longitudinalPlan.shouldStop = bool(self.output_should_stop) or sm['frogpilotPlan'].forcingStopLength < 1
longitudinalPlan.allowBrake = True
longitudinalPlan.allowThrottle = bool(self.allow_throttle)
+6 -4
View File
@@ -1,4 +1,5 @@
from parameterized import parameterized
from types import SimpleNamespace
from cereal import car, log
from opendbc.car.car_helpers import interfaces
@@ -16,7 +17,7 @@ from openpilot.selfdrive.controls.lib.latcontrol_angle import LatControlAngle
class TestLatControl:
@parameterized.expand([(HONDA.HONDA_CIVIC, LatControlPID), (TOYOTA.TOYOTA_RAV4, LatControlTorque),
(NISSAN.NISSAN_LEAF, LatControlAngle), (GM.CHEVROLET_BOLT_EUV, LatControlTorque)])
(NISSAN.NISSAN_LEAF, LatControlAngle), (GM.CHEVROLET_BOLT_ACC_2022_2023, LatControlTorque)])
def test_saturation(self, car_name, controller):
CarInterface = interfaces[car_name]
CP = CarInterface.get_non_essential_params(car_name)
@@ -30,16 +31,17 @@ class TestLatControl:
CS.steeringPressed = False
params = log.LiveParametersData.new_message()
frogpilot_toggles = SimpleNamespace()
# Saturate for curvature limited and controller limited
for _ in range(1000):
_, _, lac_log = controller.update(True, CS, VM, params, False, 0, True, 0.2)
_, _, lac_log = controller.update(True, CS, VM, params, False, 0, True, 0.2, None, None, frogpilot_toggles)
assert lac_log.saturated
for _ in range(1000):
_, _, lac_log = controller.update(True, CS, VM, params, False, 0, False, 0.2)
_, _, lac_log = controller.update(True, CS, VM, params, False, 0, False, 0.2, None, None, frogpilot_toggles)
assert not lac_log.saturated
for _ in range(1000):
_, _, lac_log = controller.update(True, CS, VM, params, False, 1, False, 0.2)
_, _, lac_log = controller.update(True, CS, VM, params, False, 1, False, 0.2, None, None, frogpilot_toggles)
assert lac_log.saturated
+66
View File
@@ -0,0 +1,66 @@
#!/usr/bin/env python3
import argparse
import time
from cereal import log, messaging
from openpilot.system.hardware import HARDWARE
NETWORK_TYPES = {
"none": log.DeviceState.NetworkType.none,
"wifi": log.DeviceState.NetworkType.wifi,
"ethernet": log.DeviceState.NetworkType.ethernet,
"cell2G": log.DeviceState.NetworkType.cell2G,
"cell3G": log.DeviceState.NetworkType.cell3G,
"cell4G": log.DeviceState.NetworkType.cell4G,
"cell5G": log.DeviceState.NetworkType.cell5G,
}
NETWORK_STRENGTHS = {
"unknown": log.DeviceState.NetworkStrength.unknown,
"poor": log.DeviceState.NetworkStrength.poor,
"moderate": log.DeviceState.NetworkStrength.moderate,
"good": log.DeviceState.NetworkStrength.good,
"great": log.DeviceState.NetworkStrength.great,
}
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Publish fake deviceState network info for desktop UI demos.")
parser.add_argument("--network", choices=NETWORK_TYPES.keys(), default="wifi", help="network type to publish")
parser.add_argument("--strength", choices=NETWORK_STRENGTHS.keys(), default="great", help="network strength to publish")
parser.add_argument("--started", action="store_true", help="publish started=true (onroad)")
parser.add_argument("--metered", action="store_true", help="publish networkMetered=true")
parser.add_argument("--interval", type=float, default=0.2, help="publish interval in seconds")
return parser.parse_args()
def main() -> None:
args = parse_args()
pm = messaging.PubMaster(["deviceState"])
interval = max(args.interval, 0.05)
network_type = NETWORK_TYPES[args.network]
network_strength = NETWORK_STRENGTHS[args.strength]
while True:
msg = messaging.new_message("deviceState")
ds = msg.deviceState
ds.deviceType = HARDWARE.get_device_type()
ds.started = args.started
ds.networkType = network_type
ds.networkStrength = network_strength
ds.networkMetered = args.metered
ds.lastAthenaPingTime = time.monotonic_ns()
# Keep common sidebar values reasonable for desktop demos.
ds.freeSpacePercent = 80
ds.memoryUsagePercent = 35
ds.maxTempC = 45
pm.send("deviceState", msg)
time.sleep(interval)
if __name__ == "__main__":
main()
+9 -1
View File
@@ -1 +1,9 @@
generated/
generated/*
!generated/car.cpp
!generated/car.h
!generated/pose.cpp
!generated/pose.h
!generated/libcar.so
!generated/libpose.so
!generated/car.os
!generated/pose.os
@@ -0,0 +1,661 @@
#include "car.h"
namespace {
#define DIM 9
#define EDIM 9
#define MEDIM 9
typedef void (*Hfun)(double *, double *, double *);
double mass;
void set_mass(double x){ mass = x;}
double rotational_inertia;
void set_rotational_inertia(double x){ rotational_inertia = x;}
double center_to_front;
void set_center_to_front(double x){ center_to_front = x;}
double center_to_rear;
void set_center_to_rear(double x){ center_to_rear = x;}
double stiffness_front;
void set_stiffness_front(double x){ stiffness_front = x;}
double stiffness_rear;
void set_stiffness_rear(double x){ stiffness_rear = x;}
const static double MAHA_THRESH_25 = 3.8414588206941227;
const static double MAHA_THRESH_24 = 5.991464547107981;
const static double MAHA_THRESH_30 = 3.8414588206941227;
const static double MAHA_THRESH_26 = 3.8414588206941227;
const static double MAHA_THRESH_27 = 3.8414588206941227;
const static double MAHA_THRESH_29 = 3.8414588206941227;
const static double MAHA_THRESH_28 = 3.8414588206941227;
const static double MAHA_THRESH_31 = 3.8414588206941227;
/******************************************************************************
* Code generated with SymPy 1.14.0 *
* *
* See http://www.sympy.org/ for more information. *
* *
* This file is part of 'ekf' *
******************************************************************************/
void err_fun(double *nom_x, double *delta_x, double *out_2689391300508487509) {
out_2689391300508487509[0] = delta_x[0] + nom_x[0];
out_2689391300508487509[1] = delta_x[1] + nom_x[1];
out_2689391300508487509[2] = delta_x[2] + nom_x[2];
out_2689391300508487509[3] = delta_x[3] + nom_x[3];
out_2689391300508487509[4] = delta_x[4] + nom_x[4];
out_2689391300508487509[5] = delta_x[5] + nom_x[5];
out_2689391300508487509[6] = delta_x[6] + nom_x[6];
out_2689391300508487509[7] = delta_x[7] + nom_x[7];
out_2689391300508487509[8] = delta_x[8] + nom_x[8];
}
void inv_err_fun(double *nom_x, double *true_x, double *out_7177783815062799236) {
out_7177783815062799236[0] = -nom_x[0] + true_x[0];
out_7177783815062799236[1] = -nom_x[1] + true_x[1];
out_7177783815062799236[2] = -nom_x[2] + true_x[2];
out_7177783815062799236[3] = -nom_x[3] + true_x[3];
out_7177783815062799236[4] = -nom_x[4] + true_x[4];
out_7177783815062799236[5] = -nom_x[5] + true_x[5];
out_7177783815062799236[6] = -nom_x[6] + true_x[6];
out_7177783815062799236[7] = -nom_x[7] + true_x[7];
out_7177783815062799236[8] = -nom_x[8] + true_x[8];
}
void H_mod_fun(double *state, double *out_8221602802744378233) {
out_8221602802744378233[0] = 1.0;
out_8221602802744378233[1] = 0.0;
out_8221602802744378233[2] = 0.0;
out_8221602802744378233[3] = 0.0;
out_8221602802744378233[4] = 0.0;
out_8221602802744378233[5] = 0.0;
out_8221602802744378233[6] = 0.0;
out_8221602802744378233[7] = 0.0;
out_8221602802744378233[8] = 0.0;
out_8221602802744378233[9] = 0.0;
out_8221602802744378233[10] = 1.0;
out_8221602802744378233[11] = 0.0;
out_8221602802744378233[12] = 0.0;
out_8221602802744378233[13] = 0.0;
out_8221602802744378233[14] = 0.0;
out_8221602802744378233[15] = 0.0;
out_8221602802744378233[16] = 0.0;
out_8221602802744378233[17] = 0.0;
out_8221602802744378233[18] = 0.0;
out_8221602802744378233[19] = 0.0;
out_8221602802744378233[20] = 1.0;
out_8221602802744378233[21] = 0.0;
out_8221602802744378233[22] = 0.0;
out_8221602802744378233[23] = 0.0;
out_8221602802744378233[24] = 0.0;
out_8221602802744378233[25] = 0.0;
out_8221602802744378233[26] = 0.0;
out_8221602802744378233[27] = 0.0;
out_8221602802744378233[28] = 0.0;
out_8221602802744378233[29] = 0.0;
out_8221602802744378233[30] = 1.0;
out_8221602802744378233[31] = 0.0;
out_8221602802744378233[32] = 0.0;
out_8221602802744378233[33] = 0.0;
out_8221602802744378233[34] = 0.0;
out_8221602802744378233[35] = 0.0;
out_8221602802744378233[36] = 0.0;
out_8221602802744378233[37] = 0.0;
out_8221602802744378233[38] = 0.0;
out_8221602802744378233[39] = 0.0;
out_8221602802744378233[40] = 1.0;
out_8221602802744378233[41] = 0.0;
out_8221602802744378233[42] = 0.0;
out_8221602802744378233[43] = 0.0;
out_8221602802744378233[44] = 0.0;
out_8221602802744378233[45] = 0.0;
out_8221602802744378233[46] = 0.0;
out_8221602802744378233[47] = 0.0;
out_8221602802744378233[48] = 0.0;
out_8221602802744378233[49] = 0.0;
out_8221602802744378233[50] = 1.0;
out_8221602802744378233[51] = 0.0;
out_8221602802744378233[52] = 0.0;
out_8221602802744378233[53] = 0.0;
out_8221602802744378233[54] = 0.0;
out_8221602802744378233[55] = 0.0;
out_8221602802744378233[56] = 0.0;
out_8221602802744378233[57] = 0.0;
out_8221602802744378233[58] = 0.0;
out_8221602802744378233[59] = 0.0;
out_8221602802744378233[60] = 1.0;
out_8221602802744378233[61] = 0.0;
out_8221602802744378233[62] = 0.0;
out_8221602802744378233[63] = 0.0;
out_8221602802744378233[64] = 0.0;
out_8221602802744378233[65] = 0.0;
out_8221602802744378233[66] = 0.0;
out_8221602802744378233[67] = 0.0;
out_8221602802744378233[68] = 0.0;
out_8221602802744378233[69] = 0.0;
out_8221602802744378233[70] = 1.0;
out_8221602802744378233[71] = 0.0;
out_8221602802744378233[72] = 0.0;
out_8221602802744378233[73] = 0.0;
out_8221602802744378233[74] = 0.0;
out_8221602802744378233[75] = 0.0;
out_8221602802744378233[76] = 0.0;
out_8221602802744378233[77] = 0.0;
out_8221602802744378233[78] = 0.0;
out_8221602802744378233[79] = 0.0;
out_8221602802744378233[80] = 1.0;
}
void f_fun(double *state, double dt, double *out_7651705913211861388) {
out_7651705913211861388[0] = state[0];
out_7651705913211861388[1] = state[1];
out_7651705913211861388[2] = state[2];
out_7651705913211861388[3] = state[3];
out_7651705913211861388[4] = state[4];
out_7651705913211861388[5] = dt*((-state[4] + (-center_to_front*stiffness_front*state[0] + center_to_rear*stiffness_rear*state[0])/(mass*state[4]))*state[6] - 9.8100000000000005*state[8] + stiffness_front*(-state[2] - state[3] + state[7])*state[0]/(mass*state[1]) + (-stiffness_front*state[0] - stiffness_rear*state[0])*state[5]/(mass*state[4])) + state[5];
out_7651705913211861388[6] = dt*(center_to_front*stiffness_front*(-state[2] - state[3] + state[7])*state[0]/(rotational_inertia*state[1]) + (-center_to_front*stiffness_front*state[0] + center_to_rear*stiffness_rear*state[0])*state[5]/(rotational_inertia*state[4]) + (-pow(center_to_front, 2)*stiffness_front*state[0] - pow(center_to_rear, 2)*stiffness_rear*state[0])*state[6]/(rotational_inertia*state[4])) + state[6];
out_7651705913211861388[7] = state[7];
out_7651705913211861388[8] = state[8];
}
void F_fun(double *state, double dt, double *out_97859790539154602) {
out_97859790539154602[0] = 1;
out_97859790539154602[1] = 0;
out_97859790539154602[2] = 0;
out_97859790539154602[3] = 0;
out_97859790539154602[4] = 0;
out_97859790539154602[5] = 0;
out_97859790539154602[6] = 0;
out_97859790539154602[7] = 0;
out_97859790539154602[8] = 0;
out_97859790539154602[9] = 0;
out_97859790539154602[10] = 1;
out_97859790539154602[11] = 0;
out_97859790539154602[12] = 0;
out_97859790539154602[13] = 0;
out_97859790539154602[14] = 0;
out_97859790539154602[15] = 0;
out_97859790539154602[16] = 0;
out_97859790539154602[17] = 0;
out_97859790539154602[18] = 0;
out_97859790539154602[19] = 0;
out_97859790539154602[20] = 1;
out_97859790539154602[21] = 0;
out_97859790539154602[22] = 0;
out_97859790539154602[23] = 0;
out_97859790539154602[24] = 0;
out_97859790539154602[25] = 0;
out_97859790539154602[26] = 0;
out_97859790539154602[27] = 0;
out_97859790539154602[28] = 0;
out_97859790539154602[29] = 0;
out_97859790539154602[30] = 1;
out_97859790539154602[31] = 0;
out_97859790539154602[32] = 0;
out_97859790539154602[33] = 0;
out_97859790539154602[34] = 0;
out_97859790539154602[35] = 0;
out_97859790539154602[36] = 0;
out_97859790539154602[37] = 0;
out_97859790539154602[38] = 0;
out_97859790539154602[39] = 0;
out_97859790539154602[40] = 1;
out_97859790539154602[41] = 0;
out_97859790539154602[42] = 0;
out_97859790539154602[43] = 0;
out_97859790539154602[44] = 0;
out_97859790539154602[45] = dt*(stiffness_front*(-state[2] - state[3] + state[7])/(mass*state[1]) + (-stiffness_front - stiffness_rear)*state[5]/(mass*state[4]) + (-center_to_front*stiffness_front + center_to_rear*stiffness_rear)*state[6]/(mass*state[4]));
out_97859790539154602[46] = -dt*stiffness_front*(-state[2] - state[3] + state[7])*state[0]/(mass*pow(state[1], 2));
out_97859790539154602[47] = -dt*stiffness_front*state[0]/(mass*state[1]);
out_97859790539154602[48] = -dt*stiffness_front*state[0]/(mass*state[1]);
out_97859790539154602[49] = dt*((-1 - (-center_to_front*stiffness_front*state[0] + center_to_rear*stiffness_rear*state[0])/(mass*pow(state[4], 2)))*state[6] - (-stiffness_front*state[0] - stiffness_rear*state[0])*state[5]/(mass*pow(state[4], 2)));
out_97859790539154602[50] = dt*(-stiffness_front*state[0] - stiffness_rear*state[0])/(mass*state[4]) + 1;
out_97859790539154602[51] = dt*(-state[4] + (-center_to_front*stiffness_front*state[0] + center_to_rear*stiffness_rear*state[0])/(mass*state[4]));
out_97859790539154602[52] = dt*stiffness_front*state[0]/(mass*state[1]);
out_97859790539154602[53] = -9.8100000000000005*dt;
out_97859790539154602[54] = dt*(center_to_front*stiffness_front*(-state[2] - state[3] + state[7])/(rotational_inertia*state[1]) + (-center_to_front*stiffness_front + center_to_rear*stiffness_rear)*state[5]/(rotational_inertia*state[4]) + (-pow(center_to_front, 2)*stiffness_front - pow(center_to_rear, 2)*stiffness_rear)*state[6]/(rotational_inertia*state[4]));
out_97859790539154602[55] = -center_to_front*dt*stiffness_front*(-state[2] - state[3] + state[7])*state[0]/(rotational_inertia*pow(state[1], 2));
out_97859790539154602[56] = -center_to_front*dt*stiffness_front*state[0]/(rotational_inertia*state[1]);
out_97859790539154602[57] = -center_to_front*dt*stiffness_front*state[0]/(rotational_inertia*state[1]);
out_97859790539154602[58] = dt*(-(-center_to_front*stiffness_front*state[0] + center_to_rear*stiffness_rear*state[0])*state[5]/(rotational_inertia*pow(state[4], 2)) - (-pow(center_to_front, 2)*stiffness_front*state[0] - pow(center_to_rear, 2)*stiffness_rear*state[0])*state[6]/(rotational_inertia*pow(state[4], 2)));
out_97859790539154602[59] = dt*(-center_to_front*stiffness_front*state[0] + center_to_rear*stiffness_rear*state[0])/(rotational_inertia*state[4]);
out_97859790539154602[60] = dt*(-pow(center_to_front, 2)*stiffness_front*state[0] - pow(center_to_rear, 2)*stiffness_rear*state[0])/(rotational_inertia*state[4]) + 1;
out_97859790539154602[61] = center_to_front*dt*stiffness_front*state[0]/(rotational_inertia*state[1]);
out_97859790539154602[62] = 0;
out_97859790539154602[63] = 0;
out_97859790539154602[64] = 0;
out_97859790539154602[65] = 0;
out_97859790539154602[66] = 0;
out_97859790539154602[67] = 0;
out_97859790539154602[68] = 0;
out_97859790539154602[69] = 0;
out_97859790539154602[70] = 1;
out_97859790539154602[71] = 0;
out_97859790539154602[72] = 0;
out_97859790539154602[73] = 0;
out_97859790539154602[74] = 0;
out_97859790539154602[75] = 0;
out_97859790539154602[76] = 0;
out_97859790539154602[77] = 0;
out_97859790539154602[78] = 0;
out_97859790539154602[79] = 0;
out_97859790539154602[80] = 1;
}
void h_25(double *state, double *unused, double *out_1747255377487496462) {
out_1747255377487496462[0] = state[6];
}
void H_25(double *state, double *unused, double *out_7588584345949747189) {
out_7588584345949747189[0] = 0;
out_7588584345949747189[1] = 0;
out_7588584345949747189[2] = 0;
out_7588584345949747189[3] = 0;
out_7588584345949747189[4] = 0;
out_7588584345949747189[5] = 0;
out_7588584345949747189[6] = 1;
out_7588584345949747189[7] = 0;
out_7588584345949747189[8] = 0;
}
void h_24(double *state, double *unused, double *out_3070396560078381804) {
out_3070396560078381804[0] = state[4];
out_3070396560078381804[1] = state[5];
}
void H_24(double *state, double *unused, double *out_7300234128957481291) {
out_7300234128957481291[0] = 0;
out_7300234128957481291[1] = 0;
out_7300234128957481291[2] = 0;
out_7300234128957481291[3] = 0;
out_7300234128957481291[4] = 1;
out_7300234128957481291[5] = 0;
out_7300234128957481291[6] = 0;
out_7300234128957481291[7] = 0;
out_7300234128957481291[8] = 0;
out_7300234128957481291[9] = 0;
out_7300234128957481291[10] = 0;
out_7300234128957481291[11] = 0;
out_7300234128957481291[12] = 0;
out_7300234128957481291[13] = 0;
out_7300234128957481291[14] = 1;
out_7300234128957481291[15] = 0;
out_7300234128957481291[16] = 0;
out_7300234128957481291[17] = 0;
}
void h_30(double *state, double *unused, double *out_2594165583737162996) {
out_2594165583737162996[0] = state[4];
}
void H_30(double *state, double *unused, double *out_8339826769252555800) {
out_8339826769252555800[0] = 0;
out_8339826769252555800[1] = 0;
out_8339826769252555800[2] = 0;
out_8339826769252555800[3] = 0;
out_8339826769252555800[4] = 1;
out_8339826769252555800[5] = 0;
out_8339826769252555800[6] = 0;
out_8339826769252555800[7] = 0;
out_8339826769252555800[8] = 0;
}
void h_26(double *state, double *unused, double *out_1234538052727793168) {
out_1234538052727793168[0] = state[7];
}
void H_26(double *state, double *unused, double *out_3847081027075690965) {
out_3847081027075690965[0] = 0;
out_3847081027075690965[1] = 0;
out_3847081027075690965[2] = 0;
out_3847081027075690965[3] = 0;
out_3847081027075690965[4] = 0;
out_3847081027075690965[5] = 0;
out_3847081027075690965[6] = 0;
out_3847081027075690965[7] = 1;
out_3847081027075690965[8] = 0;
}
void h_27(double *state, double *unused, double *out_4784041428931829147) {
out_4784041428931829147[0] = state[3];
}
void H_27(double *state, double *unused, double *out_886124704021714080) {
out_886124704021714080[0] = 0;
out_886124704021714080[1] = 0;
out_886124704021714080[2] = 0;
out_886124704021714080[3] = 1;
out_886124704021714080[4] = 0;
out_886124704021714080[5] = 0;
out_886124704021714080[6] = 0;
out_886124704021714080[7] = 0;
out_886124704021714080[8] = 0;
}
void h_29(double *state, double *unused, double *out_2407507683923682649) {
out_2407507683923682649[0] = state[1];
}
void H_29(double *state, double *unused, double *out_3571119360136531175) {
out_3571119360136531175[0] = 0;
out_3571119360136531175[1] = 1;
out_3571119360136531175[2] = 0;
out_3571119360136531175[3] = 0;
out_3571119360136531175[4] = 0;
out_3571119360136531175[5] = 0;
out_3571119360136531175[6] = 0;
out_3571119360136531175[7] = 0;
out_3571119360136531175[8] = 0;
}
void h_28(double *state, double *unused, double *out_7679778805471580716) {
out_7679778805471580716[0] = state[0];
}
void H_28(double *state, double *unused, double *out_5534749631701857426) {
out_5534749631701857426[0] = 1;
out_5534749631701857426[1] = 0;
out_5534749631701857426[2] = 0;
out_5534749631701857426[3] = 0;
out_5534749631701857426[4] = 0;
out_5534749631701857426[5] = 0;
out_5534749631701857426[6] = 0;
out_5534749631701857426[7] = 0;
out_5534749631701857426[8] = 0;
}
void h_31(double *state, double *unused, double *out_5023579848862854474) {
out_5023579848862854474[0] = state[8];
}
void H_31(double *state, double *unused, double *out_3220872924842339489) {
out_3220872924842339489[0] = 0;
out_3220872924842339489[1] = 0;
out_3220872924842339489[2] = 0;
out_3220872924842339489[3] = 0;
out_3220872924842339489[4] = 0;
out_3220872924842339489[5] = 0;
out_3220872924842339489[6] = 0;
out_3220872924842339489[7] = 0;
out_3220872924842339489[8] = 1;
}
#include <eigen3/Eigen/Dense>
#include <iostream>
typedef Eigen::Matrix<double, DIM, DIM, Eigen::RowMajor> DDM;
typedef Eigen::Matrix<double, EDIM, EDIM, Eigen::RowMajor> EEM;
typedef Eigen::Matrix<double, DIM, EDIM, Eigen::RowMajor> DEM;
void predict(double *in_x, double *in_P, double *in_Q, double dt) {
typedef Eigen::Matrix<double, MEDIM, MEDIM, Eigen::RowMajor> RRM;
double nx[DIM] = {0};
double in_F[EDIM*EDIM] = {0};
// functions from sympy
f_fun(in_x, dt, nx);
F_fun(in_x, dt, in_F);
EEM F(in_F);
EEM P(in_P);
EEM Q(in_Q);
RRM F_main = F.topLeftCorner(MEDIM, MEDIM);
P.topLeftCorner(MEDIM, MEDIM) = (F_main * P.topLeftCorner(MEDIM, MEDIM)) * F_main.transpose();
P.topRightCorner(MEDIM, EDIM - MEDIM) = F_main * P.topRightCorner(MEDIM, EDIM - MEDIM);
P.bottomLeftCorner(EDIM - MEDIM, MEDIM) = P.bottomLeftCorner(EDIM - MEDIM, MEDIM) * F_main.transpose();
P = P + dt*Q;
// copy out state
memcpy(in_x, nx, DIM * sizeof(double));
memcpy(in_P, P.data(), EDIM * EDIM * sizeof(double));
}
// note: extra_args dim only correct when null space projecting
// otherwise 1
template <int ZDIM, int EADIM, bool MAHA_TEST>
void update(double *in_x, double *in_P, Hfun h_fun, Hfun H_fun, Hfun Hea_fun, double *in_z, double *in_R, double *in_ea, double MAHA_THRESHOLD) {
typedef Eigen::Matrix<double, ZDIM, ZDIM, Eigen::RowMajor> ZZM;
typedef Eigen::Matrix<double, ZDIM, DIM, Eigen::RowMajor> ZDM;
typedef Eigen::Matrix<double, Eigen::Dynamic, EDIM, Eigen::RowMajor> XEM;
//typedef Eigen::Matrix<double, EDIM, ZDIM, Eigen::RowMajor> EZM;
typedef Eigen::Matrix<double, Eigen::Dynamic, 1> X1M;
typedef Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> XXM;
double in_hx[ZDIM] = {0};
double in_H[ZDIM * DIM] = {0};
double in_H_mod[EDIM * DIM] = {0};
double delta_x[EDIM] = {0};
double x_new[DIM] = {0};
// state x, P
Eigen::Matrix<double, ZDIM, 1> z(in_z);
EEM P(in_P);
ZZM pre_R(in_R);
// functions from sympy
h_fun(in_x, in_ea, in_hx);
H_fun(in_x, in_ea, in_H);
ZDM pre_H(in_H);
// get y (y = z - hx)
Eigen::Matrix<double, ZDIM, 1> pre_y(in_hx); pre_y = z - pre_y;
X1M y; XXM H; XXM R;
if (Hea_fun){
typedef Eigen::Matrix<double, ZDIM, EADIM, Eigen::RowMajor> ZAM;
double in_Hea[ZDIM * EADIM] = {0};
Hea_fun(in_x, in_ea, in_Hea);
ZAM Hea(in_Hea);
XXM A = Hea.transpose().fullPivLu().kernel();
y = A.transpose() * pre_y;
H = A.transpose() * pre_H;
R = A.transpose() * pre_R * A;
} else {
y = pre_y;
H = pre_H;
R = pre_R;
}
// get modified H
H_mod_fun(in_x, in_H_mod);
DEM H_mod(in_H_mod);
XEM H_err = H * H_mod;
// Do mahalobis distance test
if (MAHA_TEST){
XXM a = (H_err * P * H_err.transpose() + R).inverse();
double maha_dist = y.transpose() * a * y;
if (maha_dist > MAHA_THRESHOLD){
R = 1.0e16 * R;
}
}
// Outlier resilient weighting
double weight = 1;//(1.5)/(1 + y.squaredNorm()/R.sum());
// kalman gains and I_KH
XXM S = ((H_err * P) * H_err.transpose()) + R/weight;
XEM KT = S.fullPivLu().solve(H_err * P.transpose());
//EZM K = KT.transpose(); TODO: WHY DOES THIS NOT COMPILE?
//EZM K = S.fullPivLu().solve(H_err * P.transpose()).transpose();
//std::cout << "Here is the matrix rot:\n" << K << std::endl;
EEM I_KH = Eigen::Matrix<double, EDIM, EDIM>::Identity() - (KT.transpose() * H_err);
// update state by injecting dx
Eigen::Matrix<double, EDIM, 1> dx(delta_x);
dx = (KT.transpose() * y);
memcpy(delta_x, dx.data(), EDIM * sizeof(double));
err_fun(in_x, delta_x, x_new);
Eigen::Matrix<double, DIM, 1> x(x_new);
// update cov
P = ((I_KH * P) * I_KH.transpose()) + ((KT.transpose() * R) * KT);
// copy out state
memcpy(in_x, x.data(), DIM * sizeof(double));
memcpy(in_P, P.data(), EDIM * EDIM * sizeof(double));
memcpy(in_z, y.data(), y.rows() * sizeof(double));
}
}
extern "C" {
void car_update_25(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea) {
update<1, 3, 0>(in_x, in_P, h_25, H_25, NULL, in_z, in_R, in_ea, MAHA_THRESH_25);
}
void car_update_24(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea) {
update<2, 3, 0>(in_x, in_P, h_24, H_24, NULL, in_z, in_R, in_ea, MAHA_THRESH_24);
}
void car_update_30(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea) {
update<1, 3, 0>(in_x, in_P, h_30, H_30, NULL, in_z, in_R, in_ea, MAHA_THRESH_30);
}
void car_update_26(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea) {
update<1, 3, 0>(in_x, in_P, h_26, H_26, NULL, in_z, in_R, in_ea, MAHA_THRESH_26);
}
void car_update_27(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea) {
update<1, 3, 0>(in_x, in_P, h_27, H_27, NULL, in_z, in_R, in_ea, MAHA_THRESH_27);
}
void car_update_29(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea) {
update<1, 3, 0>(in_x, in_P, h_29, H_29, NULL, in_z, in_R, in_ea, MAHA_THRESH_29);
}
void car_update_28(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea) {
update<1, 3, 0>(in_x, in_P, h_28, H_28, NULL, in_z, in_R, in_ea, MAHA_THRESH_28);
}
void car_update_31(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea) {
update<1, 3, 0>(in_x, in_P, h_31, H_31, NULL, in_z, in_R, in_ea, MAHA_THRESH_31);
}
void car_err_fun(double *nom_x, double *delta_x, double *out_2689391300508487509) {
err_fun(nom_x, delta_x, out_2689391300508487509);
}
void car_inv_err_fun(double *nom_x, double *true_x, double *out_7177783815062799236) {
inv_err_fun(nom_x, true_x, out_7177783815062799236);
}
void car_H_mod_fun(double *state, double *out_8221602802744378233) {
H_mod_fun(state, out_8221602802744378233);
}
void car_f_fun(double *state, double dt, double *out_7651705913211861388) {
f_fun(state, dt, out_7651705913211861388);
}
void car_F_fun(double *state, double dt, double *out_97859790539154602) {
F_fun(state, dt, out_97859790539154602);
}
void car_h_25(double *state, double *unused, double *out_1747255377487496462) {
h_25(state, unused, out_1747255377487496462);
}
void car_H_25(double *state, double *unused, double *out_7588584345949747189) {
H_25(state, unused, out_7588584345949747189);
}
void car_h_24(double *state, double *unused, double *out_3070396560078381804) {
h_24(state, unused, out_3070396560078381804);
}
void car_H_24(double *state, double *unused, double *out_7300234128957481291) {
H_24(state, unused, out_7300234128957481291);
}
void car_h_30(double *state, double *unused, double *out_2594165583737162996) {
h_30(state, unused, out_2594165583737162996);
}
void car_H_30(double *state, double *unused, double *out_8339826769252555800) {
H_30(state, unused, out_8339826769252555800);
}
void car_h_26(double *state, double *unused, double *out_1234538052727793168) {
h_26(state, unused, out_1234538052727793168);
}
void car_H_26(double *state, double *unused, double *out_3847081027075690965) {
H_26(state, unused, out_3847081027075690965);
}
void car_h_27(double *state, double *unused, double *out_4784041428931829147) {
h_27(state, unused, out_4784041428931829147);
}
void car_H_27(double *state, double *unused, double *out_886124704021714080) {
H_27(state, unused, out_886124704021714080);
}
void car_h_29(double *state, double *unused, double *out_2407507683923682649) {
h_29(state, unused, out_2407507683923682649);
}
void car_H_29(double *state, double *unused, double *out_3571119360136531175) {
H_29(state, unused, out_3571119360136531175);
}
void car_h_28(double *state, double *unused, double *out_7679778805471580716) {
h_28(state, unused, out_7679778805471580716);
}
void car_H_28(double *state, double *unused, double *out_5534749631701857426) {
H_28(state, unused, out_5534749631701857426);
}
void car_h_31(double *state, double *unused, double *out_5023579848862854474) {
h_31(state, unused, out_5023579848862854474);
}
void car_H_31(double *state, double *unused, double *out_3220872924842339489) {
H_31(state, unused, out_3220872924842339489);
}
void car_predict(double *in_x, double *in_P, double *in_Q, double dt) {
predict(in_x, in_P, in_Q, dt);
}
void car_set_mass(double x) {
set_mass(x);
}
void car_set_rotational_inertia(double x) {
set_rotational_inertia(x);
}
void car_set_center_to_front(double x) {
set_center_to_front(x);
}
void car_set_center_to_rear(double x) {
set_center_to_rear(x);
}
void car_set_stiffness_front(double x) {
set_stiffness_front(x);
}
void car_set_stiffness_rear(double x) {
set_stiffness_rear(x);
}
}
const EKF car = {
.name = "car",
.kinds = { 25, 24, 30, 26, 27, 29, 28, 31 },
.feature_kinds = { },
.f_fun = car_f_fun,
.F_fun = car_F_fun,
.err_fun = car_err_fun,
.inv_err_fun = car_inv_err_fun,
.H_mod_fun = car_H_mod_fun,
.predict = car_predict,
.hs = {
{ 25, car_h_25 },
{ 24, car_h_24 },
{ 30, car_h_30 },
{ 26, car_h_26 },
{ 27, car_h_27 },
{ 29, car_h_29 },
{ 28, car_h_28 },
{ 31, car_h_31 },
},
.Hs = {
{ 25, car_H_25 },
{ 24, car_H_24 },
{ 30, car_H_30 },
{ 26, car_H_26 },
{ 27, car_H_27 },
{ 29, car_H_29 },
{ 28, car_H_28 },
{ 31, car_H_31 },
},
.updates = {
{ 25, car_update_25 },
{ 24, car_update_24 },
{ 30, car_update_30 },
{ 26, car_update_26 },
{ 27, car_update_27 },
{ 29, car_update_29 },
{ 28, car_update_28 },
{ 31, car_update_31 },
},
.Hes = {
},
.sets = {
{ "mass", car_set_mass },
{ "rotational_inertia", car_set_rotational_inertia },
{ "center_to_front", car_set_center_to_front },
{ "center_to_rear", car_set_center_to_rear },
{ "stiffness_front", car_set_stiffness_front },
{ "stiffness_rear", car_set_stiffness_rear },
},
.extra_routines = {
},
};
ekf_lib_init(car)
@@ -0,0 +1,40 @@
#pragma once
#include "rednose/helpers/ekf.h"
extern "C" {
void car_update_25(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea);
void car_update_24(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea);
void car_update_30(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea);
void car_update_26(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea);
void car_update_27(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea);
void car_update_29(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea);
void car_update_28(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea);
void car_update_31(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea);
void car_err_fun(double *nom_x, double *delta_x, double *out_2689391300508487509);
void car_inv_err_fun(double *nom_x, double *true_x, double *out_7177783815062799236);
void car_H_mod_fun(double *state, double *out_8221602802744378233);
void car_f_fun(double *state, double dt, double *out_7651705913211861388);
void car_F_fun(double *state, double dt, double *out_97859790539154602);
void car_h_25(double *state, double *unused, double *out_1747255377487496462);
void car_H_25(double *state, double *unused, double *out_7588584345949747189);
void car_h_24(double *state, double *unused, double *out_3070396560078381804);
void car_H_24(double *state, double *unused, double *out_7300234128957481291);
void car_h_30(double *state, double *unused, double *out_2594165583737162996);
void car_H_30(double *state, double *unused, double *out_8339826769252555800);
void car_h_26(double *state, double *unused, double *out_1234538052727793168);
void car_H_26(double *state, double *unused, double *out_3847081027075690965);
void car_h_27(double *state, double *unused, double *out_4784041428931829147);
void car_H_27(double *state, double *unused, double *out_886124704021714080);
void car_h_29(double *state, double *unused, double *out_2407507683923682649);
void car_H_29(double *state, double *unused, double *out_3571119360136531175);
void car_h_28(double *state, double *unused, double *out_7679778805471580716);
void car_H_28(double *state, double *unused, double *out_5534749631701857426);
void car_h_31(double *state, double *unused, double *out_5023579848862854474);
void car_H_31(double *state, double *unused, double *out_3220872924842339489);
void car_predict(double *in_x, double *in_P, double *in_Q, double dt);
void car_set_mass(double x);
void car_set_rotational_inertia(double x);
void car_set_center_to_front(double x);
void car_set_center_to_rear(double x);
void car_set_stiffness_front(double x);
void car_set_stiffness_rear(double x);
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,22 @@
#pragma once
#include "rednose/helpers/ekf.h"
extern "C" {
void pose_update_4(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea);
void pose_update_10(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea);
void pose_update_13(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea);
void pose_update_14(double *in_x, double *in_P, double *in_z, double *in_R, double *in_ea);
void pose_err_fun(double *nom_x, double *delta_x, double *out_2843212773844661672);
void pose_inv_err_fun(double *nom_x, double *true_x, double *out_2943105406875713938);
void pose_H_mod_fun(double *state, double *out_2147512711416992695);
void pose_f_fun(double *state, double dt, double *out_9076294150866414245);
void pose_F_fun(double *state, double dt, double *out_4995496673877862289);
void pose_h_4(double *state, double *unused, double *out_7356118181286553510);
void pose_H_4(double *state, double *unused, double *out_3967211437323289556);
void pose_h_10(double *state, double *unused, double *out_320685202434969677);
void pose_H_10(double *state, double *unused, double *out_156638784834038850);
void pose_h_13(double *state, double *unused, double *out_4281107234856404284);
void pose_H_13(double *state, double *unused, double *out_7800966900625813580);
void pose_h_14(double *state, double *unused, double *out_3917712072833752253);
void pose_H_14(double *state, double *unused, double *out_7049999869618661852);
void pose_predict(double *in_x, double *in_P, double *in_Q, double dt);
}
Binary file not shown.
+8 -1
View File
@@ -1,6 +1,7 @@
#!/usr/bin/env python3
import sys
import os
import numpy as np
from openpilot.selfdrive.locationd.models.constants import ObservationKind
@@ -102,7 +103,13 @@ class PoseKalman(KalmanFilter):
def __init__(self, generated_dir, max_rewind_age):
dim_state, dim_state_err = PoseKalman.initial_x.shape[0], PoseKalman.initial_P.shape[0]
self.filter = EKF_sym_pyx(generated_dir, self.name, PoseKalman.Q, PoseKalman.initial_x, PoseKalman.initial_P,
ekf_name = self.name
# Compatibility fallback for installs that still carry the older `live` EKF artifacts.
if not os.path.exists(os.path.join(generated_dir, f"lib{ekf_name}.so")):
live_fallback = os.path.join(generated_dir, "liblive.so")
if os.path.exists(live_fallback):
ekf_name = "live"
self.filter = EKF_sym_pyx(generated_dir, ekf_name, PoseKalman.Q, PoseKalman.initial_x, PoseKalman.initial_P,
dim_state, dim_state_err, max_rewind_age=max_rewind_age)
+82 -17
View File
@@ -20,39 +20,104 @@ if arch == "Darwin":
else:
libs += ['OpenCL']
# Set path definitions
for pathdef, fn in {'TRANSFORM': 'transforms/transform.cl', 'LOADYUV': 'transforms/loadyuv.cl'}.items():
# Set path definitions to repository-relative paths so prebuilt artifacts stay relocatable.
for pathdef, fn in {'TRANSFORM': 'selfdrive/modeld/transforms/transform.cl', 'LOADYUV': 'selfdrive/modeld/transforms/loadyuv.cl'}.items():
for xenv in (lenv, lenvCython):
xenv['CXXFLAGS'].append(f'-D{pathdef}_PATH=\\"{File(fn).abspath}\\"')
xenv['CXXFLAGS'].append(f'-D{pathdef}_PATH=\\"{fn}\\"')
# Compile cython
cython_libs = envCython["LIBS"] + libs
commonmodel_lib = lenv.Library('commonmodel', common_src)
lenvCython.Program('models/commonmodel_pyx.so', 'models/commonmodel_pyx.pyx', LIBS=[commonmodel_lib, *cython_libs], FRAMEWORKS=frameworks)
tinygrad_files = ["#"+x for x in glob.glob(env.Dir("#tinygrad_repo").relpath + "/**", recursive=True, root_dir=env.Dir("#").abspath) if 'pycache' not in x]
skip_dm_tinygrad_pkl = os.getenv("SP_SKIP_DM_TINYGRAD_PKL", "").lower() in {"1", "true", "yes", "on"}
allow_host_tinygrad_pkl = os.getenv("SP_ALLOW_HOST_TINYGRAD_PKL", "").lower() in {"1", "true", "yes", "on"}
build_model_tinygrad_pkl = os.getenv("SP_BUILD_MODEL_TINYGRAD_PKL", "").lower() in {"1", "true", "yes", "on"}
has_qcom_gpu = os.path.exists('/dev/kgsl-3d0')
# Compile camera warp artifacts used by modeld/dmonitoringmodeld on C4.
if arch == 'larch64':
compile_warp_flags = 'DEV=QCOM FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0' \
if os.path.exists('/dev/kgsl-3d0') else 'DEV=CPU CPU=1 THREADS=0 NOLOCALS=1 DEBUG=0 HOME=/tmp'
else:
compile_warp_flags = {
'Darwin': f'DEV=CPU THREADS=0 NOLOCALS=1 DEBUG=0 HOME={os.path.expanduser("~")}',
}.get(arch, 'DEV=CPU CPU_LLVM=1 THREADS=0 NOLOCALS=1 DEBUG=0')
build_warp_artifacts = os.getenv("SP_BUILD_WARP_ARTIFACTS", "").lower() in {"1", "true", "yes", "on"}
if build_warp_artifacts:
default_warp_resolutions = [(1928, 1208), (1344, 760)]
raw_warp_resolutions = os.getenv("SP_WARP_RESOLUTIONS", "").strip()
if raw_warp_resolutions:
selected_warp_resolutions = []
seen_warp_resolutions = set()
for token in raw_warp_resolutions.replace(";", ",").split(","):
token = token.strip().lower()
if not token:
continue
w_str, h_str = token.split("x", 1)
wh = (int(w_str), int(h_str))
if wh not in seen_warp_resolutions:
seen_warp_resolutions.add(wh)
selected_warp_resolutions.append(wh)
if not selected_warp_resolutions:
selected_warp_resolutions = default_warp_resolutions
else:
selected_warp_resolutions = default_warp_resolutions
compile_warp_script = "#selfdrive/modeld/compile_warp.py"
warp_targets = []
# Camera resolutions required by modeld compile_warp.py
for w, h in selected_warp_resolutions:
warp_targets += [File(f"models/warp_{w}x{h}_tinygrad.pkl").abspath, File(f"models/dm_warp_{w}x{h}_tinygrad.pkl").abspath]
lenv.Command(
warp_targets,
tinygrad_files + [compile_warp_script],
f'{compile_warp_flags} python3 selfdrive/modeld/compile_warp.py',
)
else:
print("Skipping model warp precompile (set SP_BUILD_WARP_ARTIFACTS=1 to enable)")
# Get model metadata
for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']:
fn = File(f"models/{model_name}").abspath
script_files = [File(Dir("#selfdrive/modeld").File("get_model_metadata.py").abspath)]
cmd = f'python3 {Dir("#selfdrive/modeld").abspath}/get_model_metadata.py {fn}.onnx'
lenv.Command(fn + "_metadata.pkl", [fn + ".onnx"] + tinygrad_files + script_files, cmd)
model_rel_path = f"selfdrive/modeld/models/{model_name}"
model_node_path = f"#{model_rel_path}"
metadata_script = "#selfdrive/modeld/get_model_metadata.py"
cmd = f'python3 selfdrive/modeld/get_model_metadata.py {model_rel_path}.onnx'
lenv.Command(model_node_path + "_metadata.pkl", [model_node_path + ".onnx"] + tinygrad_files + [metadata_script], cmd)
def tg_compile(flags, model_name):
pythonpath_string = 'PYTHONPATH="${PYTHONPATH}:' + env.Dir("#tinygrad_repo").abspath + '"'
fn = File(f"models/{model_name}").abspath
tinygrad_root = "tinygrad_repo"
compile_script = "#tinygrad_repo/examples/openpilot/compile3.py"
pythonpath_string = 'PYTHONPATH="${PYTHONPATH}:' + tinygrad_root + '"'
model_rel_path = f"selfdrive/modeld/models/{model_name}"
model_node_path = f"#{model_rel_path}"
model_input = f"./{model_rel_path}.onnx"
return lenv.Command(
fn + "_tinygrad.pkl",
[fn + ".onnx"] + tinygrad_files,
f'{pythonpath_string} {flags} python3 {Dir("#tinygrad_repo").abspath}/examples/openpilot/compile3.py {fn}.onnx {fn}_tinygrad.pkl'
model_node_path + "_tinygrad.pkl",
[model_node_path + ".onnx"] + tinygrad_files + [compile_script],
f'{pythonpath_string} {flags} python3 tinygrad_repo/examples/openpilot/compile3.py {model_input} {model_rel_path}_tinygrad.pkl'
)
compile_model_tinygrad_pkl = build_model_tinygrad_pkl or allow_host_tinygrad_pkl
if not compile_model_tinygrad_pkl:
print("Skipping tinygrad model PKL compile (set SP_BUILD_MODEL_TINYGRAD_PKL=1 or SP_ALLOW_HOST_TINYGRAD_PKL=1 to enable)")
# Compile small models
for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']:
flags = {
'larch64': 'DEV=QCOM FLOAT16=1 NOLOCALS=1 IMAGE=2 JIT_BATCH_SIZE=0',
'Darwin': f'DEV=CPU HOME={os.path.expanduser("~")}', # tinygrad calls brew which needs a $HOME in the env
}.get(arch, 'DEV=CPU CPU_LLVM=1')
if model_name == 'dmonitoring_model' and skip_dm_tinygrad_pkl:
print("Skipping dmonitoring_model_tinygrad.pkl compile (SP_SKIP_DM_TINYGRAD_PKL enabled)")
continue
if not compile_model_tinygrad_pkl:
continue
if arch == 'larch64':
# On real devices keep QCOM codegen.
flags = 'DEV=QCOM QCOM=1 FLOAT16=1 NOLOCALS=1 IMAGE=2 JIT_BATCH_SIZE=0 DEBUG=0'
else:
# Opt-in host compile (for debugging only).
flags = {
'Darwin': 'DEV=CPU CPU=1 IMAGE=0 NOLOCALS=1 DEBUG=0 HOME=/tmp',
}.get(arch, 'DEV=CPU CPU=1 IMAGE=0 NOLOCALS=1 DEBUG=0')
tg_compile(flags, model_name)
# Compile BIG model if USB GPU is available
@@ -62,7 +127,7 @@ if "USBGPU" in os.environ:
devs = subprocess.check_output('python3 -c "from tinygrad import Device; print(list(Device.get_available_devices()))"', shell=True, cwd=env.Dir('#').abspath)
if b"AMD" in devs:
print("USB GPU detected... building")
flags = "DEV=AMD AMD_IFACE=USB AMD_LLVM=1 NOLOCALS=0 IMAGE=0"
flags = "DEV=AMD AMD=1 AMD_IFACE=USB AMD_LLVM=1 NOLOCALS=0 IMAGE=0 DEBUG=0"
bp = tg_compile(flags, "big_driving_policy")
bv = tg_compile(flags, "big_driving_vision")
lenv.SideEffect('lock', [bp, bv]) # tg doesn't support multi-process so build serially
+242
View File
@@ -0,0 +1,242 @@
#!/usr/bin/env python3
import os
import time
import pickle
import numpy as np
from pathlib import Path
from tinygrad.tensor import Tensor
from tinygrad.helpers import Context
from tinygrad.device import Device
from tinygrad.engine.jit import TinyJit
from openpilot.system.camerad.cameras.nv12_info import get_nv12_info
MODELS_DIR = Path(__file__).parent / 'models'
WARP_COMPILE_ITERS = max(2, int(os.getenv("WARP_COMPILE_ITERS", "2")))
# Keep compile_warp standalone so it does not depend on common.transformations C extensions.
MEDMODEL_INPUT_SIZE = (512, 256)
DM_INPUT_SIZE = (1440, 960)
DEFAULT_CAMERA_CONFIGS = [
(1928, 1208), # tici / tizi
(1344, 760), # mici
]
def _parse_camera_configs_from_env() -> list[tuple[int, int]]:
raw = os.getenv("SP_WARP_RESOLUTIONS", "").strip()
if not raw:
return DEFAULT_CAMERA_CONFIGS
parsed: list[tuple[int, int]] = []
for token in raw.replace(";", ",").split(","):
token = token.strip().lower()
if not token:
continue
try:
w_str, h_str = token.split("x", 1)
parsed.append((int(w_str), int(h_str)))
except Exception as e:
raise ValueError(f"Invalid SP_WARP_RESOLUTIONS token: {token!r}") from e
# Keep first occurrence order, drop duplicates.
deduped: list[tuple[int, int]] = []
seen: set[tuple[int, int]] = set()
for wh in parsed:
if wh not in seen:
seen.add(wh)
deduped.append(wh)
return deduped or DEFAULT_CAMERA_CONFIGS
CAMERA_CONFIGS = _parse_camera_configs_from_env()
UV_SCALE_MATRIX = np.array([[0.5, 0, 0], [0, 0.5, 0], [0, 0, 1]], dtype=np.float32)
UV_SCALE_MATRIX_INV = np.linalg.inv(UV_SCALE_MATRIX)
IMG_BUFFER_SHAPE = (30, MEDMODEL_INPUT_SIZE[1] // 2, MEDMODEL_INPUT_SIZE[0] // 2)
def warp_pkl_path(w, h):
return MODELS_DIR / f'warp_{w}x{h}_tinygrad.pkl'
def dm_warp_pkl_path(w, h):
return MODELS_DIR / f'dm_warp_{w}x{h}_tinygrad.pkl'
def warp_perspective_tinygrad(src_flat, M_inv, dst_shape, src_shape, stride_pad):
w_dst, h_dst = dst_shape
h_src, w_src = src_shape
x = Tensor.arange(w_dst).reshape(1, w_dst).expand(h_dst, w_dst).reshape(-1)
y = Tensor.arange(h_dst).reshape(h_dst, 1).expand(h_dst, w_dst).reshape(-1)
# inline 3x3 matmul as elementwise to avoid reduce op (enables fusion with gather)
src_x = M_inv[0, 0] * x + M_inv[0, 1] * y + M_inv[0, 2]
src_y = M_inv[1, 0] * x + M_inv[1, 1] * y + M_inv[1, 2]
src_w = M_inv[2, 0] * x + M_inv[2, 1] * y + M_inv[2, 2]
src_x = src_x / src_w
src_y = src_y / src_w
x_nn_clipped = Tensor.round(src_x).clip(0, w_src - 1).cast('int')
y_nn_clipped = Tensor.round(src_y).clip(0, h_src - 1).cast('int')
idx = y_nn_clipped * (w_src + stride_pad) + x_nn_clipped
return src_flat[idx]
def frames_to_tensor(frames, model_w, model_h):
H = (frames.shape[0] * 2) // 3
W = frames.shape[1]
in_img1 = Tensor.cat(frames[0:H:2, 0::2],
frames[1:H:2, 0::2],
frames[0:H:2, 1::2],
frames[1:H:2, 1::2],
frames[H:H+H//4].reshape((H//2, W//2)),
frames[H+H//4:H+H//2].reshape((H//2, W//2)), dim=0).reshape((6, H//2, W//2))
return in_img1
def make_frame_prepare(cam_w, cam_h, model_w, model_h):
stride, y_height, uv_height, _ = get_nv12_info(cam_w, cam_h)
uv_offset = stride * y_height
stride_pad = stride - cam_w
def frame_prepare_tinygrad(input_frame, M_inv):
# UV_SCALE @ M_inv @ UV_SCALE_INV simplifies to elementwise scaling
M_inv_uv = M_inv * Tensor([[1.0, 1.0, 0.5], [1.0, 1.0, 0.5], [2.0, 2.0, 1.0]])
# deinterleave NV12 UV plane (UVUV... -> separate U, V)
uv = input_frame[uv_offset:uv_offset + uv_height * stride].reshape(uv_height, stride)
with Context(SPLIT_REDUCEOP=0):
y = warp_perspective_tinygrad(input_frame[:cam_h*stride],
M_inv, (model_w, model_h),
(cam_h, cam_w), stride_pad).realize()
u = warp_perspective_tinygrad(uv[:cam_h//2, :cam_w:2].flatten(),
M_inv_uv, (model_w//2, model_h//2),
(cam_h//2, cam_w//2), 0).realize()
v = warp_perspective_tinygrad(uv[:cam_h//2, 1:cam_w:2].flatten(),
M_inv_uv, (model_w//2, model_h//2),
(cam_h//2, cam_w//2), 0).realize()
yuv = y.cat(u).cat(v).reshape((model_h * 3 // 2, model_w))
tensor = frames_to_tensor(yuv, model_w, model_h)
return tensor
return frame_prepare_tinygrad
def make_update_img_input(frame_prepare, model_w, model_h):
def update_img_input_tinygrad(tensor, frame, M_inv):
M_inv = M_inv.to(Device.DEFAULT)
new_img = frame_prepare(frame, M_inv)
full_buffer = tensor[6:].cat(new_img, dim=0).contiguous()
return full_buffer, Tensor.cat(full_buffer[:6], full_buffer[-6:], dim=0).contiguous().reshape(1, 12, model_h//2, model_w//2)
return update_img_input_tinygrad
def make_update_both_imgs(frame_prepare, model_w, model_h):
update_img = make_update_img_input(frame_prepare, model_w, model_h)
def update_both_imgs_tinygrad(calib_img_buffer, new_img, M_inv,
calib_big_img_buffer, new_big_img, M_inv_big):
calib_img_buffer, calib_img_pair = update_img(calib_img_buffer, new_img, M_inv)
calib_big_img_buffer, calib_big_img_pair = update_img(calib_big_img_buffer, new_big_img, M_inv_big)
return calib_img_buffer, calib_img_pair, calib_big_img_buffer, calib_big_img_pair
return update_both_imgs_tinygrad
def make_warp_dm(cam_w, cam_h, dm_w, dm_h):
stride, y_height, _, _ = get_nv12_info(cam_w, cam_h)
stride_pad = stride - cam_w
def warp_dm(input_frame, M_inv):
M_inv = M_inv.to(Device.DEFAULT)
result = warp_perspective_tinygrad(input_frame[:cam_h*stride], M_inv, (dm_w, dm_h), (cam_h, cam_w), stride_pad).reshape(-1, dm_h * dm_w)
return result
return warp_dm
def compile_modeld_warp(cam_w, cam_h):
model_w, model_h = MEDMODEL_INPUT_SIZE
_, _, _, yuv_size = get_nv12_info(cam_w, cam_h)
print(f"Compiling modeld warp for {cam_w}x{cam_h} ({WARP_COMPILE_ITERS} iters)...", flush=True)
frame_prepare = make_frame_prepare(cam_w, cam_h, model_w, model_h)
update_both_imgs = make_update_both_imgs(frame_prepare, model_w, model_h)
update_img_jit = TinyJit(update_both_imgs, prune=True)
full_buffer = Tensor.zeros(IMG_BUFFER_SHAPE, dtype='uint8').contiguous().realize()
big_full_buffer = Tensor.zeros(IMG_BUFFER_SHAPE, dtype='uint8').contiguous().realize()
full_buffer_np = np.zeros(IMG_BUFFER_SHAPE, dtype=np.uint8)
big_full_buffer_np = np.zeros(IMG_BUFFER_SHAPE, dtype=np.uint8)
for i in range(WARP_COMPILE_ITERS):
new_frame_np = (32 * np.random.randn(yuv_size).astype(np.float32) + 128).clip(0, 255).astype(np.uint8)
img_inputs = [full_buffer,
Tensor.from_blob(new_frame_np.ctypes.data, (yuv_size,), dtype='uint8').realize(),
Tensor(Tensor.randn(3, 3).mul(8).realize().numpy(), device='NPY')]
new_big_frame_np = (32 * np.random.randn(yuv_size).astype(np.float32) + 128).clip(0, 255).astype(np.uint8)
big_img_inputs = [big_full_buffer,
Tensor.from_blob(new_big_frame_np.ctypes.data, (yuv_size,), dtype='uint8').realize(),
Tensor(Tensor.randn(3, 3).mul(8).realize().numpy(), device='NPY')]
inputs = img_inputs + big_img_inputs
Device.default.synchronize()
inputs_np = [x.numpy() for x in inputs]
inputs_np[0] = full_buffer_np
inputs_np[3] = big_full_buffer_np
st = time.perf_counter()
out = update_img_jit(*inputs)
full_buffer = out[0].contiguous().realize().clone()
big_full_buffer = out[2].contiguous().realize().clone()
mt = time.perf_counter()
Device.default.synchronize()
et = time.perf_counter()
print(f" [{i+1}/{WARP_COMPILE_ITERS}] enqueue {(mt-st)*1e3:6.2f} ms -- total {(et-st)*1e3:6.2f} ms", flush=True)
pkl_path = warp_pkl_path(cam_w, cam_h)
with open(pkl_path, "wb") as f:
pickle.dump(update_img_jit, f)
print(f" Saved to {pkl_path}", flush=True)
jit = pickle.load(open(pkl_path, "rb"))
jit(*inputs)
def compile_dm_warp(cam_w, cam_h):
dm_w, dm_h = DM_INPUT_SIZE
_, _, _, yuv_size = get_nv12_info(cam_w, cam_h)
print(f"Compiling DM warp for {cam_w}x{cam_h} ({WARP_COMPILE_ITERS} iters)...", flush=True)
warp_dm = make_warp_dm(cam_w, cam_h, dm_w, dm_h)
warp_dm_jit = TinyJit(warp_dm, prune=True)
for i in range(WARP_COMPILE_ITERS):
inputs = [Tensor.from_blob((32 * Tensor.randn(yuv_size,) + 128).cast(dtype='uint8').realize().numpy().ctypes.data, (yuv_size,), dtype='uint8'),
Tensor(Tensor.randn(3, 3).mul(8).realize().numpy(), device='NPY')]
Device.default.synchronize()
st = time.perf_counter()
warp_dm_jit(*inputs)
mt = time.perf_counter()
Device.default.synchronize()
et = time.perf_counter()
print(f" [{i+1}/{WARP_COMPILE_ITERS}] enqueue {(mt-st)*1e3:6.2f} ms -- total {(et-st)*1e3:6.2f} ms", flush=True)
pkl_path = dm_warp_pkl_path(cam_w, cam_h)
with open(pkl_path, "wb") as f:
pickle.dump(warp_dm_jit, f)
print(f" Saved to {pkl_path}", flush=True)
def run_and_save_pickle():
for cam_w, cam_h in CAMERA_CONFIGS:
compile_modeld_warp(cam_w, cam_h)
compile_dm_warp(cam_w, cam_h)
if __name__ == "__main__":
run_and_save_pickle()
+75 -18
View File
@@ -3,7 +3,6 @@ import os
from openpilot.system.hardware import TICI
os.environ['DEV'] = 'QCOM' if TICI else 'CPU'
from tinygrad.tensor import Tensor
from tinygrad.dtype import dtypes
import time
import pickle
import numpy as np
@@ -16,50 +15,109 @@ from openpilot.common.swaglog import cloudlog
from openpilot.common.realtime import config_realtime_process
from openpilot.common.transformations.model import dmonitoringmodel_intrinsics
from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye
from openpilot.selfdrive.modeld.models.commonmodel_pyx import CLContext, MonitoringModelFrame
from openpilot.system.camerad.cameras.nv12_info import get_nv12_info
from openpilot.common.file_chunker import read_file_chunked
from openpilot.selfdrive.modeld.parse_model_outputs import sigmoid, safe_exp
from openpilot.selfdrive.modeld.runners.tinygrad_helpers import qcom_tensor_from_opencl_address
PROCESS_NAME = "selfdrive.modeld.dmonitoringmodeld"
SEND_RAW_PRED = os.getenv('SEND_RAW_PRED')
MODEL_PKL_PATH = Path(__file__).parent / 'models/dmonitoring_model_tinygrad.pkl'
METADATA_PATH = Path(__file__).parent / 'models/dmonitoring_model_metadata.pkl'
MODELS_DIR = Path(__file__).parent / 'models'
class ModelState:
inputs: dict[str, np.ndarray]
output: np.ndarray
def __init__(self, cl_ctx):
def __init__(self):
with open(METADATA_PATH, 'rb') as f:
model_metadata = pickle.load(f)
self.input_shapes = model_metadata['input_shapes']
self.output_slices = model_metadata['output_slices']
self.frame = MonitoringModelFrame(cl_ctx)
self.numpy_inputs = {
'calib': np.zeros(self.input_shapes['calib'], dtype=np.float32),
}
self.warp_inputs_np = {'transform': np.zeros((3,3), dtype=np.float32)}
self.warp_inputs = {k: Tensor(v, device='NPY') for k,v in self.warp_inputs_np.items()}
self.frame_buf_params = None
self.tensor_inputs = {k: Tensor(v, device='NPY').realize() for k,v in self.numpy_inputs.items()}
with open(MODEL_PKL_PATH, "rb") as f:
self.model_run = pickle.load(f)
self._blob_cache : dict[int, Tensor] = {}
self.image_warp = None
self._warp_rebuild_attempted: set[tuple[int, int]] = set()
self._warp_backend_rebuild_attempted: set[tuple[int, int]] = set()
self.model_run = pickle.loads(read_file_chunked(str(MODEL_PKL_PATH)))
def _load_or_rebuild_dm_warp(self, width: int, height: int):
warp_path = MODELS_DIR / f'dm_warp_{width}x{height}_tinygrad.pkl'
resolution_key = (width, height)
def load_warp():
with open(warp_path, "rb") as f:
return pickle.load(f)
try:
return load_warp()
except Exception as error:
if resolution_key in self._warp_rebuild_attempted:
raise
self._warp_rebuild_attempted.add(resolution_key)
cloudlog.exception(f"Failed to load DM warp artifact {warp_path}: {error}")
cloudlog.warning(f"Rebuilding DM warp artifact for {width}x{height}")
try:
warp_path.unlink(missing_ok=True)
except Exception:
pass
from openpilot.selfdrive.modeld.compile_warp import compile_dm_warp
compile_dm_warp(width, height)
try:
return load_warp()
except Exception as retry_error:
cloudlog.exception(f"Reload failed after rebuilding {warp_path}: {retry_error}")
raise
def run(self, buf: VisionBuf, calib: np.ndarray, transform: np.ndarray) -> tuple[np.ndarray, float]:
self.numpy_inputs['calib'][0,:] = calib
t1 = time.perf_counter()
input_img_cl = self.frame.prepare(buf, transform.flatten())
if TICI:
# The imgs tensors are backed by opencl memory, only need init once
if 'input_img' not in self.tensor_inputs:
self.tensor_inputs['input_img'] = qcom_tensor_from_opencl_address(input_img_cl.mem_address, self.input_shapes['input_img'], dtype=dtypes.uint8)
else:
self.tensor_inputs['input_img'] = Tensor(self.frame.buffer_from_cl(input_img_cl).reshape(self.input_shapes['input_img']), dtype=dtypes.uint8).realize()
if self.image_warp is None:
self.frame_buf_params = get_nv12_info(buf.width, buf.height)
self.image_warp = self._load_or_rebuild_dm_warp(buf.width, buf.height)
ptr = buf.data.ctypes.data
# There is a ringbuffer of imgs, just cache tensors pointing to all of them
if ptr not in self._blob_cache:
self._blob_cache[ptr] = Tensor.from_blob(ptr, (self.frame_buf_params[3],), dtype='uint8')
self.warp_inputs_np['transform'][:] = transform[:]
resolution_key = (buf.width, buf.height)
try:
self.tensor_inputs['input_img'] = self.image_warp(self._blob_cache[ptr], self.warp_inputs['transform']).realize()
except AssertionError as error:
# Handle runtime backend mismatch (e.g. CPU-captured warp artifact on QCOM device).
if "args mismatch in JIT" not in str(error) or resolution_key in self._warp_backend_rebuild_attempted:
raise
output = self.model_run(**self.tensor_inputs).contiguous().realize().uop.base.buffer.numpy()
self._warp_backend_rebuild_attempted.add(resolution_key)
cloudlog.warning(f"DM warp JIT backend mismatch for {buf.width}x{buf.height}; rebuilding artifact for active backend")
warp_path = MODELS_DIR / f'dm_warp_{buf.width}x{buf.height}_tinygrad.pkl'
try:
warp_path.unlink(missing_ok=True)
except Exception:
pass
from openpilot.selfdrive.modeld.compile_warp import compile_dm_warp
compile_dm_warp(buf.width, buf.height)
self.image_warp = self._load_or_rebuild_dm_warp(buf.width, buf.height)
self.tensor_inputs['input_img'] = self.image_warp(self._blob_cache[ptr], self.warp_inputs['transform']).realize()
output = self.model_run(**self.tensor_inputs).contiguous().realize().uop.base.buffer.numpy().flatten()
t2 = time.perf_counter()
return output, t2 - t1
@@ -107,12 +165,11 @@ def get_driverstate_packet(model_output, frame_id: int, location_ts: int, exec_t
def main():
config_realtime_process(7, 5)
cl_context = CLContext()
model = ModelState(cl_context)
model = ModelState()
cloudlog.warning("models loaded, dmonitoringmodeld starting")
cloudlog.warning("connecting to driver stream")
vipc_client = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_DRIVER, True, cl_context)
vipc_client = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_DRIVER, True)
while not vipc_client.connect(False):
time.sleep(0.1)
assert vipc_client.is_connected()
+45 -12
View File
@@ -31,16 +31,47 @@ from openpilot.selfdrive.modeld.constants import ModelConstants, Plan
from openpilot.selfdrive.modeld.models.commonmodel_pyx import DrivingModelFrame, CLContext
from openpilot.selfdrive.modeld.runners.tinygrad_helpers import qcom_tensor_from_opencl_address
from openpilot.frogpilot.common.frogpilot_variables import get_frogpilot_toggles
from openpilot.frogpilot.common.frogpilot_variables import MODELS_PATH, get_frogpilot_toggles
PROCESS_NAME = "selfdrive.modeld.modeld"
SEND_RAW_PRED = os.getenv('SEND_RAW_PRED')
VISION_PKL_PATH = Path(__file__).parent / 'models/driving_vision_tinygrad.pkl'
POLICY_PKL_PATH = Path(__file__).parent / 'models/driving_policy_tinygrad.pkl'
VISION_METADATA_PATH = Path(__file__).parent / 'models/driving_vision_metadata.pkl'
POLICY_METADATA_PATH = Path(__file__).parent / 'models/driving_policy_metadata.pkl'
BUILTIN_MODEL_DIR = Path(__file__).parent / "models"
BUILTIN_VISION_PKL_PATH = BUILTIN_MODEL_DIR / "driving_vision_tinygrad.pkl"
BUILTIN_POLICY_PKL_PATH = BUILTIN_MODEL_DIR / "driving_policy_tinygrad.pkl"
BUILTIN_VISION_METADATA_PATH = BUILTIN_MODEL_DIR / "driving_vision_metadata.pkl"
BUILTIN_POLICY_METADATA_PATH = BUILTIN_MODEL_DIR / "driving_policy_metadata.pkl"
def _decode_param(value) -> str:
if isinstance(value, bytes):
return value.decode("utf-8")
return str(value or "")
def _resolve_model_paths(params: Params) -> tuple[str, dict[str, Path]]:
model_key = _decode_param(params.get("DrivingModel")).strip()
candidates = [model_key] if model_key else []
if model_key.endswith("_default"):
candidates.append(model_key[:-8])
for candidate in candidates:
files = {
"vision_pkl": MODELS_PATH / f"{candidate}_driving_vision_tinygrad.pkl",
"policy_pkl": MODELS_PATH / f"{candidate}_driving_policy_tinygrad.pkl",
"vision_meta": MODELS_PATH / f"{candidate}_driving_vision_metadata.pkl",
"policy_meta": MODELS_PATH / f"{candidate}_driving_policy_metadata.pkl",
}
if all(path.is_file() for path in files.values()):
return candidate, files
return "builtin", {
"vision_pkl": BUILTIN_VISION_PKL_PATH,
"policy_pkl": BUILTIN_POLICY_PKL_PATH,
"vision_meta": BUILTIN_VISION_METADATA_PATH,
"policy_meta": BUILTIN_POLICY_METADATA_PATH,
}
LAT_SMOOTH_SECONDS = 0.0
LONG_SMOOTH_SECONDS = 0.3
@@ -143,15 +174,15 @@ class ModelState:
output: np.ndarray
prev_desire: np.ndarray # for tracking the rising edge of the pulse
def __init__(self, context: CLContext):
with open(VISION_METADATA_PATH, 'rb') as f:
def __init__(self, context: CLContext, model_paths: dict[str, Path]):
with open(model_paths["vision_meta"], 'rb') as f:
vision_metadata = pickle.load(f)
self.vision_input_shapes = vision_metadata['input_shapes']
self.vision_input_names = list(self.vision_input_shapes.keys())
self.vision_output_slices = vision_metadata['output_slices']
vision_output_size = vision_metadata['output_shapes']['outputs'][1]
with open(POLICY_METADATA_PATH, 'rb') as f:
with open(model_paths["policy_meta"], 'rb') as f:
policy_metadata = pickle.load(f)
self.policy_input_shapes = policy_metadata['input_shapes']
self.policy_output_slices = policy_metadata['output_slices']
@@ -174,10 +205,10 @@ class ModelState:
self.policy_output = np.zeros(policy_output_size, dtype=np.float32)
self.parser = Parser()
with open(VISION_PKL_PATH, "rb") as f:
with open(model_paths["vision_pkl"], "rb") as f:
self.vision_run = pickle.load(f)
with open(POLICY_PKL_PATH, "rb") as f:
with open(model_paths["policy_pkl"], "rb") as f:
self.policy_run = pickle.load(f)
def slice_outputs(self, model_outputs: np.ndarray, output_slices: dict[str, slice]) -> dict[str, np.ndarray]:
@@ -238,8 +269,11 @@ def main(demo=False):
st = time.monotonic()
cloudlog.warning("setting up CL context")
cl_context = CLContext()
params = Params()
selected_model_key, model_paths = _resolve_model_paths(params)
cloudlog.warning("CL context ready; loading model")
model = ModelState(cl_context)
model = ModelState(cl_context, model_paths)
cloudlog.warning(f"selected model source: {selected_model_key}")
cloudlog.warning(f"models loaded in {time.monotonic() - st:.1f}s, modeld starting")
# visionipc clients
@@ -270,7 +304,6 @@ def main(demo=False):
sm = SubMaster(["deviceState", "carState", "roadCameraState", "liveCalibration", "driverMonitoringState", "carControl", "liveDelay"])
publish_state = PublishState()
params = Params()
# setup filter to track dropped frames
frame_dropped_filter = FirstOrderFilter(0., 10., 1. / ModelConstants.MODEL_RUN_FREQ)
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
-1
View File
@@ -1,3 +1,2 @@
pandad
pandad_api_impl.cpp
tests/test_pandad_usbprotocol
+1 -1
View File
@@ -137,7 +137,7 @@ std::optional<std::string> Panda::get_serial() {
bool Panda::up_to_date() {
if (auto fw_sig = get_firmware_version()) {
for (auto fn : { "panda.bin.signed", "panda_h7.bin.signed" }) {
for (auto fn : { "panda.bin.signed", "panda_h7.bin.signed", "panda_remote.bin.signed", "panda_h7_remote.bin.signed" }) {
auto content = util::read_file(std::string("../../panda/board/obj/") + fn);
if (content.size() >= fw_sig->size() &&
memcmp(content.data() + content.size() - fw_sig->size(), fw_sig->data(), fw_sig->size()) == 0) {
BIN
View File
Binary file not shown.
+32 -7
View File
@@ -8,20 +8,39 @@ import subprocess
from panda import Panda, PandaDFU, PandaProtocolMismatch, FW_PATH
from openpilot.common.basedir import BASEDIR
from openpilot.common.params import Params
from openpilot.common.params import Params, UnknownKeyName
from openpilot.system.hardware import HARDWARE
from openpilot.common.swaglog import cloudlog
def get_expected_signature(panda: Panda) -> bytes:
def get_expected_firmware_path(panda: Panda, remote_start: bool) -> str:
app_fn = panda.get_mcu_type().config.app_fn
if remote_start:
remote_fn = "panda_h7_remote.bin.signed" if app_fn == "panda_h7.bin.signed" else "panda_remote.bin.signed"
remote_path = os.path.join(FW_PATH, remote_fn)
if os.path.isfile(remote_path):
return remote_path
cloudlog.warning(f"Remote-start panda firmware not found: {remote_path}, falling back to default")
return os.path.join(FW_PATH, app_fn)
def get_expected_signature(panda: Panda, remote_start: bool) -> bytes:
try:
fn = os.path.join(FW_PATH, panda.get_mcu_type().config.app_fn)
fn = get_expected_firmware_path(panda, remote_start)
return Panda.get_signature_from_firmware(fn)
except Exception:
cloudlog.exception("Error computing expected signature")
return b""
def flash_panda(panda_serial: str) -> Panda:
def get_remote_start_boots_comma(params: Params) -> bool:
try:
return params.get_bool("RemoteStartBootsComma")
except UnknownKeyName:
return False
def flash_panda(panda_serial: str, remote_start: bool) -> Panda:
try:
panda = Panda(panda_serial)
except PandaProtocolMismatch:
@@ -29,7 +48,8 @@ def flash_panda(panda_serial: str) -> Panda:
HARDWARE.recover_internal_panda()
raise
fw_signature = get_expected_signature(panda)
fw_path = get_expected_firmware_path(panda, remote_start)
fw_signature = get_expected_signature(panda, remote_start)
internal_panda = panda.is_internal()
panda_version = "bootstub" if panda.bootstub else panda.get_version()
@@ -38,7 +58,7 @@ def flash_panda(panda_serial: str) -> Panda:
if panda.bootstub or panda_signature != fw_signature:
cloudlog.info("Panda firmware out of date, update required")
panda.flash()
panda.flash(fn=fw_path)
cloudlog.info("Done flashing")
if panda.bootstub:
@@ -112,8 +132,9 @@ def main() -> None:
# Flash pandas
pandas: list[Panda] = []
remote_start = get_remote_start_boots_comma(params)
for serial in panda_serials:
pandas.append(flash_panda(serial))
pandas.append(flash_panda(serial, remote_start))
# Ensure internal panda is present if expected
internal_pandas = [panda for panda in pandas if panda.is_internal()]
@@ -165,6 +186,10 @@ def main() -> None:
first_run = False
# run pandad with all connected serials as arguments
if get_remote_start_boots_comma(params):
os.environ["BOARDD_SKIP_FW_CHECK"] = "1"
else:
os.environ.pop("BOARDD_SKIP_FW_CHECK", None)
os.environ['MANAGER_DAEMON'] = 'pandad'
process = subprocess.Popen(["./pandad", *panda_serials], cwd=os.path.join(BASEDIR, "selfdrive/pandad"))
process.wait()
+8
View File
@@ -0,0 +1,8 @@
"""Compatibility alias for legacy/misspelled imports.
Prefer: openpilot.selfdrive.pandad.pandad_api_impl
"""
from openpilot.selfdrive.pandad.pandad_api_impl import can_capnp_to_list, can_list_to_can_capnp
__all__ = ["can_list_to_can_capnp", "can_capnp_to_list"]
Binary file not shown.
+8
View File
@@ -0,0 +1,8 @@
"""Compatibility alias for legacy imports.
Prefer: openpilot.selfdrive.pandad.pandad_api_impl
"""
from openpilot.selfdrive.pandad.pandad_api_impl import can_capnp_to_list, can_list_to_can_capnp
__all__ = ["can_list_to_can_capnp", "can_capnp_to_list"]
+9 -8
View File
@@ -228,7 +228,7 @@ class SelfdriveD:
# body always wants to enable
self.events.add(EventName.pcmEnable)
# Disable on rising edge of accelerator or brake. Also disable on brake when speed > 0
# Disable on rising edge of accelerator or brake. Also disable on brake when speed > 0.
if (CS.gasPressed and not self.CS_prev.gasPressed and self.disengage_on_accelerator) or \
(CS.brakePressed and (not self.CS_prev.brakePressed or not CS.standstill)) or \
(CS.regenBraking and (not self.CS_prev.regenBraking or not CS.standstill)):
@@ -299,15 +299,16 @@ class SelfdriveD:
else:
self.events.add(EventName.laneChangeBlocked)
else:
# Keep pre-lane-change prompts visible even when lane-width gate blocks the maneuver.
# This preserves the onroad "Changing lanes" guidance while still surfacing
# "No Lane Available" when the configured width threshold is not met.
if direction == LaneChangeDirection.left:
if self.sm['frogpilotPlan'].laneWidthLeft >= self.frogpilot_toggles.lane_detection_width:
self.events.add(EventName.preLaneChangeLeft)
else:
self.events.add(EventName.preLaneChangeLeft)
if self.sm['frogpilotPlan'].laneWidthLeft < self.frogpilot_toggles.lane_detection_width:
self.frogpilot_events.add(FrogPilotEventName.noLaneAvailable)
else:
if self.sm['frogpilotPlan'].laneWidthRight >= self.frogpilot_toggles.lane_detection_width:
self.events.add(EventName.preLaneChangeRight)
else:
elif direction == LaneChangeDirection.right:
self.events.add(EventName.preLaneChangeRight)
if self.sm['frogpilotPlan'].laneWidthRight < self.frogpilot_toggles.lane_detection_width:
self.frogpilot_events.add(FrogPilotEventName.noLaneAvailable)
elif self.sm['modelV2'].meta.laneChangeState in (LaneChangeState.laneChangeStarting,
LaneChangeState.laneChangeFinishing):
+6 -2
View File
@@ -5,12 +5,14 @@ base_libs = [common, messaging, visionipc, transformations,
'm', 'OpenCL', 'ssl', 'crypto', 'pthread'] + qt_env["LIBS"]
# FrogPilot variables
base_libs += ['avcodec', 'avformat', 'avutil', 'yuv', 'OmxCore']
base_libs += ['avcodec', 'avformat', 'avutil', 'swresample', 'yuv', 'OmxCore']
if arch == 'larch64':
base_libs.append('EGL')
if arch == "Darwin":
if "OmxCore" in base_libs:
base_libs.remove("OmxCore")
del base_libs[base_libs.index('OpenCL')]
qt_env['FRAMEWORKS'] += ['OpenCL']
@@ -44,6 +46,7 @@ qt_env['CPPPATH'] += ["../../frogpilot/ui/screenrecorder/openmax/include/"]
frogpilot_src = ["../../frogpilot/ui/frogpilot_ui.cc", "../../frogpilot/ui/qt/offroad/data_settings.cc",
"../../frogpilot/ui/qt/offroad/device_settings.cc", "../../frogpilot/ui/qt/offroad/frogpilot_settings.cc",
"../../frogpilot/ui/qt/offroad/expandable_multi_option_dialog.cc",
"../../frogpilot/ui/qt/offroad/lateral_settings.cc", "../../frogpilot/ui/qt/offroad/longitudinal_settings.cc",
"../../frogpilot/ui/qt/offroad/maps_settings.cc", "../../frogpilot/ui/qt/offroad/model_settings.cc",
"../../frogpilot/ui/qt/offroad/navigation_settings.cc", "../../frogpilot/ui/qt/offroad/sounds_settings.cc",
@@ -78,7 +81,8 @@ with open(File(translations_assets_src).abspath, 'w') as f:
# build assets
assets = "#selfdrive/assets/assets.cc"
assets_src = "#selfdrive/assets/assets.qrc"
qt_env.Command(assets, [assets_src, translations_assets_src], f"rcc $SOURCES -o $TARGET")
rcc_bin = qt_env.get("SP_QT_RCC", "rcc")
qt_env.Command(assets, [assets_src, translations_assets_src], f"{rcc_bin} $SOURCES -o $TARGET")
qt_env.Depends(assets, Glob('#selfdrive/assets/*', exclude=[assets, assets_src, translations_assets_src, "#selfdrive/assets/assets.o"]) + [lrelease])
asset_obj = qt_env.Object("assets", assets)
+1
View File
@@ -144,6 +144,7 @@ int cachedFetch(const std::string &cache) {
LOGD("Fetching with cache: %s", cache.c_str());
run(util::string_format("cp -rp %s %s", cache.c_str(), TMP_INSTALL_PATH).c_str());
run(util::string_format("cd %s && git remote set-url origin %s", TMP_INSTALL_PATH, GIT_URL.c_str()).c_str());
run(util::string_format("cd %s && git remote set-branches --add origin %s", TMP_INSTALL_PATH, migrated_branch.c_str()).c_str());
renderProgress(10);
+7 -4
View File
@@ -1,6 +1,7 @@
import pyray as rl
from enum import IntEnum
import cereal.messaging as messaging
from openpilot.system.hardware import PC
from openpilot.system.ui.lib.application import gui_app
from openpilot.selfdrive.ui.layouts.sidebar import Sidebar, SIDEBAR_WIDTH
from openpilot.selfdrive.ui.layouts.home import HomeLayout
@@ -36,10 +37,12 @@ class MainLayout(Widget):
# Set callbacks
self._setup_callbacks()
# Start onboarding if terms or training not completed
self._onboarding_window = OnboardingWindow()
if not self._onboarding_window.completed:
gui_app.set_modal_overlay(self._onboarding_window)
# Skip onboarding on desktop; keep normal flow on device.
self._onboarding_window = None
if not PC:
self._onboarding_window = OnboardingWindow()
if not self._onboarding_window.completed:
gui_app.set_modal_overlay(self._onboarding_window)
def _render(self, _):
self._handle_onroad_transition()
@@ -25,6 +25,10 @@ DESCRIPTIONS = {
"Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha. " +
"Changing this setting will restart openpilot if the car is powered on."
),
'use_prebuilt': tr_noop(
"When enabled (default), openpilot uses prebuilt binaries at startup and skips source compilation. " +
"Disable this if you intend to edit code and compile directly on-device."
),
}
@@ -43,6 +47,14 @@ class DeveloperLayout(Widget):
enabled=ui_state.is_offroad,
)
self._use_prebuilt_toggle = toggle_item(
lambda: tr("Use Prebuilt Binaries"),
description=lambda: tr(DESCRIPTIONS["use_prebuilt"]),
initial_state=self._params.get_bool("UsePrebuilt"),
callback=self._on_use_prebuilt,
enabled=ui_state.is_offroad,
)
# SSH enable toggle + SSH key management
self._ssh_toggle = toggle_item(
lambda: tr("Enable SSH"),
@@ -85,6 +97,7 @@ class DeveloperLayout(Widget):
self._scroller = Scroller([
self._adb_toggle,
self._use_prebuilt_toggle,
self._ssh_toggle,
self._ssh_keys,
self._joystick_toggle,
@@ -133,6 +146,7 @@ class DeveloperLayout(Widget):
# refresh toggles from params to mirror external changes
for key, item in (
("AdbEnabled", self._adb_toggle),
("UsePrebuilt", self._use_prebuilt_toggle),
("SshEnabled", self._ssh_toggle),
("JoystickDebugMode", self._joystick_toggle),
("LongitudinalManeuverMode", self._long_maneuver_toggle),
@@ -152,6 +166,9 @@ class DeveloperLayout(Widget):
def _on_enable_ssh(self, state: bool):
self._params.put_bool("SshEnabled", state)
def _on_use_prebuilt(self, state: bool):
self._params.put_bool("UsePrebuilt", state)
def _on_joystick_debug_mode(self, state: bool):
self._params.put_bool("JoystickDebugMode", state)
self._params.put_bool("LongitudinalManeuverMode", False)
+53 -22
View File
@@ -5,6 +5,7 @@ from collections.abc import Callable
from openpilot.selfdrive.ui.layouts.settings.developer import DeveloperLayout
from openpilot.selfdrive.ui.layouts.settings.device import DeviceLayout
from openpilot.selfdrive.ui.layouts.settings.firehose import FirehoseLayout
from openpilot.selfdrive.ui.layouts.settings.starpilot.main_panel import StarPilotLayout
from openpilot.selfdrive.ui.layouts.settings.software import SoftwareLayout
from openpilot.selfdrive.ui.layouts.settings.toggles import TogglesLayout
from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos
@@ -13,6 +14,7 @@ from openpilot.system.ui.lib.text_measure import measure_text_cached
from openpilot.system.ui.lib.wifi_manager import WifiManager
from openpilot.system.ui.widgets import Widget
from openpilot.system.ui.widgets.network import NetworkUI
from openpilot.common.params import Params
# Constants
SIDEBAR_WIDTH = 500
@@ -37,6 +39,7 @@ class PanelType(IntEnum):
SOFTWARE = 3
FIREHOSE = 4
DEVELOPER = 5
STARPILOT = 6
@dataclass
@@ -49,8 +52,13 @@ class PanelInfo:
class SettingsLayout(Widget):
def __init__(self):
super().__init__()
self._params = Params()
self._current_panel = PanelType.DEVICE
# Panel depth tracking for hierarchical back navigation
# 0 = top level (settings main), 1+ = sub-panels (FrogPilot categories)
self._panel_depth = 0
# Panel configuration
wifi_manager = WifiManager()
wifi_manager.set_active(False)
@@ -62,10 +70,15 @@ class SettingsLayout(Widget):
PanelType.SOFTWARE: PanelInfo(tr_noop("Software"), SoftwareLayout()),
PanelType.FIREHOSE: PanelInfo(tr_noop("Firehose"), FirehoseLayout()),
PanelType.DEVELOPER: PanelInfo(tr_noop("Developer"), DeveloperLayout()),
PanelType.STARPILOT: PanelInfo(tr_noop("StarPilot"), StarPilotLayout()),
}
# Connect StarPilot depth callback for hierarchical back navigation
self._panels[PanelType.STARPILOT].instance.set_depth_callback(self.set_panel_depth)
self._panels[PanelType.STARPILOT].instance.set_settings_layout(self)
self._font_medium = gui_app.font(FontWeight.MEDIUM)
self._close_icon = gui_app.texture("icons/close2.png", CLOSE_ICON_SIZE, CLOSE_ICON_SIZE)
self._close_icon = gui_app.texture("icons/backspace.png", CLOSE_ICON_SIZE, CLOSE_ICON_SIZE)
# Callbacks
self._close_callback: Callable | None = None
@@ -73,6 +86,24 @@ class SettingsLayout(Widget):
def set_callbacks(self, on_close: Callable):
self._close_callback = on_close
def set_panel_depth(self, depth: int):
self._panel_depth = depth
def refresh_developer_visibility(self):
pass
def get_panel_depth(self) -> int:
return self._panel_depth
def _handle_back_navigation(self):
if self._panel_depth > 0:
self._panel_depth -= 1
if hasattr(self._panels[PanelType.STARPILOT].instance, 'navigate_back'):
self._panels[PanelType.STARPILOT].instance.navigate_back()
else:
if self._close_callback:
self._close_callback()
def _render(self, rect: rl.Rectangle):
# Calculate layout
sidebar_rect = rl.Rectangle(rect.x, rect.y, SIDEBAR_WIDTH, rect.height)
@@ -85,20 +116,17 @@ class SettingsLayout(Widget):
def _draw_sidebar(self, rect: rl.Rectangle):
rl.draw_rectangle_rec(rect, SIDEBAR_COLOR)
# Close button
close_btn_rect = rl.Rectangle(
rect.x + (rect.width - CLOSE_BTN_SIZE) / 2, rect.y + 60, CLOSE_BTN_SIZE, CLOSE_BTN_SIZE
)
# Back/Close button - hierarchical navigation
back_btn_rect = rl.Rectangle(rect.x + (rect.width - CLOSE_BTN_SIZE) / 2, rect.y + 60, CLOSE_BTN_SIZE, CLOSE_BTN_SIZE)
pressed = (rl.is_mouse_button_down(rl.MouseButton.MOUSE_BUTTON_LEFT) and
rl.check_collision_point_rec(rl.get_mouse_position(), close_btn_rect))
pressed = rl.is_mouse_button_down(rl.MouseButton.MOUSE_BUTTON_LEFT) and rl.check_collision_point_rec(rl.get_mouse_position(), back_btn_rect)
close_color = CLOSE_BTN_PRESSED if pressed else CLOSE_BTN_COLOR
rl.draw_rectangle_rounded(close_btn_rect, 1.0, 20, close_color)
rl.draw_rectangle_rounded(back_btn_rect, 1.0, 20, close_color)
icon_color = rl.Color(255, 255, 255, 255) if not pressed else rl.Color(220, 220, 220, 255)
icon_dest = rl.Rectangle(
close_btn_rect.x + (close_btn_rect.width - self._close_icon.width) / 2,
close_btn_rect.y + (close_btn_rect.height - self._close_icon.height) / 2,
back_btn_rect.x + (back_btn_rect.width - self._close_icon.width) / 2,
back_btn_rect.y + (back_btn_rect.height - self._close_icon.height) / 2,
self._close_icon.width,
self._close_icon.height,
)
@@ -111,12 +139,20 @@ class SettingsLayout(Widget):
icon_color,
)
# Store close button rect for click detection
self._close_btn_rect = close_btn_rect
# Store back button rect for click detection
self._back_btn_rect = back_btn_rect
# Get tuning level to control Developer panel visibility
tuning_level_str = self._params.get("TuningLevel", return_default=True, default="1")
tuning_level = int(tuning_level_str) if tuning_level_str else 1
# Navigation buttons
y = rect.y + 300
for panel_type, panel_info in self._panels.items():
# Hide Developer panel unless tuning level >= 3
if panel_type == PanelType.DEVELOPER and tuning_level < 3:
continue
button_rect = rl.Rectangle(rect.x + 50, y, rect.width - 150, NAV_BTN_HEIGHT)
# Button styling
@@ -125,9 +161,7 @@ class SettingsLayout(Widget):
# Draw button text (right-aligned)
panel_name = tr(panel_info.name)
text_size = measure_text_cached(self._font_medium, panel_name, 65)
text_pos = rl.Vector2(
button_rect.x + button_rect.width - text_size.x, button_rect.y + (button_rect.height - text_size.y) / 2
)
text_pos = rl.Vector2(button_rect.x + button_rect.width - text_size.x, button_rect.y + (button_rect.height - text_size.y) / 2)
rl.draw_text_ex(self._font_medium, panel_name, text_pos, 65, 0, text_color)
# Store button rect for click detection
@@ -136,9 +170,7 @@ class SettingsLayout(Widget):
y += NAV_BTN_HEIGHT
def _draw_current_panel(self, rect: rl.Rectangle):
rl.draw_rectangle_rounded(
rl.Rectangle(rect.x + 10, rect.y + 10, rect.width - 20, rect.height - 20), 0.04, 30, PANEL_COLOR
)
rl.draw_rectangle_rounded(rl.Rectangle(rect.x + 10, rect.y + 10, rect.width - 20, rect.height - 20), 0.04, 30, PANEL_COLOR)
content_rect = rl.Rectangle(rect.x + PANEL_MARGIN, rect.y + 25, rect.width - (PANEL_MARGIN * 2), rect.height - 50)
# rl.draw_rectangle_rounded(content_rect, 0.03, 30, PANEL_COLOR)
panel = self._panels[self._current_panel]
@@ -146,10 +178,9 @@ class SettingsLayout(Widget):
panel.instance.render(content_rect)
def _handle_mouse_release(self, mouse_pos: MousePos) -> None:
# Check close button
if rl.check_collision_point_rec(mouse_pos, self._close_btn_rect):
if self._close_callback:
self._close_callback()
# Check back/close button - hierarchical navigation
if rl.check_collision_point_rec(mouse_pos, self._back_btn_rect):
self._handle_back_navigation()
return
# Check navigation buttons
+12 -1
View File
@@ -7,7 +7,7 @@ from openpilot.system.ui.lib.application import gui_app
from openpilot.system.ui.lib.multilang import tr, trn
from openpilot.system.ui.widgets import Widget, DialogResult
from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog
from openpilot.system.ui.widgets.list_view import button_item, text_item, ListItem
from openpilot.system.ui.widgets.list_view import button_item, text_item, toggle_item, ListItem
from openpilot.system.ui.widgets.option_dialog import MultiOptionDialog
from openpilot.system.ui.widgets.scroller_tici import Scroller
@@ -54,6 +54,12 @@ class SoftwareLayout(Widget):
self._onroad_label = ListItem(lambda: tr("Updates are only downloaded while the car is off."))
self._version_item = text_item(lambda: tr("Current Version"), ui_state.params.get("UpdaterCurrentDescription") or "")
self._auto_updates_toggle = toggle_item(
lambda: tr("Automatically Install Updates"),
lambda: tr("Automatically install updates when parked with an active internet connection."),
initial_state=ui_state.params.get_bool("AutomaticUpdates"),
callback=self._on_auto_updates_toggle,
)
self._download_btn = button_item(lambda: tr("Download"), lambda: tr("CHECK"), callback=self._on_download_update)
# Install button is initially hidden
@@ -73,6 +79,7 @@ class SoftwareLayout(Widget):
self._scroller = Scroller([
self._onroad_label,
self._version_item,
self._auto_updates_toggle,
self._download_btn,
self._install_btn,
self._branch_btn,
@@ -94,6 +101,7 @@ class SoftwareLayout(Widget):
current_release_notes = (ui_state.params.get("UpdaterCurrentReleaseNotes") or b"").decode("utf-8", "replace")
self._version_item.action_item.set_text(current_desc)
self._version_item.set_description(current_release_notes)
self._auto_updates_toggle.action_item.set_state(ui_state.params.get_bool("AutomaticUpdates"))
# Update download button visibility and state
self._download_btn.set_visible(ui_state.is_offroad())
@@ -164,6 +172,9 @@ class SoftwareLayout(Widget):
self._waiting_start_ts = time.monotonic()
os.system("pkill -SIGHUP -f system.updated.updated")
def _on_auto_updates_toggle(self, enabled: bool):
ui_state.params.put_bool("AutomaticUpdates", enabled)
def _on_uninstall(self):
def handle_uninstall_confirmation(result):
if result == DialogResult.CONFIRM:
@@ -0,0 +1,25 @@
from __future__ import annotations
from openpilot.system.ui.lib.multilang import tr, tr_noop
from openpilot.system.ui.widgets.list_view import button_item
from openpilot.system.ui.widgets.scroller_tici import Scroller
from openpilot.selfdrive.ui.layouts.settings.starpilot.panel import StarPilotPanel
class StarPilotDataLayout(StarPilotPanel):
def __init__(self):
super().__init__()
items = [
button_item(
tr_noop("Manage Backups"),
lambda: tr("MANAGE"),
tr_noop("<b>Create, restore, or delete backups</b> of your StarPilot settings."),
),
button_item(
tr_noop("Manage Storage"),
lambda: tr("MANAGE"),
tr_noop("<b>View and manage storage usage</b> for models, maps, and other data."),
),
]
self._scroller = Scroller(items, line_separator=True, spacing=0)
@@ -0,0 +1,25 @@
from __future__ import annotations
from openpilot.system.ui.lib.multilang import tr, tr_noop
from openpilot.system.ui.widgets.list_view import button_item
from openpilot.system.ui.widgets.scroller_tici import Scroller
from openpilot.selfdrive.ui.layouts.settings.starpilot.panel import StarPilotPanel
class StarPilotDeviceLayout(StarPilotPanel):
def __init__(self):
super().__init__()
items = [
button_item(
tr_noop("Screen Controls"),
lambda: tr("MANAGE"),
tr_noop("<b>Adjust screen brightness, timeout,</b> and other display settings."),
),
button_item(
tr_noop("Device Settings"),
lambda: tr("MANAGE"),
tr_noop("<b>Configure device-specific options</b> like orientation and controls."),
),
]
self._scroller = Scroller(items, line_separator=True, spacing=0)
@@ -0,0 +1,429 @@
from __future__ import annotations
import json
import os
import threading
from pathlib import Path
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.frogpilot.assets.model_manager import ModelManager
from openpilot.system.hardware import HARDWARE, PC
from openpilot.system.ui.lib.application import gui_app
from openpilot.system.ui.lib.multilang import tr, tr_noop
from openpilot.system.ui.widgets import DialogResult
from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog, alert_dialog
from openpilot.system.ui.widgets.selection_dialog import SelectionDialog
from openpilot.system.ui.widgets.list_view import toggle_item, button_item
from openpilot.system.ui.widgets.scroller_tici import Scroller
from openpilot.selfdrive.ui.layouts.settings.starpilot.panel import StarPilotPanel
class StarPilotDrivingModelLayout(StarPilotPanel):
def __init__(self):
super().__init__()
self._toggle_items = {}
if PC:
self._model_dir = Path(os.path.expanduser("~/.comma/frogpilot/data/models"))
else:
self._model_dir = Path("/data/frogpilot/models")
self._model_dir.mkdir(parents=True, exist_ok=True)
self._available_models = []
self._available_model_names = []
self._available_model_series = []
self._available_model_versions = []
self._model_released_dates = {}
self._model_file_to_name = {}
self._model_series_map = {}
self._model_version_map = {}
self._current_model_name = ""
self._toggle_items["ModelRandomizer"] = toggle_item(
tr_noop("Model Randomizer"),
tr_noop("<b>Driving models are chosen at random each drive</b> and feedback prompts are used to find the model that best suits your needs."),
self._params.get_bool("ModelRandomizer"),
callback=self._on_model_randomizer_toggled,
)
self._toggle_items["AutomaticallyDownloadModels"] = toggle_item(
tr_noop("Automatically Download New Models"),
tr_noop("<b>Automatically download new driving models</b> as they become available."),
self._params.get_bool("AutomaticallyDownloadModels"),
callback=self._on_auto_download_toggled,
)
self._select_model_btn = button_item(
tr_noop("Select Driving Model"),
lambda: tr("SELECT"),
tr_noop("<b>Select the active driving model.</b>"),
callback=self._on_select_model_clicked,
)
self._download_model_btn = button_item(
tr_noop("Download Driving Models"),
lambda: tr("DOWNLOAD"),
tr_noop("<b>Download driving models to the device.</b>"),
callback=self._on_download_clicked,
)
self._delete_model_btn = button_item(
tr_noop("Delete Driving Models"),
lambda: tr("DELETE"),
tr_noop("<b>Delete driving models from the device.</b>"),
callback=self._on_delete_clicked,
)
items = [
self._select_model_btn,
self._download_model_btn,
self._delete_model_btn,
self._toggle_items["ModelRandomizer"],
self._toggle_items["AutomaticallyDownloadModels"],
button_item(
tr_noop("Manage Model Blacklist"),
lambda: tr("MANAGE"),
tr_noop("<b>Add or remove models</b> from the Model Randomizer's blacklist."),
callback=self._on_blacklist_clicked,
),
button_item(
tr_noop("Manage Model Ratings"),
lambda: tr("MANAGE"),
tr_noop("<b>Reset or view the saved ratings</b> for the driving models."),
callback=self._on_scores_clicked,
),
]
self._model_manager = ModelManager(self._params, self._params_memory)
self._download_thread = None
self._scroller = Scroller(items, line_separator=True, spacing=0)
self._update_model_metadata()
def _render(self, rect: rl.Rectangle):
self._update_state()
super()._render(rect)
def show_event(self):
super().show_event()
self._update_model_metadata()
def refresh_visibility(self):
current_level = int(self._params.get("TuningLevel", return_default=True, default="1") or "1")
for key, item in self._toggle_items.items():
min_level = self._tuning_levels.get(key, 0)
item.set_visible(current_level >= min_level)
def _on_auto_download_toggled(self, state: bool):
self._params.put_bool("AutomaticallyDownloadModels", state)
def _is_model_installed(self, key: str) -> bool:
if not key:
return False
has_thneed = False
has_policy_meta = False
has_policy_tg = False
has_vision_meta = False
has_vision_tg = False
found_any = False
for file in self._model_dir.iterdir():
if not (file.name.startswith(key) or file.name.startswith(key + "_")):
continue
found_any = True
ext = file.suffix.lower()
base = file.stem
if ext == ".thneed":
has_thneed = True
elif ext == ".pkl":
if "_driving_policy_metadata" in base:
has_policy_meta = True
elif "_driving_policy_tinygrad" in base:
has_policy_tg = True
elif "_driving_vision_metadata" in base:
has_vision_meta = True
elif "_driving_vision_tinygrad" in base:
has_vision_tg = True
if has_thneed:
return True
return has_policy_meta and has_policy_tg and has_vision_meta and has_vision_tg
def _update_model_metadata(self):
available_models_raw = self._params.get("AvailableModels", encoding='utf-8')
if not available_models_raw:
manager = ModelManager(self._params, self._params_memory)
manager.update_models()
available_models_raw = self._params.get("AvailableModels", encoding='utf-8')
self._available_models = [m.strip() for m in (available_models_raw or "").split(",") if m.strip()]
self._available_model_names = [m.strip() for m in (self._params.get("AvailableModelNames", encoding='utf-8') or "").split(",") if m.strip()]
self._available_model_series = [m.strip() for m in (self._params.get("AvailableModelSeries", encoding='utf-8') or "").split(",") if m.strip()]
self._available_model_versions = [m.strip() for m in (self._params.get("ModelVersions", encoding='utf-8') or "").split(",")]
released_dates = (self._params.get("ModelReleasedDates", encoding='utf-8') or "").split(",")
self._model_file_to_name.clear()
self._model_series_map.clear()
self._model_version_map.clear()
self._model_released_dates.clear()
size = min(len(self._available_models), len(self._available_model_names))
for i in range(size):
key = self._available_models[i].strip()
name = self._available_model_names[i].strip()
if not key or not name:
continue
series = self._available_model_series[i].strip() if i < len(self._available_model_series) else tr("Custom Series")
self._model_file_to_name[key] = name
self._model_series_map[key] = series
if i < len(self._available_model_versions):
version = self._available_model_versions[i].strip()
if version:
self._model_version_map[key] = version
if i < len(released_dates):
date = released_dates[i].strip()
if date:
self._model_released_dates[key] = date
model_key = self._params.get("Model") or self._params.get("DrivingModel")
if model_key:
if isinstance(model_key, bytes):
model_key = model_key.decode()
if not model_key or not self._is_model_installed(model_key):
model_key = self._params.get_default_value("Model") or self._params.get_default_value("DrivingModel") or ""
if isinstance(model_key, bytes):
model_key = model_key.decode()
self._current_model_name = self._model_file_to_name.get(model_key, "Default")
self._select_model_btn.action_item._text_source = self._current_model_name
def _show_selection_dialog(self, title: str, options: dict[str, str] | list[str], current_val: str, on_confirm: Callable):
if not options:
gui_app.set_modal_overlay(alert_dialog(tr("No options available.")))
return
if isinstance(options, list):
def _on_close_list(res, val):
if res == DialogResult.CONFIRM:
on_confirm(val)
dialog = SelectionDialog(title, options, current_val, on_close=_on_close_list)
gui_app.set_modal_overlay(dialog)
return
grouped = {}
name_to_key = {}
for key, name in options.items():
series = self._model_series_map.get(key, tr("Custom Series"))
if series not in grouped:
grouped[series] = []
grouped[series].append(name)
name_to_key[name] = key
for series in grouped:
grouped[series].sort()
sorted_series = sorted(grouped.keys())
if "StarPilot" in sorted_series:
sorted_series.remove("StarPilot")
sorted_series.insert(0, "StarPilot")
final_grouped = {s: grouped[s] for s in sorted_series}
def _on_close_grouped(res, val):
if res == DialogResult.CONFIRM:
key = name_to_key.get(val, val)
on_confirm(key)
def _on_favorite_toggled(key):
favs = [f.strip() for f in (self._params.get("UserFavorites", encoding='utf-8') or "").split(",") if f.strip()]
if key in favs:
favs.remove(key)
else:
favs.append(key)
self._params.put("UserFavorites", ",".join(favs))
user_favs = [f.strip() for f in (self._params.get("UserFavorites", encoding='utf-8') or "").split(",") if f.strip()]
comm_favs = [f.strip() for f in (self._params.get("CommunityFavorites", encoding='utf-8') or "").split(",") if f.strip()]
dialog = SelectionDialog(
title,
final_grouped,
current_val,
on_close=_on_close_grouped,
model_released_dates=self._model_released_dates,
model_file_to_name=self._model_file_to_name,
user_favorites=user_favs,
community_favorites=comm_favs,
on_favorite_toggled=_on_favorite_toggled
)
gui_app.set_modal_overlay(dialog)
def _on_select_model_clicked(self):
installed_models = {k: v for k, v in self._model_file_to_name.items() if self._is_model_installed(k)}
if not installed_models:
return
def _on_confirm(model_key):
self._params.put("Model", model_key)
self._params.put("DrivingModel", model_key)
self._params.put("DrivingModelName", installed_models[model_key])
model_version = self._model_version_map.get(model_key, "")
if model_version:
self._params.put("ModelVersion", model_version)
self._params.put("DrivingModelVersion", model_version)
self._update_model_metadata()
if ui_state.started:
reboot_dialog = ConfirmDialog(
tr("Reboot required to take effect. Reboot now?"),
tr("Reboot"),
tr("Cancel"),
on_close=lambda res: HARDWARE.reboot() if res == DialogResult.CONFIRM else None
)
gui_app.set_modal_overlay(reboot_dialog)
self._show_selection_dialog(tr("Select Driving Model"), installed_models, self._current_model_name, _on_confirm)
def _on_download_clicked(self):
is_downloading = self._params_memory.get("ModelToDownload") or self._params_memory.get_bool("DownloadAllModels")
if is_downloading:
self._params_memory.remove("ModelToDownload")
self._params_memory.put_bool("DownloadAllModels", False)
self._params_memory.remove("ModelDownloadProgress")
return
not_installed = {k: v for k, v in self._model_file_to_name.items() if not self._is_model_installed(k)}
def _on_confirm(model_key):
self._params_memory.put("ModelToDownload", model_key)
self._params_memory.put("ModelDownloadProgress", "Downloading...")
self._show_selection_dialog(tr("Select Model to Download"), not_installed, "", _on_confirm)
def _on_delete_clicked(self):
installed = {k: v for k, v in self._model_file_to_name.items() if self._is_model_installed(k)}
default_key = self._params.get_default_value("Model") or ""
if isinstance(default_key, bytes):
default_key = default_key.decode()
current_key = self._params.get("Model", encoding='utf-8') or ""
deletable = {k: v for k, v in installed.items() if k != default_key and k != current_key}
def _on_confirm(model_key):
def _execute_delete(confirm_res):
if confirm_res == DialogResult.CONFIRM:
for file in self._model_dir.iterdir():
if file.name.startswith(model_key):
file.unlink()
self._update_model_metadata()
confirm = ConfirmDialog(
tr(f"Are you sure you want to delete the '{deletable[model_key]}' model?"),
tr("Delete"),
on_close=_execute_delete
)
gui_app.set_modal_overlay(confirm)
self._show_selection_dialog(tr("Select Model to Delete"), deletable, "", _on_confirm)
def _on_blacklist_clicked(self):
blacklisted = [m.strip() for m in (self._params.get("BlacklistedModels", encoding='utf-8') or "").split(",") if m.strip()]
blacklisted = [b for b in blacklisted if b]
def _on_action_selected(res, val):
if res == DialogResult.CONFIRM:
if val == tr("ADD"):
blacklistable = {k: v for k, v in self._model_file_to_name.items() if k not in blacklisted}
self._show_selection_dialog(tr("Add to Blacklist"), blacklistable, "", lambda k: self._params.put("BlacklistedModels", ",".join(blacklisted + [k])))
elif val == tr("REMOVE"):
options = {k: self._model_file_to_name.get(k, k) for k in blacklisted}
def _remove(k):
blacklisted.remove(k)
self._params.put("BlacklistedModels", ",".join(blacklisted))
self._show_selection_dialog(tr("Remove from Blacklist"), options, "", _remove)
elif val == tr("RESET ALL"):
self._params.remove("BlacklistedModels")
dialog = SelectionDialog(
tr("Manage Blacklist"),
[tr("ADD"), tr("REMOVE"), tr("RESET ALL")],
on_close=_on_action_selected
)
gui_app.set_modal_overlay(dialog)
def _on_scores_clicked(self):
scores_raw = self._params.get("ModelDrivesAndScores", encoding='utf-8') or ""
if not scores_raw:
gui_app.set_modal_overlay(alert_dialog(tr("No model ratings found.")))
return
try:
scores = json.loads(scores_raw)
lines = [f"{k}: {v.get('Score', 0)}% ({v.get('Drives', 0)} drives)" for k, v in scores.items()]
confirm = ConfirmDialog("\n".join(lines), tr("Close"), rich=True)
gui_app.set_modal_overlay(confirm)
except:
pass
def _on_model_randomizer_toggled(self, state: bool):
self._params.put_bool("ModelRandomizer", state)
if state:
not_installed = [k for k in self._model_file_to_name if not self._is_model_installed(k)]
if not_installed:
def _on_download_confirm(res):
if res == DialogResult.CONFIRM:
self._params_memory.put_bool("DownloadAllModels", True)
self._params_memory.put("ModelDownloadProgress", "Downloading...")
confirm = ConfirmDialog(
tr("Model Randomizer works best with all models. Download all now?"),
tr("Download All"),
on_close=_on_download_confirm
)
gui_app.set_modal_overlay(confirm)
def _update_state(self):
if not self.is_visible:
return
model_to_download = self._params_memory.get("ModelToDownload", encoding='utf-8') or ""
download_all = self._params_memory.get_bool("DownloadAllModels")
progress = self._params_memory.get("ModelDownloadProgress", encoding='utf-8') or ""
is_downloading = bool(model_to_download or download_all)
if is_downloading and (self._download_thread is None or not self._download_thread.is_alive()):
def _download_task():
try:
if download_all:
print("Starting [All Models] download thread...")
self._model_manager.download_all_models()
else:
print(f"Starting [{model_to_download}] download thread...")
self._model_manager.download_model(model_to_download)
print("Download thread finished successfully.")
except Exception as e:
print(f"Download thread CRASHED: {e}")
import traceback
traceback.print_exc()
finally:
self._download_thread = None
self._download_thread = threading.Thread(target=_download_task, daemon=True)
self._download_thread.start()
if is_downloading:
self._download_model_btn.action_item._text_source = tr("CANCEL")
self._download_model_btn.action_item._value_source = progress if progress else tr("Downloading...")
else:
self._download_model_btn.action_item._text_source = tr("DOWNLOAD")
parked = not ui_state.started
online = ui_state.sm["deviceState"].networkType != 0 if ui_state.sm.valid.get("deviceState", False) else True
if not online:
self._download_model_btn.action_item._value_source = tr("Offline...")
elif not parked:
self._download_model_btn.action_item._value_source = tr("Not parked")
else:
self._download_model_btn.action_item._value_source = ""
all_installed = all(self._is_model_installed(k) for k in self._model_file_to_name)
if all_installed:
self._download_model_btn.action_item._value_source = tr("All Downloaded!")
@@ -0,0 +1,380 @@
from __future__ import annotations
from openpilot.selfdrive.ui.lib.starpilot_state import starpilot_state
from openpilot.system.ui.lib.multilang import tr, tr_noop
from openpilot.system.ui.widgets import DialogResult
from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog
from openpilot.system.ui.widgets.list_view import button_item, value_button_item, button_toggle_item, toggle_item, value_item
from openpilot.system.ui.widgets.scroller_tici import Scroller
from openpilot.selfdrive.ui.layouts.settings.starpilot.panel import StarPilotPanel
class StarPilotAdvancedLateralLayout(StarPilotPanel):
def __init__(self):
super().__init__()
items = [
value_button_item(
lambda: (
tr("Actuator Delay")
+ (f" (Default: {starpilot_state.car_state.steerActuatorDelay:.2f})" if starpilot_state.car_state.steerActuatorDelay != 0 else "")
),
"SteerDelay",
min_val=0.01,
max_val=1.0,
step=0.01,
button_text="Reset",
button_callback=lambda: self._params.put_float("SteerDelay", starpilot_state.car_state.steerActuatorDelay),
description=tr_noop(
"<b>The time between openpilot's steering command and the vehicle's response.</b> Increase if the vehicle reacts late; decrease if it feels jumpy. Auto-learned by default."
),
enabled=lambda: starpilot_state.car_state.steerActuatorDelay != 0,
),
value_button_item(
lambda: tr("Friction") + (f" (Default: {starpilot_state.car_state.friction:.2f})" if starpilot_state.car_state.friction != 0 else ""),
"SteerFriction",
min_val=0.0,
max_val=1.0,
step=0.01,
button_text="Reset",
button_callback=lambda: self._params.put_float("SteerFriction", starpilot_state.car_state.friction),
description=tr_noop(
"<b>Compensates for steering friction.</b> Increase if the wheel sticks near center; decrease if it jitters. Auto-learned by default."
),
enabled=lambda: (
starpilot_state.car_state.friction != 0
and (not starpilot_state.car_state.hasAutoTune or (starpilot_state.car_state.hasAutoTune and self._params.get_bool("ForceAutoTuneOff")))
),
),
value_button_item(
lambda: tr("Kp Factor") + (f" (Default: {starpilot_state.car_state.steerKp:.2f})" if starpilot_state.car_state.steerKp != 0 else ""),
"SteerKP",
min_val=lambda: starpilot_state.car_state.steerKp * 0.5,
max_val=lambda: starpilot_state.car_state.steerKp * 1.5,
step=0.01,
button_text="Reset",
button_callback=lambda: self._params.put_float("SteerKP", starpilot_state.car_state.steerKp),
description=tr_noop(
"<b>How strongly openpilot corrects lane position.</b> Higher is tighter but twitchier; lower is smoother but slower. Auto-learned by default."
),
enabled=lambda: starpilot_state.car_state.steerKp != 0 and not starpilot_state.car_state.isAngleCar,
),
value_button_item(
lambda: (
tr("Lateral Acceleration") + (f" (Default: {starpilot_state.car_state.latAccelFactor:.2f})" if starpilot_state.car_state.latAccelFactor != 0 else "")
),
"SteerLatAccel",
min_val=lambda: starpilot_state.car_state.latAccelFactor * 0.5,
max_val=lambda: starpilot_state.car_state.latAccelFactor * 1.5,
step=0.01,
button_text="Reset",
button_callback=lambda: self._params.put_float("SteerLatAccel", starpilot_state.car_state.latAccelFactor),
description=tr_noop(
"<b>Maps steering torque to turning response.</b> Increase for sharper turns; decrease for gentler steering. Auto-learned by default."
),
enabled=lambda: (
starpilot_state.car_state.latAccelFactor != 0
and (not starpilot_state.car_state.hasAutoTune or (starpilot_state.car_state.hasAutoTune and self._params.get_bool("ForceAutoTuneOff")))
),
),
value_button_item(
lambda: tr("Steer Ratio") + (f" (Default: {starpilot_state.car_state.steerRatio:.2f})" if starpilot_state.car_state.steerRatio != 0 else ""),
"SteerRatio",
min_val=lambda: starpilot_state.car_state.steerRatio * 0.5,
max_val=lambda: starpilot_state.car_state.steerRatio * 1.5,
step=0.01,
button_text="Reset",
button_callback=lambda: self._params.put_float("SteerRatio", starpilot_state.car_state.steerRatio),
description=tr_noop(
"<b>The relationship between steering wheel rotation and road wheel angle.</b> Increase if steering feels too quick or twitchy; decrease if it feels too slow or weak. Auto-learned by default."
),
enabled=lambda: (
starpilot_state.car_state.steerRatio != 0
and (not starpilot_state.car_state.hasAutoTune or (starpilot_state.car_state.hasAutoTune and self._params.get_bool("ForceAutoTuneOff")))
),
),
toggle_item(
tr_noop("Force Auto-Tune On"),
tr_noop("<b>Force-enable openpilot's live auto-tuning for \"Friction\" and \"Lateral Acceleration\".</b>"),
self._params.get_bool("ForceAutoTune"),
callback=lambda x: self._params.put_bool("ForceAutoTune", x),
enabled=lambda: not starpilot_state.car_state.hasAutoTune and not starpilot_state.car_state.isAngleCar,
),
toggle_item(
tr_noop("Force Auto-Tune Off"),
tr_noop("<b>Force-disable openpilot's live auto-tuning for \"Friction\" and \"Lateral Acceleration\" and use the set value instead.</b>"),
self._params.get_bool("ForceAutoTuneOff"),
callback=lambda x: self._params.put_bool("ForceAutoTuneOff", x),
enabled=lambda: starpilot_state.car_state.hasAutoTune,
),
toggle_item(
tr_noop("Force Torque Controller"),
tr_noop("<b>Use torque-based steering control instead of angle-based control for smoother lane keeping, especially in curves.</b>"),
self._params.get_bool("ForceTorqueController"),
callback=lambda x: self._on_reboot_toggle("ForceTorqueController", x),
enabled=lambda: not starpilot_state.car_state.isAngleCar and not starpilot_state.car_state.isTorqueCar,
),
]
self._scroller = Scroller(items, line_separator=True, spacing=0)
def _on_reboot_toggle(self, key, state):
self._params.put_bool(key, state)
from openpilot.selfdrive.ui.ui_state import ui_state
if ui_state.started:
from openpilot.system.ui.lib.application import gui_app
def _confirm_reboot(res):
gui_app.set_modal_overlay(None)
if res == DialogResult.CONFIRM:
from openpilot.system.hardware import HARDWARE
HARDWARE.reboot()
dialog = ConfirmDialog("Reboot required to take effect. Reboot now?", "Reboot", "Cancel", on_close=_confirm_reboot)
gui_app.set_modal_overlay(dialog)
class StarPilotAlwaysOnLateralLayout(StarPilotPanel):
def __init__(self):
super().__init__()
items = [
toggle_item(
tr_noop("Always On Lateral"),
tr_noop("<b>openpilot's steering remains active even when the accelerator or brake pedals are pressed.</b>"),
self._params.get_bool("AlwaysOnLateral"),
callback=lambda x: self._on_reboot_toggle("AlwaysOnLateral", x),
icon="toggle_icons/icon_always_on_lateral.png",
starpilot_icon=True,
),
toggle_item(
tr_noop("Enable With LKAS"),
tr_noop("<b>Enable \"Always On Lateral\" whenever \"LKAS\" is on, even when openpilot is not engaged.</b>"),
self._params.get_bool("AlwaysOnLateralLKAS"),
callback=lambda x: self._params.put_bool("AlwaysOnLateralLKAS", x),
enabled=lambda: starpilot_state.car_state.lkasAllowedForAOL,
),
value_item(
tr_noop("Pause on Brake Press Below"),
"PauseAOLOnBrake",
min_val=0,
max_val=99,
step=1,
unit="mph",
description=tr_noop("<b>Pause \"Always On Lateral\" below the set speed while the brake pedal is pressed.</b>"),
is_metric=True,
),
]
self._scroller = Scroller(items, line_separator=True, spacing=0)
def _on_reboot_toggle(self, key, state):
self._params.put_bool(key, state)
from openpilot.selfdrive.ui.ui_state import ui_state
if ui_state.started:
from openpilot.system.ui.lib.application import gui_app
def _confirm_reboot(res):
gui_app.set_modal_overlay(None)
if res == DialogResult.CONFIRM:
from openpilot.system.hardware import HARDWARE
HARDWARE.reboot()
dialog = ConfirmDialog("Reboot required to take effect. Reboot now?", "Reboot", "Cancel", on_close=_confirm_reboot)
gui_app.set_modal_overlay(dialog)
class StarPilotLaneChangesLayout(StarPilotPanel):
def __init__(self):
super().__init__()
def _get_lane_change_labels():
labels = {0.0: tr("Instant")}
for i in range(1, 51):
val = i / 10.0
labels[val] = f"{val:.1f} seconds" if val != 1.0 else "1.0 second"
return labels
items = [
toggle_item(
tr_noop("Lane Changes"),
tr_noop("<b>Allow openpilot to change lanes.</b>"),
self._params.get_bool("LaneChanges"),
callback=lambda x: self._params.put_bool("LaneChanges", x),
icon="toggle_icons/icon_lane.png",
starpilot_icon=True,
),
toggle_item(
tr_noop("Automatic Lane Changes"),
tr_noop("<b>When the turn signal is on, openpilot will automatically change lanes.</b> No steering-wheel nudge required!"),
self._params.get_bool("NudgelessLaneChange"),
callback=lambda x: self._params.put_bool("NudgelessLaneChange", x),
),
value_item(
tr_noop("Lane Change Delay"),
"LaneChangeTime",
min_val=0.0,
max_val=5.0,
step=0.1,
description=tr_noop("<b>Delay between turn signal activation and the start of an automatic lane change.</b>"),
labels=_get_lane_change_labels(),
enabled=lambda: self._params.get_bool("LaneChanges") and self._params.get_bool("NudgelessLaneChange"),
),
value_item(
tr_noop("Minimum Lane Change Speed"),
"MinimumLaneChangeSpeed",
min_val=0,
max_val=99,
step=1,
unit="mph",
description=tr_noop("<b>Lowest speed at which openpilot will change lanes.</b>"),
is_metric=True,
),
value_item(
tr_noop("Minimum Lane Width"),
"LaneDetectionWidth",
min_val=0.0,
max_val=15.0,
step=0.1,
unit="feet",
description=tr_noop("<b>Prevent automatic lane changes into lanes narrower than the set width.</b>"),
enabled=lambda: self._params.get_bool("LaneChanges") and self._params.get_bool("NudgelessLaneChange"),
is_metric=True,
),
toggle_item(
tr_noop("One Lane Change Per Signal"),
tr_noop("<b>Limit automatic lane changes to one per turn-signal activation.</b>"),
self._params.get_bool("OneLaneChange"),
callback=lambda x: self._params.put_bool("OneLaneChange", x),
),
]
self._scroller = Scroller(items, line_separator=True, spacing=0)
class StarPilotLateralTuneLayout(StarPilotPanel):
def __init__(self):
super().__init__()
items = [
toggle_item(
tr_noop("Force Turn Desires Below Lane Change Speed"),
tr_noop("<b>While driving below the minimum lane change speed with an active turn signal, instruct openpilot to turn left/right.</b>"),
self._params.get_bool("TurnDesires"),
callback=lambda x: self._params.put_bool("TurnDesires", x),
),
toggle_item(
tr_noop("Neural Network Feedforward (NNFF)"),
tr_noop(
"<b>Twilsonco's \"Neural Network FeedForward\" controller.</b> Uses a trained neural network model to predict steering torque based on vehicle speed, roll, and past/future planned path data for smoother, model-based steering."
),
self._params.get_bool("NNFF"),
callback=lambda x: self._on_reboot_toggle("NNFF", x),
enabled=lambda: starpilot_state.car_state.hasNNFFLog and not starpilot_state.car_state.isAngleCar,
),
toggle_item(
tr_noop("Neural Network Feedforward (NNFF) Lite"),
tr_noop(
"<b>A lightweight version of Twilsonco's \"Neural Network FeedForward\" controller.</b> Uses the \"look-ahead\" planned lateral jerk logic from the full model to help smoothen steering adjustments in curves, but does not use the full neural network for torque calculation."
),
self._params.get_bool("NNFFLite"),
callback=lambda x: self._on_reboot_toggle("NNFFLite", x),
enabled=lambda: not self._params.get_bool("NNFF") and not starpilot_state.car_state.isAngleCar,
),
]
self._scroller = Scroller(items, line_separator=True, spacing=0)
def _on_reboot_toggle(self, key, state):
self._params.put_bool(key, state)
from openpilot.selfdrive.ui.ui_state import ui_state
if ui_state.started:
from openpilot.system.ui.lib.application import gui_app
def _confirm_reboot(res):
gui_app.set_modal_overlay(None)
if res == DialogResult.CONFIRM:
from openpilot.system.hardware import HARDWARE
HARDWARE.reboot()
dialog = ConfirmDialog("Reboot required to take effect. Reboot now?", "Reboot", "Cancel", on_close=_confirm_reboot)
gui_app.set_modal_overlay(dialog)
class StarPilotLateralQOLLayout(StarPilotPanel):
def __init__(self):
super().__init__()
items = [
value_button_item(
tr_noop("Pause Steering Below"),
"PauseLateralSpeed",
min_val=0,
max_val=99,
step=1,
unit="mph",
description=tr_noop("<b>Pause steering below the set speed.</b>"),
sub_toggles=[("PauseLateralOnSignal", True)],
labels={0: tr_noop("Off")},
is_metric=True,
)
]
self._scroller = Scroller(items, line_separator=True, spacing=0)
class StarPilotLateralLayout(StarPilotPanel):
def __init__(self):
super().__init__()
self._sub_panels = {
"advanced_lateral": StarPilotAdvancedLateralLayout(),
"always_on_lateral": StarPilotAlwaysOnLateralLayout(),
"lane_changes": StarPilotLaneChangesLayout(),
"lateral_tune": StarPilotLateralTuneLayout(),
"qol": StarPilotLateralQOLLayout(),
}
for name, panel in self._sub_panels.items():
if hasattr(panel, 'set_navigate_callback'):
panel.set_navigate_callback(self._navigate_to)
if hasattr(panel, 'set_back_callback'):
panel.set_back_callback(self._go_back)
items = [
button_item(
tr_noop("Advanced Lateral Tuning"),
lambda: tr("MANAGE"),
tr_noop("<b>Advanced steering control changes to fine-tune how openpilot drives.</b>"),
callback=lambda: self._navigate_to("advanced_lateral"),
icon="toggle_icons/icon_advanced_lateral_tune.png",
starpilot_icon=True,
),
button_item(
tr_noop("Always On Lateral"),
lambda: tr("MANAGE"),
tr_noop("<b>openpilot's steering remains active even when the accelerator or brake pedals are pressed.</b>"),
callback=lambda: self._navigate_to("always_on_lateral"),
icon="toggle_icons/icon_always_on_lateral.png",
starpilot_icon=True,
),
button_item(
tr_noop("Lane Changes"),
lambda: tr("MANAGE"),
tr_noop("<b>Allow openpilot to change lanes.</b>"),
callback=lambda: self._navigate_to("lane_changes"),
icon="toggle_icons/icon_lane.png",
starpilot_icon=True,
),
button_item(
tr_noop("Lateral Tuning"),
lambda: tr("MANAGE"),
tr_noop("<b>Miscellaneous steering control changes</b> to fine-tune how openpilot drives."),
callback=lambda: self._navigate_to("lateral_tune"),
icon="toggle_icons/icon_lateral_tune.png",
starpilot_icon=True,
),
button_item(
tr_noop("Quality of Life"),
lambda: tr("MANAGE"),
tr_noop("<b>Steering control changes to fine-tune how openpilot drives.</b>"),
callback=lambda: self._navigate_to("qol"),
icon="toggle_icons/icon_quality_of_life.png",
starpilot_icon=True,
),
]
self._scroller = Scroller(items, line_separator=True, spacing=0)
@@ -0,0 +1,368 @@
from __future__ import annotations
from openpilot.system.ui.lib.multilang import tr, tr_noop
from openpilot.system.ui.widgets.list_view import button_item, value_item
from openpilot.system.ui.widgets.scroller_tici import Scroller
from openpilot.selfdrive.ui.layouts.settings.starpilot.panel import StarPilotPanel
class StarPilotLongitudinalLayout(StarPilotPanel):
def __init__(self):
super().__init__()
# Main panel items
items = [
button_item(
tr_noop("Advanced Longitudinal Tuning"),
lambda: tr("MANAGE"),
tr_noop("<b>Advanced acceleration and braking control changes</b> to fine-tune how openpilot drives."),
),
button_item(
tr_noop("Conditional Experimental Mode"),
lambda: tr("MANAGE"),
tr_noop("<b>Automatically switch to Experimental Mode</b> when set conditions are met."),
),
button_item(
tr_noop("Curve Speed Controller"),
lambda: tr("MANAGE"),
tr_noop("<b>Automatically slow down for upcoming curves</b> using data learned from your driving style."),
),
button_item(
tr_noop("Driving Personalities"),
lambda: tr("MANAGE"),
tr_noop("<b>Customize the Driving Personalities</b> to better match your driving style."),
),
button_item(
tr_noop("Longitudinal Tuning"),
lambda: tr("MANAGE"),
tr_noop("<b>Acceleration and braking control changes</b> to fine-tune how openpilot drives."),
),
button_item(
tr_noop("Quality of Life"),
lambda: tr("MANAGE"),
tr_noop("<b>Miscellaneous acceleration and braking control changes</b> to fine-tune how openpilot drives."),
),
button_item(
tr_noop("Weather"),
lambda: tr("MANAGE"),
tr_noop("<b>Adjust driving behavior</b> based on weather conditions."),
callback=lambda: self._navigate_to("weather"),
),
]
self._scroller = Scroller(items, line_separator=True, spacing=0)
# Sub-panels
self._sub_panels = {
"weather": StarPilotWeatherLayout(),
"low_visibility": StarPilotLowVisibilityLayout(),
"rain": StarPilotRainLayout(),
"rainstorm": StarPilotRainStormLayout(),
"snow": StarPilotSnowLayout(),
}
# Wire up navigation callbacks for sub-panels
for name, panel in self._sub_panels.items():
if hasattr(panel, 'set_navigate_callback'):
panel.set_navigate_callback(self._navigate_to)
if hasattr(panel, 'set_back_callback'):
panel.set_back_callback(self._go_back)
class StarPilotWeatherLayout(StarPilotPanel):
def __init__(self):
super().__init__()
items = [
button_item(
tr_noop("Low Visibility"),
lambda: tr("MANAGE"),
tr_noop("<b>Driving adjustments for fog, haze, or other low-visibility conditions.</b>"),
callback=lambda: self._navigate("low_visibility"),
),
button_item(
tr_noop("Rain"),
lambda: tr("MANAGE"),
tr_noop("<b>Driving adjustments for rainy conditions.</b>"),
callback=lambda: self._navigate("rain"),
),
button_item(
tr_noop("Rainstorms"),
lambda: tr("MANAGE"),
tr_noop("<b>Driving adjustments for rainstorms.</b>"),
callback=lambda: self._navigate("rainstorm"),
),
button_item(
tr_noop("Snow"),
lambda: tr("MANAGE"),
tr_noop("<b>Driving adjustments for snowy conditions.</b>"),
callback=lambda: self._navigate("snow"),
),
]
self._scroller = Scroller(items, line_separator=True, spacing=0)
def _navigate(self, sub_panel: str):
if self._navigate_callback:
self._navigate_callback(sub_panel)
class StarPilotLowVisibilityLayout(StarPilotPanel):
def __init__(self):
super().__init__()
def save_following(value: float):
self._params.put_int("IncreaseFollowingLowVisibility", int(value))
def save_stopped_distance(value: float):
self._params.put_int("IncreasedStoppedDistanceLowVisibility", int(value))
def save_reduce_accel(value: float):
self._params.put_int("ReduceAccelerationLowVisibility", int(value))
def save_reduce_lateral(value: float):
self._params.put_int("ReduceLateralAccelerationLowVisibility", int(value))
items = [
value_item(
tr_noop("Increase Following Distance by:"),
lambda: self._params.get_int("IncreaseFollowingLowVisibility", return_default=True, default="0"),
min_val=0,
max_val=3,
step=0.5,
unit=" seconds",
description=tr_noop("<b>Add extra space behind lead vehicles in low visibility.</b> Increase for more space; decrease for tighter gaps."),
callback=save_following,
),
value_item(
tr_noop("Increase Stopped Distance by:"),
lambda: self._params.get_int("IncreasedStoppedDistanceLowVisibility", return_default=True, default="0"),
min_val=0,
max_val=10,
step=1,
unit=" feet",
description=tr_noop("<b>Add extra buffer when stopped behind vehicles in low visibility.</b> Increase for more room; decrease for shorter gaps."),
callback=save_stopped_distance,
),
value_item(
tr_noop("Reduce Acceleration by:"),
lambda: self._params.get_int("ReduceAccelerationLowVisibility", return_default=True, default="0"),
min_val=0,
max_val=50,
step=5,
unit="%",
description=tr_noop(
"<b>Lower the maximum acceleration in low visibility.</b> Increase for softer takeoffs; decrease for quicker but less stable takeoffs."
),
callback=save_reduce_accel,
),
value_item(
tr_noop("Reduce Speed in Curves by:"),
lambda: self._params.get_int("ReduceLateralAccelerationLowVisibility", return_default=True, default="0"),
min_val=0,
max_val=50,
step=5,
unit="%",
description=tr_noop(
"<b>Lower the desired speed while driving through curves in low visibility.</b> Increase for safer, gentler turns; decrease for more aggressive driving in curves."
),
callback=save_reduce_lateral,
),
]
self._scroller = Scroller(items, line_separator=True, spacing=0)
class StarPilotRainLayout(StarPilotPanel):
def __init__(self):
super().__init__()
def save_following(value: float):
self._params.put_int("IncreaseFollowingRain", int(value))
def save_stopped_distance(value: float):
self._params.put_int("IncreasedStoppedDistanceRain", int(value))
def save_reduce_accel(value: float):
self._params.put_int("ReduceAccelerationRain", int(value))
def save_reduce_lateral(value: float):
self._params.put_int("ReduceLateralAccelerationRain", int(value))
items = [
value_item(
tr_noop("Increase Following Distance by:"),
lambda: self._params.get_int("IncreaseFollowingRain", return_default=True, default="0"),
min_val=0,
max_val=3,
step=0.5,
unit=" seconds",
description=tr_noop("<b>Add extra space behind lead vehicles in rain.</b> Increase for more space; decrease for tighter gaps."),
callback=save_following,
),
value_item(
tr_noop("Increase Stopped Distance by:"),
lambda: self._params.get_int("IncreasedStoppedDistanceRain", return_default=True, default="0"),
min_val=0,
max_val=10,
step=1,
unit=" feet",
description=tr_noop("<b>Add extra buffer when stopped behind vehicles in rain.</b> Increase for more room; decrease for shorter gaps."),
callback=save_stopped_distance,
),
value_item(
tr_noop("Reduce Acceleration by:"),
lambda: self._params.get_int("ReduceAccelerationRain", return_default=True, default="0"),
min_val=0,
max_val=50,
step=5,
unit="%",
description=tr_noop("<b>Lower the maximum acceleration in rain.</b> Increase for softer takeoffs; decrease for quicker but less stable takeoffs."),
callback=save_reduce_accel,
),
value_item(
tr_noop("Reduce Speed in Curves by:"),
lambda: self._params.get_int("ReduceLateralAccelerationRain", return_default=True, default="0"),
min_val=0,
max_val=50,
step=5,
unit="%",
description=tr_noop(
"<b>Lower the desired speed while driving through curves in rain.</b> Increase for safer, gentler turns; decrease for more aggressive driving in curves."
),
callback=save_reduce_lateral,
),
]
self._scroller = Scroller(items, line_separator=True, spacing=0)
class StarPilotRainStormLayout(StarPilotPanel):
def __init__(self):
super().__init__()
def save_following(value: float):
self._params.put_int("IncreaseFollowingRainStorm", int(value))
def save_stopped_distance(value: float):
self._params.put_int("IncreasedStoppedDistanceRainStorm", int(value))
def save_reduce_accel(value: float):
self._params.put_int("ReduceAccelerationRainStorm", int(value))
def save_reduce_lateral(value: float):
self._params.put_int("ReduceLateralAccelerationRainStorm", int(value))
items = [
value_item(
tr_noop("Increase Following Distance by:"),
lambda: self._params.get_int("IncreaseFollowingRainStorm", return_default=True, default="0"),
min_val=0,
max_val=3,
step=0.5,
unit=" seconds",
description=tr_noop("<b>Add extra space behind lead vehicles in a rainstorm.</b> Increase for more space; decrease for tighter gaps."),
callback=save_following,
),
value_item(
tr_noop("Increase Stopped Distance by:"),
lambda: self._params.get_int("IncreasedStoppedDistanceRainStorm", return_default=True, default="0"),
min_val=0,
max_val=10,
step=1,
unit=" feet",
description=tr_noop("<b>Add extra buffer when stopped behind vehicles in a rainstorm.</b> Increase for more room; decrease for shorter gaps."),
callback=save_stopped_distance,
),
value_item(
tr_noop("Reduce Acceleration by:"),
lambda: self._params.get_int("ReduceAccelerationRainStorm", return_default=True, default="0"),
min_val=0,
max_val=10,
step=1,
unit=" feet",
description=tr_noop("<b>Add extra buffer when stopped behind vehicles in rain.</b> Increase for more room; decrease for shorter gaps."),
callback=save_reduce_accel,
),
value_item(
tr_noop("Reduce Acceleration by:"),
lambda: self._params.get_int("ReduceAccelerationRain", return_default=True, default="0"),
min_val=0,
max_val=50,
step=5,
unit="%",
description=tr_noop("<b>Lower the maximum acceleration in rain.</b> Increase for softer takeoffs; decrease for quicker but less stable takeoffs."),
),
value_item(
tr_noop("Reduce Speed in Curves by:"),
lambda: self._params.get_int("ReduceLateralAccelerationRain", return_default=True, default="0"),
min_val=0,
max_val=50,
step=5,
unit="%",
description=tr_noop(
"<b>Lower the desired speed while driving through curves in rain.</b> Increase for safer, gentler turns; decrease for more aggressive driving in curves."
),
callback=save_reduce_lateral,
),
]
self._scroller = Scroller(items, line_separator=True, spacing=0)
class StarPilotSnowLayout(StarPilotPanel):
def __init__(self):
super().__init__()
def save_following(value: float):
self._params.put_int("IncreaseFollowingSnow", int(value))
def save_stopped_distance(value: float):
self._params.put_int("IncreasedStoppedDistanceSnow", int(value))
def save_reduce_accel(value: float):
self._params.put_int("ReduceAccelerationSnow", int(value))
def save_reduce_lateral(value: float):
self._params.put_int("ReduceLateralAccelerationSnow", int(value))
items = [
value_item(
tr_noop("Increase Following Distance by:"),
lambda: self._params.get_int("IncreaseFollowingSnow", return_default=True, default="0"),
min_val=0,
max_val=3,
step=0.5,
unit=" seconds",
description=tr_noop("<b>Add extra space behind lead vehicles in snow.</b> Increase for more space; decrease for tighter gaps."),
callback=save_following,
),
value_item(
tr_noop("Increase Stopped Distance by:"),
lambda: self._params.get_int("IncreasedStoppedDistanceSnow", return_default=True, default="0"),
min_val=0,
max_val=10,
step=1,
unit=" feet",
description=tr_noop("<b>Add extra buffer when stopped behind vehicles in snow.</b> Increase for more room; decrease for shorter gaps."),
callback=save_stopped_distance,
),
value_item(
tr_noop("Reduce Acceleration by:"),
lambda: self._params.get_int("ReduceAccelerationSnow", return_default=True, default="0"),
min_val=0,
max_val=50,
step=5,
unit="%",
description=tr_noop("<b>Lower the maximum acceleration in snow.</b> Increase for softer takeoffs; decrease for quicker but less stable takeoffs."),
callback=save_reduce_accel,
),
value_item(
tr_noop("Reduce Speed in Curves by:"),
lambda: self._params.get_int("ReduceLateralAccelerationSnow", return_default=True, default="0"),
min_val=0,
max_val=50,
step=5,
unit="%",
description=tr_noop(
"<b>Lower the desired speed while driving through curves in snow.</b> Increase for safer, gentler turns; decrease for more aggressive driving in curves."
),
callback=save_reduce_lateral,
),
]
self._scroller = Scroller(items, line_separator=True, spacing=0)
@@ -0,0 +1,324 @@
from __future__ import annotations
from collections.abc import Callable
import pyray as rl
from openpilot.common.params import Params
from openpilot.system.ui.widgets import Widget
from openpilot.system.ui.lib.multilang import tr, tr_noop
from openpilot.system.ui.widgets.scroller_tici import Scroller
from openpilot.system.ui.widgets.list_view import multiple_button_item, category_buttons_item
from openpilot.selfdrive.ui.layouts.settings.starpilot.panel import StarPilotPanelType, StarPilotPanelInfo
from openpilot.selfdrive.ui.layouts.settings.starpilot.sounds import StarPilotSoundsLayout
from openpilot.selfdrive.ui.layouts.settings.starpilot.driving_model import StarPilotDrivingModelLayout
from openpilot.selfdrive.ui.layouts.settings.starpilot.longitudinal import StarPilotLongitudinalLayout
from openpilot.selfdrive.ui.layouts.settings.starpilot.lateral import StarPilotLateralLayout
from openpilot.selfdrive.ui.layouts.settings.starpilot.maps import StarPilotMapsLayout
from openpilot.selfdrive.ui.layouts.settings.starpilot.navigation import StarPilotNavigationLayout
from openpilot.selfdrive.ui.layouts.settings.starpilot.data import StarPilotDataLayout
from openpilot.selfdrive.ui.layouts.settings.starpilot.device import StarPilotDeviceLayout
from openpilot.selfdrive.ui.layouts.settings.starpilot.utilities import StarPilotUtilitiesLayout
from openpilot.selfdrive.ui.layouts.settings.starpilot.visuals import StarPilotVisualsLayout
from openpilot.selfdrive.ui.layouts.settings.starpilot.themes import StarPilotThemesLayout
from openpilot.selfdrive.ui.layouts.settings.starpilot.vehicle import StarPilotVehicleSettingsLayout
from openpilot.selfdrive.ui.layouts.settings.starpilot.wheel import StarPilotWheelLayout
STARPILOT_ICONS_DIR = "toggle_icons"
class StarPilotLayout(Widget):
CATEGORIES = [
{
"title": "Alerts and Sounds",
"icon": "icon_sound.png",
"desc": "<b>Adjust alert volumes and enable custom notifications.</b>",
"buttons": [("MANAGE", "SOUNDS", 0)],
},
{
"title": "Driving Controls",
"icon": "icon_steering.png",
"desc": "<b>Fine-tune custom StarPilot acceleration, braking, and steering controls.</b>",
"buttons": [("DRIVING MODEL", "DRIVING_MODEL", 0), ("GAS / BRAKE", "LONGITUDINAL", 0), ("STEERING", "LATERAL", 0)],
},
{
"title": "Navigation",
"icon": "icon_navigate.png",
"desc": "<b>Download map data for the Speed Limit Controller.</b>",
"buttons": [("MAP DATA", "MAPS", 0), ("NAVIGATION", "NAVIGATION", 1)],
},
{
"title": "System Settings",
"icon": "icon_system.png",
"desc": "<b>Manage backups, device settings, screen options, storage, and tools to keep StarPilot running smoothly.</b>",
"buttons": [("DATA", "DATA", 0), ("DEVICE CONTROLS", "DEVICE", 2), ("UTILITIES", "UTILITIES", 0)],
},
{
"title": "Theme and Appearance",
"icon": "icon_display.png",
"desc": "<b>Customize the look of the driving screen and interface, including themes!</b>",
"buttons": [("APPEARANCE", "VISUALS", 0), ("THEME", "THEMES", 0)],
},
{
"title": "Vehicle Settings",
"icon": "icon_vehicle.png",
"desc": "<b>Configure car-specific options and steering wheel button mappings.</b>",
"buttons": [("VEHICLE SETTINGS", "VEHICLE", 0), ("WHEEL CONTROLS", "WHEEL", 1)],
},
]
def __init__(self):
super().__init__()
self._params = Params()
self._current_panel = StarPilotPanelType.MAIN
self._depth_callback: Callable | None = None
self._settings_layout = None
self._panel_stack: list[tuple[StarPilotPanelType, str]] = []
self._sub_panel_callbacks: dict[str, Callable] = {}
self._toggle_tuning_levels: dict[str, int] = {}
all_keys = self._params.all_keys()
for key in all_keys:
level = self._params.get_tuning_level(key)
if level is not None:
self._toggle_tuning_levels[key] = level
self._panels = {
StarPilotPanelType.MAIN: StarPilotPanelInfo("", None),
StarPilotPanelType.SOUNDS: StarPilotPanelInfo(tr_noop("Sounds"), StarPilotSoundsLayout()),
StarPilotPanelType.DRIVING_MODEL: StarPilotPanelInfo(tr_noop("Driving Model"), StarPilotDrivingModelLayout()),
StarPilotPanelType.LONGITUDINAL: StarPilotPanelInfo(tr_noop("Gas / Brake"), StarPilotLongitudinalLayout()),
StarPilotPanelType.LATERAL: StarPilotPanelInfo(tr_noop("Steering"), StarPilotLateralLayout()),
StarPilotPanelType.MAPS: StarPilotPanelInfo(tr_noop("Map Data"), StarPilotMapsLayout()),
StarPilotPanelType.NAVIGATION: StarPilotPanelInfo(tr_noop("Navigation"), StarPilotNavigationLayout()),
StarPilotPanelType.DATA: StarPilotPanelInfo(tr_noop("Data Management"), StarPilotDataLayout()),
StarPilotPanelType.DEVICE: StarPilotPanelInfo(tr_noop("Device Controls"), StarPilotDeviceLayout()),
StarPilotPanelType.UTILITIES: StarPilotPanelInfo(tr_noop("Utilities"), StarPilotUtilitiesLayout()),
StarPilotPanelType.VISUALS: StarPilotPanelInfo(tr_noop("Appearance"), StarPilotVisualsLayout()),
StarPilotPanelType.THEMES: StarPilotPanelInfo(tr_noop("Themes"), StarPilotThemesLayout()),
StarPilotPanelType.VEHICLE: StarPilotPanelInfo(tr_noop("Vehicle Settings"), StarPilotVehicleSettingsLayout()),
StarPilotPanelType.WHEEL: StarPilotPanelInfo(tr_noop("Wheel Controls"), StarPilotWheelLayout()),
}
self._setup_longitudinal_sub_panels()
self._setup_sounds_sub_panels()
self._setup_lateral_sub_panels()
for panel_type in [
StarPilotPanelType.SOUNDS,
StarPilotPanelType.DRIVING_MODEL,
]:
panel = self._panels[panel_type].instance
if panel and hasattr(panel, 'set_tuning_levels'):
panel.set_tuning_levels(self._toggle_tuning_levels)
tuning_levels = [tr("Minimal"), tr("Standard"), tr("Advanced"), tr("Developer")]
tuning_level_str = self._params.get("TuningLevel", return_default=True, default="1")
current_tuning_level = int(tuning_level_str) if tuning_level_str else 1
items = [
multiple_button_item(
tr_noop("Tuning Level"),
tr_noop(
"Choose your tuning level. Lower levels keep it simple; higher levels unlock more toggles for finer control.\n\n"
"Minimal - Ideal for those who prefer simplicity or ease of use\n"
"Standard - Recommended for most users for a balanced experience\n"
"Advanced - Fine-tuning for experienced users\n"
"Developer - Highly customizable settings for seasoned enthusiasts"
),
tuning_levels,
current_tuning_level,
callback=self._on_tuning_level_changed,
icon=f"{STARPILOT_ICONS_DIR}/icon_tuning.png",
starpilot_icon=True,
),
]
panel_type_map = {
"SOUNDS": StarPilotPanelType.SOUNDS,
"DRIVING_MODEL": StarPilotPanelType.DRIVING_MODEL,
"LONGITUDINAL": StarPilotPanelType.LONGITUDINAL,
"LATERAL": StarPilotPanelType.LATERAL,
"MAPS": StarPilotPanelType.MAPS,
"NAVIGATION": StarPilotPanelType.NAVIGATION,
"DATA": StarPilotPanelType.DATA,
"DEVICE": StarPilotPanelType.DEVICE,
"UTILITIES": StarPilotPanelType.UTILITIES,
"VISUALS": StarPilotPanelType.VISUALS,
"THEMES": StarPilotPanelType.THEMES,
"VEHICLE": StarPilotPanelType.VEHICLE,
"WHEEL": StarPilotPanelType.WHEEL,
}
for cat in self.CATEGORIES:
filtered_buttons = []
for btn_label, panel_key, min_level in cat["buttons"]:
if current_tuning_level >= min_level:
panel_type = panel_type_map[panel_key]
callback = lambda p=panel_type: self._set_current_panel(p)
filtered_buttons.append((tr(btn_label), callback))
if filtered_buttons:
full_icon_path = f"{STARPILOT_ICONS_DIR}/{cat['icon']}"
item = category_buttons_item(
title=tr(cat["title"]),
buttons=filtered_buttons,
description=tr(cat["desc"]),
icon=full_icon_path,
starpilot_icon=True,
)
items.append(item)
self._main_scroller = Scroller(items, line_separator=True, spacing=0)
def set_depth_callback(self, callback: Callable):
self._depth_callback = callback
def set_settings_layout(self, settings_layout):
self._settings_layout = settings_layout
def navigate_back(self):
if self._panel_stack:
self._panel_stack.pop()
if self._panel_stack:
self._update_sub_panel_visibility()
else:
self._set_current_panel(StarPilotPanelType.MAIN)
def _push_sub_panel(self, sub_panel_name: str):
self._panel_stack.append((self._current_panel, sub_panel_name))
self._update_sub_panel_visibility()
def _update_sub_panel_visibility(self):
if self._current_panel == StarPilotPanelType.LONGITUDINAL:
longitudinal = self._panels[StarPilotPanelType.LONGITUDINAL].instance
if longitudinal:
current_sub = self._get_current_sub_panel()
if hasattr(longitudinal, 'set_current_sub_panel'):
longitudinal.set_current_sub_panel(current_sub)
elif self._current_panel == StarPilotPanelType.SOUNDS:
sounds = self._panels[StarPilotPanelType.SOUNDS].instance
if sounds:
current_sub = self._get_current_sub_panel()
if hasattr(sounds, '_navigate_to'):
sounds._current_sub_panel = current_sub
def _get_current_sub_panel(self) -> str:
if self._panel_stack and self._panel_stack[-1][0] == self._current_panel:
return self._panel_stack[-1][1]
return ""
def _setup_longitudinal_sub_panels(self):
longitudinal = self._panels[StarPilotPanelType.LONGITUDINAL].instance
if longitudinal and hasattr(longitudinal, 'set_navigate_callback'):
longitudinal.set_navigate_callback(self._push_sub_panel)
def _setup_sounds_sub_panels(self):
sounds = self._panels[StarPilotPanelType.SOUNDS].instance
if sounds and hasattr(sounds, 'set_navigate_callback'):
sounds.set_navigate_callback(self._push_sub_panel)
def _setup_lateral_sub_panels(self):
lateral = self._panels[StarPilotPanelType.LATERAL].instance
if lateral and hasattr(lateral, 'set_navigate_callback'):
lateral.set_navigate_callback(self._push_sub_panel)
def _on_tuning_level_changed(self, index: int):
self._params.put_nonblocking("TuningLevel", index)
if self._settings_layout:
self._settings_layout.refresh_developer_visibility()
for panel_info in self._panels.values():
panel = panel_info.instance
if panel and hasattr(panel, 'refresh_visibility'):
panel.refresh_visibility()
self._rebuild_main_scroller(index)
def _rebuild_main_scroller(self, tuning_level: int):
tuning_levels = [tr("Minimal"), tr("Standard"), tr("Advanced"), tr("Developer")]
items = [
multiple_button_item(
tr_noop("Tuning Level"),
tr_noop(
"Choose your tuning level. Lower levels keep it simple; higher levels unlock more toggles for finer control.\n\n"
"Minimal - Ideal for those who prefer simplicity or ease of use\n"
"Standard - Recommended for most users for a balanced experience\n"
"Advanced - Fine-tuning for experienced users\n"
"Developer - Highly customizable settings for seasoned enthusiasts"
),
tuning_levels,
tuning_level,
callback=self._on_tuning_level_changed,
icon=f"{STARPILOT_ICONS_DIR}/icon_tuning.png",
starpilot_icon=True,
),
]
panel_type_map = {
"SOUNDS": StarPilotPanelType.SOUNDS,
"DRIVING_MODEL": StarPilotPanelType.DRIVING_MODEL,
"LONGITUDINAL": StarPilotPanelType.LONGITUDINAL,
"LATERAL": StarPilotPanelType.LATERAL,
"MAPS": StarPilotPanelType.MAPS,
"NAVIGATION": StarPilotPanelType.NAVIGATION,
"DATA": StarPilotPanelType.DATA,
"DEVICE": StarPilotPanelType.DEVICE,
"UTILITIES": StarPilotPanelType.UTILITIES,
"VISUALS": StarPilotPanelType.VISUALS,
"THEMES": StarPilotPanelType.THEMES,
"VEHICLE": StarPilotPanelType.VEHICLE,
"WHEEL": StarPilotPanelType.WHEEL,
}
for cat in self.CATEGORIES:
filtered_buttons = []
for btn_label, panel_key, min_level in cat["buttons"]:
if tuning_level >= min_level:
panel_type = panel_type_map[panel_key]
callback = lambda p=panel_type: self._set_current_panel(p)
filtered_buttons.append((tr(btn_label), callback))
if filtered_buttons:
full_icon_path = f"{STARPILOT_ICONS_DIR}/{cat['icon']}"
item = category_buttons_item(
title=tr(cat["title"]),
buttons=filtered_buttons,
description=tr(cat["desc"]),
icon=full_icon_path,
starpilot_icon=True,
)
items.append(item)
self._main_scroller = Scroller(items, line_separator=True, spacing=0)
def _set_current_panel(self, panel_type: StarPilotPanelType):
if panel_type != self._current_panel:
if self._current_panel != StarPilotPanelType.MAIN:
self._panels[self._current_panel].instance.hide_event()
self._current_panel = panel_type
if panel_type != StarPilotPanelType.MAIN:
self._panels[panel_type].instance.show_event()
depth = 1 if panel_type != StarPilotPanelType.MAIN else 0
if self._depth_callback:
self._depth_callback(depth)
def _render(self, rect: rl.Rectangle):
if self._current_panel == StarPilotPanelType.MAIN:
self._main_scroller.render(rect)
else:
panel = self._panels[self._current_panel]
if panel.instance:
panel.instance.render(rect)
def show_event(self):
super().show_event()
if self._current_panel == StarPilotPanelType.MAIN:
self._main_scroller.show_event()
else:
self._panels[self._current_panel].instance.show_event()
def hide_event(self):
super().hide_event()
if self._current_panel != StarPilotPanelType.MAIN:
self._panels[self._current_panel].instance.hide_event()
@@ -0,0 +1,25 @@
from __future__ import annotations
from openpilot.system.ui.lib.multilang import tr, tr_noop
from openpilot.system.ui.widgets.list_view import button_item
from openpilot.system.ui.widgets.scroller_tici import Scroller
from openpilot.selfdrive.ui.layouts.settings.starpilot.panel import StarPilotPanel
class StarPilotMapsLayout(StarPilotPanel):
def __init__(self):
super().__init__()
items = [
button_item(
tr_noop("Download Map Data"),
lambda: tr("DOWNLOAD"),
tr_noop("<b>Download map data</b> for the Speed Limit Controller."),
),
button_item(
tr_noop("Manage Map Data"),
lambda: tr("MANAGE"),
tr_noop("<b>View or delete downloaded map data.</b>"),
),
]
self._scroller = Scroller(items, line_separator=True, spacing=0)
@@ -0,0 +1,25 @@
from __future__ import annotations
from openpilot.system.ui.lib.multilang import tr, tr_noop
from openpilot.system.ui.widgets.list_view import toggle_item, button_item
from openpilot.system.ui.widgets.scroller_tici import Scroller
from openpilot.selfdrive.ui.layouts.settings.starpilot.panel import StarPilotPanel
class StarPilotNavigationLayout(StarPilotPanel):
def __init__(self):
super().__init__()
items = [
toggle_item(
tr_noop("Use Live Map Data"),
tr_noop("<b>Use live map data</b> for real-time navigation updates."),
False,
),
button_item(
tr_noop("Navigation Settings"),
lambda: tr("MANAGE"),
tr_noop("<b>Configure navigation-specific options</b> like route preferences."),
),
]
self._scroller = Scroller(items, line_separator=True, spacing=0)
@@ -0,0 +1,74 @@
from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
from enum import IntEnum
import pyray as rl
from openpilot.common.params import Params
from openpilot.system.ui.widgets import Widget
class StarPilotPanelType(IntEnum):
MAIN = 0
SOUNDS = 1
DRIVING_MODEL = 2
LONGITUDINAL = 3
LATERAL = 4
MAPS = 5
NAVIGATION = 6
DATA = 7
DEVICE = 8
UTILITIES = 9
VISUALS = 10
THEMES = 11
VEHICLE = 12
WHEEL = 13
@dataclass
class StarPilotPanelInfo:
name: str
instance: Widget
class StarPilotPanel(Widget):
def __init__(self):
super().__init__()
self._params = Params()
self._params_memory = Params(memory=True)
self._tuning_levels: dict[str, int] = {}
self._navigate_callback: Callable | None = None
self._back_callback: Callable | None = None
self._current_sub_panel = ""
self._sub_panels: dict[str, Widget] = {}
self._scroller = None
def set_navigate_callback(self, callback: Callable):
self._navigate_callback = callback
def set_back_callback(self, callback: Callable):
self._back_callback = callback
def set_tuning_levels(self, levels: dict[str, int]):
self._tuning_levels = levels
def _navigate_to(self, sub_panel: str):
self._current_sub_panel = sub_panel
if self._navigate_callback:
self._navigate_callback(sub_panel)
def _go_back(self):
self._current_sub_panel = ""
if self._back_callback:
self._back_callback()
def _render(self, rect: rl.Rectangle):
if self._current_sub_panel and self._current_sub_panel in self._sub_panels:
self._sub_panels[self._current_sub_panel].render(rect)
elif self._scroller:
self._scroller.render(rect)
def show_event(self):
super().show_event()
if self._current_sub_panel and self._current_sub_panel in self._sub_panels:
self._sub_panels[self._current_sub_panel].show_event()
elif self._scroller:
self._scroller.show_event()
@@ -0,0 +1,271 @@
from __future__ import annotations
import subprocess
from pathlib import Path
from openpilot.common.basedir import BASEDIR
from openpilot.frogpilot.common.frogpilot_variables import ACTIVE_THEME_PATH
from openpilot.system.ui.lib.multilang import tr, tr_noop
from openpilot.system.ui.widgets.list_view import button_item, toggle_item, value_button_item
from openpilot.system.ui.widgets.scroller_tici import Scroller
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.selfdrive.ui.lib.starpilot_state import starpilot_state
from openpilot.selfdrive.ui.layouts.settings.starpilot.panel import StarPilotPanel
class StarPilotSoundsLayout(StarPilotPanel):
VOLUME_KEYS = [
"DisengageVolume",
"EngageVolume",
"PromptVolume",
"PromptDistractedVolume",
"RefuseVolume",
"WarningSoftVolume",
"WarningImmediateVolume",
]
CUSTOM_ALERTS_KEYS = [
"GoatScream",
"GreenLightAlert",
"LeadDepartingAlert",
"LoudBlindspotAlert",
"SpeedLimitChangedAlert",
]
def __init__(self):
super().__init__()
items = [
button_item(
tr_noop("Alert Volume Controller"),
lambda: tr("MANAGE"),
tr_noop("<b>Set how loud each type of openpilot alert is</b> to keep routine prompts from becoming distracting."),
callback=lambda: self._navigate_to("volume_control"),
icon="toggle_icons/icon_mute.png",
starpilot_icon=True,
),
button_item(
tr_noop("StarPilot Alerts"),
lambda: tr("MANAGE"),
tr_noop("<b>Optional StarPilot alerts</b> that highlight driving events in a more noticeable way."),
callback=lambda: self._navigate_to("custom_alerts"),
icon="toggle_icons/icon_green_light.png",
starpilot_icon=True,
),
]
self._scroller = Scroller(items, line_separator=True, spacing=0)
self._sub_panels = {
"volume_control": StarPilotVolumeControlLayout(),
"custom_alerts": StarPilotCustomAlertsLayout(),
}
# Wire up navigation callbacks for sub-panels
for name, panel in self._sub_panels.items():
if hasattr(panel, 'set_navigate_callback'):
panel.set_navigate_callback(self._navigate_to)
if hasattr(panel, 'set_back_callback'):
panel.set_back_callback(self._go_back)
def refresh_visibility(self):
for panel in self._sub_panels.values():
if hasattr(panel, 'refresh_visibility'):
panel.refresh_visibility()
class StarPilotVolumeControlLayout(StarPilotPanel):
VOLUME_INFO = {
"DisengageVolume": {
"title": tr_noop("Disengage Volume"),
"desc": tr_noop(
"<b>Set the volume for alerts when openpilot disengages.</b><br><br>Examples include: \"Cruise Fault: Restart the Car\", \"Parking Brake Engaged\", \"Pedal Pressed\"."
),
"min": 0,
},
"EngageVolume": {
"title": tr_noop("Engage Volume"),
"desc": tr_noop("<b>Set the volume for the chime when openpilot engages</b>, such as after pressing the \"RESUME\" or \"SET\" steering wheel buttons."),
"min": 0,
},
"PromptVolume": {
"title": tr_noop("Prompt Volume"),
"desc": tr_noop(
"<b>Set the volume for prompts that need attention.</b><br><br>Examples include: \"Car Detected in Blindspot\", \"Steering Temporarily Unavailable\", \"Turn Exceeds Steering Limit\"."
),
"min": 0,
},
"PromptDistractedVolume": {
"title": tr_noop("Prompt Distracted Volume"),
"desc": tr_noop(
"<b>Set the volume for prompts when openpilot detects driver distraction or unresponsiveness.</b><br><br>Examples include: \"Pay Attention\", \"Touch Steering Wheel\"."
),
"min": 0,
},
"RefuseVolume": {
"title": tr_noop("Refuse Volume"),
"desc": tr_noop(
"<b>Set the volume for alerts when openpilot refuses to engage.</b><br><br>Examples include: \"Brake Hold Active\", \"Door Open\", \"Seatbelt Unlatched\"."
),
"min": 0,
},
"WarningSoftVolume": {
"title": tr_noop("Warning Soft Volume"),
"desc": tr_noop(
"<b>Set the volume for softer warnings about potential risks.</b><br><br>Examples include: \"BRAKE! Risk of Collision\", \"Steering Temporarily Unavailable\"."
),
"min": 25,
},
"WarningImmediateVolume": {
"title": tr_noop("Warning Immediate Volume"),
"desc": tr_noop(
"<b>Set the volume for the loudest warnings that require urgent attention.</b><br><br>Examples include: \"DISENGAGE IMMEDIATELY — Driver Distracted\", \"DISENGAGE IMMEDIATELY — Driver Unresponsive\"."
),
"min": 25,
},
}
_sound_player_process = None
def __init__(self):
super().__init__()
self._init_sound_player()
volume_labels = {0.0: tr("Muted"), 101.0: tr("Auto")}
for i in range(1, 101):
volume_labels[float(i)] = f"{i}%"
items = []
for key in StarPilotSoundsLayout.VOLUME_KEYS:
info = self.VOLUME_INFO[key]
items.append(
value_button_item(
info["title"],
key,
min_val=info["min"],
max_val=101,
step=1,
button_text="Test",
button_callback=lambda k=key: self._test_sound(k),
description=info["desc"],
labels=volume_labels,
)
)
self._scroller = Scroller(items, line_separator=True, spacing=0)
@classmethod
def _init_sound_player(cls):
if cls._sound_player_process is not None:
return
program = """
import numpy as np
import sounddevice as sd
import sys
import wave
while True:
try:
line = sys.stdin.readline()
if not line:
break
path, volume = line.strip().split('|')
sound_file = wave.open(path, 'rb')
audio = np.frombuffer(sound_file.readframes(sound_file.getnframes()), dtype=np.int16).astype(np.float32) / 32768.0
sd.play(audio * float(volume), sound_file.getframerate())
sd.wait()
except Exception:
pass
"""
cls._sound_player_process = subprocess.Popen(
["python3", "-u", "-c", program],
stdin=subprocess.PIPE,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
def _play_sound_offroad(self, key: str):
base_name = key.replace("Volume", "")
snake_case = "".join(["_" + c.lower() if c.isupper() else c for c in base_name]).lstrip("_")
stock_path = Path(BASEDIR) / "selfdrive" / "assets" / "sounds" / f"{snake_case}.wav"
theme_path = ACTIVE_THEME_PATH / "sounds" / f"{snake_case}.wav"
sound_path = theme_path if theme_path.exists() else stock_path
if not sound_path.exists():
return
volume = self._params.get_int(key, return_default=True, default=100) / 100.0
try:
self._sound_player_process.stdin.write(f"{sound_path}|{volume}\n".encode())
self._sound_player_process.stdin.flush()
except Exception:
pass
def _test_sound(self, key: str):
base_name = key.replace("Volume", "")
if ui_state.started:
camel_case = base_name[0].lower() + base_name[1:]
self._params_memory.put("TestAlert", camel_case)
else:
self._play_sound_offroad(key)
class StarPilotCustomAlertsLayout(StarPilotPanel):
ALERT_INFO = {
"GoatScream": {
"title": tr_noop("Goat Scream"),
"desc": tr_noop(
"<b>Play the infamous \"Goat Scream\" when the steering controller reaches its limit.</b> Based on the \"Turn Exceeds Steering Limit\" event."
),
},
"GreenLightAlert": {
"title": tr_noop("Green Light Alert"),
"desc": tr_noop(
"<b>Play an alert when the model predicts a red light has turned green.</b><br><br><i><b>Disclaimer</b>: openpilot does not explicitly detect traffic lights. This alert is based on end-to-end model predictions from camera input and may trigger even when the light has not changed.</i>"
),
},
"LeadDepartingAlert": {
"title": tr_noop("Lead Departing Alert"),
"desc": tr_noop("<b>Play an alert when the lead vehicle departs from a stop.</b>"),
},
"LoudBlindspotAlert": {
"title": tr_noop("Loud \"Car Detected in Blindspot\" Alert"),
"desc": tr_noop(
"<b>Play a louder alert if a vehicle is in the blind spot when attempting to change lanes.</b> Based on the \"Car Detected in Blindspot\" event."
),
},
"SpeedLimitChangedAlert": {
"title": tr_noop("Speed Limit Changed Alert"),
"desc": tr_noop("<b>Play an alert when the posted speed limit changes.</b>"),
},
}
def __init__(self):
super().__init__()
self._toggle_items = {}
for key in StarPilotSoundsLayout.CUSTOM_ALERTS_KEYS:
info = self.ALERT_INFO[key]
self._toggle_items[key] = toggle_item(
info["title"],
info["desc"],
self._params.get_bool(key),
callback=lambda s, k=key: self._params.put_bool(k, s),
)
self._scroller = Scroller(list(self._toggle_items.values()), line_separator=True, spacing=0)
def refresh_visibility(self):
current_level = int(self._params.get("TuningLevel", return_default=True, default="1") or "1")
for key, item in self._toggle_items.items():
min_level = self._tuning_levels.get(key, 0)
visible = current_level >= min_level
if key == "LoudBlindspotAlert":
visible &= starpilot_state.car_state.hasBSM
elif key == "SpeedLimitChangedAlert":
visible &= self._params.get_bool("ShowSpeedLimits") or (
starpilot_state.car_state.hasOpenpilotLongitudinal and self._params.get_bool("SpeedLimitController")
)
item.set_visible(visible)
@@ -0,0 +1,30 @@
from __future__ import annotations
from openpilot.system.ui.lib.multilang import tr, tr_noop
from openpilot.system.ui.widgets.list_view import button_item
from openpilot.system.ui.widgets.scroller_tici import Scroller
from openpilot.selfdrive.ui.layouts.settings.starpilot.panel import StarPilotPanel
class StarPilotThemesLayout(StarPilotPanel):
def __init__(self):
super().__init__()
items = [
button_item(
tr_noop("Select Theme"),
lambda: tr("SELECT"),
tr_noop("<b>Select a theme</b> for the StarPilot interface."),
),
button_item(
tr_noop("Holiday Themes"),
lambda: tr("MANAGE"),
tr_noop("<b>Enable or disable holiday-themed visuals.</b>"),
),
button_item(
tr_noop("Custom Theme"),
lambda: tr("MANAGE"),
tr_noop("<b>Create or import a custom theme.</b>"),
),
]
self._scroller = Scroller(items, line_separator=True, spacing=0)
@@ -0,0 +1,30 @@
from __future__ import annotations
from openpilot.system.ui.lib.multilang import tr, tr_noop
from openpilot.system.ui.widgets.list_view import button_item
from openpilot.system.ui.widgets.scroller_tici import Scroller
from openpilot.selfdrive.ui.layouts.settings.starpilot.panel import StarPilotPanel
class StarPilotUtilitiesLayout(StarPilotPanel):
def __init__(self):
super().__init__()
items = [
button_item(
tr_noop("Update StarPilot"),
lambda: tr("CHECK"),
tr_noop("<b>Check for updates</b> and update StarPilot to the latest version."),
),
button_item(
tr_noop("Reset Settings"),
lambda: tr("RESET"),
tr_noop("<b>Reset all StarPilot settings</b> to their default values."),
),
button_item(
tr_noop("View Logs"),
lambda: tr("VIEW"),
tr_noop("<b>View StarPilot logs</b> for debugging."),
),
]
self._scroller = Scroller(items, line_separator=True, spacing=0)
@@ -0,0 +1,20 @@
from __future__ import annotations
from openpilot.system.ui.lib.multilang import tr, tr_noop
from openpilot.system.ui.widgets.list_view import button_item
from openpilot.system.ui.widgets.scroller_tici import Scroller
from openpilot.selfdrive.ui.layouts.settings.starpilot.panel import StarPilotPanel
class StarPilotVehicleSettingsLayout(StarPilotPanel):
def __init__(self):
super().__init__()
items = [
button_item(
tr_noop("Vehicle Settings"),
lambda: tr("MANAGE"),
tr_noop("<b>Configure car-specific options</b> like model name and features."),
),
]
self._scroller = Scroller(items, line_separator=True, spacing=0)
@@ -0,0 +1,40 @@
from __future__ import annotations
from openpilot.system.ui.lib.multilang import tr, tr_noop
from openpilot.system.ui.widgets.list_view import button_item
from openpilot.system.ui.widgets.scroller_tici import Scroller
from openpilot.selfdrive.ui.layouts.settings.starpilot.panel import StarPilotPanel
class StarPilotVisualsLayout(StarPilotPanel):
def __init__(self):
super().__init__()
items = [
button_item(
tr_noop("Advanced UI Controls"),
lambda: tr("MANAGE"),
tr_noop("<b>Advanced visual changes</b> to fine-tune how the driving screen looks."),
),
button_item(
tr_noop("Driving Screen Widgets"),
lambda: tr("MANAGE"),
tr_noop("<b>Custom StarPilot widgets</b> for the driving screen."),
),
button_item(
tr_noop("Model UI"),
lambda: tr("MANAGE"),
tr_noop("<b>Model visualizations</b> for the driving path, lane lines, path edges, and road edges."),
),
button_item(
tr_noop("Navigation Widgets"),
lambda: tr("MANAGE"),
tr_noop("<b>Speed limits, and other navigation widgets.</b>"),
),
button_item(
tr_noop("Quality of Life"),
lambda: tr("MANAGE"),
tr_noop("<b>Miscellaneous visual changes</b> to fine-tune how the driving screen looks."),
),
]
self._scroller = Scroller(items, line_separator=True, spacing=0)
@@ -0,0 +1,20 @@
from __future__ import annotations
from openpilot.system.ui.lib.multilang import tr, tr_noop
from openpilot.system.ui.widgets.list_view import button_item
from openpilot.system.ui.widgets.scroller_tici import Scroller
from openpilot.selfdrive.ui.layouts.settings.starpilot.panel import StarPilotPanel
class StarPilotWheelLayout(StarPilotPanel):
def __init__(self):
super().__init__()
items = [
button_item(
tr_noop("Wheel Controls"),
lambda: tr("MANAGE"),
tr_noop("<b>Configure steering wheel button mappings</b> for custom controls."),
),
]
self._scroller = Scroller(items, line_separator=True, spacing=0)

Some files were not shown because too many files have changed in this diff Show More