From a627a718602393468e771ee6005859db6e0ac1c9 Mon Sep 17 00:00:00 2001 From: sasha80 Date: Thu, 20 Jun 2024 12:59:21 +0300 Subject: [PATCH 01/20] =?UTF-8?q?=D0=A0=D0=B5=D1=84=D0=B0=D0=BA=D1=82?= =?UTF-8?q?=D0=BE=D1=80.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scenes/pribor-rtr/pribor-rtr.gd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scenes/pribor-rtr/pribor-rtr.gd b/scenes/pribor-rtr/pribor-rtr.gd index 93ae0324..11030370 100644 --- a/scenes/pribor-rtr/pribor-rtr.gd +++ b/scenes/pribor-rtr/pribor-rtr.gd @@ -101,7 +101,7 @@ func map_value_to_item(data, item, value_name: StringName): ## 1. Поле [b]'ErCode'[/b] содержит [b]0[/b] - если нет ошибки, и другое значение - если есть ошибка.[br] ## 2. Для каждого проверяемого параметра устройства есть своё поле [b]'ErCode'[/b].[br] -## 3. Индикатор исправности у устройства один.[br] +## 3. Есть только один индикатор исправности при отображении состояния устройства.[br] ## 4. [i]Нет результата порверки[/i] - поле [param state] принимает значение [b]0[/b], [i]всё ## исправно[/i] - [b]1[/b], [i]хотябы один[/i] параметр с ошибкой - [b]2[/b].[br] func map_error_codes_to_item(data, item, value_name: StringName): From 21d7cd8f6dfeecd2fc8e03a27f7201bcaa28ee0b Mon Sep 17 00:00:00 2001 From: sasha80 Date: Thu, 11 Jul 2024 16:39:04 +0300 Subject: [PATCH 02/20] =?UTF-8?q?=D0=94=D0=BE=D1=80=D0=B0=D0=B1=D0=BE?= =?UTF-8?q?=D1=82=D0=BA=D0=B0.=20=D0=A3=D0=BB=D1=83=D1=87=D1=88=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=B8=D0=B7=D0=BE=D0=B1=D1=80=D0=B0=D0=B6?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=BF=D1=80=D0=B8=D0=B1=D0=BE=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=20=D0=B4=D0=BB=D1=8F=20=D1=80=D0=B5=D0=B6=D0=B8?= =?UTF-8?q?=D0=BC=D0=B0=20=D0=9A=D0=BE=D0=BD=D1=82=D1=80=D0=BE=D0=BB=D1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- data/Изделие МП-550.png | Bin 18617 -> 26005 bytes data/П16.png | Bin 6592 -> 15367 bytes data/ПРД.png | Bin 3708 -> 13560 bytes data/СПТ.png | Bin 1666 -> 7909 bytes data/УФ.png | Bin 3472 -> 10845 bytes data/Щ3.png | Bin 1806 -> 9074 bytes 6 files changed, 0 insertions(+), 0 deletions(-) diff --git a/data/Изделие МП-550.png b/data/Изделие МП-550.png index 41ae78a5ec7ac9ab9eb4a69a9189a9ffb93bb755..0e22de58cce226be55f9c75562828bf7f462d9f2 100644 GIT binary patch literal 26005 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 Wdo}XIrA>VF_c}KXHA=2qzxp3A`%`rQ literal 18617 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

>_v>1nAhsToL~68fQ7=k_`{BY_edE}b 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 Jmok0zzW@O$+fM)h delta 6555 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 diff --git a/data/ПРД.png b/data/ПРД.png index d3ee562c45592f9caad133e52fa066a19109a455..b58fd7f725b57a4e18832711ff10d85417bd12f5 100644 GIT binary patch literal 13560 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 Byz&45 literal 3708 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 aEB^&noeNJ?cRl|A0000H~hWg#^Ig4Y!NC06-8C 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 delta 1650 zcmV-&295dUJ%SC8BYy@-Nkl7uqX;>(XE?hkQeYfknEfQ6v7`B#b|mQF|FA9LI}%Tl3?cXsLYn9KEQIip$>ZbWA6b^UEXxw_ z{cj{cCwZR#QipA3uZrspyAQ$cisjdO9c5BT!h3I7mSMSECX2;lvRp2oXIb`Zp69=l ze4cpkH_C~b-J02rnN7?ru^%b4ry3Fy)HM@N5{2PduYcEgczD45{XLe;C9*6-p669k zl045h3e$B*_M4spH-eb93Vm_Z^KA&T?TETmd5W>dx$!fK#8fFM# zqokCy%O+C4>>E|=8+~kRXw9VVO7gb*uMNAjd$n5O{{9~G`5X@q4|sfhM4snA{S7li zGM~?@Uw>|14t4S-xLvomC2Zf2a9=VHCHL5EcD?3fBq(`#@3CI5k)|oqG{s`Es9^7F zyvb^{svfMvKGdyGwPu&SPucdEJ?Kz!pOW+@^eoG;UazrUudBMfPu2>09q^$HowIAU zwe6`1J$4L#Oj#*y_uikxu7X|``z}s!j)8Cruzz!sU6-_38IBECf08zBY;{_F0pO=b zv;Up){BA6ZOkI*>G|;cLcU=s6-%3l@`jqQ4@V4UfYB1{UCAzJ>E>9vC(cLFa#cLDeyhm1`CrQuzPzRn@swx-yJnP=~p0s4BZOYhfSy7Es9 zwRxUAWK;|o`WP(l6!bAovU*ishO*4;`hQ~ikk<9pNXH0$Y?jOPF+v}s590}WqC(UAKNkT zt_zl{yvXwYLYL){p{sdzw7iV@1TuFm%T-=vd4HkH^2pHDJbN}H6?9oH(*<3Y_kYK9 z0ly%04G|+lS6NXb9X@nfF4Kn(U6yy$bOFC0bPW+ZYPu}HAaq$C8M>NhN6U+Vp8@^Y zFYi;dWV*^q%h30IfUCR=AAhra;aWefx z%P)Bxzn~up{F1A%f*u*Tplfo}?K*zRQ}+cuauXHwh@b`igh@~v@CAL(1b;}TM=mRE zmKZ_Tka75y3;KaZD(I0XLV`Y!@wuQkK*v0mQ>I58p9}hxEFU%W*v(VWBM%V;{mPaD zBRm=)=FD?flTPY)j_DMbmj7247Y|?o;9ruT%I_ zn%M^cp8$M!+ujEp>Sr~!A%ByZ!8up{ybbu&%>D%MH-LWt{Fj6frnUNZ3*ZL8q%1uo zZRp0I^Bl#6&N-6w08)}O08;>W0H($51%O8YOlsMvz?<0($w{r|Tho@dO3^+T=l-or zG|>8Cbp`0kY?3pQQ<8TD^jTrN2f(*)-*N+Hm*V~V_g1{Nq}c0j0BhU=cvW22_eLxC z#(g53I%+wPe1;GhLhyN>ry+zH$;#{|>^E=TY`maRe$ei_ckjwXZ6wvqs-&7(mDC!i w%IpwAN^%xLn3B9Jpl`r_{ra`ei0(@K5BYk$&-BbCkN^Mx07*qoM6N<$g45kL*#H0l diff --git a/data/УФ.png b/data/УФ.png index d1937ffcc8b0c1b42425eb25eddec07fa7c991af..fd227b4faf9979ec448d8f86aa9f8141f9485457 100644 GIT binary patch literal 10845 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 diff --git a/data/Щ3.png b/data/Щ3.png index af4bee299d73f4702d747088305198ca30577fd0..4ae77f8937953854e2b755bf7fec4da2d0adb68c 100644 GIT binary patch literal 9074 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 delta 1791 zcmZvcX*3&%7RPA_-GVokE`k}ORD)KGG_lp72C;;cFl4k!M&4^`ueAw5Fm1dlseRBA zwL~nTg3{5VO%bA{NVU~e(olw=qB1X?^FF=v?m721o(lT6Arbh(&Djy1 zIJ;8e;jwW}GuyjP$8l}_U}k=X4a`@4UX`7&bRHd5{TDJgCnbPz;DBtO4!(IZLq~ne z_so?O3-~{*K1_CWBXO_OCkyV0o9;i0gDH4l+lW#+;pB8IqOuT(teSrq+ z?djP7a!{uV1HO#fG~GkpA#{0+Mr?S~U`qVXD-CEOCQ3#u5 zR)6oqhm*s@DrwiRQ>WbYB-i`Bot>$Rn%0RfSHjD}saQ;D;c^~tr!w&0>akn$k4^n(PrsYm1q{PeM*Nse4p zOeTnPFoRlP824U~SAOd|i*fbjAl^TaR6$S~L;Aqa>k?dJ;>i=`cm()rMX{Bs-X&sb zdU#;Rg9ksQq@;8QzQ(g;Q?88N(9;IP(Y>va;o&YJ!kXaVV4oiTmZ`bU1SUGaC`Y)m za?{Jp>sKPtVI$kB%#+>QD;$=`}Yu-`Bxrp1gUUJiD@`j($bh3B<}| zGA}xvUJ@kx{8^~Decar@hPr6?n8yC@A1@6{o<;9^Vi$Bgw7bc^z3-AR9jt zZW^i=@1bNjmSHeb>nj>(H<_u;nyW&>AW@wqzTCUV^k8th&~FQM0;EM$w$)@4gF?7p z2AR{>%hu2C7A|>8jr$rB4^xcnE|cFS&!Abo?zO(T6|N#;$os%k$2T=gV`VD$Y$u?j zNCVLiw3u#Pn_E-A^Gza?eHPzYW-@_VX=cu{gxmvnvIAd64&AI;bWS&6|{(e#F?kTn7P7d*o+RSqziPN_YOBlwY& zuN)%qZJDOmRJzYIJn%am-hfB2GssLrso*r2JId^h5+abdc(ZX8Cm5C(f@<71Q0>bW zb&fE8Ofg|!90tTwmwi(k<^JI zI2}S1GIi?!2*|hs!hGSwA#khi88G)^5|FFGiOr^Bs9&4g0)#mNxRWbn{3znkcxNGx zbeJ=21x_#wSBlHjE+$yZw8kja1w^uzFQ_OJqs_lPZd1I1cukdf` zoU3Bo-#*=UqwG&bsnWpkUZr-GJPr0&0N0g;!K()4z*CbB1!-~klT%0+S%!FA$mANC8C>A56qm4JjL2}qS;L;Hjy*ynA! z+>7j^GynprBA0WHB4X-tjZe9J(NTr+V={FB5SVr{il(ox|8tH=SplZa4iCIfq1Ep_ z9iUXLE-$~a=(AK~z0$pxlZed^!t)Q@{TA_h-krIHgCREWMZeZ?w)R znlyaN5aYKuPW<a%TJ(n(-gM4~uXB From 9e4adc110ba1f8beddb110a7728e8bc2694cf8d7 Mon Sep 17 00:00:00 2001 From: sasha80 Date: Wed, 17 Jul 2024 13:40:00 +0300 Subject: [PATCH 03/20] =?UTF-8?q?=D0=94=D0=BE=D1=80=D0=B0=D0=B1=D0=BE?= =?UTF-8?q?=D1=82=D0=BA=D0=B0.=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=20=D1=80=D0=B5=D0=B6=D0=B8=D0=BC=20=D0=AD=D0=9C?= =?UTF-8?q?=D0=A12?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scenes/tabs-switch/tab-switch.tscn | 13 +++++++++++++ scenes/эмс2/эмс2.tscn | 14 ++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 scenes/эмс2/эмс2.tscn diff --git a/scenes/tabs-switch/tab-switch.tscn b/scenes/tabs-switch/tab-switch.tscn index 746e897d..605a3a24 100644 --- a/scenes/tabs-switch/tab-switch.tscn +++ b/scenes/tabs-switch/tab-switch.tscn @@ -39,6 +39,19 @@ layout_mode = 2 visible = false layout_mode = 2 +[node name="ЭМС2" type="Panel" parent="tab_switch"] +visible = false +layout_mode = 2 + +[node name="Label" type="Label" parent="tab_switch/ЭМС2"] +layout_mode = 0 +offset_left = 476.0 +offset_top = 64.0 +offset_right = 932.0 +offset_bottom = 83.0 +text = "Режим ЭМС2 +" + [node name="Настройки" parent="tab_switch" instance=ExtResource("6_i8iv3")] visible = false layout_mode = 2 diff --git a/scenes/эмс2/эмс2.tscn b/scenes/эмс2/эмс2.tscn new file mode 100644 index 00000000..401b29c5 --- /dev/null +++ b/scenes/эмс2/эмс2.tscn @@ -0,0 +1,14 @@ +[gd_scene format=3 uid="uid://musb21x2u0xs"] + +[node name="yems2" type="Panel"] + +[node name="lbl_title" type="Label" parent="."] +layout_mode = 0 +offset_left = 476.0 +offset_top = 59.0 +offset_right = 932.0 +offset_bottom = 91.0 +text = "Режим ЭМС2 +" +horizontal_alignment = 1 +vertical_alignment = 1 From 2cb2d28b99b3edd945fac0f39f382a3b0f664712 Mon Sep 17 00:00:00 2001 From: Vlad Date: Thu, 18 Jul 2024 09:32:09 +0300 Subject: [PATCH 04/20] =?UTF-8?q?=D0=94=D0=BE=D1=80=D0=B0=D0=B1=D0=BE?= =?UTF-8?q?=D1=82=D0=BA=D0=B0.=20=D0=93=D1=80=D0=B0=D1=84=D0=B8=D1=87?= =?UTF-8?q?=D0=B5=D1=81=D0=BA=D0=B8=D0=B5=20=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F=20=D1=80=D0=B5=D0=B6=D0=B8=D0=BC=D0=B0?= =?UTF-8?q?=20=D0=AD=D0=9C=D0=A12?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scenes/tabs-switch/tab-switch.tscn | 17 +-- scenes/эмс2/эмс2.tscn | 181 +++++++++++++++++++++++++++-- 2 files changed, 177 insertions(+), 21 deletions(-) diff --git a/scenes/tabs-switch/tab-switch.tscn b/scenes/tabs-switch/tab-switch.tscn index 605a3a24..39f28bb9 100644 --- a/scenes/tabs-switch/tab-switch.tscn +++ b/scenes/tabs-switch/tab-switch.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=10 format=3 uid="uid://3slb0i3pvowc"] +[gd_scene load_steps=11 format=3 uid="uid://3slb0i3pvowc"] [ext_resource type="Script" path="res://scenes/tabs-switch/tab-switch.gd" id="1_fg0vd"] [ext_resource type="PackedScene" uid="uid://b276iygic5itk" path="res://scenes/работа/работа.tscn" id="2_u7p16"] @@ -6,6 +6,7 @@ [ext_resource type="PackedScene" uid="uid://trt0q8th3bn2" path="res://scenes/журнал/журнал.tscn" id="4_cu5k8"] [ext_resource type="PackedScene" uid="uid://dab6loryocc73" path="res://scenes/эмс/эмс.tscn" id="5_u71bh"] [ext_resource type="PackedScene" uid="uid://bnptm4rlp60dq" path="res://scenes/настройки/настройки.tscn" id="6_i8iv3"] +[ext_resource type="PackedScene" uid="uid://musb21x2u0xs" path="res://scenes/эмс2/эмс2.tscn" id="6_rsg03"] [ext_resource type="FontFile" uid="uid://clrxfflc1wrhj" path="res://data/main-font.tres" id="7_i7l8n"] [sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_fe3e1"] @@ -22,9 +23,11 @@ layout_mode = 0 offset_right = 1600.0 offset_bottom = 1200.0 tooltip_text = "Переключает режимы работы программы" +current_tab = 4 script = ExtResource("1_fg0vd") [node name="Работа" parent="tab_switch" instance=ExtResource("2_u7p16")] +visible = false layout_mode = 2 [node name="Контроль" parent="tab_switch" instance=ExtResource("3_txp0s")] @@ -39,19 +42,9 @@ layout_mode = 2 visible = false layout_mode = 2 -[node name="ЭМС2" type="Panel" parent="tab_switch"] -visible = false +[node name="ЭМС2" parent="tab_switch" instance=ExtResource("6_rsg03")] layout_mode = 2 -[node name="Label" type="Label" parent="tab_switch/ЭМС2"] -layout_mode = 0 -offset_left = 476.0 -offset_top = 64.0 -offset_right = 932.0 -offset_bottom = 83.0 -text = "Режим ЭМС2 -" - [node name="Настройки" parent="tab_switch" instance=ExtResource("6_i8iv3")] visible = false layout_mode = 2 diff --git a/scenes/эмс2/эмс2.tscn b/scenes/эмс2/эмс2.tscn index 401b29c5..c424327c 100644 --- a/scenes/эмс2/эмс2.tscn +++ b/scenes/эмс2/эмс2.tscn @@ -1,14 +1,177 @@ -[gd_scene format=3 uid="uid://musb21x2u0xs"] +[gd_scene load_steps=2 format=3 uid="uid://musb21x2u0xs"] -[node name="yems2" type="Panel"] +[ext_resource type="Texture2D" uid="uid://co5xj5uhjfk84" path="res://data/Окно.png" id="1_k6k6b"] -[node name="lbl_title" type="Label" parent="."] +[node name="ЭМС2" type="Panel"] + +[node name="GridContainer" type="GridContainer" parent="."] layout_mode = 0 -offset_left = 476.0 -offset_top = 59.0 -offset_right = 932.0 -offset_bottom = 91.0 +offset_left = 123.0 +offset_top = 154.0 +offset_right = 163.0 +offset_bottom = 194.0 +columns = 2 + +[node name="Button" type="Button" parent="GridContainer"] +layout_mode = 2 +text = "122324" + +[node name="Button2" type="Button" parent="GridContainer"] +layout_mode = 2 +text = "122324" + +[node name="Label" type="Label" parent="."] +layout_mode = 0 +offset_left = 71.0 +offset_top = 48.0 +offset_right = 527.0 +offset_bottom = 67.0 text = "Режим ЭМС2 " -horizontal_alignment = 1 -vertical_alignment = 1 + +[node name="TextureRect2" type="TextureRect" parent="."] +layout_mode = 0 +offset_left = 7.0 +offset_top = 177.0 +offset_right = 1596.0 +offset_bottom = 783.0 +texture = ExtResource("1_k6k6b") + +[node name="Label2" type="Label" parent="TextureRect2"] +layout_mode = 0 +offset_left = 795.0 +offset_top = 8.0 +offset_right = 868.0 +offset_bottom = 27.0 +theme_override_colors/font_color = Color(0, 0, 0, 1) +text = "Излучение" + +[node name="Label3" type="Label" parent="TextureRect2"] +layout_mode = 0 +offset_left = 813.0 +offset_top = 293.0 +offset_right = 864.0 +offset_bottom = 312.0 +theme_override_colors/font_color = Color(0, 0, 0, 1) +text = "Приём" + +[node name="RichTextLabel" type="RichTextLabel" parent="TextureRect2"] +layout_mode = 0 +offset_left = 11.0 +offset_top = 36.0 +offset_right = 126.0 +offset_bottom = 286.0 + +[node name="RichTextLabel2" type="RichTextLabel" parent="TextureRect2"] +layout_mode = 0 +offset_left = 12.0 +offset_top = 318.0 +offset_right = 127.0 +offset_bottom = 568.0 + +[node name="GridContainer2" type="GridContainer" parent="."] +layout_mode = 0 +offset_left = 22.0 +offset_top = 898.0 +offset_right = 984.0 +offset_bottom = 1184.0 + +[node name="Label4" type="Label" parent="."] +layout_mode = 2 +offset_left = 24.0 +offset_top = 856.0 +offset_right = 98.0 +offset_bottom = 875.0 +text = "Комплекс" + +[node name="Label5" type="Label" parent="."] +layout_mode = 2 +offset_left = 120.0 +offset_top = 856.0 +offset_right = 194.0 +offset_bottom = 875.0 +text = "Изделие" + +[node name="Label6" type="Label" parent="."] +layout_mode = 2 +offset_left = 200.0 +offset_top = 856.0 +offset_right = 274.0 +offset_bottom = 875.0 +text = "Режим" + +[node name="Label7" type="Label" parent="."] +layout_mode = 2 +offset_left = 272.0 +offset_top = 856.0 +offset_right = 346.0 +offset_bottom = 875.0 +text = "Азимут" + +[node name="Label8" type="Label" parent="."] +layout_mode = 2 +offset_left = 344.0 +offset_top = 856.0 +offset_right = 418.0 +offset_bottom = 875.0 +text = "Угол места" + +[node name="Label9" type="Label" parent="."] +layout_mode = 2 +offset_left = 440.0 +offset_top = 856.0 +offset_right = 506.0 +offset_bottom = 875.0 +text = "Кластер" + +[node name="Label10" type="Label" parent="."] +layout_mode = 2 +offset_left = 520.0 +offset_top = 848.0 +offset_right = 586.0 +offset_bottom = 889.0 +text = "Нижняя +частота" + +[node name="Label11" type="Label" parent="."] +layout_mode = 2 +offset_left = 600.0 +offset_top = 848.0 +offset_right = 666.0 +offset_bottom = 889.0 +text = "Верхняя +частота" + +[node name="Label12" type="Label" parent="."] +layout_mode = 2 +offset_left = 832.0 +offset_top = 856.0 +offset_right = 898.0 +offset_bottom = 875.0 +text = "." + +[node name="Label13" type="Label" parent="."] +layout_mode = 2 +offset_left = 752.0 +offset_top = 848.0 +offset_right = 818.0 +offset_bottom = 889.0 +text = "Запрет +приёма" + +[node name="Label14" type="Label" parent="."] +layout_mode = 2 +offset_left = 680.0 +offset_top = 856.0 +offset_right = 746.0 +offset_bottom = 875.0 +text = "." + +[node name="Label15" type="Label" parent="."] +layout_mode = 2 +offset_left = 920.0 +offset_top = 848.0 +offset_right = 986.0 +offset_bottom = 889.0 +text = "Запрет +передачи" From d2488aed7b7086af7598233f96c481116b650b15 Mon Sep 17 00:00:00 2001 From: Vlad Date: Thu, 18 Jul 2024 09:32:30 +0300 Subject: [PATCH 05/20] =?UTF-8?q?=D0=94=D0=BE=D1=80=D0=B0=D0=B1=D0=BE?= =?UTF-8?q?=D1=82=D0=BA=D0=B0.=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=BE=20=D0=B8=D0=B7=D0=BE=D0=B1=D1=80=D0=B0=D0=B6?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B4=D0=BB=D1=8F=20=D1=80=D0=B5?= =?UTF-8?q?=D0=B6=D0=B8=D0=BC=D0=B0=20=D0=AD=D0=9C=D0=A12?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- data/Окно.png | Bin 0 -> 16534 bytes data/Окно.png.import | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 data/Окно.png create mode 100644 data/Окно.png.import diff --git a/data/Окно.png b/data/Окно.png new file mode 100644 index 0000000000000000000000000000000000000000..18d7d2800386695a8bbcb63f013bfb3659f59168 GIT binary patch literal 16534 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 g*6Q$qAQ>)4^ Date: Thu, 18 Jul 2024 13:34:20 +0300 Subject: [PATCH 06/20] =?UTF-8?q?=D0=94=D0=BE=D1=80=D0=B0=D0=B1=D0=BE?= =?UTF-8?q?=D1=82=D0=BA=D0=B0.=20=D0=93=D1=80=D0=B0=D1=84=D0=B8=D1=87?= =?UTF-8?q?=D0=B5=D1=81=D0=BA=D0=B8=D0=B5=20=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F=20=D1=80=D0=B5=D0=B6=D0=B8=D0=BC=D0=B0?= =?UTF-8?q?=20=D0=AD=D0=9C=D0=A12=20(2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scenes/эмс2/эмс2.tscn | 418 +++++++++++++++++++++++++++++------------- 1 file changed, 293 insertions(+), 125 deletions(-) diff --git a/scenes/эмс2/эмс2.tscn b/scenes/эмс2/эмс2.tscn index c424327c..06f453bc 100644 --- a/scenes/эмс2/эмс2.tscn +++ b/scenes/эмс2/эмс2.tscn @@ -4,37 +4,11 @@ [node name="ЭМС2" type="Panel"] -[node name="GridContainer" type="GridContainer" parent="."] -layout_mode = 0 -offset_left = 123.0 -offset_top = 154.0 -offset_right = 163.0 -offset_bottom = 194.0 -columns = 2 - -[node name="Button" type="Button" parent="GridContainer"] -layout_mode = 2 -text = "122324" - -[node name="Button2" type="Button" parent="GridContainer"] -layout_mode = 2 -text = "122324" - -[node name="Label" type="Label" parent="."] -layout_mode = 0 -offset_left = 71.0 -offset_top = 48.0 -offset_right = 527.0 -offset_bottom = 67.0 -text = "Режим ЭМС2 -" - [node name="TextureRect2" type="TextureRect" parent="."] layout_mode = 0 -offset_left = 7.0 -offset_top = 177.0 -offset_right = 1596.0 -offset_bottom = 783.0 +offset_top = 8.0 +offset_right = 1597.0 +offset_bottom = 712.0 texture = ExtResource("1_k6k6b") [node name="Label2" type="Label" parent="TextureRect2"] @@ -70,108 +44,302 @@ offset_right = 127.0 offset_bottom = 568.0 [node name="GridContainer2" type="GridContainer" parent="."] +modulate = Color(0.8, 0.533333, 0.27451, 1) +self_modulate = Color(0.8, 0.533333, 0.27451, 1) layout_mode = 0 -offset_left = 22.0 -offset_top = 898.0 -offset_right = 984.0 -offset_bottom = 1184.0 - -[node name="Label4" type="Label" parent="."] -layout_mode = 2 -offset_left = 24.0 -offset_top = 856.0 -offset_right = 98.0 -offset_bottom = 875.0 -text = "Комплекс" - -[node name="Label5" type="Label" parent="."] -layout_mode = 2 -offset_left = 120.0 -offset_top = 856.0 -offset_right = 194.0 -offset_bottom = 875.0 -text = "Изделие" - -[node name="Label6" type="Label" parent="."] -layout_mode = 2 -offset_left = 200.0 -offset_top = 856.0 -offset_right = 274.0 -offset_bottom = 875.0 -text = "Режим" - -[node name="Label7" type="Label" parent="."] -layout_mode = 2 -offset_left = 272.0 -offset_top = 856.0 -offset_right = 346.0 -offset_bottom = 875.0 -text = "Азимут" - -[node name="Label8" type="Label" parent="."] -layout_mode = 2 -offset_left = 344.0 -offset_top = 856.0 -offset_right = 418.0 -offset_bottom = 875.0 -text = "Угол места" - -[node name="Label9" type="Label" parent="."] -layout_mode = 2 -offset_left = 440.0 -offset_top = 856.0 -offset_right = 506.0 -offset_bottom = 875.0 -text = "Кластер" - -[node name="Label10" type="Label" parent="."] -layout_mode = 2 -offset_left = 520.0 -offset_top = 848.0 -offset_right = 586.0 -offset_bottom = 889.0 -text = "Нижняя -частота" +offset_left = -8.0 +offset_top = 840.0 +offset_right = 1592.0 +offset_bottom = 1208.0 [node name="Label11" type="Label" parent="."] -layout_mode = 2 -offset_left = 600.0 -offset_top = 848.0 -offset_right = 666.0 -offset_bottom = 889.0 -text = "Верхняя -частота" - -[node name="Label12" type="Label" parent="."] -layout_mode = 2 -offset_left = 832.0 -offset_top = 856.0 -offset_right = 898.0 -offset_bottom = 875.0 -text = "." - -[node name="Label13" type="Label" parent="."] -layout_mode = 2 -offset_left = 752.0 -offset_top = 848.0 -offset_right = 818.0 -offset_bottom = 889.0 +layout_mode = 0 +offset_left = 1224.0 +offset_top = 752.0 +offset_right = 3304.0 +offset_bottom = 1872.0 +scale = Vector2(0.05, 0.05) +theme_override_colors/font_color = Color(0, 0, 0, 1) +theme_override_font_sizes/font_size = 350 text = "Запрет приёма" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="ColorRect2" type="ColorRect" parent="Label11"] +modulate = Color(0.8, 0.533333, 0.27451, 1) +show_behind_parent = true +layout_mode = 1 +anchors_preset = -1 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 [node name="Label14" type="Label" parent="."] -layout_mode = 2 -offset_left = 680.0 -offset_top = 856.0 -offset_right = 746.0 -offset_bottom = 875.0 -text = "." +layout_mode = 0 +offset_left = 1336.0 +offset_top = 752.0 +offset_right = 3736.0 +offset_bottom = 1872.0 +scale = Vector2(0.05, 0.05) +theme_override_colors/font_color = Color(0, 0, 0, 1) +theme_override_font_sizes/font_size = 350 +text = "Исполнено" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="ColorRect2" type="ColorRect" parent="Label14"] +modulate = Color(0.8, 0.533333, 0.27451, 1) +show_behind_parent = true +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="Label12" type="Label" parent="."] +layout_mode = 0 +offset_left = 1464.0 +offset_top = 752.0 +offset_right = 3704.0 +offset_bottom = 1872.0 +scale = Vector2(0.05, 0.05) +theme_override_colors/font_color = Color(0, 0, 0, 1) +theme_override_font_sizes/font_size = 350 +text = "Запрет +приёма" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="ColorRect2" type="ColorRect" parent="Label12"] +modulate = Color(0.8, 0.533333, 0.27451, 1) +show_behind_parent = true +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 [node name="Label15" type="Label" parent="."] -layout_mode = 2 -offset_left = 920.0 -offset_top = 848.0 -offset_right = 986.0 -offset_bottom = 889.0 -text = "Запрет -передачи" +layout_mode = 0 +offset_left = 1096.0 +offset_top = 752.0 +offset_right = 3496.0 +offset_bottom = 1872.0 +scale = Vector2(0.05, 0.05) +theme_override_colors/font_color = Color(0, 0, 0, 1) +theme_override_font_sizes/font_size = 350 +text = "Исполнено" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="ColorRect2" type="ColorRect" parent="Label15"] +modulate = Color(0.8, 0.533333, 0.27451, 1) +show_behind_parent = true +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="Label13" type="Label" parent="."] +layout_mode = 0 +offset_left = 976.0 +offset_top = 752.0 +offset_right = 3216.0 +offset_bottom = 1872.0 +scale = Vector2(0.05, 0.05) +theme_override_colors/font_color = Color(0, 0, 0, 1) +theme_override_font_sizes/font_size = 350 +text = "Верхняя +Частота" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="ColorRect2" type="ColorRect" parent="Label13"] +modulate = Color(0.8, 0.533333, 0.27451, 1) +show_behind_parent = true +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="Label16" type="Label" parent="."] +layout_mode = 0 +offset_left = 856.0 +offset_top = 752.0 +offset_right = 3096.0 +offset_bottom = 1872.0 +scale = Vector2(0.05, 0.05) +theme_override_colors/font_color = Color(0, 0, 0, 1) +theme_override_font_sizes/font_size = 350 +text = "Нижняя +частота" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="ColorRect2" type="ColorRect" parent="Label16"] +modulate = Color(0.8, 0.533333, 0.27451, 1) +show_behind_parent = true +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = -0.000976563 +offset_right = -0.000976563 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="Label17" type="Label" parent="."] +layout_mode = 0 +offset_left = 728.0 +offset_top = 752.0 +offset_right = 3128.0 +offset_bottom = 1872.0 +scale = Vector2(0.05, 0.05) +theme_override_colors/font_color = Color(0, 0, 0, 1) +theme_override_font_sizes/font_size = 350 +text = "Кластер" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="ColorRect2" type="ColorRect" parent="Label17"] +modulate = Color(0.8, 0.533333, 0.27451, 1) +show_behind_parent = true +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="Label18" type="Label" parent="."] +layout_mode = 0 +offset_left = 600.0 +offset_top = 752.0 +offset_right = 3000.0 +offset_bottom = 1872.0 +scale = Vector2(0.05, 0.05) +theme_override_colors/font_color = Color(0, 0, 0, 1) +theme_override_font_sizes/font_size = 350 +text = "Угол места" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="ColorRect2" type="ColorRect" parent="Label18"] +modulate = Color(0.8, 0.533333, 0.27451, 1) +show_behind_parent = true +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="Label19" type="Label" parent="."] +layout_mode = 0 +offset_left = 472.0 +offset_top = 752.0 +offset_right = 2872.0 +offset_bottom = 1872.0 +scale = Vector2(0.05, 0.05) +theme_override_colors/font_color = Color(0, 0, 0, 1) +theme_override_font_sizes/font_size = 350 +text = "Азимут" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="ColorRect2" type="ColorRect" parent="Label19"] +modulate = Color(0.8, 0.533333, 0.27451, 1) +show_behind_parent = true +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = 0.000610352 +offset_top = 6.10352e-05 +offset_right = 0.000488281 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="Label20" type="Label" parent="."] +layout_mode = 0 +offset_left = 344.0 +offset_top = 752.0 +offset_right = 2744.0 +offset_bottom = 1872.0 +scale = Vector2(0.05, 0.05) +theme_override_colors/font_color = Color(0, 0, 0, 1) +theme_override_font_sizes/font_size = 350 +text = "Режим" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="ColorRect2" type="ColorRect" parent="Label20"] +modulate = Color(0.8, 0.533333, 0.27451, 1) +show_behind_parent = true +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = 0.000610352 +offset_top = 6.10352e-05 +offset_right = 0.000488281 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="Label21" type="Label" parent="."] +layout_mode = 0 +offset_left = 216.0 +offset_top = 752.0 +offset_right = 2616.0 +offset_bottom = 1872.0 +scale = Vector2(0.05, 0.05) +theme_override_colors/font_color = Color(0, 0, 0, 1) +theme_override_font_sizes/font_size = 350 +text = "Изделие" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="ColorRect2" type="ColorRect" parent="Label21"] +modulate = Color(0.8, 0.533333, 0.27451, 1) +show_behind_parent = true +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_top = 6.10352e-05 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="Label22" type="Label" parent="."] +layout_mode = 0 +offset_left = 88.0 +offset_top = 752.0 +offset_right = 2488.0 +offset_bottom = 1872.0 +scale = Vector2(0.05, 0.05) +theme_override_colors/font_color = Color(0, 0, 0, 1) +theme_override_font_sizes/font_size = 350 +text = "Комплекс +" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="ColorRect2" type="ColorRect" parent="Label22"] +modulate = Color(0.8, 0.533333, 0.27451, 1) +show_behind_parent = true +layout_mode = 1 +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_top = 6.10352e-05 +grow_horizontal = 2 +grow_vertical = 2 From 1078d318388d90387563cf79aae169d408aed114 Mon Sep 17 00:00:00 2001 From: sasha80 Date: Thu, 18 Jul 2024 15:50:14 +0300 Subject: [PATCH 07/20] =?UTF-8?q?=D0=A0=D0=B5=D0=B3=D1=80=D0=B5=D1=81?= =?UTF-8?q?=D1=81=D0=B8=D1=8F.=20=D0=A8=D0=B8=D1=80=D0=B8=D0=BD=D0=B0=20?= =?UTF-8?q?=D1=87=D1=91=D1=80=D1=82=D0=BE=D1=87=D0=B5=D0=BA=20=D0=B4=D0=BB?= =?UTF-8?q?=D1=8F=20=D0=BD=D0=B5=D0=B0=D0=BA=D1=82=D0=B8=D0=B2=D0=BD=D1=8B?= =?UTF-8?q?=D1=85=20=D0=BA=D0=BD=D0=BE=D0=BF=D0=BE=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scenes/button-flat/button-flat.tscn | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scenes/button-flat/button-flat.tscn b/scenes/button-flat/button-flat.tscn index 2a6dca18..74c54947 100644 --- a/scenes/button-flat/button-flat.tscn +++ b/scenes/button-flat/button-flat.tscn @@ -11,7 +11,7 @@ resource_local_to_scene = true shader = ExtResource("2_5ngiq") shader_parameter/rotation = 0.0 shader_parameter/color = Vector4(1, 1, 1, 0.1) -shader_parameter/width = 1.0 +shader_parameter/width = 3.0 shader_parameter/speed = 0.0 shader_parameter/disabled = false shader_parameter/scale = Vector2(1, 1) @@ -64,6 +64,7 @@ texture = SubResource("PlaceholderTexture2D_6nup5") centered = false script = SubResource("GDScript_uyl5d") speed = 0.0 +disabled = true [node name="state" type="NinePatchRect" parent="."] layout_mode = 2 From bae7a949e46149da17ab1c45cd9bbdd873cf0515 Mon Sep 17 00:00:00 2001 From: sasha80 Date: Thu, 18 Jul 2024 15:51:27 +0300 Subject: [PATCH 08/20] =?UTF-8?q?=D0=A0=D0=B5=D0=B3=D1=80=D0=B5=D1=81?= =?UTF-8?q?=D1=81=D0=B8=D1=8F.=20=D0=A0=D0=B5=D0=B6=D0=B8=D0=BC=20=D0=BF?= =?UTF-8?q?=D1=80=D0=BE=D0=B3=D1=80=D0=B0=D0=BC=D0=BC=D1=8B=20=D0=BF=D0=BE?= =?UTF-8?q?=D1=81=D0=BB=D0=B5=20=D0=B7=D0=B0=D0=BF=D1=83=D1=81=D0=BA=D0=B0?= =?UTF-8?q?=20=D0=B4=D0=BE=D0=BB=D0=B6=D0=B5=D0=BD=20=D0=B1=D1=8B=D1=82?= =?UTF-8?q?=D1=8C=20"=D0=A0=D0=B0=D0=B1=D0=BE=D1=82=D0=B0"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scenes/tabs-switch/tab-switch.tscn | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scenes/tabs-switch/tab-switch.tscn b/scenes/tabs-switch/tab-switch.tscn index 39f28bb9..605eb0a9 100644 --- a/scenes/tabs-switch/tab-switch.tscn +++ b/scenes/tabs-switch/tab-switch.tscn @@ -23,11 +23,9 @@ layout_mode = 0 offset_right = 1600.0 offset_bottom = 1200.0 tooltip_text = "Переключает режимы работы программы" -current_tab = 4 script = ExtResource("1_fg0vd") [node name="Работа" parent="tab_switch" instance=ExtResource("2_u7p16")] -visible = false layout_mode = 2 [node name="Контроль" parent="tab_switch" instance=ExtResource("3_txp0s")] @@ -43,6 +41,7 @@ visible = false layout_mode = 2 [node name="ЭМС2" parent="tab_switch" instance=ExtResource("6_rsg03")] +visible = false layout_mode = 2 [node name="Настройки" parent="tab_switch" instance=ExtResource("6_i8iv3")] From d826f8745c87b560247cc5e2075dc0dc440671f0 Mon Sep 17 00:00:00 2001 From: lepshiy Date: Thu, 25 Jul 2024 16:10:22 +0300 Subject: [PATCH 09/20] =?UTF-8?q?=D0=94=D0=BE=D1=80=D0=B0=D0=B1=D0=BE?= =?UTF-8?q?=D1=82=D0=BA=D0=B0.=20=D0=A3=D0=BB=D1=83=D1=87=D1=88=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=B4=D0=BE=D0=BA=D1=83=D0=BC=D0=B5=D0=BD?= =?UTF-8?q?=D1=82=D0=B0=D1=86=D0=B8=D0=B8.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addons/godot-logger/README.md | 2 +- addons/godot-logger/plugin.gd | 44 +- addons/godot-logger/scripts/logger.gd | 317 ++++--- addons/godot-logger/scripts/logger_output.gd | 89 +- project.godot | 23 + scenes/bip/bip.gd | 26 +- scenes/button-flat/nine-patch-button.gd | 66 +- scenes/cell/cell-front.gd | 4 +- scenes/diagram/diagram.gd | 30 +- scenes/frame-ecm-list/frm-ecm-list.gd | 12 +- scenes/frame-ecm-params/scroll-ecm-params.gd | 118 +-- scenes/frame-params/scroll-params.gd | 66 +- scenes/frame-threats/scroll-threats.gd | 112 +-- scenes/frame/frame.gd | 12 +- scenes/pribor-afsp/pribor-afsp.gd | 10 +- scenes/pribor-rtr/pribor-rtr.gd | 200 ++-- scenes/tabs-switch/tab-switch.gd | 6 +- scenes/tilemap/tilemap.gd | 267 +++--- scenes/tilemap/tilemap.tscn | 29 +- scenes/журнал/журнал.gd | 68 +- scenes/контроль/кассета.gd | 10 +- scenes/контроль/кнопка-прибора.gd | 8 +- scenes/контроль/контроль.gd | 76 +- scenes/контроль/прибор.gd | 18 +- scenes/настройки/настройки.gd | 24 +- scenes/работа/sld-mode.gd | 14 +- scenes/работа/работа.gd | 695 +++++++------- scenes/работа/работа.tscn | 31 +- scenes/эмс/кнопка-эмс.gd | 32 +- scenes/эмс/эмс.gd | 524 +++++------ scripts/capsrpb.gd | 80 +- scripts/file-table.gd | 54 +- scripts/hotkeys.gd | 22 +- scripts/interfer.gd | 414 ++++----- scripts/mercator.gd | 904 +++++++++---------- scripts/modbus.gd | 234 ++--- scripts/repnetwork.gd | 238 ++--- scripts/repsettings.gd | 69 +- scripts/signaller.gd | 42 +- scripts/tcp5p28.gd | 342 +++---- scripts/threats.gd | 188 ++-- scripts/tools.gd | 122 +-- scripts/yau07.gd | 404 ++++----- scripts/yems-boards.gd | 24 +- scripts/yems-consts.gd | 272 +++--- scripts/yems-devs.gd | 58 +- table/table.gd | 714 +++++++-------- 47 files changed, 3607 insertions(+), 3507 deletions(-) diff --git a/addons/godot-logger/README.md b/addons/godot-logger/README.md index 416382e0..0d6f5981 100644 --- a/addons/godot-logger/README.md +++ b/addons/godot-logger/README.md @@ -33,7 +33,7 @@ Log.fatal(text) const CUSTOM = Log.MAX << 1 # Bitwise left shift the MAX value for a custom level. func _ready() -> void: - Log.add_level(CUSTOM, "Level Name") + Log.add_level(CUSTOM, "Level Name") ``` ## Calling the custom level: diff --git a/addons/godot-logger/plugin.gd b/addons/godot-logger/plugin.gd index a6ff3f7f..116e5127 100644 --- a/addons/godot-logger/plugin.gd +++ b/addons/godot-logger/plugin.gd @@ -14,36 +14,36 @@ const LOGGER_OUTPUT_ICON = "res://addons/godot-logger/icons/logger.svg" func _def_settings(pname: String, default: Variant) -> void: - if not ProjectSettings.has_setting(pname): - ProjectSettings.set_setting(pname, default) + if not ProjectSettings.has_setting(pname): + ProjectSettings.set_setting(pname, default) - ProjectSettings.set_initial_value(pname, default) + ProjectSettings.set_initial_value(pname, default) func _enter_tree() -> void: - _def_settings("plugins/logger/log_enabled", true) - _def_settings("plugins/logger/default_output", false) + _def_settings("plugins/logger/log_enabled", true) + _def_settings("plugins/logger/default_output", false) - _def_settings("plugins/logger/level", 31) # The magic number is - INFO | DEBUG | WARNING | ERROR | FATAL - ProjectSettings.add_property_info( - { - "name": "plugins/logger/level", - "type": TYPE_INT, - "hint": PROPERTY_HINT_FLAGS, - "hint_string": "Info,Debug,Warning,Error,Fatal", - } - ) + _def_settings("plugins/logger/level", 31) # The magic number is - INFO | DEBUG | WARNING | ERROR | FATAL + ProjectSettings.add_property_info( + { + "name": "plugins/logger/level", + "type": TYPE_INT, + "hint": PROPERTY_HINT_FLAGS, + "hint_string": "Info,Debug,Warning,Error,Fatal", + } + ) - _def_settings("plugins/logger/file/file_path", "user://uarep-ctl.log") - _def_settings("plugins/logger/file/log_file_write", true) + _def_settings("plugins/logger/file/file_path", "user://uarep-ctl.log") + _def_settings("plugins/logger/file/log_file_write", true) - _def_settings("plugins/logger/default_output_format", "[{year}-{month}-{day} {hour}:{minute}:{second}][{level}]{text}") - _def_settings("plugins/logger/file_format", "[{year}-{month}-{day} {hour}:{minute}:{second}][{level}]{text}") + _def_settings("plugins/logger/default_output_format", "[{year}-{month}-{day} {hour}:{minute}:{second}][{level}]{text}") + _def_settings("plugins/logger/file_format", "[{year}-{month}-{day} {hour}:{minute}:{second}][{level}]{text}") - add_autoload_singleton(AUTOLOAD_NAME, AUTOLOAD_PATH) - add_custom_type(LOGGER_OUTPUT, "RichTextLabel", load(LOGGER_OUTPUT_SCRIPT), load(LOGGER_OUTPUT_ICON)) + add_autoload_singleton(AUTOLOAD_NAME, AUTOLOAD_PATH) + add_custom_type(LOGGER_OUTPUT, "RichTextLabel", load(LOGGER_OUTPUT_SCRIPT), load(LOGGER_OUTPUT_ICON)) func _exit_tree() -> void: - remove_custom_type(LOGGER_OUTPUT) - remove_autoload_singleton(AUTOLOAD_NAME) + remove_custom_type(LOGGER_OUTPUT) + remove_autoload_singleton(AUTOLOAD_NAME) diff --git a/addons/godot-logger/scripts/logger.gd b/addons/godot-logger/scripts/logger.gd index 1648f794..2f1ba8f6 100644 --- a/addons/godot-logger/scripts/logger.gd +++ b/addons/godot-logger/scripts/logger.gd @@ -1,21 +1,20 @@ # Copyright (c) 2020-2023 Mansur Isaev and contributors - MIT License # See `LICENSE.md` included in the source distribution for details. -## Logger class. -class_name Logger -extends Node +## Класс журнал событий. +class_name Logger extends Node -## Emitted when the logger create a message. +## Выдается, когда журнал событий создаёт сообщение. signal logged(message: Dictionary) enum { - INFO = 1, ## Info level. - DEBUG = 1<<1, ## Warning level. - WARNING = 1<<2, ## Debug level. - ERROR = 1<<3, ## Error level. - FATAL = 1<<4, ## Fatal level. - MAX = FATAL, ## Bitwise left shift for custom levels. + INFO = 1, ## Информационный уровень. + DEBUG = 1<<1, ## Уровень предупреждения. + WARNING = 1<<2, ## Уровень отладки. + ERROR = 1<<3, ## Уровень ошибок. + FATAL = 1<<4, ## Критический уровень. + MAX = FATAL, ## Побитовый сдвиг влево для пользовательских уровней. } @@ -32,235 +31,235 @@ var _format_default_output : String var _format_file : String var _names = { - INFO: "ИНФО", - DEBUG: "ОТЛАДКА", - WARNING: "ВНИМАНИЕ", - ERROR: "ОШИБКА", - FATAL: "АВАРИЯ" + INFO: "ИНФО", + DEBUG: "ОТЛАДКА", + WARNING: "ВНИМАНИЕ", + ERROR: "ОШИБКА", + FATAL: "АВАРИЯ" } var _msg_array: Array var _filters: Array -# Can be overridden to define custom default values. +## Преопределение пользовательских значений по умолчанию. func _enter_tree() -> void: - set_log_enabled(ProjectSettings.get_setting("plugins/logger/log_enabled", true)) - set_default_output_enabled(ProjectSettings.get_setting("plugins/logger/default_output", false)) + set_log_enabled(ProjectSettings.get_setting("plugins/logger/log_enabled", true)) + set_default_output_enabled(ProjectSettings.get_setting("plugins/logger/default_output", false)) - _level = ProjectSettings.get_setting("plugins/logger/level", INFO | DEBUG | WARNING | ERROR | FATAL) + _level = ProjectSettings.get_setting("plugins/logger/level", INFO | DEBUG | WARNING | ERROR | FATAL) - set_file_path(ProjectSettings.get_setting("plugins/logger/file/file_path", "user://uarep-ctl.log")) - set_file_write_enabled(ProjectSettings.get_setting("plugins/logger/file/log_file_write", true)) + set_file_path(ProjectSettings.get_setting("plugins/logger/file/file_path", "user://uarep-ctl.log")) + set_file_write_enabled(ProjectSettings.get_setting("plugins/logger/file/log_file_write", true)) - _format_default_output = ProjectSettings.get_setting("plugins/logger/default_output_format", "[{year}-{month}-{day} {hour}:{minute}:{second}][{level}]{text}") - _format_file = ProjectSettings.get_setting("plugins/logger/file_format", "[{year}-{month}-{day} {hour}:{minute}:{second}][{level}]{text}") + _format_default_output = ProjectSettings.get_setting("plugins/logger/default_output_format", "[{year}-{month}-{day} {hour}:{minute}:{second}][{level}]{text}") + _format_file = ProjectSettings.get_setting("plugins/logger/file_format", "[{year}-{month}-{day} {hour}:{minute}:{second}][{level}]{text}") func _exit_tree() -> void: - _close_file() + _close_file() -## Return [param true] if logger has level. +## Возвращает [param true], если у журнала события есть уровень. func has_level(level: int) -> bool: - return _names.has(level) + return _names.has(level) -## Set the level enabled. +## Установить включенный уровень. func set_level(level: int, enabled: bool) -> void: - assert(has_level(level), "Invalid level.") - if not has_level(level): - return + assert(has_level(level), "Invalid level.") + if not has_level(level): + return - if enabled: - _level |= level - else: - _level &= ~level + if enabled: + _level |= level + else: + _level &= ~level -## Add a custom log level. The level value must be unique and be greater than [member MAX]. -## To call the custom level use [method message] method. +## Добавить пользовательский уровень журнала. Значение уровня должно быть уникальным и превышать [member MAX]. +## Для вызова пользовательского уровня используйте метод - [method message]. func add_level(level: int, nm: String) -> void: - assert(not has_level(level), "Has level.") - assert(level > MAX and not level % 2, "Invalid level.") - assert(name, "Invalid name.") + assert(not has_level(level), "Has level.") + assert(level > MAX and not level % 2, "Invalid level.") + assert(name, "Invalid name.") - if not has_level(level) and level > MAX and not level % MAX and not name.is_empty(): - _names[level] = nm - set_level(level, true) + if not has_level(level) and level > MAX and not level % MAX and not name.is_empty(): + _names[level] = nm + set_level(level, true) -## Return the level name. +## Возвращает имя уровня. func get_level_name(level: int) -> String: - return _names[level] + return _names[level] -## Set Log enabled. Disable ALL messages. +## Установить включенный журнал. Отключить ВСЕ сообщения. func set_log_enabled(enabled: bool) -> void: - _log_enabled = enabled + _log_enabled = enabled -## Return [param true] if Log enabled. +## Возвращает [param true], если журнал включен. func is_log_enabled() -> bool: - return _log_enabled + return _log_enabled -## Set default output enabled. +## Установка выхода по умолчанию включена. func set_default_output_enabled(enabled: bool) -> void: - _default_output_enabled = enabled + _default_output_enabled = enabled -## Returns [param true] if default output is enabled. +## Возвращает [param true], если вывод по умолчанию включен. func is_default_output_enabled() -> bool: - return _default_output_enabled + return _default_output_enabled -## Set file write enabled. +## Установить разрешение на запись файла. func set_file_write_enabled(enabled: bool) -> void: - if _file_write_enabled == enabled: - return + if _file_write_enabled == enabled: + return - _file_write_enabled = enabled + _file_write_enabled = enabled - if enabled: - _open_file() - else: - _close_file() + if enabled: + _open_file() + else: + _close_file() -## Returns [param true] if write to a file is enabled. +## Возвращает [param true], если запись в файл разрешена. func is_file_write_enabled() -> bool: - return _file_write_enabled + return _file_write_enabled -## Set the path to the log file. +## Задать путь к файлу журнала. func set_file_path(path: String) -> void: - assert(path.is_absolute_path(), "Invalid path.") - if not path.is_absolute_path() or _file_path == path: - return + assert(path.is_absolute_path(), "Invalid path.") + if not path.is_absolute_path() or _file_path == path: + return - _file_path = path + _file_path = path - if is_file_write_enabled(): - _open_file() + if is_file_write_enabled(): + _open_file() -## Return path to log file. +## Возвращает путь к файлу журнала. func get_file_path() -> String: - return _file_path + return _file_path -## Create an info message. +## Создать информационное сообщение. func info(text: String) -> void: - message(INFO, text) + message(INFO, text) -## Create a debug message. Debug build only. +## Создать отладочное сообщение. Только для отладочной сборки. func debug(text: String) -> void: - if OS.is_debug_build(): - message(DEBUG, text) + if OS.is_debug_build(): + message(DEBUG, text) -## Create a warning message. +## Создать предупреждающее сообщение. func warning(text: String) -> void: - message(WARNING, text) + message(WARNING, text) -## Create an error message. +## Создать сообщение об ошибке. func error(text: String) -> void: - message(ERROR, text) + message(ERROR, text) -## Create a fatal error message. +## Создать сообщение о критической ошибке. func fatal(text: String) -> void: - message(FATAL, text) + message(FATAL, text) -## Format string for log file. +## Форматировать строку для файла. func format_file(msg: Dictionary) -> String: - return _format_file.format( - { - "year": "%02d" % msg["year"], - "month": "%02d" % msg["month"], - "day": "%02d" % msg["day"], - "hour": "%02d" % msg["hour"], - "minute":"%02d" % msg["minute"], - "second":"%02d" % msg["second"], - "level": _names[msg["level"]], - "text": msg["text"], - } - ) + return _format_file.format( + { + "year": "%02d" % msg["year"], + "month": "%02d" % msg["month"], + "day": "%02d" % msg["day"], + "hour": "%02d" % msg["hour"], + "minute":"%02d" % msg["minute"], + "second":"%02d" % msg["second"], + "level": _names[msg["level"]], + "text": msg["text"], + } + ) -## Format string for editor output. +## Форматировать строку для вывода редактора. func format_stdout(msg: Dictionary) -> String: - return _format_default_output.format( - { - "year": "%02d" % msg["year"], - "month": "%02d" % msg["month"], - "day": "%02d" % msg["day"], - "hour": "%02d" % msg["hour"], - "minute":"%02d" % msg["minute"], - "second":"%02d" % msg["second"], - "level": _names[msg["level"]], - "text": msg["text"], - } - ) + return _format_default_output.format( + { + "year": "%02d" % msg["year"], + "month": "%02d" % msg["month"], + "day": "%02d" % msg["day"], + "hour": "%02d" % msg["hour"], + "minute":"%02d" % msg["minute"], + "second":"%02d" % msg["second"], + "level": _names[msg["level"]], + "text": msg["text"], + } + ) -## Create a message with a custom level. +## Создать сообщение с пользовательским уровнем. func message(level: int, text: String) -> void: - assert(has_level(level), "Has not level.") - assert(text, "Invalid message text.") + assert(has_level(level), "Has not level.") + assert(text, "Invalid message text.") - if _log_enabled and _level & level: - var msg : Dictionary = Time.get_datetime_dict_from_system() - msg["level"] = level - msg["level_name"] = _names[level] - msg["text"] = " " + text + if _log_enabled and _level & level: + var msg : Dictionary = Time.get_datetime_dict_from_system() + msg["level"] = level + msg["level_name"] = _names[level] + msg["text"] = " " + text - _msg_array.append(msg) - if (_flt_msg(msg)): - logged.emit(msg) + _msg_array.append(msg) + if (_flt_msg(msg)): + logged.emit(msg) - if _file_write_enabled: - _file.store_line(format_file(msg)) - _file.call_deferred('flush') + if _file_write_enabled: + _file.store_line(format_file(msg)) + _file.call_deferred('flush') - if _default_output_enabled: - print(format_stdout(msg)) + if _default_output_enabled: + print(format_stdout(msg)) func _open_file() -> void: - if is_instance_valid(_file) and _file.is_open(): - _file.close() + if is_instance_valid(_file) and _file.is_open(): + _file.close() - _file = FileAccess.open(get_file_path(), FileAccess.READ_WRITE) + _file = FileAccess.open(get_file_path(), FileAccess.READ_WRITE) - var err := FileAccess.get_open_error() - if err: - _file = FileAccess.open(get_file_path(), FileAccess.WRITE) - _file.close() - _file = FileAccess.open(get_file_path(), FileAccess.READ_WRITE) - return print_debug(error_string(err)) + var err := FileAccess.get_open_error() + if err: + _file = FileAccess.open(get_file_path(), FileAccess.WRITE) + _file.close() + _file = FileAccess.open(get_file_path(), FileAccess.READ_WRITE) + return print_debug(error_string(err)) - err = _file.get_error() - if err: - return print_debug(error_string(err)) - _file.seek_end(0) + err = _file.get_error() + if err: + return print_debug(error_string(err)) + _file.seek_end(0) func _close_file() -> void: - if is_instance_valid(_file) and _file.is_open(): - _file = null + if is_instance_valid(_file) and _file.is_open(): + _file = null func save_file(path): - var f = FileAccess.open(path, FileAccess.WRITE) - var err := FileAccess.get_open_error() - if err: - print_debug(error_string(err)) - var log_text = _file.get_as_text() - f.store_line(log_text) - f.close() + var f = FileAccess.open(path, FileAccess.WRITE) + var err := FileAccess.get_open_error() + if err: + print_debug(error_string(err)) + var log_text = _file.get_as_text() + f.store_line(log_text) + f.close() func set_filtrs(flt_array: Array): - _filters = flt_array + _filters = flt_array func _flt_msg(msg): - var flt_arr: Array = [] - for item in _filters: - if item.button_pressed: - flt_arr.append(item.get_meta('filter')) - if msg['text'].find(item.get_meta('filter')) != -1: - return true - if msg['level_name'] in flt_arr: - return true - return false + var flt_arr: Array = [] + for item in _filters: + if item.button_pressed: + flt_arr.append(item.get_meta('filter')) + if msg['text'].find(item.get_meta('filter')) != -1: + return true + if msg['level_name'] in flt_arr: + return true + return false func add_msgs(): - for msg in _msg_array: - if (_flt_msg(msg)): - logged.emit(msg) + for msg in _msg_array: + if (_flt_msg(msg)): + logged.emit(msg) diff --git a/addons/godot-logger/scripts/logger_output.gd b/addons/godot-logger/scripts/logger_output.gd index 539daac2..768248a8 100644 --- a/addons/godot-logger/scripts/logger_output.gd +++ b/addons/godot-logger/scripts/logger_output.gd @@ -2,21 +2,20 @@ # See `LICENSE.md` included in the source distribution for details. @tool -## LoggerOutput class. -class_name LoggerOutput -extends RichTextLabel +## Класс вывода журнала событий. +class_name LoggerOutput extends RichTextLabel -## Emited when handle [signal Logger.logged] signal. +## Выдаётся при ошибке сигнала - [signal Logger.logged]. signal handled() @export var format_text : String = "[{year}-{month}-{day} {hour}:{minute}:{second}][{level}]{text}" @export var colors : Dictionary = { - Logger.INFO: Color.WHITE, - Logger.DEBUG: Color.GRAY, - Logger.WARNING: Color.YELLOW, - Logger.ERROR: Color.RED, - Logger.FATAL: Color.RED, + Logger.INFO: Color.WHITE, + Logger.DEBUG: Color.GRAY, + Logger.WARNING: Color.YELLOW, + Logger.ERROR: Color.RED, + Logger.FATAL: Color.RED, } @@ -24,55 +23,55 @@ var _logger : Logger func _enter_tree() -> void: - var logger := get_node_or_null("/root/log") as Logger - if is_instance_valid(logger): - set_logger(logger) + var logger := get_node_or_null("/root/log") as Logger + if is_instance_valid(logger): + set_logger(logger) func _exit_tree() -> void: - if is_instance_valid(_logger) and _logger.logged.is_connected(handle_message): - _logger.logged.disconnect(handle_message) + if is_instance_valid(_logger) and _logger.logged.is_connected(handle_message): + _logger.logged.disconnect(handle_message) -## Return the level color or [member Color.WHITE]. +## Вернуть цвет уровня или [member Color.WHITE]. func get_color(level: int) -> Color: - return colors.get(level, Color.WHITE) + return colors.get(level, Color.WHITE) -## Set the logger. +## Установить журнал событий. func set_logger(logger: Logger) -> void: - assert(is_instance_valid(logger), "Invalid Logger.") - if not is_instance_valid(logger) or is_same(_logger, logger): - return + assert(is_instance_valid(logger), "Invalid Logger.") + if not is_instance_valid(logger) or is_same(_logger, logger): + return - if is_instance_valid(_logger) and _logger.logged.is_connected(handle_message): - _logger.logged.disconnect(handle_message) + if is_instance_valid(_logger) and _logger.logged.is_connected(handle_message): + _logger.logged.disconnect(handle_message) - if not logger.logged.is_connected(handle_message): - logger.logged.connect(handle_message) + if not logger.logged.is_connected(handle_message): + logger.logged.connect(handle_message) - _logger = logger + _logger = logger -## Return the logger. +## Вернуть журнал событий. func get_logger() -> Logger: - return _logger + return _logger -## Return formatted string from message. +## Вернуть строку после форматирования из сообщения. func format(message: Dictionary) -> String: - return format_text.format( - { - "year": "%02d" % message["year"], - "month": "%02d" % message["month"], - "day": "%02d" % message["day"], - "hour": "%02d" % message["hour"], - "minute": "%02d" % message["minute"], - "second": "%02d" % message["second"], - "level": message["level_name"], - "text": message["text"], - } - ) + return format_text.format( + { + "year": "%02d" % message["year"], + "month": "%02d" % message["month"], + "day": "%02d" % message["day"], + "hour": "%02d" % message["hour"], + "minute": "%02d" % message["minute"], + "second": "%02d" % message["second"], + "level": message["level_name"], + "text": message["text"], + } + ) -## Handle the message. +## Обработать сообщение. func handle_message(message: Dictionary) -> void: - push_color(get_color(message["level"])) - append_text(format(message) + "\n") - pop() - handled.emit() + push_color(get_color(message["level"])) + append_text(format(message) + "\n") + pop() + handled.emit() diff --git a/project.godot b/project.godot index c198b867..77b0bbbe 100644 --- a/project.godot +++ b/project.godot @@ -111,6 +111,29 @@ theme/default_font_subpixel_positioning=3 theme/lcd_subpixel_layout=0 common/drop_mouse_on_gui_input_disabled=true +[input] + +up={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"echo":false,"script":null) +] +} +down={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"echo":false,"script":null) +] +} +left={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"echo":false,"script":null) +] +} +right={ +"deadzone": 0.5, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"echo":false,"script":null) +] +} + [input_devices] buffering/agile_event_flushing=true diff --git a/scenes/bip/bip.gd b/scenes/bip/bip.gd index ae49b23f..f3c5a29b 100644 --- a/scenes/bip/bip.gd +++ b/scenes/bip/bip.gd @@ -1,25 +1,25 @@ -extends Control +class_name bip_ extends Control var selected: bool -# Функция присваивает заданое значение видимости селектору +## Функция присваивает заданое значение видимости селектору. func set_sel_visible(value: bool): - selected = value - $selector.visible = value + selected = value + $selector.visible = value -# Функция присваивает заданое значение видимости селектору +## Функция присваивает заданое значение видимости селектору. func set_track_visible(value: bool): $track.visible = value -# Функция присваивает заданое значение видимости индикатору выполнения +## Функция присваивает заданое значение видимости индикатору выполнения. func set_ecm_visible(value: bool): $ecm_done.visible = value func _on_bip_gui_input(event): - if event is InputEventMouseButton and event.pressed: - match event.button_index: - MOUSE_BUTTON_LEFT: - signaller.emit_signal('bip_pressed', name.to_int()) - MOUSE_BUTTON_RIGHT: - if selected: - signaller.emit_signal('rto_threat_unsel', name.to_int()) + if event is InputEventMouseButton and event.pressed: + match event.button_index: + MOUSE_BUTTON_LEFT: + signaller.emit_signal('bip_pressed', name.to_int()) + MOUSE_BUTTON_RIGHT: + if selected: + signaller.emit_signal('rto_threat_unsel', name.to_int()) diff --git a/scenes/button-flat/nine-patch-button.gd b/scenes/button-flat/nine-patch-button.gd index 67189a3b..977151a4 100644 --- a/scenes/button-flat/nine-patch-button.gd +++ b/scenes/button-flat/nine-patch-button.gd @@ -12,66 +12,66 @@ func is_toggle_mode(): return $button.is_toggle_mode() @export var texture_state0: Texture2D = preload('res://data/кнопка-квадрат-0.png'): - set(val): $state.set_texture(val) + set(val): $state.set_texture(val) @export var texture_state1: Texture2D = preload('res://data/кнопка-квадрат-1.png'): - set(val): $state.set_texture(val) + set(val): $state.set_texture(val) @export var strips_rotation: float = 0.0: - set(val): - strips_rotation = val - $back.strips_rotation = deg_to_rad(val) - get: return strips_rotation + set(val): + strips_rotation = val + $back.strips_rotation = deg_to_rad(val) + get: return strips_rotation @export var text: String = '': - set(val): $button.set_text(val) - get: return $button.get_text() + set(val): $button.set_text(val) + get: return $button.get_text() @export var disabled: bool = false: - set(val): - disabled = val - $button.set_disabled(val) - $back.disabled = val - $state.modulate.a = 0.5 if val else 1.0 - get: return disabled + set(val): + disabled = val + $button.set_disabled(val) + $back.disabled = val + $state.modulate.a = 0.5 if val else 1.0 + get: return disabled @export var pressed: bool = false: - set(val): - pressed = val - $button.set_pressed(val) - get: - return pressed + set(val): + pressed = val + $button.set_pressed(val) + get: + return pressed @export var toggle_mode: bool = false: - set(val): - toggle_mode = val - $button.set_toggle_mode(val) - get: - return toggle_mode + set(val): + toggle_mode = val + $button.set_toggle_mode(val) + get: + return toggle_mode func _on_resized() -> void: - $back.scale = size - $back.material.set('shader_parameter/scale', size / Vector2(20.0, 20.0)) + $back.scale = size + $back.material.set('shader_parameter/scale', size / Vector2(20.0, 20.0)) func _enter_tree() -> void: - $back.scale = size - $back.material.set('shader_parameter/scale', size / Vector2(20.0, 20.0)) - $button.set_toggle_mode(true) - $button.connect('toggled', Callable(self, '_on_button_toggled')) + $back.scale = size + $back.material.set('shader_parameter/scale', size / Vector2(20.0, 20.0)) + $button.set_toggle_mode(true) + $button.connect('toggled', Callable(self, '_on_button_toggled')) func set_toggle_mode(val: bool): - $button.set_toggle_mode(val) + $button.set_toggle_mode(val) func _on_button_toggled(val: bool): - if val: $state.set_texture(texture_state1) - else: $state.set_texture(texture_state0) + if val: $state.set_texture(texture_state1) + else: $state.set_texture(texture_state0) diff --git a/scenes/cell/cell-front.gd b/scenes/cell/cell-front.gd index 0623b7a9..f0382a2a 100644 --- a/scenes/cell/cell-front.gd +++ b/scenes/cell/cell-front.gd @@ -1,5 +1,5 @@ extends Control @export var text: String = '': - set(val): $label.set_text(val) - get: return $label.get_text() + set(val): $label.set_text(val) + get: return $label.get_text() diff --git a/scenes/diagram/diagram.gd b/scenes/diagram/diagram.gd index 968c90db..f6af816e 100644 --- a/scenes/diagram/diagram.gd +++ b/scenes/diagram/diagram.gd @@ -1,10 +1,10 @@ -extends Sprite2D +class_name Diagram extends Sprite2D -var fs_default_colors: Array # Цвета по умолчанию для блоков ФС +var fs_default_colors: Array ## Цвета по умолчанию для блоков ФС. func _ready() -> void: - signaller.connect('map_mode_changed', Callable(self, 'set_view_mode')) - set_def_colors() + signaller.connect('map_mode_changed', Callable(self, 'set_view_mode')) + set_def_colors() func sv(val: float) -> float: return val / ($строб.scale.x / 2.0) * 0.125 @@ -30,15 +30,15 @@ func get_radius_inner_1() -> float: return material.get('shader_parameter/radiu func set_view_mode(val: int): material.set('shader_parameter/mode', val); func set_band_colors(val: Array): material.set('shader_parameter/ant_band_c', val) -## Закрашивание по умолчанию блоков ФС цветами из настроек +## Закрашивание по умолчанию блоков ФС цветами из настроек. func set_def_colors(): - var color0: Vector3 = ProjectSettings.get_setting('application/interfer/default_color0', Vector3(0.38, 0.47, 0.51)) - var color1: Vector3 = ProjectSettings.get_setting('application/interfer/default_color1', Vector3(0.39, 0.44, 0.50)) - var col_arr: Array = [color0, color1] - var col = false - for i in range(7): - for j in range (4): - fs_default_colors.append(col_arr[int(col)]) - col = not col - col = not col - set_band_colors(fs_default_colors) + var color0: Vector3 = ProjectSettings.get_setting('application/interfer/default_color0', Vector3(0.38, 0.47, 0.51)) + var color1: Vector3 = ProjectSettings.get_setting('application/interfer/default_color1', Vector3(0.39, 0.44, 0.50)) + var col_arr: Array = [color0, color1] + var col = false + for i in range(7): + for j in range (4): + fs_default_colors.append(col_arr[int(col)]) + col = not col + col = not col + set_band_colors(fs_default_colors) diff --git a/scenes/frame-ecm-list/frm-ecm-list.gd b/scenes/frame-ecm-list/frm-ecm-list.gd index c08ba4a9..c64bfce8 100644 --- a/scenes/frame-ecm-list/frm-ecm-list.gd +++ b/scenes/frame-ecm-list/frm-ecm-list.gd @@ -1,12 +1,12 @@ -extends NinePatchRect +class_name frm_ecm_list extends NinePatchRect -# Called when the node enters the scene tree for the first time. +## Called when the node enters the scene tree for the first time. func _ready(): - $grd_forced/btn_cancel.button_connect('pressed', Callable(self, 'on_btn_cancel_pressed').bind($grd_forced/btn_cancel)) + $grd_forced/btn_cancel.button_connect('pressed', Callable(self, 'on_btn_cancel_pressed').bind($grd_forced/btn_cancel)) func on_btn_cancel_pressed(btn): - signaller.emit_signal('interfer_off_all') - await get_tree().create_timer(0.1).timeout - btn.pressed = false + signaller.emit_signal('interfer_off_all') + await get_tree().create_timer(0.1).timeout + btn.pressed = false diff --git a/scenes/frame-ecm-params/scroll-ecm-params.gd b/scenes/frame-ecm-params/scroll-ecm-params.gd index f65b8635..2b8294a6 100644 --- a/scenes/frame-ecm-params/scroll-ecm-params.gd +++ b/scenes/frame-ecm-params/scroll-ecm-params.gd @@ -1,4 +1,4 @@ -extends ScrollContainer +class_name scroll_ecm_params extends ScrollContainer ## Отображает список параметров помехи в таблице. @@ -11,82 +11,82 @@ const TABLE_HEADERS_TEXT = ['Параметр', 'Значение' ] ## const TABLE_COLUMN_SIZE = [ 370, 85 ] ## Ширины колонок. const TABLE_PARAMS = { - 'kni': [0, 'Курсовой угол, гр.', false, 0.0, 360.0], - 'powp': [1, 'Мощность, %', true, 0.0, 100.0], - 'freq': [2, 'Частота, МГц', true, 400.0, 6000.0], - 'width': [3, 'Занимая полоса частот, МГц', true, 0.0, 120.0], - 'dur': [4, 'Длительность импульса, мкс', true, 0.0, 1000000.0], - 'period': [5, 'Период повтора, мкс', true, 0.0, 1000000.0] } + 'kni': [0, 'Курсовой угол, гр.', false, 0.0, 360.0], + 'powp': [1, 'Мощность, %', true, 0.0, 100.0], + 'freq': [2, 'Частота, МГц', true, 400.0, 6000.0], + 'width': [3, 'Занимая полоса частот, МГц', true, 0.0, 120.0], + 'dur': [4, 'Длительность импульса, мкс', true, 0.0, 1000000.0], + 'period': [5, 'Период повтора, мкс', true, 0.0, 1000000.0] } func init_table(): - for i in TABLE_PARAMS.size(): $table.add_row(TABLE_ROW) - for key in TABLE_PARAMS.keys(): - var p = TABLE_PARAMS[key] - $table.set_node_text(0, p[0], p[1]) - $table.set_node_alligment(0, p[0], HORIZONTAL_ALIGNMENT_LEFT) - $table.set_node_editable(0, p[0], false) - $table.set_node_editable(1, p[0], false) - $table.set_node_user_data(1, p[0], key) - $table.connect_columns(self, 'text_submitted', 'on_edit_entry', [1], []) - $table.set_columns_min_size(TABLE_COLUMN_SIZE) - $table.set_meta('id', 0) + for i in TABLE_PARAMS.size(): $table.add_row(TABLE_ROW) + for key in TABLE_PARAMS.keys(): + var p = TABLE_PARAMS[key] + $table.set_node_text(0, p[0], p[1]) + $table.set_node_alligment(0, p[0], HORIZONTAL_ALIGNMENT_LEFT) + $table.set_node_editable(0, p[0], false) + $table.set_node_editable(1, p[0], false) + $table.set_node_user_data(1, p[0], key) + $table.connect_columns(self, 'text_submitted', 'on_edit_entry', [1], []) + $table.set_columns_min_size(TABLE_COLUMN_SIZE) + $table.set_meta('id', 0) -# Called when the node enters the scene tree for the first time. +## Called when the node enters the scene tree for the first time. func _ready(): - signaller.connect('interfer_selected', Callable(self, 'on_interfer_selected')) - signaller.connect('rto_threat_unsel', Callable(self, 'on_rto_threat_unsel')) - call_deferred('init_table') + signaller.connect('interfer_selected', Callable(self, 'on_interfer_selected')) + signaller.connect('rto_threat_unsel', Callable(self, 'on_rto_threat_unsel')) + call_deferred('init_table') func on_edit_entry(text : String, entry): - var ecm = $table.get_meta('ecm') - if $table.has_meta('ecm') == false: return - var indexis = $table.get_key_indexes(entry[0].name) - var row_i = indexis[1] - var param = $table.get_node_user_data(1, row_i) - var p = TABLE_PARAMS[param] - var old_val = ecm.get_value(param) - var val = check_value(text, p[3], p[4], old_val) - ecm.set_value(val, param) - signaller.emit_signal('interfer_new', ecm) + var ecm = $table.get_meta('ecm') + if $table.has_meta('ecm') == false: return + var indexis = $table.get_key_indexes(entry[0].name) + var row_i = indexis[1] + var param = $table.get_node_user_data(1, row_i) + var p = TABLE_PARAMS[param] + var old_val = ecm.get_value(param) + var val = check_value(text, p[3], p[4], old_val) + ecm.set_value(val, param) + signaller.emit_signal('interfer_new', ecm) func check_value(val, min_val, max_val, old_val): - if val.is_valid_float(): - if float(val) < min_val: return min_val - if float(val) > max_val: return max_val - return float(val) - else: return old_val + if val.is_valid_float(): + if float(val) < min_val: return min_val + if float(val) > max_val: return max_val + return float(val) + else: return old_val func on_interfer_selected(ecm): - if ecm != null: - $table.set_meta('ecm', ecm) - else: - $table.set_meta('ecm', null) - on_ecm_update() + if ecm != null: + $table.set_meta('ecm', ecm) + else: + $table.set_meta('ecm', null) + on_ecm_update() func on_ecm_update(): - for item in len(TABLE_PARAMS): - $table.set_node_text(1, item, '') - $table.set_node_editable(1, item, false) - if $table.has_meta('ecm') == false: return - var ecm: interfer.Interfer = $table.get_meta('ecm') - var mnms = tools.get_members_names(ecm) - for item in ecm['params'].keys(): - mnms.append(item) - for member_name in mnms: - if not TABLE_PARAMS.has(member_name): continue - var p = TABLE_PARAMS[member_name] - var val = ecm.get_value(member_name) - $table.set_node_text(1, p[0], '%s' % val) - $table.set_node_editable(1, p[0], p[2]) + for item in len(TABLE_PARAMS): + $table.set_node_text(1, item, '') + $table.set_node_editable(1, item, false) + if $table.has_meta('ecm') == false: return + var ecm: interfer.Interfer = $table.get_meta('ecm') + var mnms = tools.get_members_names(ecm) + for item in ecm['params'].keys(): + mnms.append(item) + for member_name in mnms: + if not TABLE_PARAMS.has(member_name): continue + var p = TABLE_PARAMS[member_name] + var val = ecm.get_value(member_name) + $table.set_node_text(1, p[0], '%s' % val) + $table.set_node_editable(1, p[0], p[2]) func on_rto_threat_unsel(_th_id): - for item in range(len(TABLE_PARAMS)): - $table.set_node_text(1, item, '') - $table.set_node_editable(1, item, false) + for item in range(len(TABLE_PARAMS)): + $table.set_node_text(1, item, '') + $table.set_node_editable(1, item, false) diff --git a/scenes/frame-params/scroll-params.gd b/scenes/frame-params/scroll-params.gd index 45a6ce07..5a7c689a 100644 --- a/scenes/frame-params/scroll-params.gd +++ b/scenes/frame-params/scroll-params.gd @@ -1,6 +1,6 @@ -extends ScrollContainer +class_name scroll_params extends ScrollContainer -## Отображение параметров цели +## Отображение параметров цели. const CellLineEdit = preload('res://table/ячейка-2.tscn') ## Ячейка таблицы. const TableHeader = preload('res://table/header.tscn') ## Ячейка заголовок таблицы. @@ -15,51 +15,51 @@ const TABLE_COLUMN_SIZE = [ 370, 85 ] ## Ширины к ## Параметры цели для отображения в таблице.[br] ## [b]val[0][/b] - Индекс строки.[br][b]val[1][/b] - Содержание. const TABLE_PARAMS = { - 'baud': [0, 'Модуляционная скорость, бод'], - 'freqm': [1, 'Режим смены частоты.'], - 'power': [2, 'Уровень сигнала, дБм.'], - 'tmod': [3, 'Тип модуляции'], - 'width': [4, 'Занимая полоса частот, МГц.'], - 'rkrp': [5, 'Рекомендуемая помеха'] } + 'baud': [0, 'Модуляционная скорость, бод'], + 'freqm': [1, 'Режим смены частоты.'], + 'power': [2, 'Уровень сигнала, дБм.'], + 'tmod': [3, 'Тип модуляции'], + 'width': [4, 'Занимая полоса частот, МГц.'], + 'rkrp': [5, 'Рекомендуемая помеха'] } -## Обрабатывает сигнал [signal threats.threat_selected] +## Обрабатывает сигнал [signal threats.threat_selected]. func on_threat_selected(th): - $table.set_meta('id', th.id) - on_threat_update(th) + $table.set_meta('id', th.id) + on_threat_update(th) func init_table(): - for i in TABLE_PARAMS.size(): $table.add_row(TABLE_ROW) - for p in TABLE_PARAMS.values(): - $table.set_node_text(0, p[0], p[1]) - $table.set_node_alligment(0, p[0], HORIZONTAL_ALIGNMENT_LEFT) - $table.set_node_editable(0, p[0], false) - $table.set_node_editable(1, p[0], false) - $table.set_columns_min_size(TABLE_COLUMN_SIZE) - $table.set_meta('id', 0) + for i in TABLE_PARAMS.size(): $table.add_row(TABLE_ROW) + for p in TABLE_PARAMS.values(): + $table.set_node_text(0, p[0], p[1]) + $table.set_node_alligment(0, p[0], HORIZONTAL_ALIGNMENT_LEFT) + $table.set_node_editable(0, p[0], false) + $table.set_node_editable(1, p[0], false) + $table.set_columns_min_size(TABLE_COLUMN_SIZE) + $table.set_meta('id', 0) func _ready() -> void: - signaller.connect('threat_selected', Callable(self, 'on_threat_selected')) - signaller.connect('rto_threat_unsel', Callable(self, 'on_rto_threat_unsel')) - threats.connect('threat_update', Callable(self, 'on_threat_update')) - call_deferred('init_table') + signaller.connect('threat_selected', Callable(self, 'on_threat_selected')) + signaller.connect('rto_threat_unsel', Callable(self, 'on_rto_threat_unsel')) + threats.connect('threat_update', Callable(self, 'on_threat_update')) + call_deferred('init_table') ## Обрабатывает сигнал [signal threats.threat_update]. ## Обновляет отображение только для выбранной цели. func on_threat_update(th): - if th.selected == false: return - if th.id != $table.get_meta('id'): return - var mnms = tools.get_members_names(th) - for member_name in mnms: - if not TABLE_PARAMS.has(member_name): continue - var p = TABLE_PARAMS[member_name] - var val = th.get(member_name) - $table.set_node_text(1, p[0], '%s' % val) + if th.selected == false: return + if th.id != $table.get_meta('id'): return + var mnms = tools.get_members_names(th) + for member_name in mnms: + if not TABLE_PARAMS.has(member_name): continue + var p = TABLE_PARAMS[member_name] + var val = th.get(member_name) + $table.set_node_text(1, p[0], '%s' % val) func on_rto_threat_unsel(_th_id): - for item in range(len(TABLE_PARAMS)): - $table.set_node_text(1, item, '') + for item in range(len(TABLE_PARAMS)): + $table.set_node_text(1, item, '') diff --git a/scenes/frame-threats/scroll-threats.gd b/scenes/frame-threats/scroll-threats.gd index 3438a731..ea5136ba 100644 --- a/scenes/frame-threats/scroll-threats.gd +++ b/scenes/frame-threats/scroll-threats.gd @@ -1,4 +1,4 @@ -extends ScrollContainer +class_name scroll_threats extends ScrollContainer ## Отображает список целей в таблице. @@ -16,89 +16,89 @@ const TABLE_COLUMN_SIZE = [ 65, 145, 145, 90 ] ## Вызывается при нажатии указателем мыши на выбранный ряд. func on_row_pressed(i_row: int): - row_pressed(i_row) - signaller.emit_signal('rto_threat_sel', $table.get_node_user_data(0, i_row).id) + row_pressed(i_row) + signaller.emit_signal('rto_threat_sel', $table.get_node_user_data(0, i_row).id) func row_pressed(i_row: int): - $table.clear_rows_selected() - $table.set_row_selected(i_row, true) - $table.ensure_row_visible(i_row) - var threat = $table.get_node_user_data(0, i_row) - signaller.emit_signal('threat_selected', threat) + $table.clear_rows_selected() + $table.set_row_selected(i_row, true) + $table.ensure_row_visible(i_row) + var threat = $table.get_node_user_data(0, i_row) + signaller.emit_signal('threat_selected', threat) func init_table(): - $table.set_header(TABLE_HEADER) - $table.set_header_text(TABLE_HEADERS_TEXT) - $table.set_columns_min_size(TABLE_COLUMN_SIZE) - $table.pin_header(self, get_parent()) + $table.set_header(TABLE_HEADER) + $table.set_header_text(TABLE_HEADERS_TEXT) + $table.set_columns_min_size(TABLE_COLUMN_SIZE) + $table.pin_header(self, get_parent()) func _ready() -> void: - var table_index: = Dictionary() - threats.connect('threat_new', Callable(self, 'on_threat_new').bind(table_index)) - threats.connect('threat_lost', Callable(self, 'on_threat_lost').bind(table_index)) - threats.connect('threat_update', Callable(self, 'on_threat_update').bind(table_index)) - signaller.connect('rto_threat_sel', Callable(self, 'on_rto_threat_sel').bind($table)) - signaller.connect('rto_threat_unsel', Callable(self, 'on_rto_threat_unsel')) - $table.connect('row_pressed', Callable(self, 'on_row_pressed')) - call_deferred('init_table') + var table_index: = Dictionary() + threats.connect('threat_new', Callable(self, 'on_threat_new').bind(table_index)) + threats.connect('threat_lost', Callable(self, 'on_threat_lost').bind(table_index)) + threats.connect('threat_update', Callable(self, 'on_threat_update').bind(table_index)) + signaller.connect('rto_threat_sel', Callable(self, 'on_rto_threat_sel').bind($table)) + signaller.connect('rto_threat_unsel', Callable(self, 'on_rto_threat_unsel')) + $table.connect('row_pressed', Callable(self, 'on_row_pressed')) + call_deferred('init_table') -## Перестраивает индекс таблицы. Вызывать при удалении строки +## Перестраивает индекс таблицы. Вызывать при удалении строки. func rehash(table_index: Dictionary): - var rows_count = $table.get_rows_count() - for i_row in rows_count: - var item = $table.get_node2(0, i_row) - table_index[int(item.text)] = i_row + var rows_count = $table.get_rows_count() + for i_row in rows_count: + var item = $table.get_node2(0, i_row) + table_index[int(item.text)] = i_row -## Обрабатывает сигнал [b]threat_update[/b] +## Обрабатывает сигнал [b]threat_update[/b]. func on_threat_update(th, table_index: Dictionary): - var row = map_th_to_row(th) - var i_row = table_index[th.id] - $table.set_row_text(i_row, row) + var row = map_th_to_row(th) + var i_row = table_index[th.id] + $table.set_row_text(i_row, row) -## Обрабатывает сигнал [b]threat_lost[/b] +## Обрабатывает сигнал [b]threat_lost[/b]. func on_threat_lost(th, table_index: Dictionary): - $table.remove_row(table_index[th.id]) - table_index.clear() - rehash(table_index) + $table.remove_row(table_index[th.id]) + table_index.clear() + rehash(table_index) -## Обрабатывает сигнал [b]threat_new[/b] +## Обрабатывает сигнал [b]threat_new[/b]. func on_threat_new(th, table_index): - var i_row = $table.get_rows_count() - $table.add_row(TABLE_ROW) - var row = map_th_to_row(th) - $table.set_row_text(i_row, row) - $table.set_row_editable(i_row, false) - $table.set_node_user_data(0, i_row, th) - table_index[th.id] = i_row + var i_row = $table.get_rows_count() + $table.add_row(TABLE_ROW) + var row = map_th_to_row(th) + $table.set_row_text(i_row, row) + $table.set_row_editable(i_row, false) + $table.set_node_user_data(0, i_row, th) + table_index[th.id] = i_row -## Преобразует цель в строку таблицы +## Преобразует цель в строку таблицы. func map_th_to_row(th) -> Array: - var row: = Array() - row.resize(TABLE_COLUMN_PARAM.size()) - var i_col: int = 0 - for param_name in TABLE_COLUMN_PARAM: - row[i_col] = '%s' % th.get(param_name) - i_col += 1 - return row + var row: = Array() + row.resize(TABLE_COLUMN_PARAM.size()) + var i_col: int = 0 + for param_name in TABLE_COLUMN_PARAM: + row[i_col] = '%s' % th.get(param_name) + i_col += 1 + return row -## Выделяет выбранную цель в таблице +## Выделяет выбранную цель в таблице. func on_rto_threat_sel(th_id, tbl): - for i in tbl.get_rows_count(): - var node = tbl.get_node2(0, i) - if node.text.to_int() == th_id: - row_pressed(i) - break + for i in tbl.get_rows_count(): + var node = tbl.get_node2(0, i) + if node.text.to_int() == th_id: + row_pressed(i) + break -## Снимает выделение цели +## Снимает выделение цели. func on_rto_threat_unsel(_th_id): $table.clear_rows_selected() diff --git a/scenes/frame/frame.gd b/scenes/frame/frame.gd index f83e3a38..f51e0506 100644 --- a/scenes/frame/frame.gd +++ b/scenes/frame/frame.gd @@ -4,15 +4,15 @@ extends NinePatchRect @export var horizontal_alignment: HorizontalAlignment = HorizontalAlignment.HORIZONTAL_ALIGNMENT_CENTER: - get: return $header.horizontal_alignment if is_inside_tree() else HorizontalAlignment.HORIZONTAL_ALIGNMENT_CENTER - set(val): if is_inside_tree(): $header.horizontal_alignment = val + get: return $header.horizontal_alignment if is_inside_tree() else HorizontalAlignment.HORIZONTAL_ALIGNMENT_CENTER + set(val): if is_inside_tree(): $header.horizontal_alignment = val @export var vertical_alignment: VerticalAlignment = VerticalAlignment.VERTICAL_ALIGNMENT_CENTER: - get: return $header.vertical_alignment if is_inside_tree() else VerticalAlignment.VERTICAL_ALIGNMENT_CENTER - set(val): if is_inside_tree(): $header.vertical_alignment = val + get: return $header.vertical_alignment if is_inside_tree() else VerticalAlignment.VERTICAL_ALIGNMENT_CENTER + set(val): if is_inside_tree(): $header.vertical_alignment = val @export var text: String = 'caption text': - get: return $header.text if is_inside_tree() else '' - set(val): if is_inside_tree(): $header.text = val + get: return $header.text if is_inside_tree() else '' + set(val): if is_inside_tree(): $header.text = val diff --git a/scenes/pribor-afsp/pribor-afsp.gd b/scenes/pribor-afsp/pribor-afsp.gd index ba0c5654..fb4e4bca 100644 --- a/scenes/pribor-afsp/pribor-afsp.gd +++ b/scenes/pribor-afsp/pribor-afsp.gd @@ -6,13 +6,13 @@ var test_addr: = String() var test_data: = {} func _ready(): - test_req = HTTPRequest.new() - add_child(test_req) - test_req.request_completed.connect(_map_request_completed) + test_req = HTTPRequest.new() + add_child(test_req) + test_req.request_completed.connect(_map_request_completed) func _map_request_completed(): - pass + pass func on_req_test_data_timer(): - test_req.request(test_addr) + test_req.request(test_addr) diff --git a/scenes/pribor-rtr/pribor-rtr.gd b/scenes/pribor-rtr/pribor-rtr.gd index 11030370..e0bdd808 100644 --- a/scenes/pribor-rtr/pribor-rtr.gd +++ b/scenes/pribor-rtr/pribor-rtr.gd @@ -1,4 +1,4 @@ -extends "res://scenes/контроль/прибор.gd" +class_name pribor_rtr extends "res://scenes/контроль/прибор.gd" static var json_parser: JSON = JSON.new() @@ -9,94 +9,94 @@ static var json_parser: JSON = JSON.new() ## [param paths] - Массив содержащий отдельные элементы пути.[br] ## [param tokens] - Разделители используемые во входной строке для разделения элементов пути.[br] func map_solid_to_array(solid: String, paths: Array, tokens: Array = ['[', ']', '.']) -> void: - paths.clear() - var i_chs = [] - for sep in tokens: - var i = 0 - while i != -1: - i = solid.find(sep, i + 1) - i_chs.append(i) - i_chs.pop_back() - i_chs.sort() + paths.clear() + var i_chs = [] + for sep in tokens: + var i = 0 + while i != -1: + i = solid.find(sep, i + 1) + i_chs.append(i) + i_chs.pop_back() + i_chs.sort() - var i0 = 0 - for i1 in i_chs: - var ss: = [String()] - tools.get_string_slice(solid, ss, i0, i1) - if ss[0].is_valid_int(): - paths.append(ss[0].to_int()) - else: - if ss[0]: - paths.append(ss[0]) - i0 = i1 + 1 + var i0 = 0 + for i1 in i_chs: + var ss: = [String()] + tools.get_string_slice(solid, ss, i0, i1) + if ss[0].is_valid_int(): + paths.append(ss[0].to_int()) + else: + if ss[0]: + paths.append(ss[0]) + i0 = i1 + 1 func _on_request_rtr_request_completed(result, _response_code, headers, body: PackedByteArray): - if result != 0: - var rs = $request_rtr.get_result_string(result) - log.error('ошибка: \"%s\"' % rs) - return + if result != 0: + var rs = $request_rtr.get_result_string(result) + log.error('ошибка: \"%s\"' % rs) + return - var json_str: String = body.get_string_from_utf8() - if not json_str: - push_error('ошибка: не удаётся получить строку utf8 из данных (%s)' % headers) - return + var json_str: String = body.get_string_from_utf8() + if not json_str: + push_error('ошибка: не удаётся получить строку utf8 из данных (%s)' % headers) + return - var rc = json_parser.parse(json_str) - if rc != Error.OK: - var err = json_parser.get_error_message() - push_error('ошибка: разбор json прерван - \"%s\" (%s)' % [err, headers]) - return + var rc = json_parser.parse(json_str) + if rc != Error.OK: + var err = json_parser.get_error_message() + push_error('ошибка: разбор json прерван - \"%s\" (%s)' % [err, headers]) + return - var pp = ['DeviceDescription', 'DeviceName'] - var ret_val = [null, false] - var pp_copy = pp.duplicate(true) - tools.get_value_by_path(json_parser.data, pp, ret_val) - if not ret_val[1]: - push_error('ошибка: путь %s не найден (%s)' % [pp_copy, headers]) - return + var pp = ['DeviceDescription', 'DeviceName'] + var ret_val = [null, false] + var pp_copy = pp.duplicate(true) + tools.get_value_by_path(json_parser.data, pp, ret_val) + if not ret_val[1]: + push_error('ошибка: путь %s не найден (%s)' % [pp_copy, headers]) + return - var bort_rtr_name_left: StringName = $left.get_meta('DeviceName', '') - var bort_rtr_name_right: StringName = $right.get_meta('DeviceName', '') - var bb = {bort_rtr_name_left: $left, bort_rtr_name_right: $right} - if not bb.has(ret_val[0]): - return + var bort_rtr_name_left: StringName = $left.get_meta('DeviceName', '') + var bort_rtr_name_right: StringName = $right.get_meta('DeviceName', '') + var bb = {bort_rtr_name_left: $left, bort_rtr_name_right: $right} + if not bb.has(ret_val[0]): + return - var bort_rtr = bb[ret_val[0]] - for child in bort_rtr.get_node('konverter/grid').get_children(): - map_value_to_item(json_parser.data, child, 'value0') - map_value_to_item(json_parser.data, child, 'value1') - map_value_to_item(json_parser.data, child, 'value2') - map_error_codes_to_item(json_parser.data, child, 'state') - for child in bort_rtr.get_node('antenna1/grid').get_children(): - map_value_to_item(json_parser.data, child, 'value0') - map_value_to_item(json_parser.data, child, 'value1') - map_error_codes_to_item(json_parser.data, child, 'state') - for child in bort_rtr.get_node('antenna2/grid').get_children(): - map_value_to_item(json_parser.data, child, 'value0') - map_value_to_item(json_parser.data, child, 'value1') - map_error_codes_to_item(json_parser.data, child, 'state') - for child in bort_rtr.get_children(): - map_value_to_item(json_parser.data, child, 'value0') - map_value_to_item(json_parser.data, child, 'value1') - map_error_codes_to_item(json_parser.data, child, 'state') + var bort_rtr = bb[ret_val[0]] + for child in bort_rtr.get_node('konverter/grid').get_children(): + map_value_to_item(json_parser.data, child, 'value0') + map_value_to_item(json_parser.data, child, 'value1') + map_value_to_item(json_parser.data, child, 'value2') + map_error_codes_to_item(json_parser.data, child, 'state') + for child in bort_rtr.get_node('antenna1/grid').get_children(): + map_value_to_item(json_parser.data, child, 'value0') + map_value_to_item(json_parser.data, child, 'value1') + map_error_codes_to_item(json_parser.data, child, 'state') + for child in bort_rtr.get_node('antenna2/grid').get_children(): + map_value_to_item(json_parser.data, child, 'value0') + map_value_to_item(json_parser.data, child, 'value1') + map_error_codes_to_item(json_parser.data, child, 'state') + for child in bort_rtr.get_children(): + map_value_to_item(json_parser.data, child, 'value0') + map_value_to_item(json_parser.data, child, 'value1') + map_error_codes_to_item(json_parser.data, child, 'state') func map_value_to_item(data, item, value_name: StringName): - var meta = item.get_meta(value_name, '') - if meta == '': - return - if not (meta is String): - return - var paths: = Array() - map_solid_to_array(meta, paths) - var ret_val: = [null, false] - var paths_copy = paths.duplicate(true) - tools.get_value_by_path(data, paths, ret_val) - if ret_val[1]: - item.set(value_name, ret_val[0]) - else: - push_error('ошибка: путь %s для $%s.%s не найден' % [paths_copy, item.name, value_name]) + var meta = item.get_meta(value_name, '') + if meta == '': + return + if not (meta is String): + return + var paths: = Array() + map_solid_to_array(meta, paths) + var ret_val: = [null, false] + var paths_copy = paths.duplicate(true) + tools.get_value_by_path(data, paths, ret_val) + if ret_val[1]: + item.set(value_name, ret_val[0]) + else: + push_error('ошибка: путь %s для $%s.%s не найден' % [paths_copy, item.name, value_name]) ## 1. Поле [b]'ErCode'[/b] содержит [b]0[/b] - если нет ошибки, и другое значение - если есть ошибка.[br] @@ -105,29 +105,29 @@ func map_value_to_item(data, item, value_name: StringName): ## 4. [i]Нет результата порверки[/i] - поле [param state] принимает значение [b]0[/b], [i]всё ## исправно[/i] - [b]1[/b], [i]хотябы один[/i] параметр с ошибкой - [b]2[/b].[br] func map_error_codes_to_item(data, item, value_name: StringName): - var metas = item.get_meta(value_name, '') - if not (metas is Array): - return - var paths: = Array() - var values: = Array() - for meta in metas: - map_solid_to_array(meta, paths) - var ret_val: = [null, false] - var paths_copy = paths.duplicate(true) - tools.get_value_by_path(data, paths, ret_val) - if ret_val[1]: - values.append(int(ret_val[0])) - else: - push_error('ошибка: путь %s для $%s.%s не найден' % [paths_copy, item.name, value_name]) - var test_res = 1 if values.count(int(0)) == values.size() else 2 - item.set(value_name, test_res) + var metas = item.get_meta(value_name, '') + if not (metas is Array): + return + var paths: = Array() + var values: = Array() + for meta in metas: + map_solid_to_array(meta, paths) + var ret_val: = [null, false] + var paths_copy = paths.duplicate(true) + tools.get_value_by_path(data, paths, ret_val) + if ret_val[1]: + values.append(int(ret_val[0])) + else: + push_error('ошибка: путь %s для $%s.%s не найден' % [paths_copy, item.name, value_name]) + var test_res = 1 if values.count(int(0)) == values.size() else 2 + item.set(value_name, test_res) -## Вызывается при клике мыши на элементе контроля -## [param block_a] - Кликнутый элемент контроля +## Вызывается при клике мыши на элементе контроля. +## [param block_a] - Кликнутый элемент контроля. func _on_item_pressed(block_a: Control): - var metas = block_a.get_meta_list() - print_debug(block_a) - for n in metas: - var v = block_a.get_meta(n) - print('%s:%s' % [n, v]) + var metas = block_a.get_meta_list() + print_debug(block_a) + for n in metas: + var v = block_a.get_meta(n) + print('%s:%s' % [n, v]) diff --git a/scenes/tabs-switch/tab-switch.gd b/scenes/tabs-switch/tab-switch.gd index 31a8f966..d7715fe7 100644 --- a/scenes/tabs-switch/tab-switch.gd +++ b/scenes/tabs-switch/tab-switch.gd @@ -4,9 +4,9 @@ const TAB_TOSIGNAL_NAME = {0: 'режим_работа', 1: 'режим_конт func _enter_tree() -> void: - repnetwork.logger_page = $Журнал + repnetwork.logger_page = $Журнал func _on_tab_changed(tab) -> void: - if TAB_TOSIGNAL_NAME.has(tab): - signaller.emit_signal(TAB_TOSIGNAL_NAME[tab]) + if TAB_TOSIGNAL_NAME.has(tab): + signaller.emit_signal(TAB_TOSIGNAL_NAME[tab]) diff --git a/scenes/tilemap/tilemap.gd b/scenes/tilemap/tilemap.gd index 3920258b..4bd9c6d0 100644 --- a/scenes/tilemap/tilemap.gd +++ b/scenes/tilemap/tilemap.gd @@ -1,173 +1,174 @@ -extends Sprite2D +class_name main_tilemap extends Sprite2D -## Возможные состояния конечного автомата заданий +## Возможные состояния конечного автомата заданий. enum TASK_STATE { - DONE, ## Выполнено - WAIT, ## Ожидание плитки - RUN, ## Запуск выполнения - CHECK, ## Проверка пустого места - ERROR ## Ошибка + DONE, ## Выполнено. + WAIT, ## Ожидание плитки. + RUN, ## Запуск выполнения. + CHECK, ## Проверка пустого места. + ERROR ## Ошибка. } -## Координаты для проверок [lng, lat] +## Координаты для проверок [lng, lat]. const TEST_DATA: = { - 'Калиниград': [ 20.51095, 54.70649 ], - 'Москва': [ 37.6156, 55.7522 ], - 'Нью-Йорк': [ -74.006, 40.7143 ], - 'Сидней': [ 151.20732, -33.8678500 ], - 'Таганрог': [ 38.8969, 47.2362 ], - 'Хельсинки': [ 24.9354, 60.1695 ], - 'Азорские о-ва': [ 28.3996582, 38.5397568 ], - 'Мехико': [ -99.12766, 19.42847 ], - 'Белое море': [ 68.136606, 42.045546 ], - 'море Беллинсгаузена': [ -72.406587, -74.857997 ], - 'Фиджи': [ 178.065032, -17.713371 ], - 'Ноль': [ 0.0, 0.0 ], - 'Точка1': [ 170.0, 0.0 ], - 'Точка2': [-170.0, 0.0 ], - 'Море' : [ 19.4, 54.91 ] } + 'Калиниград': [ 20.51095, 54.70649 ], + 'Москва': [ 37.6156, 55.7522 ], + 'Нью-Йорк': [ -74.006, 40.7143 ], + 'Сидней': [ 151.20732, -33.8678500 ], + 'Таганрог': [ 38.8969, 47.2362 ], + 'Хельсинки': [ 24.9354, 60.1695 ], + 'Азорские о-ва': [ 28.3996582, 38.5397568 ], + 'Мехико': [ -99.12766, 19.42847 ], + 'Белое море': [ 68.136606, 42.045546 ], + 'море Беллинсгаузена': [ -72.406587, -74.857997 ], + 'Фиджи': [ 178.065032, -17.713371 ], + 'Ноль': [ 0.0, 0.0 ], + 'Точка1': [ 170.0, 0.0 ], + 'Точка2': [-170.0, 0.0 ], + 'Море' : [ 19.4, 54.91 ] } const MAP_MODES: = { - 0: 'application/config/map_image_size_rto', - 1: 'application/config/map_image_size_rls' } + 0: 'application/config/map_image_size_rto', + 1: 'application/config/map_image_size_rls' } const MAP_RELATIVE_RADIUSES: = { - 0: 0.39, - 1: 0.466 + 0: 0.39, + 1: 0.466 } -var map_image: Image ## Изображение с картой -var map_tile_state: TASK_STATE ## Текущее состояние конечного автомата плитки -var map_bcs: Vector3 ## Яркость, контраст насыщенность -var map_server_addr: String ## Адрес сервера изображений карт в проекции Меркатора +var map_image: Image ## Изображение с картой. +var map_tile_state: TASK_STATE ## Текущее состояние конечного автомата плитки. +var map_bcs: Vector3 ## Яркость, контраст насыщенность. +var map_server_addr: String ## Адрес сервера изображений карт в проекции Меркатора. var map_tile_format: String ## -var map_tile_stack: Array ## Стек для хранения плиток карты -var map_request: HTTPRequest ## Обработчик запросов http -var map_zoom: int ## Уровень детализации карты -var map_tile: Rect2i ## Область одной плитки: [x-координата области, y-координата области, x-индекс плитки, y-индекс плитки] -var map_mode: int ## Режим карты: 0 - приоритет РТО, 1 - приоритет РЛС +var map_tile_stack: Array ## Стек для хранения плиток карты. +var map_request: HTTPRequest ## Обработчик запросов http. +var map_zoom: int ## Уровень детализации карты. +var map_tile: Rect2i ## Область одной плитки: [x-координата области, y-координата области, x-индекс плитки, y-индекс плитки]. +var map_mode: int ## Режим карты: 0 - приоритет РТО, 1 - приоритет РЛС. var prev_tiles_dic: Dictionary ## -var lnglat: Array ## Текущие координаты +var lnglat: Array ## Текущие координаты. var root_rect: Rect2i - - + func _ready(): - lnglat = TEST_DATA['Таганрог'] - map_tile_state = TASK_STATE.DONE - map_request = HTTPRequest.new() - add_child(map_request) - map_request.request_completed.connect(self._map_request_completed) - map_bcs = ProjectSettings.get_setting('application/config/map_bcs', Vector3(1.0, 1.0, 1.0)) - map_server_addr = ProjectSettings.get_setting('application/config/map_server_addr') - map_tile_format = ProjectSettings.get_setting('application/config/map_tile_format', '%s/%d/%d/%d.png') - var map_image_size = ProjectSettings.get_setting(MAP_MODES[0], Vector2i(512, 512)) - var map_tile_size = ProjectSettings.get_setting('application/config/map_tile_size', 256) - map_image = Image.create(map_image_size.x, map_image_size.y, true, Image.FORMAT_RGB8) - map_tile = Rect2i(0, 0, map_tile_size, map_tile_size) - signaller.connect('zoom_changed', Callable(self, '_on_zoom_changed')) - signaller.connect('map_mode_changed', Callable(self, '_on_map_mode_changed')) - log.info('Адрес сервера поставщика карт: \"%s\"' % map_server_addr) - log.info('Модуль географической карты готов') + lnglat = TEST_DATA['Таганрог'] + map_tile_state = TASK_STATE.DONE + map_request = HTTPRequest.new() + add_child(map_request) + map_request.request_completed.connect(self._map_request_completed) + map_bcs = ProjectSettings.get_setting('application/config/map_bcs', Vector3(1.0, 1.0, 1.0)) + map_server_addr = ProjectSettings.get_setting('application/config/map_server_addr') + map_tile_format = ProjectSettings.get_setting('application/config/map_tile_format', '%s/%d/%d/%d.png') + var map_image_size = ProjectSettings.get_setting(MAP_MODES[0], Vector2i(512, 512)) + var map_tile_size = ProjectSettings.get_setting('application/config/map_tile_size', 256) + map_image = Image.create(map_image_size.x, map_image_size.y, true, Image.FORMAT_RGB8) + map_tile = Rect2i(0, 0, map_tile_size, map_tile_size) + signaller.connect('zoom_changed', Callable(self, '_on_zoom_changed')) + signaller.connect('map_mode_changed', Callable(self, '_on_map_mode_changed')) + log.info('Адрес сервера поставщика карт: \"%s\"' % map_server_addr) + log.info('Модуль географической карты готов') + func _on_map_mode_changed(mode: int) -> void: - var map_image_size = ProjectSettings.get_setting(MAP_MODES[mode]) - material.set('shader_parameter/radius', MAP_RELATIVE_RADIUSES[mode]) - map_image.fill(Color(0, 0, 0, 0)) - map_image.resize(map_image_size.x, map_image_size.y) - map_tile_state = TASK_STATE.DONE - map_mode = mode - var tile: = mercator.create_tile(lnglat[0], lnglat[1], map_zoom) - map_tile_stack.clear() - map_tile_stack.append(tile) - prev_tiles_dic.clear() + var map_image_size = ProjectSettings.get_setting(MAP_MODES[mode]) + material.set('shader_parameter/radius', MAP_RELATIVE_RADIUSES[mode]) + map_image.fill(Color(0, 0, 0, 0)) + map_image.resize(map_image_size.x, map_image_size.y) + map_tile_state = TASK_STATE.DONE + map_mode = mode + var tile: = mercator.create_tile(lnglat[0], lnglat[1], map_zoom) + map_tile_stack.clear() + map_tile_stack.append(tile) + prev_tiles_dic.clear() func mark_image_center(image: Image, color: Color = Color.RED): - var c = image.get_size() / 2 - map_image.set_pixel(c.x - 1, c.y - 1, color) - map_image.set_pixel(c.x - 1, c.y, color) - map_image.set_pixel(c.x, c.y - 1, color) - map_image.set_pixel(c.x, c.y, color) + var c = image.get_size() / 2 + map_image.set_pixel(c.x - 1, c.y - 1, color) + map_image.set_pixel(c.x - 1, c.y, color) + map_image.set_pixel(c.x, c.y - 1, color) + map_image.set_pixel(c.x, c.y, color) func _on_zoom_changed(zoom: int): - if map_tile_stack.size() != 0: - return - map_zoom = zoom - var tile: = mercator.create_tile(lnglat[0], lnglat[1], zoom) - map_tile_stack.clear() - map_tile_stack.append(tile) - prev_tiles_dic.clear() + if map_tile_stack.size() != 0: + return + map_zoom = zoom + var tile: = mercator.create_tile(lnglat[0], lnglat[1], zoom) + map_tile_stack.clear() + map_tile_stack.append(tile) + prev_tiles_dic.clear() func img_tile_to_map_image(image: Image, dst_pos: Vector2i): - image.adjust_bcs(map_bcs.x, map_bcs.y, map_bcs.z) - map_image.blit_rect(image, map_tile, dst_pos) + image.adjust_bcs(map_bcs.x, map_bcs.y, map_bcs.z) + map_image.blit_rect(image, map_tile, dst_pos) func _process(_delta: float): - if (map_tile_state == TASK_STATE.DONE) and map_tile_stack.size(): - var tile: mercator.Tile = map_tile_stack[-1] - var tile_addr: = map_tile_format % [map_server_addr, tile.z, tile.x , tile.y] - var rc = map_request.request(tile_addr) - if rc == OK: - map_tile_state = TASK_STATE.WAIT - else: - log.error('%s: Невозможно сформировать запрос HTTP' % self) - map_tile_state = TASK_STATE.ERROR - elif map_tile_state == TASK_STATE.CHECK: - var root_tile: = mercator.Tile.new(root_rect.size.x, root_rect.size.y, map_zoom) - var neighbors_tiles = mercator.get_perimetr_tiles(root_tile, (map_image.get_size().x / map_tile.size.x) / 2) - var neighbors_tiles_dic = {} - for tile in neighbors_tiles: - neighbors_tiles_dic[[tile.x, tile.y]] = false - if neighbors_tiles_dic != prev_tiles_dic: - for tile in neighbors_tiles: - tile.is_root = false - map_tile_stack.push_front(tile) - prev_tiles_dic[[tile.x, tile.y]] = false - else: - if map_tile_stack.size() == 0: - signaller.emit_signal('zoom_complete', map_zoom) - map_tile_state = TASK_STATE.DONE + if (map_tile_state == TASK_STATE.DONE) and map_tile_stack.size(): + var tile: mercator.Tile = map_tile_stack[-1] + var tile_addr: = map_tile_format % [map_server_addr, tile.z, tile.x , tile.y] + var rc = map_request.request(tile_addr) + if rc == OK: + map_tile_state = TASK_STATE.WAIT + else: + log.error('%s: Невозможно сформировать запрос HTTP' % self) + map_tile_state = TASK_STATE.ERROR + elif map_tile_state == TASK_STATE.CHECK: + var root_tile: = mercator.Tile.new(root_rect.size.x, root_rect.size.y, map_zoom) + var neighbors_tiles = mercator.get_perimetr_tiles(root_tile, (map_image.get_size().x / map_tile.size.x) / 2) + var neighbors_tiles_dic = {} + for tile in neighbors_tiles: + neighbors_tiles_dic[[tile.x, tile.y]] = false + if neighbors_tiles_dic != prev_tiles_dic: + for tile in neighbors_tiles: + tile.is_root = false + map_tile_stack.push_front(tile) + prev_tiles_dic[[tile.x, tile.y]] = false + else: + if map_tile_stack.size() == 0: + signaller.emit_signal('zoom_complete', map_zoom) + map_tile_state = TASK_STATE.DONE func _map_request_completed(result, response_code, headers, data): - if result != HTTPRequest.RESULT_SUCCESS: - log.error('%s: Запрос не обработан. result: \"%s\", response_code: \"%s\", headers: \"%s\"' % [self, result, response_code, headers]) - map_tile_state = TASK_STATE.ERROR - return + if result != HTTPRequest.RESULT_SUCCESS: + log.error('%s: Запрос не обработан. result: \"%s\", response_code: \"%s\", headers: \"%s\"' % [self, result, response_code, headers]) + map_tile_state = TASK_STATE.ERROR + return - var img_tile = Image.new() - var error = img_tile.load_png_from_buffer(data) - if error != OK: - log.error('%s: Полученые данные невозможно использовать для формироания изображения. result: \"%s\", response_code: \"%s\", headers: \"%s\"' % [self, result, response_code, headers]) - map_tile_state = TASK_STATE.ERROR - return + var img_tile = Image.new() + var error = img_tile.load_png_from_buffer(data) + if error != OK: + log.error('%s: Полученые данные невозможно использовать для формироания изображения. result: \"%s\", response_code: \"%s\", headers: \"%s\"' % [self, result, response_code, headers]) + map_tile_state = TASK_STATE.ERROR + return - var tile = map_tile_stack.pop_back() - map_tile_state = TASK_STATE.CHECK + var tile = map_tile_stack.pop_back() + map_tile_state = TASK_STATE.CHECK - if tile.is_root: - # Отображение корневой плитки - map_image.fill(Color(0, 0, 0, 0)) - var bounds: = mercator.bounds(tile) - var lng_c = (bounds.east + bounds.west) / 2.0 - var lat_c = (bounds.north + bounds.south) / 2.0 - var sx: float = float(map_tile.size.x) / (bounds.east - bounds.west) * (lng_c - tile.lng) + map_tile.size.x / 2.0 # Cмещение в пискселях центра плитки от заданной точки по горизонтали. - var sy: float = float(map_tile.size.y) / (bounds.north - bounds.south) * (lat_c - tile.lat) - map_tile.size.y / 2.0 # Cмещение в пискселях центра плитки от заданной точки по вертикали. - var dst_pos: Vector2i = Vector2i(int(sx), int(-sy)) + map_tile.size * map_mode - img_tile_to_map_image(img_tile, dst_pos) - root_rect = Rect2i(dst_pos.x, dst_pos.y, tile.x, tile.y) - else: - # Отображение соседних плиток - var delta: = Vector2i(tile.x - root_rect.size.x, tile.y - root_rect.size.y) - var dst_pos: Vector2i = root_rect.position + delta * map_tile.size - img_tile_to_map_image(img_tile, dst_pos) - mark_image_center(map_image) - texture = ImageTexture.create_from_image(map_image) + if tile.is_root: + # Отображение корневой плитки + map_image.fill(Color(0, 0, 0, 0)) + var bounds: = mercator.bounds(tile) + var lng_c = (bounds.east + bounds.west) / 2.0 + var lat_c = (bounds.north + bounds.south) / 2.0 + var sx: float = float(map_tile.size.x) / (bounds.east - bounds.west) * (lng_c - tile.lng) + map_tile.size.x / 2.0 # Cмещение в пискселях центра плитки от заданной точки по горизонтали. + var sy: float = float(map_tile.size.y) / (bounds.north - bounds.south) * (lat_c - tile.lat) - map_tile.size.y / 2.0 # Cмещение в пискселях центра плитки от заданной точки по вертикали. + var dst_pos: Vector2i = Vector2i(int(sx), int(-sy)) + map_tile.size * map_mode + img_tile_to_map_image(img_tile, dst_pos) + root_rect = Rect2i(dst_pos.x, dst_pos.y, tile.x, tile.y) + else: + # Отображение соседних плиток + var delta: = Vector2i(tile.x - root_rect.size.x, tile.y - root_rect.size.y) + var dst_pos: Vector2i = root_rect.position + delta * map_tile.size + img_tile_to_map_image(img_tile, dst_pos) + mark_image_center(map_image) + texture = ImageTexture.create_from_image(map_image) + diff --git a/scenes/tilemap/tilemap.tscn b/scenes/tilemap/tilemap.tscn index 406784f3..9dcc09b9 100644 --- a/scenes/tilemap/tilemap.tscn +++ b/scenes/tilemap/tilemap.tscn @@ -1,14 +1,41 @@ -[gd_scene load_steps=4 format=3 uid="uid://bbq6nklh36yij"] +[gd_scene load_steps=6 format=3 uid="uid://bbq6nklh36yij"] [ext_resource type="Script" path="res://scenes/tilemap/tilemap.gd" id="1_mkfgj"] [ext_resource type="Shader" path="res://shaders/tilemap.gdshader" id="1_tsdui"] +[ext_resource type="Script" path="res://scenes/tilemap/Node2D.gd" id="3_lr4ti"] [sub_resource type="ShaderMaterial" id="ShaderMaterial_pmqvj"] resource_local_to_scene = true shader = ExtResource("1_tsdui") shader_parameter/radius = 0.39 +[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_ys5v4"] + [node name="tilemap" type="Sprite2D"] material = SubResource("ShaderMaterial_pmqvj") position = Vector2(560, 560) script = ExtResource("1_mkfgj") + +[node name="Panel" type="Panel" parent="."] +offset_left = -563.0 +offset_top = -557.0 +offset_right = 1022.0 +offset_bottom = 641.0 +theme_override_styles/panel = SubResource("StyleBoxEmpty_ys5v4") + +[node name="Node2D" type="Node2D" parent="."] +script = ExtResource("3_lr4ti") + +[node name="LineEdit" type="LineEdit" parent="Node2D"] +offset_left = -490.0 +offset_top = 518.0 +offset_right = -244.0 +offset_bottom = 565.0 + +[node name="VBoxContainer" type="VBoxContainer" parent="Node2D"] +offset_left = -499.0 +offset_top = 200.0 +offset_right = -239.0 +offset_bottom = 430.0 + +[connection signal="gui_input" from="Panel" to="." method="_on_panel_gui_input"] diff --git a/scenes/журнал/журнал.gd b/scenes/журнал/журнал.gd index ad400e81..1385bcd6 100644 --- a/scenes/журнал/журнал.gd +++ b/scenes/журнал/журнал.gd @@ -9,58 +9,58 @@ func on_interfer_off(ecm, _ecms, _thrs,msg: String): log.message(Logger.INFO, '% func on_set_program_mode(msg: String): log.message(Logger.INFO, msg) func on_interfer_new(ecm, msg: String): - if ecm.krp != interfer.INTERFER_OFF: - log.message(Logger.INFO, '%s: %s' % [msg, ecm.to_string()]) + if ecm.krp != interfer.INTERFER_OFF: + log.message(Logger.INFO, '%s: %s' % [msg, ecm.to_string()]) func _enter_tree(): - var flts: Array = [] - for item in $filtrs.get_children(): - flts.append(item) - log.set_filtrs(flts) + var flts: Array = [] + for item in $filtrs.get_children(): + flts.append(item) + log.set_filtrs(flts) func _ready() -> void: - log.info('Модуль журнала работы готов') - log.info('Аргументы коммандной строки: \"%s\"' % OS.get_cmdline_args()) - log.info('Операционная система: %s' % [OS.get_distribution_name()]) - log.info('Текущая версия ПО УАРЭП: %s' % commit.VCS_HEAD) - threats.connect('threat_new', Callable(self, 'on_threats_changed').bind('Цель обнаружена')) - threats.connect('threat_lost', Callable(self, 'on_threats_changed').bind('Цель потеряна')) - signaller.conn(signaller.interfer_new, Callable(self, 'on_interfer_new').bind('Создан сеанс помехи')) - signaller.conn(signaller.interfer_off, Callable(self, 'on_interfer_off').bind('Сеанс помехи отключен')) - signaller.conn(signaller.режим_работа, Callable(log, 'info').bind('Программа в режиме \"Работа\"')) - signaller.conn(signaller.режим_настройки, Callable(log, 'info').bind('Программа в режиме \"Настройки\"')) - signaller.conn(signaller.режим_эмс, Callable(log, 'info').bind('Программа в режиме \"ЭМС\"')) - signaller.conn(signaller.режим_журнал, Callable(log, 'info').bind('Программа в режиме \"Журнал\"')) - signaller.conn(signaller.режим_контроль, Callable(log, 'info').bind('Программа в режиме \"Контроль\"')) + log.info('Модуль журнала работы готов') + log.info('Аргументы коммандной строки: \"%s\"' % OS.get_cmdline_args()) + log.info('Операционная система: %s' % [OS.get_distribution_name()]) + log.info('Текущая версия ПО УАРЭП: %s' % commit.VCS_HEAD) + threats.connect('threat_new', Callable(self, 'on_threats_changed').bind('Цель обнаружена')) + threats.connect('threat_lost', Callable(self, 'on_threats_changed').bind('Цель потеряна')) + signaller.conn(signaller.interfer_new, Callable(self, 'on_interfer_new').bind('Создан сеанс помехи')) + signaller.conn(signaller.interfer_off, Callable(self, 'on_interfer_off').bind('Сеанс помехи отключен')) + signaller.conn(signaller.режим_работа, Callable(log, 'info').bind('Программа в режиме \"Работа\"')) + signaller.conn(signaller.режим_настройки, Callable(log, 'info').bind('Программа в режиме \"Настройки\"')) + signaller.conn(signaller.режим_эмс, Callable(log, 'info').bind('Программа в режиме \"ЭМС\"')) + signaller.conn(signaller.режим_журнал, Callable(log, 'info').bind('Программа в режиме \"Журнал\"')) + signaller.conn(signaller.режим_контроль, Callable(log, 'info').bind('Программа в режиме \"Контроль\"')) func _on_clear_button_down() -> void: - $content.clear() - log.info('Cодержание журнала очищено оператором') + $content.clear() + log.info('Cодержание журнала очищено оператором') func _on_btn_save_button_down() -> void: - $content.hide() - $btn_clear.hide() - $btn_save.hide() - $filtrs.hide() - $dlg_file.show() + $content.hide() + $btn_clear.hide() + $btn_save.hide() + $filtrs.hide() + $dlg_file.show() func _on_dlg_file_confirmed() -> void: - $content.show() - $btn_clear.show() - $btn_save.show() - $filtrs.show() - $dlg_file.hide() + $content.show() + $btn_clear.show() + $btn_save.show() + $filtrs.show() + $dlg_file.hide() func _on_dlg_file_file_selected(path): - log.save_file(path) + log.save_file(path) func _on_check_box_pressed(): - $content.clear() - log.add_msgs() + $content.clear() + log.add_msgs() diff --git a/scenes/контроль/кассета.gd b/scenes/контроль/кассета.gd index fb83827d..a8d394b0 100644 --- a/scenes/контроль/кассета.gd +++ b/scenes/контроль/кассета.gd @@ -7,11 +7,11 @@ extends NinePatchRect """ @export var fname: String: - set(v): - fname = v - $lbl_fname.text = v + set(v): + fname = v + $lbl_fname.text = v @export var rname: String: - set(v): - rname = v + set(v): + rname = v diff --git a/scenes/контроль/кнопка-прибора.gd b/scenes/контроль/кнопка-прибора.gd index 4575a6ca..e0f2369e 100644 --- a/scenes/контроль/кнопка-прибора.gd +++ b/scenes/контроль/кнопка-прибора.gd @@ -7,7 +7,7 @@ extends Node func _on_toggled(toggled_on): $frame.visible = toggled_on @export var state: int: - set(v): - v = 0 if v < 0 else v % state_colors.size() - state = v - $label.self_modulate = state_colors[v] + set(v): + v = 0 if v < 0 else v % state_colors.size() + state = v + $label.self_modulate = state_colors[v] diff --git a/scenes/контроль/контроль.gd b/scenes/контроль/контроль.gd index 562cb76b..295867b6 100644 --- a/scenes/контроль/контроль.gd +++ b/scenes/контроль/контроль.gd @@ -6,50 +6,50 @@ extends Panel const PRIBORS_RES = \ { - 'pribor_afsp_l': ['res://scenes/pribor-afsp/pribor-afsp.tscn', 'Контроль исправности прибора АФСП левый'], - 'pribor_afsp_r': ['res://scenes/pribor-afsp/pribor-afsp.tscn', 'Контроль исправности прибора АФСП правый'], - 'pribor_prd_k_1': ['res://scenes/pribor-prd-k/pribor-prd-k.tscn', 'Контроль исправности прибора ПРД-К 1'], - 'pribor_prd_k_2': ['res://scenes/pribor-prd-k/pribor-prd-k.tscn', 'Контроль исправности прибора ПРД-К 2'], - 'pribor_prd_k_3': ['res://scenes/pribor-prd-k/pribor-prd-k.tscn', 'Контроль исправности прибора ПРД-К 3'], - 'pribor_prd_k_4': ['res://scenes/pribor-prd-k/pribor-prd-k.tscn', 'Контроль исправности прибора ПРД-К 4'], - 'pribor_prd_n_1': ['res://scenes/pribor-prd-n/pribor-prd-n.tscn', 'Контроль исправности прибора ПРД-Н 1'], - 'pribor_prd_n_2': ['res://scenes/pribor-prd-n/pribor-prd-n.tscn', 'Контроль исправности прибора ПРД-Н 2'], - 'pribor_prd_n_3': ['res://scenes/pribor-prd-n/pribor-prd-n.tscn', 'Контроль исправности прибора ПРД-Н 3'], - 'pribor_prd_n_4': ['res://scenes/pribor-prd-n/pribor-prd-n.tscn', 'Контроль исправности прибора ПРД-Н 4'], - 'pribor_prd_v_1': ['res://scenes/pribor-prd-v/pribor-prd-v.tscn', 'Контроль исправности прибора ПРД-В 1'], - 'pribor_prd_v_2': ['res://scenes/pribor-prd-v/pribor-prd-v.tscn', 'Контроль исправности прибора ПРД-В 2'], - 'pribor_prd_v_3': ['res://scenes/pribor-prd-v/pribor-prd-v.tscn', 'Контроль исправности прибора ПРД-В 3'], - 'pribor_prd_v_4': ['res://scenes/pribor-prd-v/pribor-prd-v.tscn', 'Контроль исправности прибора ПРД-В 4'], - 'pribor_rtr': ['res://scenes/pribor-rtr/pribor-rtr.tscn', 'Контроль исправности прибора РТР'], - 'pribor_uf': ['res://scenes/pribor-uf/pribor-uf.tscn', 'Контроль исправности прибора УФ'], - 'pribor_uyep': ['res://scenes/pribor-uyep/pribor-uyep.tscn', 'Контроль исправности прибора УЭП'], - } + 'pribor_afsp_l': ['res://scenes/pribor-afsp/pribor-afsp.tscn', 'Контроль исправности прибора АФСП левый'], + 'pribor_afsp_r': ['res://scenes/pribor-afsp/pribor-afsp.tscn', 'Контроль исправности прибора АФСП правый'], + 'pribor_prd_k_1': ['res://scenes/pribor-prd-k/pribor-prd-k.tscn', 'Контроль исправности прибора ПРД-К 1'], + 'pribor_prd_k_2': ['res://scenes/pribor-prd-k/pribor-prd-k.tscn', 'Контроль исправности прибора ПРД-К 2'], + 'pribor_prd_k_3': ['res://scenes/pribor-prd-k/pribor-prd-k.tscn', 'Контроль исправности прибора ПРД-К 3'], + 'pribor_prd_k_4': ['res://scenes/pribor-prd-k/pribor-prd-k.tscn', 'Контроль исправности прибора ПРД-К 4'], + 'pribor_prd_n_1': ['res://scenes/pribor-prd-n/pribor-prd-n.tscn', 'Контроль исправности прибора ПРД-Н 1'], + 'pribor_prd_n_2': ['res://scenes/pribor-prd-n/pribor-prd-n.tscn', 'Контроль исправности прибора ПРД-Н 2'], + 'pribor_prd_n_3': ['res://scenes/pribor-prd-n/pribor-prd-n.tscn', 'Контроль исправности прибора ПРД-Н 3'], + 'pribor_prd_n_4': ['res://scenes/pribor-prd-n/pribor-prd-n.tscn', 'Контроль исправности прибора ПРД-Н 4'], + 'pribor_prd_v_1': ['res://scenes/pribor-prd-v/pribor-prd-v.tscn', 'Контроль исправности прибора ПРД-В 1'], + 'pribor_prd_v_2': ['res://scenes/pribor-prd-v/pribor-prd-v.tscn', 'Контроль исправности прибора ПРД-В 2'], + 'pribor_prd_v_3': ['res://scenes/pribor-prd-v/pribor-prd-v.tscn', 'Контроль исправности прибора ПРД-В 3'], + 'pribor_prd_v_4': ['res://scenes/pribor-prd-v/pribor-prd-v.tscn', 'Контроль исправности прибора ПРД-В 4'], + 'pribor_rtr': ['res://scenes/pribor-rtr/pribor-rtr.tscn', 'Контроль исправности прибора РТР'], + 'pribor_uf': ['res://scenes/pribor-uf/pribor-uf.tscn', 'Контроль исправности прибора УФ'], + 'pribor_uyep': ['res://scenes/pribor-uyep/pribor-uyep.tscn', 'Контроль исправности прибора УЭП'], + } func _ready(): - var group = ButtonGroup.new() - for k in PRIBORS_RES: - var pribor_node = get_node(k) - assert(pribor_node) - var pribor_path = PRIBORS_RES[k][0] - var header_text = PRIBORS_RES[k][1] - pribor_node.set_button_group(group) - pribor_node.connect('button_down', Callable(self, 'on_pribor_press').bind(pribor_path, header_text)) - log.info('Модуль контроля работы готов') + var group = ButtonGroup.new() + for k in PRIBORS_RES: + var pribor_node = get_node(k) + assert(pribor_node) + var pribor_path = PRIBORS_RES[k][0] + var header_text = PRIBORS_RES[k][1] + pribor_node.set_button_group(group) + pribor_node.connect('button_down', Callable(self, 'on_pribor_press').bind(pribor_path, header_text)) + log.info('Модуль контроля работы готов') func on_pribor_press(pribor_path, header_text): - clear_obj($mnemo_container) - var pribor_obj = load(pribor_path) - assert(pribor_obj) - var pribor = pribor_obj.instantiate() - assert(pribor) - pribor.set_header_text(header_text) - $mnemo_container.add_child(pribor) + clear_obj($mnemo_container) + var pribor_obj = load(pribor_path) + assert(pribor_obj) + var pribor = pribor_obj.instantiate() + assert(pribor) + pribor.set_header_text(header_text) + $mnemo_container.add_child(pribor) func clear_obj(obj: Object): - var items = obj.get_children() - for item in items: - obj.remove_child(item) - item.queue_free() + var items = obj.get_children() + for item in items: + obj.remove_child(item) + item.queue_free() diff --git a/scenes/контроль/прибор.gd b/scenes/контроль/прибор.gd index 09099753..80f630d0 100644 --- a/scenes/контроль/прибор.gd +++ b/scenes/контроль/прибор.gd @@ -7,20 +7,20 @@ extends Control """ @export var fname: String: - set(v): - fname = v - get: - return fname + set(v): + fname = v + get: + return fname @export var rname: String: - set(v): - rname = v - get: - return rname + set(v): + rname = v + get: + return rname func set_header_text(header_text: String): - $lbl_header.text = header_text + $lbl_header.text = header_text @export var nodes_mask: String = '' diff --git a/scenes/настройки/настройки.gd b/scenes/настройки/настройки.gd index 18b07fab..c7f05b08 100644 --- a/scenes/настройки/настройки.gd +++ b/scenes/настройки/настройки.gd @@ -1,18 +1,18 @@ -extends Panel +class_name setting extends Panel -# Called when the node enters the scene tree for the first time. +## Вызывается, когда узел впервые попадает в дерево сцены. func _ready() -> void: - $lbl_commit.text = 'Текущая версия ПО УАРЭП: \"%s\"' % commit.VCS_HEAD - if not OS.get_cmdline_args().has('++ограниченный'): - return - $Label2.text = 'Текущий режим доступа: ограниченный' - for node in $GridContainer.get_children().filter(func(b): return b is Button): - node.disabled = true + $lbl_commit.text = 'Текущая версия ПО УАРЭП: \"%s\"' % commit.VCS_HEAD + if not OS.get_cmdline_args().has('++ограниченный'): + return + $Label2.text = 'Текущий режим доступа: ограниченный' + for node in $GridContainer.get_children().filter(func(b): return b is Button): + node.disabled = true func _on_external_cu_btn_pressed() -> void: - var state = $GridContainer2/external_cu_btn.button_pressed - ProjectSettings.set_setting('application/config/external_cu', state) - if state: - signaller.emit_signal('interfer_off_all') + var state = $GridContainer2/external_cu_btn.button_pressed + ProjectSettings.set_setting('application/config/external_cu', state) + if state: + signaller.emit_signal('interfer_off_all') diff --git a/scenes/работа/sld-mode.gd b/scenes/работа/sld-mode.gd index f8e97d42..dc5e0063 100644 --- a/scenes/работа/sld-mode.gd +++ b/scenes/работа/sld-mode.gd @@ -7,10 +7,10 @@ func _ready(): _on_value_changed(3) func _on_value_changed(val): - val = int(val) - for nm in VAL_TO_LABEL.values(): - var lbl0 = get_node(nm) - lbl0.self_modulate.a = 0.45 - var lbl1 = get_node(VAL_TO_LABEL[val]) - lbl1.self_modulate.a = 1.0 - signaller.emit_signal(VAL_TO_EVENT[val]) + val = int(val) + for nm in VAL_TO_LABEL.values(): + var lbl0 = get_node(nm) + lbl0.self_modulate.a = 0.45 + var lbl1 = get_node(VAL_TO_LABEL[val]) + lbl1.self_modulate.a = 1.0 + signaller.emit_signal(VAL_TO_EVENT[val]) diff --git a/scenes/работа/работа.gd b/scenes/работа/работа.gd index 6e04b6c3..c2da2304 100644 --- a/scenes/работа/работа.gd +++ b/scenes/работа/работа.gd @@ -1,477 +1,492 @@ @tool -extends Panel +class_name work extends Panel -## Основной скрипт для режима "Работа" +## Основной скрипт для режима "Работа". -@export var Bip: PackedScene ## Индикатор излучения цели -@export var col_grey: Color ## Второй цвет для фона -@export var col_red: Color ## Первый цвет для фона -@export var drag_scale_band: float ## Масштаб изменения параметра от движения мыши для строба по радиальному размеру -@export var drag_scale_width: float ## Масштаб изменения параметра от движения мыши для строба по угловому размеру +@export var Bip: PackedScene ## Индикатор излучения цели. +@export var col_grey: Color ## Второй цвет для фона. +@export var col_red: Color ## Первый цвет для фона. +@export var drag_scale_band: float ## Масштаб изменения параметра от движения мыши для строба по радиальному размеру. +@export var drag_scale_width: float ## Масштаб изменения параметра от движения мыши для строба по угловому размеру. @export var freq_high: float ## Верхняя рабочая частота, МГЦ. @export var freq_low: float ## Нижняя рабочая частота, МГЦ. -@export var radius_center: Vector2 ## Координаты центра диаграммы пеленг-частота -@export var radius_internal: float ## Внутренний радиус диаграммы пеленг-частота -@export var radius_outter: float ## Внешний радиус диаграммы пеленг-частота -@export var strob_max_band: float ## Максимальная ширина строба по углу -@export var strob_max_width: float ## Максимальная ширина строба по углу -@export var strob_min_band: float ## Минимальная ширина строба по частоте -@export var strob_min_width: float ## Минимальная ширина строба по углу -@export var window_0_id: int ## Идетификатор окна. Присваивается godot от 0 до количества созданных окон +@export var radius_center: Vector2 ## Координаты центра диаграммы пеленг-частота. +@export var radius_internal: float ## Внутренний радиус диаграммы пеленг-частота. +@export var radius_outter: float ## Внешний радиус диаграммы пеленг-частота. +@export var strob_max_band: float ## Максимальная ширина строба по углу. +@export var strob_max_width: float ## Максимальная ширина строба по углу. +@export var strob_min_band: float ## Минимальная ширина строба по частоте. +@export var strob_min_width: float ## Минимальная ширина строба по углу. +@export var window_0_id: int ## Идетификатор окна. Присваивается godot от 0 до количества созданных окон. -var drag_button: MouseButton ## Кнопка мыши нажатая при перетаскивании -var drag_fsm: DragFSM = DragFSM.IDLE ## Текущее состояние перетаскивания -var btn_select: BtnSel = BtnSel.THREAT ## Текущее состояние кнопки выбора цели -var drag_pos_begin: Vector2 ## Точка начала перетаскивания -var max_zoom: int ## Максимальный уровень детализации -var min_zoom: int ## Минимальный уровень детализации -var shift_pressed: bool ## Кнопка SHIFT нажата -var strob_band_begin: float ## Ширина строба в момент начала перетаскивания -var strob_dir_begin: float ## Направление антенны в момент начала перетаскивания -var strob_width_begin: float ## Ширина строба по углу в момент начала перетаскивания +var drag_button: MouseButton ## Кнопка мыши нажатая при перетаскивании. +var drag_fsm: DragFSM = DragFSM.IDLE ## Текущее состояние перетаскивания. +var btn_select: BtnSel = BtnSel.THREAT ## Текущее состояние кнопки выбора цели. +var drag_pos_begin: Vector2 ## Точка начала перетаскивания. +var max_zoom: int ## Максимальный уровень детализации. +var min_zoom: int ## Минимальный уровень детализации. +var shift_pressed: bool ## Кнопка SHIFT нажата. +var strob_band_begin: float ## Ширина строба в момент начала перетаскивания. +var strob_dir_begin: float ## Направление антенны в момент начала перетаскивания. +var strob_width_begin: float ## Ширина строба по углу в момент начала перетаскивания. -signal drag_begin ## Вызывается в момент начала перетаскивания -signal drag_continue ## Вызывается во время продолжения перетаскивания -signal drag_end ## Вызывается в момент завершения перетаскивания +signal drag_begin ## Вызывается в момент начала перетаскивания. +signal drag_continue ## Вызывается во время продолжения перетаскивания. +signal drag_end ## Вызывается в момент завершения перетаскивания. -## Границы секторов блоков ФС по частоте в режиме РТО +## Границы секторов блоков ФС по частоте в режиме РТО. const fs_band_rto: Dictionary = { - 0: [200.0, 250.0], - 1: [250.0, 300.0], - 2: [300.0, 350.0], - 3: [350.0, 400.0], - 4: [400.0, 450.0], - 5: [450.0, 500.0], - 6: [500.0, 550.0] } + 0: [200.0, 250.0], + 1: [250.0, 300.0], + 2: [300.0, 350.0], + 3: [350.0, 400.0], + 4: [400.0, 450.0], + 5: [450.0, 500.0], + 6: [500.0, 550.0] } -## Границы секторов блоков ФС по частоте в режиме РЛС +## Границы секторов блоков ФС по частоте в режиме РЛС. const fs_band_rls: Dictionary = { - 0: [480.0, 490.0], - 1: [490.0, 500.0], - 2: [500.0, 510.0], - 3: [510.0, 520.0], - 4: [520.0, 530.0], - 5: [530.0, 540.0], - 6: [540.0, 550.0] } + 0: [480.0, 490.0], + 1: [490.0, 500.0], + 2: [500.0, 510.0], + 3: [510.0, 520.0], + 4: [520.0, 530.0], + 5: [530.0, 540.0], + 6: [540.0, 550.0] } -## Границы секторов блоков ФС по пеленгу +## Границы секторов блоков ФС по пеленгу. const fs_sectors: Dictionary = { - 0: [0.0, 90.0], - 3: [90.0, 180.0], - 2: [180.0, 270.0], - 1: [270.0, 360.0] } + 0: [0.0, 90.0], + 3: [90.0, 180.0], + 2: [180.0, 270.0], + 1: [270.0, 360.0] } const fs_freq: Dictionary = { - 0: [400.0, 1200.0], - 1: [1200.0, 2000.0], - 2: [2000.0, 2800.0], - 3: [2800.0, 3600.0], - 4: [3600.0, 4400.0], - 5: [4400.0, 5200.0], - 6: [5200.0, 6000.0] } + 0: [400.0, 1200.0], + 1: [1200.0, 2000.0], + 2: [2000.0, 2800.0], + 3: [2800.0, 3600.0], + 4: [3600.0, 4400.0], + 5: [4400.0, 5200.0], + 6: [5200.0, 6000.0] } var fs_band = fs_band_rto # Текущий режим блоков ФС по частоте var fs_selected: Dictionary # Словарь выбранных секторов var fs_active: Dictionary # Словарь секторов назначенных на подавление -## Состояние перетаскивания +## Состояние перетаскивания. enum DragFSM { - DRAG, ## В процессе - IDLE, ## Не активно, ожидание - OFF ## Выключено - } + DRAG, ## В процессе. + IDLE, ## Не активно, ожидание. + OFF ## Выключено. + } -## Состояние перетаскивания +## Состояние перетаскивания. enum BtnSel { - SECTOR, ## В процессе - THREAT, ## Не активно, ожидание - FS ## Выключено - } + SECTOR, ## В процессе. + THREAT, ## Не активно, ожидание. + FS ## Выключено. + } -## Обработчик сигнала изменения списка целей +## Обработчик сигнала изменения списка целей. func on_threats_resized(threats: Dictionary): $count_all_pad/count_all.text = '%02d' % threats.size() -## Обработчик выбора цели +## Обработчик выбора цели. func on_threat_selected(th, ecm_btns): on_rto_threat_sel(th.id, ecm_btns) -## Обработчик нажатия на кнопку вид +## Обработчик нажатия на кнопку вид. func on_button_view_toggled(toggled: bool): - signaller.emit_signal('map_mode_changed', int(toggled)) - if toggled: - fs_band = fs_band_rls - else: - fs_band = fs_band_rto + signaller.emit_signal('map_mode_changed', int(toggled)) + if toggled: + fs_band = fs_band_rls + else: + fs_band = fs_band_rto ## func _enter_tree() -> void: - var timer1 = Timer.new() - timer1.name = 'timer1' - add_child(timer1) - timer1.one_shot = true - timer1.connect('timeout', Callable(self, 'on_enter_tree')) - timer1.start(1.0) + var timer1 = Timer.new() + timer1.name = 'timer1' + add_child(timer1) + timer1.one_shot = true + timer1.connect('timeout', Callable(self, 'on_enter_tree')) + timer1.start(1.0) func _exit_tree() -> void: - var timer1 = get_node('timer1') - remove_child(timer1) - timer1.queue_free() + var timer1 = get_node('timer1') + remove_child(timer1) + timer1.queue_free() -## Производит инициализацию +## Производит инициализацию. func _ready(): - min_zoom = ProjectSettings.get_setting('application/config/map_minimum_zoom', 5) - max_zoom = ProjectSettings.get_setting('application/config/map_maximum_zoom', 19) - drag_fsm = DragFSM.OFF - $btn_zoom.set_meta('zoom', min_zoom - 1) - var btns_select: Array = $btns_select.get_children() - btns_select.any(func(btn): btn.button_connect('pressed', Callable(self, 'on_button_select').bind(btn, btns_select))) - $btn_view.button_connect('toggled', Callable(self, 'on_button_view_toggled')) - var nodes: Array = get_tree().get_nodes_in_group('группа-режим-помехи') - nodes.any(func(btn): btn.button_connect('pressed', Callable(self, 'on_button_pressed').bind(btn, nodes))) - threats.connect('threat_new', Callable(self, 'on_threat_new')) - threats.connect('threat_lost', Callable(self, 'on_threat_lost')) - threats.connect('threats_resized', Callable(self, 'on_threats_resized')) - threats.connect('threat_update', Callable(self, 'on_threat_update')) - signaller.connect('zoom_complete', Callable(self, '_on_zoom_complete')) - signaller.connect('rto_threat_sel', Callable(self, 'on_rto_threat_sel').bind(nodes)) - signaller.connect('bip_pressed', Callable(self, 'on_bip_pressed').bind(nodes)) - signaller.connect('rto_threat_unsel', Callable(self, 'on_rto_threat_unsel').bind(nodes)) - signaller.connect('threat_selected', Callable(self, 'on_threat_selected').bind(nodes)) - signaller.connect('interfer_selected', Callable(self, 'on_interfer_selected').bind(nodes)) - signaller.connect('interfer_new', Callable(self, 'on_interfer_new')) - signaller.connect('interfer_off_all', Callable(self, 'on_interfer_off_all')) - signaller.connect('fs_selected', Callable(self, 'on_fs_selected')) - interfer.connect('interfer_update', Callable(self, 'on_interfer_new')) - + min_zoom = ProjectSettings.get_setting('application/config/map_minimum_zoom', 5) + max_zoom = ProjectSettings.get_setting('application/config/map_maximum_zoom', 19) + drag_fsm = DragFSM.OFF + $btn_zoom.set_meta('zoom', min_zoom - 1) + var btns_select: Array = $btns_select.get_children() + btns_select.any(func(btn): btn.button_connect('pressed', Callable(self, 'on_button_select').bind(btn, btns_select))) + $btn_view.button_connect('toggled', Callable(self, 'on_button_view_toggled')) + var nodes: Array = get_tree().get_nodes_in_group('группа-режим-помехи') + nodes.any(func(btn): btn.button_connect('pressed', Callable(self, 'on_button_pressed').bind(btn, nodes))) + threats.connect('threat_new', Callable(self, 'on_threat_new')) + threats.connect('threat_lost', Callable(self, 'on_threat_lost')) + threats.connect('threats_resized', Callable(self, 'on_threats_resized')) + threats.connect('threat_update', Callable(self, 'on_threat_update')) + signaller.connect('zoom_complete', Callable(self, '_on_zoom_complete')) + signaller.connect('rto_threat_sel', Callable(self, 'on_rto_threat_sel').bind(nodes)) + signaller.connect('bip_pressed', Callable(self, 'on_bip_pressed').bind(nodes)) + signaller.connect('rto_threat_unsel', Callable(self, 'on_rto_threat_unsel').bind(nodes)) + signaller.connect('threat_selected', Callable(self, 'on_threat_selected').bind(nodes)) + signaller.connect('interfer_selected', Callable(self, 'on_interfer_selected').bind(nodes)) + signaller.connect('interfer_new', Callable(self, 'on_interfer_new')) + signaller.connect('interfer_off_all', Callable(self, 'on_interfer_off_all')) + signaller.connect('fs_selected', Callable(self, 'on_fs_selected')) + interfer.connect('interfer_update', Callable(self, 'on_interfer_new')) + var items_list = [] + recurse(get_tree().get_root(), items_list) + print_debug(items_list) +func recurse(obj, items_list: Array): + var childrens = obj.get_children() + for ch in childrens: + items_list.append(ch) + recurse(ch, items_list) + +func get_all_nodes(root): + var nodes = [] + nodes.append(root) + for child in root.get_children(): + nodes.append_array(get_all_nodes(child)) + return nodes + func on_enter_tree() -> void: - call_deferred('_on_btn_scale_pressed') - call_deferred('on_button_strobe_pressed') - $tilemap.position = $canvas.get_strob_center() + call_deferred('_on_btn_scale_pressed') + call_deferred('on_button_strobe_pressed') + $tilemap.position = $canvas.get_strob_center() -## Производит отображение цели на диаграмму +## Производит отображение цели на диаграмму. func map_threat_to_bip(th: threats.Threat, bip: Control): - bip.position = tools.pos_calc( - th.freq, - th.aoa, - radius_center, - radius_internal, - radius_outter, - freq_low, - freq_high) - bip.size / 2 + bip.position = tools.pos_calc( + th.freq, + th.aoa, + radius_center, + radius_internal, + radius_outter, + freq_low, + freq_high) - bip.size / 2 -## Обработчик сигнала обновления цели +## Обработчик сигнала обновления цели. func on_threat_update(th): - var bip = get_node('%d' % th.id) - map_threat_to_bip(th, bip) + var bip = get_node('%d' % th.id) + map_threat_to_bip(th, bip) -## Обработчик выбора помехи для цели +## Обработчик выбора помехи для цели. func on_interfer_new(ecm): - if interfer.thrs.has(ecm.ispp): - if interfer.thrs.has(ecm.ispp): - var th_id = interfer.thrs[ecm.ispp] - var bip = get_node('%d' % th_id) - if ecm.krp != interfer.INTERFER_OFF: - bip.set_track_visible(true) - if ecm.svk == 1: - bip.set_ecm_visible(true) - else: bip.set_ecm_visible(false) - else: - bip.set_ecm_visible(false) - bip.set_track_visible(false) - set_fs_color(ecm) + if interfer.thrs.has(ecm.ispp): + if interfer.thrs.has(ecm.ispp): + var th_id = interfer.thrs[ecm.ispp] + var bip = get_node('%d' % th_id) + if ecm.krp != interfer.INTERFER_OFF: + bip.set_track_visible(true) + if ecm.svk == 1: + bip.set_ecm_visible(true) + else: bip.set_ecm_visible(false) + else: + bip.set_ecm_visible(false) + bip.set_track_visible(false) + set_fs_color(ecm) -## Обработчик выключения всех помех +## Обработчик выключения всех помех. func on_interfer_off_all(): - var bips: Array = get_children().filter(func(item): return item.name.is_valid_int()) - for bip in bips: - bip.set_ecm_visible(false) - bip.set_track_visible(false) - fs_active.clear() - update_fs_colors() + var bips: Array = get_children().filter(func(item): return item.name.is_valid_int()) + for bip in bips: + bip.set_ecm_visible(false) + bip.set_track_visible(false) + fs_active.clear() + update_fs_colors() -## Обработчик сигнала обнаружения цели +## Обработчик сигнала обнаружения цели. func on_threat_new(th) -> void: - var bip = Bip.instantiate() - bip.name = '%d' % th.id - map_threat_to_bip(th, bip) - add_child(bip) + var bip = Bip.instantiate() + bip.name = '%d' % th.id + map_threat_to_bip(th, bip) + add_child(bip) -## Обработчик сигнала потери цели +## Обработчик сигнала потери цели. func on_threat_lost(th) -> void: - var bip = get_node('%d' % th.id) - remove_child(bip) - bip.free() + var bip = get_node('%d' % th.id) + remove_child(bip) + bip.free() -## обработчик нажатия на цель на экране +## обработчик нажатия на цель на экране. func on_bip_pressed(th_id, _ecm_btns): - if btn_select == BtnSel.THREAT: - signaller.emit_signal('rto_threat_sel', th_id) + if btn_select == BtnSel.THREAT: + signaller.emit_signal('rto_threat_sel', th_id) -## Обработчик выбора РТО цели +## Обработчик выбора РТО цели. func on_rto_threat_sel(th_id, ecm_btns): - var bips: Array = get_children().filter(func(item): return item.name.is_valid_int()) - for item in bips: - if th_id == item.name.to_int(): - item.set_sel_visible(true) - unsel_fs_all(false) - else: - item.set_sel_visible(false) - for btn in ecm_btns: - set_btns_disabled(false, btn) + var bips: Array = get_children().filter(func(item): return item.name.is_valid_int()) + for item in bips: + if th_id == item.name.to_int(): + item.set_sel_visible(true) + unsel_fs_all(false) + else: + item.set_sel_visible(false) + for btn in ecm_btns: + set_btns_disabled(false, btn) -## Обработчик снятия выбора РТО цели +## Обработчик снятия выбора РТО цели. func on_rto_threat_unsel(th_id, _ecm_btns): - var bips: Array = get_children().filter(func(item): return item.name.is_valid_int()) - for item in bips: - if th_id == item.name.to_int(): - item.set_sel_visible(false) - break - set_ecm_btns_state(true) + var bips: Array = get_children().filter(func(item): return item.name.is_valid_int()) + for item in bips: + if th_id == item.name.to_int(): + item.set_sel_visible(false) + break + set_ecm_btns_state(true) -## Обработчик нажатия кнопки "Строб". Управляет отображением строба +## Обработчик нажатия кнопки "Строб". Управляет отображением строба. func on_button_strobe_pressed() -> void: - drag_fsm = DragFSM.IDLE if $btns_select/btn_strob.is_pressed() else DragFSM.OFF - $canvas.set_strob_visible(drag_fsm == DragFSM.IDLE) + drag_fsm = DragFSM.IDLE if $btns_select/btn_strob.is_pressed() else DragFSM.OFF + $canvas.set_strob_visible(drag_fsm == DragFSM.IDLE) -## Поведение радиокнопок в группе. Управляет состоянием кнопок выбора целей +## Поведение радиокнопок в группе. Управляет состоянием кнопок выбора целей. func on_button_select(btn_this, btn_others) -> void: - for btn_other in btn_others: btn_other.set_pressed(btn_this == btn_other) - btn_select = btn_this.get_meta('state') - on_button_strobe_pressed() - signaller.emit_signal('rto_threat_unsel_all') - unsel_fs_all(true) - set_ecm_btns_state(true) + for btn_other in btn_others: btn_other.set_pressed(btn_this == btn_other) + btn_select = btn_this.get_meta('state') + on_button_strobe_pressed() + signaller.emit_signal('rto_threat_unsel_all') + unsel_fs_all(true) + set_ecm_btns_state(true) -## Поведение радиокнопок в группе. Управляет состоянием кнопок в радиогруппе +## Поведение радиокнопок в группе. Управляет состоянием кнопок в радиогруппе. func on_button_pressed(btn_this, btn_others) -> void: - var fs_arr: Array = [] - for btn_other in btn_others: btn_other.set_pressed(btn_this == btn_other) - for key in fs_selected.keys(): - fs_arr.append(get_fs_param(key)) - signaller.emit_signal('interfer_init', btn_this, fs_arr) + var fs_arr: Array = [] + for btn_other in btn_others: btn_other.set_pressed(btn_this == btn_other) + for key in fs_selected.keys(): + fs_arr.append(get_fs_param(key)) + signaller.emit_signal('interfer_init', btn_this, fs_arr) -## Управляет состоянием кнопок в радиогруппе при выборе помехи +## Управляет состоянием кнопок в радиогруппе при выборе помехи. func on_interfer_selected(ecm, btns) -> void: - var ecm_name: String - if ecm != null: - ecm_name = ecm.krp - else: - ecm_name = interfer.INTERFER_OFF - for btn in btns: - set_btns_disabled(false, btn) - btn.get_meta('rfi_name') - if ecm_name == btn.get_meta('rfi_name'): - btn.set_pressed(true) - else: - btn.set_pressed(false) + var ecm_name: String + if ecm != null: + ecm_name = ecm.krp + else: + ecm_name = interfer.INTERFER_OFF + for btn in btns: + set_btns_disabled(false, btn) + btn.get_meta('rfi_name') + if ecm_name == btn.get_meta('rfi_name'): + btn.set_pressed(true) + else: + btn.set_pressed(false) -## Возвращает ширину строба по направлению +## Возвращает ширину строба по направлению. func get_strob_width(pos: Vector2, prev: float) -> float: - var strob_dir_width: float = prev + (drag_pos_begin.y - pos.y) * drag_scale_width - strob_dir_width = tools.clip_value(strob_dir_width, strob_min_width, strob_max_width) - return strob_dir_width + var strob_dir_width: float = prev + (drag_pos_begin.y - pos.y) * drag_scale_width + strob_dir_width = tools.clip_value(strob_dir_width, strob_min_width, strob_max_width) + return strob_dir_width -## Возвращает ширину строба по частоте +## Возвращает ширину строба по частоте. func get_strob_band(pos: Vector2, prev: float) -> float: - var strob_dir_band: float = prev - (drag_pos_begin.y - pos.y) * drag_scale_band - strob_dir_band = tools.clip_value(strob_dir_band, strob_min_band, strob_max_band) - return strob_dir_band + var strob_dir_band: float = prev - (drag_pos_begin.y - pos.y) * drag_scale_band + strob_dir_band = tools.clip_value(strob_dir_band, strob_min_band, strob_max_band) + return strob_dir_band -## Устанавливает центр строба по частоте и направлению +## Устанавливает центр строба по частоте и направлению. func set_strob(pos: Vector2) -> void: - var radp: Vector2 = tools.map_dec_to_rad(radius_center, pos) - if radp.y < $canvas.get_radius_outter(): - $canvas.set_strob_dir(radp.x) - $canvas.set_strob_radius(radp.y) + var radp: Vector2 = tools.map_dec_to_rad(radius_center, pos) + if radp.y < $canvas.get_radius_outter(): + $canvas.set_strob_dir(radp.x) + $canvas.set_strob_radius(radp.y) -## Обработка движения мыши для управления стробом. Вызывается во время движения мыши +## Обработка движения мыши для управления стробом. Вызывается во время движения мыши. func _on_drag_continue(event) -> void: - if drag_button == MOUSE_BUTTON_RIGHT: - if shift_pressed: - var strob_width: float = get_strob_width(event.position, strob_width_begin) - $canvas.set_strob_width(strob_width) - else: - var strob_band: float = get_strob_band(event.position, strob_band_begin) - $canvas.set_strob_band(strob_band) - elif drag_button == MOUSE_BUTTON_LEFT: - set_strob(event.position) + if drag_button == MOUSE_BUTTON_RIGHT: + if shift_pressed: + var strob_width: float = get_strob_width(event.position, strob_width_begin) + $canvas.set_strob_width(strob_width) + else: + var strob_band: float = get_strob_band(event.position, strob_band_begin) + $canvas.set_strob_band(strob_band) + elif drag_button == MOUSE_BUTTON_LEFT: + set_strob(event.position) -## Обработка движения мыши для управления стробом. Вызывается при нажатии мыши +## Обработка движения мыши для управления стробом. Вызывается при нажатии мыши. func _on_drag_begin(event) -> void: - drag_pos_begin = event.position - if drag_button == MOUSE_BUTTON_LEFT: - set_strob(event.position) - elif drag_button == MOUSE_BUTTON_RIGHT: - strob_width_begin = $canvas.get_strob_width() - strob_band_begin = $canvas.get_strob_band() + drag_pos_begin = event.position + if drag_button == MOUSE_BUTTON_LEFT: + set_strob(event.position) + elif drag_button == MOUSE_BUTTON_RIGHT: + strob_width_begin = $canvas.get_strob_width() + strob_band_begin = $canvas.get_strob_band() -## Обработчик событий ввода. Вызывается godot +## Обработчик событий ввода. Вызывается godot. func _input(event) -> void: - if not is_visible_in_tree(): return - if event is InputEventMouseMotion: - if drag_fsm == DragFSM.DRAG: - emit_signal('drag_continue', event) - elif event is InputEventMouseButton: - drag_button = event.button_index - if event.pressed: - if drag_fsm == DragFSM.IDLE: - drag_fsm = DragFSM.DRAG - emit_signal('drag_begin', event) - else: - if btn_select == BtnSel.FS: - sel_fs_sector(event) - drag_fsm = DragFSM.IDLE - emit_signal('drag_end', event) + if not is_visible_in_tree(): return + if event is InputEventMouseMotion: + if drag_fsm == DragFSM.DRAG: + emit_signal('drag_continue', event) + elif event is InputEventMouseButton: + drag_button = event.button_index + if event.pressed: + if drag_fsm == DragFSM.IDLE: + drag_fsm = DragFSM.DRAG + emit_signal('drag_begin', event) + else: + if btn_select == BtnSel.FS: + sel_fs_sector(event) + drag_fsm = DragFSM.IDLE + emit_signal('drag_end', event) - elif event is InputEventKey: - if event.physical_keycode == KEY_SHIFT: - shift_pressed = event.pressed + elif event is InputEventKey: + if event.physical_keycode == KEY_SHIFT: + shift_pressed = event.pressed -## Вызывается по нажатию кнопки "Детализация" +## Вызывается по нажатию кнопки "Детализация". func _on_btn_scale_pressed() -> void: - var zoom = $btn_zoom.get_meta('zoom') - zoom = zoom + 1 if zoom < max_zoom else min_zoom - signaller.emit_signal('zoom_changed', zoom) + var zoom = $btn_zoom.get_meta('zoom') + zoom = zoom + 1 if zoom < max_zoom else min_zoom + signaller.emit_signal('zoom_changed', zoom) -## Вызывается по завершению изменения детализации карты +## Вызывается по завершению изменения детализации карты. func _on_zoom_complete(zoom: int) -> void: - var prefix = $btn_zoom.get_meta('prefix') - $btn_zoom/lbl.text = '%s %d' % [prefix, zoom] - $btn_zoom.set_meta('zoom', zoom) + var prefix = $btn_zoom.get_meta('prefix') + $btn_zoom/lbl.text = '%s %d' % [prefix, zoom] + $btn_zoom.set_meta('zoom', zoom) -# Обработчик нажатия по сектору формирователя сигналов +## Обработчик нажатия по сектору формирователя сигналов. func sel_fs_sector(event): - if (event.button_index == MOUSE_BUTTON_LEFT) or (event.button_index == MOUSE_BUTTON_RIGHT): - var center: Vector2 = $tilemap.position - var center_offset: Vector2 = ProjectSettings.get_setting('application/interfer/center_offset', Vector2i(0, 25)) - var angle: float = event.position.angle_to_point(center + center_offset) * 180/PI - 45.0 - if angle < 0: - angle += 360 - var distance: float = event.position.distance_to(center + center_offset) - var sector: int - var band: int - var has_band: bool = false - for sec in fs_sectors.values(): - if angle >= sec[0] and angle <= sec[1]: - sector = fs_sectors.find_key(sec) - for diap in fs_band.values(): - if distance >= diap[0] and distance <= diap[1]: - band = fs_band.find_key(diap) - has_band = true - if not has_band: - return - var fs_index = band * 4 + sector - if event.button_index == MOUSE_BUTTON_LEFT: - fs_selected[fs_index] = null - else: - fs_selected.erase(fs_index) - update_fs_colors() - signaller.emit_signal('rto_threat_unsel_all') - var btn_state = len(fs_selected) == 0 - set_ecm_btns_state(btn_state) + if (event.button_index == MOUSE_BUTTON_LEFT) or (event.button_index == MOUSE_BUTTON_RIGHT): + var center: Vector2 = $tilemap.position + var center_offset: Vector2 = ProjectSettings.get_setting('application/interfer/center_offset', Vector2i(0, 25)) + var angle: float = event.position.angle_to_point(center + center_offset) * 180/PI - 45.0 + if angle < 0: + angle += 360 + var distance: float = event.position.distance_to(center + center_offset) + var sector: int + var band: int + var has_band: bool = false + for sec in fs_sectors.values(): + if angle >= sec[0] and angle <= sec[1]: + sector = fs_sectors.find_key(sec) + for diap in fs_band.values(): + if distance >= diap[0] and distance <= diap[1]: + band = fs_band.find_key(diap) + has_band = true + if not has_band: + return + var fs_index = band * 4 + sector + if event.button_index == MOUSE_BUTTON_LEFT: + fs_selected[fs_index] = null + else: + fs_selected.erase(fs_index) + update_fs_colors() + signaller.emit_signal('rto_threat_unsel_all') + var btn_state = len(fs_selected) == 0 + set_ecm_btns_state(btn_state) func set_fs_color(ecm): - var fs_index = get_fs_index(ecm) - if ecm.krp != interfer.INTERFER_OFF: - fs_active[fs_index] = null - else: - fs_active.erase(fs_index) - update_fs_colors() + var fs_index = get_fs_index(ecm) + if ecm.krp != interfer.INTERFER_OFF: + fs_active[fs_index] = null + else: + fs_active.erase(fs_index) + update_fs_colors() func update_fs_colors(): - var fs_colors = $canvas.fs_default_colors.duplicate() - var color_ecm: Vector3 = ProjectSettings.get_setting('application/interfer/interfer_color', Vector3(0.58, 0.47, 0.51)) - var color_select: Vector3 = ProjectSettings.get_setting('application/interfer/select_color', Vector3(1.0, 1.0, 1.0)) - for key in fs_active.keys(): - fs_colors[key] = color_ecm - for key in fs_selected.keys(): - fs_colors[key] = color_select - $canvas.set_band_colors(fs_colors) + var fs_colors = $canvas.fs_default_colors.duplicate() + var color_ecm: Vector3 = ProjectSettings.get_setting('application/interfer/interfer_color', Vector3(0.58, 0.47, 0.51)) + var color_select: Vector3 = ProjectSettings.get_setting('application/interfer/select_color', Vector3(1.0, 1.0, 1.0)) + for key in fs_active.keys(): + fs_colors[key] = color_ecm + for key in fs_selected.keys(): + fs_colors[key] = color_select + $canvas.set_band_colors(fs_colors) func unsel_fs_all(need_btns_off: bool): - fs_selected.clear() - update_fs_colors() - if need_btns_off: - set_ecm_btns_state(true) + fs_selected.clear() + update_fs_colors() + if need_btns_off: + set_ecm_btns_state(true) func set_ecm_btns_state(value: bool): - var btns: Array = get_tree().get_nodes_in_group('группа-режим-помехи') - for btn in btns: - btn.set_pressed(false) - set_btns_disabled(value, btn) + var btns: Array = get_tree().get_nodes_in_group('группа-режим-помехи') + for btn in btns: + btn.set_pressed(false) + set_btns_disabled(value, btn) func get_fs_param(index: int) -> Array: - var freq = (fs_freq[index / 4][0] + fs_freq[index / 4][1])/2 - var sector = (fs_sectors[index % 4][0] + fs_sectors[index % 4][1])/2 - 45.0 - if sector < 0: - sector += 360.0 - var width = fs_freq[index / 4][1] - fs_freq[index / 4][0] - return [sector, freq, width, index] + var freq = (fs_freq[index / 4][0] + fs_freq[index / 4][1])/2 + var sector = (fs_sectors[index % 4][0] + fs_sectors[index % 4][1])/2 - 45.0 + if sector < 0: + sector += 360.0 + var width = fs_freq[index / 4][1] - fs_freq[index / 4][0] + return [sector, freq, width, index] func on_fs_selected(ecm): - var fs_index = get_fs_index(ecm) - fs_selected.clear() - fs_selected[fs_index] = null - update_fs_colors() + var fs_index = get_fs_index(ecm) + fs_selected.clear() + fs_selected[fs_index] = null + update_fs_colors() func get_fs_index(ecm): - var sector: int - var band: int - var angle: float = ecm.kni + 45.0 - if angle > 360.0: - angle -= 360.0 - for sec in fs_sectors.values(): - if float(angle) >= sec[0] and float(angle) <= sec[1]: - sector = fs_sectors.find_key(sec) - for diap in fs_freq.values(): - if ecm.params.has('freq'): - if ecm.params['freq'] >= diap[0] and ecm.params['freq'] <= diap[1]: - band = fs_freq.find_key(diap) - var fs_index = band * 4 + sector - return fs_index + var sector: int + var band: int + var angle: float = ecm.kni + 45.0 + if angle > 360.0: + angle -= 360.0 + for sec in fs_sectors.values(): + if float(angle) >= sec[0] and float(angle) <= sec[1]: + sector = fs_sectors.find_key(sec) + for diap in fs_freq.values(): + if ecm.params.has('freq'): + if ecm.params['freq'] >= diap[0] and ecm.params['freq'] <= diap[1]: + band = fs_freq.find_key(diap) + var fs_index = band * 4 + sector + return fs_index func set_btns_disabled(value: bool, btn): - var external_cu = ProjectSettings.get_setting('application/config/external_cu', false) - if external_cu: - btn.set_pressed(false) - btn.set_disabled(true) - else: - btn.set_disabled(value) + var external_cu = ProjectSettings.get_setting('application/config/external_cu', false) + if external_cu: + btn.set_pressed(false) + btn.set_disabled(true) + else: + btn.set_disabled(value) diff --git a/scenes/работа/работа.tscn b/scenes/работа/работа.tscn index b7359562..8c7d6a0f 100644 --- a/scenes/работа/работа.tscn +++ b/scenes/работа/работа.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=14 format=3 uid="uid://b276iygic5itk"] +[gd_scene load_steps=15 format=3 uid="uid://b276iygic5itk"] [ext_resource type="Script" path="res://scenes/работа/работа.gd" id="1_niok4"] [ext_resource type="PackedScene" uid="uid://nl1vklubr5kr" path="res://scenes/bip/bip.tscn" id="2_br3s6"] @@ -18,6 +18,8 @@ resource_local_to_scene = true shader = ExtResource("11_ekapd") shader_parameter/radius = 0.39 +[sub_resource type="ImageTexture" id="ImageTexture_cnmvt"] + [node name="Работа" type="Panel"] script = ExtResource("1_niok4") Bip = ExtResource("2_br3s6") @@ -120,6 +122,33 @@ size_flags_vertical = 3 text = "Подавление" toggle_mode = true +[node name="bt_navig_u" type="TextureButton" parent="."] +layout_mode = 0 +offset_left = 530.0 +offset_top = 428.0 +offset_right = 570.0 +offset_bottom = 468.0 + +[node name="bt_navig_l" type="TextureButton" parent="."] +layout_mode = 0 +offset_left = 410.0 +offset_top = 532.0 +offset_right = 460.0 +offset_bottom = 581.0 +texture_normal = SubResource("ImageTexture_cnmvt") + +[node name="bt_navig_r" type="TextureButton" parent="."] +layout_mode = 0 +offset_left = 651.0 +offset_top = 540.0 +offset_right = 691.0 +offset_bottom = 580.0 + +[node name="bt_navig_d" type="TextureButton" parent="."] +layout_mode = 0 +offset_right = 40.0 +offset_bottom = 40.0 + [node name="btn_zoom" parent="." instance=ExtResource("9_oey3x")] layout_mode = 0 offset_left = 12.0 diff --git a/scenes/эмс/кнопка-эмс.gd b/scenes/эмс/кнопка-эмс.gd index 37bcaf7d..e8aa9c4a 100644 --- a/scenes/эмс/кнопка-эмс.gd +++ b/scenes/эмс/кнопка-эмс.gd @@ -4,28 +4,28 @@ extends PanelContainer @export var texture_state0: Texture2D = preload('res://data/кнопка-эмс-1.png'): - set(val): $state.set_texture(val) + set(val): $state.set_texture(val) @export var texture_state1: Texture2D = preload('res://data/кнопка-эмс-0.png'): - set(val): $state.set_texture(val) + set(val): $state.set_texture(val) @export var text: String = '': - set(val): $button.set_text(val) - get: return $button.get_text() + set(val): $button.set_text(val) + get: return $button.get_text() @export var pressed: bool = false: - set(val): $button.set_pressed(val) - get: return $button.is_pressed() + set(val): $button.set_pressed(val) + get: return $button.is_pressed() @export var toggle_mode: bool = false: - set(val): - if is_inside_tree(): - $button.set_toggle_mode(val) - get: return $button.is_toggle_mode() + set(val): + if is_inside_tree(): + $button.set_toggle_mode(val) + get: return $button.is_toggle_mode() func set_text (val: String): $button.set_text(val) @@ -33,8 +33,8 @@ func set_pressed (val: bool): $button.set_pressed(val) func set_toggle_mode (val: bool): - if is_inside_tree(): - $button.set_toggle_mode(val) + if is_inside_tree(): + $button.set_toggle_mode(val) func get_text(): return $button.get_text() @@ -43,13 +43,13 @@ func is_toggle_mode(): return $button.is_toggle_mode() func _on_button_toggled(val: bool): - if val: $state.set_texture(texture_state1) - else: $state.set_texture(texture_state0) + if val: $state.set_texture(texture_state1) + else: $state.set_texture(texture_state0) func button_connect(button_signal: String, proc: Callable): - $button.connect(button_signal, proc) + $button.connect(button_signal, proc) func _on_button_mouse_entered(): - emit_signal("mouse_entered") + emit_signal("mouse_entered") diff --git a/scenes/эмс/эмс.gd b/scenes/эмс/эмс.gd index 054309bb..6b948e9b 100644 --- a/scenes/эмс/эмс.gd +++ b/scenes/эмс/эмс.gd @@ -1,6 +1,6 @@ -extends Panel +class_name ems extends Panel -# Предзагрузка ячеек таблиц +## Предзагрузка ячеек таблиц. const TableHeader = preload('res://table/header.tscn') const CellYemsButton = preload('res://scenes/эмс/кнопка-эмс.tscn') const CellLineEdit = preload('res://table/ячейка-2.tscn') @@ -10,7 +10,7 @@ const CellLEEmpty = preload('res://scenes/эмс/line-edit-empty.tscn') const CellFront = preload('res://scenes/cell/cell-front.tscn') const CellFrontRear = preload('res://scenes/cell/cell-front-rear.tscn') -# Режимы работы выходов +## Режимы работы выходов. const OUT_MODE: Array = ['Работа', 'Контроль', '1', '0'] # const COLUMN_OPTS_BTN: Array = [5] const TABLE_ENTRY_COLUMNS: Array = [2,3] @@ -18,339 +18,347 @@ const OUT_SET_ROW: Array = [CellLineEdit, CellLEEmpty, CellLEEmpty, CellLEEmpty, const IN_SET_ROW: Array = [CellLineEdit, CellLEEmpty, CellLEEmpty, CellLEEmpty, CellLEEmpty] -var g_boards: Dictionary # Словарь плат ЭМС -var posx: int = 125 # Позиция элементов по x -var posy: int = 70 # Позиция элементов по y +var g_boards: Dictionary ## Словарь плат ЭМС. +var posx: int = 125 ## Позиция элементов по x. +var posy: int = 70 ## Позиция элементов по y. func _ready() -> void: - call_deferred('init_boards') + call_deferred('init_boards') -## Инициализация основных элементов экрана ЭМС +## Инициализация основных элементов экрана ЭМС. func init_boards(): - # Выставление позиций элементов ЭМС на экране - $scrollin.set_position(Vector2(posx,posy)) - $scrollout.set_position(Vector2(posx+650,posy)) - $scrl_in_set.set_position(Vector2(posx,posy + 700)) - $scrl_out_set.set_position(Vector2(posx+650,posy + 700)) - $lbl_online.set_position(Vector2(posx,posy-60)) - $btn_flash_read.set_position(Vector2(posx,posy + 900)) - $btn_flash_write.set_position(Vector2(posx,posy+ 935)) - $btn_connect.set_position(Vector2(posx+590,posy-25)) - $btn_disconnect.set_position(Vector2(posx+587,posy + 675)) + # Выставление позиций элементов ЭМС на экране + $scrollin.set_position(Vector2(posx,posy)) + $scrollout.set_position(Vector2(posx+650,posy)) + $scrl_in_set.set_position(Vector2(posx,posy + 700)) + $scrl_out_set.set_position(Vector2(posx+650,posy + 700)) + $lbl_online.set_position(Vector2(posx,posy-60)) + $btn_flash_read.set_position(Vector2(posx,posy + 900)) + $btn_flash_write.set_position(Vector2(posx,posy+ 935)) + $btn_connect.set_position(Vector2(posx+590,posy-25)) + $btn_disconnect.set_position(Vector2(posx+587,posy + 675)) - g_boards['КЭМС'] = yemsboards.EmsBoard.new('КЭМС', 0x130, yemsconsts.INPUT_ROWS, yemsconsts.OUTPUT_ROWS) - g_boards['П5-28'] = yemsboards.EmsBoard.new('П5-28', 0x140, yemsconsts.INPUT_ROWS1, yemsconsts.OUTPUT_ROWS1) - init_tbl_in() - $scrollin/tbl_in.pin_header($scrollin, self) - init_tbl_out() - $scrollout/tbl_out.pin_header($scrollout, self) - $scrl_in_set/tbl_in_set.pin_header($scrl_in_set, self) - $scrl_out_set/tbl_out_set.pin_header($scrl_out_set, self) - init_tbl_files() - $scrl_files/tbl_files.pin_header($scrl_files, self) - $btn_connect.connect('pressed', Callable(self, 'on_btn_connect')) - $btn_disconnect.connect('pressed', Callable(self, 'on_btn_disconnect')) - $btn_flash_read.button_connect('pressed', Callable(self, 'on_btn_flash_read').bind($btn_flash_read)) - $btn_flash_write.button_connect('pressed', Callable(self, 'on_btn_flash_write').bind($btn_flash_write)) + g_boards['КЭМС'] = yemsboards.EmsBoard.new('КЭМС', 0x130, yemsconsts.INPUT_ROWS, yemsconsts.OUTPUT_ROWS) + g_boards['П5-28'] = yemsboards.EmsBoard.new('П5-28', 0x140, yemsconsts.INPUT_ROWS1, yemsconsts.OUTPUT_ROWS1) + init_tbl_in() + $scrollin/tbl_in.pin_header($scrollin, self) + init_tbl_out() + $scrollout/tbl_out.pin_header($scrollout, self) + $scrl_in_set/tbl_in_set.pin_header($scrl_in_set, self) + $scrl_out_set/tbl_out_set.pin_header($scrl_out_set, self) + init_tbl_files() + $scrl_files/tbl_files.pin_header($scrl_files, self) + $btn_connect.connect('pressed', Callable(self, 'on_btn_connect')) + $btn_disconnect.connect('pressed', Callable(self, 'on_btn_disconnect')) + $btn_flash_read.button_connect('pressed', Callable(self, 'on_btn_flash_read').bind($btn_flash_read)) + $btn_flash_write.button_connect('pressed', Callable(self, 'on_btn_flash_write').bind($btn_flash_write)) ## Обработчик нажатия кнопки disconnect. func on_btn_disconnect(): - var selected_rows_in: Array = [] - var selected_rows_out: Array = [] - $scrl_in_set/tbl_in_set.get_selected_rows(selected_rows_in) - $scrl_out_set/tbl_out_set.get_selected_rows(selected_rows_out) - if len(selected_rows_in): - on_disconnect($scrl_in_set/tbl_in_set, selected_rows_in, 'вход') - elif len(selected_rows_out): - on_disconnect($scrl_out_set/tbl_out_set, selected_rows_out, 'выход') + var selected_rows_in: Array = [] + var selected_rows_out: Array = [] + $scrl_in_set/tbl_in_set.get_selected_rows(selected_rows_in) + $scrl_out_set/tbl_out_set.get_selected_rows(selected_rows_out) + if len(selected_rows_in): + on_disconnect($scrl_in_set/tbl_in_set, selected_rows_in, 'вход') + elif len(selected_rows_out): + on_disconnect($scrl_out_set/tbl_out_set, selected_rows_out, 'выход') -## Разрывает связь входа и выхода, [param tbl] - таблица, [param selected_rows] - выбранные строки -## [param type_dev] - тип, 'вход' или 'выход' +## Разрывает связь входа и выхода, [param tbl] - таблица, [param selected_rows] - выбранные строки. +## [param type_dev] - тип, 'вход' или 'выход'. func on_disconnect(tbl, selected_rows, type_dev: String): - var dev = tbl.get_node_user_data(0, 0) - var user_data: Array = tbl.get_node_user_data(0, selected_rows[0]) - var con_dev = user_data[0] - var connector: String = con_dev.connector - dev.connect_devs.erase(con_dev.connector) - con_dev.connect_devs.erase(dev.connector) - if type_dev == 'вход': - set_in_dev(dev) - set_out_dev(con_dev) - elif type_dev == 'выход': - set_in_dev(con_dev) - set_out_dev(dev) + var dev = tbl.get_node_user_data(0, 0) + var user_data: Array = tbl.get_node_user_data(0, selected_rows[0]) + var con_dev = user_data[0] + var connector: String = con_dev.connector + dev.connect_devs.erase(con_dev.connector) + con_dev.connect_devs.erase(dev.connector) + if type_dev == 'вход': + set_in_dev(dev) + set_out_dev(con_dev) + elif type_dev == 'выход': + set_in_dev(con_dev) + set_out_dev(dev) -## Обработчик нажатия кнопки "Прочитать" +## Обработчик нажатия кнопки "Прочитать". func on_btn_flash_read(btn): - await get_tree().create_timer(0.1).timeout - btn.pressed = false + await get_tree().create_timer(0.1).timeout + btn.pressed = false -## Обработчик нажатия кнопки "Записать" +## Обработчик нажатия кнопки "Записать". func on_btn_flash_write(btn): - await get_tree().create_timer(0.1).timeout - btn.pressed = false + await get_tree().create_timer(0.1).timeout + btn.pressed = false -## Инициализация таблицы с файлами настроек ЭМС +## Инициализация таблицы с файлами настроек ЭМС. func init_tbl_files(): - $scrl_files/tbl_files.set_header([TableHeader, TableHeader, TableHeader]) - $scrl_files/tbl_files.set_header_text(['Имя файла', 'Дата изменения', 'Размер']) - $scrl_files/tbl_files.set_columns_min_size([200, 120, 70]) + $scrl_files/tbl_files.set_header([TableHeader, TableHeader, TableHeader]) + $scrl_files/tbl_files.set_header_text(['Имя файла', 'Дата изменения', 'Размер']) + $scrl_files/tbl_files.set_columns_min_size([200, 120, 70]) -## Инициализация таблиц входов +## Инициализация таблиц входов. func init_tbl_in(): - # Инициализация основной таблицы входов, в ней отображается информация о параметрах и состоянии - var header_type: Array = [TableHeader, TableHeader, CellFront, CellFrontRear, TableHeader] - var header_text: Array = ['Входы', 'Соединитель', 'мкс', 'мкс', 'Состояние'] - var row_type: Array = [CellLineEdit, CellLineEdit, CellLineEdit, CellLineEdit, CellState] - var yams_alligment_in: Array = [ HORIZONTAL_ALIGNMENT_LEFT, HORIZONTAL_ALIGNMENT_CENTER, HORIZONTAL_ALIGNMENT_CENTER, HORIZONTAL_ALIGNMENT_CENTER, HORIZONTAL_ALIGNMENT_CENTER] - var column_size: Array = [250, 100, 70, 70, 50] - $scrollin/tbl_in.set_header(header_type) - $scrollin/tbl_in.set_header_text(header_text) - for board in g_boards: - for item in g_boards[board].in_tbl: - $scrollin/tbl_in.add_row(row_type) - var row_index : int = $scrollin/tbl_in.get_rows_count() - 1 - var params : Array = item[1] - var in_dev: yemsdevs.InDev = yemsdevs.InDev.new(item[0], params, row_index) - in_dev.board = board - for i in range(len(row_type)): - if i < len(params): - var node = $scrollin/tbl_in.get_node2(i, row_index) - node.text = params[i] - node.editable = false - $scrollin/tbl_in.set_node_user_data(i, row_index, [in_dev, board]) - $scrollin/tbl_in.set_columns_alignments(yams_alligment_in) - $scrollin/tbl_in.set_columns_min_size(column_size) - $scrollin/tbl_in.connect('row_pressed', Callable(self, 'on_row_pressed_in')) - $scrollin/tbl_in.connect('selected_changed', Callable(self, 'on_selected_changed_in')) + # Инициализация основной таблицы входов, в ней отображается информация о параметрах и состоянии + var header_type: Array = [TableHeader, TableHeader, CellFront, CellFrontRear, TableHeader] + var header_text: Array = ['Входы', 'Соединитель', 'мкс', 'мкс', 'Состояние'] + var row_type: Array = [CellLineEdit, CellLineEdit, CellLineEdit, CellLineEdit, CellState] + var yams_alligment_in: Array = [ HORIZONTAL_ALIGNMENT_LEFT, HORIZONTAL_ALIGNMENT_CENTER, HORIZONTAL_ALIGNMENT_CENTER, HORIZONTAL_ALIGNMENT_CENTER, HORIZONTAL_ALIGNMENT_CENTER] + var column_size: Array = [250, 100, 70, 70, 50] + $scrollin/tbl_in.set_header(header_type) + $scrollin/tbl_in.set_header_text(header_text) + for board in g_boards: + for item in g_boards[board].in_tbl: + $scrollin/tbl_in.add_row(row_type) + var row_index : int = $scrollin/tbl_in.get_rows_count() - 1 + var params : Array = item[1] + var in_dev: yemsdevs.InDev = yemsdevs.InDev.new(item[0], params, row_index) + in_dev.board = board + for i in range(len(row_type)): + if i < len(params): + var node = $scrollin/tbl_in.get_node2(i, row_index) + node.text = params[i] + node.editable = false + $scrollin/tbl_in.set_node_user_data(i, row_index, [in_dev, board]) + $scrollin/tbl_in.set_columns_alignments(yams_alligment_in) + $scrollin/tbl_in.set_columns_min_size(column_size) + $scrollin/tbl_in.connect('row_pressed', Callable(self, 'on_row_pressed_in')) + $scrollin/tbl_in.connect('selected_changed', Callable(self, 'on_selected_changed_in')) - # Инициализация таблицы изменения параметров входа и списка выходов связанных с ним - header_text[0] = 'Вход' - $scrl_in_set/tbl_in_set.set_header(header_type) - $scrl_in_set/tbl_in_set.set_header_text(header_text) - $scrl_in_set/tbl_in_set.set_columns_alignments(yams_alligment_in) - $scrl_in_set/tbl_in_set.set_columns_min_size(column_size) - $scrl_in_set/tbl_in_set.connect('row_pressed', Callable(self, 'on_row_pressed_si')) - $scrl_in_set/tbl_in_set.connect('selected_changed', Callable(self, 'on_selected_changed_si')) + # Инициализация таблицы изменения параметров входа и списка выходов связанных с ним + header_text[0] = 'Вход' + $scrl_in_set/tbl_in_set.set_header(header_type) + $scrl_in_set/tbl_in_set.set_header_text(header_text) + $scrl_in_set/tbl_in_set.set_columns_alignments(yams_alligment_in) + $scrl_in_set/tbl_in_set.set_columns_min_size(column_size) + $scrl_in_set/tbl_in_set.connect('row_pressed', Callable(self, 'on_row_pressed_si')) + $scrl_in_set/tbl_in_set.connect('selected_changed', Callable(self, 'on_selected_changed_si')) -## Обработчик редактирования Entry в таблице параметров входов/выходов +## Обработчик редактирования Entry в таблице параметров входов/выходов. func on_edit_entry(text : String, entry): - print_debug(text, entry) + print_debug(text, entry) -## Обработчик выбора строки в таблице входов +## Обработчик выбора строки в таблице входов. func on_selected_changed_in(i_row: int): - var selecter_rows: Array = [] - $scrollin/tbl_in.get_selected_rows(selecter_rows) - var user_data = $scrollin/tbl_in.get_node_user_data(0, i_row) - var dev = user_data[0] - set_in_dev(dev) + var selecter_rows: Array = [] + $scrollin/tbl_in.get_selected_rows(selecter_rows) + var user_data = $scrollin/tbl_in.get_node_user_data(0, i_row) + var dev = user_data[0] + set_in_dev(dev) -## Обработчик нажатия на таблицу входов +## Обработчик нажатия на таблицу входов. func on_row_pressed_in(i_row: int): - on_row_pressed(i_row, $scrollin/tbl_in) - on_selected_changed_in(i_row) + on_row_pressed(i_row, $scrollin/tbl_in) + on_selected_changed_in(i_row) -## Выбор входного устройства, для отображения в таблице соединений входа и изменения параметров +## Выбор входного устройства, для отображения в таблице соединений входа и изменения параметров. func set_in_dev(dev): - var row_type: Array = [CellLineEdit, CellLineEdit, CellLineEdit, CellLineEdit, CellState] - set_dev($scrl_in_set/tbl_in_set, dev, row_type, IN_SET_ROW, 'Список соединённых выходов :', [250, 100, 70, 70, 70]) + var row_type: Array = [CellLineEdit, CellLineEdit, CellLineEdit, CellLineEdit, CellState] + set_dev($scrl_in_set/tbl_in_set, dev, row_type, IN_SET_ROW, 'Список соединённых выходов :', [250, 100, 70, 70, 70]) -## Обработчик выбора строки в таблице соединений входа и изменения параметров +## Обработчик выбора строки в таблице соединений входа и изменения параметров. func on_selected_changed_si(i_row: int): - if i_row < 2: - return - var out_user_data = $scrl_in_set/tbl_in_set.get_node_user_data(0, i_row) - var dev = out_user_data[0] - var connector = dev.connector - for i in range($scrollout/tbl_out.get_rows_count()): - var node = $scrollout/tbl_out.get_node2(1, i) - if connector == node.text: - on_row_pressed_out(i) + if i_row < 2: + return + var out_user_data = $scrl_in_set/tbl_in_set.get_node_user_data(0, i_row) + var dev = out_user_data[0] + var connector = dev.connector + for i in range($scrollout/tbl_out.get_rows_count()): + var node = $scrollout/tbl_out.get_node2(1, i) + if connector == node.text: + on_row_pressed_out(i) -## Обработчик нажатия на таблицу соединений входа и изменения параметров +## Обработчик нажатия на таблицу соединений входа и изменения параметров. func on_row_pressed_si(i_row: int): - if i_row < 2: - return - on_row_pressed(i_row, $scrl_in_set/tbl_in_set) - on_selected_changed_si(i_row) + if i_row < 2: + return + on_row_pressed(i_row, $scrl_in_set/tbl_in_set) + on_selected_changed_si(i_row) -## Инициализация таблиц выходов +## Инициализация таблиц выходов. func init_tbl_out(): - # Инициализация основной таблицы выходов, в ней отображается информация о параметрах и состоянии - var header_text = ['Выходы', 'Соединитель', 'мкс', 'мкс', 'Состояние', 'Режим'] - var yams_alligment_out: Array = [ HORIZONTAL_ALIGNMENT_LEFT, HORIZONTAL_ALIGNMENT_CENTER, HORIZONTAL_ALIGNMENT_CENTER, HORIZONTAL_ALIGNMENT_CENTER, HORIZONTAL_ALIGNMENT_CENTER, HORIZONTAL_ALIGNMENT_CENTER] - $scrollout/tbl_out.set_header([TableHeader, TableHeader, CellFront, CellFrontRear, TableHeader, TableHeader]) - $scrollout/tbl_out.set_header_text(header_text) - for board in g_boards: - for item in g_boards[board].out_tbl: - $scrollout/tbl_out.add_row([CellLineEdit, CellLineEdit, CellLineEdit, CellLineEdit, CellState, CellLineEdit]) - var row_index : int = $scrollout/tbl_out.get_rows_count() - 1 - var params : Array = item[1] - var out_dev = yemsdevs.OutDev.new(item[0], params, row_index) - out_dev.board = board - for i in range(len(header_text)): - var cell = $scrollout/tbl_out.get_node2(i, row_index) - if i < len(params): - cell.text = params[i] - cell.editable = false - $scrollout/tbl_out.set_node_user_data(i, row_index, [out_dev, board]) - if i in COLUMN_OPTS_BTN: - cell.text = out_dev.mode - $scrollout/tbl_out.set_columns_min_size([250, 100, 70, 70, 70, 100]) - $scrollout/tbl_out.set_columns_alignments(yams_alligment_out) - $scrollout/tbl_out.connect('row_pressed', Callable(self, 'on_row_pressed_out')) - $scrollout/tbl_out.connect('selected_changed', Callable(self, 'on_selected_changed_out')) + # Инициализация основной таблицы выходов, в ней отображается информация о параметрах и состоянии + var header_text = ['Выходы', 'Соединитель', 'мкс', 'мкс', 'Состояние', 'Режим'] + var yams_alligment_out: Array = [ HORIZONTAL_ALIGNMENT_LEFT, HORIZONTAL_ALIGNMENT_CENTER, HORIZONTAL_ALIGNMENT_CENTER, HORIZONTAL_ALIGNMENT_CENTER, HORIZONTAL_ALIGNMENT_CENTER, HORIZONTAL_ALIGNMENT_CENTER] + $scrollout/tbl_out.set_header([TableHeader, TableHeader, CellFront, CellFrontRear, TableHeader, TableHeader]) + $scrollout/tbl_out.set_header_text(header_text) + for board in g_boards: + for item in g_boards[board].out_tbl: + $scrollout/tbl_out.add_row([CellLineEdit, CellLineEdit, CellLineEdit, CellLineEdit, CellState, CellLineEdit]) + var row_index : int = $scrollout/tbl_out.get_rows_count() - 1 + var params : Array = item[1] + var out_dev = yemsdevs.OutDev.new(item[0], params, row_index) + out_dev.board = board + for i in range(len(header_text)): + var cell = $scrollout/tbl_out.get_node2(i, row_index) + if i < len(params): + cell.text = params[i] + cell.editable = false + $scrollout/tbl_out.set_node_user_data(i, row_index, [out_dev, board]) + if i in COLUMN_OPTS_BTN: + cell.text = out_dev.mode + $scrollout/tbl_out.set_columns_min_size([250, 100, 70, 70, 70, 100]) + $scrollout/tbl_out.set_columns_alignments(yams_alligment_out) + $scrollout/tbl_out.connect('row_pressed', Callable(self, 'on_row_pressed_out')) + $scrollout/tbl_out.connect('selected_changed', Callable(self, 'on_selected_changed_out')) - # Инициализация таблицы изменения параметров выхода и списка входов связанных с ним - header_text[0] = 'Выход' - $scrl_out_set/tbl_out_set.set_header([TableHeader, TableHeader, CellFront, CellFrontRear, TableHeader, TableHeader]) - $scrl_out_set/tbl_out_set.set_header_text(header_text) - $scrl_out_set/tbl_out_set.set_columns_min_size([250, 100, 70, 70, 70, 100]) - $scrl_out_set/tbl_out_set.connect('row_pressed', Callable(self, 'on_row_pressed_so')) - $scrl_out_set/tbl_out_set.connect('selected_changed', Callable(self, 'on_selected_changed_so')) + # Инициализация таблицы изменения параметров выхода и списка входов связанных с ним + header_text[0] = 'Выход' + $scrl_out_set/tbl_out_set.set_header([TableHeader, TableHeader, CellFront, CellFrontRear, TableHeader, TableHeader]) + $scrl_out_set/tbl_out_set.set_header_text(header_text) + $scrl_out_set/tbl_out_set.set_columns_min_size([250, 100, 70, 70, 70, 100]) + $scrl_out_set/tbl_out_set.connect('row_pressed', Callable(self, 'on_row_pressed_so')) + $scrl_out_set/tbl_out_set.connect('selected_changed', Callable(self, 'on_selected_changed_so')) -## Обработчик выбора строки в таблице соединений выхода и изменения параметров +## Обработчик выбора строки в таблице соединений выхода и изменения параметров. func on_selected_changed_so(i_row: int): - if i_row < 2: - return - var in_user_data = $scrl_out_set/tbl_out_set.get_node_user_data(0, i_row) - var dev = in_user_data[0] - var connector = dev.connector - for i in range($scrollin/tbl_in.get_rows_count()): - var node = $scrollin/tbl_in.get_node2(1, i) - if connector == node.text: - on_row_pressed_in(i) + if i_row < 2: + return + var in_user_data = $scrl_out_set/tbl_out_set.get_node_user_data(0, i_row) + var dev = in_user_data[0] + var connector = dev.connector + for i in range($scrollin/tbl_in.get_rows_count()): + var node = $scrollin/tbl_in.get_node2(1, i) + if connector == node.text: + on_row_pressed_in(i) -## Обработчик нажатия на таблицу соединений выхода и изменения параметров +## Обработчик нажатия на таблицу соединений выхода и изменения параметров. func on_row_pressed_so(i_row: int): - if i_row < 2: - return - on_row_pressed(i_row, $scrl_out_set/tbl_out_set) - on_selected_changed_so(i_row) + if i_row < 2: + return + on_row_pressed(i_row, $scrl_out_set/tbl_out_set) + on_selected_changed_so(i_row) -## Обработчик выбора строки в таблице выходов +## Обработчик выбора строки в таблице выходов. func on_selected_changed_out(i_row: int): - var selecter_rows: Array = [] - $scrollout/tbl_out.get_selected_rows(selecter_rows) - var user_data = $scrollout/tbl_out.get_node_user_data(0, i_row) - var dev = user_data[0] - set_out_dev(dev) + var selecter_rows: Array = [] + $scrollout/tbl_out.get_selected_rows(selecter_rows) + var user_data = $scrollout/tbl_out.get_node_user_data(0, i_row) + var dev = user_data[0] + set_out_dev(dev) -## Обработчик выбора строки в таблице выходов +## Обработчик выбора строки в таблице выходов. func on_row_pressed_out(i_row: int): - on_row_pressed(i_row, $scrollout/tbl_out) - on_selected_changed_out(i_row) + on_row_pressed(i_row, $scrollout/tbl_out) + on_selected_changed_out(i_row) -## Выбор выходного устройства, для отображения в таблице соединений выхода и изменения параметров +## Выбор выходного устройства, для отображения в таблице соединений выхода и изменения параметров. func set_out_dev(dev): - var row_type: Array = [CellLineEdit, CellLineEdit, CellLineEdit, CellLineEdit, CellState, CellOptBtn] - set_dev($scrl_out_set/tbl_out_set, dev, row_type, OUT_SET_ROW, 'Список соединённых входов :', [250, 100, 70, 70, 70, 100]) - var mode = $scrl_out_set/tbl_out_set.get_node2(5, 0) - for opt in OUT_MODE: - mode.add_item(opt) - var mode_index: int = OUT_MODE.find(dev.mode) - if mode_index != -1: - mode.select(mode_index) + var row_type: Array = [CellLineEdit, CellLineEdit, CellLineEdit, CellLineEdit, CellState, CellOptBtn] + set_dev($scrl_out_set/tbl_out_set, dev, row_type, OUT_SET_ROW, 'Список соединённых входов :', [250, 100, 70, 70, 70, 100]) + var mode = $scrl_out_set/tbl_out_set.get_node2(5, 0) + for opt in OUT_MODE: + mode.add_item(opt) + var mode_index: int = OUT_MODE.find(dev.mode) + if mode_index != -1: + mode.select(mode_index) -## Выбор входного или выходного устройства [param tbl] - таблица, [param dev] - устройство, -## [param row_type] - массив с типами колонок, [param row_type_conn] - массив с пустыми LineEdit -## [param lbl_text] - текст отображаемый во 2 строке: пример: ('Список соединённых входов :') +## Выбор входного или выходного устройства.[br] +## [param tbl] - таблица,[br] +## [param dev] - устройство,[br] +## [param row_type] - массив с типами колонок,[br] +## [param row_type_conn] - массив с пустыми LineEdit,[br] +## [param lbl_text] - текст отображаемый во 2 строке: пример: ('Список соединённых входов :').[br] func set_dev(tbl, dev, row_type, row_type_conn, lbl_text, row_size): - tbl.clear_rows() - tbl.add_row(row_type) - tbl.set_columns_min_size(row_size) - var le_name: LineEdit = tbl.get_node2(0, 0) - le_name.editable = false - le_name.text = dev.dev_name - var connector: LineEdit = tbl.get_node2(1, 0) - connector.editable = false - connector.text = dev.connector - var delay: LineEdit = tbl.get_node2(2, 0) - delay.text = '%4.2f'%dev.delay - var offset: LineEdit = tbl.get_node2(3, 0) - offset.text = '%4.2f'%dev.offset - var state = tbl.get_node2(4, 0) - state.set_frame(dev.state) - tbl.add_row(row_type_conn) - var lbl: LineEdit = tbl.get_node2(0, 1) - lbl.text = lbl_text - for i in len(row_type_conn): - var le: LineEdit = tbl.get_node2(i, 1) - tbl.set_node_user_data(i, 0, dev) - le.editable = false + tbl.clear_rows() + tbl.add_row(row_type) + tbl.set_columns_min_size(row_size) + var le_name: LineEdit = tbl.get_node2(0, 0) + le_name.editable = false + le_name.text = dev.dev_name + var connector: LineEdit = tbl.get_node2(1, 0) + connector.editable = false + connector.text = dev.connector + var delay: LineEdit = tbl.get_node2(2, 0) + delay.text = '%4.2f'%dev.delay + var offset: LineEdit = tbl.get_node2(3, 0) + offset.text = '%4.2f'%dev.offset + var state = tbl.get_node2(4, 0) + state.set_frame(dev.state) + tbl.add_row(row_type_conn) + var lbl: LineEdit = tbl.get_node2(0, 1) + lbl.text = lbl_text + for i in len(row_type_conn): + var le: LineEdit = tbl.get_node2(i, 1) + tbl.set_node_user_data(i, 0, dev) + le.editable = false - for i in dev.connect_devs: - var dev_con = dev.connect_devs[i][0] - tbl.add_row(row_type_conn) - var node_index: Array = [0, tbl.get_rows_count() -1] - var le_name_out: LineEdit = tbl.get_node2(node_index[0], node_index[1]) - disable_edit(tbl, len(row_type_conn), node_index[1]) - le_name_out.text = dev_con.dev_name - tbl.set_node_user_data(node_index[0], node_index[1], dev.connect_devs[i]) - tbl.connect_columns(self, 'text_submitted', 'on_edit_entry', TABLE_ENTRY_COLUMNS, []) + for i in dev.connect_devs: + var dev_con = dev.connect_devs[i][0] + tbl.add_row(row_type_conn) + var node_index: Array = [0, tbl.get_rows_count() -1] + var le_name_out: LineEdit = tbl.get_node2(node_index[0], node_index[1]) + disable_edit(tbl, len(row_type_conn), node_index[1]) + le_name_out.text = dev_con.dev_name + tbl.set_node_user_data(node_index[0], node_index[1], dev.connect_devs[i]) + tbl.connect_columns(self, 'text_submitted', 'on_edit_entry', TABLE_ENTRY_COLUMNS, []) -## Обработчик нажатия кнопки connect +## Обработчик нажатия кнопки connect. func on_btn_connect(): - var in_row_index: Array = [] - var out_row_index: Array = [] - $scrollin/tbl_in.get_selected_rows(in_row_index) - $scrollout/tbl_out.get_selected_rows(out_row_index) - if len(in_row_index) and len(out_row_index): - var in_user_data = $scrollin/tbl_in.get_node_user_data(0, in_row_index[0]) - var out_user_data = $scrollout/tbl_out.get_node_user_data(0, out_row_index[0]) - var in_dev = in_user_data[0] - var out_dev = out_user_data[0] - add_connection_dev($scrl_out_set/tbl_out_set, in_dev, out_dev, OUT_SET_ROW, in_user_data) - add_connection_dev($scrl_in_set/tbl_in_set, out_dev, in_dev, IN_SET_ROW, out_user_data) + var in_row_index: Array = [] + var out_row_index: Array = [] + $scrollin/tbl_in.get_selected_rows(in_row_index) + $scrollout/tbl_out.get_selected_rows(out_row_index) + if len(in_row_index) and len(out_row_index): + var in_user_data = $scrollin/tbl_in.get_node_user_data(0, in_row_index[0]) + var out_user_data = $scrollout/tbl_out.get_node_user_data(0, out_row_index[0]) + var in_dev = in_user_data[0] + var out_dev = out_user_data[0] + add_connection_dev($scrl_out_set/tbl_out_set, in_dev, out_dev, OUT_SET_ROW, in_user_data) + add_connection_dev($scrl_in_set/tbl_in_set, out_dev, in_dev, IN_SET_ROW, out_user_data) -## образовывает связь между выбранным входом и выходом [param tbl] - таблица, [param dev1] - устройство1, -## [param dev2] - устройство 2, [param set_row] - массив с пустыми LineEdit -## [param user_data] - пользовательские данные устройства1 +## Образовывает связь между выбранным входом и выходом.[br] +## [param tbl] - таблица,[br] +## [param dev1] - устройство1,[br] +## [param dev2] - устройство 2,[br] +## [param set_row] - массив с пустыми LineEdit,[br] +## [param user_data] - пользовательские данные устройства1.[br] func add_connection_dev(tbl, dev1, dev2, set_row, user_data): - if not dev1.connector in dev2.connect_devs: - tbl.add_row(set_row) - var node_index: Array = [0, tbl.get_rows_count() -1] - var le_name: LineEdit = tbl.get_node2(node_index[0], node_index[1]) - le_name.text = dev1.connector - disable_edit(tbl, len(set_row), node_index[1]) - tbl.set_node_user_data(node_index[0], node_index[1], user_data) - dev2.connect_devs[dev1.connector] = user_data + if not dev1.connector in dev2.connect_devs: + tbl.add_row(set_row) + var node_index: Array = [0, tbl.get_rows_count() -1] + var le_name: LineEdit = tbl.get_node2(node_index[0], node_index[1]) + le_name.text = dev1.connector + disable_edit(tbl, len(set_row), node_index[1]) + tbl.set_node_user_data(node_index[0], node_index[1], user_data) + dev2.connect_devs[dev1.connector] = user_data -## Отключает возможность редактирования LineEdit в таблице [param tbl] - таблица, -## [param num_col] - номер колонки, [param i_row] - номер столбца +## Отключает возможность редактирования LineEdit в таблице.[br] +## [param tbl] - таблица,[br] +## [param num_col] - номер колонки,[br] +## [param i_row] - номер столбца.[br] func disable_edit(tbl, num_col: int, i_row: int): - for col in range(num_col): - var node: LineEdit = tbl.get_node2(col, i_row) - node.editable = false + for col in range(num_col): + var node: LineEdit = tbl.get_node2(col, i_row) + node.editable = false -## Обработчик выбора строки с таблице +## Обработчик выбора строки с таблице. func on_row_pressed(i_row: int, tbl): - tbl.clear_rows_selected() - var is_row_selected = tbl.is_row_selected(i_row) - tbl.set_row_selected(i_row, not is_row_selected) + tbl.clear_rows_selected() + var is_row_selected = tbl.is_row_selected(i_row) + tbl.set_row_selected(i_row, not is_row_selected) diff --git a/scripts/capsrpb.gd b/scripts/capsrpb.gd index bf790dac..e4f4491d 100644 --- a/scripts/capsrpb.gd +++ b/scripts/capsrpb.gd @@ -1,50 +1,50 @@ -extends Node +class_name STC extends Node -## Реализация "Протокол информационного сопряжения -## СПО АСУ изделия «МП‑550» с СПО «Управления КАПС РПБ»" +## Реализация "Протокол информационного сопряжения. +## СПО АСУ изделия «МП‑550» с СПО «Управления КАПС РПБ». -const ONLINE_TIMEOUT = 3000 ## Время ожидания пакета от КАПС РПБ, мс -const BUFFER_SIZE = 2048 ## Размер приёмного буфера для датаграмм +const ONLINE_TIMEOUT = 3000 ## Время ожидания пакета от КАПС РПБ, мс. +const BUFFER_SIZE = 2048 ## Размер приёмного буфера для датаграмм. class CapsRpb: - signal line_changed(unit: CapsRpb) ## Сигнал состояние связи изменилось - signal data_received(unit: CapsRpb) ## Сигнал данные приняты - signal command_fail(unit: CapsRpb) ## Сигнал при не выполнении команды - signal parse_error(unit: CapsRpb, msg: String) ## Сигнал ошибки разбора сообщения + signal line_changed(unit: CapsRpb) ## Сигнал состояние связи изменилось. + signal data_received(unit: CapsRpb) ## Сигнал данные приняты. + signal command_fail(unit: CapsRpb) ## Сигнал при не выполнении команды. + signal parse_error(unit: CapsRpb, msg: String) ## Сигнал ошибки разбора сообщения. - static var json_conv: = JSON.new() - var online: = false ## Состояние связи с источником сообщений - var name: String ## Имя экземпляра - var json_dic: Dictionary ## Аппарат для разбора сообщений - var tick: = 0 ## Тик время последнего обновления, мс - var tx_data: = PackedByteArray() ## Пакет для отправки - var tx_stack: Array ## Массив пакетов для отправки + static var json_conv: = JSON.new() + var online: = false ## Состояние связи с источником сообщений. + var name: String ## Имя экземпляра. + var json_dic: Dictionary ## Аппарат для разбора сообщений. + var tick: = 0 ## Тик время последнего обновления, мс. + var tx_data: = PackedByteArray() ## Пакет для отправки. + var tx_stack: Array ## Массив пакетов для отправки. - func _to_string() -> String: return String('капсрпб: "%s" %s ' % [self.name, ['отключен', 'на связи'][int(online)]]) - func _init(nm): self.name = nm + func _to_string() -> String: return String('капсрпб: "%s" %s ' % [self.name, ['отключен', 'на связи'][int(online)]]) + func _init(nm): self.name = nm - func parse(data: PackedByteArray, now_tick: int): - tick = now_tick - if not online: - online = true - emit_signal('line_changed', self) - var utf8_data = data.get_string_from_utf8() - if Error.OK == json_conv.parse(utf8_data): - json_dic.merge(json_conv.get_data(), true) - emit_signal('data_received', self) - else: - var msg = json_conv.get_error_message() - emit_signal('parse_error', self, msg) + func parse(data: PackedByteArray, now_tick: int): + tick = now_tick + if not online: + online = true + emit_signal('line_changed', self) + var utf8_data = data.get_string_from_utf8() + if Error.OK == json_conv.parse(utf8_data): + json_dic.merge(json_conv.get_data(), true) + emit_signal('data_received', self) + else: + var msg = json_conv.get_error_message() + emit_signal('parse_error', self, msg) - func process(now_tick: int): - if online and ((now_tick - tick) > ONLINE_TIMEOUT): - online = false - emit_signal('line_changed', self) - if len(tx_stack): - tx_data = tx_stack[0] - tx_stack.remove_at(0) - return Error.OK - else: - return Error.ERR_UNAVAILABLE + func process(now_tick: int): + if online and ((now_tick - tick) > ONLINE_TIMEOUT): + online = false + emit_signal('line_changed', self) + if len(tx_stack): + tx_data = tx_stack[0] + tx_stack.remove_at(0) + return Error.OK + else: + return Error.ERR_UNAVAILABLE diff --git a/scripts/file-table.gd b/scripts/file-table.gd index d2012c5d..5b734926 100644 --- a/scripts/file-table.gd +++ b/scripts/file-table.gd @@ -9,30 +9,30 @@ var FILES_ROWS_ALIGNMENT = [ HORIZONTAL_ALIGNMENT_LEFT, HORIZONTAL_ALIGNM func fill_files_table(tbl: table, folder: String, rows_types: Array): - var dir: = DirAccess.open(folder) - if dir == null: - return - var files: = [] - var exclude_names: = ['.', '..'] - dir.list_dir_begin() # TODOGODOT4 fill missing arguments https://github.com/godotengine/godot/pull/40547 - while true: - var file: = dir.get_next() - if file == '': - break - if file in exclude_names: - continue - var file_name = folder + '/' + file - var fd = FileAccess.open(file_name, FileAccess.READ) - if not fd: - continue - var glob_path = ProjectSettings.globalize_path(file_name) - var sz = fd.get_length() - var ut = fd.get_modified_time(file_name) - fd.close() - var mt = Time.get_datetime_dict_from_unix_time(ut) - var file_row = [file, '%d' % sz, '%d-%02d-%02d %02d:%02d' % [mt.year, mt.month, mt.day, mt.hour, mt.minute]] - tbl.add_row(rows_types) - var i_row = tbl.get_rows_count() - 1 - tbl.set_row_text(i_row, file_row) - tbl.set_node_meta(0, i_row, 'file_path', glob_path) - dir.list_dir_end() + var dir: = DirAccess.open(folder) + if dir == null: + return + var files: = [] + var exclude_names: = ['.', '..'] + dir.list_dir_begin() # TODOGODOT4 fill missing arguments https://github.com/godotengine/godot/pull/40547 + while true: + var file: = dir.get_next() + if file == '': + break + if file in exclude_names: + continue + var file_name = folder + '/' + file + var fd = FileAccess.open(file_name, FileAccess.READ) + if not fd: + continue + var glob_path = ProjectSettings.globalize_path(file_name) + var sz = fd.get_length() + var ut = fd.get_modified_time(file_name) + fd.close() + var mt = Time.get_datetime_dict_from_unix_time(ut) + var file_row = [file, '%d' % sz, '%d-%02d-%02d %02d:%02d' % [mt.year, mt.month, mt.day, mt.hour, mt.minute]] + tbl.add_row(rows_types) + var i_row = tbl.get_rows_count() - 1 + tbl.set_row_text(i_row, file_row) + tbl.set_node_meta(0, i_row, 'file_path', glob_path) + dir.list_dir_end() diff --git a/scripts/hotkeys.gd b/scripts/hotkeys.gd index a75efc97..a01762c6 100644 --- a/scripts/hotkeys.gd +++ b/scripts/hotkeys.gd @@ -1,20 +1,20 @@ -extends Node +class_name HotKeys extends Node func _ready() -> void: - signaller.connect('full_screen', Callable(self, 'toggle_full_screen').bind(0)) + signaller.connect('full_screen', Callable(self, 'toggle_full_screen').bind(0)) func _input(event: InputEvent) -> void: - if event is InputEventKey: - if event.pressed and event.physical_keycode == KEY_F11: - signaller.emit_signal('full_screen') + if event is InputEventKey: + if event.pressed and event.physical_keycode == KEY_F11: + signaller.emit_signal('full_screen') -## Переключает режим окна программы полный - обычный экран +## Переключает режим окна программы полный - обычный экран. func toggle_full_screen(id: int) -> void: - var mode = DisplayServer.window_get_mode(id) - mode = DisplayServer.WINDOW_MODE_WINDOWED \ - if mode == DisplayServer.WINDOW_MODE_FULLSCREEN \ - else DisplayServer.WINDOW_MODE_FULLSCREEN - DisplayServer.window_set_mode(mode, id) + var mode = DisplayServer.window_get_mode(id) + mode = DisplayServer.WINDOW_MODE_WINDOWED \ + if mode == DisplayServer.WINDOW_MODE_FULLSCREEN \ + else DisplayServer.WINDOW_MODE_FULLSCREEN + DisplayServer.window_set_mode(mode, id) diff --git a/scripts/interfer.gd b/scripts/interfer.gd index f7a1f131..9de6555c 100644 --- a/scripts/interfer.gd +++ b/scripts/interfer.gd @@ -1,25 +1,25 @@ -extends Node +class_name Iterfer extends Node -## Представление помехи +## Представление помехи. -## Режимы помехи +## Режимы помехи. const ITERFER_MODE = { - 'off': ['Откл', ['freq', 'width'], 0], - 'auto': ['АВТО', ['freq', 'width'], 1000], - 'srni1': ['СРН-И1', ['freq', 'width'], 1], - 'srnm2': ['СРН-М2', ['freq', 'width'], 2], - 'srngps': ['СРН-GPS', ['freq', 'width'], 3], - 'lchm1': ['ЛЧМ-1', ['freq', 'width'], 10], - 'lchm2': ['ЛЧМ-2', ['freq', 'width'], 4], - 'mchm1': ['МЧМ-1', ['freq', 'width'], 11], - 'pdm2': ['ПД-М2', ['freq', 'width'], 12], - 'pdm4': ['ПД-М4', ['freq', 'width'], 13], - 'pdm4s': ['ПД-М4С', ['freq', 'width'], 14], - 'pdchm': ['ПД-ЧМ', ['freq', 'width'], 15], - 'svi1': ['СВ-И1', ['freq', 'width'], 20], - 'svm2': ['СВ-М2', ['freq', 'width'], 21], - 'k1': ['K1', ['freq', 'width', 'dur', 'period'], 50], - 'ozp1': ['ОЗП-1', ['freq', 'width'], 51] } + 'off': ['Откл', ['freq', 'width'], 0], + 'auto': ['АВТО', ['freq', 'width'], 1000], + 'srni1': ['СРН-И1', ['freq', 'width'], 1], + 'srnm2': ['СРН-М2', ['freq', 'width'], 2], + 'srngps': ['СРН-GPS', ['freq', 'width'], 3], + 'lchm1': ['ЛЧМ-1', ['freq', 'width'], 10], + 'lchm2': ['ЛЧМ-2', ['freq', 'width'], 4], + 'mchm1': ['МЧМ-1', ['freq', 'width'], 11], + 'pdm2': ['ПД-М2', ['freq', 'width'], 12], + 'pdm4': ['ПД-М4', ['freq', 'width'], 13], + 'pdm4s': ['ПД-М4С', ['freq', 'width'], 14], + 'pdchm': ['ПД-ЧМ', ['freq', 'width'], 15], + 'svi1': ['СВ-И1', ['freq', 'width'], 20], + 'svm2': ['СВ-М2', ['freq', 'width'], 21], + 'k1': ['K1', ['freq', 'width', 'dur', 'period'], 50], + 'ozp1': ['ОЗП-1', ['freq', 'width'], 51] } const FREQ_MAX = 6000 const FREQ_MIN = 400 @@ -27,63 +27,63 @@ const WIDTH_MAX = 120 const WIDTH_MIN = 0 const SOURCE_INTERFER = ['th','fs','cu'] -const ID_INTERFERS_LEN = 63 ## Размер пула номеров целей +const ID_INTERFERS_LEN = 63 ## Размер пула номеров целей. const INTERFER_OFF = 'off' -const STATE_TSMT = 5 ## Значение svk для цели, когда ответ ещё не получен -var msg_ss: int = 0 ## счётчик сообщений -var id_array: Array ## Массив id воздействий -var thrs: Dictionary ## Словарь воздействия для целей, ключ - id воздействия -var fs: Dictionary ## Словарь воздействий для секторов, ключ - id воздействия -var cu: Dictionary ## Словарь воздействий для 5П-28, ключ - id воздействия +const STATE_TSMT = 5 ## Значение svk для цели, когда ответ ещё не получен. +var msg_ss: int = 0 ## Счётчик сообщений. +var id_array: Array ## Массив id воздействий. +var thrs: Dictionary ## Словарь воздействия для целей, ключ - id воздействия. +var fs: Dictionary ## Словарь воздействий для секторов, ключ - id воздействия. +var cu: Dictionary ## Словарь воздействий для 5П-28, ключ - id воздействия. -## Сеанс помехи +## Сеанс помехи. class Interfer: - var ss: int = 0 ## Счётчик сообщений. - var ispp: int = 0 ## Индекс сеанса. - var svk: = 0 ## Состояние сеанса. Ответ КАПСРПБ. Возможные значения в [param ITERFER_STATE]. - var krp: String = '' ## Тип помехи. Возможные значения в [param INTERFER_MODE]. - var powp: float = 0.0 ## Мощность излучения выраженная в процентах. - var kni: = 0.0 ## Курсовой угол направления излучения. - var params: = {} ## Параметры помехи. - var get_params: Array[String] ## Запрос массива параметров (список запрошенных параметров). - var retss: int ## Значение счётчика сообщения в котором был произведён запрос массива параметров. - var tick_tx: int ## Тик время обновления цели, мс. - var source_interfer: String = '' ## Тип объекта по которой поставлена помеха + var ss: int = 0 ## Счётчик сообщений. + var ispp: int = 0 ## Индекс сеанса. + var svk: = 0 ## Состояние сеанса. Ответ КАПСРПБ. Возможные значения в [param ITERFER_STATE]. + var krp: String = '' ## Тип помехи. Возможные значения в [param INTERFER_MODE]. + var powp: float = 0.0 ## Мощность излучения выраженная в процентах. + var kni: = 0.0 ## Курсовой угол направления излучения. + var params: = {} ## Параметры помехи. + var get_params: Array[String] ## Запрос массива параметров (список запрошенных параметров). + var retss: int ## Значение счётчика сообщения в котором был произведён запрос массива параметров. + var tick_tx: int ## Тик время обновления цели, мс. + var source_interfer: String = '' ## Тип объекта по которой поставлена помеха. - func _to_string() -> String: - return 'номер: %d название: %s частота: %0.1f МГц пеленг: %0.1f гр.' % [ispp, ITERFER_MODE[krp][0], params['freq'], kni] + func _to_string() -> String: + return 'номер: %d название: %s частота: %0.1f МГц пеленг: %0.1f гр.' % [ispp, ITERFER_MODE[krp][0], params['freq'], kni] - func get_msg6_data(): - var data_to_json : Dictionary = {} - data_to_json['ts'] = 6 - data_to_json['ss'] = ss - data_to_json['ispp'] = ispp - data_to_json['krp'] = krp - if krp != INTERFER_OFF: - data_to_json['kni'] = kni - data_to_json['powp'] = powp # Необходимо добавить управление мощностью - data_to_json['get'] = [] - data_to_json['params'] = params - var json = JSON.stringify(data_to_json) - var pac = json.to_utf8_buffer() - return pac + func get_msg6_data(): + var data_to_json : Dictionary = {} + data_to_json['ts'] = 6 + data_to_json['ss'] = ss + data_to_json['ispp'] = ispp + data_to_json['krp'] = krp + if krp != INTERFER_OFF: + data_to_json['kni'] = kni + data_to_json['powp'] = powp # Необходимо добавить управление мощностью + data_to_json['get'] = [] + data_to_json['params'] = params + var json = JSON.stringify(data_to_json) + var pac = json.to_utf8_buffer() + return pac - func set_from_dic(dic: Dictionary): - var names: = tools.get_members_names(self) - names.any(func(vn): if dic.has(vn): set(vn, dic[vn]); return false) + func set_from_dic(dic: Dictionary): + var names: = tools.get_members_names(self) + names.any(func(vn): if dic.has(vn): set(vn, dic[vn]); return false) - func get_value(value_key): - var param = self.get(value_key) - if param == null: - if params.has(value_key): param = params[value_key] - return param + func get_value(value_key): + var param = self.get(value_key) + if param == null: + if params.has(value_key): param = params[value_key] + return param - func set_value(value, value_key): - var param = self.get(value_key) - if param == null: - params[value_key] = value - else: - self.set(value_key, value) + func set_value(value, value_key): + var param = self.get(value_key) + if param == null: + params[value_key] = value + else: + self.set(value_key, value) signal interfer_preparing(ecm: Interfer) ## Вызывается в момент запуска помехи. @@ -94,173 +94,173 @@ signal interfer_resized(ecms: Dictionary) ## Вызывается пр func _ready(): - var ecms: Dictionary = {} ## Словарь помех - for i in range(ID_INTERFERS_LEN): # Заполняет массив ID целей - id_array.append(i + 1) - var unit_key = repsettings.get_unit_key('уарэп-капсрпб-отп') - var unit = repnetwork.units[unit_key] - var unit_5p28_key = repsettings.get_unit_key('уарэп-5п28') - var unit_5p28 = repnetwork.units_tcp[unit_5p28_key] - var timer_check_state: = Timer.new() - add_child(timer_check_state) - var resend_timeout = ProjectSettings.get_setting('application/interfer/resend_timeout', 1000.0) - timer_check_state.connect('timeout', Callable(self, 'on_timer_check_state').bind(unit, ecms, resend_timeout)) - timer_check_state.start(resend_timeout / 1000.0) - unit.connect('data_received', Callable(self, 'on_data_capsrpb_received').bind(ecms)) - unit_5p28.connect('get_interfers', Callable(self, 'on_get_interfers').bind(unit_5p28, ecms)) - signaller.connect('interfer_create', Callable(self, 'on_interfer_create').bind(unit, ecms)) - signaller.connect('interfer_rcv', Callable(self, 'on_interfer_rcv').bind(unit_5p28, ecms)) - signaller.connect('interfer_new', Callable(self, 'send_ecm').bind(unit)) - signaller.connect('interfer_off_all', Callable(self, 'on_interfer_off_all').bind(unit, ecms)) + var ecms: Dictionary = {} ## Словарь помех. + for i in range(ID_INTERFERS_LEN): # Заполняет массив ID целей + id_array.append(i + 1) + var unit_key = repsettings.get_unit_key('уарэп-капсрпб-отп') + var unit = repnetwork.units[unit_key] + var unit_5p28_key = repsettings.get_unit_key('уарэп-5п28') + var unit_5p28 = repnetwork.units_tcp[unit_5p28_key] + var timer_check_state: = Timer.new() + add_child(timer_check_state) + var resend_timeout = ProjectSettings.get_setting('application/interfer/resend_timeout', 1000.0) + timer_check_state.connect('timeout', Callable(self, 'on_timer_check_state').bind(unit, ecms, resend_timeout)) + timer_check_state.start(resend_timeout / 1000.0) + unit.connect('data_received', Callable(self, 'on_data_capsrpb_received').bind(ecms)) + unit_5p28.connect('get_interfers', Callable(self, 'on_get_interfers').bind(unit_5p28, ecms)) + signaller.connect('interfer_create', Callable(self, 'on_interfer_create').bind(unit, ecms)) + signaller.connect('interfer_rcv', Callable(self, 'on_interfer_rcv').bind(unit_5p28, ecms)) + signaller.connect('interfer_new', Callable(self, 'send_ecm').bind(unit)) + signaller.connect('interfer_off_all', Callable(self, 'on_interfer_off_all').bind(unit, ecms)) -# Проверяет состояние отправки пакета и если не доставлен, повторяет отправку +## Проверяет состояние отправки пакета и если не доставлен, повторяет отправку. func on_timer_check_state(unit, ecms, resend_timeout): - var tick = Time.get_ticks_msec() - for ecm in ecms.values(): - if ecm.svk == STATE_TSMT: - if (tick - ecm.tick_tx) >= resend_timeout: - send_ecm(ecm, unit) - ecm.tick_tx = tick + var tick = Time.get_ticks_msec() + for ecm in ecms.values(): + if ecm.svk == STATE_TSMT: + if (tick - ecm.tick_tx) >= resend_timeout: + send_ecm(ecm, unit) + ecm.tick_tx = tick -## Производит приём состояния помех. [param unit] -> [param ecms] +## Производит приём состояния помех. [param unit] -> [param ecms]. func on_data_capsrpb_received(unit: capsrpb.CapsRpb, ecms: Dictionary): - if not unit.json_dic.has('ts'): return - if not (unit.json_dic['ts'] is float): return - if unit.json_dic['ts'] != 5: return - if not unit.json_dic.has('msvk'): return - if not (unit.json_dic['msvk'] is Array): return - var msvk = unit.json_dic['msvk'] - var sz: = ecms.size() - for dic_msvk in msvk: - if not (dic_msvk is Dictionary): continue - if dic_msvk.has('ispp'): - var ispp = int(dic_msvk['ispp']) - if ecms.has(ispp): - var ecm = ecms[ispp] - ecm.set_from_dic(dic_msvk) - call_deferred('emit_signal', 'interfer_update', ecm) - else: - var ecm = Interfer.new() - ecm.set_from_dic(dic_msvk) - ecm.krp = INTERFER_OFF - send_ecm(ecm, unit) - if sz != ecms.size(): - call_deferred('emit_signal', 'interfer_resized', ecms) + if not unit.json_dic.has('ts'): return + if not (unit.json_dic['ts'] is float): return + if unit.json_dic['ts'] != 5: return + if not unit.json_dic.has('msvk'): return + if not (unit.json_dic['msvk'] is Array): return + var msvk = unit.json_dic['msvk'] + var sz: = ecms.size() + for dic_msvk in msvk: + if not (dic_msvk is Dictionary): continue + if dic_msvk.has('ispp'): + var ispp = int(dic_msvk['ispp']) + if ecms.has(ispp): + var ecm = ecms[ispp] + ecm.set_from_dic(dic_msvk) + call_deferred('emit_signal', 'interfer_update', ecm) + else: + var ecm = Interfer.new() + ecm.set_from_dic(dic_msvk) + ecm.krp = INTERFER_OFF + send_ecm(ecm, unit) + if sz != ecms.size(): + call_deferred('emit_signal', 'interfer_resized', ecms) -## Вызыватся, когда пользователь выбрал помеху и выбраны цели +## Вызыватся, когда пользователь выбрал помеху и выбраны цели. func on_interfer_create(btn, sel_threats, fs_arr, _unit, ecms): - for item in sel_threats: - var param: Dictionary = {} - var kni: float = item.aoa - var id = item.id - var interfer_name: String = btn.get_meta('rfi_name') - param['freq'] = item.freq - param['width'] = item.width - interfer_create(interfer_name, ecms, param, kni, id, thrs, SOURCE_INTERFER[0]) + for item in sel_threats: + var param: Dictionary = {} + var kni: float = item.aoa + var id = item.id + var interfer_name: String = btn.get_meta('rfi_name') + param['freq'] = item.freq + param['width'] = item.width + interfer_create(interfer_name, ecms, param, kni, id, thrs, SOURCE_INTERFER[0]) - for item in fs_arr: - var param: Dictionary = {} - var kni: float = item[0] - var id = item[3] - var interfer_name: String = btn.get_meta('rfi_name') - param['freq'] = item[1] - param['width'] = item[2] - interfer_create(interfer_name, ecms, param, kni, id, fs, SOURCE_INTERFER[1]) + for item in fs_arr: + var param: Dictionary = {} + var kni: float = item[0] + var id = item[3] + var interfer_name: String = btn.get_meta('rfi_name') + param['freq'] = item[1] + param['width'] = item[2] + interfer_create(interfer_name, ecms, param, kni, id, fs, SOURCE_INTERFER[1]) func on_interfer_rcv(interfer_params: Array, unit, ecms): - var err: bool = false - var mode = interfer_params[0] - var id = interfer_params[1] - var aoa = interfer_params[2] - if (aoa < 0) and (aoa > 360): - err = true - var param: Dictionary = {} - param['freq'] = interfer_params[4] - if (param['freq'] < FREQ_MIN) and (param['freq'] > FREQ_MAX): - err = true - param['width'] = interfer_params[5] - if (param['width'] < WIDTH_MIN) and (param['width'] > WIDTH_MAX): - err = true - var interfer_name: String - for key in ITERFER_MODE: - if ITERFER_MODE[key][2] == mode: - interfer_name = key - break - if interfer_name == '': - err = true - if not err: - interfer_create(interfer_name, ecms, param, aoa, id, cu, SOURCE_INTERFER[2]) - unit.on_interfer_accept(err) + var err: bool = false + var mode = interfer_params[0] + var id = interfer_params[1] + var aoa = interfer_params[2] + if (aoa < 0) and (aoa > 360): + err = true + var param: Dictionary = {} + param['freq'] = interfer_params[4] + if (param['freq'] < FREQ_MIN) and (param['freq'] > FREQ_MAX): + err = true + param['width'] = interfer_params[5] + if (param['width'] < WIDTH_MIN) and (param['width'] > WIDTH_MAX): + err = true + var interfer_name: String + for key in ITERFER_MODE: + if ITERFER_MODE[key][2] == mode: + interfer_name = key + break + if interfer_name == '': + err = true + if not err: + interfer_create(interfer_name, ecms, param, aoa, id, cu, SOURCE_INTERFER[2]) + unit.on_interfer_accept(err) func interfer_create(interfer_name, ecms, param, kni, id, dict, source_interfer): - var ispp: int = 0 - if interfer_name == 'k1': - param['dur'] = 1.0 # Необходимо добавить параметры управления - param['period'] = 1.0 # Необходимо добавить параметры управления - var key = dict.find_key(id) - if source_interfer != SOURCE_INTERFER[2]: - if key != null: - ispp = key - else: - if interfer_name == 'off': - return - ispp = pop_id() - if ispp == 0: return - else: ispp = id - dict[ispp] = id - msg_ss += 1 - var ecm: Interfer - if ecms.has(ispp): - ecm = ecms[ispp] - else: - ecm = Interfer.new() - ecm.ispp = ispp - ecms[ispp] = ecm - ecm.svk = STATE_TSMT # Подготовка к передаче сообщения - ecm.ss = msg_ss - ecm.kni = kni - ecm.krp = interfer_name - ecm.params = param - ecm.powp = 100.0 # Необходимо добавить управление мощностью - ecm.source_interfer = source_interfer - signaller.emit_signal('interfer_new', ecm) - if interfer_name == 'off': - signaller.emit_signal('interfer_off', ecm, ecms, thrs) - push_id(ecm.ispp) - thrs.erase(ispp) - ecm.tick_tx = Time.get_ticks_msec() + var ispp: int = 0 + if interfer_name == 'k1': + param['dur'] = 1.0 # Необходимо добавить параметры управления + param['period'] = 1.0 # Необходимо добавить параметры управления + var key = dict.find_key(id) + if source_interfer != SOURCE_INTERFER[2]: + if key != null: + ispp = key + else: + if interfer_name == 'off': + return + ispp = pop_id() + if ispp == 0: return + else: ispp = id + dict[ispp] = id + msg_ss += 1 + var ecm: Interfer + if ecms.has(ispp): + ecm = ecms[ispp] + else: + ecm = Interfer.new() + ecm.ispp = ispp + ecms[ispp] = ecm + ecm.svk = STATE_TSMT # Подготовка к передаче сообщения + ecm.ss = msg_ss + ecm.kni = kni + ecm.krp = interfer_name + ecm.params = param + ecm.powp = 100.0 # Необходимо добавить управление мощностью + ecm.source_interfer = source_interfer + signaller.emit_signal('interfer_new', ecm) + if interfer_name == 'off': + signaller.emit_signal('interfer_off', ecm, ecms, thrs) + push_id(ecm.ispp) + thrs.erase(ispp) + ecm.tick_tx = Time.get_ticks_msec() func send_ecm(ecm, unit): - var json_data = ecm.get_msg6_data() - unit.tx_stack.append(json_data) + var json_data = ecm.get_msg6_data() + unit.tx_stack.append(json_data) func on_interfer_off_all(unit, ecms): - for ecm in ecms.values(): - ecm.krp = INTERFER_OFF - push_id(ecm.ispp) - send_ecm(ecm, unit) - signaller.emit_signal('interfer_off',ecm, ecms, thrs) + for ecm in ecms.values(): + ecm.krp = INTERFER_OFF + push_id(ecm.ispp) + send_ecm(ecm, unit) + signaller.emit_signal('interfer_off',ecm, ecms, thrs) func pop_id(): - var id = 0 - if len(id_array): - id = id_array[0] - id_array.remove_at(0) - return id + var id = 0 + if len(id_array): + id = id_array[0] + id_array.remove_at(0) + return id func push_id(id : int): - id_array.append(id) - id_array.sort() + id_array.append(id) + id_array.sort() -## отправляет словаhь помех в юнит, для запаковки и отправки в 5П28 +## Отправляет словарь помех в юнит, для запаковки и отправки в 5П28. func on_get_interfers(unit, ecms): unit.pack_interfers(cu, ecms) diff --git a/scripts/mercator.gd b/scripts/mercator.gd index c876d452..9a496d67 100644 --- a/scripts/mercator.gd +++ b/scripts/mercator.gd @@ -1,12 +1,12 @@ -extends Node +class_name Mercator extends Node ## Утилиты для работы с меркаторовскими XYZ-плитками. const R2D: = 180.0 / PI -const RE: = 6378137.0 ## Радиус Земли экваториальный в метрах -const RP: = 6356752.31424518 ## Радиус Земли полярный в метрах -const CE: = 2.0 * PI * RE ## Длина окружности Земли в метрах по экватору -const CP: = 2.0 * PI * RP ## Длина окружности Земли в метрах через полюсы +const RE: = 6378137.0 ## Радиус Земли экваториальный в метрах. +const RP: = 6356752.31424518 ## Радиус Земли полярный в метрах. +const CE: = 2.0 * PI * RE ## Длина окружности Земли в метрах по экватору. +const CP: = 2.0 * PI * RP ## Длина окружности Земли в метрах через полюсы. const EPSILON: = 1e-14 const LL_EPSILON: = 1e-11 const MAX_ZOOM: = 20 @@ -14,95 +14,95 @@ const MAX_NORTH = 85.051129 const MAX_SOUTH = -85.051129 -## XYZ-плитка веб-меркатора [param x], [param y], [param z] индексы -# [b]x[/b] и [b]y[/b] плитки и уровень масштабирования [b]z[/b]. +## XYZ-плитка веб-меркатора [param x], [param y], [param z] индексы. +## [b]x[/b] и [b]y[/b] плитки и уровень масштабирования [b]z[/b]. class Tile: - var x: int - var y: int - var z: int - var lng: float ## Координаты, которые плитка должна покрывать, долгота, градусы - var lat: float ## Широта, градусы - var is_root: bool ## Признак, что плитка корневая (покрывает координаты) + var x: int + var y: int + var z: int + var lng: float ## Координаты, которые плитка должна покрывать, долгота, градусы. + var lat: float ## Широта, градусы. + var is_root: bool ## Признак, что плитка корневая (покрывает координаты). - func as_array(): return [self.x, self.y, self.z] + func as_array(): return [self.x, self.y, self.z] - func _to_string() -> String: return '[%d, %d, %d]' % [self.x, self.y, self.z] + func _to_string() -> String: return '[%d, %d, %d]' % [self.x, self.y, self.z] - func _init(x0: int = 0, y0: int = 0, z0: int = 0, is_root0: bool = true): - x = x0 - y = y0 - z = z0 - is_root = is_root0 + func _init(x0: int = 0, y0: int = 0, z0: int = 0, is_root0: bool = true): + x = x0 + y = y0 + z = z0 + is_root = is_root0 - func is_valid() -> bool: - if (z >= 0) and (z < MAX_ZOOM): - var mz: int = 2 ** z - return (x >= 0) and (x < mz) \ - and (y >= 0) and (y < mz) - return false + func is_valid() -> bool: + if (z >= 0) and (z < MAX_ZOOM): + var mz: int = 2 ** z + return (x >= 0) and (x < mz) \ + and (y >= 0) and (y < mz) + return false - func _bound_index(index: int) -> int: - var mi: int = 2 ** z - if index < 0: - index *= -1 - index %= mi - index = mi - index - else: - index %= mi - return index + func _bound_index(index: int) -> int: + var mi: int = 2 ** z + if index < 0: + index *= -1 + index %= mi + index = mi - index + else: + index %= mi + return index - func fix_xy(): - x = _bound_index(x) - y = _bound_index(y) + func fix_xy(): + x = _bound_index(x) + y = _bound_index(y) ## Пара долготы и широты. class LngLat: - var lng: float = 0.0 ## Долгота в десятичных градусах восточной долготы. - var lat: float = 0.0 ## Широта в десятичных градусах северной широты. + var lng: float = 0.0 ## Долгота в десятичных градусах восточной долготы. + var lat: float = 0.0 ## Широта в десятичных градусах северной широты. - ## Возвращает в виде массива - func as_array() -> Array: return [self.lng, self.lat] + ## Возвращает в виде массива. + func as_array() -> Array: return [self.lng, self.lat] - func _to_string() -> String: return '[%f, %f, %f]' % [self.x, self.y, self.z] + func _to_string() -> String: return '[%f, %f, %f]' % [self.x, self.y, self.z] - func _init(lng0, lat0): - self.lng = lng0 - self.lat = lat0 + func _init(lng0, lat0): + self.lng = lng0 + self.lat = lat0 ## Географические границы. Хранит значения границ в десятичных градусах. class LngLatBbox: - var west: float ## Запад - var south: float ## Юг - var east: float ## Восток - var north: float ## Север + var west: float ## Запад + var south: float ## Юг + var east: float ## Восток + var north: float ## Север - ## Возвращает в виде массива - func as_array() -> Array: return [self.west, self.south, self.east, self.north] + ## Возвращает в виде массива. + func as_array() -> Array: return [self.west, self.south, self.east, self.north] - func _to_string() -> String: return '[%f, %f, %f, %f]' % [self.west, self.south, self.east, self.north] + func _to_string() -> String: return '[%f, %f, %f, %f]' % [self.west, self.south, self.east, self.north] - func _init(w: float = 0.0, s: float = 0.0, e: float = 0.0, n: float = 0.0): - self.west = w - self.south = s - self.east = e - self.north = n + func _init(w: float = 0.0, s: float = 0.0, e: float = 0.0, n: float = 0.0): + self.west = w + self.south = s + self.east = e + self.north = n ## Граничная область веб-меркатора. Хранит значения границ в метрах. class Bbox: - var left: float - var bottom: float - var right: float - var top: float - func as_array() -> Array: return [self.left, self.bottom, self.right, self.top] - func _init(l: float = 0.0, b: float = 0.0, r: float = 0.0, t: float = 0.0): - self.left = l - self.bottom = b - self.right = r - self.top = t + var left: float + var bottom: float + var right: float + var top: float + func as_array() -> Array: return [self.left, self.bottom, self.right, self.top] + func _init(l: float = 0.0, b: float = 0.0, r: float = 0.0, t: float = 0.0): + self.left = l + self.bottom = b + self.right = r + self.top = t func degrees(v: float) -> float: return (v * 180.0) / PI @@ -114,69 +114,69 @@ func radians(v: float) -> float: return (v * PI) / 180.0 func rshift(val: int, n: int) -> int: return (val % 0x100000000) >> n -# Возвращает арксинус гиперболический от значения [param val] +## Возвращает арксинус гиперболический от значения [param val]. func asinh(val: float): return log(val + sqrt(val * val + 1.0)) -# Преобразует [param lon] в градусах восточной долготы, [param lat] в градусах -# северной долготы, [param alt] высоты в метрах в фиксированные координаты (x, y, z) Земли. +## Преобразует [param lon] в градусах восточной долготы, [param lat] в градусах. +## северной долготы, [param alt] высоты в метрах в фиксированные координаты (x, y, z) Земли. func lnglat_to_ecef(lng: float, lat: float, alt: float = 0.0) -> Array: - alt += (RE + RP) / 2.0 - var latrad: float = radians(lat) - var lonrad: float = radians(lng) - var coslat: float = cos(latrad) - var sinlat: float = sin(latrad) - var coslon: float = cos(lonrad) - var sinlon: float = sin(lonrad) - var term1: float = (RE * coslat) / tools.hypot(RE * coslat, RP * sinlat) - var term2: float = alt * coslat + term1; - var x: float = coslon * term2 - var y: float = sinlon * term2 - var z: float = alt * sinlat + (RP * sinlat) / tools.hypot(RE * coslat, RP * sinlat) - return [x, y, z] + alt += (RE + RP) / 2.0 + var latrad: float = radians(lat) + var lonrad: float = radians(lng) + var coslat: float = cos(latrad) + var sinlat: float = sin(latrad) + var coslon: float = cos(lonrad) + var sinlon: float = sin(lonrad) + var term1: float = (RE * coslat) / tools.hypot(RE * coslat, RP * sinlat) + var term2: float = alt * coslat + term1; + var x: float = coslon * term2 + var y: float = sinlon * term2 + var z: float = alt * sinlat + (RP * sinlat) / tools.hypot(RE * coslat, RP * sinlat) + return [x, y, z] func find_up_left(tiles: Array): - var x = 0 - var y = 0 - for tile in tiles: - if x < tile.x: x = tile.x - if y < tile.y: y = tile.y - for tile in tiles: - if tile.x == x and tile.y == y: - return tile - return null + var x = 0 + var y = 0 + for tile in tiles: + if x < tile.x: x = tile.x + if y < tile.y: y = tile.y + for tile in tiles: + if tile.x == x and tile.y == y: + return tile + return null -## Lon./lat. to tile numbers +## [param lon]/[param lat]. для номера плитки. func deg2num(lng: float, lat: float, zoom: int): - var n: = float(1 << zoom) - var xtile: = int((lng + 180.0) / 360.0 * n) - var ytile: = int((1.0 - asinh(tan(radians(lat))) / PI) / 2.0 * n) - return [xtile, ytile] + var n: = float(1 << zoom) + var xtile: = int((lng + 180.0) / 360.0 * n) + var ytile: = int((1.0 - asinh(tan(radians(lat))) / PI) / 2.0 * n) + return [xtile, ytile] -## Tile numbers to lon./lat. +## Номера плитки для [param lon]/[param lat]. func num2deg(x: int, y: int, zoom: int): - var n: = float(1 << zoom) - var lng: = float(x) / n * 360.0 - 180.0 - var lat: = atan(sinh(PI * (1.0 - 2.0 * float(y) / n))) - return [lng, degrees(lat)] + var n: = float(1 << zoom) + var lng: = float(x) / n * 360.0 - 180.0 + var lat: = atan(sinh(PI * (1.0 - 2.0 * float(y) / n))) + return [lng, degrees(lat)] -## Расстояние по горизонтали в метрах, представленное одним пикселем +## Расстояние по горизонтали в метрах, представленное одним пикселем. ## [param lat] - Широта, градусы.[br] ## [param zoom] - Уровень детализации.[br] ## [param size] - Размер плитки в пикселях.[br] func dist_per_pixel(lat: float, zoom: int, size: int = 256) -> float: - return (CE / float(size)) * cos(radians(lat)) / float(2 ** zoom) + return (CE / float(size)) * cos(radians(lat)) / float(2 ** zoom) -## Расстояние по горизонтали в метрах, представленное одной плиткой +## Расстояние по горизонтали в метрах, представленное одной плиткой. ## [param lat] - Широта, градусы.[br] ## [param zoom] - Уровень детализации.[br] func dist_per_tile(lat: float, zoom: int) -> float: - return CE * cos(radians(lat)) / float(2 ** zoom) + return CE * cos(radians(lat)) / float(2 ** zoom) ## Возвращает плитки и занимаемый ими размер области в пикселях: @@ -188,21 +188,21 @@ func dist_per_tile(lat: float, zoom: int) -> float: ## [param size] - Размер плитки в пикселях.[br] ## [param zoom] - Детализация.[br] func get_tiles(west: float, south: float, east: float, north: float, zoom: int, size: int) -> Array: - var tiles: = create_tiles(west, south, east, north, zoom) + var tiles: = create_tiles(west, south, east, north, zoom) - var mm = [] - for t in tiles: mm.append(t.x) - var min_x = mm.min() - var max_x = mm.max() + var mm = [] + for t in tiles: mm.append(t.x) + var min_x = mm.min() + var max_x = mm.max() - mm.clear() - for t in tiles: mm.append(t.y) - var min_y = mm.min() - var max_y = mm.max() + mm.clear() + for t in tiles: mm.append(t.y) + var min_y = mm.min() + var max_y = mm.max() - var w = size * (max_x - min_x + 1) - var h = size * (max_y - min_y + 1) - return [w, h, min_x, min_y, tiles, max_x - min_x + 1] + var w = size * (max_x - min_x + 1) + var h = size * (max_y - min_y + 1) + return [w, h, min_x, min_y, tiles, max_x - min_x + 1] ## Возвращает размер области в метрах.[br] @@ -211,9 +211,9 @@ func get_tiles(west: float, south: float, east: float, north: float, zoom: int, ## [param south] - Южная граница области.[br] ## [param north] - Северная граница области.[br] func get_size_from_degrees(west: float, south: float, east: float, north: float) -> Array[float]: - var xy0: = xy(west, north, true) - var xy1: = xy(east, south, true) - return [xy0[0] - xy1[0], xy0[1] - xy1[1]] + var xy0: = xy(west, north, true) + var xy1: = xy(east, south, true) + return [xy0[0] - xy1[0], xy0[1] - xy1[1]] ## Возвращает границы области в градусах.[br] @@ -221,31 +221,31 @@ func get_size_from_degrees(west: float, south: float, east: float, north: float) ## [param lng] - Долгота центра области в градусах (-180...180).[br] ## [param lat] - Широта центра области в градусах (-90...90).[br] func get_degrees_bounds(m: float, lng: float, lat: float) -> Array[float]: - m /= 2.0 - var x_y = xy(lng, lat, true) - return [x_y[0] - m, x_y[1] - m, x_y[0] + m, x_y[1] + m] + m /= 2.0 + var x_y = xy(lng, lat, true) + return [x_y[0] - m, x_y[1] - m, x_y[0] + m, x_y[1] + m] -## Возвращает границы плиток в градусах +## Возвращает границы плиток в градусах. func get_tiles_bounds(tiles: Array[Tile]) -> Array: - var mm = [] - for t in tiles: mm.append(bounds(t).west) - var mmw = mm.min(); mm.clear() + var mm = [] + for t in tiles: mm.append(bounds(t).west) + var mmw = mm.min(); mm.clear() - for t in tiles: mm.append(bounds(t).south) - var mms = mm.min(); mm.clear() + for t in tiles: mm.append(bounds(t).south) + var mms = mm.min(); mm.clear() - for t in tiles: mm.append(bounds(t).east) - var mme = mm.max(); mm.clear() + for t in tiles: mm.append(bounds(t).east) + var mme = mm.max(); mm.clear() - for t in tiles: mm.append(bounds(t).north) - var mmn = mm.max(); mm.clear() + for t in tiles: mm.append(bounds(t).north) + var mmn = mm.max(); mm.clear() - return [mmw, mms, mme, mmn] + return [mmw, mms, mme, mmn] ## Возвращает левую верхнюю долготу и широту плитки.[br] -## [param tile] Плитка.[br] +## [param tile] - Плитка.[br] ## [codeblock] ## >>> ul(Tile(x=0, y=0, z=1)) ## LngLat(lng=-180.0, lat=85.0511287798066) @@ -253,11 +253,11 @@ func get_tiles_bounds(tiles: Array[Tile]) -> Array: ## LngLat(lng=0.0, lat=0.0) ## [/codeblock] func ul(tile: Tile, zoom: int) -> LngLat: - var Z2 = pow(2, zoom) - var lon_deg = tile.x / Z2 * 360.0 - 180.0 - var lat_rad = atan(sinh(PI * (1 - 2 * tile.y / Z2))) - var lat_deg = degrees(lat_rad) - return LngLat.new(lon_deg, lat_deg) + var Z2 = pow(2, zoom) + var lon_deg = tile.x / Z2 * 360.0 - 180.0 + var lat_rad = atan(sinh(PI * (1 - 2 * tile.y / Z2))) + var lat_deg = degrees(lat_rad) + return LngLat.new(lon_deg, lat_deg) ## Возвращает левую верхнюю долготу и широту плитки.[br] @@ -269,32 +269,32 @@ func ul(tile: Tile, zoom: int) -> LngLat: ## LngLat(lng=0.0, lat=0.0) ## [/codeblock] func ul2(tile: Tile) -> Array[float]: - var z2 = pow(2, tile.z) - var lon_deg = tile.x / z2 * 360.0 - 180.0 - var lat_rad = atan(sinh(PI * (1 - 2 * tile.y / z2))) - var lat_deg = degrees(lat_rad) - return [lon_deg, lat_deg] + var z2 = pow(2, tile.z) + var lon_deg = tile.x / z2 * 360.0 - 180.0 + var lat_rad = atan(sinh(PI * (1 - 2 * tile.y / z2))) + var lat_deg = degrees(lat_rad) + return [lon_deg, lat_deg] ## Возвращает граничную область плитки.[br] ## [param tile] - Плитка.[br] func bounds(tile: Tile) -> LngLatBbox: - var z2: = pow(2, tile.z) - var ul_lon_deg = float(tile.x) / z2 * 360.0 - 180.0 - var lr_lon_deg = (float(tile.x) + 1.0) / z2 * 360.0 - 180.0 - var ul_lat_rad = atan(sinh(PI * (1.0 - 2.0 * float(tile.y) / z2))) - var ul_lat_deg = degrees(ul_lat_rad) - var lr_lat_rad = atan(sinh(PI * (1.0 - 2.0 * (float(tile.y) + 1.0) / z2))) - var lr_lat_deg = degrees(lr_lat_rad) - return LngLatBbox.new(ul_lon_deg, lr_lat_deg, lr_lon_deg, ul_lat_deg) + var z2: = pow(2, tile.z) + var ul_lon_deg = float(tile.x) / z2 * 360.0 - 180.0 + var lr_lon_deg = (float(tile.x) + 1.0) / z2 * 360.0 - 180.0 + var ul_lat_rad = atan(sinh(PI * (1.0 - 2.0 * float(tile.y) / z2))) + var ul_lat_deg = degrees(ul_lat_rad) + var lr_lat_rad = atan(sinh(PI * (1.0 - 2.0 * (float(tile.y) + 1.0) / z2))) + var lr_lat_deg = degrees(lr_lat_rad) + return LngLatBbox.new(ul_lon_deg, lr_lat_deg, lr_lon_deg, ul_lat_deg) func truncate_lnglat(lng: float, lat: float) -> Array: - if lng > 180.0: lng = 180.0 - elif lng < -180.0: lng = -180.0 - if lat > 90.0: lat = 90.0 - elif lat < -90.0: lat = -90.0 - return [lng, lat] + if lng > 180.0: lng = 180.0 + elif lng < -180.0: lng = -180.0 + if lat > 90.0: lat = 90.0 + elif lat < -90.0: lat = -90.0 + return [lng, lat] ## Преобразование долготы и широты в веб-меркатор x, y в метрах.[br] @@ -303,159 +303,159 @@ func truncate_lnglat(lng: float, lat: float) -> Array: ## [param truncate] - Нужно ли усекать входные данные до пределов веб-меркатора.[br] ## ([param lat] >= 90) [b]inf[/b] на северном и [b]-inf[/b] на южном полюсе ([param lat] <= -90). func xy(lng: float, lat: float, truncate: bool = false) -> Array[float]: - var ll = truncate_lnglat(lng, lat) if truncate else [lng, lat] - var x: float = RE * radians(lng) - var y: float = 0.0 - if ll[1] <= -90.0: y = float('-inf') - elif ll[1] >= 90.0: y = float('inf') - else: y = RE * log(tan((PI * 0.25) + (0.5 * radians(lat)))) - return [x, y] + var ll = truncate_lnglat(lng, lat) if truncate else [lng, lat] + var x: float = RE * radians(lng) + var y: float = 0.0 + if ll[1] <= -90.0: y = float('-inf') + elif ll[1] >= 90.0: y = float('inf') + else: y = RE * log(tan((PI * 0.25) + (0.5 * radians(lat)))) + return [x, y] ## Преобразование координат веб-меркатора x, y в долготу и широту.[br] ## [param x], [param y] - Координаты веб-меркатора в метрах.[br] ## [param truncate] - Нужно ли усекать или обрезать входные данные до границ веб-меркатора. func lnglat(x: float, y: float, truncate: bool = false) -> LngLat: - var lng: = x * R2D / RE - var lat: = ((PI * 0.5) - 2.0 * atan(exp(-y / RE))) * R2D - var ll: = truncate_lnglat(lng, lat) if truncate else [lng, lat] - return LngLat.new(ll[0], ll[1]) + var lng: = x * R2D / RE + var lat: = ((PI * 0.5) - 2.0 * atan(exp(-y / RE))) * R2D + var ll: = truncate_lnglat(lng, lat) if truncate else [lng, lat] + return LngLat.new(ll[0], ll[1]) -## Соседи плитки. [method neighbors] возвращает до восьми -## соседних плиток, при этом плитки будут опущены, если они не являются действительными, например. -## Tile(-1, -1, z). [param tile]: Плитка или последовательность int -## Может быть либо экземпляр Tile, либо 3 ints, X, Y, Z. +## Соседи плитки. [method neighbors] возвращает до восьми.[br] +## Cоседних плиток, при этом плитки будут опущены, если они не являются действительными, например:[br] +## Tile(-1, -1, z). [param tile]: Плитка или последовательность int.[br] +## Может быть либо экземпляр Tile, либо 3 ints, X, Y, Z.[br] ## [return] Массив ## [codeblock] ## >>> neighbors(Tile(486, 332, 10)) ## [Tile(x=485, y=331, z=10), Tile(x=485, y=332, z=10), Tile(x=485, y=333, z=10), Tile(x=486, y=331, z=10), Tile(x=486, y=333, z=10), Tile(x=487, y=331, z=10), Tile(x=487, y=332, z=10), Tile(x=487, y=333, z=10)] ## [/codeblock] func neighbors(tile: Tile) -> Array: - var tiles: = [] - for i in [-1, 0, 1]: - for j in [-1, 0, 1]: - if i == 0 and j == 0: - continue - var t = Tile.new(tile.x + i, tile.y + j, tile.z) - t.fix_xy() - tiles.append(t) - return tiles + var tiles: = [] + for i in [-1, 0, 1]: + for j in [-1, 0, 1]: + if i == 0 and j == 0: + continue + var t = Tile.new(tile.x + i, tile.y + j, tile.z) + t.fix_xy() + tiles.append(t) + return tiles -## Возвращает все плитки в радиусе [param r] вокруг заданной плитки [param tile] +## Возвращает все плитки в радиусе [param r] вокруг заданной плитки [param tile]. func get_allaround_tiles(tile: Tile, mr: int = 1) -> Array: - var xys = {} - var mi: int = 2 ** tile.z - for r in mr: - for a in range(0, 360, 180 / mi): - var ra = radians(a) - var ix = roundi(r * cos(ra)) + tile.x - var iy = roundi(r * sin(ra)) + tile.y - var t = Tile.new(ix, iy, tile.z) - t.fix_xy() - xys[[t.x, t.y]] = false - var tiles = [] - for txy in xys.keys(): - tiles.append(Tile.new(txy[0], txy[1], tile.z)) - return tiles + var xys = {} + var mi: int = 2 ** tile.z + for r in mr: + for a in range(0, 360, 180 / mi): + var ra = radians(a) + var ix = roundi(r * cos(ra)) + tile.x + var iy = roundi(r * sin(ra)) + tile.y + var t = Tile.new(ix, iy, tile.z) + t.fix_xy() + xys[[t.x, t.y]] = false + var tiles = [] + for txy in xys.keys(): + tiles.append(Tile.new(txy[0], txy[1], tile.z)) + return tiles -## Возвращает все плитки по периметру заданной плитки +## Возвращает все плитки по периметру заданной плитки. func get_perimetr_tiles(tile: Tile, mr: int = 1) -> Array: - var tiles = [] - for i in range(-mr, mr + 1): - for j in range(-mr, mr + 1): - if i == 0 and j == 0: continue - var t = Tile.new(i + tile.x, j + tile.y, tile.z) - t.fix_xy() - tiles.append(t) - return tiles + var tiles = [] + for i in range(-mr, mr + 1): + for j in range(-mr, mr + 1): + if i == 0 and j == 0: continue + var t = Tile.new(i + tile.x, j + tile.y, tile.z) + t.fix_xy() + tiles.append(t) + return tiles ## Получение меркаторской граничной рамки веб-плитки.[br] ## [param tile] - Плитка.[br] -## [i]Эпсилон вычитается из правой границы и прибавляется к нижней[/i] +## [i]Эпсилон вычитается из правой границы и прибавляется к нижней.[/i] func xy_bounds(tile: Tile, zoom: int) -> Bbox: - var tile_size = CE / pow(2, zoom) - var left = tile.x * tile_size - CE / 2 - var right = left + tile_size - var top = CE / 2 - tile.y * tile_size - var bottom = top - tile_size - return Bbox.new(left, bottom, right, top) + var tile_size = CE / pow(2, zoom) + var left = tile.x * tile_size - CE / 2 + var right = left + tile_size + var top = CE / 2 - tile.y * tile_size + var bottom = top - tile_size + return Bbox.new(left, bottom, right, top) func _xy(lng: float, lat: float, truncate=false) -> Array: - var ll: = truncate_lnglat(lng, lat) if truncate else [lng, lat] - var x: float = ll[0] / 360.0 + 0.5 - var sinlat: = sin(radians(ll[1])) - var y: = 0.5 - 0.25 * log((1.0 + sinlat) / (1.0 - sinlat)) / PI - return [x, y] + var ll: = truncate_lnglat(lng, lat) if truncate else [lng, lat] + var x: float = ll[0] / 360.0 + 0.5 + var sinlat: = sin(radians(ll[1])) + var y: = 0.5 - 0.25 * log((1.0 + sinlat) / (1.0 - sinlat)) / PI + return [x, y] -## Получение плитки, содержащей долготу и широту +## Получение плитки, содержащей долготу и широту. ## [param lng], [param lat] - Пара долготы и широты в десятичных градусах.[br] ## [param zoom] - Уровень масштабирования веб-меркатора.[br] ## [param truncate] - Нужно ли усекать вводимые данные до границ веб-меркатора.[br] func create_tile(lng: float, lat: float, zoom: int, truncate: bool=false) -> Tile: - var xy0: = _xy(lng, lat, truncate) - var z2 = pow(2, zoom) + var xy0: = _xy(lng, lat, truncate) + var z2 = pow(2, zoom) - # Чтобы устранить потерю точности при переходе от одной плитки к другой - # и lng/lat, точки в пределах EPSILON от правой стороны плитки - # считаются в следующей за ней плитке. - var tile: = Tile.new() - if xy0[0] <= 0: tile.x = 0 - elif xy0[0] >= 1: tile.x = int(z2 - 1) - else: tile.x = int(floor((xy0[0] + EPSILON) * z2)) + # Чтобы устранить потерю точности при переходе от одной плитки к другой + # и lng/lat, точки в пределах EPSILON от правой стороны плитки + # считаются в следующей за ней плитке. + var tile: = Tile.new() + if xy0[0] <= 0: tile.x = 0 + elif xy0[0] >= 1: tile.x = int(z2 - 1) + else: tile.x = int(floor((xy0[0] + EPSILON) * z2)) - if xy0[1] <= 0: tile.y = 0 - elif xy0[1] >= 1: tile.y = int(z2 - 1) - else: tile.y = int(floor((xy0[1] + EPSILON) * z2)) - tile.z = zoom - tile.lng = lng - tile.lat = lat - return tile + if xy0[1] <= 0: tile.y = 0 + elif xy0[1] >= 1: tile.y = int(z2 - 1) + else: tile.y = int(floor((xy0[1] + EPSILON) * z2)) + tile.z = zoom + tile.lng = lng + tile.lat = lat + return tile ## Получение четверного ключа плитки.[br] -## [param tile] - Плитка +## [param tile] - Плитка. func quadkey(tile: Tile, zoom: int) -> String: - var rs: = '' - for z in range(zoom, 0, -1): - var digit = 0 - var mask = 1 << (z - 1) - if tile.x & mask: - digit += 1 - if tile.y & mask: - digit += 2 - rs += str(digit) - return rs + var rs: = '' + for z in range(zoom, 0, -1): + var digit = 0 + var mask = 1 << (z - 1) + if tile.x & mask: + digit += 1 + if tile.y & mask: + digit += 2 + rs += str(digit) + return rs ## Получение плитки, соответствующей четверному ключу.[br] ## [param qk] - Строка четверного ключа. func quadkey_to_tile(qk: Array) -> Tile: - var tile: = Tile.new() - if len(qk) == 0: return tile - var i = 0 - qk.reverse() - for digit in qk: - var mask = 1 << i - i += 1 - if digit == '1': - tile.x |= mask - elif digit == '2': - tile.y |= mask - elif digit == '3': - tile.x |= mask - tile.y |= mask - elif digit == '0': - continue - else: - push_error('Не ожидаемый четырёхзначный знак: \"%s\"' % digit) - tile.z = i - return tile + var tile: = Tile.new() + if len(qk) == 0: return tile + var i = 0 + qk.reverse() + for digit in qk: + var mask = 1 << i + i += 1 + if digit == '1': + tile.x |= mask + elif digit == '2': + tile.y |= mask + elif digit == '3': + tile.x |= mask + tile.y |= mask + elif digit == '0': + continue + else: + push_error('Не ожидаемый четырёхзначный знак: \"%s\"' % digit) + tile.z = i + return tile ## Получение плиток, перекрываемых географической рамкой.[br] @@ -465,27 +465,27 @@ func quadkey_to_tile(qk: Array) -> Tile: ## [i]В параметрах south и east используется небольшой эпсилон, чтобы ## функция выдавала ровно одну плитку при задании границ этой же плитки.[/i] func create_tiles(west: float, south: float, east: float, north: float, zooms) -> Array[Tile]: - var bboxes = [[west, south, east, north]] if west <= east else [[-180.0, south, east, north], [west, south, 180.0, north]] - var tiles = [] - if not is_instance_of(zooms, TYPE_ARRAY): - zooms = [zooms] - for box in bboxes: - var w = box[0] - var s = box[1] - var e = box[2] - var n = box[3] - # Зажим граничных значений. - w = max(-180.0, w) - s = max(MAX_SOUTH, s) - e = min(180.0, e) - n = min(85.051129, n) - for z in zooms: - var ul_tile = create_tile(w, n, z) - var lr_tile = create_tile(e - LL_EPSILON, s + LL_EPSILON, z) - for i in range(ul_tile.x, lr_tile.x + 1): - for j in range(ul_tile.y, lr_tile.y + 1): - tiles.append(Tile.new(i, j, z)) - return tiles + var bboxes = [[west, south, east, north]] if west <= east else [[-180.0, south, east, north], [west, south, 180.0, north]] + var tiles = [] + if not is_instance_of(zooms, TYPE_ARRAY): + zooms = [zooms] + for box in bboxes: + var w = box[0] + var s = box[1] + var e = box[2] + var n = box[3] + # Зажим граничных значений. + w = max(-180.0, w) + s = max(MAX_SOUTH, s) + e = min(180.0, e) + n = min(85.051129, n) + for z in zooms: + var ul_tile = create_tile(w, n, z) + var lr_tile = create_tile(e - LL_EPSILON, s + LL_EPSILON, z) + for i in range(ul_tile.x, lr_tile.x + 1): + for j in range(ul_tile.y, lr_tile.y + 1): + tiles.append(Tile.new(i, j, z)) + return tiles ## Получение родителя плитки. Родительская плитка - это плитка на один уровень масштабирования ниже, которая @@ -500,28 +500,28 @@ func create_tiles(west: float, south: float, east: float, north: float, zooms) - ## Tile(x=0, y=0, z=0) ## [/codeblock] func parent(tile: Tile, zoom=null) -> Tile: - if tile.z == 0: return null + if tile.z == 0: return null - assert((zoom != null) and (tile.z > zoom or zoom == int(zoom)), 'Масштаб (zoom) должен быть целым числом и меньше масштаба входной плитки (tile).') - assert(tile.x == int(tile.x) and tile.y == int(tile.y) and tile.z == int(tile.z), 'Родитель нецелочисленной плитки (tile) является неуточнённым') + assert((zoom != null) and (tile.z > zoom or zoom == int(zoom)), 'Масштаб (zoom) должен быть целым числом и меньше масштаба входной плитки (tile).') + assert(tile.x == int(tile.x) and tile.y == int(tile.y) and tile.z == int(tile.z), 'Родитель нецелочисленной плитки (tile) является неуточнённым') - var target_zoom = tile.z - 1 if zoom == null else zoom + var target_zoom = tile.z - 1 if zoom == null else zoom - # Алгоритм, в значительной степени вдохновленный https://github.com/mapbox/tilebelt - var return_tile = Tile.new(tile.x, tile.y, tile.z) - while return_tile.z > target_zoom: - tile.x = return_tile.x - tile.y = return_tile.y - tile.z = return_tile.z - if tile.x % 2 == 0 and tile.y % 2 == 0: - return_tile = Tile.new(tile.x / 2, tile.y / 2, tile.z - 1) - elif tile.x % 2 == 0: - return_tile = Tile.new(tile.x / 2, (tile.y - 1) / 2, tile.z - 1) - elif not tile.x % 2 == 0 and tile.y % 2 == 0: - return_tile = Tile.new((tile.x - 1) / 2, tile.y / 2, tile.z - 1) - else: - return_tile = Tile.new((tile.x - 1) / 2, (tile.y - 1) / 2, tile.z - 1) - return return_tile + # Алгоритм, в значительной степени вдохновленный https://github.com/mapbox/tilebelt + var return_tile = Tile.new(tile.x, tile.y, tile.z) + while return_tile.z > target_zoom: + tile.x = return_tile.x + tile.y = return_tile.y + tile.z = return_tile.z + if tile.x % 2 == 0 and tile.y % 2 == 0: + return_tile = Tile.new(tile.x / 2, tile.y / 2, tile.z - 1) + elif tile.x % 2 == 0: + return_tile = Tile.new(tile.x / 2, (tile.y - 1) / 2, tile.z - 1) + elif not tile.x % 2 == 0 and tile.y % 2 == 0: + return_tile = Tile.new((tile.x - 1) / 2, tile.y / 2, tile.z - 1) + else: + return_tile = Tile.new((tile.x - 1) / 2, (tile.y - 1) / 2, tile.z - 1) + return return_tile ## Получение дочерних элементов плитки. Дочерние элементы упорядочены: @@ -538,16 +538,16 @@ func parent(tile: Tile, zoom=null) -> Tile: ## [Tile(x=0, y=0, z=2), Tile(x=0, y=1, z=2), Tile(x=0, y=2, z=2), Tile(x=0, y=3, z=2), ...] ## [/codeblock] func children(tile: Tile, zoom: int) -> Array: - var target_zoom = zoom if zoom != null else tile.z + 1 - var tiles: Array[Tile] = [tile] - while tiles[0].z >= target_zoom: - tile = tiles.pop_at(0) - tiles += [ - Tile.new(tile.x * 2, tile.y * 2, tile.z + 1), - Tile.new(tile.x * 2 + 1, tile.y * 2, tile.z + 1), - Tile.new(tile.x * 2 + 1, tile.y * 2 + 1, tile.z + 1), - Tile.new(tile.x * 2, tile.y * 2 + 1, tile.z + 1) ] - return tiles + var target_zoom = zoom if zoom != null else tile.z + 1 + var tiles: Array[Tile] = [tile] + while tiles[0].z >= target_zoom: + tile = tiles.pop_at(0) + tiles += [ + Tile.new(tile.x * 2, tile.y * 2, tile.z + 1), + Tile.new(tile.x * 2 + 1, tile.y * 2, tile.z + 1), + Tile.new(tile.x * 2 + 1, tile.y * 2 + 1, tile.z + 1), + Tile.new(tile.x * 2, tile.y * 2 + 1, tile.z + 1) ] + return tiles ## Проверяется, есть ли в [param merge_set] 4 плитки, которые могут быть объединены. @@ -555,52 +555,52 @@ func children(tile: Tile, zoom: int) -> Array: ## число, указывающее, были ли они объединены. При многократном применяя ## [method merge], можно упростить набор плиток. func merge(merge_set) -> Array: - var upwards_merge: = {} - for tile in merge_set.keys(): - var tile_parent = parent(tile) - if not upwards_merge.has(tile_parent): - upwards_merge[tile_parent] = {} - upwards_merge[tile_parent].update({tile: false}) - var current_tileset: = [] - var changed: = false - for supertile in upwards_merge.keys(): - var tile_children = upwards_merge[supertile] - if len(tile_children) == 4: - current_tileset += [supertile] - changed = true - else: - current_tileset += [tile_children] - return [current_tileset, changed] + var upwards_merge: = {} + for tile in merge_set.keys(): + var tile_parent = parent(tile) + if not upwards_merge.has(tile_parent): + upwards_merge[tile_parent] = {} + upwards_merge[tile_parent].update({tile: false}) + var current_tileset: = [] + var changed: = false + for supertile in upwards_merge.keys(): + var tile_children = upwards_merge[supertile] + if len(tile_children) == 4: + current_tileset += [supertile] + changed = true + else: + current_tileset += [tile_children] + return [current_tileset, changed] ## Максимально уменьшает размер набора плиток путем слияния листьев с родителями. ## [param tiles] - Последовательность плиток для слияния. func simplify(tiles: Array) -> Array: - # Проверьте, существует ли уже плитка и ее родитель. - # Убедитесь, что плитки отсортированы по масштабу, поэтому родительские - # плитки встречаются первыми. Если да, отбросьте дочернюю плитку - # (она покрыта родительской). - var root_set = {} - tiles.sort_custom(func(a, b): return a[2] < b[2]) - for tile in tiles: - var is_new_tile: bool = true - var supertiles = [] - for i in tile.z: - supertiles.append(parent(tile, i)) - for supertile in supertiles: - if supertile in root_set: - is_new_tile = false - continue - if is_new_tile: - root_set.update({tile: false}) + # Проверьте, существует ли уже плитка и ее родитель. + # Убедитесь, что плитки отсортированы по масштабу, поэтому родительские + # плитки встречаются первыми. Если да, отбросьте дочернюю плитку + # (она покрыта родительской). + var root_set = {} + tiles.sort_custom(func(a, b): return a[2] < b[2]) + for tile in tiles: + var is_new_tile: bool = true + var supertiles = [] + for i in tile.z: + supertiles.append(parent(tile, i)) + for supertile in supertiles: + if supertile in root_set: + is_new_tile = false + continue + if is_new_tile: + root_set.update({tile: false}) - # Повторять слияние до тех пор, пока дальнейшее упрощение перестанет быть невозможным. - var is_merging: bool = true - while is_merging: - var root_set_is_merging = merge(root_set) - root_set = root_set_is_merging[0] - is_merging = root_set_is_merging[1] - return root_set + # Повторять слияние до тех пор, пока дальнейшее упрощение перестанет быть невозможным. + var is_merging: bool = true + while is_merging: + var root_set_is_merging = merge(root_set) + root_set = root_set_is_merging[0] + is_merging = root_set_is_merging[1] + return root_set ## Получение наименьшей плитки, содержащей географическую границу. @@ -610,139 +610,139 @@ func simplify(tiles: Array) -> Array: ## севера в десятичных градусах. func bounding_tile(bbox: Bbox, truncate: bool = false) -> Tile: - if truncate: - var w_s = truncate_lnglat(bbox.left, bbox.bottom) - bbox.left = w_s[0] - bbox.bottom = w_s[1] - var e_n = truncate_lnglat(bbox.right, bbox.north) - bbox.right = e_n[0] - bbox.top = e_n[1] + if truncate: + var w_s = truncate_lnglat(bbox.left, bbox.bottom) + bbox.left = w_s[0] + bbox.bottom = w_s[1] + var e_n = truncate_lnglat(bbox.right, bbox.north) + bbox.right = e_n[0] + bbox.top = e_n[1] - bbox.right -= LL_EPSILON - bbox.bottom += LL_EPSILON + bbox.right -= LL_EPSILON + bbox.bottom += LL_EPSILON - var tmin = create_tile(bbox.left, bbox.top, 32) - var tmax = create_tile(bbox.right, bbox.bottom, 32) - assert(tmin.is_valid()) - assert(tmax.is_valid()) + var tmin = create_tile(bbox.left, bbox.top, 32) + var tmax = create_tile(bbox.right, bbox.bottom, 32) + assert(tmin.is_valid()) + assert(tmax.is_valid()) - var cell = [tmin.x, tmin.y, tmax.x, tmax.y] - var z = _get_bbox_zoom(cell) + var cell = [tmin.x, tmin.y, tmax.x, tmax.y] + var z = _get_bbox_zoom(cell) - if z == 0: - return Tile.new() + if z == 0: + return Tile.new() - var x = rshift(cell[0], 32 - z) - var y = rshift(cell[1], 32 - z) + var x = rshift(cell[0], 32 - z) + var y = rshift(cell[1], 32 - z) - return Tile.new(x, y, z) + return Tile.new(x, y, z) func _get_bbox_zoom(bbox: Array): - for z in MAX_ZOOM: - var mask: int = 1 << (32 - (z + 1)) - if (bbox[0] & mask) != (bbox[2] & mask) or (bbox[1] & mask) != (bbox[3] & mask): - return z - return MAX_ZOOM + for z in MAX_ZOOM: + var mask: int = 1 << (32 - (z + 1)) + if (bbox[0] & mask) != (bbox[2] & mask) or (bbox[1] & mask) != (bbox[3] & mask): + return z + return MAX_ZOOM ## Получение GeoJSON-функции, соответствующей плитке.[br] ## [param tile] - Плитка.[br] ## [param fid] - Идентификатор объекта.[br] ## [param props] - Необязательные дополнительные свойства признака.[br] -## [param projected] - Можно создать нестандартный веб-меркатор GeoJSON передав 'mercator'[br] +## [param projected] - Можно создать нестандартный веб-меркатор GeoJSON передав 'mercator'.[br] ## [param buffer] - Необязательное буферное расстояние для полигона GeoJSON.[br] ## [param precision] - Координаты GeoJSON будут усечены до данного количества знаков после запятой.[br] func feature(tile: Tile, fid:String = 'null', props=null, projected='geographic', buffer=null) -> Dictionary: - var west_south_east_north = bounds(tile).as_array() - var west = west_south_east_north[0] - var south = west_south_east_north[1] - var east = west_south_east_north[2] - var north = west_south_east_north[3] - if projected == 'mercator': - var west_south = xy(west, south, false) - west = west_south[0] - south = west_south[1] - var east_north = xy(east, north, false) - east = east_north[0] - north = east_north[1] + var west_south_east_north = bounds(tile).as_array() + var west = west_south_east_north[0] + var south = west_south_east_north[1] + var east = west_south_east_north[2] + var north = west_south_east_north[3] + if projected == 'mercator': + var west_south = xy(west, south, false) + west = west_south[0] + south = west_south[1] + var east_north = xy(east, north, false) + east = east_north[0] + north = east_north[1] - if buffer: - west -= buffer - south -= buffer - east += buffer - north += buffer + if buffer: + west -= buffer + south -= buffer + east += buffer + north += buffer - west = round(west) - south = round(south) - east = round(east) - north = round(north) + west = round(west) + south = round(south) + east = round(east) + north = round(north) - var bbox: = [min(west, east), min(south, north), max(west, east), max(south, north)] - var geom: = {'type': 'Polygon', 'coordinates': [[[west, south], [west, north], [east, north], [east, south], [west, south]]] } - var xyz: = str(tile) - var feat: = { - 'type': 'Feature', - 'bbox': bbox, - 'id': xyz, - 'geometry': geom, - 'properties': {'title': 'XYZ tile %s' % xyz} } + var bbox: = [min(west, east), min(south, north), max(west, east), max(south, north)] + var geom: = {'type': 'Polygon', 'coordinates': [[[west, south], [west, north], [east, north], [east, south], [west, south]]] } + var xyz: = str(tile) + var feat: = { + 'type': 'Feature', + 'bbox': bbox, + 'id': xyz, + 'geometry': geom, + 'properties': {'title': 'XYZ tile %s' % xyz} } - if props: - feat['properties'].update(props) + if props: + feat['properties'].update(props) - if fid != 'null': - feat['id'] = fid + if fid != 'null': + feat['id'] = fid - return feat + return feat func isinstance(obj, types: Array) -> bool: - for type in types: - if is_instance_of(obj, type): - return true - return false + for type in types: + if is_instance_of(obj, type): + return true + return false ## Все кортежи координат из геометрии, объекта или коллекции.[br] ## [param lng] - Долгота.[br] ## [param lat] - широта.[br] func _coords(obj) -> Array: - var coordinates = [] - if is_instance_of(obj, TYPE_ARRAY): - coordinates = obj - elif 'features' in obj: - for feat in obj['features']: - coordinates = feat['geometry']['coordinates'] - elif 'geometry' in obj: - coordinates = obj['geometry']['coordinates'] - else: - coordinates = obj.get('coordinates', obj) + var coordinates = [] + if is_instance_of(obj, TYPE_ARRAY): + coordinates = obj + elif 'features' in obj: + for feat in obj['features']: + coordinates = feat['geometry']['coordinates'] + elif 'geometry' in obj: + coordinates = obj['geometry']['coordinates'] + else: + coordinates = obj.get('coordinates', obj) - var v: = [] - for e in coordinates: - if isinstance(e, [TYPE_FLOAT, TYPE_INT]): - v.append(coordinates) - break - else: - for f in _coords(e): - v.append([f[0], f[1]]) - return v + var v: = [] + for e in coordinates: + if isinstance(e, [TYPE_FLOAT, TYPE_INT]): + v.append(coordinates) + break + else: + for f in _coords(e): + v.append([f[0], f[1]]) + return v func _func1(bbox: Array, coords: Array) -> Array[float]: - var w = bbox[0] - var s = bbox[1] - var e = bbox[2] - var n = bbox[3] - var lng = coords[0] - var lat = coords[1] - return [min(w, lng), min(s, lat), max(e, lng), max(n, lat)] + var w = bbox[0] + var s = bbox[1] + var e = bbox[2] + var n = bbox[3] + var lng = coords[0] + var lat = coords[1] + return [min(w, lng), min(s, lat), max(e, lng), max(n, lat)] ## Возвращает граничную область объекта GeoJSON.[br] ## [param obj] - Отображение, геометрия GeoJSON, объект или коллекция объектов.[br] func geojson_bounds(obj) -> LngLatBbox: - var coords: Array = _coords(obj) - var wsen = coords.reduce(_func1, [180.0, 90.0, -180.0, -90.0]) - return LngLatBbox.new(wsen[0], wsen[1], wsen[2], wsen[3]) + var coords: Array = _coords(obj) + var wsen = coords.reduce(_func1, [180.0, 90.0, -180.0, -90.0]) + return LngLatBbox.new(wsen[0], wsen[1], wsen[2], wsen[3]) diff --git a/scripts/modbus.gd b/scripts/modbus.gd index b7fe105d..08ed3ed9 100644 --- a/scripts/modbus.gd +++ b/scripts/modbus.gd @@ -1,131 +1,131 @@ -extends Node +class_name mod_bus extends Node -const ONLINE_TIMEOUT = 5.0 ## Время ожидания пакета от ячейки, секунды +const ONLINE_TIMEOUT = 5.0 ## Время ожидания пакета от ячейки, секунды. class ModbusClient: - ## Состояние автомата выполнения команды - enum CmdState { - DONE, ## Команда выполнена - FAIL, ## Команда не выполнена - SEND, ## Буфер готов для отправки - WAIT ## Ожидание выполнения - } + ## Состояние автомата выполнения команды. + enum CmdState { + DONE, ## Команда выполнена. + FAIL, ## Команда не выполнена. + SEND, ## Буфер готов для отправки. + WAIT ## Ожидание выполнения. + } - var name: String - var unit: = 1 ## Адрес устройства Modbus - var cmd_state: = CmdState.DONE ## Код текущей команды - var tx_data: PackedByteArray ## Данные для отправки - var online: = false ## Флаг. Если установлен, устройство на связи. Активный уровень - [b]true[/b]. - var tick_rx: = 0 ## Время приёма крайнего сообщения - var tick_tx: = 0 ## Время отправки крайнего сообщения + var name: String + var unit: = 1 ## Адрес устройства Modbus. + var cmd_state: = CmdState.DONE ## Код текущей команды. + var tx_data: PackedByteArray ## Данные для отправки. + var online: = false ## Флаг. Если установлен, устройство на связи. Активный уровень - [b]true[/b]. + var tick_rx: = 0 ## Время приёма крайнего сообщения. + var tick_tx: = 0 ## Время отправки крайнего сообщения. - static var json_conv: = JSON.new() + static var json_conv: = JSON.new() - signal command_done() ## Вызывается при успешном выполнении команды - signal command_fail() ## Вызывается при возникновении ошибки во время выполнения команды - signal line_changed() ## Вызывается при смене состояния связи с ячейкой - signal data_received() ## Вызывается при приёме сообщения, до его разбора - signal parse_failed(msg: String) ## Вызывается при возникновении ошибки во время разбора пакета. Передаёт сообщение об ошибке + signal command_done() ## Вызывается при успешном выполнении команды. + signal command_fail() ## Вызывается при возникновении ошибки во время выполнения команды. + signal line_changed() ## Вызывается при смене состояния связи с ячейкой. + signal data_received() ## Вызывается при приёме сообщения, до его разбора. + signal parse_failed(msg: String) ## Вызывается при возникновении ошибки во время разбора пакета. Передаёт сообщение об ошибке. - signal holding_received(addr: int, data: Array) ## Вызывается при получении сообщения c данными из регистров хранения - signal input_received(addr: int, data: Array) ## Вызывается при получении сообщения c данными из регистров входов - signal info_received(msg: String) ## Вызывается при получении информационного сообщения - signal except_received(msg: String) ## Вызывается при получении сообщения об исключительной ситуации + signal holding_received(addr: int, data: Array) ## Вызывается при получении сообщения c данными из регистров хранения. + signal input_received(addr: int, data: Array) ## Вызывается при получении сообщения c данными из регистров входов. + signal info_received(msg: String) ## Вызывается при получении информационного сообщения. + signal except_received(msg: String) ## Вызывается при получении сообщения об исключительной ситуации. - func _init(nm: String, u: int = 1): - unit = u - name = nm + func _init(nm: String, u: int = 1): + unit = u + name = nm - ## Изменяет состояние. Вызывать периодически[br] - ## [param tick] - Текущщее время в мс[br] - func process(tick: int): - if online and ((tick - tick_rx) > ONLINE_TIMEOUT): - online = false - emit_signal('line_changed') - if (cmd_state == CmdState.WAIT) or (cmd_state == CmdState.SEND): - cmd_state = CmdState.FAIL - emit_signal('command_fail') - var rc = Error.ERR_BUSY - if cmd_state == CmdState.SEND: - rc = Error.OK - tick_tx = tick - cmd_state = CmdState.WAIT - return rc + ## Изменяет состояние. Вызывать периодически.[br] + ## [param tick] - Текущщее время в мс.[br] + func process(tick: int): + if online and ((tick - tick_rx) > ONLINE_TIMEOUT): + online = false + emit_signal('line_changed') + if (cmd_state == CmdState.WAIT) or (cmd_state == CmdState.SEND): + cmd_state = CmdState.FAIL + emit_signal('command_fail') + var rc = Error.ERR_BUSY + if cmd_state == CmdState.SEND: + rc = Error.OK + tick_tx = tick + cmd_state = CmdState.WAIT + return rc - ## Производит разбор принятых данных. Вызывать при приёме данных на сокете[br] - ## [param data] - Данные принятые на сокете[br] - ## [param tick] - Текущщее время в мс[br] - func parse(data: PackedByteArray, tick: int) -> bool: - tick_rx = tick - if not online: - online = true - emit_signal('line_changed') - var utf8_data = data.get_string_from_utf8() - if Error.OK == json_conv.parse(utf8_data): - emit_signal('data_received') - var message: Dictionary = json_conv.get_data() - if message.has('code') and message.has('data'): - var rc: = true - var message_code = message['code'] - var message_data = message['data'] - match message_code: - 'read_input': - var addr = message_data[0] - var regs = message_data.slice(1) - emit_signal('input_received', addr, regs) - 'read_holding': - var addr = message_data[0] - var regs = message_data.slice(1) - emit_signal('holding_received', addr, regs) - 'info': - var msg = message['data'] - emit_signal('info_received', msg) - 'exception': - var msg = message['data'] - emit_signal('except_received', msg) - rc = false - _: - var msg: String = 'код команды \"%s\" - не распознан' % message_code - emit_signal('parse_failed', msg) - rc = false - if (cmd_state == CmdState.WAIT) and rc: - cmd_state = CmdState.DONE - emit_signal('command_done') - elif (cmd_state == CmdState.WAIT) and (not rc): - cmd_state = CmdState.FAIL - emit_signal('command_fail') - else: - var message = json_conv.get_error_message() - emit_signal('parse_failed', message) - return true + ## Производит разбор принятых данных. Вызывать при приёме данных на сокете.[br] + ## [param data] - Данные принятые на сокете.[br] + ## [param tick] - Текущщее время в мс.[br] + func parse(data: PackedByteArray, tick: int) -> bool: + tick_rx = tick + if not online: + online = true + emit_signal('line_changed') + var utf8_data = data.get_string_from_utf8() + if Error.OK == json_conv.parse(utf8_data): + emit_signal('data_received') + var message: Dictionary = json_conv.get_data() + if message.has('code') and message.has('data'): + var rc: = true + var message_code = message['code'] + var message_data = message['data'] + match message_code: + 'read_input': + var addr = message_data[0] + var regs = message_data.slice(1) + emit_signal('input_received', addr, regs) + 'read_holding': + var addr = message_data[0] + var regs = message_data.slice(1) + emit_signal('holding_received', addr, regs) + 'info': + var msg = message['data'] + emit_signal('info_received', msg) + 'exception': + var msg = message['data'] + emit_signal('except_received', msg) + rc = false + _: + var msg: String = 'код команды \"%s\" - не распознан' % message_code + emit_signal('parse_failed', msg) + rc = false + if (cmd_state == CmdState.WAIT) and rc: + cmd_state = CmdState.DONE + emit_signal('command_done') + elif (cmd_state == CmdState.WAIT) and (not rc): + cmd_state = CmdState.FAIL + emit_signal('command_fail') + else: + var message = json_conv.get_error_message() + emit_signal('parse_failed', message) + return true - ## Отправляет команду "записать в регистры".[br] - ## [param addr] - Начальный адрес регистров.[br] - ## [param count] - Данные для записи в регистры от начального адреса.[br] - func write(addr: int, data: Array) -> bool: - if cmd_state != CmdState.DONE: return false - var json_dict = {'code': 'write', 'data': [addr] + data} - tx_data = JSON.stringify(json_dict).to_utf8_buffer() - cmd_state = CmdState.SEND - return true + ## Отправляет команду "записать в регистры".[br] + ## [param addr] - Начальный адрес регистров.[br] + ## [param count] - Данные для записи в регистры от начального адреса.[br] + func write(addr: int, data: Array) -> bool: + if cmd_state != CmdState.DONE: return false + var json_dict = {'code': 'write', 'data': [addr] + data} + tx_data = JSON.stringify(json_dict).to_utf8_buffer() + cmd_state = CmdState.SEND + return true - ## Отправляет команду "читать регистры хранения".[br] - ## [param addr] - Начальный адрес регистров.[br] - ## [param count] - Количество регистров от начального адреса.[br] - func read_holdind(addr: int, count: int = 1) -> bool: - if cmd_state != CmdState.DONE: return false - var json_dict = {'code': 'read_holding', 'data': [addr, count]} - tx_data = JSON.stringify(json_dict).to_utf8_buffer() - cmd_state = CmdState.SEND - return true + ## Отправляет команду "читать регистры хранения".[br] + ## [param addr] - Начальный адрес регистров.[br] + ## [param count] - Количество регистров от начального адреса.[br] + func read_holdind(addr: int, count: int = 1) -> bool: + if cmd_state != CmdState.DONE: return false + var json_dict = {'code': 'read_holding', 'data': [addr, count]} + tx_data = JSON.stringify(json_dict).to_utf8_buffer() + cmd_state = CmdState.SEND + return true - ## Отправляет команду "читать регистры входов".[br] - ## [param addr] - Начальный адрес регистров.[br] - ## [param count] - Количество регистров от начального адреса.[br] - func read_input(addr: int, count: int = 1) -> bool: - if cmd_state != CmdState.DONE: return false - var json_dict = {'code': 'read_input', 'data': [addr, count]} - tx_data = JSON.stringify(json_dict).to_utf8_buffer() - cmd_state = CmdState.SEND - return true + ## Отправляет команду "читать регистры входов".[br] + ## [param addr] - Начальный адрес регистров.[br] + ## [param count] - Количество регистров от начального адреса.[br] + func read_input(addr: int, count: int = 1) -> bool: + if cmd_state != CmdState.DONE: return false + var json_dict = {'code': 'read_input', 'data': [addr, count]} + tx_data = JSON.stringify(json_dict).to_utf8_buffer() + cmd_state = CmdState.SEND + return true diff --git a/scripts/repnetwork.gd b/scripts/repnetwork.gd index ffd8a7ff..3dcf65a2 100644 --- a/scripts/repnetwork.gd +++ b/scripts/repnetwork.gd @@ -1,31 +1,31 @@ -extends Node +class_name rep_network extends Node const PROTOCOL_TYPES: = {'yau07tx': yau07.YaU07, 'jsontx': capsrpb.CapsRpb, 'udp-json-mbcs': modbus.ModbusClient} const TCP_PROTO: = {'5p28': tcp5p28.TCP5P28} class Socket extends PacketPeerUDP: - func send_to(addr, data): - self.set_dest_address(addr[0], addr[1]) - self.put_packet(data) + func send_to(addr, data): + self.set_dest_address(addr[0], addr[1]) + self.put_packet(data) class SocketTCP extends TCPServer: - const RX_TIMEOUT: int = 1000 # Время сброса счётчика принятия части пакета - var peerstream: = PacketPeerStream.new() - var unit_key: Array # Клюс юнита - var head_len: int = 8 # Длинна заголовка команды - var len_place: int = 4 # Номер начального байта с длинной блока данных - var type_len: int = 4 # Количество байт в размере длинны - var rx_tick: int # Время приёма последнего пакета - var rx_all: bool = true # Флаг, что пакет принят полностью - var rx_len: int # Количество принятых байт - var rx_data: PackedByteArray # Принятые данные - var data_len: int # Размер ожидаемых данных + const RX_TIMEOUT: int = 1000 ## Время сброса счётчика принятия части пакета. + var peerstream: = PacketPeerStream.new() + var unit_key: Array ## Клюс юнита. + var head_len: int = 8 ## Длинна заголовка команды. + var len_place: int = 4 ## Номер начального байта с длинной блока данных. + var type_len: int = 4 ## Количество байт в размере длинны. + var rx_tick: int ## Время приёма последнего пакета. + var rx_all: bool = true ## Флаг, что пакет принят полностью. + var rx_len: int ## Количество принятых байт. + var rx_data: PackedByteArray ## Принятые данные. + var data_len: int ## Размер ожидаемых данных. - func send_to(data): - var peer = peerstream.get_stream_peer() - peer.put_data(data) + func send_to(data): + var peer = peerstream.get_stream_peer() + peer.put_data(data) var poll_sockets: Array[Socket] @@ -38,129 +38,129 @@ var logger_page: Node func create_socket(nm) -> Socket: - var addr = repsettings.UnitProfiles[nm][1] - var port = repsettings.UnitProfiles[nm][2] - var broad = repsettings.UnitProfiles[nm][3] - var bind = repsettings.UnitProfiles[nm][4] - var sock: = Socket.new() - sock.set_broadcast_enabled(broad) - var rc = Error.OK if not bind else sock.bind(port, addr) - var errlevel = log.INFO if rc == Error.OK else log.ERROR - log.message(errlevel, '%s: сокет:%s:%d привязанный:%s %s' % [nm, addr, port, ['нет', 'да'][int(bind)], error_string(rc)]) - return sock + var addr = repsettings.UnitProfiles[nm][1] + var port = repsettings.UnitProfiles[nm][2] + var broad = repsettings.UnitProfiles[nm][3] + var bind = repsettings.UnitProfiles[nm][4] + var sock: = Socket.new() + sock.set_broadcast_enabled(broad) + var rc = Error.OK if not bind else sock.bind(port, addr) + var errlevel = log.INFO if rc == Error.OK else log.ERROR + log.message(errlevel, '%s: сокет:%s:%d привязанный:%s %s' % [nm, addr, port, ['нет', 'да'][int(bind)], error_string(rc)]) + return sock func create_tcpsocket(nm) -> SocketTCP: - var addr = repsettings.UnitProfiles[nm][1] - var port = repsettings.UnitProfiles[nm][2] - var sock = SocketTCP.new() - sock.unit_key = repsettings.get_unit_key(nm) - sock.listen(port, addr) - log.message(log.INFO, '%s: сокет:%s:%d привязанный:' % [nm, addr, port]) - return sock + var addr = repsettings.UnitProfiles[nm][1] + var port = repsettings.UnitProfiles[nm][2] + var sock = SocketTCP.new() + sock.unit_key = repsettings.get_unit_key(nm) + sock.listen(port, addr) + log.message(log.INFO, '%s: сокет:%s:%d привязанный:' % [nm, addr, port]) + return sock func _ready() -> void: - log.info('Модуль работы с сетью готов') - poll_sockets.append(create_socket('уарэп-яу07-частный')) - poll_sockets.append(create_socket('уарэп-яу07-общий')) - poll_sockets.append(create_socket('уарэп-капсрпб-пол')) - sock_unicast = poll_sockets[0] - sock_capsrpb = poll_sockets[2] + log.info('Модуль работы с сетью готов') + poll_sockets.append(create_socket('уарэп-яу07-частный')) + poll_sockets.append(create_socket('уарэп-яу07-общий')) + poll_sockets.append(create_socket('уарэп-капсрпб-пол')) + sock_unicast = poll_sockets[0] + sock_capsrpb = poll_sockets[2] - for unit_name in repsettings.UnitProfiles: - var setting = repsettings.UnitProfiles[unit_name] - var proto = setting[0] - if proto in PROTOCOL_TYPES: - var unit = PROTOCOL_TYPES[proto].new(unit_name) - var unit_key = repsettings.get_unit_key(unit_name) - units[unit_key] = unit - if logger_page: - unit.connect('line_changed', Callable(logger_page, 'on_line_changed').bind(unit_key)) - unit.connect('command_fail', Callable(logger_page, 'on_command_fail').bind(unit_key)) + for unit_name in repsettings.UnitProfiles: + var setting = repsettings.UnitProfiles[unit_name] + var proto = setting[0] + if proto in PROTOCOL_TYPES: + var unit = PROTOCOL_TYPES[proto].new(unit_name) + var unit_key = repsettings.get_unit_key(unit_name) + units[unit_key] = unit + if logger_page: + unit.connect('line_changed', Callable(logger_page, 'on_line_changed').bind(unit_key)) + unit.connect('command_fail', Callable(logger_page, 'on_command_fail').bind(unit_key)) - if proto in TCP_PROTO: - var unit = TCP_PROTO[proto].new(unit_name) - var unit_key = repsettings.get_unit_key(unit_name) - units_tcp[unit_key] = unit - if logger_page: - unit.connect('line_changed', Callable(logger_page, 'on_line_changed').bind(unit_key)) - unit.connect('command_fail', Callable(logger_page, 'on_command_fail').bind(unit_key)) + if proto in TCP_PROTO: + var unit = TCP_PROTO[proto].new(unit_name) + var unit_key = repsettings.get_unit_key(unit_name) + units_tcp[unit_key] = unit + if logger_page: + unit.connect('line_changed', Callable(logger_page, 'on_line_changed').bind(unit_key)) + unit.connect('command_fail', Callable(logger_page, 'on_command_fail').bind(unit_key)) - tcp_sockets.append([create_tcpsocket('уарэп-5п28'), units_tcp[repsettings.get_unit_key('уарэп-5п28')]]) + tcp_sockets.append([create_tcpsocket('уарэп-5п28'), units_tcp[repsettings.get_unit_key('уарэп-5п28')]]) - for key in units: log.info('%s %s:%d' % [units[key].name, key[0], key[1]]) - for key in units_tcp: log.info('%s %s:%d' % [units_tcp[key].name, key[0], key[1]]) + for key in units: log.info('%s %s:%d' % [units[key].name, key[0], key[1]]) + for key in units_tcp: log.info('%s %s:%d' % [units_tcp[key].name, key[0], key[1]]) func poll_receive(sock: Socket, tick: int) -> bool: - while sock.get_available_packet_count() > 0: - var data: = sock.get_packet() - var addr: = sock.get_packet_ip() - var port: = sock.get_packet_port() - var addr_port: = [addr, port] - if addr_port in units: units[addr_port].parse(data, tick) - return false + while sock.get_available_packet_count() > 0: + var data: = sock.get_packet() + var addr: = sock.get_packet_ip() + var port: = sock.get_packet_port() + var addr_port: = [addr, port] + if addr_port in units: units[addr_port].parse(data, tick) + return false func poll_receive_tcp(sock: SocketTCP, tick: int) -> bool: - if sock.is_connection_available(): # check if someone's trying to connect - var client = sock.take_connection() # accept connection - sock.peerstream.set_stream_peer(client) # bind peerstream to new client - print_debug('client_is_connected') - var peer = sock.peerstream.get_stream_peer() - if peer: - var peer_len_data = peer.get_available_bytes() - if peer_len_data > 0: - get_client_data(sock, peer_len_data, peer, tick) - if sock.unit_key in units_tcp and sock.rx_all: units_tcp[sock.unit_key].parse(sock.rx_data, tick) - return false + if sock.is_connection_available(): # check if someone's trying to connect + var client = sock.take_connection() # accept connection + sock.peerstream.set_stream_peer(client) # bind peerstream to new client + print_debug('client_is_connected') + var peer = sock.peerstream.get_stream_peer() + if peer: + var peer_len_data = peer.get_available_bytes() + if peer_len_data > 0: + get_client_data(sock, peer_len_data, peer, tick) + if sock.unit_key in units_tcp and sock.rx_all: units_tcp[sock.unit_key].parse(sock.rx_data, tick) + return false func get_client_data(sock: SocketTCP, peer_len_data, peer, tick): - if tick - sock.rx_tick > sock.RX_TIMEOUT: - sock.rx_all = true - sock.rx_tick = tick - if sock.rx_all: - if peer_len_data >= sock.head_len: - sock.rx_all = false - sock.rx_len = sock.head_len - var head = peer.get_data(sock.head_len) - sock.data_len = head[1].decode_u32(sock.len_place) - var len_data_in_buf = peer.get_available_bytes() - if len_data_in_buf > 0: - sock.rx_data = head[1] - var len_rx = len_data_in_buf - if len_data_in_buf >= sock.data_len: - len_rx = sock.data_len - get_tcp_data(sock, peer, len_rx) - else: - var len_rx = (sock.head_len + sock.data_len) - sock.rx_len - if peer_len_data < len_rx: - len_rx = peer_len_data - get_tcp_data(sock, peer, len_rx) + if tick - sock.rx_tick > sock.RX_TIMEOUT: + sock.rx_all = true + sock.rx_tick = tick + if sock.rx_all: + if peer_len_data >= sock.head_len: + sock.rx_all = false + sock.rx_len = sock.head_len + var head = peer.get_data(sock.head_len) + sock.data_len = head[1].decode_u32(sock.len_place) + var len_data_in_buf = peer.get_available_bytes() + if len_data_in_buf > 0: + sock.rx_data = head[1] + var len_rx = len_data_in_buf + if len_data_in_buf >= sock.data_len: + len_rx = sock.data_len + get_tcp_data(sock, peer, len_rx) + else: + var len_rx = (sock.head_len + sock.data_len) - sock.rx_len + if peer_len_data < len_rx: + len_rx = peer_len_data + get_tcp_data(sock, peer, len_rx) func get_tcp_data(sock: SocketTCP, peer, len_rx): - var temp_data = peer.get_data(len_rx)[1] - sock.rx_data.append_array(temp_data) - sock.rx_len += len_rx - if sock.rx_len >= sock.head_len + sock.data_len: - sock.rx_all = true + var temp_data = peer.get_data(len_rx)[1] + sock.rx_data.append_array(temp_data) + sock.rx_len += len_rx + if sock.rx_len >= sock.head_len + sock.data_len: + sock.rx_all = true func _process(_delta: float) -> void: - var tick = Time.get_ticks_msec() - for addr in units: - var unit = units[addr] - match unit.process(tick): - Error.OK: sock_unicast.send_to(addr, unit.tx_data) - Error.FAILED: emit_signal('socket_error', 'ошибка: %s %s:%d' % [unit, addr[0], addr[1]]) - for sock_unit in tcp_sockets: - var sock = sock_unit[0] - var unit = sock_unit[1] - match unit.process(tick): - Error.OK: sock.send_to(unit.tx_data) - Error.FAILED: emit_signal('socket_error', 'ошибка: %s' %unit) - poll_receive_tcp(sock, tick) - poll_sockets.any(func(sock): poll_receive(sock, tick)) + var tick = Time.get_ticks_msec() + for addr in units: + var unit = units[addr] + match unit.process(tick): + Error.OK: sock_unicast.send_to(addr, unit.tx_data) + Error.FAILED: emit_signal('socket_error', 'ошибка: %s %s:%d' % [unit, addr[0], addr[1]]) + for sock_unit in tcp_sockets: + var sock = sock_unit[0] + var unit = sock_unit[1] + match unit.process(tick): + Error.OK: sock.send_to(unit.tx_data) + Error.FAILED: emit_signal('socket_error', 'ошибка: %s' %unit) + poll_receive_tcp(sock, tick) + poll_sockets.any(func(sock): poll_receive(sock, tick)) diff --git a/scripts/repsettings.gd b/scripts/repsettings.gd index 4ca1c06a..131afebc 100644 --- a/scripts/repsettings.gd +++ b/scripts/repsettings.gd @@ -1,50 +1,49 @@ -extends Node - -## Параметры +class_name rep_settings extends Node +## Параметры. @export var UnitProfiles: = { \ - # Название | Протокол | Адрес | Порт | Широковещательное | Привязывать к адресу | Комментарий - 'уарэп-яу07-частный': ['yau07rx', '10.1.1.2', 50002, false, true, 'IP-адрес на котором принимают частные сообщения от ЯУ-07'], - 'уарэп-яу07-общий': ['yau07rx', '10.1.1.255', 50000, true, true, 'IP-адрес на котором принимают широковещательные сообщения от ЯУ-07'], - 'уарэп-капсрпб-пол': ['jsonrx', '127.0.0.1', 42023, false, true, 'IP-адрес на котором принимают сообщения от КАПС РПБ'], - 'уарэп-капсрпб-отп': ['jsontx', '127.0.0.1', 42022, false, false, 'IP-адрес на который отправляют сообщения для КАПС РПБ'], - 'уарэп-эмс': ['yau07tx', '10.1.1.27', 50027, false, false, 'IP-адрес ЯУ-07 прибора УФ'], - 'уарэп-яу07-2в': ['yau07tx', '10.1.1.34', 50034, false, false, 'IP-адрес ЯУ-07 прибора ПРД-В'], - 'уарэп-яу07-2н': ['yau07tx', '10.1.1.33', 50033, false, false, 'IP-адрес ЯУ-07 прибора ПРД-Н'], - 'уарэп-яу07-2к': ['yau07tx', '10.1.1.35', 50035, false, false, 'IP-адрес ЯУ-07 прибора ПРД-К'], - 'уарэп-яу07-4в': ['yau07tx', '10.1.1.66', 50066, false, false, 'IP-адрес ЯУ-07 прибора ПРД-В'], - 'уарэп-яу07-4н': ['yau07tx', '10.1.1.65', 50065, false, false, 'IP-адрес ЯУ-07 прибора ПРД-Н'], - 'уарэп-яу07-4к': ['yau07tx', '10.1.1.67', 50067, false, false, 'IP-адрес ЯУ-07 прибора ПРД-К'], - 'уарэп-яу07-1в': ['yau07tx', '10.1.1.18', 50018, false, false, 'IP-адрес ЯУ-07 прибора ПРД-В'], - 'уарэп-яу07-1н': ['yau07tx', '10.1.1.17', 50017, false, false, 'IP-адрес ЯУ-07 прибора ПРД-Н'], - 'уарэп-яу07-1к': ['yau07tx', '10.1.1.19', 50019, false, false, 'IP-адрес ЯУ-07 прибора ПРД-К'], - 'уарэп-яу07-3в': ['yau07tx', '10.1.1.50', 50050, false, false, 'IP-адрес ЯУ-07 прибора ПРД-В'], - 'уарэп-яу07-3н': ['yau07tx', '10.1.1.49', 50049, false, false, 'IP-адрес ЯУ-07 прибора ПРД-Н'], - 'уарэп-яу07-3к': ['yau07tx', '10.1.1.51', 50051, false, false, 'IP-адрес ЯУ-07 прибора ПРД-Н'], + # Название | Протокол | Адрес | Порт | Широковещательное | Привязывать к адресу | Комментарий + 'уарэп-яу07-частный': ['yau07rx', '10.1.1.2', 50002, false, true, 'IP-адрес на котором принимают частные сообщения от ЯУ-07'], + 'уарэп-яу07-общий': ['yau07rx', '10.1.1.255', 50000, true, true, 'IP-адрес на котором принимают широковещательные сообщения от ЯУ-07'], + 'уарэп-капсрпб-пол': ['jsonrx', '127.0.0.1', 42023, false, true, 'IP-адрес на котором принимают сообщения от КАПС РПБ'], + 'уарэп-капсрпб-отп': ['jsontx', '127.0.0.1', 42022, false, false, 'IP-адрес на который отправляют сообщения для КАПС РПБ'], + 'уарэп-эмс': ['yau07tx', '10.1.1.27', 50027, false, false, 'IP-адрес ЯУ-07 прибора УФ'], + 'уарэп-яу07-2в': ['yau07tx', '10.1.1.34', 50034, false, false, 'IP-адрес ЯУ-07 прибора ПРД-В'], + 'уарэп-яу07-2н': ['yau07tx', '10.1.1.33', 50033, false, false, 'IP-адрес ЯУ-07 прибора ПРД-Н'], + 'уарэп-яу07-2к': ['yau07tx', '10.1.1.35', 50035, false, false, 'IP-адрес ЯУ-07 прибора ПРД-К'], + 'уарэп-яу07-4в': ['yau07tx', '10.1.1.66', 50066, false, false, 'IP-адрес ЯУ-07 прибора ПРД-В'], + 'уарэп-яу07-4н': ['yau07tx', '10.1.1.65', 50065, false, false, 'IP-адрес ЯУ-07 прибора ПРД-Н'], + 'уарэп-яу07-4к': ['yau07tx', '10.1.1.67', 50067, false, false, 'IP-адрес ЯУ-07 прибора ПРД-К'], + 'уарэп-яу07-1в': ['yau07tx', '10.1.1.18', 50018, false, false, 'IP-адрес ЯУ-07 прибора ПРД-В'], + 'уарэп-яу07-1н': ['yau07tx', '10.1.1.17', 50017, false, false, 'IP-адрес ЯУ-07 прибора ПРД-Н'], + 'уарэп-яу07-1к': ['yau07tx', '10.1.1.19', 50019, false, false, 'IP-адрес ЯУ-07 прибора ПРД-К'], + 'уарэп-яу07-3в': ['yau07tx', '10.1.1.50', 50050, false, false, 'IP-адрес ЯУ-07 прибора ПРД-В'], + 'уарэп-яу07-3н': ['yau07tx', '10.1.1.49', 50049, false, false, 'IP-адрес ЯУ-07 прибора ПРД-Н'], + 'уарэп-яу07-3к': ['yau07tx', '10.1.1.51', 50051, false, false, 'IP-адрес ЯУ-07 прибора ПРД-Н'], - # Название | Протокол | Адрес | Порт получения | Комментарий - 'уарэп-5п28': ['5p28', '127.0.0.1', 50000, 'IP-адрес комплекса 5П-28'], + # Название | Протокол | Адрес | Порт получения | Комментарий + 'уарэп-5п28': ['5p28', '127.0.0.1', 50000, 'IP-адрес комплекса 5П-28'], - # Название | Протокол | Адрес | Порт получения | Порт отправки | Комментарий - 'уарэп-щ3': ['udp-json-mbcs', 'localhost', 48000, 44100, 'IP-адрес и порты через которые производят обмен сообщения с драйвером клиента Modbus Serial'], + # Название | Протокол | Адрес | Порт получения | Порт отправки | Комментарий + 'уарэп-щ3': ['udp-json-mbcs', 'localhost', 48000, 44100, 'IP-адрес и порты через которые производят обмен сообщения с драйвером клиента Modbus Serial'], - # Название | Протокол | Имя порта | Скорость, бод | Чётность | Комментарий - 'уарэп-спт25-1': ['uart', '/dev/tty2', 115200, false, 'Адрес прибора СПТ-25 (G1)'], - 'уарэп-спт25-2': ['uart', '/dev/tty3', 115200, false, 'Адрес прибора СПТ-25 (G2)'], + # Название | Протокол | Имя порта | Скорость, бод | Чётность | Комментарий + 'уарэп-спт25-1': ['uart', '/dev/tty2', 115200, false, 'Адрес прибора СПТ-25 (G1)'], + 'уарэп-спт25-2': ['uart', '/dev/tty3', 115200, false, 'Адрес прибора СПТ-25 (G2)'], } -## Параметры цели +## Параметры цели. @export var ThreatParams = { - 'time_lost': 5000 ## Время после последнего обновления в мс, через которое цель считается потерянной. - } + 'time_lost': 5000 ## Время после последнего обновления в мс, через которое цель считается потерянной. + } func _ready() -> void: log.info('Модуль настроек готов') -## Возвращает ключ устройства +## Возвращает ключ устройства. func get_unit_key(unit_name: String) -> Array: - assert(UnitProfiles.has(unit_name), 'ошибка: нет такого устройства \"%s\"' % unit_name) - var item = UnitProfiles[unit_name] - return [item[1], item[2]] + assert(UnitProfiles.has(unit_name), 'ошибка: нет такого устройства \"%s\"' % unit_name) + var item = UnitProfiles[unit_name] + return [item[1], item[2]] diff --git a/scripts/signaller.gd b/scripts/signaller.gd index 77718839..05f9591a 100644 --- a/scripts/signaller.gd +++ b/scripts/signaller.gd @@ -1,43 +1,43 @@ class_name Signaller extends Node -## Обмен сигналами между узлами. Здесь объявлены сигналы, которые используют +## Обмен сигналами между узлами. Здесь объявлены сигналы, которые используют. ## два и более узла, из разных файлов. Должен быть помещён в автозагрузку. func conn(sgnl: Signal, clbl: Callable): connect(sgnl.get_name(), clbl) -## Вызывается при переходе в режим [b]Работа[/b] +## Вызывается при переходе в режим [b]Работа[/b]. signal режим_работа() -## Вызывается при переходе в режим [b]Контроль[/b] +## Вызывается при переходе в режим [b]Контроль[/b]. signal режим_контроль() -## Вызывается при переходе в режим [b]Журнал[/b] +## Вызывается при переходе в режим [b]Журнал[/b]. signal режим_журнал() -## Вызывается при переходе в режим [b]ЭМС[/b] +## Вызывается при переходе в режим [b]ЭМС[/b]. signal режим_эмс() -## Вызывается при переходе в режим [b]Настройка[/b] +## Вызывается при переходе в режим [b]Настройка[/b]. signal режим_настройки() -## Вызывается, когда оператор переводит программу в режим [b]Автомат выбора цели и помехи[/b] +## Вызывается, когда оператор переводит программу в режим [b]Автомат выбора цели и помехи[/b]. signal full_auto() -## Вызывается, когда оператор переводит программу в режим [b]Автомат выбора цели[/b] +## Вызывается, когда оператор переводит программу в режим [b]Автомат выбора цели[/b]. signal threat_auto() -## Вызывается, когда оператор переводит программу в режим [b]Автомат выбора помехи[/b] +## Вызывается, когда оператор переводит программу в режим [b]Автомат выбора помехи[/b]. signal interfer_auto() -## Вызывается, когда оператор переводит программу в режим [b]Ручной[/b] +## Вызывается, когда оператор переводит программу в режим [b]Ручной[/b]. signal manual_control() @@ -59,21 +59,21 @@ signal full_screen(id: int) ## Вызывается, когда пользователь выбирает цель.[br] -## [param th_id] - Отметка цели на экране +## [param th_id] - Отметка цели на экране. signal rto_threat_sel (th_id: int) ## Вызывается, когда пользователь нажимает на значёк цели.[br] -## [param th_id] - Отметка цели на экране +## [param th_id] - Отметка цели на экране. signal bip_pressed (th_id: int) ## Вызывается, когда пользователь снимает выделение с цели.[br] -## [param th_id] - Отметка цели на экране +## [param th_id] - Отметка цели на экране. signal rto_threat_unsel (th_id: int) -## Вызывается, когда пользователь снимает выделение со всех целей +## Вызывается, когда пользователь снимает выделение со всех целей. signal rto_threat_unsel_all () @@ -100,32 +100,32 @@ signal interfer_new (ecm: interfer.Interfer) signal interfer_off (ecm: interfer.Interfer, ecms: Dictionary, thrs: Dictionary) -## Вызывается при нажатии отключить все помехи +## Вызывается при нажатии отключить все помехи. signal interfer_off_all () -## Вызывается при получении внешнего целеуказания +## Вызывается при получении внешнего целеуказания. ## [param interfer_params] - Параметры полученной помехи от 5П-28.[br] signal interfer_rcv(interfer_params: Array) ## Вызывается, когда отключается действующая помеха.[br] -## [param ecm] - Выбранная помеха +## [param ecm] - Выбранная помеха. signal interfer_selected (ecm: interfer.Interfer) ## Вызывается, когда отключается выбираем помеху по сектору в таблице.[br] -## [param ecm] - Выбранная помеха +## [param ecm] - Выбранная помеха. signal fs_selected(ecm: interfer.Interfer) ## Вызывается при запросе изменения уровня детализации карты.[br] -## [param zoom] - Вновь установленный уровень детализации +## [param zoom] - Вновь установленный уровень детализации. signal zoom_changed(zoom: int) ## Вызывается при завершении изменения уровня детализации карты.[br] -## [param zoom] - Вновь установленный уровень детализации +## [param zoom] - Вновь установленный уровень детализации. signal zoom_complete(zoom: int) @@ -135,5 +135,5 @@ signal map_coords_changed(lan: float, lat: float) ## Вызывается при изменении размера изображения для отображения карты.[br] -## [param mode] - Вновь установленный режим +## [param mode] - Вновь установленный режим. signal map_mode_changed(mode: int) diff --git a/scripts/tcp5p28.gd b/scripts/tcp5p28.gd index cc7811bf..47bafdfc 100644 --- a/scripts/tcp5p28.gd +++ b/scripts/tcp5p28.gd @@ -1,193 +1,193 @@ -extends Node +class_name tcp5_p28 extends Node -## Реализация "Протокол информационного сопряжения -## СПО АСУ изделия «МП‑550» с СПО 5П-28" +## Реализация "Протокол информационного сопряжения. +## СПО АСУ изделия «МП‑550» с СПО 5П-28". -const ONLINE_TIMEOUT = 5000 ## Время ожидания пакета от 5П-28, мс -const BUFFER_SIZE = 2048 ## Размер приёмного буфера для датаграмм +const ONLINE_TIMEOUT = 5000 ## Время ожидания пакета от 5П-28, мс. +const BUFFER_SIZE = 2048 ## Размер приёмного буфера для датаграмм. -## Тип модуляции для запаковки в пакет для 5П-28 +## Тип модуляции для запаковки в пакет для 5П-28. const MOD_TYPES: Array = ['', - 'am', - 'ook', - 'ask', - 'fm', - 'qpsk', - 'oqpsk', - 'pi4qpsk', - 'qam16', - 'qam32', - 'qam64', - 'qam128', - 'qam256', - 'qam512', - 'cpfsk', - 'psk', - 'ofdm',] + 'am', + 'ook', + 'ask', + 'fm', + 'qpsk', + 'oqpsk', + 'pi4qpsk', + 'qam16', + 'qam32', + 'qam64', + 'qam128', + 'qam256', + 'qam512', + 'cpfsk', + 'psk', + 'ofdm',] const CU_STATE: Dictionary = { - 0:2, # Подготовка к выполнению - 1:1, # Выполняется - 2:9, # Ошибка в команде - 3:7, # Занят выполнением другого ЦУ - 4:9, # Неисправность - } + 0:2, # Подготовка к выполнению + 1:1, # Выполняется + 2:9, # Ошибка в команде + 3:7, # Занят выполнением другого ЦУ + 4:9, # Неисправность + } class TCP5P28: - const PACK_TYPE_CMD: int = 1 - const PACK_TYPE_RES: int = 2 - const CMD_TYPE_STATE: int = 1 - const CMD_TYPE_TH: int = 2 - const CMD_TYPE_IN: int = 3 + const PACK_TYPE_CMD: int = 1 + const PACK_TYPE_RES: int = 2 + const CMD_TYPE_STATE: int = 1 + const CMD_TYPE_TH: int = 2 + const CMD_TYPE_IN: int = 3 - signal line_changed(unit: TCP5P28) ## Сигнал состояние связи изменилось - signal data_received(unit: TCP5P28) ## Сигнал данные приняты - signal command_fail(unit: TCP5P28) ## Сигнал при не выполнении команды - signal parse_error(unit: TCP5P28, msg: String) ## Сигнал ошибки разбора сообщения - signal get_threats(unit: TCP5P28) ## Сигнал запрос целей - signal get_interfers(unit: TCP5P28) ## Запрос состояния выполнения целеуказания + signal line_changed(unit: TCP5P28) ## Сигнал состояние связи изменилось. + signal data_received(unit: TCP5P28) ## Сигнал данные приняты. + signal command_fail(unit: TCP5P28) ## Сигнал при не выполнении команды. + signal parse_error(unit: TCP5P28, msg: String) ## Сигнал ошибки разбора сообщения. + signal get_threats(unit: TCP5P28) ## Сигнал запрос целей. + signal get_interfers(unit: TCP5P28) ## Запрос состояния выполнения целеуказания. - var online: = false ## Состояние связи с источником сообщений - var name: String ## Имя экземпляра - var rx_tick: = 0 ## Тик время последнего приёма, мс - var tx_data: = PackedByteArray() ## Пакет для отправки - var tx_stack: Array ## Массив пакетов для отправки + var online: = false ## Состояние связи с источником сообщений. + var name: String ## Имя экземпляра. + var rx_tick: = 0 ## Тик время последнего приёма, мс. + var tx_data: = PackedByteArray() ## Пакет для отправки. + var tx_stack: Array ## Массив пакетов для отправки. - func _to_string() -> String: return String('капсрпб: "%s" %s ' % [self.name, ['отключен', 'на связи'][int(online)]]) - func _init(nm): self.name = nm + func _to_string() -> String: return String('капсрпб: "%s" %s ' % [self.name, ['отключен', 'на связи'][int(online)]]) + func _init(nm): self.name = nm - func parse(data: PackedByteArray, now_tick: int): - rx_tick = now_tick - if not online: - online = true - emit_signal('line_changed', self) - var pack_type = data.decode_u8(2) - if pack_type == PACK_TYPE_CMD: - var cmd_type = data.decode_u8(8) - if cmd_type == CMD_TYPE_TH: - emit_signal('get_threats') - if cmd_type == CMD_TYPE_STATE: - emit_signal('get_interfers') - var external_cu = ProjectSettings.get_setting('application/config/external_cu', false) - if cmd_type == CMD_TYPE_IN and external_cu: - set_interfer(data) + func parse(data: PackedByteArray, now_tick: int): + rx_tick = now_tick + if not online: + online = true + emit_signal('line_changed', self) + var pack_type = data.decode_u8(2) + if pack_type == PACK_TYPE_CMD: + var cmd_type = data.decode_u8(8) + if cmd_type == CMD_TYPE_TH: + emit_signal('get_threats') + if cmd_type == CMD_TYPE_STATE: + emit_signal('get_interfers') + var external_cu = ProjectSettings.get_setting('application/config/external_cu', false) + if cmd_type == CMD_TYPE_IN and external_cu: + set_interfer(data) - func process(now_tick: int): - if online and ((now_tick - rx_tick) > ONLINE_TIMEOUT): - online = false - emit_signal('line_changed', self) - if len(tx_stack): - tx_data = tx_stack[0] - tx_stack.remove_at(0) - return Error.OK - else: - return Error.ERR_UNAVAILABLE + func process(now_tick: int): + if online and ((now_tick - rx_tick) > ONLINE_TIMEOUT): + online = false + emit_signal('line_changed', self) + if len(tx_stack): + tx_data = tx_stack[0] + tx_stack.remove_at(0) + return Error.OK + else: + return Error.ERR_UNAVAILABLE - func pack_threats(ths): - var data = PackedByteArray() - var data_len = 4 + len(ths) * 80 # длинна блока данных в байтах - var tick = Time.get_ticks_msec() - data.resize(4) - data.encode_u16(0, 0) # Код ошибки - data.encode_u16(2, len(ths)) # Количество целей - for th in ths.values(): - data.append_array(get_threats_data(th, int(tick))) - var data_to_send: PackedByteArray = create_header(data_len) - data_to_send.append_array(data) - tx_stack.append(data_to_send) + func pack_threats(ths): + var data = PackedByteArray() + var data_len = 4 + len(ths) * 80 # длинна блока данных в байтах + var tick = Time.get_ticks_msec() + data.resize(4) + data.encode_u16(0, 0) # Код ошибки + data.encode_u16(2, len(ths)) # Количество целей + for th in ths.values(): + data.append_array(get_threats_data(th, int(tick))) + var data_to_send: PackedByteArray = create_header(data_len) + data_to_send.append_array(data) + tx_stack.append(data_to_send) - func pack_interfers(interfers, ecms): - var data = PackedByteArray() - var data_len = 12 # длинна блока данных в байтах - data.resize(data_len) - data.encode_u16(0, 0) # Код ошибки - data.encode_u16(2, len(interfers)) # Количество целей - data.encode_u32(4, 0) # Состояние УА-РЕП - data.encode_u32(8, 0) # Состояние линий связи - for id in interfers.values(): - var cu_data: PackedByteArray = [] - cu_data.resize(4) - var cu = ecms[id] - cu_data.encode_u16(0, cu.ispp) - var state: int - if CU_STATE.has(cu.svk): - state = CU_STATE[cu.svk] - else: - state = 9 - cu_data.encode_u8(2, state) # Состояние выполнения команды - cu_data.encode_u8(3, 0xFF) # Эффективность работы помехи - data.append_array(cu_data) - var data_to_send: PackedByteArray = create_header(data_len + 4 * len(interfers)) - data_to_send.append_array(data) - tx_stack.append(data_to_send) + func pack_interfers(interfers, ecms): + var data = PackedByteArray() + var data_len = 12 # длинна блока данных в байтах + data.resize(data_len) + data.encode_u16(0, 0) # Код ошибки + data.encode_u16(2, len(interfers)) # Количество целей + data.encode_u32(4, 0) # Состояние УА-РЕП + data.encode_u32(8, 0) # Состояние линий связи + for id in interfers.values(): + var cu_data: PackedByteArray = [] + cu_data.resize(4) + var cu = ecms[id] + cu_data.encode_u16(0, cu.ispp) + var state: int + if CU_STATE.has(cu.svk): + state = CU_STATE[cu.svk] + else: + state = 9 + cu_data.encode_u8(2, state) # Состояние выполнения команды + cu_data.encode_u8(3, 0xFF) # Эффективность работы помехи + data.append_array(cu_data) + var data_to_send: PackedByteArray = create_header(data_len + 4 * len(interfers)) + data_to_send.append_array(data) + tx_stack.append(data_to_send) - func create_header(data_len): - var head_data = PackedByteArray() - head_data.resize(8) # Размер заголовка - head_data.encode_u16(0, 0) # Код ошибки - head_data.encode_u8(2, 2) # Тип пакета, ответ на запрос - head_data.encode_u8(3, 5) # Версия протокола, всегда 5 - head_data.encode_u32(4, data_len) # Длинна пакета данных - return head_data + func create_header(data_len): + var head_data = PackedByteArray() + head_data.resize(8) # Размер заголовка + head_data.encode_u16(0, 0) # Код ошибки + head_data.encode_u8(2, 2) # Тип пакета, ответ на запрос + head_data.encode_u8(3, 5) # Версия протокола, всегда 5 + head_data.encode_u32(4, data_len) # Длинна пакета данных + return head_data - func get_threats_data(th, tick): - var th_data = PackedByteArray() - th_data.resize(80) # Количество байт в массиве - th_data.encode_u64(0, tick) - var flags: int = 0 # с 0 по 7 бит рекомендованная помеха - flags |= 0 << 8 # Флаг рекомендуемой помехи - flags |= int(th.fflags['aoa']) << 9 # Флаг доставерности пеленга - flags |= int(th.fflags['alt']) << 10 # Флаг доставерности высоты - flags |= int(th.fflags['power']) << 11 # Флаг доставерности мощности - flags |= int(th.fflags['baud']) << 12 # Флаг доставерности ск. мод. - flags |= int(th.fflags['slon']) << 13 # Флаг доставерности долготы ст. позиции - flags |= int(th.fflags['slat']) << 14 # Флаг доставерности широты ст. позиции - flags |= int(th.fflags['lon']) << 15 # Флаг доставерности долготы - flags |= int(th.fflags['lat']) << 16 # Флаг доставерности широты - flags |= int(0) << 30 # Флаг признак назначения помехи на цель - flags |= int(0) << 31 # Флаг признак назначения опасной цель - th_data.encode_u32(8, flags) # Флаги - th_data.encode_u16(12, th.id) # Идентификатор объекта - th_data.encode_u16(14, int(round(th.aoa/0.1))) # Пеленг объекта - th_data.encode_u16(16, int(th.alt)) # Высота объекта - th_data.encode_u16(18, int(th.freq)) # Частота объекта - th_data.encode_u16(20, int(th.width)) # Ширина частотной полосы объекта - th_data.encode_u8(22, int(th.power)) # Мощность излучения объекта - var mod = MOD_TYPES.find(th.tmod) - if mod == -1: mod = 0 - th_data.encode_u8(23, int(mod)) # Тип модуляции - th_data.encode_u32(24, int(th.baud)) # Скорость модуляции, бод - to_cp866(th.proto, th_data) - var c1 = pow(2, 30) / 180.0 - th_data.encode_s32(64, int(th.slon * c1)) - th_data.encode_s32(68, int(th.slat * c1)) - th_data.encode_s32(72, int(th.lon * c1)) - th_data.encode_s32(76, int(th.lat * c1)) - return th_data + func get_threats_data(th, tick): + var th_data = PackedByteArray() + th_data.resize(80) # Количество байт в массиве + th_data.encode_u64(0, tick) + var flags: int = 0 # с 0 по 7 бит рекомендованная помеха + flags |= 0 << 8 # Флаг рекомендуемой помехи + flags |= int(th.fflags['aoa']) << 9 # Флаг доставерности пеленга + flags |= int(th.fflags['alt']) << 10 # Флаг доставерности высоты + flags |= int(th.fflags['power']) << 11 # Флаг доставерности мощности + flags |= int(th.fflags['baud']) << 12 # Флаг доставерности ск. мод. + flags |= int(th.fflags['slon']) << 13 # Флаг доставерности долготы ст. позиции + flags |= int(th.fflags['slat']) << 14 # Флаг доставерности широты ст. позиции + flags |= int(th.fflags['lon']) << 15 # Флаг доставерности долготы + flags |= int(th.fflags['lat']) << 16 # Флаг доставерности широты + flags |= int(0) << 30 # Флаг признак назначения помехи на цель + flags |= int(0) << 31 # Флаг признак назначения опасной цель + th_data.encode_u32(8, flags) # Флаги + th_data.encode_u16(12, th.id) # Идентификатор объекта + th_data.encode_u16(14, int(round(th.aoa/0.1))) # Пеленг объекта + th_data.encode_u16(16, int(th.alt)) # Высота объекта + th_data.encode_u16(18, int(th.freq)) # Частота объекта + th_data.encode_u16(20, int(th.width)) # Ширина частотной полосы объекта + th_data.encode_u8(22, int(th.power)) # Мощность излучения объекта + var mod = MOD_TYPES.find(th.tmod) + if mod == -1: mod = 0 + th_data.encode_u8(23, int(mod)) # Тип модуляции + th_data.encode_u32(24, int(th.baud)) # Скорость модуляции, бод + to_cp866(th.proto, th_data) + var c1 = pow(2, 30) / 180.0 + th_data.encode_s32(64, int(th.slon * c1)) + th_data.encode_s32(68, int(th.slat * c1)) + th_data.encode_s32(72, int(th.lon * c1)) + th_data.encode_s32(76, int(th.lat * c1)) + return th_data - func to_cp866(proto: String, th_data): - var cp866_proto: PackedByteArray = proto.to_ascii_buffer() - cp866_proto.resize(36) - for i in len(cp866_proto): - th_data[28 + i] = cp866_proto[i] + func to_cp866(proto: String, th_data): + var cp866_proto: PackedByteArray = proto.to_ascii_buffer() + cp866_proto.resize(36) + for i in len(cp866_proto): + th_data[28 + i] = cp866_proto[i] - func set_interfer(data): - var mode = data.decode_u8(9) - var id = data.decode_u16(10) - var aoa = float(data.decode_u16(12)) / 10 - var ela = float(data.decode_u16(14)) / 10 - var freq = data.decode_u16(16) - var width = data.decode_u16(18) - var interfer_params: Array = [mode, id, aoa, ela, freq, width] - signaller.emit_signal('interfer_rcv', interfer_params) + func set_interfer(data): + var mode = data.decode_u8(9) + var id = data.decode_u16(10) + var aoa = float(data.decode_u16(12)) / 10 + var ela = float(data.decode_u16(14)) / 10 + var freq = data.decode_u16(16) + var width = data.decode_u16(18) + var interfer_params: Array = [mode, id, aoa, ela, freq, width] + signaller.emit_signal('interfer_rcv', interfer_params) - func on_interfer_accept(err: bool): - var data_tx = PackedByteArray() - var data_len = 2 # длинна блока данных в байтах - data_tx.resize(data_len) - data_tx.encode_u16(0, err) # Код ошибки - var data_to_send: PackedByteArray = create_header(data_len) - data_to_send.append_array(data_tx) - tx_stack.append(data_to_send) + func on_interfer_accept(err: bool): + var data_tx = PackedByteArray() + var data_len = 2 # длинна блока данных в байтах + data_tx.resize(data_len) + data_tx.encode_u16(0, err) # Код ошибки + var data_to_send: PackedByteArray = create_header(data_len) + data_to_send.append_array(data_tx) + tx_stack.append(data_to_send) diff --git a/scripts/threats.gd b/scripts/threats.gd index b26ae779..e5793da6 100644 --- a/scripts/threats.gd +++ b/scripts/threats.gd @@ -1,40 +1,40 @@ extends Node -## Работа с целями +## Работа с целями. ## Цель class Threat: - var alt: float ## Текущая высота, метры. - var aoa: float ## Курсовой угол на источник сигнала, градусы. - var atick: float ## Тик обнаружения. - var baud: float ## Модуляционная скорость. - var fflags: Dictionary ## Флаги достоверности полей. - var freq: float ## Частота несущей или центральная частота, МГц. - var freqm: String ## Режим смены частоты. - var id: int ## Идентификатор цели. - var lat: float ## Текущие координаты, широта в градусах. - var lon: float ## Текущие координаты, долгота в градусах. - var power: float ## Уровень принимаемого сигнала, дБм. - var proto: String ## Тип протокола источника сигнала. - var rkrp: String ## Рекомендуемый режим помехи. - var rparams: String ## Рекомендуемые параметры помехи. - var salt: float ## Координаты старта, высота от уровня моря, м. - var slat: float ## Координаты старта, широта в градусах. - var slon: float ## Координаты старта, долгота в градусах. - var tick: int ## Тик время приёма сигнала, мс. - var tick_rx: int ## Тик время обновления цели, мс. - var tmod: String ## Тип модуляции. - var width: float ## Ширина занимаемого диапазона частот, МГц. - var selected: bool ## Флаг, что цель выбрана + var alt: float ## Текущая высота, метры. + var aoa: float ## Курсовой угол на источник сигнала, градусы. + var atick: float ## Тик обнаружения. + var baud: float ## Модуляционная скорость. + var fflags: Dictionary ## Флаги достоверности полей. + var freq: float ## Частота несущей или центральная частота, МГц. + var freqm: String ## Режим смены частоты. + var id: int ## Идентификатор цели. + var lat: float ## Текущие координаты, широта в градусах. + var lon: float ## Текущие координаты, долгота в градусах. + var power: float ## Уровень принимаемого сигнала, дБм. + var proto: String ## Тип протокола источника сигнала. + var rkrp: String ## Рекомендуемый режим помехи. + var rparams: String ## Рекомендуемые параметры помехи. + var salt: float ## Координаты старта, высота от уровня моря, м. + var slat: float ## Координаты старта, широта в градусах. + var slon: float ## Координаты старта, долгота в градусах. + var tick: int ## Тик время приёма сигнала, мс. + var tick_rx: int ## Тик время обновления цели, мс. + var tmod: String ## Тип модуляции. + var width: float ## Ширина занимаемого диапазона частот, МГц. + var selected: bool ## Флаг, что цель выбрана. - func _init(): clear_fflags() - func _to_string() -> String: return 'номер: %d пеленг: %0.1f частота: %0.1f МГц' % [id, aoa, freq] - func clear_fflags(): tools.get_members_names(self).any(func(vn): fflags[vn] = false) + func _init(): clear_fflags() + func _to_string() -> String: return 'номер: %d пеленг: %0.1f частота: %0.1f МГц' % [id, aoa, freq] + func clear_fflags(): tools.get_members_names(self).any(func(vn): fflags[vn] = false) - func set_from_dic(dic: Dictionary): - var items: = fflags.keys().filter(func(vn): return dic.has(vn)) - items.any(func(vn): set(vn, dic[vn]); fflags[vn] = true; return false) + func set_from_dic(dic: Dictionary): + var items: = fflags.keys().filter(func(vn): return dic.has(vn)) + items.any(func(vn): set(vn, dic[vn]); fflags[vn] = true; return false) signal threat_new(th: Threat) @@ -44,37 +44,37 @@ signal threats_resized(th: Dictionary) func _ready() -> void: - log.info('Модуль обработки целей готов') - var threats = {} ## Список целей. Индекс - <номер цели> - var unit_key = repsettings.get_unit_key('уарэп-капсрпб-отп') - var unit = repnetwork.units[unit_key] - var unit_5p28_key = repsettings.get_unit_key('уарэп-5п28') - var unit_5p28 = repnetwork.units_tcp[unit_5p28_key] - var timer_check_lost: = Timer.new() - add_child(timer_check_lost) - unit.connect('data_received', Callable(self, 'on_data_capsrpb_received').bind(threats)) - unit_5p28.connect('get_threats', Callable(self, 'on_get_threats').bind(unit_5p28, threats)) - timer_check_lost.connect('timeout', Callable(self, 'on_timer_check_lost').bind(repsettings.ThreatParams.time_lost, threats)) - timer_check_lost.start(repsettings.ThreatParams.time_lost / 1000.0) - signaller.connect('rto_threat_sel', Callable(self, 'on_rto_threat_sel').bind(threats)) - signaller.connect('rto_threat_unsel', Callable(self, 'on_rto_threat_unsel').bind(threats)) - signaller.connect('rto_threat_unsel_all', Callable(self, 'on_rto_threat_unsel_all').bind(threats)) - signaller.connect('threat_selected', Callable(self, 'on_threat_selected').bind(threats)) - signaller.connect('interfer_init', Callable(self, 'on_interfer_init').bind(threats)) + log.info('Модуль обработки целей готов') + var threats = {} ## Список целей. Индекс - <номер цели> + var unit_key = repsettings.get_unit_key('уарэп-капсрпб-отп') + var unit = repnetwork.units[unit_key] + var unit_5p28_key = repsettings.get_unit_key('уарэп-5п28') + var unit_5p28 = repnetwork.units_tcp[unit_5p28_key] + var timer_check_lost: = Timer.new() + add_child(timer_check_lost) + unit.connect('data_received', Callable(self, 'on_data_capsrpb_received').bind(threats)) + unit_5p28.connect('get_threats', Callable(self, 'on_get_threats').bind(unit_5p28, threats)) + timer_check_lost.connect('timeout', Callable(self, 'on_timer_check_lost').bind(repsettings.ThreatParams.time_lost, threats)) + timer_check_lost.start(repsettings.ThreatParams.time_lost / 1000.0) + signaller.connect('rto_threat_sel', Callable(self, 'on_rto_threat_sel').bind(threats)) + signaller.connect('rto_threat_unsel', Callable(self, 'on_rto_threat_unsel').bind(threats)) + signaller.connect('rto_threat_unsel_all', Callable(self, 'on_rto_threat_unsel_all').bind(threats)) + signaller.connect('threat_selected', Callable(self, 'on_threat_selected').bind(threats)) + signaller.connect('interfer_init', Callable(self, 'on_interfer_init').bind(threats)) -## Периодически удаляет устаревшие цели +## Периодически удаляет устаревшие цели. func on_timer_check_lost(time_lost: int, threats: Dictionary): - var tick = Time.get_ticks_msec() - var sz: = threats.size() - for th in threats.values(): - if (tick - th.tick_rx) >= time_lost: - threats.erase(th.id) - call_deferred('emit_signal', 'threat_lost', th) - if sz != threats.size(): - call_deferred('emit_signal', 'threats_resized', threats) + var tick = Time.get_ticks_msec() + var sz: = threats.size() + for th in threats.values(): + if (tick - th.tick_rx) >= time_lost: + threats.erase(th.id) + call_deferred('emit_signal', 'threat_lost', th) + if sz != threats.size(): + call_deferred('emit_signal', 'threats_resized', threats) -## отправляет словарь целей в юнит, для запаковки и отправки в 5П28 +## отправляет словарь целей в юнит, для запаковки и отправки в 5П28. func on_get_threats(unit, threats): unit.pack_threats(threats) @@ -82,56 +82,56 @@ func on_get_threats(unit, threats): unit.pack_threats(threats) func on_rto_threat_unsel(th_id, threats): threats[th_id].selected = false -## Снимает выделение со всех целей +## Снимает выделение со всех целей. func on_rto_threat_unsel_all(threats): - for th in threats.values(): - signaller.emit_signal('rto_threat_unsel', th.id) + for th in threats.values(): + signaller.emit_signal('rto_threat_unsel', th.id) ## Выбирает цель, если нажать по отметке цели. func on_rto_threat_sel(th_id, threats): on_threat_selected(threats[th_id], threats) -## Выбирает цель, если выбрать в таблице +## Выбирает цель, если выбрать в таблице. func on_threat_selected(th, threats): - for threat in threats.values(): - threat.selected = false - th.selected = true + for threat in threats.values(): + threat.selected = false + th.selected = true -## Формирует список выбранных целей для включения помехи +## Формирует список выбранных целей для включения помехи. func on_interfer_init(btn, fs_arr, threats): - var sel_threats: Array = [] - for threat in threats.values(): - if threat.selected: - sel_threats.append(threat) - if len(sel_threats) or (len(fs_arr)): - signaller.emit_signal('interfer_create', btn, sel_threats, fs_arr) + var sel_threats: Array = [] + for threat in threats.values(): + if threat.selected: + sel_threats.append(threat) + if len(sel_threats) or (len(fs_arr)): + signaller.emit_signal('interfer_create', btn, sel_threats, fs_arr) -## Производит приём целей +## Производит приём целей. func on_data_capsrpb_received(unit: capsrpb.CapsRpb, threats: Dictionary): - if not unit.json_dic.has('ts'): return - if unit.json_dic['ts'] != 5: return - if not unit.json_dic.has('mis'): return + if not unit.json_dic.has('ts'): return + if unit.json_dic['ts'] != 5: return + if not unit.json_dic.has('mis'): return - var mis = unit.json_dic['mis'] - var sz: = threats.size() - for dic_th in mis: - if dic_th is Dictionary: - if dic_th.has('id'): - var id = int(dic_th['id']) - if threats.has(id): - var th = threats[id] - th.clear_fflags() - th.set_from_dic(dic_th) - th.tick_rx = unit.tick - call_deferred('emit_signal', 'threat_update', th) - else: - var th = Threat.new() - th.set_from_dic(dic_th) - threats[id] = th - th.tick_rx = unit.tick - call_deferred('emit_signal', 'threat_new', th) - if sz != threats.size(): - call_deferred('emit_signal', 'threats_resized', threats) + var mis = unit.json_dic['mis'] + var sz: = threats.size() + for dic_th in mis: + if dic_th is Dictionary: + if dic_th.has('id'): + var id = int(dic_th['id']) + if threats.has(id): + var th = threats[id] + th.clear_fflags() + th.set_from_dic(dic_th) + th.tick_rx = unit.tick + call_deferred('emit_signal', 'threat_update', th) + else: + var th = Threat.new() + th.set_from_dic(dic_th) + threats[id] = th + th.tick_rx = unit.tick + call_deferred('emit_signal', 'threat_new', th) + if sz != threats.size(): + call_deferred('emit_signal', 'threats_resized', threats) diff --git a/scripts/tools.gd b/scripts/tools.gd index 77fb48c7..d253490e 100644 --- a/scripts/tools.gd +++ b/scripts/tools.gd @@ -1,10 +1,10 @@ @tool -extends Node +class_name Tools extends Node -## Простые часто используемые функции +## Простые часто используемые функции. -## Возвращает округлённое значение [param v] с точностью [param a] +## Возвращает округлённое значение [param v] с точностью [param a]. func round_to(v: float, a: float) -> float: return snappedf(v, -a if v < 0 else a) @@ -21,7 +21,7 @@ func radians(val: float): return (val * PI) / 180.0 func ibytes_to_word(buf: PackedByteArray, i: int) -> int: return buf[i] + (buf[i + 1] * 256) -## Возвращает расстояние от центра координат до точки +## Возвращает расстояние от центра координат до точки. func hypot(x: float, y: float) -> float: return sqrt(x * x + y * y) @@ -30,14 +30,14 @@ func is_overlap(x1, x2, y1, y2) -> bool: return max(x1, y1) <= min(x2, y2) func _check_index(obj, index) -> bool: - if obj is Dictionary: - if obj.has(index): - return true - elif obj is Array: - if index is int: - if (index >= 0) and (index < obj.size()): - return true - return false + if obj is Dictionary: + if obj.has(index): + return true + elif obj is Array: + if index is int: + if (index >= 0) and (index < obj.size()): + return true + return false ## Возвращает значение поля хранящегося по указанному пути внутри объекта.[br] @@ -48,15 +48,15 @@ func _check_index(obj, index) -> bool: ## [param ret_val[1]] - устанвлено в [b]true[/b], если поле найдено, и установлено в [b]false[/b], ## если искомое поле не найдено. func get_value_by_path(obj, paths: Array, ret_val: Array): - var index = paths.pop_at(0) - ret_val[1] = false - if paths.size(): - if _check_index(obj, index): - get_value_by_path(obj[index], paths, ret_val) - else: - if _check_index(obj, index): - ret_val[0] = obj[index] - ret_val[1] = true + var index = paths.pop_at(0) + ret_val[1] = false + if paths.size(): + if _check_index(obj, index): + get_value_by_path(obj[index], paths, ret_val) + else: + if _check_index(obj, index): + ret_val[0] = obj[index] + ret_val[1] = true ## Возвращает часть от входной строки ограниченную индексами символов.[br] @@ -65,75 +65,75 @@ func get_value_by_path(obj, paths: Array, ret_val: Array): ## [param i0] - Индекс начала части строки.[br] ## [param i1] - Индекс конца части строки. Возвращает все символы до этого индекса. func get_string_slice(s_in: String, s_out: Array, i0: int, i1: int): - for i in range(i0, i1): - if s_in[i] != '\'': - s_out[0] += s_in[i] + for i in range(i0, i1): + if s_in[i] != '\'': + s_out[0] += s_in[i] -## Производит преобразование из декартовой в полярную систему координат +## Производит преобразование из декартовой в полярную систему координат. func pos_calc(freq: float, aoa: float, pos0: Vector2, r0: float, r1: float, f0: float, f1: float): - var r = ((freq - f0) / (f1 - f0)) * r1 + r0 - var aoa_rad = radians(aoa) - PI / 2.0 - return Vector2(cos(aoa_rad), sin(aoa_rad)) * r + pos0 + var r = ((freq - f0) / (f1 - f0)) * r1 + r0 + var aoa_rad = radians(aoa) - PI / 2.0 + return Vector2(cos(aoa_rad), sin(aoa_rad)) * r + pos0 -## Отсекает значение до заданного диапазона +## Отсекает значение до заданного диапазона. func clip_value(val, val_min, val_max): - if val < val_min: val = val_min - if val > val_max: val = val_max - return val + if val < val_min: val = val_min + if val > val_max: val = val_max + return val -## Возвращает массив с именами членов [param obj] +## Возвращает массив с именами членов [param obj]. func get_members_names(obj: Object) -> Array: - var pl = obj.get_property_list() - var names: = Array() - for item in pl: names.append(item.name) - return names + var pl = obj.get_property_list() + var names: = Array() + for item in pl: names.append(item.name) + return names -## Проверяет, что заданный массив [param val] похож на ip-адрес +## Проверяет, что заданный массив [param val] похож на ip-адрес. func is_array_ipaddr(val: Array): - return (val.size() == 2) \ - and (val[0] is String) \ - and (val[1] is int) \ - and (0 < val[1]) \ - and (val[1] < 65536) + return (val.size() == 2) \ + and (val[0] is String) \ + and (val[1] is int) \ + and (0 < val[1]) \ + and (val[1] < 65536) ## Преобразует декартовые координаты в полярные.[br] ## [param pos0] - координаты центра.[br] ## [param pos] - координаты точки.[br] func map_dec_to_rad(pos0: Vector2, pos: Vector2) -> Vector2: - var x1: float = pos.x - pos0.x - var y1: float = pos.y - pos0.y - var ang: float = (atan2(y1, x1) * 180.0) / PI - return Vector2(ang + 360.0 * float(ang < 0.0), hypot(x1, y1)) + var x1: float = pos.x - pos0.x + var y1: float = pos.y - pos0.y + var ang: float = (atan2(y1, x1) * 180.0) / PI + return Vector2(ang + 360.0 * float(ang < 0.0), hypot(x1, y1)) -## Переводит значение val из диапазона a0...a1 в диапазон b0...b1 +## Переводит значение val из диапазона a0...a1 в диапазон b0...b1. func map_range(val: float, a0: float, a1: float, b0: float, b1: float) -> float: - return (((val - a0) * (b1 - b0)) / (a1 - a0)) + b0 + return (((val - a0) * (b1 - b0)) / (a1 - a0)) + b0 ## Возвращает [b]true[/b], если точка внутри заданного полигона.[br] ## [param pos] - Проверяемая точка.[br] ## [param p] - Координаты вершин полигона.[br] func is_point_in_polygon(pos: Vector2, p: PackedVector2Array) -> bool: - var sz: = p.size() - assert(sz) - var j: int = sz - 1 - var odd_nodes: int = 0 - for i in sz: - if ((p[i].y < pos.y and p[j].y >= pos.y or p[j].y < pos.y and p[i].y >= pos.y) and (p[i].x <= pos.x or p[j].x <= pos.x)): - odd_nodes ^= int((p[i].x + (pos.y - p[i].y) / (p[j].y - p[i].y) * (p[j].x - p[i].x) < pos.x)) - j = i - return odd_nodes > 0 + var sz: = p.size() + assert(sz) + var j: int = sz - 1 + var odd_nodes: int = 0 + for i in sz: + if ((p[i].y < pos.y and p[j].y >= pos.y or p[j].y < pos.y and p[i].y >= pos.y) and (p[i].x <= pos.x or p[j].x <= pos.x)): + odd_nodes ^= int((p[i].x + (pos.y - p[i].y) / (p[j].y - p[i].y) * (p[j].x - p[i].x) < pos.x)) + j = i + return odd_nodes > 0 -## Записывает слово [param val] в элемент байтовго массива [param buf] +## Записывает слово [param val] в элемент байтовго массива [param buf]. ## расположенный по индексу [param index] (младшее слово).[br] ## [i]Следует использовать встроенные в[/i] [b]PackedByteArray[/b] [i]методы вместо этой функции.[/i] func set_wordi(buf: PackedByteArray, index: int, val: int) -> void: - buf[index] = val & 0xff - buf[index + 1] = (val >> 8) & 0xff + buf[index] = val & 0xff + buf[index + 1] = (val >> 8) & 0xff diff --git a/scripts/yau07.gd b/scripts/yau07.gd index e3832ad8..f5a43fa5 100644 --- a/scripts/yau07.gd +++ b/scripts/yau07.gd @@ -1,6 +1,6 @@ -extends Node +class_name yau_07 extends Node -## Протокол ячейки ЯУ-07 +## Протокол ячейки ЯУ-07. const ONLINE_TIMEOUT = 5.0 ## Время ожидания пакета от ячейки, секунды const RESEND_TIMEOUT = 0.3 ## Задержка для повторной отправки команды, секунды @@ -8,242 +8,242 @@ const RETRY_COUNT = 5 ## Количество повторных отпр const BUFFER_SIZE = 2048 ## Размер буфера для приёма датаграмм -## Ячейка ЯУ-07 +## Ячейка ЯУ-07. class YaU07: - ## Коды выполняемых команд - enum CmdCode { - READ_ISA = 0, ## Код команды "читать порт ISA" ячейки ЯУ-07 - WRITE_ISA = 1, ## Код команды "записать порт ISA" ячейки ЯУ-07 - WRITE_BLOCK = 60, ## Код команды "записать блок данных ISA" внешней шины ячейки ЯУ-07 - READ_BLOCK = 61, ## Код команды "читать блок данных ISA" внешней шины ячейки ЯУ-07 - } + ## Коды выполняемых команд. + enum CmdCode { + READ_ISA = 0, ## Код команды "читать порт ISA" ячейки ЯУ-07. + WRITE_ISA = 1, ## Код команды "записать порт ISA" ячейки ЯУ-07. + WRITE_BLOCK = 60, ## Код команды "записать блок данных ISA" внешней шины ячейки ЯУ-07. + READ_BLOCK = 61, ## Код команды "читать блок данных ISA" внешней шины ячейки ЯУ-07. + } - ## Состояние автомата выполнения команды - enum CmdState { - UNCK, ## Номер команды не ининциирован - SEND, ## Буфер готов для отправки - WAIT, ## Ожидание выполнения - DONE, ## Команда выполнена - FAIL ## Команда не выполнена - } + ## Состояние автомата выполнения команды. + enum CmdState { + UNCK, ## Номер команды не ининциирован. + SEND, ## Буфер готов для отправки. + WAIT, ## Ожидание выполнения. + DONE, ## Команда выполнена. + FAIL ## Команда не выполнена. + } - signal line_changed(unit: YaU07) ## Вызывается когда меняется состояние связи с ячейкой - signal command_done(unit: YaU07) ## Вызывается после выполнения команды - signal command_fail(unit: YaU07) ## Вызывается если возникла ошибка при выполнении команды и команда не выполнена - signal parse_failed(unit: YaU07) + signal line_changed(unit: YaU07) ## Вызывается когда меняется состояние связи с ячейкой. + signal command_done(unit: YaU07) ## Вызывается после выполнения команды. + signal command_fail(unit: YaU07) ## Вызывается если возникла ошибка при выполнении команды и команда не выполнена. + signal parse_failed(unit: YaU07) - var name: = String() - var cmd_num_rx: = 0 ## Номер команды принятый - var cmd_num_tx: = 0 ## Номер команды переданный - var cmd_state = CmdState.UNCK ## Состояние выполнения команды - var cmd_result: = 1 ## Результат выполнения команды. Устанавливается в ЯУ-07 - var cmd_data: = PackedByteArray() ## Данные команды. Устанавливается в ЯУ-07 - var cmd_retry = 0 ## Счётчик оставшихся попыток выполнить команду - var tick = 0.0 ## Текущее тик-время в мс - var cmd_tick_tx = 0.0 ## Время последней отправки команды - var cmd_tick_rx = 0.0 ## Время последнего приёма команды - var status: = PackedByteArray() ## Поле "состояние прибора" - var tx_data: = PackedByteArray() ## Буфер отправленный - var tx_len = 0 ## Длина последнего отправленного пакета - var isa_ports = {} ## Результат выполнения команды "читать порт" - var isa_block = {} ## Результат выполнения команды "читать массив из порта" - var online = false ## Состояние связи с ячейкой + var name: = String() + var cmd_num_rx: = 0 ## Номер команды принятый. + var cmd_num_tx: = 0 ## Номер команды переданный. + var cmd_state = CmdState.UNCK ## Состояние выполнения команды. + var cmd_result: = 1 ## Результат выполнения команды. Устанавливается в ЯУ-07. + var cmd_data: = PackedByteArray() ## Данные команды. Устанавливается в ЯУ-07. + var cmd_retry = 0 ## Счётчик оставшихся попыток выполнить команду. + var tick = 0.0 ## Текущее тик-время в мс. + var cmd_tick_tx = 0.0 ## Время последней отправки команды. + var cmd_tick_rx = 0.0 ## Время последнего приёма команды. + var status: = PackedByteArray() ## Поле "состояние прибора". + var tx_data: = PackedByteArray() ## Буфер отправленный. + var tx_len = 0 ## Длина последнего отправленного пакета. + var isa_ports = {} ## Результат выполнения команды "читать порт". + var isa_block = {} ## Результат выполнения команды "читать массив из порта". + var online = false ## Состояние связи с ячейкой. - func _to_string() -> String: return String('яу07: %s %s' % [self.name, ['отключен', 'на связи'][int(online)]]) + func _to_string() -> String: return String('яу07: %s %s' % [self.name, ['отключен', 'на связи'][int(online)]]) - func _init(nm): - self.name = nm - tx_data.resize(BUFFER_SIZE) - tx_data.fill(0) - tick = 0.0 + func _init(nm): + self.name = nm + tx_data.resize(BUFFER_SIZE) + tx_data.fill(0) + tick = 0.0 - func process(delta: float): - tick += delta - if online and ((tick - cmd_tick_rx) > ONLINE_TIMEOUT): - online = false - emit_signal('line_changed', self) + func process(delta: float): + tick += delta + if online and ((tick - cmd_tick_rx) > ONLINE_TIMEOUT): + online = false + emit_signal('line_changed', self) - var rc = Error.ERR_BUSY - if cmd_state == CmdState.SEND: - rc = Error.OK - cmd_tick_tx = tick - cmd_state = CmdState.WAIT - cmd_retry = RETRY_COUNT - elif cmd_state == CmdState.WAIT: - if cmd_retry: - if (tick - cmd_tick_tx) > RESEND_TIMEOUT: - rc = Error.OK - cmd_tick_tx = tick - cmd_retry -= 1 - else: - cmd_state = CmdState.FAIL - emit_signal('command_fail', self) - rc = Error.FAILED - return rc + var rc = Error.ERR_BUSY + if cmd_state == CmdState.SEND: + rc = Error.OK + cmd_tick_tx = tick + cmd_state = CmdState.WAIT + cmd_retry = RETRY_COUNT + elif cmd_state == CmdState.WAIT: + if cmd_retry: + if (tick - cmd_tick_tx) > RESEND_TIMEOUT: + rc = Error.OK + cmd_tick_tx = tick + cmd_retry -= 1 + else: + cmd_state = CmdState.FAIL + emit_signal('command_fail', self) + rc = Error.FAILED + return rc - func send_isa(cmd_code: int, ports_data: Array) -> bool: - if (cmd_state == CmdState.WAIT) \ - or (cmd_state == CmdState.SEND) \ - or (cmd_state == CmdState.UNCK): - return false - if not ((cmd_code == CmdCode.READ_ISA) or (cmd_code == CmdCode.WRITE_ISA)): - return false + func send_isa(cmd_code: int, ports_data: Array) -> bool: + if (cmd_state == CmdState.WAIT) \ + or (cmd_state == CmdState.SEND) \ + or (cmd_state == CmdState.UNCK): + return false + if not ((cmd_code == CmdCode.READ_ISA) or (cmd_code == CmdCode.WRITE_ISA)): + return false - cmd_num_tx = (cmd_num_rx + 1) % 0xffff + cmd_num_tx = (cmd_num_rx + 1) % 0xffff - var ports_count = ports_data.size() - tx_len = ports_count * 2 + 8 - tx_data.encode_u16(0, cmd_retry) # Исправление для сетевого стека "nm-callback" процессоров STM - tx_data.encode_u16(2, cmd_num_tx) + var ports_count = ports_data.size() + tx_len = ports_count * 2 + 8 + tx_data.encode_u16(0, cmd_retry) # Исправление для сетевого стека "nm-callback" процессоров STM + tx_data.encode_u16(2, cmd_num_tx) - if cmd_code == CmdCode.WRITE_ISA: - ports_count /= 2 - tx_data[6] = cmd_code - tx_data[7] = ports_count + if cmd_code == CmdCode.WRITE_ISA: + ports_count /= 2 + tx_data[6] = cmd_code + tx_data[7] = ports_count - var i: = 0 - for word in ports_data: - tx_data.encode_u16(8 + i, word) - i += 2 - cmd_state = CmdState.SEND - return true + var i: = 0 + for word in ports_data: + tx_data.encode_u16(8 + i, word) + i += 2 + cmd_state = CmdState.SEND + return true - func send_block(cmd_code: int, block_len: int, port: Array) -> bool: - if (cmd_state == CmdState.WAIT) \ - or (cmd_state == CmdState.SEND) \ - or (cmd_state == CmdState.UNCK): - return false - if not ((cmd_code == CmdCode.READ_BLOCK) or (cmd_code == CmdCode.WRITE_BLOCK)): - return false + func send_block(cmd_code: int, block_len: int, port: Array) -> bool: + if (cmd_state == CmdState.WAIT) \ + or (cmd_state == CmdState.SEND) \ + or (cmd_state == CmdState.UNCK): + return false + if not ((cmd_code == CmdCode.READ_BLOCK) or (cmd_code == CmdCode.WRITE_BLOCK)): + return false - cmd_num_tx = (cmd_num_rx + 1) % 0xffff + cmd_num_tx = (cmd_num_rx + 1) % 0xffff - tx_len = 11 - tx_data.encode_u16(0, cmd_retry) # Исправление для сетевого стека "nm-callback" процессоров STM - tx_data.encode_u16(2, cmd_num_tx) + tx_len = 11 + tx_data.encode_u16(0, cmd_retry) # Исправление для сетевого стека "nm-callback" процессоров STM + tx_data.encode_u16(2, cmd_num_tx) - if cmd_code == CmdCode.WRITE_BLOCK: - block_len /= 2 - tx_data[6] = cmd_code - tx_data.encode_u16(7, block_len) + if cmd_code == CmdCode.WRITE_BLOCK: + block_len /= 2 + tx_data[6] = cmd_code + tx_data.encode_u16(7, block_len) - tx_data.encode_u16(9, int(port[0])) - cmd_state = CmdState.SEND - return true - ## Возвращает код команды ЯУ-07 - ## rx_data - Принятые данные - ## Возвращает - Код команды не найден, 0...255 - код команды - func get_cmd_code(rx_data: PackedByteArray): + tx_data.encode_u16(9, int(port[0])) + cmd_state = CmdState.SEND + return true + ## Возвращает код команды ЯУ-07. + ## rx_data - Принятые данные. + ## Возвращает - Код команды не найден, 0...255 - код команды. + func get_cmd_code(rx_data: PackedByteArray): - var rx_data_len = rx_data.size() - if rx_data_len < 7: - return null - var status_len = rx_data.decode_u16(6) # Длина массива байтового "состояние прибора" - if rx_data_len <= (status_len + 6): # Нет поля "ответ на команду", разбор пакета завершить - return null - var cmd_code_pos = status_len + 8 # Положение в приёмном буфере "кода команды" - if rx_data_len <= cmd_code_pos: - return null - return rx_data[cmd_code_pos] # "код команды" + var rx_data_len = rx_data.size() + if rx_data_len < 7: + return null + var status_len = rx_data.decode_u16(6) # Длина массива байтового "состояние прибора" + if rx_data_len <= (status_len + 6): # Нет поля "ответ на команду", разбор пакета завершить + return null + var cmd_code_pos = status_len + 8 # Положение в приёмном буфере "кода команды" + if rx_data_len <= cmd_code_pos: + return null + return rx_data[cmd_code_pos] # "код команды" - func parse_isa(rx_data: PackedByteArray, status_len) -> bool: - var ports_count_pos = status_len + 10 # Положение в приёмном буфере количества портов - if rx_data.size() < ports_count_pos: - return false - var ports_count = rx_data[ports_count_pos] # Количество портов - var counted_len = status_len + (ports_count * 4) + 11 # Вычисленная длина пакета - if rx_data.size() < counted_len: - return false - var addr_pos = status_len + 11 # Смещение положения адреса в приёмном буфере - var data_pos = status_len + 13 # Смещение положения данных в приёмном буфере - var i: = 0 - while i < ports_count: - var shift: = i * 4 - var isa_addr = rx_data.decode_u16(addr_pos + shift) # Адрес порта ISA яу-07 - var isa_data = rx_data.decode_u16(data_pos + shift) # Данные порта ISA яу-07 - isa_ports[isa_addr] = isa_data - i += 1 - cmd_data = rx_data.slice(addr_pos, (addr_pos + ports_count * 4) - 1) - return i > 0 + func parse_isa(rx_data: PackedByteArray, status_len) -> bool: + var ports_count_pos = status_len + 10 # Положение в приёмном буфере количества портов + if rx_data.size() < ports_count_pos: + return false + var ports_count = rx_data[ports_count_pos] # Количество портов + var counted_len = status_len + (ports_count * 4) + 11 # Вычисленная длина пакета + if rx_data.size() < counted_len: + return false + var addr_pos = status_len + 11 # Смещение положения адреса в приёмном буфере + var data_pos = status_len + 13 # Смещение положения данных в приёмном буфере + var i: = 0 + while i < ports_count: + var shift: = i * 4 + var isa_addr = rx_data.decode_u16(addr_pos + shift) # Адрес порта ISA яу-07 + var isa_data = rx_data.decode_u16(data_pos + shift) # Данные порта ISA яу-07 + isa_ports[isa_addr] = isa_data + i += 1 + cmd_data = rx_data.slice(addr_pos, (addr_pos + ports_count * 4) - 1) + return i > 0 - func parse_block(rx_data: PackedByteArray, status_len) -> bool: - var ports_count_pos = status_len + 7 # Положение в приёмном буфере количества портов - if rx_data.size() < ports_count_pos: - return false - var block_len = rx_data.decode_u16(ports_count_pos + 3) - var block_adr = rx_data.decode_u16(ports_count_pos + 5) - var block_data = [] - for i in block_len: - var shift: int = i * 2 - block_data.append(rx_data.decode_u16(ports_count_pos + 7 + shift)) - isa_block[block_adr] = block_data - return true + func parse_block(rx_data: PackedByteArray, status_len) -> bool: + var ports_count_pos = status_len + 7 # Положение в приёмном буфере количества портов + if rx_data.size() < ports_count_pos: + return false + var block_len = rx_data.decode_u16(ports_count_pos + 3) + var block_adr = rx_data.decode_u16(ports_count_pos + 5) + var block_data = [] + for i in block_len: + var shift: int = i * 2 + block_data.append(rx_data.decode_u16(ports_count_pos + 7 + shift)) + isa_block[block_adr] = block_data + return true - ## Заполняет состояние прибора данными прочитанными из портов ЯУ-07 - ## для команд "читать порты" или "записать порты"[br] - ## [param rx_data] - Принятые данные[br] - ## Возвращает [b]true[/b] - Данные обработаны успешно[br] - ## Возвращает [b]false[/b] - Данные не обработаны из за ошибки[br] - func parse(rx_data: PackedByteArray) -> bool: + ## Заполняет состояние прибора данными прочитанными из портов ЯУ-07. + ## для команд "читать порты" или "записать порты".[br] + ## [param rx_data] - Принятые данные.[br] + ## Возвращает [b]true[/b] - Данные обработаны успешно.[br] + ## Возвращает [b]false[/b] - Данные не обработаны из за ошибки.[br] + func parse(rx_data: PackedByteArray) -> bool: - cmd_tick_rx = tick + cmd_tick_rx = tick - if not online: - online = true - emit_signal('line_changed', self) + if not online: + online = true + emit_signal('line_changed', self) - var rx_data_len = rx_data.size() - if rx_data_len < 10: - return false + var rx_data_len = rx_data.size() + if rx_data_len < 10: + return false - cmd_num_rx = rx_data.decode_u16(2) - if cmd_state == CmdState.UNCK: - cmd_state = CmdState.DONE + cmd_num_rx = rx_data.decode_u16(2) + if cmd_state == CmdState.UNCK: + cmd_state = CmdState.DONE - var status_len = rx_data.decode_u16(6) # Длина массива байтового "состояние прибора" - if rx_data_len < (status_len + 6): - return false - var i: = 0 - var len_rx_data: = rx_data.size() - status.resize(status_len) - while i < status_len: - var byte_i = i + 8 - if byte_i < len_rx_data: - status[i] = rx_data[byte_i] # Массив байтовый "состояние прибора" - i += 1 + var status_len = rx_data.decode_u16(6) # Длина массива байтового "состояние прибора" + if rx_data_len < (status_len + 6): + return false + var i: = 0 + var len_rx_data: = rx_data.size() + status.resize(status_len) + while i < status_len: + var byte_i = i + 8 + if byte_i < len_rx_data: + status[i] = rx_data[byte_i] # Массив байтовый "состояние прибора" + i += 1 - if rx_data_len == (status_len + 6): - return true # Нет поля "ответ на команду", разбор пакета завершить + if rx_data_len == (status_len + 6): + return true # Нет поля "ответ на команду", разбор пакета завершить - var cmd_code_pos = status_len + 8 # Положение в приёмном буфере "код команды" - if rx_data_len <= cmd_code_pos: - return false + var cmd_code_pos = status_len + 8 # Положение в приёмном буфере "код команды" + if rx_data_len <= cmd_code_pos: + return false - var cmd_status_pos = status_len + 9 # Положение в приёмном буфере "состояния выполнения команды" - if rx_data_len <= cmd_status_pos: - return false - cmd_result = rx_data[cmd_status_pos] - if cmd_result != 1: # Команда не выполнена - return false + var cmd_status_pos = status_len + 9 # Положение в приёмном буфере "состояния выполнения команды" + if rx_data_len <= cmd_status_pos: + return false + cmd_result = rx_data[cmd_status_pos] + if cmd_result != 1: # Команда не выполнена + return false - var cmd_code: CmdCode = rx_data[cmd_code_pos] as CmdCode # "код команды" - var rc = true - if (cmd_code == CmdCode.READ_ISA) or (cmd_code == CmdCode.WRITE_ISA): - rc = parse_isa(rx_data, status_len) + var cmd_code: CmdCode = rx_data[cmd_code_pos] as CmdCode # "код команды" + var rc = true + if (cmd_code == CmdCode.READ_ISA) or (cmd_code == CmdCode.WRITE_ISA): + rc = parse_isa(rx_data, status_len) - elif (cmd_code == CmdCode.READ_BLOCK) or (cmd_code == CmdCode.WRITE_BLOCK): - rc = parse_block(rx_data, status_len) + elif (cmd_code == CmdCode.READ_BLOCK) or (cmd_code == CmdCode.WRITE_BLOCK): + rc = parse_block(rx_data, status_len) - if rc: - if (cmd_state == CmdState.WAIT) and (cmd_num_rx == cmd_num_tx): - cmd_state = CmdState.DONE - emit_signal('command_done', self) - else: - emit_signal('parse_failed', self) - return true + if rc: + if (cmd_state == CmdState.WAIT) and (cmd_num_rx == cmd_num_tx): + cmd_state = CmdState.DONE + emit_signal('command_done', self) + else: + emit_signal('parse_failed', self) + return true diff --git a/scripts/yems-boards.gd b/scripts/yems-boards.gd index 347bea60..8e14f3d3 100644 --- a/scripts/yems-boards.gd +++ b/scripts/yems-boards.gd @@ -1,15 +1,15 @@ -extends Node +class_name yems_boards extends Node class EmsBoard: - var id_board: String - var in_devs: Dictionary ## Словарь с входными устройствами - var out_devs: Dictionary ## Словарь с выходными устройствами - var base_addr_isa: int ## Базовый адрес ячейки isa - var in_tbl: Array - var out_tbl: Array + var id_board: String + var in_devs: Dictionary ## Словарь с входными устройствами. + var out_devs: Dictionary ## Словарь с выходными устройствами. + var base_addr_isa: int ## Базовый адрес ячейки isa. + var in_tbl: Array + var out_tbl: Array - func _init(id, base_addr, in_table, out_table): - id_board = id - in_tbl = in_table - out_tbl = out_table - base_addr_isa = base_addr + func _init(id, base_addr, in_table, out_table): + id_board = id + in_tbl = in_table + out_tbl = out_table + base_addr_isa = base_addr diff --git a/scripts/yems-consts.gd b/scripts/yems-consts.gd index ac333c62..f4d56ee1 100644 --- a/scripts/yems-consts.gd +++ b/scripts/yems-consts.gd @@ -1,144 +1,144 @@ -extends Node +class_name yerms_consts extends Node -## +## Таблица с данными для входов, первое значение - индекс устройства в ячейке КЭМС. const INPUT_ROWS1 = [ - [12,['283333 МР-231S, Строб ЭМС', 'X52', '0.00', '0.00']], - [13,['29НРЛС МР-231-3, УИЗП', 'X54', '0.00', '0.00']], - [14,['30НРЛС МР-231-3, Строб ЭМС', 'X55', '0.00', '0.00']], - [15,['31ЦИСВС-1010, УИЗП', 'X23', '0.00', '0.00']], - [21,['32НРЛС МР-231S, УИЗП', 'X21', '0.00', '0.00']], - [22,['33НРЛС МР-231S, Строб ЭМС', 'X22', '0.00', '0.00']], - [23,['34НРЛС МР-231-3, УИЗП', 'X24', '0.00', '0.00']], - [32,['352222 МР-231S, Строб ЭМС', 'X32', '0.00', '0.00']], - [33,['36НРЛС МР-231-3, УИЗП', 'X34', '0.00', '0.00']], - [34,['37НРЛС МР-231-3, Строб ЭМС', 'X35', '0.00', '0.00']], - [35,['38ЦИСВС-1010, УИЗП', 'X43', '0.00', '0.00']], - [38,['394302-01, 4302-04, УИЗП', 'X46', '0.00', '0.00']], - [31,['40НРЛС МР-231S, УИЗП', 'X51', '0.00', '0.00']]] + [12,['283333 МР-231S, Строб ЭМС', 'X52', '0.00', '0.00']], + [13,['29НРЛС МР-231-3, УИЗП', 'X54', '0.00', '0.00']], + [14,['30НРЛС МР-231-3, Строб ЭМС', 'X55', '0.00', '0.00']], + [15,['31ЦИСВС-1010, УИЗП', 'X23', '0.00', '0.00']], + [21,['32НРЛС МР-231S, УИЗП', 'X21', '0.00', '0.00']], + [22,['33НРЛС МР-231S, Строб ЭМС', 'X22', '0.00', '0.00']], + [23,['34НРЛС МР-231-3, УИЗП', 'X24', '0.00', '0.00']], + [32,['352222 МР-231S, Строб ЭМС', 'X32', '0.00', '0.00']], + [33,['36НРЛС МР-231-3, УИЗП', 'X34', '0.00', '0.00']], + [34,['37НРЛС МР-231-3, Строб ЭМС', 'X35', '0.00', '0.00']], + [35,['38ЦИСВС-1010, УИЗП', 'X43', '0.00', '0.00']], + [38,['394302-01, 4302-04, УИЗП', 'X46', '0.00', '0.00']], + [31,['40НРЛС МР-231S, УИЗП', 'X51', '0.00', '0.00']]] -## Таблица с данными для выходов, первое значение - индекс устройства в ячейке КЭМС +## Таблица с данными для выходов, первое значение - индекс устройства в ячейке КЭМС. const OUTPUT_ROWS1 = [ - [41,['41НРЛС МР-231S, БИ', 'X61', '0.00', '0.00']], - [42,['42НРЛС МР-231S', 'X62', '0.00', '0.00']], - [43,['43НРЛС МР-231-3, БИ', 'X64', '0.00', '0.00']], - [44,['44НРЛС МР-231-3', 'X65', '0.00', '0.00']], - [45,['45ЦИСВС-1010, БИ', 'X63', '0.00', '0.00']], - [46,['464302-01, 4302-04, БИ', 'X66', '0.00', '0.00']], - [48,['47НРЛС 11111111', 'X69', '0.00', '0.00']], - [62,['48НРЛС МР-231S', 'X62', '0.00', '0.00']], - [63,['49НРЛС МР-231-3, БИ', 'X64', '0.00', '0.00']], - [64,['50НРЛС МР-231-3', 'X65', '0.00', '0.00']], - [65,['51ЦИСВС-1010, БИ', 'X63', '0.00', '0.00']], - [66,['524302-01, 4302-04, БИ', 'X66', '0.00', '0.00']], - [68,['53НРЛС 11111111', 'X69', '0.00', '0.00']]] + [41,['41НРЛС МР-231S, БИ', 'X61', '0.00', '0.00']], + [42,['42НРЛС МР-231S', 'X62', '0.00', '0.00']], + [43,['43НРЛС МР-231-3, БИ', 'X64', '0.00', '0.00']], + [44,['44НРЛС МР-231-3', 'X65', '0.00', '0.00']], + [45,['45ЦИСВС-1010, БИ', 'X63', '0.00', '0.00']], + [46,['464302-01, 4302-04, БИ', 'X66', '0.00', '0.00']], + [48,['47НРЛС 11111111', 'X69', '0.00', '0.00']], + [62,['48НРЛС МР-231S', 'X62', '0.00', '0.00']], + [63,['49НРЛС МР-231-3, БИ', 'X64', '0.00', '0.00']], + [64,['50НРЛС МР-231-3', 'X65', '0.00', '0.00']], + [65,['51ЦИСВС-1010, БИ', 'X63', '0.00', '0.00']], + [66,['524302-01, 4302-04, БИ', 'X66', '0.00', '0.00']], + [68,['53НРЛС 11111111', 'X69', '0.00', '0.00']]] -## Таблица с данными для входов, первое значение - индекс устройства в ячейке КЭМС +## Таблица с данными для входов, первое значение - индекс устройства в ячейке КЭМС. const INPUT_ROWS = [ - [1,['УИЗП №1', 'Х7', '0.00', '0.00']], - [2,['УИЗПФ №1', 'Х8', '0.00', '0.00']], - [3,['УИЗП №2', 'Х10', '0.00', '0.00']], - [4,['УИЗПФ №2', 'Х11', '0.00', '0.00']], - [5,['УИЗП №3', 'Х13', '0.00', '0.00']], - [6,['УИЗПФ №3', 'Х14', '0.00', '0.00']], - [7,['УИЗП №4', 'Х16', '0.00', '0.00']], - [8,['УИЗПФ №4', 'Х17', '0.00', '0.00']], - [9,['УИЗП №5', 'Х19', '0.00', '0.00']], - [10,['УИЗПФ №5', 'Х20', '0.00', '0.00']], - [11,['УИЗП №6', 'Х22', '0.00', '0.00']], - [12,['УИЗПФ №6','Х23', '0.00', '0.00']], - [13,['УИЗП №7','Х25', '0.00', '0.00']], - [14,['УИЗПФ №7','Х26', '0.00', '0.00']], - [15,['СИ1 Л1','X31 : 2', '0.00', '0.00']], - [16,['СИ1 Л6','X31 : 5', '0.00', '0.00']], - [17,['СИ1 Л7','X31 : 8', '0.00', '0.00']], - [18,['СИ2 Л1','X32 : 2', '0.00', '0.00']], - [19,['СИ2 Л6','X32 : 5', '0.00', '0.00']], - [20,['СИ2 Л7','X32 : 8', '0.00', '0.00']], - [21,['СИ3 Л1','X33 : 2', '0.00', '0.00']], - [22,['СИ3 Л6','X33 : 5', '0.00', '0.00']], - [23,['СИ3 Л7','X33 : 8', '0.00', '0.00']], - [24,['СИ4 Л1','X34 : 2', '0.00', '0.00']], - [25,['СИ4 Л6','X34 : 5', '0.00', '0.00']], - [26,['СИ4 Л7','X34 : 8', '0.00', '0.00']], - [27,['СИ1 Л2','X38 : 2', '0.00', '0.00']], - [28,['СИ1 Л3','X38 : 5', '0.00', '0.00']], - [29,['СИ2 Л2','X39 : 2', '0.00', '0.00']], - [30,['СИ2 Л3','X39 : 5', '0.00', '0.00']], - [31,['СИ3 Л2','X40 : 2', '0.00', '0.00']], - [32,['СИ3 Л3','X40 : 5', '0.00', '0.00']], - [33,['СИ4 Л2','X41 : 2', '0.00', '0.00']], - [34,['СИ4 Л3','X41 : 5', '0.00', '0.00']], - [35,['СИ1 Л4','X42 : 2', '0.00', '0.00']], - [36,['СИ1 Л5','X42 : 5', '0.00', '0.00']], - [37,['СИ2 Л4','X43 : 2', '0.00', '0.00']], - [38,['СИ2 Л5','X43 : 5', '0.00', '0.00']], - [39,['СИ3 Л4','X44 : 2', '0.00', '0.00']], - [40,['СИ3 Л5','X44 : 5', '0.00', '0.00']], - [41,['СИ4 Л4','X45 : 2', '0.00', '0.00']], - [42,['СИ4 Л5','X45 : 5', '0.00', '0.00']], - [43,['БИ В Лб','Х49 : 37', '0.00', '0.00']], - [44,['БИ В ПрБ','Х49 : 31', '0.00', '0.00']], - [45,['БИ Г Лб','Х49 : 15', '0.00', '0.00']], - [46,['БИ Г ПрБ','Х49 : 8', '0.00', '0.00']], - [47,['БИ Д Лб','Х49 : 6', '0.00', '0.00']], - [48,['БИ Д ПрБ','Х49 : 10', '0.00', '0.00']], - [49,['БИ Е Лб','Х49 : 25', '0.00', '0.00']], - [50,['БИ Е ПрБ','Х49 : 40', '0.00', '0.00']], - [51,['СЗИ В Лб','Х49 : 42', '0.00', '0.00']], - [52,['СЗИ В ПрБ','Х49 : 32', '0.00', '0.00']], - [53,['СЗИ Г Лб','Х49 : 46', '0.00', '0.00']], - [54,['СЗИ Г ПрБ','Х49 : 32', '0.00', '0.00']], - [55,['СЗИ Д Лб','Х49 : 16', '0.00', '0.00']], - [56,['СЗИ Д ПрБ','Х49 : 47', '0.00', '0.00']], - [57,['СЗИ Е Лб','Х49 : 1', '0.00', '0.00']], - [58,['СЗИ Е ПрБ','Х49 : 50', '0.00', '0.00']]] + [1,['УИЗП №1', 'Х7', '0.00', '0.00']], + [2,['УИЗПФ №1', 'Х8', '0.00', '0.00']], + [3,['УИЗП №2', 'Х10', '0.00', '0.00']], + [4,['УИЗПФ №2', 'Х11', '0.00', '0.00']], + [5,['УИЗП №3', 'Х13', '0.00', '0.00']], + [6,['УИЗПФ №3', 'Х14', '0.00', '0.00']], + [7,['УИЗП №4', 'Х16', '0.00', '0.00']], + [8,['УИЗПФ №4', 'Х17', '0.00', '0.00']], + [9,['УИЗП №5', 'Х19', '0.00', '0.00']], + [10,['УИЗПФ №5', 'Х20', '0.00', '0.00']], + [11,['УИЗП №6', 'Х22', '0.00', '0.00']], + [12,['УИЗПФ №6','Х23', '0.00', '0.00']], + [13,['УИЗП №7','Х25', '0.00', '0.00']], + [14,['УИЗПФ №7','Х26', '0.00', '0.00']], + [15,['СИ1 Л1','X31 : 2', '0.00', '0.00']], + [16,['СИ1 Л6','X31 : 5', '0.00', '0.00']], + [17,['СИ1 Л7','X31 : 8', '0.00', '0.00']], + [18,['СИ2 Л1','X32 : 2', '0.00', '0.00']], + [19,['СИ2 Л6','X32 : 5', '0.00', '0.00']], + [20,['СИ2 Л7','X32 : 8', '0.00', '0.00']], + [21,['СИ3 Л1','X33 : 2', '0.00', '0.00']], + [22,['СИ3 Л6','X33 : 5', '0.00', '0.00']], + [23,['СИ3 Л7','X33 : 8', '0.00', '0.00']], + [24,['СИ4 Л1','X34 : 2', '0.00', '0.00']], + [25,['СИ4 Л6','X34 : 5', '0.00', '0.00']], + [26,['СИ4 Л7','X34 : 8', '0.00', '0.00']], + [27,['СИ1 Л2','X38 : 2', '0.00', '0.00']], + [28,['СИ1 Л3','X38 : 5', '0.00', '0.00']], + [29,['СИ2 Л2','X39 : 2', '0.00', '0.00']], + [30,['СИ2 Л3','X39 : 5', '0.00', '0.00']], + [31,['СИ3 Л2','X40 : 2', '0.00', '0.00']], + [32,['СИ3 Л3','X40 : 5', '0.00', '0.00']], + [33,['СИ4 Л2','X41 : 2', '0.00', '0.00']], + [34,['СИ4 Л3','X41 : 5', '0.00', '0.00']], + [35,['СИ1 Л4','X42 : 2', '0.00', '0.00']], + [36,['СИ1 Л5','X42 : 5', '0.00', '0.00']], + [37,['СИ2 Л4','X43 : 2', '0.00', '0.00']], + [38,['СИ2 Л5','X43 : 5', '0.00', '0.00']], + [39,['СИ3 Л4','X44 : 2', '0.00', '0.00']], + [40,['СИ3 Л5','X44 : 5', '0.00', '0.00']], + [41,['СИ4 Л4','X45 : 2', '0.00', '0.00']], + [42,['СИ4 Л5','X45 : 5', '0.00', '0.00']], + [43,['БИ В Лб','Х49 : 37', '0.00', '0.00']], + [44,['БИ В ПрБ','Х49 : 31', '0.00', '0.00']], + [45,['БИ Г Лб','Х49 : 15', '0.00', '0.00']], + [46,['БИ Г ПрБ','Х49 : 8', '0.00', '0.00']], + [47,['БИ Д Лб','Х49 : 6', '0.00', '0.00']], + [48,['БИ Д ПрБ','Х49 : 10', '0.00', '0.00']], + [49,['БИ Е Лб','Х49 : 25', '0.00', '0.00']], + [50,['БИ Е ПрБ','Х49 : 40', '0.00', '0.00']], + [51,['СЗИ В Лб','Х49 : 42', '0.00', '0.00']], + [52,['СЗИ В ПрБ','Х49 : 32', '0.00', '0.00']], + [53,['СЗИ Г Лб','Х49 : 46', '0.00', '0.00']], + [54,['СЗИ Г ПрБ','Х49 : 32', '0.00', '0.00']], + [55,['СЗИ Д Лб','Х49 : 16', '0.00', '0.00']], + [56,['СЗИ Д ПрБ','Х49 : 47', '0.00', '0.00']], + [57,['СЗИ Е Лб','Х49 : 1', '0.00', '0.00']], + [58,['СЗИ Е ПрБ','Х49 : 50', '0.00', '0.00']]] -## Таблица с данными для выходов, первое значение - индекс устройства в ячейке КЭМС +## Таблица с данными для выходов, первое значение - индекс устройства в ячейке КЭМС. const OUTPUT_ROWS = [ - [11,['Бланк №1','Х6', '0.00', '0.00']], - [12,['Бланк №2','Х9', '0.00', '0.00']], - [13,['Бланк №3','Х12', '0.00', '0.00']], - [14,['Бланк №4','Х15', '0.00', '0.00']], - [15,['Бланк №5','Х18', '0.00', '0.00']], - [16,['Бланк №6','Х21', '0.00', '0.00']], - [17,['Бланк №7','Х24', '0.00', '0.00']], - [18,['СЗИ1 Л1','X31 : 11', '0.00', '0.00']], - [19,['СЗИ1 Л6','X31 :14', '0.00', '0.00']], - [20,['СЗИ1 Л7','X31 : 17', '0.00', '0.00']], - [21,['СЗИ2 Л1','X32 : 11', '0.00', '0.00']], - [22,['СЗИ2 Л6','X32 : 14', '0.00', '0.00']], - [23,['СЗИ2 Л7','X32: 17', '0.00', '0.00']], - [24,['СЗИ3 Л1','X33 : 11', '0.00', '0.00']], - [25,['СЗИ3 Л6','X33 :14', '0.00', '0.00']], - [26,['СЗИ3 Л7','X33: 17', '0.00', '0.00']], - [27,['СЗИ4 Л1','X34 : 11', '0.00', '0.00']], - [28,['СЗИ4 Л6','X34 :14', '0.00', '0.00']], - [29,['СЗИ4 Л7','X34: 17', '0.00', '0.00']], - [30,['СЗИ1 Л2','X38 : 11', '0.00', '0.00']], - [31,['СЗИ1 Л3','X38 : 14', '0.00', '0.00']], - [32,['СЗИ2 Л2','X39 : 11', '0.00', '0.00']], - [33,['СЗИ2 Л3','X39 : 14', '0.00', '0.00']], - [34,['СЗИ3 Л2','X40 : 11', '0.00', '0.00']], - [35,['СЗИ3 Л3','X40 : 14', '0.00', '0.00']], - [36,['СЗИ4 Л2','X41 : 11', '0.00', '0.00']], - [37,['СЗИ4 Л3','X41 :14', '0.00', '0.00']], - [38,['СЗИ1 Л4','X42 : 11', '0.00', '0.00']], - [39,['СЗИ1 Л5','X42 :14', '0.00', '0.00']], - [40,['СЗИ2 Л4','X43 : 11', '0.00', '0.00']], - [41,['СЗИ2 Л5','X43 : 14', '0.00', '0.00']], - [42,['СЗИ3 Л4','X44 : 11', '0.00', '0.00']], - [43,['СЗИ3 Л5','X44 :14', '0.00', '0.00']], - [44,['СЗИ4 Л4','X45 : 11', '0.00', '0.00']], - [45,['СЗИ4 Л5','X45 : 14', '0.00', '0.00']], - [46,['БИ РТР №1','X47 : 2', '0.00', '0.00']], - [47,['БИ РТР №2','X47 : 5', '0.00', '0.00']], - [48,['БИ РТР №3','X47 : 8', '0.00', '0.00']], - [49,['БИ РТР №4','X47 : 11', '0.00', '0.00']], - [50,['СИ В Лб','Х49 : 21', '0.00', '0.00']], - [51,['СИ В ПрБ','Х49 : 19', '0.00', '0.00']], - [52,['СИ Г Лб','Х49 : 28', '0.00', '0.00']], - [53,['СИ Г ПрБ','Х49 : 30', '0.00', '0.00']], - [54,['СИ Д Лб','Х49 : 13', '0.00', '0.00']], - [55,['СИ Д ПрБ','Х49 : 11', '0.00', '0.00']], - [56,['СИ Е Лб','Х49 : 26', '0.00', '0.00']], - [57,['СИ Е ПрБ','Х49 : 35', '0.00', '0.00']]] + [11,['Бланк №1','Х6', '0.00', '0.00']], + [12,['Бланк №2','Х9', '0.00', '0.00']], + [13,['Бланк №3','Х12', '0.00', '0.00']], + [14,['Бланк №4','Х15', '0.00', '0.00']], + [15,['Бланк №5','Х18', '0.00', '0.00']], + [16,['Бланк №6','Х21', '0.00', '0.00']], + [17,['Бланк №7','Х24', '0.00', '0.00']], + [18,['СЗИ1 Л1','X31 : 11', '0.00', '0.00']], + [19,['СЗИ1 Л6','X31 :14', '0.00', '0.00']], + [20,['СЗИ1 Л7','X31 : 17', '0.00', '0.00']], + [21,['СЗИ2 Л1','X32 : 11', '0.00', '0.00']], + [22,['СЗИ2 Л6','X32 : 14', '0.00', '0.00']], + [23,['СЗИ2 Л7','X32: 17', '0.00', '0.00']], + [24,['СЗИ3 Л1','X33 : 11', '0.00', '0.00']], + [25,['СЗИ3 Л6','X33 :14', '0.00', '0.00']], + [26,['СЗИ3 Л7','X33: 17', '0.00', '0.00']], + [27,['СЗИ4 Л1','X34 : 11', '0.00', '0.00']], + [28,['СЗИ4 Л6','X34 :14', '0.00', '0.00']], + [29,['СЗИ4 Л7','X34: 17', '0.00', '0.00']], + [30,['СЗИ1 Л2','X38 : 11', '0.00', '0.00']], + [31,['СЗИ1 Л3','X38 : 14', '0.00', '0.00']], + [32,['СЗИ2 Л2','X39 : 11', '0.00', '0.00']], + [33,['СЗИ2 Л3','X39 : 14', '0.00', '0.00']], + [34,['СЗИ3 Л2','X40 : 11', '0.00', '0.00']], + [35,['СЗИ3 Л3','X40 : 14', '0.00', '0.00']], + [36,['СЗИ4 Л2','X41 : 11', '0.00', '0.00']], + [37,['СЗИ4 Л3','X41 :14', '0.00', '0.00']], + [38,['СЗИ1 Л4','X42 : 11', '0.00', '0.00']], + [39,['СЗИ1 Л5','X42 :14', '0.00', '0.00']], + [40,['СЗИ2 Л4','X43 : 11', '0.00', '0.00']], + [41,['СЗИ2 Л5','X43 : 14', '0.00', '0.00']], + [42,['СЗИ3 Л4','X44 : 11', '0.00', '0.00']], + [43,['СЗИ3 Л5','X44 :14', '0.00', '0.00']], + [44,['СЗИ4 Л4','X45 : 11', '0.00', '0.00']], + [45,['СЗИ4 Л5','X45 : 14', '0.00', '0.00']], + [46,['БИ РТР №1','X47 : 2', '0.00', '0.00']], + [47,['БИ РТР №2','X47 : 5', '0.00', '0.00']], + [48,['БИ РТР №3','X47 : 8', '0.00', '0.00']], + [49,['БИ РТР №4','X47 : 11', '0.00', '0.00']], + [50,['СИ В Лб','Х49 : 21', '0.00', '0.00']], + [51,['СИ В ПрБ','Х49 : 19', '0.00', '0.00']], + [52,['СИ Г Лб','Х49 : 28', '0.00', '0.00']], + [53,['СИ Г ПрБ','Х49 : 30', '0.00', '0.00']], + [54,['СИ Д Лб','Х49 : 13', '0.00', '0.00']], + [55,['СИ Д ПрБ','Х49 : 11', '0.00', '0.00']], + [56,['СИ Е Лб','Х49 : 26', '0.00', '0.00']], + [57,['СИ Е ПрБ','Х49 : 35', '0.00', '0.00']]] diff --git a/scripts/yems-devs.gd b/scripts/yems-devs.gd index 00955865..4049bd5e 100644 --- a/scripts/yems-devs.gd +++ b/scripts/yems-devs.gd @@ -1,37 +1,37 @@ -extends Node +class_name yems_dev extends Node class InDev: - var id: int ## Идентификатор индекс устройства - var dev_name: String ## Имя подключаемого комплекса - var connector: String ## Номер разъёма в приборе - var delay: float ## Задержка фронта импульса - var offset: float ## Смещение фронта импульса - var state: int ## Состояние входа - var freq: int ## Предполагаемая частота на которой работает комплекс - var ports_data: Dictionary ## Словарь состояния портов устройства - var tbl_row_id: int ## Индекс устройства в таблице выходов - var out_dots: Array ## Список выходных устройств в порядке, как в таблице и точки матрицы - var connect_devs: Dictionary ## Список присоединённых устройств - var board: String ## Плата в которой находится устройство + var id: int ## Идентификатор индекс устройства. + var dev_name: String ## Имя подключаемого комплекса. + var connector: String ## Номер разъёма в приборе. + var delay: float ## Задержка фронта импульса. + var offset: float ## Смещение фронта импульса. + var state: int ## Состояние входа. + var freq: int ## Предполагаемая частота на которой работает комплекс. + var ports_data: Dictionary ## Словарь состояния портов устройства. + var tbl_row_id: int ## Индекс устройства в таблице выходов. + var out_dots: Array ## Список выходных устройств в порядке, как в таблице и точки матрицы. + var connect_devs: Dictionary ## Список присоединённых устройств. + var board: String ## Плата в которой находится устройство. - func _init(dev_id : int, params : Array, row_id : int): - id = dev_id - dev_name = String(params[0]) - connector = String(params[1]) - delay = float(params[2]) - offset = float(params[3]) - tbl_row_id = row_id + func _init(dev_id : int, params : Array, row_id : int): + id = dev_id + dev_name = String(params[0]) + connector = String(params[1]) + delay = float(params[2]) + offset = float(params[3]) + tbl_row_id = row_id class OutDev extends InDev: - var mode: String ## Режим работы выхода + var mode: String ## Режим работы выхода. - func _init(dev_id : int, params : Array, row_id : int): - id = dev_id - dev_name = String(params[0]) - connector = String(params[1]) - delay = float(params[2]) - offset = float(params[3]) - tbl_row_id = row_id - mode = 'Работа' + func _init(dev_id : int, params : Array, row_id : int): + id = dev_id + dev_name = String(params[0]) + connector = String(params[1]) + delay = float(params[2]) + offset = float(params[3]) + tbl_row_id = row_id + mode = 'Работа' diff --git a/table/table.gd b/table/table.gd index 9d9363d1..cd1dd1fc 100644 --- a/table/table.gd +++ b/table/table.gd @@ -57,94 +57,94 @@ func defaultselector_color_proc(_i_row: int): return selector_color ## [param scrl] - Прокрутка родитель таблицы.[br] ## [param prnt] - Родитель прокрутки.[br] func pin_header(scrl: ScrollContainer, prnt: Control): - scrl.remove_child(header) - prnt.add_child(header) - var v_separation = get_theme_constant('v_separation') - header.position = scrl.position - Vector2(0, header.size.y + v_separation) - scroll = scrl - scroll_adj = scrl.position + scrl.remove_child(header) + prnt.add_child(header) + var v_separation = get_theme_constant('v_separation') + header.position = scrl.position - Vector2(0, header.size.y + v_separation) + scroll = scrl + scroll_adj = scrl.position ## Перемещает указатель на одну строку вверх func selector_up(): - var i_rows: = [] - get_selected_rows(i_rows) - for i in i_rows: - set_row_selected(i, false) - var rows_count: = get_rows_count() - if not (i_rows.size() and (rows_count > 1)): - return - var i_row = i_rows[0] - i_row -= 1 - if i_row < 0: - i_row = rows_count - 1 - set_row_selected(i_row, true) - var node = get_node2(0, i_row) - if scroll is ScrollContainer: - scroll.ensure_control_visible(node) - emit_signal('selected_changed', i_row) + var i_rows: = [] + get_selected_rows(i_rows) + for i in i_rows: + set_row_selected(i, false) + var rows_count: = get_rows_count() + if not (i_rows.size() and (rows_count > 1)): + return + var i_row = i_rows[0] + i_row -= 1 + if i_row < 0: + i_row = rows_count - 1 + set_row_selected(i_row, true) + var node = get_node2(0, i_row) + if scroll is ScrollContainer: + scroll.ensure_control_visible(node) + emit_signal('selected_changed', i_row) ## Перемещает указатель на одну строку вниз func selector_down(): - var i_rows: = [] - get_selected_rows(i_rows) - for i in i_rows: - set_row_selected(i, false) - var rows_count: = get_rows_count() - if not (i_rows.size() and (rows_count > 1)): - return - var i_row = i_rows[0] - i_row += 1 - i_row %= rows_count - set_row_selected(i_row, true) - var node = get_node2(0, i_row) - if scroll is ScrollContainer: - scroll.ensure_control_visible(node) - emit_signal('selected_changed', i_row) + var i_rows: = [] + get_selected_rows(i_rows) + for i in i_rows: + set_row_selected(i, false) + var rows_count: = get_rows_count() + if not (i_rows.size() and (rows_count > 1)): + return + var i_row = i_rows[0] + i_row += 1 + i_row %= rows_count + set_row_selected(i_row, true) + var node = get_node2(0, i_row) + if scroll is ScrollContainer: + scroll.ensure_control_visible(node) + emit_signal('selected_changed', i_row) ## Делает ряд [param i_row] видимым. func ensure_row_visible(i_row: int): - assert(scroll is ScrollContainer, 'Таблица должна быть дочерней к ScrollContainer') - var key: = get_node_key(NODE_KEY_FORMAT, 0, i_row) - var node = nodes[key] - scroll.ensure_control_visible(node) + assert(scroll is ScrollContainer, 'Таблица должна быть дочерней к ScrollContainer') + var key: = get_node_key(NODE_KEY_FORMAT, 0, i_row) + var node = nodes[key] + scroll.ensure_control_visible(node) ## Устанавливает выравнивание из массива [param alignments] для всех колонок. func set_columns_alignments(alignments: Array): - assert(columns == alignments.size()) - var rows_count: = get_rows_count() - for i_row in rows_count: - for i_col in alignments.size(): - set_node_alligment(i_col, i_row, alignments[i_col]) + assert(columns == alignments.size()) + var rows_count: = get_rows_count() + for i_row in rows_count: + for i_col in alignments.size(): + set_node_alligment(i_col, i_row, alignments[i_col]) ## func load_selector_scene(file_name: String): - selector_type_file = file_name - ScnSelector = load(file_name) + selector_type_file = file_name + ScnSelector = load(file_name) ## func load_node_scene(file_name: String): - node_type_file = file_name - ScnNode = load(file_name) + node_type_file = file_name + ScnNode = load(file_name) ## func _ready(): - ScnSelector = load(selector_type_file) - ScnNode = load(node_type_file) - assert(ScnSelector is PackedScene) - assert(ScnNode is PackedScene) + ScnSelector = load(selector_type_file) + ScnNode = load(node_type_file) + assert(ScnSelector is PackedScene) + assert(ScnNode is PackedScene) ## Выравнивает по нижнему левому углу. Используется для выравнивания по заголовку. func align_to_left_bottompos(hdr: Control, gap: = Vector2(0, 0)): - position.x = hdr.position.x + gap.x - position.y = hdr.position.y + hdr.size.y + gap.y + position.x = hdr.position.x + gap.x + position.y = hdr.position.y + hdr.size.y + gap.y ## Устанавливает пользовательские данные для ячейки.[br] @@ -153,9 +153,9 @@ func align_to_left_bottompos(hdr: Control, gap: = Vector2(0, 0)): ## [param pname] - Имя пользовательских данных.[br] ## [param val] - Значение пользовательских данных.[br] func set_node_meta(i_col: int, i_row: int, pname: String, val) -> void: - var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) - var node = nodes[key] - node.set_meta(pname, val) + var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) + var node = nodes[key] + node.set_meta(pname, val) ## Возвращает пользовательские данные для ячейки.[br] @@ -163,10 +163,10 @@ func set_node_meta(i_col: int, i_row: int, pname: String, val) -> void: ## [param i_row] - Индекс ряда.[br] ## [param pname] - Имя пользовательских данных.[br] func get_node_meta(i_col: int, i_row: int, pname: String): - var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) - var node = nodes[key] - assert_meta(i_col, i_row, pname, node) - return node.get_meta(pname) + var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) + var node = nodes[key] + assert_meta(i_col, i_row, pname, node) + return node.get_meta(pname) ## Устанавливает свойство ячейки.[br] @@ -176,9 +176,9 @@ func get_node_meta(i_col: int, i_row: int, pname: String): ## [param val] - Значение свойства.[br] ## [i]Свойство[/i] [param pname] [i]ячейки должно существовать.[/i] func set_node_property(i_col: int, i_row: int, pname: String, val) -> void: - var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) - var node = nodes[key] - node.set(pname, val) + var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) + var node = nodes[key] + node.set(pname, val) ## Возвращает значение поля ячейки.[br] @@ -186,9 +186,9 @@ func set_node_property(i_col: int, i_row: int, pname: String, val) -> void: ## [param i_col] - Индекс колонки.[br] ## [param i_row] - Индекс ряда. func get_node_property(i_col: int, i_row: int, pname: String): - var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) - var node = nodes[key] - return node.get(pname) + var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) + var node = nodes[key] + return node.get(pname) ## Возвращает ключ для существующей ячейки. Если ячейки с заданными индексами нет, @@ -196,313 +196,313 @@ func get_node_property(i_col: int, i_row: int, pname: String): ## [param i_col] - Индекс колонки.[br] ## [param i_row] - Индекс ряда func get_node_key(format: String, i_col: int, i_row: int) -> String: - var key: String = format % [i_row, i_col] - assert(nodes.has(key), 'ошибка: нет такой ячейки - колонка:%d ряд:%d' % [i_col, i_row]) - return key + var key: String = format % [i_row, i_col] + assert(nodes.has(key), 'ошибка: нет такой ячейки - колонка:%d ряд:%d' % [i_col, i_row]) + return key ## Возвращает массив с индексом ячейки:[br] ## [b][0][/b] - индекс колонки,[br] ## [b][1][/b] - индекс строки. func get_key_indexes(key: String) -> Array: - var tokens: = key.split('_') - assert(tokens.size() == 3, 'ошибка: неверный ключ: \"%s\"' % key) - return [int(tokens[2]), int(tokens[1])] + var tokens: = key.split('_') + assert(tokens.size() == 3, 'ошибка: неверный ключ: \"%s\"' % key) + return [int(tokens[2]), int(tokens[1])] ## func set_columns_min_size(sizes: Array) -> void: - assert((columns == 1) or (columns == sizes.size())) - columns = sizes.size() - var rows_count: = get_rows_count() - columns_min_size.clear() - for sz in sizes: - columns_min_size.append(sz) - for i_row in rows_count: - set_nodes_min_size(i_row, sizes, NODE_KEY_FORMAT) - if nodes.has(HEAD_KEY_FORMAT % [0, 0]): - set_nodes_min_size(0, sizes, HEAD_KEY_FORMAT) + assert((columns == 1) or (columns == sizes.size())) + columns = sizes.size() + var rows_count: = get_rows_count() + columns_min_size.clear() + for sz in sizes: + columns_min_size.append(sz) + for i_row in rows_count: + set_nodes_min_size(i_row, sizes, NODE_KEY_FORMAT) + if nodes.has(HEAD_KEY_FORMAT % [0, 0]): + set_nodes_min_size(0, sizes, HEAD_KEY_FORMAT) ## func set_columns_aligment(alignments: Array) -> void: - var rows_count: = get_rows_count() - for i_row in rows_count: - for i_col in alignments.size(): - set_node_alligment(i_col, i_row, alignments[i_col]) + var rows_count: = get_rows_count() + for i_row in rows_count: + for i_col in alignments.size(): + set_node_alligment(i_col, i_row, alignments[i_col]) ## func _input(event) -> void: - if not is_visible_in_tree(): return - if event is InputEventKey: - if event.pressed and in_focus: - if event.keycode == selector_key_up: - selector_up() - elif event.keycode == selector_key_down: - selector_down() - elif event is InputEventMouseButton: - if not event.pressed: - return - if check_event_pos(event.position, scroll): - if not in_focus: - in_focus = true - emit_signal('focus_entered') - elif not check_event_pos(event.position, scroll): - if in_focus: - in_focus = false - emit_signal('focus_exited') + if not is_visible_in_tree(): return + if event is InputEventKey: + if event.pressed and in_focus: + if event.keycode == selector_key_up: + selector_up() + elif event.keycode == selector_key_down: + selector_down() + elif event is InputEventMouseButton: + if not event.pressed: + return + if check_event_pos(event.position, scroll): + if not in_focus: + in_focus = true + emit_signal('focus_entered') + elif not check_event_pos(event.position, scroll): + if in_focus: + in_focus = false + emit_signal('focus_exited') ## func _init() -> void: - selector_color_proc = Callable(self, 'defaultselector_color_proc') - assert(selector_color_proc.is_valid()) - selector_size_proc = Callable(self, 'default_selector_size_proc') - assert(selector_size_proc.is_valid()) - scroll = self + selector_color_proc = Callable(self, 'defaultselector_color_proc') + assert(selector_color_proc.is_valid()) + selector_size_proc = Callable(self, 'default_selector_size_proc') + assert(selector_size_proc.is_valid()) + scroll = self ## func check_event_pos(event_pos: Vector2, obj: Control): - return \ - (event_pos.x > obj.position.x) \ - and (event_pos.x < (obj.position.x + obj.size.x)) \ - and (event_pos.y > obj.position.y) \ - and (event_pos.y < (obj.position.y + obj.size.y)) + return \ + (event_pos.x > obj.position.x) \ + and (event_pos.x < (obj.position.x + obj.size.x)) \ + and (event_pos.y > obj.position.y) \ + and (event_pos.y < (obj.position.y + obj.size.y)) ## func setselector_color_proc(instance: Object, function: String) -> void: - selector_color_proc = Callable(instance, function) - assert(selector_color_proc.is_valid()) + selector_color_proc = Callable(instance, function) + assert(selector_color_proc.is_valid()) ## func set_selector_size_proc(instance: Object, function: String) -> void: - selector_size_proc = Callable(instance, function) - assert(selector_size_proc.is_valid()) + selector_size_proc = Callable(instance, function) + assert(selector_size_proc.is_valid()) ## [param i_row] - Индекс ряда func default_selector_size_proc(i_row: int, rect: Array) -> void: - get_row_rect(i_row, rect) - rect[0] += position.x - node_gap.x / 2.0 - rect[1] += position.y - node_gap.y / 2.0 - rect[2] += node_gap.x - rect[3] += node_gap.y + get_row_rect(i_row, rect) + rect[0] += position.x - node_gap.x / 2.0 + rect[1] += position.y - node_gap.y / 2.0 + rect[2] += node_gap.x + rect[3] += node_gap.y ## func clear_rows_selected(): - var rows_count: = get_rows_count() - for i_row in rows_count: - set_row_selected(i_row, false) + var rows_count: = get_rows_count() + for i_row in rows_count: + set_row_selected(i_row, false) ## [param i_row] - Индекс ряда. func set_row_selected(i_row: int, selected: bool = true) -> void: - call_deferred('_set_row_selected', i_row, selected) + call_deferred('_set_row_selected', i_row, selected) ## [param i_row] - Индекс ряда. func _set_row_selected(i_row: int, selected: bool = true) -> void: - var selector_key: = SELECTOR_KEY_FORMAT % [i_row, 0] - var node_key: = NODE_KEY_FORMAT % [i_row, 0] - if not nodes.has(node_key): - push_error('ошибка: нет такого ряда: %d' % i_row) - return - if nodes.has(selector_key) and (not selected): - var node = nodes[node_key] - var selector = nodes[selector_key] - node.remove_child(selector) - selector.queue_free() - nodes.erase(selector_key) - elif (not nodes.has(selector_key)) and selected: - var selector = ScnSelector.instantiate() - var rect: = [0, 0, 0, 0] - selector_size_proc.call(i_row, rect) - selector.size.x = rect[2] - selector.size.y = rect[3] - selector.modulate = selector_color_proc.call(i_row) - selector.z_index = z_index + 1 - nodes[selector_key] = selector - var node = nodes[node_key] - node.add_child(selector) + var selector_key: = SELECTOR_KEY_FORMAT % [i_row, 0] + var node_key: = NODE_KEY_FORMAT % [i_row, 0] + if not nodes.has(node_key): + push_error('ошибка: нет такого ряда: %d' % i_row) + return + if nodes.has(selector_key) and (not selected): + var node = nodes[node_key] + var selector = nodes[selector_key] + node.remove_child(selector) + selector.queue_free() + nodes.erase(selector_key) + elif (not nodes.has(selector_key)) and selected: + var selector = ScnSelector.instantiate() + var rect: = [0, 0, 0, 0] + selector_size_proc.call(i_row, rect) + selector.size.x = rect[2] + selector.size.y = rect[3] + selector.modulate = selector_color_proc.call(i_row) + selector.z_index = z_index + 1 + nodes[selector_key] = selector + var node = nodes[node_key] + node.add_child(selector) ## [param i_row] - Индекс ряда. func is_row_selected(i_row: int) -> bool: - var key: = SELECTOR_KEY_FORMAT % [i_row, 0] - return nodes.has(key) + var key: = SELECTOR_KEY_FORMAT % [i_row, 0] + return nodes.has(key) ## [param i_row] - Индексы рядов. func get_selected_rows(i_rows: Array) -> void: - for key in nodes.keys(): - if SLCT_PREFIX in key: - var indexes = get_key_indexes(key) - i_rows.append(indexes[1]) + for key in nodes.keys(): + if SLCT_PREFIX in key: + var indexes = get_key_indexes(key) + i_rows.append(indexes[1]) ## Возвращает количество рядов func get_rows_count() -> int: - var nodes_count: int = 0 - for key in nodes: - nodes_count += int(NODE_PREFIX in key) - return nodes_count / columns + var nodes_count: int = 0 + for key in nodes: + nodes_count += int(NODE_PREFIX in key) + return nodes_count / columns ## [param i_col] - Индекс колонки. [param i_row] - Индекс ряда. func get_node2(i_col: int, i_row: int) -> Node: - var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) - return nodes[key] + var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) + return nodes[key] ## Добавляет ряд. [param row_types] - Массив типов ячеек ряда func add_row(row_types: Array) -> void: - assert(row_types.size() < MAX_INDEX, 'ошибка: слишком много колонок, больше %d' % (MAX_INDEX + 1)) - if columns == 1: - set_columns(row_types.size()) - else: - assert(columns == row_types.size(), 'ошибка: %s количество колонок установлено ранее - %d, но требуется %d' % [self, columns, row_types.size()]) - var i_row: int = get_rows_count() - assert(i_row < MAX_INDEX, 'ошибка: слишком много рядов, больше %d' % (MAX_INDEX + 1)) - var allow_set_size = columns_min_size.size() >= row_types.size() - for i_col in columns: - var node_type = row_types[i_col] - var node = node_type.instantiate() - assert(node.name == node.name, 'ошибка: экземпляр не имеет поля \"name\"') - var node_key: String = NODE_KEY_FORMAT % [i_row, i_col] - node.name = node_key - add_child(node) - if allow_set_size: - node.custom_minimum_size.x = columns_min_size[i_col] - node.set_deferred('size.x', columns_min_size[i_col]) - nodes[node_key] = node - var conn = Callable(self, '_on_focus_entered').bind(node) - node.connect('focus_entered', conn) - set_node_meta(i_col, i_row, 'callable_focus_entered', conn) + assert(row_types.size() < MAX_INDEX, 'ошибка: слишком много колонок, больше %d' % (MAX_INDEX + 1)) + if columns == 1: + set_columns(row_types.size()) + else: + assert(columns == row_types.size(), 'ошибка: %s количество колонок установлено ранее - %d, но требуется %d' % [self, columns, row_types.size()]) + var i_row: int = get_rows_count() + assert(i_row < MAX_INDEX, 'ошибка: слишком много рядов, больше %d' % (MAX_INDEX + 1)) + var allow_set_size = columns_min_size.size() >= row_types.size() + for i_col in columns: + var node_type = row_types[i_col] + var node = node_type.instantiate() + assert(node.name == node.name, 'ошибка: экземпляр не имеет поля \"name\"') + var node_key: String = NODE_KEY_FORMAT % [i_row, i_col] + node.name = node_key + add_child(node) + if allow_set_size: + node.custom_minimum_size.x = columns_min_size[i_col] + node.set_deferred('size.x', columns_min_size[i_col]) + nodes[node_key] = node + var conn = Callable(self, '_on_focus_entered').bind(node) + node.connect('focus_entered', conn) + set_node_meta(i_col, i_row, 'callable_focus_entered', conn) ## func _on_focus_entered(node): - var indexes = get_key_indexes(node.name) - emit_signal('row_pressed', indexes[1]) + var indexes = get_key_indexes(node.name) + emit_signal('row_pressed', indexes[1]) ## func set_header(header_types: Array): - assert(header_types.size() < MAX_INDEX, 'ошибка: количество колонок в заголовке: %d больше допустимого: %d' % [header_types.size(), MAX_INDEX + 1]) - if columns == 1: - set_columns(header_types.size()) - else: - assert(columns == header_types.size(), 'ошибка: количество колонок в заголовке %d, но ранее установлено: %d' % [header_types.size(), columns]) - var do_adj: = false - if not header: - header = GridContainer.new() - var parent = scroll.get_parent() - parent.add_child(header) - header.position = position - header.columns = header_types.size() - do_adj = true - else: - var childrens = header.get_children() - for node in childrens: - childrens.erase(node.name) - childrens.remove_child(node) - node.queue_free() - for i_col in columns: - var node = header_types[i_col].instantiate() - var key: String = HEAD_KEY_FORMAT % [0, i_col] - node.name = key - nodes[key] = node - header.add_child(node) - if do_adj: - var rect = [0, 0, 0, 0] - get_node_rect(0, 0, rect, HEAD_KEY_FORMAT) - position.y += rect[3] + assert(header_types.size() < MAX_INDEX, 'ошибка: количество колонок в заголовке: %d больше допустимого: %d' % [header_types.size(), MAX_INDEX + 1]) + if columns == 1: + set_columns(header_types.size()) + else: + assert(columns == header_types.size(), 'ошибка: количество колонок в заголовке %d, но ранее установлено: %d' % [header_types.size(), columns]) + var do_adj: = false + if not header: + header = GridContainer.new() + var parent = scroll.get_parent() + parent.add_child(header) + header.position = position + header.columns = header_types.size() + do_adj = true + else: + var childrens = header.get_children() + for node in childrens: + childrens.erase(node.name) + childrens.remove_child(node) + node.queue_free() + for i_col in columns: + var node = header_types[i_col].instantiate() + var key: String = HEAD_KEY_FORMAT % [0, i_col] + node.name = key + nodes[key] = node + header.add_child(node) + if do_adj: + var rect = [0, 0, 0, 0] + get_node_rect(0, 0, rect, HEAD_KEY_FORMAT) + position.y += rect[3] ## func set_header_text(headers: Array): - assert(columns == headers.size()) - set_row_text(0, headers, 0, HEAD_KEY_FORMAT) + assert(columns == headers.size()) + set_row_text(0, headers, 0, HEAD_KEY_FORMAT) ## [param i_row] - индекс ряда. func remove_row(i_row: int): - var sel_key: String = SELECTOR_KEY_FORMAT % [i_row, 0] + var sel_key: String = SELECTOR_KEY_FORMAT % [i_row, 0] - if nodes.has(sel_key): - var selector = nodes[sel_key] - var prnt = selector.get_parent() - prnt.remove_child(selector) - selector.queue_free() - nodes.erase(sel_key) + if nodes.has(sel_key): + var selector = nodes[sel_key] + var prnt = selector.get_parent() + prnt.remove_child(selector) + selector.queue_free() + nodes.erase(sel_key) - for i_col in columns: + for i_col in columns: - var node_key: String = NODE_KEY_FORMAT % [i_row, i_col] - var node = nodes[node_key] + var node_key: String = NODE_KEY_FORMAT % [i_row, i_col] + var node = nodes[node_key] - var conn = get_node_meta(i_col, i_row, 'callable_focus_entered') - node.disconnect('focus_entered', conn) + var conn = get_node_meta(i_col, i_row, 'callable_focus_entered') + node.disconnect('focus_entered', conn) - if node.has_meta(META_CALLABLE): - conn = node.get_meta(META_CALLABLE) - var event_name = node.get_meta(META_EVENT_NAME) - node.disconnect(event_name, conn) + if node.has_meta(META_CALLABLE): + conn = node.get_meta(META_CALLABLE) + var event_name = node.get_meta(META_EVENT_NAME) + node.disconnect(event_name, conn) - remove_child(node) - nodes.erase(node_key) - node.queue_free() - rehash() + remove_child(node) + nodes.erase(node_key) + node.queue_free() + rehash() ## func next_index(indexes: Array) -> void: - indexes[0].x += 1 - if indexes[0].x == columns: - indexes[0].x = 0 - indexes[0].y += 1 + indexes[0].x += 1 + if indexes[0].x == columns: + indexes[0].x = 0 + indexes[0].y += 1 ## func swap_nodes(nodes_copy, node_key_format, i0, i1) -> void: - var key0 = node_key_format % [i0.y, i0.x] - var key1 = node_key_format % [i1.y, i1.x] - nodes_copy[key1] = nodes[key0] - nodes_copy[key1].name = key1 + var key0 = node_key_format % [i0.y, i0.x] + var key1 = node_key_format % [i1.y, i1.x] + nodes_copy[key1] = nodes[key0] + nodes_copy[key1].name = key1 ## Переназначает ключи в соответствии с размерами таблицы. func rehash(): - var i_node: = [Vector2i()] - var i_hdr: = [Vector2i()] - var i_slct: = [Vector2i()] - var nodes_copy = {} - for key in nodes: - var iv = get_key_indexes(key) - var i0 = Vector2(iv[0], iv[1]) - if HEAD_PREFIX in key: - swap_nodes(nodes_copy, HEAD_KEY_FORMAT, i0, i_hdr[0]) - next_index(i_hdr) - elif NODE_PREFIX in key: - swap_nodes(nodes_copy, NODE_KEY_FORMAT, i0, i_node[0]) - next_index(i_node) - elif SLCT_PREFIX in key: - swap_nodes(nodes_copy, SELECTOR_KEY_FORMAT, i0, i_slct[0]) - next_index(i_slct) - nodes.clear() - nodes.merge(nodes_copy) + var i_node: = [Vector2i()] + var i_hdr: = [Vector2i()] + var i_slct: = [Vector2i()] + var nodes_copy = {} + for key in nodes: + var iv = get_key_indexes(key) + var i0 = Vector2(iv[0], iv[1]) + if HEAD_PREFIX in key: + swap_nodes(nodes_copy, HEAD_KEY_FORMAT, i0, i_hdr[0]) + next_index(i_hdr) + elif NODE_PREFIX in key: + swap_nodes(nodes_copy, NODE_KEY_FORMAT, i0, i_node[0]) + next_index(i_node) + elif SLCT_PREFIX in key: + swap_nodes(nodes_copy, SELECTOR_KEY_FORMAT, i0, i_slct[0]) + next_index(i_slct) + nodes.clear() + nodes.merge(nodes_copy) ## Удаляет все строки таблицы func clear_rows() -> void: - var rows_count: int = get_rows_count() - for i_row in range(rows_count - 1, -1, -1): - remove_row(i_row) + var rows_count: int = get_rows_count() + for i_row in range(rows_count - 1, -1, -1): + remove_row(i_row) ## Настраевает обратные вызовы для колонок.[br] @@ -512,57 +512,57 @@ func clear_rows() -> void: ## [param clmns] - Индексы колонок[br] ## [param user_data] - Аргументы функции получателя события[br] func connect_columns(dest_node: Node, event_name: String, callback: String, clmns: Array, user_data: Array = []) -> void: - var rows_count = get_rows_count() - for i_row in rows_count: - for i_col in clmns: - assert(i_col >= 0 and i_col < columns, 'ошибка: нет такой колонки: %d' % i_col) - var node = get_node2(i_col, i_row) - if node.has_meta(META_CALLABLE): - var conn_prev = node.get_meta(META_CALLABLE) - node.disconnect(event_name, conn_prev) - var conn = Callable(dest_node, callback).bind([node] + user_data) - var rc = node.connect(event_name, conn) - if rc == Error.OK: - node.set_meta(META_CALLABLE, conn) - node.set_meta(META_EVENT_NAME, event_name) - else: - var es = error_string(rc) - push_error('ошибка: событие \"%s\" не может быть подключено к %s: %s' % [event_name, node, es]) + var rows_count = get_rows_count() + for i_row in rows_count: + for i_col in clmns: + assert(i_col >= 0 and i_col < columns, 'ошибка: нет такой колонки: %d' % i_col) + var node = get_node2(i_col, i_row) + if node.has_meta(META_CALLABLE): + var conn_prev = node.get_meta(META_CALLABLE) + node.disconnect(event_name, conn_prev) + var conn = Callable(dest_node, callback).bind([node] + user_data) + var rc = node.connect(event_name, conn) + if rc == Error.OK: + node.set_meta(META_CALLABLE, conn) + node.set_meta(META_EVENT_NAME, event_name) + else: + var es = error_string(rc) + push_error('ошибка: событие \"%s\" не может быть подключено к %s: %s' % [event_name, node, es]) ## func set_rows_text(rows: Array) -> void: - for i_row in rows.size(): - for i_col in columns: - var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) - var node = nodes[key] - node.text = rows[i_row][i_col] + for i_row in rows.size(): + for i_col in columns: + var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) + var node = nodes[key] + node.text = rows[i_row][i_col] ## [param i_row] - Индекс ряда. func set_nodes_min_size(i_row: int, sizes: Array, key_format: String = NODE_KEY_FORMAT) -> void: - columns_min_size.clear() - for sz in sizes: - columns_min_size.append(sz) - for i_col in columns: - var key: = get_node_key(key_format, i_col, i_row) - var node = nodes[key] - node.custom_minimum_size.x = sizes[i_col] - node.set_deferred('size.x', sizes[i_col]) + columns_min_size.clear() + for sz in sizes: + columns_min_size.append(sz) + for i_col in columns: + var key: = get_node_key(key_format, i_col, i_row) + var node = nodes[key] + node.custom_minimum_size.x = sizes[i_col] + node.set_deferred('size.x', sizes[i_col]) ## [param i_col] - Индекс колонки.[br][param i_row] - Индекс ряда. func set_node_text(i_col: int, i_row: int, val: String) -> void: - var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) - nodes[key].text = val + var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) + nodes[key].text = val ## func set_rows_state(rows: Array) -> void: - for i_row in rows.size(): - for i_col in columns: - var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) - nodes[key].button_pressed = rows[i_row][i_col] + for i_row in rows.size(): + for i_col in columns: + var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) + nodes[key].button_pressed = rows[i_row][i_col] ## Устанавливает состояние ячейки. @@ -571,59 +571,59 @@ func set_rows_state(rows: Array) -> void: ## [param val] - Новое состояние.[br] ## [i]Ячейка должна иметь свойство[/i] [param button_pressed] func set_node_state(i_col: int, i_row: int, val: bool) -> void: - var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) - nodes[key].button_pressed = val + var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) + nodes[key].button_pressed = val ## [param i_col] - Индекс колонки.[br] ## [param i_row] - Индекс ряда. func set_node_frame(i_col: int, i_row: int, node_name: String, val: int) -> void: - var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) - var node = nodes[key] - var sprite = node.get_node(node_name) - sprite.frame = val + var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) + var node = nodes[key] + var sprite = node.get_node(node_name) + sprite.frame = val ## Разрешает редактирование ячейки. Ячейка должна иметь свойство [param editable].[br] ## [param i_col] - Индекс колонки.[br] ## [param i_row] - Индекс ряда. func set_node_editable(i_col: int, i_row: int, val: bool) -> void: - var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) - nodes[key].editable = val + var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) + nodes[key].editable = val ## Разрешает редактирование ряда. Ячейки ряда должны иметь свойство [param editable].[br] ## [param i_row] - Индекс ряда. func set_row_editable(i_row: int, val: bool) -> void: - for i_col in columns: - var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) - nodes[key].editable = val + for i_col in columns: + var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) + nodes[key].editable = val ## [param i_col] - Индекс колонки.[br] ## [param i_row] - Индекс ряда. func set_node_alligment(i_col: int, i_row: int, val: int) -> void: - var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) - if 'horizontal_alignment' in nodes[key]: - nodes[key].horizontal_alignment = val - elif 'alignment' in nodes[key]: - nodes[key].alignment = val + var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) + if 'horizontal_alignment' in nodes[key]: + nodes[key].horizontal_alignment = val + elif 'alignment' in nodes[key]: + nodes[key].alignment = val ## [param i_row] - Индекс ряда. func set_row_alligments(i_row: int, aligns: Array) -> void: - for i_col in columns: - var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) - nodes[key].align = aligns[i_col] + for i_col in columns: + var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) + nodes[key].align = aligns[i_col] ## [param i_row] - Индекс ряда. func get_row_text(i_row: int) -> Array: - var row: = [] - for i_col in columns: - var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) - row.append(nodes[key].text) - return row + var row: = [] + for i_col in columns: + var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) + row.append(nodes[key].text) + return row ## Устанавливает текст ячеек ряда.[br] @@ -632,51 +632,51 @@ func get_row_text(i_row: int) -> Array: ## [param i_begin] - Начальный индекс колонки начиная с которого установить новые значения.[br] ## [i]Ячейки ряда должны иметь свойство[/i] [param text] func set_row_text(i_row: int, row: Array, i_begin: int = 0, key_format: String = NODE_KEY_FORMAT) -> void: - assert((i_begin >= 0) and (i_begin < columns)) - assert((columns - i_begin) >= row.size()) - for i_col in row.size(): - var key: = get_node_key(key_format, i_col + i_begin, i_row) - nodes[key].text = row[i_col] + assert((i_begin >= 0) and (i_begin < columns)) + assert((columns - i_begin) >= row.size()) + for i_col in row.size(): + var key: = get_node_key(key_format, i_col + i_begin, i_row) + nodes[key].text = row[i_col] ## Возвращает все строки таблицы в двумерном массиве func get_rows_text() -> Array: - var rows_count: = get_rows_count() - var rows: = [] - for i_row in rows_count: - var row = get_row_text(i_row) - rows.append(row) - return rows + var rows_count: = get_rows_count() + var rows: = [] + for i_row in rows_count: + var row = get_row_text(i_row) + rows.append(row) + return rows ## Возвращает массив ячеек из ряда [param i_row] func get_row(i_row: int) -> Array: - var row: = [] - for i_col in columns: - var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) - row.append(nodes[key]) - return row + var row: = [] + for i_col in columns: + var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) + row.append(nodes[key]) + return row ## Возвращает прямоугольник [param rect] занимаемый ячейкой в ряду [param i_row] и колонке [param i_col]. ## [param key_format] - Формат ключа ячейки func get_node_rect(i_col: int, i_row: int, rect: Array, key_format: String = NODE_KEY_FORMAT) -> void: - assert(rect.size() >= 4, 'ошибка: мало элементов для возврата результата') - var key: = get_node_key(key_format, i_col, i_row) - var cell = nodes[key] - rect[0] = cell.position.x - rect[1] = cell.position.y - rect[2] = cell.size.x - rect[3] = cell.size.y + assert(rect.size() >= 4, 'ошибка: мало элементов для возврата результата') + var key: = get_node_key(key_format, i_col, i_row) + var cell = nodes[key] + rect[0] = cell.position.x + rect[1] = cell.position.y + rect[2] = cell.size.x + rect[3] = cell.size.y ## Возвращает прямоугольник [param rect] занимаемый рядом [param i_row].[br] ## [param key_format] - Формат ключа ячейки func get_row_rect(i_row: int, rect: Array, key_format: String = NODE_KEY_FORMAT) -> void: - assert(rect.size() >= 4, 'ошибка: мало элементов для возврата результата') - var cell0 = nodes[get_node_key(key_format, 0, i_row)] - var cell1 = nodes[get_node_key(key_format, columns - 1, i_row)] - rect[0] = cell0.position.x + scroll_adj.x - rect[1] = cell0.position.y + scroll_adj.y - rect[2] = (cell1.position.x - cell0.position.x) + cell1.size.x - rect[3] = cell0.size.y + assert(rect.size() >= 4, 'ошибка: мало элементов для возврата результата') + var cell0 = nodes[get_node_key(key_format, 0, i_row)] + var cell1 = nodes[get_node_key(key_format, columns - 1, i_row)] + rect[0] = cell0.position.x + scroll_adj.x + rect[1] = cell0.position.y + scroll_adj.y + rect[2] = (cell1.position.x - cell0.position.x) + cell1.size.x + rect[3] = cell0.size.y From 596127b371351a0aebb4b6e46692b63de2e1c936 Mon Sep 17 00:00:00 2001 From: sasha80 Date: Mon, 29 Jul 2024 09:45:50 +0300 Subject: [PATCH 10/20] =?UTF-8?q?=D0=A0=D0=B5=D0=B3=D1=80=D0=B5=D1=81?= =?UTF-8?q?=D1=81=D0=B8=D1=8F.=20=D0=A7=D0=B8=D1=81=D1=82=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scenes/работа/работа.gd | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/scenes/работа/работа.gd b/scenes/работа/работа.gd index c2da2304..32ed78a6 100644 --- a/scenes/работа/работа.gd +++ b/scenes/работа/работа.gd @@ -149,22 +149,15 @@ func _ready(): signaller.connect('interfer_off_all', Callable(self, 'on_interfer_off_all')) signaller.connect('fs_selected', Callable(self, 'on_fs_selected')) interfer.connect('interfer_update', Callable(self, 'on_interfer_new')) - var items_list = [] - recurse(get_tree().get_root(), items_list) - print_debug(items_list) -func recurse(obj, items_list: Array): - var childrens = obj.get_children() - for ch in childrens: - items_list.append(ch) - recurse(ch, items_list) - + func get_all_nodes(root): var nodes = [] nodes.append(root) for child in root.get_children(): nodes.append_array(get_all_nodes(child)) return nodes + func on_enter_tree() -> void: call_deferred('_on_btn_scale_pressed') From af3ceeb2b3a5f60a9eb512cf76a23897894b2d3b Mon Sep 17 00:00:00 2001 From: sasha80 Date: Mon, 29 Jul 2024 10:06:46 +0300 Subject: [PATCH 11/20] =?UTF-8?q?=D0=A0=D0=B5=D0=B3=D1=80=D0=B5=D1=81?= =?UTF-8?q?=D1=81=D0=B8=D1=8F.=20=D0=9D=D0=B5=D0=B2=D0=B5=D1=80=D0=BD?= =?UTF-8?q?=D1=8B=D0=B9=20=D1=81=D0=B8=D0=BC=D0=B2=D0=BE=D0=BB=20=D0=BE?= =?UTF-8?q?=D1=82=D1=81=D1=82=D1=83=D0=BF=D0=B0,=20=D0=B4=D0=BE=D0=BB?= =?UTF-8?q?=D0=B6=D0=B5=D0=BD=20=D0=B1=D1=8B=D1=82=D1=8C=20"=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D0=B1=D0=B5=D0=BB".?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addons/godot-logger/scripts/logger.gd | 266 +++--- addons/godot-logger/scripts/logger_output.gd | 72 +- scenes/button-flat/button-flat.tscn | 2 +- scenes/button-flat/nine-patch-button.gd | 66 +- scenes/diagram/diagram.gd | 24 +- scenes/frame-ecm-list/frm-ecm-list.gd | 8 +- scenes/frame-threats/scroll-threats.gd | 96 +-- scenes/frame/frame.gd | 12 +- scenes/pribor-afsp/pribor-afsp.gd | 10 +- scenes/pribor-rtr/pribor-rtr.gd | 194 ++--- scenes/tabs-switch/tab-switch.gd | 6 +- scenes/tilemap/tilemap.gd | 240 +++--- scenes/tilemap/tilemap.tscn | 4 +- scenes/журнал/журнал.gd | 68 +- scenes/контроль/кассета.gd | 10 +- scenes/контроль/кнопка-прибора.gd | 8 +- scenes/контроль/контроль.gd | 76 +- scenes/контроль/прибор.gd | 18 +- scenes/настройки/настройки.gd | 20 +- scenes/работа/sld-mode.gd | 14 +- scenes/работа/работа.gd | 572 ++++++------- scenes/эмс/эмс.gd | 446 +++++----- scripts/capsrpb.gd | 70 +- scripts/interfer.gd | 384 ++++----- scripts/mercator.gd | 852 +++++++++---------- scripts/modbus.gd | 230 ++--- scripts/repnetwork.gd | 236 ++--- scripts/repsettings.gd | 60 +- scripts/tcp5p28.gd | 330 +++---- scripts/threats.gd | 174 ++-- scripts/tools.gd | 102 +-- scripts/yau07.gd | 398 ++++----- scripts/yems-devs.gd | 56 +- table/table.gd | 714 ++++++++-------- 34 files changed, 2918 insertions(+), 2920 deletions(-) diff --git a/addons/godot-logger/scripts/logger.gd b/addons/godot-logger/scripts/logger.gd index 2f1ba8f6..2a09e02e 100644 --- a/addons/godot-logger/scripts/logger.gd +++ b/addons/godot-logger/scripts/logger.gd @@ -9,12 +9,12 @@ signal logged(message: Dictionary) enum { - INFO = 1, ## Информационный уровень. - DEBUG = 1<<1, ## Уровень предупреждения. - WARNING = 1<<2, ## Уровень отладки. - ERROR = 1<<3, ## Уровень ошибок. - FATAL = 1<<4, ## Критический уровень. - MAX = FATAL, ## Побитовый сдвиг влево для пользовательских уровней. + INFO = 1, ## Информационный уровень. + DEBUG = 1<<1, ## Уровень предупреждения. + WARNING = 1<<2, ## Уровень отладки. + ERROR = 1<<3, ## Уровень ошибок. + FATAL = 1<<4, ## Критический уровень. + MAX = FATAL, ## Побитовый сдвиг влево для пользовательских уровней. } @@ -31,235 +31,235 @@ var _format_default_output : String var _format_file : String var _names = { - INFO: "ИНФО", - DEBUG: "ОТЛАДКА", - WARNING: "ВНИМАНИЕ", - ERROR: "ОШИБКА", - FATAL: "АВАРИЯ" + INFO: "ИНФО", + DEBUG: "ОТЛАДКА", + WARNING: "ВНИМАНИЕ", + ERROR: "ОШИБКА", + FATAL: "АВАРИЯ" } var _msg_array: Array var _filters: Array ## Преопределение пользовательских значений по умолчанию. func _enter_tree() -> void: - set_log_enabled(ProjectSettings.get_setting("plugins/logger/log_enabled", true)) - set_default_output_enabled(ProjectSettings.get_setting("plugins/logger/default_output", false)) + set_log_enabled(ProjectSettings.get_setting("plugins/logger/log_enabled", true)) + set_default_output_enabled(ProjectSettings.get_setting("plugins/logger/default_output", false)) - _level = ProjectSettings.get_setting("plugins/logger/level", INFO | DEBUG | WARNING | ERROR | FATAL) + _level = ProjectSettings.get_setting("plugins/logger/level", INFO | DEBUG | WARNING | ERROR | FATAL) - set_file_path(ProjectSettings.get_setting("plugins/logger/file/file_path", "user://uarep-ctl.log")) - set_file_write_enabled(ProjectSettings.get_setting("plugins/logger/file/log_file_write", true)) + set_file_path(ProjectSettings.get_setting("plugins/logger/file/file_path", "user://uarep-ctl.log")) + set_file_write_enabled(ProjectSettings.get_setting("plugins/logger/file/log_file_write", true)) - _format_default_output = ProjectSettings.get_setting("plugins/logger/default_output_format", "[{year}-{month}-{day} {hour}:{minute}:{second}][{level}]{text}") - _format_file = ProjectSettings.get_setting("plugins/logger/file_format", "[{year}-{month}-{day} {hour}:{minute}:{second}][{level}]{text}") + _format_default_output = ProjectSettings.get_setting("plugins/logger/default_output_format", "[{year}-{month}-{day} {hour}:{minute}:{second}][{level}]{text}") + _format_file = ProjectSettings.get_setting("plugins/logger/file_format", "[{year}-{month}-{day} {hour}:{minute}:{second}][{level}]{text}") func _exit_tree() -> void: - _close_file() + _close_file() ## Возвращает [param true], если у журнала события есть уровень. func has_level(level: int) -> bool: - return _names.has(level) + return _names.has(level) ## Установить включенный уровень. func set_level(level: int, enabled: bool) -> void: - assert(has_level(level), "Invalid level.") - if not has_level(level): - return + assert(has_level(level), "Invalid level.") + if not has_level(level): + return - if enabled: - _level |= level - else: - _level &= ~level + if enabled: + _level |= level + else: + _level &= ~level ## Добавить пользовательский уровень журнала. Значение уровня должно быть уникальным и превышать [member MAX]. ## Для вызова пользовательского уровня используйте метод - [method message]. func add_level(level: int, nm: String) -> void: - assert(not has_level(level), "Has level.") - assert(level > MAX and not level % 2, "Invalid level.") - assert(name, "Invalid name.") + assert(not has_level(level), "Has level.") + assert(level > MAX and not level % 2, "Invalid level.") + assert(name, "Invalid name.") - if not has_level(level) and level > MAX and not level % MAX and not name.is_empty(): - _names[level] = nm - set_level(level, true) + if not has_level(level) and level > MAX and not level % MAX and not name.is_empty(): + _names[level] = nm + set_level(level, true) ## Возвращает имя уровня. func get_level_name(level: int) -> String: - return _names[level] + return _names[level] ## Установить включенный журнал. Отключить ВСЕ сообщения. func set_log_enabled(enabled: bool) -> void: - _log_enabled = enabled + _log_enabled = enabled ## Возвращает [param true], если журнал включен. func is_log_enabled() -> bool: - return _log_enabled + return _log_enabled ## Установка выхода по умолчанию включена. func set_default_output_enabled(enabled: bool) -> void: - _default_output_enabled = enabled + _default_output_enabled = enabled ## Возвращает [param true], если вывод по умолчанию включен. func is_default_output_enabled() -> bool: - return _default_output_enabled + return _default_output_enabled ## Установить разрешение на запись файла. func set_file_write_enabled(enabled: bool) -> void: - if _file_write_enabled == enabled: - return + if _file_write_enabled == enabled: + return - _file_write_enabled = enabled + _file_write_enabled = enabled - if enabled: - _open_file() - else: - _close_file() + if enabled: + _open_file() + else: + _close_file() ## Возвращает [param true], если запись в файл разрешена. func is_file_write_enabled() -> bool: - return _file_write_enabled + return _file_write_enabled ## Задать путь к файлу журнала. func set_file_path(path: String) -> void: - assert(path.is_absolute_path(), "Invalid path.") - if not path.is_absolute_path() or _file_path == path: - return + assert(path.is_absolute_path(), "Invalid path.") + if not path.is_absolute_path() or _file_path == path: + return - _file_path = path + _file_path = path - if is_file_write_enabled(): - _open_file() + if is_file_write_enabled(): + _open_file() ## Возвращает путь к файлу журнала. func get_file_path() -> String: - return _file_path + return _file_path ## Создать информационное сообщение. func info(text: String) -> void: - message(INFO, text) + message(INFO, text) ## Создать отладочное сообщение. Только для отладочной сборки. func debug(text: String) -> void: - if OS.is_debug_build(): - message(DEBUG, text) + if OS.is_debug_build(): + message(DEBUG, text) ## Создать предупреждающее сообщение. func warning(text: String) -> void: - message(WARNING, text) + message(WARNING, text) ## Создать сообщение об ошибке. func error(text: String) -> void: - message(ERROR, text) + message(ERROR, text) ## Создать сообщение о критической ошибке. func fatal(text: String) -> void: - message(FATAL, text) + message(FATAL, text) ## Форматировать строку для файла. func format_file(msg: Dictionary) -> String: - return _format_file.format( - { - "year": "%02d" % msg["year"], - "month": "%02d" % msg["month"], - "day": "%02d" % msg["day"], - "hour": "%02d" % msg["hour"], - "minute":"%02d" % msg["minute"], - "second":"%02d" % msg["second"], - "level": _names[msg["level"]], - "text": msg["text"], - } - ) + return _format_file.format( + { + "year": "%02d" % msg["year"], + "month": "%02d" % msg["month"], + "day": "%02d" % msg["day"], + "hour": "%02d" % msg["hour"], + "minute":"%02d" % msg["minute"], + "second":"%02d" % msg["second"], + "level": _names[msg["level"]], + "text": msg["text"], + } + ) ## Форматировать строку для вывода редактора. func format_stdout(msg: Dictionary) -> String: - return _format_default_output.format( - { - "year": "%02d" % msg["year"], - "month": "%02d" % msg["month"], - "day": "%02d" % msg["day"], - "hour": "%02d" % msg["hour"], - "minute":"%02d" % msg["minute"], - "second":"%02d" % msg["second"], - "level": _names[msg["level"]], - "text": msg["text"], - } - ) + return _format_default_output.format( + { + "year": "%02d" % msg["year"], + "month": "%02d" % msg["month"], + "day": "%02d" % msg["day"], + "hour": "%02d" % msg["hour"], + "minute":"%02d" % msg["minute"], + "second":"%02d" % msg["second"], + "level": _names[msg["level"]], + "text": msg["text"], + } + ) ## Создать сообщение с пользовательским уровнем. func message(level: int, text: String) -> void: - assert(has_level(level), "Has not level.") - assert(text, "Invalid message text.") + assert(has_level(level), "Has not level.") + assert(text, "Invalid message text.") - if _log_enabled and _level & level: - var msg : Dictionary = Time.get_datetime_dict_from_system() - msg["level"] = level - msg["level_name"] = _names[level] - msg["text"] = " " + text + if _log_enabled and _level & level: + var msg : Dictionary = Time.get_datetime_dict_from_system() + msg["level"] = level + msg["level_name"] = _names[level] + msg["text"] = " " + text - _msg_array.append(msg) - if (_flt_msg(msg)): - logged.emit(msg) + _msg_array.append(msg) + if (_flt_msg(msg)): + logged.emit(msg) - if _file_write_enabled: - _file.store_line(format_file(msg)) - _file.call_deferred('flush') + if _file_write_enabled: + _file.store_line(format_file(msg)) + _file.call_deferred('flush') - if _default_output_enabled: - print(format_stdout(msg)) + if _default_output_enabled: + print(format_stdout(msg)) func _open_file() -> void: - if is_instance_valid(_file) and _file.is_open(): - _file.close() + if is_instance_valid(_file) and _file.is_open(): + _file.close() - _file = FileAccess.open(get_file_path(), FileAccess.READ_WRITE) + _file = FileAccess.open(get_file_path(), FileAccess.READ_WRITE) - var err := FileAccess.get_open_error() - if err: - _file = FileAccess.open(get_file_path(), FileAccess.WRITE) - _file.close() - _file = FileAccess.open(get_file_path(), FileAccess.READ_WRITE) - return print_debug(error_string(err)) + var err := FileAccess.get_open_error() + if err: + _file = FileAccess.open(get_file_path(), FileAccess.WRITE) + _file.close() + _file = FileAccess.open(get_file_path(), FileAccess.READ_WRITE) + return print_debug(error_string(err)) - err = _file.get_error() - if err: - return print_debug(error_string(err)) - _file.seek_end(0) + err = _file.get_error() + if err: + return print_debug(error_string(err)) + _file.seek_end(0) func _close_file() -> void: - if is_instance_valid(_file) and _file.is_open(): - _file = null + if is_instance_valid(_file) and _file.is_open(): + _file = null func save_file(path): - var f = FileAccess.open(path, FileAccess.WRITE) - var err := FileAccess.get_open_error() - if err: - print_debug(error_string(err)) - var log_text = _file.get_as_text() - f.store_line(log_text) - f.close() + var f = FileAccess.open(path, FileAccess.WRITE) + var err := FileAccess.get_open_error() + if err: + print_debug(error_string(err)) + var log_text = _file.get_as_text() + f.store_line(log_text) + f.close() func set_filtrs(flt_array: Array): - _filters = flt_array + _filters = flt_array func _flt_msg(msg): - var flt_arr: Array = [] - for item in _filters: - if item.button_pressed: - flt_arr.append(item.get_meta('filter')) - if msg['text'].find(item.get_meta('filter')) != -1: - return true - if msg['level_name'] in flt_arr: - return true - return false + var flt_arr: Array = [] + for item in _filters: + if item.button_pressed: + flt_arr.append(item.get_meta('filter')) + if msg['text'].find(item.get_meta('filter')) != -1: + return true + if msg['level_name'] in flt_arr: + return true + return false func add_msgs(): - for msg in _msg_array: - if (_flt_msg(msg)): - logged.emit(msg) + for msg in _msg_array: + if (_flt_msg(msg)): + logged.emit(msg) diff --git a/addons/godot-logger/scripts/logger_output.gd b/addons/godot-logger/scripts/logger_output.gd index 768248a8..ae31ad2b 100644 --- a/addons/godot-logger/scripts/logger_output.gd +++ b/addons/godot-logger/scripts/logger_output.gd @@ -11,11 +11,11 @@ signal handled() @export var format_text : String = "[{year}-{month}-{day} {hour}:{minute}:{second}][{level}]{text}" @export var colors : Dictionary = { - Logger.INFO: Color.WHITE, - Logger.DEBUG: Color.GRAY, - Logger.WARNING: Color.YELLOW, - Logger.ERROR: Color.RED, - Logger.FATAL: Color.RED, + Logger.INFO: Color.WHITE, + Logger.DEBUG: Color.GRAY, + Logger.WARNING: Color.YELLOW, + Logger.ERROR: Color.RED, + Logger.FATAL: Color.RED, } @@ -23,55 +23,55 @@ var _logger : Logger func _enter_tree() -> void: - var logger := get_node_or_null("/root/log") as Logger - if is_instance_valid(logger): - set_logger(logger) + var logger := get_node_or_null("/root/log") as Logger + if is_instance_valid(logger): + set_logger(logger) func _exit_tree() -> void: - if is_instance_valid(_logger) and _logger.logged.is_connected(handle_message): - _logger.logged.disconnect(handle_message) + if is_instance_valid(_logger) and _logger.logged.is_connected(handle_message): + _logger.logged.disconnect(handle_message) ## Вернуть цвет уровня или [member Color.WHITE]. func get_color(level: int) -> Color: - return colors.get(level, Color.WHITE) + return colors.get(level, Color.WHITE) ## Установить журнал событий. func set_logger(logger: Logger) -> void: - assert(is_instance_valid(logger), "Invalid Logger.") - if not is_instance_valid(logger) or is_same(_logger, logger): - return + assert(is_instance_valid(logger), "Invalid Logger.") + if not is_instance_valid(logger) or is_same(_logger, logger): + return - if is_instance_valid(_logger) and _logger.logged.is_connected(handle_message): - _logger.logged.disconnect(handle_message) + if is_instance_valid(_logger) and _logger.logged.is_connected(handle_message): + _logger.logged.disconnect(handle_message) - if not logger.logged.is_connected(handle_message): - logger.logged.connect(handle_message) + if not logger.logged.is_connected(handle_message): + logger.logged.connect(handle_message) - _logger = logger + _logger = logger ## Вернуть журнал событий. func get_logger() -> Logger: - return _logger + return _logger ## Вернуть строку после форматирования из сообщения. func format(message: Dictionary) -> String: - return format_text.format( - { - "year": "%02d" % message["year"], - "month": "%02d" % message["month"], - "day": "%02d" % message["day"], - "hour": "%02d" % message["hour"], - "minute": "%02d" % message["minute"], - "second": "%02d" % message["second"], - "level": message["level_name"], - "text": message["text"], - } - ) + return format_text.format( + { + "year": "%02d" % message["year"], + "month": "%02d" % message["month"], + "day": "%02d" % message["day"], + "hour": "%02d" % message["hour"], + "minute": "%02d" % message["minute"], + "second": "%02d" % message["second"], + "level": message["level_name"], + "text": message["text"], + } + ) ## Обработать сообщение. func handle_message(message: Dictionary) -> void: - push_color(get_color(message["level"])) - append_text(format(message) + "\n") - pop() - handled.emit() + push_color(get_color(message["level"])) + append_text(format(message) + "\n") + pop() + handled.emit() diff --git a/scenes/button-flat/button-flat.tscn b/scenes/button-flat/button-flat.tscn index 74c54947..5e1913cd 100644 --- a/scenes/button-flat/button-flat.tscn +++ b/scenes/button-flat/button-flat.tscn @@ -13,7 +13,7 @@ shader_parameter/rotation = 0.0 shader_parameter/color = Vector4(1, 1, 1, 0.1) shader_parameter/width = 3.0 shader_parameter/speed = 0.0 -shader_parameter/disabled = false +shader_parameter/disabled = true shader_parameter/scale = Vector2(1, 1) [sub_resource type="PlaceholderTexture2D" id="PlaceholderTexture2D_6nup5"] diff --git a/scenes/button-flat/nine-patch-button.gd b/scenes/button-flat/nine-patch-button.gd index 977151a4..67189a3b 100644 --- a/scenes/button-flat/nine-patch-button.gd +++ b/scenes/button-flat/nine-patch-button.gd @@ -12,66 +12,66 @@ func is_toggle_mode(): return $button.is_toggle_mode() @export var texture_state0: Texture2D = preload('res://data/кнопка-квадрат-0.png'): - set(val): $state.set_texture(val) + set(val): $state.set_texture(val) @export var texture_state1: Texture2D = preload('res://data/кнопка-квадрат-1.png'): - set(val): $state.set_texture(val) + set(val): $state.set_texture(val) @export var strips_rotation: float = 0.0: - set(val): - strips_rotation = val - $back.strips_rotation = deg_to_rad(val) - get: return strips_rotation + set(val): + strips_rotation = val + $back.strips_rotation = deg_to_rad(val) + get: return strips_rotation @export var text: String = '': - set(val): $button.set_text(val) - get: return $button.get_text() + set(val): $button.set_text(val) + get: return $button.get_text() @export var disabled: bool = false: - set(val): - disabled = val - $button.set_disabled(val) - $back.disabled = val - $state.modulate.a = 0.5 if val else 1.0 - get: return disabled + set(val): + disabled = val + $button.set_disabled(val) + $back.disabled = val + $state.modulate.a = 0.5 if val else 1.0 + get: return disabled @export var pressed: bool = false: - set(val): - pressed = val - $button.set_pressed(val) - get: - return pressed + set(val): + pressed = val + $button.set_pressed(val) + get: + return pressed @export var toggle_mode: bool = false: - set(val): - toggle_mode = val - $button.set_toggle_mode(val) - get: - return toggle_mode + set(val): + toggle_mode = val + $button.set_toggle_mode(val) + get: + return toggle_mode func _on_resized() -> void: - $back.scale = size - $back.material.set('shader_parameter/scale', size / Vector2(20.0, 20.0)) + $back.scale = size + $back.material.set('shader_parameter/scale', size / Vector2(20.0, 20.0)) func _enter_tree() -> void: - $back.scale = size - $back.material.set('shader_parameter/scale', size / Vector2(20.0, 20.0)) - $button.set_toggle_mode(true) - $button.connect('toggled', Callable(self, '_on_button_toggled')) + $back.scale = size + $back.material.set('shader_parameter/scale', size / Vector2(20.0, 20.0)) + $button.set_toggle_mode(true) + $button.connect('toggled', Callable(self, '_on_button_toggled')) func set_toggle_mode(val: bool): - $button.set_toggle_mode(val) + $button.set_toggle_mode(val) func _on_button_toggled(val: bool): - if val: $state.set_texture(texture_state1) - else: $state.set_texture(texture_state0) + if val: $state.set_texture(texture_state1) + else: $state.set_texture(texture_state0) diff --git a/scenes/diagram/diagram.gd b/scenes/diagram/diagram.gd index f6af816e..3ae62cc8 100644 --- a/scenes/diagram/diagram.gd +++ b/scenes/diagram/diagram.gd @@ -3,8 +3,8 @@ class_name Diagram extends Sprite2D var fs_default_colors: Array ## Цвета по умолчанию для блоков ФС. func _ready() -> void: - signaller.connect('map_mode_changed', Callable(self, 'set_view_mode')) - set_def_colors() + signaller.connect('map_mode_changed', Callable(self, 'set_view_mode')) + set_def_colors() func sv(val: float) -> float: return val / ($строб.scale.x / 2.0) * 0.125 @@ -32,13 +32,13 @@ func set_band_colors(val: Array): material.set('shader_parameter/ant_band_c', va ## Закрашивание по умолчанию блоков ФС цветами из настроек. func set_def_colors(): - var color0: Vector3 = ProjectSettings.get_setting('application/interfer/default_color0', Vector3(0.38, 0.47, 0.51)) - var color1: Vector3 = ProjectSettings.get_setting('application/interfer/default_color1', Vector3(0.39, 0.44, 0.50)) - var col_arr: Array = [color0, color1] - var col = false - for i in range(7): - for j in range (4): - fs_default_colors.append(col_arr[int(col)]) - col = not col - col = not col - set_band_colors(fs_default_colors) + var color0: Vector3 = ProjectSettings.get_setting('application/interfer/default_color0', Vector3(0.38, 0.47, 0.51)) + var color1: Vector3 = ProjectSettings.get_setting('application/interfer/default_color1', Vector3(0.39, 0.44, 0.50)) + var col_arr: Array = [color0, color1] + var col = false + for i in range(7): + for j in range (4): + fs_default_colors.append(col_arr[int(col)]) + col = not col + col = not col + set_band_colors(fs_default_colors) diff --git a/scenes/frame-ecm-list/frm-ecm-list.gd b/scenes/frame-ecm-list/frm-ecm-list.gd index c64bfce8..c073e96c 100644 --- a/scenes/frame-ecm-list/frm-ecm-list.gd +++ b/scenes/frame-ecm-list/frm-ecm-list.gd @@ -3,10 +3,10 @@ class_name frm_ecm_list extends NinePatchRect ## Called when the node enters the scene tree for the first time. func _ready(): - $grd_forced/btn_cancel.button_connect('pressed', Callable(self, 'on_btn_cancel_pressed').bind($grd_forced/btn_cancel)) + $grd_forced/btn_cancel.button_connect('pressed', Callable(self, 'on_btn_cancel_pressed').bind($grd_forced/btn_cancel)) func on_btn_cancel_pressed(btn): - signaller.emit_signal('interfer_off_all') - await get_tree().create_timer(0.1).timeout - btn.pressed = false + signaller.emit_signal('interfer_off_all') + await get_tree().create_timer(0.1).timeout + btn.pressed = false diff --git a/scenes/frame-threats/scroll-threats.gd b/scenes/frame-threats/scroll-threats.gd index ea5136ba..2440bb39 100644 --- a/scenes/frame-threats/scroll-threats.gd +++ b/scenes/frame-threats/scroll-threats.gd @@ -16,87 +16,87 @@ const TABLE_COLUMN_SIZE = [ 65, 145, 145, 90 ] ## Вызывается при нажатии указателем мыши на выбранный ряд. func on_row_pressed(i_row: int): - row_pressed(i_row) - signaller.emit_signal('rto_threat_sel', $table.get_node_user_data(0, i_row).id) + row_pressed(i_row) + signaller.emit_signal('rto_threat_sel', $table.get_node_user_data(0, i_row).id) func row_pressed(i_row: int): - $table.clear_rows_selected() - $table.set_row_selected(i_row, true) - $table.ensure_row_visible(i_row) - var threat = $table.get_node_user_data(0, i_row) - signaller.emit_signal('threat_selected', threat) + $table.clear_rows_selected() + $table.set_row_selected(i_row, true) + $table.ensure_row_visible(i_row) + var threat = $table.get_node_user_data(0, i_row) + signaller.emit_signal('threat_selected', threat) func init_table(): - $table.set_header(TABLE_HEADER) - $table.set_header_text(TABLE_HEADERS_TEXT) - $table.set_columns_min_size(TABLE_COLUMN_SIZE) - $table.pin_header(self, get_parent()) + $table.set_header(TABLE_HEADER) + $table.set_header_text(TABLE_HEADERS_TEXT) + $table.set_columns_min_size(TABLE_COLUMN_SIZE) + $table.pin_header(self, get_parent()) func _ready() -> void: - var table_index: = Dictionary() - threats.connect('threat_new', Callable(self, 'on_threat_new').bind(table_index)) - threats.connect('threat_lost', Callable(self, 'on_threat_lost').bind(table_index)) - threats.connect('threat_update', Callable(self, 'on_threat_update').bind(table_index)) - signaller.connect('rto_threat_sel', Callable(self, 'on_rto_threat_sel').bind($table)) - signaller.connect('rto_threat_unsel', Callable(self, 'on_rto_threat_unsel')) - $table.connect('row_pressed', Callable(self, 'on_row_pressed')) - call_deferred('init_table') + var table_index: = Dictionary() + threats.connect('threat_new', Callable(self, 'on_threat_new').bind(table_index)) + threats.connect('threat_lost', Callable(self, 'on_threat_lost').bind(table_index)) + threats.connect('threat_update', Callable(self, 'on_threat_update').bind(table_index)) + signaller.connect('rto_threat_sel', Callable(self, 'on_rto_threat_sel').bind($table)) + signaller.connect('rto_threat_unsel', Callable(self, 'on_rto_threat_unsel')) + $table.connect('row_pressed', Callable(self, 'on_row_pressed')) + call_deferred('init_table') ## Перестраивает индекс таблицы. Вызывать при удалении строки. func rehash(table_index: Dictionary): - var rows_count = $table.get_rows_count() - for i_row in rows_count: - var item = $table.get_node2(0, i_row) - table_index[int(item.text)] = i_row + var rows_count = $table.get_rows_count() + for i_row in rows_count: + var item = $table.get_node2(0, i_row) + table_index[int(item.text)] = i_row ## Обрабатывает сигнал [b]threat_update[/b]. func on_threat_update(th, table_index: Dictionary): - var row = map_th_to_row(th) - var i_row = table_index[th.id] - $table.set_row_text(i_row, row) + var row = map_th_to_row(th) + var i_row = table_index[th.id] + $table.set_row_text(i_row, row) ## Обрабатывает сигнал [b]threat_lost[/b]. func on_threat_lost(th, table_index: Dictionary): - $table.remove_row(table_index[th.id]) - table_index.clear() - rehash(table_index) + $table.remove_row(table_index[th.id]) + table_index.clear() + rehash(table_index) ## Обрабатывает сигнал [b]threat_new[/b]. func on_threat_new(th, table_index): - var i_row = $table.get_rows_count() - $table.add_row(TABLE_ROW) - var row = map_th_to_row(th) - $table.set_row_text(i_row, row) - $table.set_row_editable(i_row, false) - $table.set_node_user_data(0, i_row, th) - table_index[th.id] = i_row + var i_row = $table.get_rows_count() + $table.add_row(TABLE_ROW) + var row = map_th_to_row(th) + $table.set_row_text(i_row, row) + $table.set_row_editable(i_row, false) + $table.set_node_user_data(0, i_row, th) + table_index[th.id] = i_row ## Преобразует цель в строку таблицы. func map_th_to_row(th) -> Array: - var row: = Array() - row.resize(TABLE_COLUMN_PARAM.size()) - var i_col: int = 0 - for param_name in TABLE_COLUMN_PARAM: - row[i_col] = '%s' % th.get(param_name) - i_col += 1 - return row + var row: = Array() + row.resize(TABLE_COLUMN_PARAM.size()) + var i_col: int = 0 + for param_name in TABLE_COLUMN_PARAM: + row[i_col] = '%s' % th.get(param_name) + i_col += 1 + return row ## Выделяет выбранную цель в таблице. func on_rto_threat_sel(th_id, tbl): - for i in tbl.get_rows_count(): - var node = tbl.get_node2(0, i) - if node.text.to_int() == th_id: - row_pressed(i) - break + for i in tbl.get_rows_count(): + var node = tbl.get_node2(0, i) + if node.text.to_int() == th_id: + row_pressed(i) + break ## Снимает выделение цели. diff --git a/scenes/frame/frame.gd b/scenes/frame/frame.gd index f51e0506..f83e3a38 100644 --- a/scenes/frame/frame.gd +++ b/scenes/frame/frame.gd @@ -4,15 +4,15 @@ extends NinePatchRect @export var horizontal_alignment: HorizontalAlignment = HorizontalAlignment.HORIZONTAL_ALIGNMENT_CENTER: - get: return $header.horizontal_alignment if is_inside_tree() else HorizontalAlignment.HORIZONTAL_ALIGNMENT_CENTER - set(val): if is_inside_tree(): $header.horizontal_alignment = val + get: return $header.horizontal_alignment if is_inside_tree() else HorizontalAlignment.HORIZONTAL_ALIGNMENT_CENTER + set(val): if is_inside_tree(): $header.horizontal_alignment = val @export var vertical_alignment: VerticalAlignment = VerticalAlignment.VERTICAL_ALIGNMENT_CENTER: - get: return $header.vertical_alignment if is_inside_tree() else VerticalAlignment.VERTICAL_ALIGNMENT_CENTER - set(val): if is_inside_tree(): $header.vertical_alignment = val + get: return $header.vertical_alignment if is_inside_tree() else VerticalAlignment.VERTICAL_ALIGNMENT_CENTER + set(val): if is_inside_tree(): $header.vertical_alignment = val @export var text: String = 'caption text': - get: return $header.text if is_inside_tree() else '' - set(val): if is_inside_tree(): $header.text = val + get: return $header.text if is_inside_tree() else '' + set(val): if is_inside_tree(): $header.text = val diff --git a/scenes/pribor-afsp/pribor-afsp.gd b/scenes/pribor-afsp/pribor-afsp.gd index fb4e4bca..ba0c5654 100644 --- a/scenes/pribor-afsp/pribor-afsp.gd +++ b/scenes/pribor-afsp/pribor-afsp.gd @@ -6,13 +6,13 @@ var test_addr: = String() var test_data: = {} func _ready(): - test_req = HTTPRequest.new() - add_child(test_req) - test_req.request_completed.connect(_map_request_completed) + test_req = HTTPRequest.new() + add_child(test_req) + test_req.request_completed.connect(_map_request_completed) func _map_request_completed(): - pass + pass func on_req_test_data_timer(): - test_req.request(test_addr) + test_req.request(test_addr) diff --git a/scenes/pribor-rtr/pribor-rtr.gd b/scenes/pribor-rtr/pribor-rtr.gd index e0bdd808..71952df0 100644 --- a/scenes/pribor-rtr/pribor-rtr.gd +++ b/scenes/pribor-rtr/pribor-rtr.gd @@ -9,94 +9,94 @@ static var json_parser: JSON = JSON.new() ## [param paths] - Массив содержащий отдельные элементы пути.[br] ## [param tokens] - Разделители используемые во входной строке для разделения элементов пути.[br] func map_solid_to_array(solid: String, paths: Array, tokens: Array = ['[', ']', '.']) -> void: - paths.clear() - var i_chs = [] - for sep in tokens: - var i = 0 - while i != -1: - i = solid.find(sep, i + 1) - i_chs.append(i) - i_chs.pop_back() - i_chs.sort() + paths.clear() + var i_chs = [] + for sep in tokens: + var i = 0 + while i != -1: + i = solid.find(sep, i + 1) + i_chs.append(i) + i_chs.pop_back() + i_chs.sort() - var i0 = 0 - for i1 in i_chs: - var ss: = [String()] - tools.get_string_slice(solid, ss, i0, i1) - if ss[0].is_valid_int(): - paths.append(ss[0].to_int()) - else: - if ss[0]: - paths.append(ss[0]) - i0 = i1 + 1 + var i0 = 0 + for i1 in i_chs: + var ss: = [String()] + tools.get_string_slice(solid, ss, i0, i1) + if ss[0].is_valid_int(): + paths.append(ss[0].to_int()) + else: + if ss[0]: + paths.append(ss[0]) + i0 = i1 + 1 func _on_request_rtr_request_completed(result, _response_code, headers, body: PackedByteArray): - if result != 0: - var rs = $request_rtr.get_result_string(result) - log.error('ошибка: \"%s\"' % rs) - return + if result != 0: + var rs = $request_rtr.get_result_string(result) + log.error('ошибка: \"%s\"' % rs) + return - var json_str: String = body.get_string_from_utf8() - if not json_str: - push_error('ошибка: не удаётся получить строку utf8 из данных (%s)' % headers) - return + var json_str: String = body.get_string_from_utf8() + if not json_str: + push_error('ошибка: не удаётся получить строку utf8 из данных (%s)' % headers) + return - var rc = json_parser.parse(json_str) - if rc != Error.OK: - var err = json_parser.get_error_message() - push_error('ошибка: разбор json прерван - \"%s\" (%s)' % [err, headers]) - return + var rc = json_parser.parse(json_str) + if rc != Error.OK: + var err = json_parser.get_error_message() + push_error('ошибка: разбор json прерван - \"%s\" (%s)' % [err, headers]) + return - var pp = ['DeviceDescription', 'DeviceName'] - var ret_val = [null, false] - var pp_copy = pp.duplicate(true) - tools.get_value_by_path(json_parser.data, pp, ret_val) - if not ret_val[1]: - push_error('ошибка: путь %s не найден (%s)' % [pp_copy, headers]) - return + var pp = ['DeviceDescription', 'DeviceName'] + var ret_val = [null, false] + var pp_copy = pp.duplicate(true) + tools.get_value_by_path(json_parser.data, pp, ret_val) + if not ret_val[1]: + push_error('ошибка: путь %s не найден (%s)' % [pp_copy, headers]) + return - var bort_rtr_name_left: StringName = $left.get_meta('DeviceName', '') - var bort_rtr_name_right: StringName = $right.get_meta('DeviceName', '') - var bb = {bort_rtr_name_left: $left, bort_rtr_name_right: $right} - if not bb.has(ret_val[0]): - return + var bort_rtr_name_left: StringName = $left.get_meta('DeviceName', '') + var bort_rtr_name_right: StringName = $right.get_meta('DeviceName', '') + var bb = {bort_rtr_name_left: $left, bort_rtr_name_right: $right} + if not bb.has(ret_val[0]): + return - var bort_rtr = bb[ret_val[0]] - for child in bort_rtr.get_node('konverter/grid').get_children(): - map_value_to_item(json_parser.data, child, 'value0') - map_value_to_item(json_parser.data, child, 'value1') - map_value_to_item(json_parser.data, child, 'value2') - map_error_codes_to_item(json_parser.data, child, 'state') - for child in bort_rtr.get_node('antenna1/grid').get_children(): - map_value_to_item(json_parser.data, child, 'value0') - map_value_to_item(json_parser.data, child, 'value1') - map_error_codes_to_item(json_parser.data, child, 'state') - for child in bort_rtr.get_node('antenna2/grid').get_children(): - map_value_to_item(json_parser.data, child, 'value0') - map_value_to_item(json_parser.data, child, 'value1') - map_error_codes_to_item(json_parser.data, child, 'state') - for child in bort_rtr.get_children(): - map_value_to_item(json_parser.data, child, 'value0') - map_value_to_item(json_parser.data, child, 'value1') - map_error_codes_to_item(json_parser.data, child, 'state') + var bort_rtr = bb[ret_val[0]] + for child in bort_rtr.get_node('konverter/grid').get_children(): + map_value_to_item(json_parser.data, child, 'value0') + map_value_to_item(json_parser.data, child, 'value1') + map_value_to_item(json_parser.data, child, 'value2') + map_error_codes_to_item(json_parser.data, child, 'state') + for child in bort_rtr.get_node('antenna1/grid').get_children(): + map_value_to_item(json_parser.data, child, 'value0') + map_value_to_item(json_parser.data, child, 'value1') + map_error_codes_to_item(json_parser.data, child, 'state') + for child in bort_rtr.get_node('antenna2/grid').get_children(): + map_value_to_item(json_parser.data, child, 'value0') + map_value_to_item(json_parser.data, child, 'value1') + map_error_codes_to_item(json_parser.data, child, 'state') + for child in bort_rtr.get_children(): + map_value_to_item(json_parser.data, child, 'value0') + map_value_to_item(json_parser.data, child, 'value1') + map_error_codes_to_item(json_parser.data, child, 'state') func map_value_to_item(data, item, value_name: StringName): - var meta = item.get_meta(value_name, '') - if meta == '': - return - if not (meta is String): - return - var paths: = Array() - map_solid_to_array(meta, paths) - var ret_val: = [null, false] - var paths_copy = paths.duplicate(true) - tools.get_value_by_path(data, paths, ret_val) - if ret_val[1]: - item.set(value_name, ret_val[0]) - else: - push_error('ошибка: путь %s для $%s.%s не найден' % [paths_copy, item.name, value_name]) + var meta = item.get_meta(value_name, '') + if meta == '': + return + if not (meta is String): + return + var paths: = Array() + map_solid_to_array(meta, paths) + var ret_val: = [null, false] + var paths_copy = paths.duplicate(true) + tools.get_value_by_path(data, paths, ret_val) + if ret_val[1]: + item.set(value_name, ret_val[0]) + else: + push_error('ошибка: путь %s для $%s.%s не найден' % [paths_copy, item.name, value_name]) ## 1. Поле [b]'ErCode'[/b] содержит [b]0[/b] - если нет ошибки, и другое значение - если есть ошибка.[br] @@ -105,29 +105,29 @@ func map_value_to_item(data, item, value_name: StringName): ## 4. [i]Нет результата порверки[/i] - поле [param state] принимает значение [b]0[/b], [i]всё ## исправно[/i] - [b]1[/b], [i]хотябы один[/i] параметр с ошибкой - [b]2[/b].[br] func map_error_codes_to_item(data, item, value_name: StringName): - var metas = item.get_meta(value_name, '') - if not (metas is Array): - return - var paths: = Array() - var values: = Array() - for meta in metas: - map_solid_to_array(meta, paths) - var ret_val: = [null, false] - var paths_copy = paths.duplicate(true) - tools.get_value_by_path(data, paths, ret_val) - if ret_val[1]: - values.append(int(ret_val[0])) - else: - push_error('ошибка: путь %s для $%s.%s не найден' % [paths_copy, item.name, value_name]) - var test_res = 1 if values.count(int(0)) == values.size() else 2 - item.set(value_name, test_res) + var metas = item.get_meta(value_name, '') + if not (metas is Array): + return + var paths: = Array() + var values: = Array() + for meta in metas: + map_solid_to_array(meta, paths) + var ret_val: = [null, false] + var paths_copy = paths.duplicate(true) + tools.get_value_by_path(data, paths, ret_val) + if ret_val[1]: + values.append(int(ret_val[0])) + else: + push_error('ошибка: путь %s для $%s.%s не найден' % [paths_copy, item.name, value_name]) + var test_res = 1 if values.count(int(0)) == values.size() else 2 + item.set(value_name, test_res) ## Вызывается при клике мыши на элементе контроля. ## [param block_a] - Кликнутый элемент контроля. func _on_item_pressed(block_a: Control): - var metas = block_a.get_meta_list() - print_debug(block_a) - for n in metas: - var v = block_a.get_meta(n) - print('%s:%s' % [n, v]) + var metas = block_a.get_meta_list() + print_debug(block_a) + for n in metas: + var v = block_a.get_meta(n) + print('%s:%s' % [n, v]) diff --git a/scenes/tabs-switch/tab-switch.gd b/scenes/tabs-switch/tab-switch.gd index d7715fe7..31a8f966 100644 --- a/scenes/tabs-switch/tab-switch.gd +++ b/scenes/tabs-switch/tab-switch.gd @@ -4,9 +4,9 @@ const TAB_TOSIGNAL_NAME = {0: 'режим_работа', 1: 'режим_конт func _enter_tree() -> void: - repnetwork.logger_page = $Журнал + repnetwork.logger_page = $Журнал func _on_tab_changed(tab) -> void: - if TAB_TOSIGNAL_NAME.has(tab): - signaller.emit_signal(TAB_TOSIGNAL_NAME[tab]) + if TAB_TOSIGNAL_NAME.has(tab): + signaller.emit_signal(TAB_TOSIGNAL_NAME[tab]) diff --git a/scenes/tilemap/tilemap.gd b/scenes/tilemap/tilemap.gd index 4bd9c6d0..feef13e3 100644 --- a/scenes/tilemap/tilemap.gd +++ b/scenes/tilemap/tilemap.gd @@ -2,41 +2,41 @@ class_name main_tilemap extends Sprite2D ## Возможные состояния конечного автомата заданий. enum TASK_STATE { - DONE, ## Выполнено. - WAIT, ## Ожидание плитки. - RUN, ## Запуск выполнения. - CHECK, ## Проверка пустого места. - ERROR ## Ошибка. + DONE, ## Выполнено. + WAIT, ## Ожидание плитки. + RUN, ## Запуск выполнения. + CHECK, ## Проверка пустого места. + ERROR ## Ошибка. } ## Координаты для проверок [lng, lat]. const TEST_DATA: = { - 'Калиниград': [ 20.51095, 54.70649 ], - 'Москва': [ 37.6156, 55.7522 ], - 'Нью-Йорк': [ -74.006, 40.7143 ], - 'Сидней': [ 151.20732, -33.8678500 ], - 'Таганрог': [ 38.8969, 47.2362 ], - 'Хельсинки': [ 24.9354, 60.1695 ], - 'Азорские о-ва': [ 28.3996582, 38.5397568 ], - 'Мехико': [ -99.12766, 19.42847 ], - 'Белое море': [ 68.136606, 42.045546 ], - 'море Беллинсгаузена': [ -72.406587, -74.857997 ], - 'Фиджи': [ 178.065032, -17.713371 ], - 'Ноль': [ 0.0, 0.0 ], - 'Точка1': [ 170.0, 0.0 ], - 'Точка2': [-170.0, 0.0 ], - 'Море' : [ 19.4, 54.91 ] } + 'Калиниград': [ 20.51095, 54.70649 ], + 'Москва': [ 37.6156, 55.7522 ], + 'Нью-Йорк': [ -74.006, 40.7143 ], + 'Сидней': [ 151.20732, -33.8678500 ], + 'Таганрог': [ 38.8969, 47.2362 ], + 'Хельсинки': [ 24.9354, 60.1695 ], + 'Азорские о-ва': [ 28.3996582, 38.5397568 ], + 'Мехико': [ -99.12766, 19.42847 ], + 'Белое море': [ 68.136606, 42.045546 ], + 'море Беллинсгаузена': [ -72.406587, -74.857997 ], + 'Фиджи': [ 178.065032, -17.713371 ], + 'Ноль': [ 0.0, 0.0 ], + 'Точка1': [ 170.0, 0.0 ], + 'Точка2': [-170.0, 0.0 ], + 'Море' : [ 19.4, 54.91 ] } const MAP_MODES: = { - 0: 'application/config/map_image_size_rto', - 1: 'application/config/map_image_size_rls' } + 0: 'application/config/map_image_size_rto', + 1: 'application/config/map_image_size_rls' } const MAP_RELATIVE_RADIUSES: = { - 0: 0.39, - 1: 0.466 + 0: 0.39, + 1: 0.466 } @@ -53,122 +53,122 @@ var map_mode: int ## Режим карты: 0 - приорите var prev_tiles_dic: Dictionary ## var lnglat: Array ## Текущие координаты. var root_rect: Rect2i - + func _ready(): - lnglat = TEST_DATA['Таганрог'] - map_tile_state = TASK_STATE.DONE - map_request = HTTPRequest.new() - add_child(map_request) - map_request.request_completed.connect(self._map_request_completed) - map_bcs = ProjectSettings.get_setting('application/config/map_bcs', Vector3(1.0, 1.0, 1.0)) - map_server_addr = ProjectSettings.get_setting('application/config/map_server_addr') - map_tile_format = ProjectSettings.get_setting('application/config/map_tile_format', '%s/%d/%d/%d.png') - var map_image_size = ProjectSettings.get_setting(MAP_MODES[0], Vector2i(512, 512)) - var map_tile_size = ProjectSettings.get_setting('application/config/map_tile_size', 256) - map_image = Image.create(map_image_size.x, map_image_size.y, true, Image.FORMAT_RGB8) - map_tile = Rect2i(0, 0, map_tile_size, map_tile_size) - signaller.connect('zoom_changed', Callable(self, '_on_zoom_changed')) - signaller.connect('map_mode_changed', Callable(self, '_on_map_mode_changed')) - log.info('Адрес сервера поставщика карт: \"%s\"' % map_server_addr) - log.info('Модуль географической карты готов') + lnglat = TEST_DATA['Таганрог'] + map_tile_state = TASK_STATE.DONE + map_request = HTTPRequest.new() + add_child(map_request) + map_request.request_completed.connect(self._map_request_completed) + map_bcs = ProjectSettings.get_setting('application/config/map_bcs', Vector3(1.0, 1.0, 1.0)) + map_server_addr = ProjectSettings.get_setting('application/config/map_server_addr') + map_tile_format = ProjectSettings.get_setting('application/config/map_tile_format', '%s/%d/%d/%d.png') + var map_image_size = ProjectSettings.get_setting(MAP_MODES[0], Vector2i(512, 512)) + var map_tile_size = ProjectSettings.get_setting('application/config/map_tile_size', 256) + map_image = Image.create(map_image_size.x, map_image_size.y, true, Image.FORMAT_RGB8) + map_tile = Rect2i(0, 0, map_tile_size, map_tile_size) + signaller.connect('zoom_changed', Callable(self, '_on_zoom_changed')) + signaller.connect('map_mode_changed', Callable(self, '_on_map_mode_changed')) + log.info('Адрес сервера поставщика карт: \"%s\"' % map_server_addr) + log.info('Модуль географической карты готов') func _on_map_mode_changed(mode: int) -> void: - var map_image_size = ProjectSettings.get_setting(MAP_MODES[mode]) - material.set('shader_parameter/radius', MAP_RELATIVE_RADIUSES[mode]) - map_image.fill(Color(0, 0, 0, 0)) - map_image.resize(map_image_size.x, map_image_size.y) - map_tile_state = TASK_STATE.DONE - map_mode = mode - var tile: = mercator.create_tile(lnglat[0], lnglat[1], map_zoom) - map_tile_stack.clear() - map_tile_stack.append(tile) - prev_tiles_dic.clear() + var map_image_size = ProjectSettings.get_setting(MAP_MODES[mode]) + material.set('shader_parameter/radius', MAP_RELATIVE_RADIUSES[mode]) + map_image.fill(Color(0, 0, 0, 0)) + map_image.resize(map_image_size.x, map_image_size.y) + map_tile_state = TASK_STATE.DONE + map_mode = mode + var tile: = mercator.create_tile(lnglat[0], lnglat[1], map_zoom) + map_tile_stack.clear() + map_tile_stack.append(tile) + prev_tiles_dic.clear() func mark_image_center(image: Image, color: Color = Color.RED): - var c = image.get_size() / 2 - map_image.set_pixel(c.x - 1, c.y - 1, color) - map_image.set_pixel(c.x - 1, c.y, color) - map_image.set_pixel(c.x, c.y - 1, color) - map_image.set_pixel(c.x, c.y, color) + var c = image.get_size() / 2 + map_image.set_pixel(c.x - 1, c.y - 1, color) + map_image.set_pixel(c.x - 1, c.y, color) + map_image.set_pixel(c.x, c.y - 1, color) + map_image.set_pixel(c.x, c.y, color) func _on_zoom_changed(zoom: int): - if map_tile_stack.size() != 0: - return - map_zoom = zoom - var tile: = mercator.create_tile(lnglat[0], lnglat[1], zoom) - map_tile_stack.clear() - map_tile_stack.append(tile) - prev_tiles_dic.clear() + if map_tile_stack.size() != 0: + return + map_zoom = zoom + var tile: = mercator.create_tile(lnglat[0], lnglat[1], zoom) + map_tile_stack.clear() + map_tile_stack.append(tile) + prev_tiles_dic.clear() func img_tile_to_map_image(image: Image, dst_pos: Vector2i): - image.adjust_bcs(map_bcs.x, map_bcs.y, map_bcs.z) - map_image.blit_rect(image, map_tile, dst_pos) + image.adjust_bcs(map_bcs.x, map_bcs.y, map_bcs.z) + map_image.blit_rect(image, map_tile, dst_pos) func _process(_delta: float): - if (map_tile_state == TASK_STATE.DONE) and map_tile_stack.size(): - var tile: mercator.Tile = map_tile_stack[-1] - var tile_addr: = map_tile_format % [map_server_addr, tile.z, tile.x , tile.y] - var rc = map_request.request(tile_addr) - if rc == OK: - map_tile_state = TASK_STATE.WAIT - else: - log.error('%s: Невозможно сформировать запрос HTTP' % self) - map_tile_state = TASK_STATE.ERROR - elif map_tile_state == TASK_STATE.CHECK: - var root_tile: = mercator.Tile.new(root_rect.size.x, root_rect.size.y, map_zoom) - var neighbors_tiles = mercator.get_perimetr_tiles(root_tile, (map_image.get_size().x / map_tile.size.x) / 2) - var neighbors_tiles_dic = {} - for tile in neighbors_tiles: - neighbors_tiles_dic[[tile.x, tile.y]] = false - if neighbors_tiles_dic != prev_tiles_dic: - for tile in neighbors_tiles: - tile.is_root = false - map_tile_stack.push_front(tile) - prev_tiles_dic[[tile.x, tile.y]] = false - else: - if map_tile_stack.size() == 0: - signaller.emit_signal('zoom_complete', map_zoom) - map_tile_state = TASK_STATE.DONE + if (map_tile_state == TASK_STATE.DONE) and map_tile_stack.size(): + var tile: mercator.Tile = map_tile_stack[-1] + var tile_addr: = map_tile_format % [map_server_addr, tile.z, tile.x , tile.y] + var rc = map_request.request(tile_addr) + if rc == OK: + map_tile_state = TASK_STATE.WAIT + else: + log.error('%s: Невозможно сформировать запрос HTTP' % self) + map_tile_state = TASK_STATE.ERROR + elif map_tile_state == TASK_STATE.CHECK: + var root_tile: = mercator.Tile.new(root_rect.size.x, root_rect.size.y, map_zoom) + var neighbors_tiles = mercator.get_perimetr_tiles(root_tile, (map_image.get_size().x / map_tile.size.x) / 2) + var neighbors_tiles_dic = {} + for tile in neighbors_tiles: + neighbors_tiles_dic[[tile.x, tile.y]] = false + if neighbors_tiles_dic != prev_tiles_dic: + for tile in neighbors_tiles: + tile.is_root = false + map_tile_stack.push_front(tile) + prev_tiles_dic[[tile.x, tile.y]] = false + else: + if map_tile_stack.size() == 0: + signaller.emit_signal('zoom_complete', map_zoom) + map_tile_state = TASK_STATE.DONE func _map_request_completed(result, response_code, headers, data): - if result != HTTPRequest.RESULT_SUCCESS: - log.error('%s: Запрос не обработан. result: \"%s\", response_code: \"%s\", headers: \"%s\"' % [self, result, response_code, headers]) - map_tile_state = TASK_STATE.ERROR - return + if result != HTTPRequest.RESULT_SUCCESS: + log.error('%s: Запрос не обработан. result: \"%s\", response_code: \"%s\", headers: \"%s\"' % [self, result, response_code, headers]) + map_tile_state = TASK_STATE.ERROR + return - var img_tile = Image.new() - var error = img_tile.load_png_from_buffer(data) - if error != OK: - log.error('%s: Полученые данные невозможно использовать для формироания изображения. result: \"%s\", response_code: \"%s\", headers: \"%s\"' % [self, result, response_code, headers]) - map_tile_state = TASK_STATE.ERROR - return + var img_tile = Image.new() + var error = img_tile.load_png_from_buffer(data) + if error != OK: + log.error('%s: Полученые данные невозможно использовать для формироания изображения. result: \"%s\", response_code: \"%s\", headers: \"%s\"' % [self, result, response_code, headers]) + map_tile_state = TASK_STATE.ERROR + return - var tile = map_tile_stack.pop_back() - map_tile_state = TASK_STATE.CHECK + var tile = map_tile_stack.pop_back() + map_tile_state = TASK_STATE.CHECK + + if tile.is_root: + # Отображение корневой плитки + map_image.fill(Color(0, 0, 0, 0)) + var bounds: = mercator.bounds(tile) + var lng_c = (bounds.east + bounds.west) / 2.0 + var lat_c = (bounds.north + bounds.south) / 2.0 + var sx: float = float(map_tile.size.x) / (bounds.east - bounds.west) * (lng_c - tile.lng) + map_tile.size.x / 2.0 # Cмещение в пискселях центра плитки от заданной точки по горизонтали. + var sy: float = float(map_tile.size.y) / (bounds.north - bounds.south) * (lat_c - tile.lat) - map_tile.size.y / 2.0 # Cмещение в пискселях центра плитки от заданной точки по вертикали. + var dst_pos: Vector2i = Vector2i(int(sx), int(-sy)) + map_tile.size * map_mode + img_tile_to_map_image(img_tile, dst_pos) + root_rect = Rect2i(dst_pos.x, dst_pos.y, tile.x, tile.y) + else: + # Отображение соседних плиток + var delta: = Vector2i(tile.x - root_rect.size.x, tile.y - root_rect.size.y) + var dst_pos: Vector2i = root_rect.position + delta * map_tile.size + img_tile_to_map_image(img_tile, dst_pos) + mark_image_center(map_image) + texture = ImageTexture.create_from_image(map_image) - if tile.is_root: - # Отображение корневой плитки - map_image.fill(Color(0, 0, 0, 0)) - var bounds: = mercator.bounds(tile) - var lng_c = (bounds.east + bounds.west) / 2.0 - var lat_c = (bounds.north + bounds.south) / 2.0 - var sx: float = float(map_tile.size.x) / (bounds.east - bounds.west) * (lng_c - tile.lng) + map_tile.size.x / 2.0 # Cмещение в пискселях центра плитки от заданной точки по горизонтали. - var sy: float = float(map_tile.size.y) / (bounds.north - bounds.south) * (lat_c - tile.lat) - map_tile.size.y / 2.0 # Cмещение в пискселях центра плитки от заданной точки по вертикали. - var dst_pos: Vector2i = Vector2i(int(sx), int(-sy)) + map_tile.size * map_mode - img_tile_to_map_image(img_tile, dst_pos) - root_rect = Rect2i(dst_pos.x, dst_pos.y, tile.x, tile.y) - else: - # Отображение соседних плиток - var delta: = Vector2i(tile.x - root_rect.size.x, tile.y - root_rect.size.y) - var dst_pos: Vector2i = root_rect.position + delta * map_tile.size - img_tile_to_map_image(img_tile, dst_pos) - mark_image_center(map_image) - texture = ImageTexture.create_from_image(map_image) - diff --git a/scenes/tilemap/tilemap.tscn b/scenes/tilemap/tilemap.tscn index 9dcc09b9..9920581a 100644 --- a/scenes/tilemap/tilemap.tscn +++ b/scenes/tilemap/tilemap.tscn @@ -1,8 +1,7 @@ -[gd_scene load_steps=6 format=3 uid="uid://bbq6nklh36yij"] +[gd_scene load_steps=5 format=3 uid="uid://bbq6nklh36yij"] [ext_resource type="Script" path="res://scenes/tilemap/tilemap.gd" id="1_mkfgj"] [ext_resource type="Shader" path="res://shaders/tilemap.gdshader" id="1_tsdui"] -[ext_resource type="Script" path="res://scenes/tilemap/Node2D.gd" id="3_lr4ti"] [sub_resource type="ShaderMaterial" id="ShaderMaterial_pmqvj"] resource_local_to_scene = true @@ -24,7 +23,6 @@ offset_bottom = 641.0 theme_override_styles/panel = SubResource("StyleBoxEmpty_ys5v4") [node name="Node2D" type="Node2D" parent="."] -script = ExtResource("3_lr4ti") [node name="LineEdit" type="LineEdit" parent="Node2D"] offset_left = -490.0 diff --git a/scenes/журнал/журнал.gd b/scenes/журнал/журнал.gd index 1385bcd6..ad400e81 100644 --- a/scenes/журнал/журнал.gd +++ b/scenes/журнал/журнал.gd @@ -9,58 +9,58 @@ func on_interfer_off(ecm, _ecms, _thrs,msg: String): log.message(Logger.INFO, '% func on_set_program_mode(msg: String): log.message(Logger.INFO, msg) func on_interfer_new(ecm, msg: String): - if ecm.krp != interfer.INTERFER_OFF: - log.message(Logger.INFO, '%s: %s' % [msg, ecm.to_string()]) + if ecm.krp != interfer.INTERFER_OFF: + log.message(Logger.INFO, '%s: %s' % [msg, ecm.to_string()]) func _enter_tree(): - var flts: Array = [] - for item in $filtrs.get_children(): - flts.append(item) - log.set_filtrs(flts) + var flts: Array = [] + for item in $filtrs.get_children(): + flts.append(item) + log.set_filtrs(flts) func _ready() -> void: - log.info('Модуль журнала работы готов') - log.info('Аргументы коммандной строки: \"%s\"' % OS.get_cmdline_args()) - log.info('Операционная система: %s' % [OS.get_distribution_name()]) - log.info('Текущая версия ПО УАРЭП: %s' % commit.VCS_HEAD) - threats.connect('threat_new', Callable(self, 'on_threats_changed').bind('Цель обнаружена')) - threats.connect('threat_lost', Callable(self, 'on_threats_changed').bind('Цель потеряна')) - signaller.conn(signaller.interfer_new, Callable(self, 'on_interfer_new').bind('Создан сеанс помехи')) - signaller.conn(signaller.interfer_off, Callable(self, 'on_interfer_off').bind('Сеанс помехи отключен')) - signaller.conn(signaller.режим_работа, Callable(log, 'info').bind('Программа в режиме \"Работа\"')) - signaller.conn(signaller.режим_настройки, Callable(log, 'info').bind('Программа в режиме \"Настройки\"')) - signaller.conn(signaller.режим_эмс, Callable(log, 'info').bind('Программа в режиме \"ЭМС\"')) - signaller.conn(signaller.режим_журнал, Callable(log, 'info').bind('Программа в режиме \"Журнал\"')) - signaller.conn(signaller.режим_контроль, Callable(log, 'info').bind('Программа в режиме \"Контроль\"')) + log.info('Модуль журнала работы готов') + log.info('Аргументы коммандной строки: \"%s\"' % OS.get_cmdline_args()) + log.info('Операционная система: %s' % [OS.get_distribution_name()]) + log.info('Текущая версия ПО УАРЭП: %s' % commit.VCS_HEAD) + threats.connect('threat_new', Callable(self, 'on_threats_changed').bind('Цель обнаружена')) + threats.connect('threat_lost', Callable(self, 'on_threats_changed').bind('Цель потеряна')) + signaller.conn(signaller.interfer_new, Callable(self, 'on_interfer_new').bind('Создан сеанс помехи')) + signaller.conn(signaller.interfer_off, Callable(self, 'on_interfer_off').bind('Сеанс помехи отключен')) + signaller.conn(signaller.режим_работа, Callable(log, 'info').bind('Программа в режиме \"Работа\"')) + signaller.conn(signaller.режим_настройки, Callable(log, 'info').bind('Программа в режиме \"Настройки\"')) + signaller.conn(signaller.режим_эмс, Callable(log, 'info').bind('Программа в режиме \"ЭМС\"')) + signaller.conn(signaller.режим_журнал, Callable(log, 'info').bind('Программа в режиме \"Журнал\"')) + signaller.conn(signaller.режим_контроль, Callable(log, 'info').bind('Программа в режиме \"Контроль\"')) func _on_clear_button_down() -> void: - $content.clear() - log.info('Cодержание журнала очищено оператором') + $content.clear() + log.info('Cодержание журнала очищено оператором') func _on_btn_save_button_down() -> void: - $content.hide() - $btn_clear.hide() - $btn_save.hide() - $filtrs.hide() - $dlg_file.show() + $content.hide() + $btn_clear.hide() + $btn_save.hide() + $filtrs.hide() + $dlg_file.show() func _on_dlg_file_confirmed() -> void: - $content.show() - $btn_clear.show() - $btn_save.show() - $filtrs.show() - $dlg_file.hide() + $content.show() + $btn_clear.show() + $btn_save.show() + $filtrs.show() + $dlg_file.hide() func _on_dlg_file_file_selected(path): - log.save_file(path) + log.save_file(path) func _on_check_box_pressed(): - $content.clear() - log.add_msgs() + $content.clear() + log.add_msgs() diff --git a/scenes/контроль/кассета.gd b/scenes/контроль/кассета.gd index a8d394b0..fb83827d 100644 --- a/scenes/контроль/кассета.gd +++ b/scenes/контроль/кассета.gd @@ -7,11 +7,11 @@ extends NinePatchRect """ @export var fname: String: - set(v): - fname = v - $lbl_fname.text = v + set(v): + fname = v + $lbl_fname.text = v @export var rname: String: - set(v): - rname = v + set(v): + rname = v diff --git a/scenes/контроль/кнопка-прибора.gd b/scenes/контроль/кнопка-прибора.gd index e0f2369e..4575a6ca 100644 --- a/scenes/контроль/кнопка-прибора.gd +++ b/scenes/контроль/кнопка-прибора.gd @@ -7,7 +7,7 @@ extends Node func _on_toggled(toggled_on): $frame.visible = toggled_on @export var state: int: - set(v): - v = 0 if v < 0 else v % state_colors.size() - state = v - $label.self_modulate = state_colors[v] + set(v): + v = 0 if v < 0 else v % state_colors.size() + state = v + $label.self_modulate = state_colors[v] diff --git a/scenes/контроль/контроль.gd b/scenes/контроль/контроль.gd index 295867b6..562cb76b 100644 --- a/scenes/контроль/контроль.gd +++ b/scenes/контроль/контроль.gd @@ -6,50 +6,50 @@ extends Panel const PRIBORS_RES = \ { - 'pribor_afsp_l': ['res://scenes/pribor-afsp/pribor-afsp.tscn', 'Контроль исправности прибора АФСП левый'], - 'pribor_afsp_r': ['res://scenes/pribor-afsp/pribor-afsp.tscn', 'Контроль исправности прибора АФСП правый'], - 'pribor_prd_k_1': ['res://scenes/pribor-prd-k/pribor-prd-k.tscn', 'Контроль исправности прибора ПРД-К 1'], - 'pribor_prd_k_2': ['res://scenes/pribor-prd-k/pribor-prd-k.tscn', 'Контроль исправности прибора ПРД-К 2'], - 'pribor_prd_k_3': ['res://scenes/pribor-prd-k/pribor-prd-k.tscn', 'Контроль исправности прибора ПРД-К 3'], - 'pribor_prd_k_4': ['res://scenes/pribor-prd-k/pribor-prd-k.tscn', 'Контроль исправности прибора ПРД-К 4'], - 'pribor_prd_n_1': ['res://scenes/pribor-prd-n/pribor-prd-n.tscn', 'Контроль исправности прибора ПРД-Н 1'], - 'pribor_prd_n_2': ['res://scenes/pribor-prd-n/pribor-prd-n.tscn', 'Контроль исправности прибора ПРД-Н 2'], - 'pribor_prd_n_3': ['res://scenes/pribor-prd-n/pribor-prd-n.tscn', 'Контроль исправности прибора ПРД-Н 3'], - 'pribor_prd_n_4': ['res://scenes/pribor-prd-n/pribor-prd-n.tscn', 'Контроль исправности прибора ПРД-Н 4'], - 'pribor_prd_v_1': ['res://scenes/pribor-prd-v/pribor-prd-v.tscn', 'Контроль исправности прибора ПРД-В 1'], - 'pribor_prd_v_2': ['res://scenes/pribor-prd-v/pribor-prd-v.tscn', 'Контроль исправности прибора ПРД-В 2'], - 'pribor_prd_v_3': ['res://scenes/pribor-prd-v/pribor-prd-v.tscn', 'Контроль исправности прибора ПРД-В 3'], - 'pribor_prd_v_4': ['res://scenes/pribor-prd-v/pribor-prd-v.tscn', 'Контроль исправности прибора ПРД-В 4'], - 'pribor_rtr': ['res://scenes/pribor-rtr/pribor-rtr.tscn', 'Контроль исправности прибора РТР'], - 'pribor_uf': ['res://scenes/pribor-uf/pribor-uf.tscn', 'Контроль исправности прибора УФ'], - 'pribor_uyep': ['res://scenes/pribor-uyep/pribor-uyep.tscn', 'Контроль исправности прибора УЭП'], - } + 'pribor_afsp_l': ['res://scenes/pribor-afsp/pribor-afsp.tscn', 'Контроль исправности прибора АФСП левый'], + 'pribor_afsp_r': ['res://scenes/pribor-afsp/pribor-afsp.tscn', 'Контроль исправности прибора АФСП правый'], + 'pribor_prd_k_1': ['res://scenes/pribor-prd-k/pribor-prd-k.tscn', 'Контроль исправности прибора ПРД-К 1'], + 'pribor_prd_k_2': ['res://scenes/pribor-prd-k/pribor-prd-k.tscn', 'Контроль исправности прибора ПРД-К 2'], + 'pribor_prd_k_3': ['res://scenes/pribor-prd-k/pribor-prd-k.tscn', 'Контроль исправности прибора ПРД-К 3'], + 'pribor_prd_k_4': ['res://scenes/pribor-prd-k/pribor-prd-k.tscn', 'Контроль исправности прибора ПРД-К 4'], + 'pribor_prd_n_1': ['res://scenes/pribor-prd-n/pribor-prd-n.tscn', 'Контроль исправности прибора ПРД-Н 1'], + 'pribor_prd_n_2': ['res://scenes/pribor-prd-n/pribor-prd-n.tscn', 'Контроль исправности прибора ПРД-Н 2'], + 'pribor_prd_n_3': ['res://scenes/pribor-prd-n/pribor-prd-n.tscn', 'Контроль исправности прибора ПРД-Н 3'], + 'pribor_prd_n_4': ['res://scenes/pribor-prd-n/pribor-prd-n.tscn', 'Контроль исправности прибора ПРД-Н 4'], + 'pribor_prd_v_1': ['res://scenes/pribor-prd-v/pribor-prd-v.tscn', 'Контроль исправности прибора ПРД-В 1'], + 'pribor_prd_v_2': ['res://scenes/pribor-prd-v/pribor-prd-v.tscn', 'Контроль исправности прибора ПРД-В 2'], + 'pribor_prd_v_3': ['res://scenes/pribor-prd-v/pribor-prd-v.tscn', 'Контроль исправности прибора ПРД-В 3'], + 'pribor_prd_v_4': ['res://scenes/pribor-prd-v/pribor-prd-v.tscn', 'Контроль исправности прибора ПРД-В 4'], + 'pribor_rtr': ['res://scenes/pribor-rtr/pribor-rtr.tscn', 'Контроль исправности прибора РТР'], + 'pribor_uf': ['res://scenes/pribor-uf/pribor-uf.tscn', 'Контроль исправности прибора УФ'], + 'pribor_uyep': ['res://scenes/pribor-uyep/pribor-uyep.tscn', 'Контроль исправности прибора УЭП'], + } func _ready(): - var group = ButtonGroup.new() - for k in PRIBORS_RES: - var pribor_node = get_node(k) - assert(pribor_node) - var pribor_path = PRIBORS_RES[k][0] - var header_text = PRIBORS_RES[k][1] - pribor_node.set_button_group(group) - pribor_node.connect('button_down', Callable(self, 'on_pribor_press').bind(pribor_path, header_text)) - log.info('Модуль контроля работы готов') + var group = ButtonGroup.new() + for k in PRIBORS_RES: + var pribor_node = get_node(k) + assert(pribor_node) + var pribor_path = PRIBORS_RES[k][0] + var header_text = PRIBORS_RES[k][1] + pribor_node.set_button_group(group) + pribor_node.connect('button_down', Callable(self, 'on_pribor_press').bind(pribor_path, header_text)) + log.info('Модуль контроля работы готов') func on_pribor_press(pribor_path, header_text): - clear_obj($mnemo_container) - var pribor_obj = load(pribor_path) - assert(pribor_obj) - var pribor = pribor_obj.instantiate() - assert(pribor) - pribor.set_header_text(header_text) - $mnemo_container.add_child(pribor) + clear_obj($mnemo_container) + var pribor_obj = load(pribor_path) + assert(pribor_obj) + var pribor = pribor_obj.instantiate() + assert(pribor) + pribor.set_header_text(header_text) + $mnemo_container.add_child(pribor) func clear_obj(obj: Object): - var items = obj.get_children() - for item in items: - obj.remove_child(item) - item.queue_free() + var items = obj.get_children() + for item in items: + obj.remove_child(item) + item.queue_free() diff --git a/scenes/контроль/прибор.gd b/scenes/контроль/прибор.gd index 80f630d0..09099753 100644 --- a/scenes/контроль/прибор.gd +++ b/scenes/контроль/прибор.gd @@ -7,20 +7,20 @@ extends Control """ @export var fname: String: - set(v): - fname = v - get: - return fname + set(v): + fname = v + get: + return fname @export var rname: String: - set(v): - rname = v - get: - return rname + set(v): + rname = v + get: + return rname func set_header_text(header_text: String): - $lbl_header.text = header_text + $lbl_header.text = header_text @export var nodes_mask: String = '' diff --git a/scenes/настройки/настройки.gd b/scenes/настройки/настройки.gd index c7f05b08..cd817dbb 100644 --- a/scenes/настройки/настройки.gd +++ b/scenes/настройки/настройки.gd @@ -3,16 +3,16 @@ class_name setting extends Panel ## Вызывается, когда узел впервые попадает в дерево сцены. func _ready() -> void: - $lbl_commit.text = 'Текущая версия ПО УАРЭП: \"%s\"' % commit.VCS_HEAD - if not OS.get_cmdline_args().has('++ограниченный'): - return - $Label2.text = 'Текущий режим доступа: ограниченный' - for node in $GridContainer.get_children().filter(func(b): return b is Button): - node.disabled = true + $lbl_commit.text = 'Текущая версия ПО УАРЭП: \"%s\"' % commit.VCS_HEAD + if not OS.get_cmdline_args().has('++ограниченный'): + return + $Label2.text = 'Текущий режим доступа: ограниченный' + for node in $GridContainer.get_children().filter(func(b): return b is Button): + node.disabled = true func _on_external_cu_btn_pressed() -> void: - var state = $GridContainer2/external_cu_btn.button_pressed - ProjectSettings.set_setting('application/config/external_cu', state) - if state: - signaller.emit_signal('interfer_off_all') + var state = $GridContainer2/external_cu_btn.button_pressed + ProjectSettings.set_setting('application/config/external_cu', state) + if state: + signaller.emit_signal('interfer_off_all') diff --git a/scenes/работа/sld-mode.gd b/scenes/работа/sld-mode.gd index dc5e0063..f8e97d42 100644 --- a/scenes/работа/sld-mode.gd +++ b/scenes/работа/sld-mode.gd @@ -7,10 +7,10 @@ func _ready(): _on_value_changed(3) func _on_value_changed(val): - val = int(val) - for nm in VAL_TO_LABEL.values(): - var lbl0 = get_node(nm) - lbl0.self_modulate.a = 0.45 - var lbl1 = get_node(VAL_TO_LABEL[val]) - lbl1.self_modulate.a = 1.0 - signaller.emit_signal(VAL_TO_EVENT[val]) + val = int(val) + for nm in VAL_TO_LABEL.values(): + var lbl0 = get_node(nm) + lbl0.self_modulate.a = 0.45 + var lbl1 = get_node(VAL_TO_LABEL[val]) + lbl1.self_modulate.a = 1.0 + signaller.emit_signal(VAL_TO_EVENT[val]) diff --git a/scenes/работа/работа.gd b/scenes/работа/работа.gd index 32ed78a6..ad138c41 100644 --- a/scenes/работа/работа.gd +++ b/scenes/работа/работа.gd @@ -39,39 +39,39 @@ signal drag_end ## Вызывается в момент завершени ## Границы секторов блоков ФС по частоте в режиме РТО. const fs_band_rto: Dictionary = { - 0: [200.0, 250.0], - 1: [250.0, 300.0], - 2: [300.0, 350.0], - 3: [350.0, 400.0], - 4: [400.0, 450.0], - 5: [450.0, 500.0], - 6: [500.0, 550.0] } + 0: [200.0, 250.0], + 1: [250.0, 300.0], + 2: [300.0, 350.0], + 3: [350.0, 400.0], + 4: [400.0, 450.0], + 5: [450.0, 500.0], + 6: [500.0, 550.0] } ## Границы секторов блоков ФС по частоте в режиме РЛС. const fs_band_rls: Dictionary = { - 0: [480.0, 490.0], - 1: [490.0, 500.0], - 2: [500.0, 510.0], - 3: [510.0, 520.0], - 4: [520.0, 530.0], - 5: [530.0, 540.0], - 6: [540.0, 550.0] } + 0: [480.0, 490.0], + 1: [490.0, 500.0], + 2: [500.0, 510.0], + 3: [510.0, 520.0], + 4: [520.0, 530.0], + 5: [530.0, 540.0], + 6: [540.0, 550.0] } ## Границы секторов блоков ФС по пеленгу. const fs_sectors: Dictionary = { - 0: [0.0, 90.0], - 3: [90.0, 180.0], - 2: [180.0, 270.0], - 1: [270.0, 360.0] } + 0: [0.0, 90.0], + 3: [90.0, 180.0], + 2: [180.0, 270.0], + 1: [270.0, 360.0] } const fs_freq: Dictionary = { - 0: [400.0, 1200.0], - 1: [1200.0, 2000.0], - 2: [2000.0, 2800.0], - 3: [2800.0, 3600.0], - 4: [3600.0, 4400.0], - 5: [4400.0, 5200.0], - 6: [5200.0, 6000.0] } + 0: [400.0, 1200.0], + 1: [1200.0, 2000.0], + 2: [2000.0, 2800.0], + 3: [2800.0, 3600.0], + 4: [3600.0, 4400.0], + 5: [4400.0, 5200.0], + 6: [5200.0, 6000.0] } var fs_band = fs_band_rto # Текущий режим блоков ФС по частоте var fs_selected: Dictionary # Словарь выбранных секторов @@ -79,17 +79,17 @@ var fs_active: Dictionary # Словарь секторов назна ## Состояние перетаскивания. enum DragFSM { - DRAG, ## В процессе. - IDLE, ## Не активно, ожидание. - OFF ## Выключено. - } + DRAG, ## В процессе. + IDLE, ## Не активно, ожидание. + OFF ## Выключено. + } ## Состояние перетаскивания. enum BtnSel { - SECTOR, ## В процессе. - THREAT, ## Не активно, ожидание. - FS ## Выключено. - } + SECTOR, ## В процессе. + THREAT, ## Не активно, ожидание. + FS ## Выключено. + } ## Обработчик сигнала изменения списка целей. func on_threats_resized(threats: Dictionary): $count_all_pad/count_all.text = '%02d' % threats.size() @@ -101,385 +101,385 @@ func on_threat_selected(th, ecm_btns): on_rto_threat_sel(th.id, ecm_btns) ## Обработчик нажатия на кнопку вид. func on_button_view_toggled(toggled: bool): - signaller.emit_signal('map_mode_changed', int(toggled)) - if toggled: - fs_band = fs_band_rls - else: - fs_band = fs_band_rto + signaller.emit_signal('map_mode_changed', int(toggled)) + if toggled: + fs_band = fs_band_rls + else: + fs_band = fs_band_rto ## func _enter_tree() -> void: - var timer1 = Timer.new() - timer1.name = 'timer1' - add_child(timer1) - timer1.one_shot = true - timer1.connect('timeout', Callable(self, 'on_enter_tree')) - timer1.start(1.0) + var timer1 = Timer.new() + timer1.name = 'timer1' + add_child(timer1) + timer1.one_shot = true + timer1.connect('timeout', Callable(self, 'on_enter_tree')) + timer1.start(1.0) func _exit_tree() -> void: - var timer1 = get_node('timer1') - remove_child(timer1) - timer1.queue_free() + var timer1 = get_node('timer1') + remove_child(timer1) + timer1.queue_free() ## Производит инициализацию. func _ready(): - min_zoom = ProjectSettings.get_setting('application/config/map_minimum_zoom', 5) - max_zoom = ProjectSettings.get_setting('application/config/map_maximum_zoom', 19) - drag_fsm = DragFSM.OFF - $btn_zoom.set_meta('zoom', min_zoom - 1) - var btns_select: Array = $btns_select.get_children() - btns_select.any(func(btn): btn.button_connect('pressed', Callable(self, 'on_button_select').bind(btn, btns_select))) - $btn_view.button_connect('toggled', Callable(self, 'on_button_view_toggled')) - var nodes: Array = get_tree().get_nodes_in_group('группа-режим-помехи') - nodes.any(func(btn): btn.button_connect('pressed', Callable(self, 'on_button_pressed').bind(btn, nodes))) - threats.connect('threat_new', Callable(self, 'on_threat_new')) - threats.connect('threat_lost', Callable(self, 'on_threat_lost')) - threats.connect('threats_resized', Callable(self, 'on_threats_resized')) - threats.connect('threat_update', Callable(self, 'on_threat_update')) - signaller.connect('zoom_complete', Callable(self, '_on_zoom_complete')) - signaller.connect('rto_threat_sel', Callable(self, 'on_rto_threat_sel').bind(nodes)) - signaller.connect('bip_pressed', Callable(self, 'on_bip_pressed').bind(nodes)) - signaller.connect('rto_threat_unsel', Callable(self, 'on_rto_threat_unsel').bind(nodes)) - signaller.connect('threat_selected', Callable(self, 'on_threat_selected').bind(nodes)) - signaller.connect('interfer_selected', Callable(self, 'on_interfer_selected').bind(nodes)) - signaller.connect('interfer_new', Callable(self, 'on_interfer_new')) - signaller.connect('interfer_off_all', Callable(self, 'on_interfer_off_all')) - signaller.connect('fs_selected', Callable(self, 'on_fs_selected')) - interfer.connect('interfer_update', Callable(self, 'on_interfer_new')) + min_zoom = ProjectSettings.get_setting('application/config/map_minimum_zoom', 5) + max_zoom = ProjectSettings.get_setting('application/config/map_maximum_zoom', 19) + drag_fsm = DragFSM.OFF + $btn_zoom.set_meta('zoom', min_zoom - 1) + var btns_select: Array = $btns_select.get_children() + btns_select.any(func(btn): btn.button_connect('pressed', Callable(self, 'on_button_select').bind(btn, btns_select))) + $btn_view.button_connect('toggled', Callable(self, 'on_button_view_toggled')) + var nodes: Array = get_tree().get_nodes_in_group('группа-режим-помехи') + nodes.any(func(btn): btn.button_connect('pressed', Callable(self, 'on_button_pressed').bind(btn, nodes))) + threats.connect('threat_new', Callable(self, 'on_threat_new')) + threats.connect('threat_lost', Callable(self, 'on_threat_lost')) + threats.connect('threats_resized', Callable(self, 'on_threats_resized')) + threats.connect('threat_update', Callable(self, 'on_threat_update')) + signaller.connect('zoom_complete', Callable(self, '_on_zoom_complete')) + signaller.connect('rto_threat_sel', Callable(self, 'on_rto_threat_sel').bind(nodes)) + signaller.connect('bip_pressed', Callable(self, 'on_bip_pressed').bind(nodes)) + signaller.connect('rto_threat_unsel', Callable(self, 'on_rto_threat_unsel').bind(nodes)) + signaller.connect('threat_selected', Callable(self, 'on_threat_selected').bind(nodes)) + signaller.connect('interfer_selected', Callable(self, 'on_interfer_selected').bind(nodes)) + signaller.connect('interfer_new', Callable(self, 'on_interfer_new')) + signaller.connect('interfer_off_all', Callable(self, 'on_interfer_off_all')) + signaller.connect('fs_selected', Callable(self, 'on_fs_selected')) + interfer.connect('interfer_update', Callable(self, 'on_interfer_new')) func get_all_nodes(root): - var nodes = [] - nodes.append(root) - for child in root.get_children(): - nodes.append_array(get_all_nodes(child)) - return nodes + var nodes = [] + nodes.append(root) + for child in root.get_children(): + nodes.append_array(get_all_nodes(child)) + return nodes + - func on_enter_tree() -> void: - call_deferred('_on_btn_scale_pressed') - call_deferred('on_button_strobe_pressed') - $tilemap.position = $canvas.get_strob_center() + call_deferred('_on_btn_scale_pressed') + call_deferred('on_button_strobe_pressed') + $tilemap.position = $canvas.get_strob_center() ## Производит отображение цели на диаграмму. func map_threat_to_bip(th: threats.Threat, bip: Control): - bip.position = tools.pos_calc( - th.freq, - th.aoa, - radius_center, - radius_internal, - radius_outter, - freq_low, - freq_high) - bip.size / 2 + bip.position = tools.pos_calc( + th.freq, + th.aoa, + radius_center, + radius_internal, + radius_outter, + freq_low, + freq_high) - bip.size / 2 ## Обработчик сигнала обновления цели. func on_threat_update(th): - var bip = get_node('%d' % th.id) - map_threat_to_bip(th, bip) + var bip = get_node('%d' % th.id) + map_threat_to_bip(th, bip) ## Обработчик выбора помехи для цели. func on_interfer_new(ecm): - if interfer.thrs.has(ecm.ispp): - if interfer.thrs.has(ecm.ispp): - var th_id = interfer.thrs[ecm.ispp] - var bip = get_node('%d' % th_id) - if ecm.krp != interfer.INTERFER_OFF: - bip.set_track_visible(true) - if ecm.svk == 1: - bip.set_ecm_visible(true) - else: bip.set_ecm_visible(false) - else: - bip.set_ecm_visible(false) - bip.set_track_visible(false) - set_fs_color(ecm) + if interfer.thrs.has(ecm.ispp): + if interfer.thrs.has(ecm.ispp): + var th_id = interfer.thrs[ecm.ispp] + var bip = get_node('%d' % th_id) + if ecm.krp != interfer.INTERFER_OFF: + bip.set_track_visible(true) + if ecm.svk == 1: + bip.set_ecm_visible(true) + else: bip.set_ecm_visible(false) + else: + bip.set_ecm_visible(false) + bip.set_track_visible(false) + set_fs_color(ecm) ## Обработчик выключения всех помех. func on_interfer_off_all(): - var bips: Array = get_children().filter(func(item): return item.name.is_valid_int()) - for bip in bips: - bip.set_ecm_visible(false) - bip.set_track_visible(false) - fs_active.clear() - update_fs_colors() + var bips: Array = get_children().filter(func(item): return item.name.is_valid_int()) + for bip in bips: + bip.set_ecm_visible(false) + bip.set_track_visible(false) + fs_active.clear() + update_fs_colors() ## Обработчик сигнала обнаружения цели. func on_threat_new(th) -> void: - var bip = Bip.instantiate() - bip.name = '%d' % th.id - map_threat_to_bip(th, bip) - add_child(bip) + var bip = Bip.instantiate() + bip.name = '%d' % th.id + map_threat_to_bip(th, bip) + add_child(bip) ## Обработчик сигнала потери цели. func on_threat_lost(th) -> void: - var bip = get_node('%d' % th.id) - remove_child(bip) - bip.free() + var bip = get_node('%d' % th.id) + remove_child(bip) + bip.free() ## обработчик нажатия на цель на экране. func on_bip_pressed(th_id, _ecm_btns): - if btn_select == BtnSel.THREAT: - signaller.emit_signal('rto_threat_sel', th_id) + if btn_select == BtnSel.THREAT: + signaller.emit_signal('rto_threat_sel', th_id) ## Обработчик выбора РТО цели. func on_rto_threat_sel(th_id, ecm_btns): - var bips: Array = get_children().filter(func(item): return item.name.is_valid_int()) - for item in bips: - if th_id == item.name.to_int(): - item.set_sel_visible(true) - unsel_fs_all(false) - else: - item.set_sel_visible(false) - for btn in ecm_btns: - set_btns_disabled(false, btn) + var bips: Array = get_children().filter(func(item): return item.name.is_valid_int()) + for item in bips: + if th_id == item.name.to_int(): + item.set_sel_visible(true) + unsel_fs_all(false) + else: + item.set_sel_visible(false) + for btn in ecm_btns: + set_btns_disabled(false, btn) ## Обработчик снятия выбора РТО цели. func on_rto_threat_unsel(th_id, _ecm_btns): - var bips: Array = get_children().filter(func(item): return item.name.is_valid_int()) - for item in bips: - if th_id == item.name.to_int(): - item.set_sel_visible(false) - break - set_ecm_btns_state(true) + var bips: Array = get_children().filter(func(item): return item.name.is_valid_int()) + for item in bips: + if th_id == item.name.to_int(): + item.set_sel_visible(false) + break + set_ecm_btns_state(true) ## Обработчик нажатия кнопки "Строб". Управляет отображением строба. func on_button_strobe_pressed() -> void: - drag_fsm = DragFSM.IDLE if $btns_select/btn_strob.is_pressed() else DragFSM.OFF - $canvas.set_strob_visible(drag_fsm == DragFSM.IDLE) + drag_fsm = DragFSM.IDLE if $btns_select/btn_strob.is_pressed() else DragFSM.OFF + $canvas.set_strob_visible(drag_fsm == DragFSM.IDLE) ## Поведение радиокнопок в группе. Управляет состоянием кнопок выбора целей. func on_button_select(btn_this, btn_others) -> void: - for btn_other in btn_others: btn_other.set_pressed(btn_this == btn_other) - btn_select = btn_this.get_meta('state') - on_button_strobe_pressed() - signaller.emit_signal('rto_threat_unsel_all') - unsel_fs_all(true) - set_ecm_btns_state(true) + for btn_other in btn_others: btn_other.set_pressed(btn_this == btn_other) + btn_select = btn_this.get_meta('state') + on_button_strobe_pressed() + signaller.emit_signal('rto_threat_unsel_all') + unsel_fs_all(true) + set_ecm_btns_state(true) ## Поведение радиокнопок в группе. Управляет состоянием кнопок в радиогруппе. func on_button_pressed(btn_this, btn_others) -> void: - var fs_arr: Array = [] - for btn_other in btn_others: btn_other.set_pressed(btn_this == btn_other) - for key in fs_selected.keys(): - fs_arr.append(get_fs_param(key)) - signaller.emit_signal('interfer_init', btn_this, fs_arr) + var fs_arr: Array = [] + for btn_other in btn_others: btn_other.set_pressed(btn_this == btn_other) + for key in fs_selected.keys(): + fs_arr.append(get_fs_param(key)) + signaller.emit_signal('interfer_init', btn_this, fs_arr) ## Управляет состоянием кнопок в радиогруппе при выборе помехи. func on_interfer_selected(ecm, btns) -> void: - var ecm_name: String - if ecm != null: - ecm_name = ecm.krp - else: - ecm_name = interfer.INTERFER_OFF - for btn in btns: - set_btns_disabled(false, btn) - btn.get_meta('rfi_name') - if ecm_name == btn.get_meta('rfi_name'): - btn.set_pressed(true) - else: - btn.set_pressed(false) + var ecm_name: String + if ecm != null: + ecm_name = ecm.krp + else: + ecm_name = interfer.INTERFER_OFF + for btn in btns: + set_btns_disabled(false, btn) + btn.get_meta('rfi_name') + if ecm_name == btn.get_meta('rfi_name'): + btn.set_pressed(true) + else: + btn.set_pressed(false) ## Возвращает ширину строба по направлению. func get_strob_width(pos: Vector2, prev: float) -> float: - var strob_dir_width: float = prev + (drag_pos_begin.y - pos.y) * drag_scale_width - strob_dir_width = tools.clip_value(strob_dir_width, strob_min_width, strob_max_width) - return strob_dir_width + var strob_dir_width: float = prev + (drag_pos_begin.y - pos.y) * drag_scale_width + strob_dir_width = tools.clip_value(strob_dir_width, strob_min_width, strob_max_width) + return strob_dir_width ## Возвращает ширину строба по частоте. func get_strob_band(pos: Vector2, prev: float) -> float: - var strob_dir_band: float = prev - (drag_pos_begin.y - pos.y) * drag_scale_band - strob_dir_band = tools.clip_value(strob_dir_band, strob_min_band, strob_max_band) - return strob_dir_band + var strob_dir_band: float = prev - (drag_pos_begin.y - pos.y) * drag_scale_band + strob_dir_band = tools.clip_value(strob_dir_band, strob_min_band, strob_max_band) + return strob_dir_band ## Устанавливает центр строба по частоте и направлению. func set_strob(pos: Vector2) -> void: - var radp: Vector2 = tools.map_dec_to_rad(radius_center, pos) - if radp.y < $canvas.get_radius_outter(): - $canvas.set_strob_dir(radp.x) - $canvas.set_strob_radius(radp.y) + var radp: Vector2 = tools.map_dec_to_rad(radius_center, pos) + if radp.y < $canvas.get_radius_outter(): + $canvas.set_strob_dir(radp.x) + $canvas.set_strob_radius(radp.y) ## Обработка движения мыши для управления стробом. Вызывается во время движения мыши. func _on_drag_continue(event) -> void: - if drag_button == MOUSE_BUTTON_RIGHT: - if shift_pressed: - var strob_width: float = get_strob_width(event.position, strob_width_begin) - $canvas.set_strob_width(strob_width) - else: - var strob_band: float = get_strob_band(event.position, strob_band_begin) - $canvas.set_strob_band(strob_band) - elif drag_button == MOUSE_BUTTON_LEFT: - set_strob(event.position) + if drag_button == MOUSE_BUTTON_RIGHT: + if shift_pressed: + var strob_width: float = get_strob_width(event.position, strob_width_begin) + $canvas.set_strob_width(strob_width) + else: + var strob_band: float = get_strob_band(event.position, strob_band_begin) + $canvas.set_strob_band(strob_band) + elif drag_button == MOUSE_BUTTON_LEFT: + set_strob(event.position) ## Обработка движения мыши для управления стробом. Вызывается при нажатии мыши. func _on_drag_begin(event) -> void: - drag_pos_begin = event.position - if drag_button == MOUSE_BUTTON_LEFT: - set_strob(event.position) - elif drag_button == MOUSE_BUTTON_RIGHT: - strob_width_begin = $canvas.get_strob_width() - strob_band_begin = $canvas.get_strob_band() + drag_pos_begin = event.position + if drag_button == MOUSE_BUTTON_LEFT: + set_strob(event.position) + elif drag_button == MOUSE_BUTTON_RIGHT: + strob_width_begin = $canvas.get_strob_width() + strob_band_begin = $canvas.get_strob_band() ## Обработчик событий ввода. Вызывается godot. func _input(event) -> void: - if not is_visible_in_tree(): return - if event is InputEventMouseMotion: - if drag_fsm == DragFSM.DRAG: - emit_signal('drag_continue', event) - elif event is InputEventMouseButton: - drag_button = event.button_index - if event.pressed: - if drag_fsm == DragFSM.IDLE: - drag_fsm = DragFSM.DRAG - emit_signal('drag_begin', event) - else: - if btn_select == BtnSel.FS: - sel_fs_sector(event) - drag_fsm = DragFSM.IDLE - emit_signal('drag_end', event) + if not is_visible_in_tree(): return + if event is InputEventMouseMotion: + if drag_fsm == DragFSM.DRAG: + emit_signal('drag_continue', event) + elif event is InputEventMouseButton: + drag_button = event.button_index + if event.pressed: + if drag_fsm == DragFSM.IDLE: + drag_fsm = DragFSM.DRAG + emit_signal('drag_begin', event) + else: + if btn_select == BtnSel.FS: + sel_fs_sector(event) + drag_fsm = DragFSM.IDLE + emit_signal('drag_end', event) - elif event is InputEventKey: - if event.physical_keycode == KEY_SHIFT: - shift_pressed = event.pressed + elif event is InputEventKey: + if event.physical_keycode == KEY_SHIFT: + shift_pressed = event.pressed ## Вызывается по нажатию кнопки "Детализация". func _on_btn_scale_pressed() -> void: - var zoom = $btn_zoom.get_meta('zoom') - zoom = zoom + 1 if zoom < max_zoom else min_zoom - signaller.emit_signal('zoom_changed', zoom) + var zoom = $btn_zoom.get_meta('zoom') + zoom = zoom + 1 if zoom < max_zoom else min_zoom + signaller.emit_signal('zoom_changed', zoom) ## Вызывается по завершению изменения детализации карты. func _on_zoom_complete(zoom: int) -> void: - var prefix = $btn_zoom.get_meta('prefix') - $btn_zoom/lbl.text = '%s %d' % [prefix, zoom] - $btn_zoom.set_meta('zoom', zoom) + var prefix = $btn_zoom.get_meta('prefix') + $btn_zoom/lbl.text = '%s %d' % [prefix, zoom] + $btn_zoom.set_meta('zoom', zoom) ## Обработчик нажатия по сектору формирователя сигналов. func sel_fs_sector(event): - if (event.button_index == MOUSE_BUTTON_LEFT) or (event.button_index == MOUSE_BUTTON_RIGHT): - var center: Vector2 = $tilemap.position - var center_offset: Vector2 = ProjectSettings.get_setting('application/interfer/center_offset', Vector2i(0, 25)) - var angle: float = event.position.angle_to_point(center + center_offset) * 180/PI - 45.0 - if angle < 0: - angle += 360 - var distance: float = event.position.distance_to(center + center_offset) - var sector: int - var band: int - var has_band: bool = false - for sec in fs_sectors.values(): - if angle >= sec[0] and angle <= sec[1]: - sector = fs_sectors.find_key(sec) - for diap in fs_band.values(): - if distance >= diap[0] and distance <= diap[1]: - band = fs_band.find_key(diap) - has_band = true - if not has_band: - return - var fs_index = band * 4 + sector - if event.button_index == MOUSE_BUTTON_LEFT: - fs_selected[fs_index] = null - else: - fs_selected.erase(fs_index) - update_fs_colors() - signaller.emit_signal('rto_threat_unsel_all') - var btn_state = len(fs_selected) == 0 - set_ecm_btns_state(btn_state) + if (event.button_index == MOUSE_BUTTON_LEFT) or (event.button_index == MOUSE_BUTTON_RIGHT): + var center: Vector2 = $tilemap.position + var center_offset: Vector2 = ProjectSettings.get_setting('application/interfer/center_offset', Vector2i(0, 25)) + var angle: float = event.position.angle_to_point(center + center_offset) * 180/PI - 45.0 + if angle < 0: + angle += 360 + var distance: float = event.position.distance_to(center + center_offset) + var sector: int + var band: int + var has_band: bool = false + for sec in fs_sectors.values(): + if angle >= sec[0] and angle <= sec[1]: + sector = fs_sectors.find_key(sec) + for diap in fs_band.values(): + if distance >= diap[0] and distance <= diap[1]: + band = fs_band.find_key(diap) + has_band = true + if not has_band: + return + var fs_index = band * 4 + sector + if event.button_index == MOUSE_BUTTON_LEFT: + fs_selected[fs_index] = null + else: + fs_selected.erase(fs_index) + update_fs_colors() + signaller.emit_signal('rto_threat_unsel_all') + var btn_state = len(fs_selected) == 0 + set_ecm_btns_state(btn_state) func set_fs_color(ecm): - var fs_index = get_fs_index(ecm) - if ecm.krp != interfer.INTERFER_OFF: - fs_active[fs_index] = null - else: - fs_active.erase(fs_index) - update_fs_colors() + var fs_index = get_fs_index(ecm) + if ecm.krp != interfer.INTERFER_OFF: + fs_active[fs_index] = null + else: + fs_active.erase(fs_index) + update_fs_colors() func update_fs_colors(): - var fs_colors = $canvas.fs_default_colors.duplicate() - var color_ecm: Vector3 = ProjectSettings.get_setting('application/interfer/interfer_color', Vector3(0.58, 0.47, 0.51)) - var color_select: Vector3 = ProjectSettings.get_setting('application/interfer/select_color', Vector3(1.0, 1.0, 1.0)) - for key in fs_active.keys(): - fs_colors[key] = color_ecm - for key in fs_selected.keys(): - fs_colors[key] = color_select - $canvas.set_band_colors(fs_colors) + var fs_colors = $canvas.fs_default_colors.duplicate() + var color_ecm: Vector3 = ProjectSettings.get_setting('application/interfer/interfer_color', Vector3(0.58, 0.47, 0.51)) + var color_select: Vector3 = ProjectSettings.get_setting('application/interfer/select_color', Vector3(1.0, 1.0, 1.0)) + for key in fs_active.keys(): + fs_colors[key] = color_ecm + for key in fs_selected.keys(): + fs_colors[key] = color_select + $canvas.set_band_colors(fs_colors) func unsel_fs_all(need_btns_off: bool): - fs_selected.clear() - update_fs_colors() - if need_btns_off: - set_ecm_btns_state(true) + fs_selected.clear() + update_fs_colors() + if need_btns_off: + set_ecm_btns_state(true) func set_ecm_btns_state(value: bool): - var btns: Array = get_tree().get_nodes_in_group('группа-режим-помехи') - for btn in btns: - btn.set_pressed(false) - set_btns_disabled(value, btn) + var btns: Array = get_tree().get_nodes_in_group('группа-режим-помехи') + for btn in btns: + btn.set_pressed(false) + set_btns_disabled(value, btn) func get_fs_param(index: int) -> Array: - var freq = (fs_freq[index / 4][0] + fs_freq[index / 4][1])/2 - var sector = (fs_sectors[index % 4][0] + fs_sectors[index % 4][1])/2 - 45.0 - if sector < 0: - sector += 360.0 - var width = fs_freq[index / 4][1] - fs_freq[index / 4][0] - return [sector, freq, width, index] + var freq = (fs_freq[index / 4][0] + fs_freq[index / 4][1])/2 + var sector = (fs_sectors[index % 4][0] + fs_sectors[index % 4][1])/2 - 45.0 + if sector < 0: + sector += 360.0 + var width = fs_freq[index / 4][1] - fs_freq[index / 4][0] + return [sector, freq, width, index] func on_fs_selected(ecm): - var fs_index = get_fs_index(ecm) - fs_selected.clear() - fs_selected[fs_index] = null - update_fs_colors() + var fs_index = get_fs_index(ecm) + fs_selected.clear() + fs_selected[fs_index] = null + update_fs_colors() func get_fs_index(ecm): - var sector: int - var band: int - var angle: float = ecm.kni + 45.0 - if angle > 360.0: - angle -= 360.0 - for sec in fs_sectors.values(): - if float(angle) >= sec[0] and float(angle) <= sec[1]: - sector = fs_sectors.find_key(sec) - for diap in fs_freq.values(): - if ecm.params.has('freq'): - if ecm.params['freq'] >= diap[0] and ecm.params['freq'] <= diap[1]: - band = fs_freq.find_key(diap) - var fs_index = band * 4 + sector - return fs_index + var sector: int + var band: int + var angle: float = ecm.kni + 45.0 + if angle > 360.0: + angle -= 360.0 + for sec in fs_sectors.values(): + if float(angle) >= sec[0] and float(angle) <= sec[1]: + sector = fs_sectors.find_key(sec) + for diap in fs_freq.values(): + if ecm.params.has('freq'): + if ecm.params['freq'] >= diap[0] and ecm.params['freq'] <= diap[1]: + band = fs_freq.find_key(diap) + var fs_index = band * 4 + sector + return fs_index func set_btns_disabled(value: bool, btn): - var external_cu = ProjectSettings.get_setting('application/config/external_cu', false) - if external_cu: - btn.set_pressed(false) - btn.set_disabled(true) - else: - btn.set_disabled(value) + var external_cu = ProjectSettings.get_setting('application/config/external_cu', false) + if external_cu: + btn.set_pressed(false) + btn.set_disabled(true) + else: + btn.set_disabled(value) diff --git a/scenes/эмс/эмс.gd b/scenes/эмс/эмс.gd index 6b948e9b..f05680df 100644 --- a/scenes/эмс/эмс.gd +++ b/scenes/эмс/эмс.gd @@ -24,253 +24,253 @@ var posy: int = 70 ## Позиция элементов по y. func _ready() -> void: - call_deferred('init_boards') + call_deferred('init_boards') ## Инициализация основных элементов экрана ЭМС. func init_boards(): - # Выставление позиций элементов ЭМС на экране - $scrollin.set_position(Vector2(posx,posy)) - $scrollout.set_position(Vector2(posx+650,posy)) - $scrl_in_set.set_position(Vector2(posx,posy + 700)) - $scrl_out_set.set_position(Vector2(posx+650,posy + 700)) - $lbl_online.set_position(Vector2(posx,posy-60)) - $btn_flash_read.set_position(Vector2(posx,posy + 900)) - $btn_flash_write.set_position(Vector2(posx,posy+ 935)) - $btn_connect.set_position(Vector2(posx+590,posy-25)) - $btn_disconnect.set_position(Vector2(posx+587,posy + 675)) + # Выставление позиций элементов ЭМС на экране + $scrollin.set_position(Vector2(posx,posy)) + $scrollout.set_position(Vector2(posx+650,posy)) + $scrl_in_set.set_position(Vector2(posx,posy + 700)) + $scrl_out_set.set_position(Vector2(posx+650,posy + 700)) + $lbl_online.set_position(Vector2(posx,posy-60)) + $btn_flash_read.set_position(Vector2(posx,posy + 900)) + $btn_flash_write.set_position(Vector2(posx,posy+ 935)) + $btn_connect.set_position(Vector2(posx+590,posy-25)) + $btn_disconnect.set_position(Vector2(posx+587,posy + 675)) - g_boards['КЭМС'] = yemsboards.EmsBoard.new('КЭМС', 0x130, yemsconsts.INPUT_ROWS, yemsconsts.OUTPUT_ROWS) - g_boards['П5-28'] = yemsboards.EmsBoard.new('П5-28', 0x140, yemsconsts.INPUT_ROWS1, yemsconsts.OUTPUT_ROWS1) - init_tbl_in() - $scrollin/tbl_in.pin_header($scrollin, self) - init_tbl_out() - $scrollout/tbl_out.pin_header($scrollout, self) - $scrl_in_set/tbl_in_set.pin_header($scrl_in_set, self) - $scrl_out_set/tbl_out_set.pin_header($scrl_out_set, self) - init_tbl_files() - $scrl_files/tbl_files.pin_header($scrl_files, self) - $btn_connect.connect('pressed', Callable(self, 'on_btn_connect')) - $btn_disconnect.connect('pressed', Callable(self, 'on_btn_disconnect')) - $btn_flash_read.button_connect('pressed', Callable(self, 'on_btn_flash_read').bind($btn_flash_read)) - $btn_flash_write.button_connect('pressed', Callable(self, 'on_btn_flash_write').bind($btn_flash_write)) + g_boards['КЭМС'] = yemsboards.EmsBoard.new('КЭМС', 0x130, yemsconsts.INPUT_ROWS, yemsconsts.OUTPUT_ROWS) + g_boards['П5-28'] = yemsboards.EmsBoard.new('П5-28', 0x140, yemsconsts.INPUT_ROWS1, yemsconsts.OUTPUT_ROWS1) + init_tbl_in() + $scrollin/tbl_in.pin_header($scrollin, self) + init_tbl_out() + $scrollout/tbl_out.pin_header($scrollout, self) + $scrl_in_set/tbl_in_set.pin_header($scrl_in_set, self) + $scrl_out_set/tbl_out_set.pin_header($scrl_out_set, self) + init_tbl_files() + $scrl_files/tbl_files.pin_header($scrl_files, self) + $btn_connect.connect('pressed', Callable(self, 'on_btn_connect')) + $btn_disconnect.connect('pressed', Callable(self, 'on_btn_disconnect')) + $btn_flash_read.button_connect('pressed', Callable(self, 'on_btn_flash_read').bind($btn_flash_read)) + $btn_flash_write.button_connect('pressed', Callable(self, 'on_btn_flash_write').bind($btn_flash_write)) ## Обработчик нажатия кнопки disconnect. func on_btn_disconnect(): - var selected_rows_in: Array = [] - var selected_rows_out: Array = [] - $scrl_in_set/tbl_in_set.get_selected_rows(selected_rows_in) - $scrl_out_set/tbl_out_set.get_selected_rows(selected_rows_out) - if len(selected_rows_in): - on_disconnect($scrl_in_set/tbl_in_set, selected_rows_in, 'вход') - elif len(selected_rows_out): - on_disconnect($scrl_out_set/tbl_out_set, selected_rows_out, 'выход') + var selected_rows_in: Array = [] + var selected_rows_out: Array = [] + $scrl_in_set/tbl_in_set.get_selected_rows(selected_rows_in) + $scrl_out_set/tbl_out_set.get_selected_rows(selected_rows_out) + if len(selected_rows_in): + on_disconnect($scrl_in_set/tbl_in_set, selected_rows_in, 'вход') + elif len(selected_rows_out): + on_disconnect($scrl_out_set/tbl_out_set, selected_rows_out, 'выход') ## Разрывает связь входа и выхода, [param tbl] - таблица, [param selected_rows] - выбранные строки. ## [param type_dev] - тип, 'вход' или 'выход'. func on_disconnect(tbl, selected_rows, type_dev: String): - var dev = tbl.get_node_user_data(0, 0) - var user_data: Array = tbl.get_node_user_data(0, selected_rows[0]) - var con_dev = user_data[0] - var connector: String = con_dev.connector - dev.connect_devs.erase(con_dev.connector) - con_dev.connect_devs.erase(dev.connector) - if type_dev == 'вход': - set_in_dev(dev) - set_out_dev(con_dev) - elif type_dev == 'выход': - set_in_dev(con_dev) - set_out_dev(dev) + var dev = tbl.get_node_user_data(0, 0) + var user_data: Array = tbl.get_node_user_data(0, selected_rows[0]) + var con_dev = user_data[0] + var connector: String = con_dev.connector + dev.connect_devs.erase(con_dev.connector) + con_dev.connect_devs.erase(dev.connector) + if type_dev == 'вход': + set_in_dev(dev) + set_out_dev(con_dev) + elif type_dev == 'выход': + set_in_dev(con_dev) + set_out_dev(dev) ## Обработчик нажатия кнопки "Прочитать". func on_btn_flash_read(btn): - await get_tree().create_timer(0.1).timeout - btn.pressed = false + await get_tree().create_timer(0.1).timeout + btn.pressed = false ## Обработчик нажатия кнопки "Записать". func on_btn_flash_write(btn): - await get_tree().create_timer(0.1).timeout - btn.pressed = false + await get_tree().create_timer(0.1).timeout + btn.pressed = false ## Инициализация таблицы с файлами настроек ЭМС. func init_tbl_files(): - $scrl_files/tbl_files.set_header([TableHeader, TableHeader, TableHeader]) - $scrl_files/tbl_files.set_header_text(['Имя файла', 'Дата изменения', 'Размер']) - $scrl_files/tbl_files.set_columns_min_size([200, 120, 70]) + $scrl_files/tbl_files.set_header([TableHeader, TableHeader, TableHeader]) + $scrl_files/tbl_files.set_header_text(['Имя файла', 'Дата изменения', 'Размер']) + $scrl_files/tbl_files.set_columns_min_size([200, 120, 70]) ## Инициализация таблиц входов. func init_tbl_in(): - # Инициализация основной таблицы входов, в ней отображается информация о параметрах и состоянии - var header_type: Array = [TableHeader, TableHeader, CellFront, CellFrontRear, TableHeader] - var header_text: Array = ['Входы', 'Соединитель', 'мкс', 'мкс', 'Состояние'] - var row_type: Array = [CellLineEdit, CellLineEdit, CellLineEdit, CellLineEdit, CellState] - var yams_alligment_in: Array = [ HORIZONTAL_ALIGNMENT_LEFT, HORIZONTAL_ALIGNMENT_CENTER, HORIZONTAL_ALIGNMENT_CENTER, HORIZONTAL_ALIGNMENT_CENTER, HORIZONTAL_ALIGNMENT_CENTER] - var column_size: Array = [250, 100, 70, 70, 50] - $scrollin/tbl_in.set_header(header_type) - $scrollin/tbl_in.set_header_text(header_text) - for board in g_boards: - for item in g_boards[board].in_tbl: - $scrollin/tbl_in.add_row(row_type) - var row_index : int = $scrollin/tbl_in.get_rows_count() - 1 - var params : Array = item[1] - var in_dev: yemsdevs.InDev = yemsdevs.InDev.new(item[0], params, row_index) - in_dev.board = board - for i in range(len(row_type)): - if i < len(params): - var node = $scrollin/tbl_in.get_node2(i, row_index) - node.text = params[i] - node.editable = false - $scrollin/tbl_in.set_node_user_data(i, row_index, [in_dev, board]) - $scrollin/tbl_in.set_columns_alignments(yams_alligment_in) - $scrollin/tbl_in.set_columns_min_size(column_size) - $scrollin/tbl_in.connect('row_pressed', Callable(self, 'on_row_pressed_in')) - $scrollin/tbl_in.connect('selected_changed', Callable(self, 'on_selected_changed_in')) + # Инициализация основной таблицы входов, в ней отображается информация о параметрах и состоянии + var header_type: Array = [TableHeader, TableHeader, CellFront, CellFrontRear, TableHeader] + var header_text: Array = ['Входы', 'Соединитель', 'мкс', 'мкс', 'Состояние'] + var row_type: Array = [CellLineEdit, CellLineEdit, CellLineEdit, CellLineEdit, CellState] + var yams_alligment_in: Array = [ HORIZONTAL_ALIGNMENT_LEFT, HORIZONTAL_ALIGNMENT_CENTER, HORIZONTAL_ALIGNMENT_CENTER, HORIZONTAL_ALIGNMENT_CENTER, HORIZONTAL_ALIGNMENT_CENTER] + var column_size: Array = [250, 100, 70, 70, 50] + $scrollin/tbl_in.set_header(header_type) + $scrollin/tbl_in.set_header_text(header_text) + for board in g_boards: + for item in g_boards[board].in_tbl: + $scrollin/tbl_in.add_row(row_type) + var row_index : int = $scrollin/tbl_in.get_rows_count() - 1 + var params : Array = item[1] + var in_dev: yemsdevs.InDev = yemsdevs.InDev.new(item[0], params, row_index) + in_dev.board = board + for i in range(len(row_type)): + if i < len(params): + var node = $scrollin/tbl_in.get_node2(i, row_index) + node.text = params[i] + node.editable = false + $scrollin/tbl_in.set_node_user_data(i, row_index, [in_dev, board]) + $scrollin/tbl_in.set_columns_alignments(yams_alligment_in) + $scrollin/tbl_in.set_columns_min_size(column_size) + $scrollin/tbl_in.connect('row_pressed', Callable(self, 'on_row_pressed_in')) + $scrollin/tbl_in.connect('selected_changed', Callable(self, 'on_selected_changed_in')) - # Инициализация таблицы изменения параметров входа и списка выходов связанных с ним - header_text[0] = 'Вход' - $scrl_in_set/tbl_in_set.set_header(header_type) - $scrl_in_set/tbl_in_set.set_header_text(header_text) - $scrl_in_set/tbl_in_set.set_columns_alignments(yams_alligment_in) - $scrl_in_set/tbl_in_set.set_columns_min_size(column_size) - $scrl_in_set/tbl_in_set.connect('row_pressed', Callable(self, 'on_row_pressed_si')) - $scrl_in_set/tbl_in_set.connect('selected_changed', Callable(self, 'on_selected_changed_si')) + # Инициализация таблицы изменения параметров входа и списка выходов связанных с ним + header_text[0] = 'Вход' + $scrl_in_set/tbl_in_set.set_header(header_type) + $scrl_in_set/tbl_in_set.set_header_text(header_text) + $scrl_in_set/tbl_in_set.set_columns_alignments(yams_alligment_in) + $scrl_in_set/tbl_in_set.set_columns_min_size(column_size) + $scrl_in_set/tbl_in_set.connect('row_pressed', Callable(self, 'on_row_pressed_si')) + $scrl_in_set/tbl_in_set.connect('selected_changed', Callable(self, 'on_selected_changed_si')) ## Обработчик редактирования Entry в таблице параметров входов/выходов. func on_edit_entry(text : String, entry): - print_debug(text, entry) + print_debug(text, entry) ## Обработчик выбора строки в таблице входов. func on_selected_changed_in(i_row: int): - var selecter_rows: Array = [] - $scrollin/tbl_in.get_selected_rows(selecter_rows) - var user_data = $scrollin/tbl_in.get_node_user_data(0, i_row) - var dev = user_data[0] - set_in_dev(dev) + var selecter_rows: Array = [] + $scrollin/tbl_in.get_selected_rows(selecter_rows) + var user_data = $scrollin/tbl_in.get_node_user_data(0, i_row) + var dev = user_data[0] + set_in_dev(dev) ## Обработчик нажатия на таблицу входов. func on_row_pressed_in(i_row: int): - on_row_pressed(i_row, $scrollin/tbl_in) - on_selected_changed_in(i_row) + on_row_pressed(i_row, $scrollin/tbl_in) + on_selected_changed_in(i_row) ## Выбор входного устройства, для отображения в таблице соединений входа и изменения параметров. func set_in_dev(dev): - var row_type: Array = [CellLineEdit, CellLineEdit, CellLineEdit, CellLineEdit, CellState] - set_dev($scrl_in_set/tbl_in_set, dev, row_type, IN_SET_ROW, 'Список соединённых выходов :', [250, 100, 70, 70, 70]) + var row_type: Array = [CellLineEdit, CellLineEdit, CellLineEdit, CellLineEdit, CellState] + set_dev($scrl_in_set/tbl_in_set, dev, row_type, IN_SET_ROW, 'Список соединённых выходов :', [250, 100, 70, 70, 70]) ## Обработчик выбора строки в таблице соединений входа и изменения параметров. func on_selected_changed_si(i_row: int): - if i_row < 2: - return - var out_user_data = $scrl_in_set/tbl_in_set.get_node_user_data(0, i_row) - var dev = out_user_data[0] - var connector = dev.connector - for i in range($scrollout/tbl_out.get_rows_count()): - var node = $scrollout/tbl_out.get_node2(1, i) - if connector == node.text: - on_row_pressed_out(i) + if i_row < 2: + return + var out_user_data = $scrl_in_set/tbl_in_set.get_node_user_data(0, i_row) + var dev = out_user_data[0] + var connector = dev.connector + for i in range($scrollout/tbl_out.get_rows_count()): + var node = $scrollout/tbl_out.get_node2(1, i) + if connector == node.text: + on_row_pressed_out(i) ## Обработчик нажатия на таблицу соединений входа и изменения параметров. func on_row_pressed_si(i_row: int): - if i_row < 2: - return - on_row_pressed(i_row, $scrl_in_set/tbl_in_set) - on_selected_changed_si(i_row) + if i_row < 2: + return + on_row_pressed(i_row, $scrl_in_set/tbl_in_set) + on_selected_changed_si(i_row) ## Инициализация таблиц выходов. func init_tbl_out(): - # Инициализация основной таблицы выходов, в ней отображается информация о параметрах и состоянии - var header_text = ['Выходы', 'Соединитель', 'мкс', 'мкс', 'Состояние', 'Режим'] - var yams_alligment_out: Array = [ HORIZONTAL_ALIGNMENT_LEFT, HORIZONTAL_ALIGNMENT_CENTER, HORIZONTAL_ALIGNMENT_CENTER, HORIZONTAL_ALIGNMENT_CENTER, HORIZONTAL_ALIGNMENT_CENTER, HORIZONTAL_ALIGNMENT_CENTER] - $scrollout/tbl_out.set_header([TableHeader, TableHeader, CellFront, CellFrontRear, TableHeader, TableHeader]) - $scrollout/tbl_out.set_header_text(header_text) - for board in g_boards: - for item in g_boards[board].out_tbl: - $scrollout/tbl_out.add_row([CellLineEdit, CellLineEdit, CellLineEdit, CellLineEdit, CellState, CellLineEdit]) - var row_index : int = $scrollout/tbl_out.get_rows_count() - 1 - var params : Array = item[1] - var out_dev = yemsdevs.OutDev.new(item[0], params, row_index) - out_dev.board = board - for i in range(len(header_text)): - var cell = $scrollout/tbl_out.get_node2(i, row_index) - if i < len(params): - cell.text = params[i] - cell.editable = false - $scrollout/tbl_out.set_node_user_data(i, row_index, [out_dev, board]) - if i in COLUMN_OPTS_BTN: - cell.text = out_dev.mode - $scrollout/tbl_out.set_columns_min_size([250, 100, 70, 70, 70, 100]) - $scrollout/tbl_out.set_columns_alignments(yams_alligment_out) - $scrollout/tbl_out.connect('row_pressed', Callable(self, 'on_row_pressed_out')) - $scrollout/tbl_out.connect('selected_changed', Callable(self, 'on_selected_changed_out')) + # Инициализация основной таблицы выходов, в ней отображается информация о параметрах и состоянии + var header_text = ['Выходы', 'Соединитель', 'мкс', 'мкс', 'Состояние', 'Режим'] + var yams_alligment_out: Array = [ HORIZONTAL_ALIGNMENT_LEFT, HORIZONTAL_ALIGNMENT_CENTER, HORIZONTAL_ALIGNMENT_CENTER, HORIZONTAL_ALIGNMENT_CENTER, HORIZONTAL_ALIGNMENT_CENTER, HORIZONTAL_ALIGNMENT_CENTER] + $scrollout/tbl_out.set_header([TableHeader, TableHeader, CellFront, CellFrontRear, TableHeader, TableHeader]) + $scrollout/tbl_out.set_header_text(header_text) + for board in g_boards: + for item in g_boards[board].out_tbl: + $scrollout/tbl_out.add_row([CellLineEdit, CellLineEdit, CellLineEdit, CellLineEdit, CellState, CellLineEdit]) + var row_index : int = $scrollout/tbl_out.get_rows_count() - 1 + var params : Array = item[1] + var out_dev = yemsdevs.OutDev.new(item[0], params, row_index) + out_dev.board = board + for i in range(len(header_text)): + var cell = $scrollout/tbl_out.get_node2(i, row_index) + if i < len(params): + cell.text = params[i] + cell.editable = false + $scrollout/tbl_out.set_node_user_data(i, row_index, [out_dev, board]) + if i in COLUMN_OPTS_BTN: + cell.text = out_dev.mode + $scrollout/tbl_out.set_columns_min_size([250, 100, 70, 70, 70, 100]) + $scrollout/tbl_out.set_columns_alignments(yams_alligment_out) + $scrollout/tbl_out.connect('row_pressed', Callable(self, 'on_row_pressed_out')) + $scrollout/tbl_out.connect('selected_changed', Callable(self, 'on_selected_changed_out')) - # Инициализация таблицы изменения параметров выхода и списка входов связанных с ним - header_text[0] = 'Выход' - $scrl_out_set/tbl_out_set.set_header([TableHeader, TableHeader, CellFront, CellFrontRear, TableHeader, TableHeader]) - $scrl_out_set/tbl_out_set.set_header_text(header_text) - $scrl_out_set/tbl_out_set.set_columns_min_size([250, 100, 70, 70, 70, 100]) - $scrl_out_set/tbl_out_set.connect('row_pressed', Callable(self, 'on_row_pressed_so')) - $scrl_out_set/tbl_out_set.connect('selected_changed', Callable(self, 'on_selected_changed_so')) + # Инициализация таблицы изменения параметров выхода и списка входов связанных с ним + header_text[0] = 'Выход' + $scrl_out_set/tbl_out_set.set_header([TableHeader, TableHeader, CellFront, CellFrontRear, TableHeader, TableHeader]) + $scrl_out_set/tbl_out_set.set_header_text(header_text) + $scrl_out_set/tbl_out_set.set_columns_min_size([250, 100, 70, 70, 70, 100]) + $scrl_out_set/tbl_out_set.connect('row_pressed', Callable(self, 'on_row_pressed_so')) + $scrl_out_set/tbl_out_set.connect('selected_changed', Callable(self, 'on_selected_changed_so')) ## Обработчик выбора строки в таблице соединений выхода и изменения параметров. func on_selected_changed_so(i_row: int): - if i_row < 2: - return - var in_user_data = $scrl_out_set/tbl_out_set.get_node_user_data(0, i_row) - var dev = in_user_data[0] - var connector = dev.connector - for i in range($scrollin/tbl_in.get_rows_count()): - var node = $scrollin/tbl_in.get_node2(1, i) - if connector == node.text: - on_row_pressed_in(i) + if i_row < 2: + return + var in_user_data = $scrl_out_set/tbl_out_set.get_node_user_data(0, i_row) + var dev = in_user_data[0] + var connector = dev.connector + for i in range($scrollin/tbl_in.get_rows_count()): + var node = $scrollin/tbl_in.get_node2(1, i) + if connector == node.text: + on_row_pressed_in(i) ## Обработчик нажатия на таблицу соединений выхода и изменения параметров. func on_row_pressed_so(i_row: int): - if i_row < 2: - return - on_row_pressed(i_row, $scrl_out_set/tbl_out_set) - on_selected_changed_so(i_row) + if i_row < 2: + return + on_row_pressed(i_row, $scrl_out_set/tbl_out_set) + on_selected_changed_so(i_row) ## Обработчик выбора строки в таблице выходов. func on_selected_changed_out(i_row: int): - var selecter_rows: Array = [] - $scrollout/tbl_out.get_selected_rows(selecter_rows) - var user_data = $scrollout/tbl_out.get_node_user_data(0, i_row) - var dev = user_data[0] - set_out_dev(dev) + var selecter_rows: Array = [] + $scrollout/tbl_out.get_selected_rows(selecter_rows) + var user_data = $scrollout/tbl_out.get_node_user_data(0, i_row) + var dev = user_data[0] + set_out_dev(dev) ## Обработчик выбора строки в таблице выходов. func on_row_pressed_out(i_row: int): - on_row_pressed(i_row, $scrollout/tbl_out) - on_selected_changed_out(i_row) + on_row_pressed(i_row, $scrollout/tbl_out) + on_selected_changed_out(i_row) ## Выбор выходного устройства, для отображения в таблице соединений выхода и изменения параметров. func set_out_dev(dev): - var row_type: Array = [CellLineEdit, CellLineEdit, CellLineEdit, CellLineEdit, CellState, CellOptBtn] - set_dev($scrl_out_set/tbl_out_set, dev, row_type, OUT_SET_ROW, 'Список соединённых входов :', [250, 100, 70, 70, 70, 100]) - var mode = $scrl_out_set/tbl_out_set.get_node2(5, 0) - for opt in OUT_MODE: - mode.add_item(opt) - var mode_index: int = OUT_MODE.find(dev.mode) - if mode_index != -1: - mode.select(mode_index) + var row_type: Array = [CellLineEdit, CellLineEdit, CellLineEdit, CellLineEdit, CellState, CellOptBtn] + set_dev($scrl_out_set/tbl_out_set, dev, row_type, OUT_SET_ROW, 'Список соединённых входов :', [250, 100, 70, 70, 70, 100]) + var mode = $scrl_out_set/tbl_out_set.get_node2(5, 0) + for opt in OUT_MODE: + mode.add_item(opt) + var mode_index: int = OUT_MODE.find(dev.mode) + if mode_index != -1: + mode.select(mode_index) ## Выбор входного или выходного устройства.[br] @@ -280,53 +280,53 @@ func set_out_dev(dev): ## [param row_type_conn] - массив с пустыми LineEdit,[br] ## [param lbl_text] - текст отображаемый во 2 строке: пример: ('Список соединённых входов :').[br] func set_dev(tbl, dev, row_type, row_type_conn, lbl_text, row_size): - tbl.clear_rows() - tbl.add_row(row_type) - tbl.set_columns_min_size(row_size) - var le_name: LineEdit = tbl.get_node2(0, 0) - le_name.editable = false - le_name.text = dev.dev_name - var connector: LineEdit = tbl.get_node2(1, 0) - connector.editable = false - connector.text = dev.connector - var delay: LineEdit = tbl.get_node2(2, 0) - delay.text = '%4.2f'%dev.delay - var offset: LineEdit = tbl.get_node2(3, 0) - offset.text = '%4.2f'%dev.offset - var state = tbl.get_node2(4, 0) - state.set_frame(dev.state) - tbl.add_row(row_type_conn) - var lbl: LineEdit = tbl.get_node2(0, 1) - lbl.text = lbl_text - for i in len(row_type_conn): - var le: LineEdit = tbl.get_node2(i, 1) - tbl.set_node_user_data(i, 0, dev) - le.editable = false + tbl.clear_rows() + tbl.add_row(row_type) + tbl.set_columns_min_size(row_size) + var le_name: LineEdit = tbl.get_node2(0, 0) + le_name.editable = false + le_name.text = dev.dev_name + var connector: LineEdit = tbl.get_node2(1, 0) + connector.editable = false + connector.text = dev.connector + var delay: LineEdit = tbl.get_node2(2, 0) + delay.text = '%4.2f'%dev.delay + var offset: LineEdit = tbl.get_node2(3, 0) + offset.text = '%4.2f'%dev.offset + var state = tbl.get_node2(4, 0) + state.set_frame(dev.state) + tbl.add_row(row_type_conn) + var lbl: LineEdit = tbl.get_node2(0, 1) + lbl.text = lbl_text + for i in len(row_type_conn): + var le: LineEdit = tbl.get_node2(i, 1) + tbl.set_node_user_data(i, 0, dev) + le.editable = false - for i in dev.connect_devs: - var dev_con = dev.connect_devs[i][0] - tbl.add_row(row_type_conn) - var node_index: Array = [0, tbl.get_rows_count() -1] - var le_name_out: LineEdit = tbl.get_node2(node_index[0], node_index[1]) - disable_edit(tbl, len(row_type_conn), node_index[1]) - le_name_out.text = dev_con.dev_name - tbl.set_node_user_data(node_index[0], node_index[1], dev.connect_devs[i]) - tbl.connect_columns(self, 'text_submitted', 'on_edit_entry', TABLE_ENTRY_COLUMNS, []) + for i in dev.connect_devs: + var dev_con = dev.connect_devs[i][0] + tbl.add_row(row_type_conn) + var node_index: Array = [0, tbl.get_rows_count() -1] + var le_name_out: LineEdit = tbl.get_node2(node_index[0], node_index[1]) + disable_edit(tbl, len(row_type_conn), node_index[1]) + le_name_out.text = dev_con.dev_name + tbl.set_node_user_data(node_index[0], node_index[1], dev.connect_devs[i]) + tbl.connect_columns(self, 'text_submitted', 'on_edit_entry', TABLE_ENTRY_COLUMNS, []) ## Обработчик нажатия кнопки connect. func on_btn_connect(): - var in_row_index: Array = [] - var out_row_index: Array = [] - $scrollin/tbl_in.get_selected_rows(in_row_index) - $scrollout/tbl_out.get_selected_rows(out_row_index) - if len(in_row_index) and len(out_row_index): - var in_user_data = $scrollin/tbl_in.get_node_user_data(0, in_row_index[0]) - var out_user_data = $scrollout/tbl_out.get_node_user_data(0, out_row_index[0]) - var in_dev = in_user_data[0] - var out_dev = out_user_data[0] - add_connection_dev($scrl_out_set/tbl_out_set, in_dev, out_dev, OUT_SET_ROW, in_user_data) - add_connection_dev($scrl_in_set/tbl_in_set, out_dev, in_dev, IN_SET_ROW, out_user_data) + var in_row_index: Array = [] + var out_row_index: Array = [] + $scrollin/tbl_in.get_selected_rows(in_row_index) + $scrollout/tbl_out.get_selected_rows(out_row_index) + if len(in_row_index) and len(out_row_index): + var in_user_data = $scrollin/tbl_in.get_node_user_data(0, in_row_index[0]) + var out_user_data = $scrollout/tbl_out.get_node_user_data(0, out_row_index[0]) + var in_dev = in_user_data[0] + var out_dev = out_user_data[0] + add_connection_dev($scrl_out_set/tbl_out_set, in_dev, out_dev, OUT_SET_ROW, in_user_data) + add_connection_dev($scrl_in_set/tbl_in_set, out_dev, in_dev, IN_SET_ROW, out_user_data) ## Образовывает связь между выбранным входом и выходом.[br] @@ -336,14 +336,14 @@ func on_btn_connect(): ## [param set_row] - массив с пустыми LineEdit,[br] ## [param user_data] - пользовательские данные устройства1.[br] func add_connection_dev(tbl, dev1, dev2, set_row, user_data): - if not dev1.connector in dev2.connect_devs: - tbl.add_row(set_row) - var node_index: Array = [0, tbl.get_rows_count() -1] - var le_name: LineEdit = tbl.get_node2(node_index[0], node_index[1]) - le_name.text = dev1.connector - disable_edit(tbl, len(set_row), node_index[1]) - tbl.set_node_user_data(node_index[0], node_index[1], user_data) - dev2.connect_devs[dev1.connector] = user_data + if not dev1.connector in dev2.connect_devs: + tbl.add_row(set_row) + var node_index: Array = [0, tbl.get_rows_count() -1] + var le_name: LineEdit = tbl.get_node2(node_index[0], node_index[1]) + le_name.text = dev1.connector + disable_edit(tbl, len(set_row), node_index[1]) + tbl.set_node_user_data(node_index[0], node_index[1], user_data) + dev2.connect_devs[dev1.connector] = user_data ## Отключает возможность редактирования LineEdit в таблице.[br] @@ -351,14 +351,14 @@ func add_connection_dev(tbl, dev1, dev2, set_row, user_data): ## [param num_col] - номер колонки,[br] ## [param i_row] - номер столбца.[br] func disable_edit(tbl, num_col: int, i_row: int): - for col in range(num_col): - var node: LineEdit = tbl.get_node2(col, i_row) - node.editable = false + for col in range(num_col): + var node: LineEdit = tbl.get_node2(col, i_row) + node.editable = false ## Обработчик выбора строки с таблице. func on_row_pressed(i_row: int, tbl): - tbl.clear_rows_selected() - var is_row_selected = tbl.is_row_selected(i_row) - tbl.set_row_selected(i_row, not is_row_selected) + tbl.clear_rows_selected() + var is_row_selected = tbl.is_row_selected(i_row) + tbl.set_row_selected(i_row, not is_row_selected) diff --git a/scripts/capsrpb.gd b/scripts/capsrpb.gd index e4f4491d..9f7b96b5 100644 --- a/scripts/capsrpb.gd +++ b/scripts/capsrpb.gd @@ -9,42 +9,42 @@ const BUFFER_SIZE = 2048 ## Размер приёмного буфера д class CapsRpb: - signal line_changed(unit: CapsRpb) ## Сигнал состояние связи изменилось. - signal data_received(unit: CapsRpb) ## Сигнал данные приняты. - signal command_fail(unit: CapsRpb) ## Сигнал при не выполнении команды. - signal parse_error(unit: CapsRpb, msg: String) ## Сигнал ошибки разбора сообщения. + signal line_changed(unit: CapsRpb) ## Сигнал состояние связи изменилось. + signal data_received(unit: CapsRpb) ## Сигнал данные приняты. + signal command_fail(unit: CapsRpb) ## Сигнал при не выполнении команды. + signal parse_error(unit: CapsRpb, msg: String) ## Сигнал ошибки разбора сообщения. - static var json_conv: = JSON.new() - var online: = false ## Состояние связи с источником сообщений. - var name: String ## Имя экземпляра. - var json_dic: Dictionary ## Аппарат для разбора сообщений. - var tick: = 0 ## Тик время последнего обновления, мс. - var tx_data: = PackedByteArray() ## Пакет для отправки. - var tx_stack: Array ## Массив пакетов для отправки. + static var json_conv: = JSON.new() + var online: = false ## Состояние связи с источником сообщений. + var name: String ## Имя экземпляра. + var json_dic: Dictionary ## Аппарат для разбора сообщений. + var tick: = 0 ## Тик время последнего обновления, мс. + var tx_data: = PackedByteArray() ## Пакет для отправки. + var tx_stack: Array ## Массив пакетов для отправки. - func _to_string() -> String: return String('капсрпб: "%s" %s ' % [self.name, ['отключен', 'на связи'][int(online)]]) - func _init(nm): self.name = nm + func _to_string() -> String: return String('капсрпб: "%s" %s ' % [self.name, ['отключен', 'на связи'][int(online)]]) + func _init(nm): self.name = nm - func parse(data: PackedByteArray, now_tick: int): - tick = now_tick - if not online: - online = true - emit_signal('line_changed', self) - var utf8_data = data.get_string_from_utf8() - if Error.OK == json_conv.parse(utf8_data): - json_dic.merge(json_conv.get_data(), true) - emit_signal('data_received', self) - else: - var msg = json_conv.get_error_message() - emit_signal('parse_error', self, msg) + func parse(data: PackedByteArray, now_tick: int): + tick = now_tick + if not online: + online = true + emit_signal('line_changed', self) + var utf8_data = data.get_string_from_utf8() + if Error.OK == json_conv.parse(utf8_data): + json_dic.merge(json_conv.get_data(), true) + emit_signal('data_received', self) + else: + var msg = json_conv.get_error_message() + emit_signal('parse_error', self, msg) - func process(now_tick: int): - if online and ((now_tick - tick) > ONLINE_TIMEOUT): - online = false - emit_signal('line_changed', self) - if len(tx_stack): - tx_data = tx_stack[0] - tx_stack.remove_at(0) - return Error.OK - else: - return Error.ERR_UNAVAILABLE + func process(now_tick: int): + if online and ((now_tick - tick) > ONLINE_TIMEOUT): + online = false + emit_signal('line_changed', self) + if len(tx_stack): + tx_data = tx_stack[0] + tx_stack.remove_at(0) + return Error.OK + else: + return Error.ERR_UNAVAILABLE diff --git a/scripts/interfer.gd b/scripts/interfer.gd index 9de6555c..f04dd088 100644 --- a/scripts/interfer.gd +++ b/scripts/interfer.gd @@ -4,22 +4,22 @@ class_name Iterfer extends Node ## Режимы помехи. const ITERFER_MODE = { - 'off': ['Откл', ['freq', 'width'], 0], - 'auto': ['АВТО', ['freq', 'width'], 1000], - 'srni1': ['СРН-И1', ['freq', 'width'], 1], - 'srnm2': ['СРН-М2', ['freq', 'width'], 2], - 'srngps': ['СРН-GPS', ['freq', 'width'], 3], - 'lchm1': ['ЛЧМ-1', ['freq', 'width'], 10], - 'lchm2': ['ЛЧМ-2', ['freq', 'width'], 4], - 'mchm1': ['МЧМ-1', ['freq', 'width'], 11], - 'pdm2': ['ПД-М2', ['freq', 'width'], 12], - 'pdm4': ['ПД-М4', ['freq', 'width'], 13], - 'pdm4s': ['ПД-М4С', ['freq', 'width'], 14], - 'pdchm': ['ПД-ЧМ', ['freq', 'width'], 15], - 'svi1': ['СВ-И1', ['freq', 'width'], 20], - 'svm2': ['СВ-М2', ['freq', 'width'], 21], - 'k1': ['K1', ['freq', 'width', 'dur', 'period'], 50], - 'ozp1': ['ОЗП-1', ['freq', 'width'], 51] } + 'off': ['Откл', ['freq', 'width'], 0], + 'auto': ['АВТО', ['freq', 'width'], 1000], + 'srni1': ['СРН-И1', ['freq', 'width'], 1], + 'srnm2': ['СРН-М2', ['freq', 'width'], 2], + 'srngps': ['СРН-GPS', ['freq', 'width'], 3], + 'lchm1': ['ЛЧМ-1', ['freq', 'width'], 10], + 'lchm2': ['ЛЧМ-2', ['freq', 'width'], 4], + 'mchm1': ['МЧМ-1', ['freq', 'width'], 11], + 'pdm2': ['ПД-М2', ['freq', 'width'], 12], + 'pdm4': ['ПД-М4', ['freq', 'width'], 13], + 'pdm4s': ['ПД-М4С', ['freq', 'width'], 14], + 'pdchm': ['ПД-ЧМ', ['freq', 'width'], 15], + 'svi1': ['СВ-И1', ['freq', 'width'], 20], + 'svm2': ['СВ-М2', ['freq', 'width'], 21], + 'k1': ['K1', ['freq', 'width', 'dur', 'period'], 50], + 'ozp1': ['ОЗП-1', ['freq', 'width'], 51] } const FREQ_MAX = 6000 const FREQ_MIN = 400 @@ -38,52 +38,52 @@ var cu: Dictionary ## Словарь воздействий для 5 ## Сеанс помехи. class Interfer: - var ss: int = 0 ## Счётчик сообщений. - var ispp: int = 0 ## Индекс сеанса. - var svk: = 0 ## Состояние сеанса. Ответ КАПСРПБ. Возможные значения в [param ITERFER_STATE]. - var krp: String = '' ## Тип помехи. Возможные значения в [param INTERFER_MODE]. - var powp: float = 0.0 ## Мощность излучения выраженная в процентах. - var kni: = 0.0 ## Курсовой угол направления излучения. - var params: = {} ## Параметры помехи. - var get_params: Array[String] ## Запрос массива параметров (список запрошенных параметров). - var retss: int ## Значение счётчика сообщения в котором был произведён запрос массива параметров. - var tick_tx: int ## Тик время обновления цели, мс. - var source_interfer: String = '' ## Тип объекта по которой поставлена помеха. + var ss: int = 0 ## Счётчик сообщений. + var ispp: int = 0 ## Индекс сеанса. + var svk: = 0 ## Состояние сеанса. Ответ КАПСРПБ. Возможные значения в [param ITERFER_STATE]. + var krp: String = '' ## Тип помехи. Возможные значения в [param INTERFER_MODE]. + var powp: float = 0.0 ## Мощность излучения выраженная в процентах. + var kni: = 0.0 ## Курсовой угол направления излучения. + var params: = {} ## Параметры помехи. + var get_params: Array[String] ## Запрос массива параметров (список запрошенных параметров). + var retss: int ## Значение счётчика сообщения в котором был произведён запрос массива параметров. + var tick_tx: int ## Тик время обновления цели, мс. + var source_interfer: String = '' ## Тип объекта по которой поставлена помеха. - func _to_string() -> String: - return 'номер: %d название: %s частота: %0.1f МГц пеленг: %0.1f гр.' % [ispp, ITERFER_MODE[krp][0], params['freq'], kni] + func _to_string() -> String: + return 'номер: %d название: %s частота: %0.1f МГц пеленг: %0.1f гр.' % [ispp, ITERFER_MODE[krp][0], params['freq'], kni] - func get_msg6_data(): - var data_to_json : Dictionary = {} - data_to_json['ts'] = 6 - data_to_json['ss'] = ss - data_to_json['ispp'] = ispp - data_to_json['krp'] = krp - if krp != INTERFER_OFF: - data_to_json['kni'] = kni - data_to_json['powp'] = powp # Необходимо добавить управление мощностью - data_to_json['get'] = [] - data_to_json['params'] = params - var json = JSON.stringify(data_to_json) - var pac = json.to_utf8_buffer() - return pac + func get_msg6_data(): + var data_to_json : Dictionary = {} + data_to_json['ts'] = 6 + data_to_json['ss'] = ss + data_to_json['ispp'] = ispp + data_to_json['krp'] = krp + if krp != INTERFER_OFF: + data_to_json['kni'] = kni + data_to_json['powp'] = powp # Необходимо добавить управление мощностью + data_to_json['get'] = [] + data_to_json['params'] = params + var json = JSON.stringify(data_to_json) + var pac = json.to_utf8_buffer() + return pac - func set_from_dic(dic: Dictionary): - var names: = tools.get_members_names(self) - names.any(func(vn): if dic.has(vn): set(vn, dic[vn]); return false) + func set_from_dic(dic: Dictionary): + var names: = tools.get_members_names(self) + names.any(func(vn): if dic.has(vn): set(vn, dic[vn]); return false) - func get_value(value_key): - var param = self.get(value_key) - if param == null: - if params.has(value_key): param = params[value_key] - return param + func get_value(value_key): + var param = self.get(value_key) + if param == null: + if params.has(value_key): param = params[value_key] + return param - func set_value(value, value_key): - var param = self.get(value_key) - if param == null: - params[value_key] = value - else: - self.set(value_key, value) + func set_value(value, value_key): + var param = self.get(value_key) + if param == null: + params[value_key] = value + else: + self.set(value_key, value) signal interfer_preparing(ecm: Interfer) ## Вызывается в момент запуска помехи. @@ -94,172 +94,172 @@ signal interfer_resized(ecms: Dictionary) ## Вызывается пр func _ready(): - var ecms: Dictionary = {} ## Словарь помех. - for i in range(ID_INTERFERS_LEN): # Заполняет массив ID целей - id_array.append(i + 1) - var unit_key = repsettings.get_unit_key('уарэп-капсрпб-отп') - var unit = repnetwork.units[unit_key] - var unit_5p28_key = repsettings.get_unit_key('уарэп-5п28') - var unit_5p28 = repnetwork.units_tcp[unit_5p28_key] - var timer_check_state: = Timer.new() - add_child(timer_check_state) - var resend_timeout = ProjectSettings.get_setting('application/interfer/resend_timeout', 1000.0) - timer_check_state.connect('timeout', Callable(self, 'on_timer_check_state').bind(unit, ecms, resend_timeout)) - timer_check_state.start(resend_timeout / 1000.0) - unit.connect('data_received', Callable(self, 'on_data_capsrpb_received').bind(ecms)) - unit_5p28.connect('get_interfers', Callable(self, 'on_get_interfers').bind(unit_5p28, ecms)) - signaller.connect('interfer_create', Callable(self, 'on_interfer_create').bind(unit, ecms)) - signaller.connect('interfer_rcv', Callable(self, 'on_interfer_rcv').bind(unit_5p28, ecms)) - signaller.connect('interfer_new', Callable(self, 'send_ecm').bind(unit)) - signaller.connect('interfer_off_all', Callable(self, 'on_interfer_off_all').bind(unit, ecms)) + var ecms: Dictionary = {} ## Словарь помех. + for i in range(ID_INTERFERS_LEN): # Заполняет массив ID целей + id_array.append(i + 1) + var unit_key = repsettings.get_unit_key('уарэп-капсрпб-отп') + var unit = repnetwork.units[unit_key] + var unit_5p28_key = repsettings.get_unit_key('уарэп-5п28') + var unit_5p28 = repnetwork.units_tcp[unit_5p28_key] + var timer_check_state: = Timer.new() + add_child(timer_check_state) + var resend_timeout = ProjectSettings.get_setting('application/interfer/resend_timeout', 1000.0) + timer_check_state.connect('timeout', Callable(self, 'on_timer_check_state').bind(unit, ecms, resend_timeout)) + timer_check_state.start(resend_timeout / 1000.0) + unit.connect('data_received', Callable(self, 'on_data_capsrpb_received').bind(ecms)) + unit_5p28.connect('get_interfers', Callable(self, 'on_get_interfers').bind(unit_5p28, ecms)) + signaller.connect('interfer_create', Callable(self, 'on_interfer_create').bind(unit, ecms)) + signaller.connect('interfer_rcv', Callable(self, 'on_interfer_rcv').bind(unit_5p28, ecms)) + signaller.connect('interfer_new', Callable(self, 'send_ecm').bind(unit)) + signaller.connect('interfer_off_all', Callable(self, 'on_interfer_off_all').bind(unit, ecms)) ## Проверяет состояние отправки пакета и если не доставлен, повторяет отправку. func on_timer_check_state(unit, ecms, resend_timeout): - var tick = Time.get_ticks_msec() - for ecm in ecms.values(): - if ecm.svk == STATE_TSMT: - if (tick - ecm.tick_tx) >= resend_timeout: - send_ecm(ecm, unit) - ecm.tick_tx = tick + var tick = Time.get_ticks_msec() + for ecm in ecms.values(): + if ecm.svk == STATE_TSMT: + if (tick - ecm.tick_tx) >= resend_timeout: + send_ecm(ecm, unit) + ecm.tick_tx = tick ## Производит приём состояния помех. [param unit] -> [param ecms]. func on_data_capsrpb_received(unit: capsrpb.CapsRpb, ecms: Dictionary): - if not unit.json_dic.has('ts'): return - if not (unit.json_dic['ts'] is float): return - if unit.json_dic['ts'] != 5: return - if not unit.json_dic.has('msvk'): return - if not (unit.json_dic['msvk'] is Array): return - var msvk = unit.json_dic['msvk'] - var sz: = ecms.size() - for dic_msvk in msvk: - if not (dic_msvk is Dictionary): continue - if dic_msvk.has('ispp'): - var ispp = int(dic_msvk['ispp']) - if ecms.has(ispp): - var ecm = ecms[ispp] - ecm.set_from_dic(dic_msvk) - call_deferred('emit_signal', 'interfer_update', ecm) - else: - var ecm = Interfer.new() - ecm.set_from_dic(dic_msvk) - ecm.krp = INTERFER_OFF - send_ecm(ecm, unit) - if sz != ecms.size(): - call_deferred('emit_signal', 'interfer_resized', ecms) + if not unit.json_dic.has('ts'): return + if not (unit.json_dic['ts'] is float): return + if unit.json_dic['ts'] != 5: return + if not unit.json_dic.has('msvk'): return + if not (unit.json_dic['msvk'] is Array): return + var msvk = unit.json_dic['msvk'] + var sz: = ecms.size() + for dic_msvk in msvk: + if not (dic_msvk is Dictionary): continue + if dic_msvk.has('ispp'): + var ispp = int(dic_msvk['ispp']) + if ecms.has(ispp): + var ecm = ecms[ispp] + ecm.set_from_dic(dic_msvk) + call_deferred('emit_signal', 'interfer_update', ecm) + else: + var ecm = Interfer.new() + ecm.set_from_dic(dic_msvk) + ecm.krp = INTERFER_OFF + send_ecm(ecm, unit) + if sz != ecms.size(): + call_deferred('emit_signal', 'interfer_resized', ecms) ## Вызыватся, когда пользователь выбрал помеху и выбраны цели. func on_interfer_create(btn, sel_threats, fs_arr, _unit, ecms): - for item in sel_threats: - var param: Dictionary = {} - var kni: float = item.aoa - var id = item.id - var interfer_name: String = btn.get_meta('rfi_name') - param['freq'] = item.freq - param['width'] = item.width - interfer_create(interfer_name, ecms, param, kni, id, thrs, SOURCE_INTERFER[0]) + for item in sel_threats: + var param: Dictionary = {} + var kni: float = item.aoa + var id = item.id + var interfer_name: String = btn.get_meta('rfi_name') + param['freq'] = item.freq + param['width'] = item.width + interfer_create(interfer_name, ecms, param, kni, id, thrs, SOURCE_INTERFER[0]) - for item in fs_arr: - var param: Dictionary = {} - var kni: float = item[0] - var id = item[3] - var interfer_name: String = btn.get_meta('rfi_name') - param['freq'] = item[1] - param['width'] = item[2] - interfer_create(interfer_name, ecms, param, kni, id, fs, SOURCE_INTERFER[1]) + for item in fs_arr: + var param: Dictionary = {} + var kni: float = item[0] + var id = item[3] + var interfer_name: String = btn.get_meta('rfi_name') + param['freq'] = item[1] + param['width'] = item[2] + interfer_create(interfer_name, ecms, param, kni, id, fs, SOURCE_INTERFER[1]) func on_interfer_rcv(interfer_params: Array, unit, ecms): - var err: bool = false - var mode = interfer_params[0] - var id = interfer_params[1] - var aoa = interfer_params[2] - if (aoa < 0) and (aoa > 360): - err = true - var param: Dictionary = {} - param['freq'] = interfer_params[4] - if (param['freq'] < FREQ_MIN) and (param['freq'] > FREQ_MAX): - err = true - param['width'] = interfer_params[5] - if (param['width'] < WIDTH_MIN) and (param['width'] > WIDTH_MAX): - err = true - var interfer_name: String - for key in ITERFER_MODE: - if ITERFER_MODE[key][2] == mode: - interfer_name = key - break - if interfer_name == '': - err = true - if not err: - interfer_create(interfer_name, ecms, param, aoa, id, cu, SOURCE_INTERFER[2]) - unit.on_interfer_accept(err) + var err: bool = false + var mode = interfer_params[0] + var id = interfer_params[1] + var aoa = interfer_params[2] + if (aoa < 0) and (aoa > 360): + err = true + var param: Dictionary = {} + param['freq'] = interfer_params[4] + if (param['freq'] < FREQ_MIN) and (param['freq'] > FREQ_MAX): + err = true + param['width'] = interfer_params[5] + if (param['width'] < WIDTH_MIN) and (param['width'] > WIDTH_MAX): + err = true + var interfer_name: String + for key in ITERFER_MODE: + if ITERFER_MODE[key][2] == mode: + interfer_name = key + break + if interfer_name == '': + err = true + if not err: + interfer_create(interfer_name, ecms, param, aoa, id, cu, SOURCE_INTERFER[2]) + unit.on_interfer_accept(err) func interfer_create(interfer_name, ecms, param, kni, id, dict, source_interfer): - var ispp: int = 0 - if interfer_name == 'k1': - param['dur'] = 1.0 # Необходимо добавить параметры управления - param['period'] = 1.0 # Необходимо добавить параметры управления - var key = dict.find_key(id) - if source_interfer != SOURCE_INTERFER[2]: - if key != null: - ispp = key - else: - if interfer_name == 'off': - return - ispp = pop_id() - if ispp == 0: return - else: ispp = id - dict[ispp] = id - msg_ss += 1 - var ecm: Interfer - if ecms.has(ispp): - ecm = ecms[ispp] - else: - ecm = Interfer.new() - ecm.ispp = ispp - ecms[ispp] = ecm - ecm.svk = STATE_TSMT # Подготовка к передаче сообщения - ecm.ss = msg_ss - ecm.kni = kni - ecm.krp = interfer_name - ecm.params = param - ecm.powp = 100.0 # Необходимо добавить управление мощностью - ecm.source_interfer = source_interfer - signaller.emit_signal('interfer_new', ecm) - if interfer_name == 'off': - signaller.emit_signal('interfer_off', ecm, ecms, thrs) - push_id(ecm.ispp) - thrs.erase(ispp) - ecm.tick_tx = Time.get_ticks_msec() + var ispp: int = 0 + if interfer_name == 'k1': + param['dur'] = 1.0 # Необходимо добавить параметры управления + param['period'] = 1.0 # Необходимо добавить параметры управления + var key = dict.find_key(id) + if source_interfer != SOURCE_INTERFER[2]: + if key != null: + ispp = key + else: + if interfer_name == 'off': + return + ispp = pop_id() + if ispp == 0: return + else: ispp = id + dict[ispp] = id + msg_ss += 1 + var ecm: Interfer + if ecms.has(ispp): + ecm = ecms[ispp] + else: + ecm = Interfer.new() + ecm.ispp = ispp + ecms[ispp] = ecm + ecm.svk = STATE_TSMT # Подготовка к передаче сообщения + ecm.ss = msg_ss + ecm.kni = kni + ecm.krp = interfer_name + ecm.params = param + ecm.powp = 100.0 # Необходимо добавить управление мощностью + ecm.source_interfer = source_interfer + signaller.emit_signal('interfer_new', ecm) + if interfer_name == 'off': + signaller.emit_signal('interfer_off', ecm, ecms, thrs) + push_id(ecm.ispp) + thrs.erase(ispp) + ecm.tick_tx = Time.get_ticks_msec() func send_ecm(ecm, unit): - var json_data = ecm.get_msg6_data() - unit.tx_stack.append(json_data) + var json_data = ecm.get_msg6_data() + unit.tx_stack.append(json_data) func on_interfer_off_all(unit, ecms): - for ecm in ecms.values(): - ecm.krp = INTERFER_OFF - push_id(ecm.ispp) - send_ecm(ecm, unit) - signaller.emit_signal('interfer_off',ecm, ecms, thrs) + for ecm in ecms.values(): + ecm.krp = INTERFER_OFF + push_id(ecm.ispp) + send_ecm(ecm, unit) + signaller.emit_signal('interfer_off',ecm, ecms, thrs) func pop_id(): - var id = 0 - if len(id_array): - id = id_array[0] - id_array.remove_at(0) - return id + var id = 0 + if len(id_array): + id = id_array[0] + id_array.remove_at(0) + return id func push_id(id : int): - id_array.append(id) - id_array.sort() + id_array.append(id) + id_array.sort() ## Отправляет словарь помех в юнит, для запаковки и отправки в 5П28. diff --git a/scripts/mercator.gd b/scripts/mercator.gd index 9a496d67..55114866 100644 --- a/scripts/mercator.gd +++ b/scripts/mercator.gd @@ -17,92 +17,92 @@ const MAX_SOUTH = -85.051129 ## XYZ-плитка веб-меркатора [param x], [param y], [param z] индексы. ## [b]x[/b] и [b]y[/b] плитки и уровень масштабирования [b]z[/b]. class Tile: - var x: int - var y: int - var z: int - var lng: float ## Координаты, которые плитка должна покрывать, долгота, градусы. - var lat: float ## Широта, градусы. - var is_root: bool ## Признак, что плитка корневая (покрывает координаты). + var x: int + var y: int + var z: int + var lng: float ## Координаты, которые плитка должна покрывать, долгота, градусы. + var lat: float ## Широта, градусы. + var is_root: bool ## Признак, что плитка корневая (покрывает координаты). - func as_array(): return [self.x, self.y, self.z] + func as_array(): return [self.x, self.y, self.z] - func _to_string() -> String: return '[%d, %d, %d]' % [self.x, self.y, self.z] + func _to_string() -> String: return '[%d, %d, %d]' % [self.x, self.y, self.z] - func _init(x0: int = 0, y0: int = 0, z0: int = 0, is_root0: bool = true): - x = x0 - y = y0 - z = z0 - is_root = is_root0 + func _init(x0: int = 0, y0: int = 0, z0: int = 0, is_root0: bool = true): + x = x0 + y = y0 + z = z0 + is_root = is_root0 - func is_valid() -> bool: - if (z >= 0) and (z < MAX_ZOOM): - var mz: int = 2 ** z - return (x >= 0) and (x < mz) \ - and (y >= 0) and (y < mz) - return false + func is_valid() -> bool: + if (z >= 0) and (z < MAX_ZOOM): + var mz: int = 2 ** z + return (x >= 0) and (x < mz) \ + and (y >= 0) and (y < mz) + return false - func _bound_index(index: int) -> int: - var mi: int = 2 ** z - if index < 0: - index *= -1 - index %= mi - index = mi - index - else: - index %= mi - return index + func _bound_index(index: int) -> int: + var mi: int = 2 ** z + if index < 0: + index *= -1 + index %= mi + index = mi - index + else: + index %= mi + return index - func fix_xy(): - x = _bound_index(x) - y = _bound_index(y) + func fix_xy(): + x = _bound_index(x) + y = _bound_index(y) ## Пара долготы и широты. class LngLat: - var lng: float = 0.0 ## Долгота в десятичных градусах восточной долготы. - var lat: float = 0.0 ## Широта в десятичных градусах северной широты. + var lng: float = 0.0 ## Долгота в десятичных градусах восточной долготы. + var lat: float = 0.0 ## Широта в десятичных градусах северной широты. - ## Возвращает в виде массива. - func as_array() -> Array: return [self.lng, self.lat] + ## Возвращает в виде массива. + func as_array() -> Array: return [self.lng, self.lat] - func _to_string() -> String: return '[%f, %f, %f]' % [self.x, self.y, self.z] + func _to_string() -> String: return '[%f, %f, %f]' % [self.x, self.y, self.z] - func _init(lng0, lat0): - self.lng = lng0 - self.lat = lat0 + func _init(lng0, lat0): + self.lng = lng0 + self.lat = lat0 ## Географические границы. Хранит значения границ в десятичных градусах. class LngLatBbox: - var west: float ## Запад - var south: float ## Юг - var east: float ## Восток - var north: float ## Север + var west: float ## Запад + var south: float ## Юг + var east: float ## Восток + var north: float ## Север - ## Возвращает в виде массива. - func as_array() -> Array: return [self.west, self.south, self.east, self.north] + ## Возвращает в виде массива. + func as_array() -> Array: return [self.west, self.south, self.east, self.north] - func _to_string() -> String: return '[%f, %f, %f, %f]' % [self.west, self.south, self.east, self.north] + func _to_string() -> String: return '[%f, %f, %f, %f]' % [self.west, self.south, self.east, self.north] - func _init(w: float = 0.0, s: float = 0.0, e: float = 0.0, n: float = 0.0): - self.west = w - self.south = s - self.east = e - self.north = n + func _init(w: float = 0.0, s: float = 0.0, e: float = 0.0, n: float = 0.0): + self.west = w + self.south = s + self.east = e + self.north = n ## Граничная область веб-меркатора. Хранит значения границ в метрах. class Bbox: - var left: float - var bottom: float - var right: float - var top: float - func as_array() -> Array: return [self.left, self.bottom, self.right, self.top] - func _init(l: float = 0.0, b: float = 0.0, r: float = 0.0, t: float = 0.0): - self.left = l - self.bottom = b - self.right = r - self.top = t + var left: float + var bottom: float + var right: float + var top: float + func as_array() -> Array: return [self.left, self.bottom, self.right, self.top] + func _init(l: float = 0.0, b: float = 0.0, r: float = 0.0, t: float = 0.0): + self.left = l + self.bottom = b + self.right = r + self.top = t func degrees(v: float) -> float: return (v * 180.0) / PI @@ -121,47 +121,47 @@ func asinh(val: float): return log(val + sqrt(val * val + 1.0)) ## Преобразует [param lon] в градусах восточной долготы, [param lat] в градусах. ## северной долготы, [param alt] высоты в метрах в фиксированные координаты (x, y, z) Земли. func lnglat_to_ecef(lng: float, lat: float, alt: float = 0.0) -> Array: - alt += (RE + RP) / 2.0 - var latrad: float = radians(lat) - var lonrad: float = radians(lng) - var coslat: float = cos(latrad) - var sinlat: float = sin(latrad) - var coslon: float = cos(lonrad) - var sinlon: float = sin(lonrad) - var term1: float = (RE * coslat) / tools.hypot(RE * coslat, RP * sinlat) - var term2: float = alt * coslat + term1; - var x: float = coslon * term2 - var y: float = sinlon * term2 - var z: float = alt * sinlat + (RP * sinlat) / tools.hypot(RE * coslat, RP * sinlat) - return [x, y, z] + alt += (RE + RP) / 2.0 + var latrad: float = radians(lat) + var lonrad: float = radians(lng) + var coslat: float = cos(latrad) + var sinlat: float = sin(latrad) + var coslon: float = cos(lonrad) + var sinlon: float = sin(lonrad) + var term1: float = (RE * coslat) / tools.hypot(RE * coslat, RP * sinlat) + var term2: float = alt * coslat + term1; + var x: float = coslon * term2 + var y: float = sinlon * term2 + var z: float = alt * sinlat + (RP * sinlat) / tools.hypot(RE * coslat, RP * sinlat) + return [x, y, z] func find_up_left(tiles: Array): - var x = 0 - var y = 0 - for tile in tiles: - if x < tile.x: x = tile.x - if y < tile.y: y = tile.y - for tile in tiles: - if tile.x == x and tile.y == y: - return tile - return null + var x = 0 + var y = 0 + for tile in tiles: + if x < tile.x: x = tile.x + if y < tile.y: y = tile.y + for tile in tiles: + if tile.x == x and tile.y == y: + return tile + return null ## [param lon]/[param lat]. для номера плитки. func deg2num(lng: float, lat: float, zoom: int): - var n: = float(1 << zoom) - var xtile: = int((lng + 180.0) / 360.0 * n) - var ytile: = int((1.0 - asinh(tan(radians(lat))) / PI) / 2.0 * n) - return [xtile, ytile] + var n: = float(1 << zoom) + var xtile: = int((lng + 180.0) / 360.0 * n) + var ytile: = int((1.0 - asinh(tan(radians(lat))) / PI) / 2.0 * n) + return [xtile, ytile] ## Номера плитки для [param lon]/[param lat]. func num2deg(x: int, y: int, zoom: int): - var n: = float(1 << zoom) - var lng: = float(x) / n * 360.0 - 180.0 - var lat: = atan(sinh(PI * (1.0 - 2.0 * float(y) / n))) - return [lng, degrees(lat)] + var n: = float(1 << zoom) + var lng: = float(x) / n * 360.0 - 180.0 + var lat: = atan(sinh(PI * (1.0 - 2.0 * float(y) / n))) + return [lng, degrees(lat)] ## Расстояние по горизонтали в метрах, представленное одним пикселем. @@ -169,14 +169,14 @@ func num2deg(x: int, y: int, zoom: int): ## [param zoom] - Уровень детализации.[br] ## [param size] - Размер плитки в пикселях.[br] func dist_per_pixel(lat: float, zoom: int, size: int = 256) -> float: - return (CE / float(size)) * cos(radians(lat)) / float(2 ** zoom) + return (CE / float(size)) * cos(radians(lat)) / float(2 ** zoom) ## Расстояние по горизонтали в метрах, представленное одной плиткой. ## [param lat] - Широта, градусы.[br] ## [param zoom] - Уровень детализации.[br] func dist_per_tile(lat: float, zoom: int) -> float: - return CE * cos(radians(lat)) / float(2 ** zoom) + return CE * cos(radians(lat)) / float(2 ** zoom) ## Возвращает плитки и занимаемый ими размер области в пикселях: @@ -188,21 +188,21 @@ func dist_per_tile(lat: float, zoom: int) -> float: ## [param size] - Размер плитки в пикселях.[br] ## [param zoom] - Детализация.[br] func get_tiles(west: float, south: float, east: float, north: float, zoom: int, size: int) -> Array: - var tiles: = create_tiles(west, south, east, north, zoom) + var tiles: = create_tiles(west, south, east, north, zoom) - var mm = [] - for t in tiles: mm.append(t.x) - var min_x = mm.min() - var max_x = mm.max() + var mm = [] + for t in tiles: mm.append(t.x) + var min_x = mm.min() + var max_x = mm.max() - mm.clear() - for t in tiles: mm.append(t.y) - var min_y = mm.min() - var max_y = mm.max() + mm.clear() + for t in tiles: mm.append(t.y) + var min_y = mm.min() + var max_y = mm.max() - var w = size * (max_x - min_x + 1) - var h = size * (max_y - min_y + 1) - return [w, h, min_x, min_y, tiles, max_x - min_x + 1] + var w = size * (max_x - min_x + 1) + var h = size * (max_y - min_y + 1) + return [w, h, min_x, min_y, tiles, max_x - min_x + 1] ## Возвращает размер области в метрах.[br] @@ -211,9 +211,9 @@ func get_tiles(west: float, south: float, east: float, north: float, zoom: int, ## [param south] - Южная граница области.[br] ## [param north] - Северная граница области.[br] func get_size_from_degrees(west: float, south: float, east: float, north: float) -> Array[float]: - var xy0: = xy(west, north, true) - var xy1: = xy(east, south, true) - return [xy0[0] - xy1[0], xy0[1] - xy1[1]] + var xy0: = xy(west, north, true) + var xy1: = xy(east, south, true) + return [xy0[0] - xy1[0], xy0[1] - xy1[1]] ## Возвращает границы области в градусах.[br] @@ -221,27 +221,27 @@ func get_size_from_degrees(west: float, south: float, east: float, north: float) ## [param lng] - Долгота центра области в градусах (-180...180).[br] ## [param lat] - Широта центра области в градусах (-90...90).[br] func get_degrees_bounds(m: float, lng: float, lat: float) -> Array[float]: - m /= 2.0 - var x_y = xy(lng, lat, true) - return [x_y[0] - m, x_y[1] - m, x_y[0] + m, x_y[1] + m] + m /= 2.0 + var x_y = xy(lng, lat, true) + return [x_y[0] - m, x_y[1] - m, x_y[0] + m, x_y[1] + m] ## Возвращает границы плиток в градусах. func get_tiles_bounds(tiles: Array[Tile]) -> Array: - var mm = [] - for t in tiles: mm.append(bounds(t).west) - var mmw = mm.min(); mm.clear() + var mm = [] + for t in tiles: mm.append(bounds(t).west) + var mmw = mm.min(); mm.clear() - for t in tiles: mm.append(bounds(t).south) - var mms = mm.min(); mm.clear() + for t in tiles: mm.append(bounds(t).south) + var mms = mm.min(); mm.clear() - for t in tiles: mm.append(bounds(t).east) - var mme = mm.max(); mm.clear() + for t in tiles: mm.append(bounds(t).east) + var mme = mm.max(); mm.clear() - for t in tiles: mm.append(bounds(t).north) - var mmn = mm.max(); mm.clear() + for t in tiles: mm.append(bounds(t).north) + var mmn = mm.max(); mm.clear() - return [mmw, mms, mme, mmn] + return [mmw, mms, mme, mmn] ## Возвращает левую верхнюю долготу и широту плитки.[br] @@ -253,11 +253,11 @@ func get_tiles_bounds(tiles: Array[Tile]) -> Array: ## LngLat(lng=0.0, lat=0.0) ## [/codeblock] func ul(tile: Tile, zoom: int) -> LngLat: - var Z2 = pow(2, zoom) - var lon_deg = tile.x / Z2 * 360.0 - 180.0 - var lat_rad = atan(sinh(PI * (1 - 2 * tile.y / Z2))) - var lat_deg = degrees(lat_rad) - return LngLat.new(lon_deg, lat_deg) + var Z2 = pow(2, zoom) + var lon_deg = tile.x / Z2 * 360.0 - 180.0 + var lat_rad = atan(sinh(PI * (1 - 2 * tile.y / Z2))) + var lat_deg = degrees(lat_rad) + return LngLat.new(lon_deg, lat_deg) ## Возвращает левую верхнюю долготу и широту плитки.[br] @@ -269,32 +269,32 @@ func ul(tile: Tile, zoom: int) -> LngLat: ## LngLat(lng=0.0, lat=0.0) ## [/codeblock] func ul2(tile: Tile) -> Array[float]: - var z2 = pow(2, tile.z) - var lon_deg = tile.x / z2 * 360.0 - 180.0 - var lat_rad = atan(sinh(PI * (1 - 2 * tile.y / z2))) - var lat_deg = degrees(lat_rad) - return [lon_deg, lat_deg] + var z2 = pow(2, tile.z) + var lon_deg = tile.x / z2 * 360.0 - 180.0 + var lat_rad = atan(sinh(PI * (1 - 2 * tile.y / z2))) + var lat_deg = degrees(lat_rad) + return [lon_deg, lat_deg] ## Возвращает граничную область плитки.[br] ## [param tile] - Плитка.[br] func bounds(tile: Tile) -> LngLatBbox: - var z2: = pow(2, tile.z) - var ul_lon_deg = float(tile.x) / z2 * 360.0 - 180.0 - var lr_lon_deg = (float(tile.x) + 1.0) / z2 * 360.0 - 180.0 - var ul_lat_rad = atan(sinh(PI * (1.0 - 2.0 * float(tile.y) / z2))) - var ul_lat_deg = degrees(ul_lat_rad) - var lr_lat_rad = atan(sinh(PI * (1.0 - 2.0 * (float(tile.y) + 1.0) / z2))) - var lr_lat_deg = degrees(lr_lat_rad) - return LngLatBbox.new(ul_lon_deg, lr_lat_deg, lr_lon_deg, ul_lat_deg) + var z2: = pow(2, tile.z) + var ul_lon_deg = float(tile.x) / z2 * 360.0 - 180.0 + var lr_lon_deg = (float(tile.x) + 1.0) / z2 * 360.0 - 180.0 + var ul_lat_rad = atan(sinh(PI * (1.0 - 2.0 * float(tile.y) / z2))) + var ul_lat_deg = degrees(ul_lat_rad) + var lr_lat_rad = atan(sinh(PI * (1.0 - 2.0 * (float(tile.y) + 1.0) / z2))) + var lr_lat_deg = degrees(lr_lat_rad) + return LngLatBbox.new(ul_lon_deg, lr_lat_deg, lr_lon_deg, ul_lat_deg) func truncate_lnglat(lng: float, lat: float) -> Array: - if lng > 180.0: lng = 180.0 - elif lng < -180.0: lng = -180.0 - if lat > 90.0: lat = 90.0 - elif lat < -90.0: lat = -90.0 - return [lng, lat] + if lng > 180.0: lng = 180.0 + elif lng < -180.0: lng = -180.0 + if lat > 90.0: lat = 90.0 + elif lat < -90.0: lat = -90.0 + return [lng, lat] ## Преобразование долготы и широты в веб-меркатор x, y в метрах.[br] @@ -303,23 +303,23 @@ func truncate_lnglat(lng: float, lat: float) -> Array: ## [param truncate] - Нужно ли усекать входные данные до пределов веб-меркатора.[br] ## ([param lat] >= 90) [b]inf[/b] на северном и [b]-inf[/b] на южном полюсе ([param lat] <= -90). func xy(lng: float, lat: float, truncate: bool = false) -> Array[float]: - var ll = truncate_lnglat(lng, lat) if truncate else [lng, lat] - var x: float = RE * radians(lng) - var y: float = 0.0 - if ll[1] <= -90.0: y = float('-inf') - elif ll[1] >= 90.0: y = float('inf') - else: y = RE * log(tan((PI * 0.25) + (0.5 * radians(lat)))) - return [x, y] + var ll = truncate_lnglat(lng, lat) if truncate else [lng, lat] + var x: float = RE * radians(lng) + var y: float = 0.0 + if ll[1] <= -90.0: y = float('-inf') + elif ll[1] >= 90.0: y = float('inf') + else: y = RE * log(tan((PI * 0.25) + (0.5 * radians(lat)))) + return [x, y] ## Преобразование координат веб-меркатора x, y в долготу и широту.[br] ## [param x], [param y] - Координаты веб-меркатора в метрах.[br] ## [param truncate] - Нужно ли усекать или обрезать входные данные до границ веб-меркатора. func lnglat(x: float, y: float, truncate: bool = false) -> LngLat: - var lng: = x * R2D / RE - var lat: = ((PI * 0.5) - 2.0 * atan(exp(-y / RE))) * R2D - var ll: = truncate_lnglat(lng, lat) if truncate else [lng, lat] - return LngLat.new(ll[0], ll[1]) + var lng: = x * R2D / RE + var lat: = ((PI * 0.5) - 2.0 * atan(exp(-y / RE))) * R2D + var ll: = truncate_lnglat(lng, lat) if truncate else [lng, lat] + return LngLat.new(ll[0], ll[1]) ## Соседи плитки. [method neighbors] возвращает до восьми.[br] @@ -332,65 +332,65 @@ func lnglat(x: float, y: float, truncate: bool = false) -> LngLat: ## [Tile(x=485, y=331, z=10), Tile(x=485, y=332, z=10), Tile(x=485, y=333, z=10), Tile(x=486, y=331, z=10), Tile(x=486, y=333, z=10), Tile(x=487, y=331, z=10), Tile(x=487, y=332, z=10), Tile(x=487, y=333, z=10)] ## [/codeblock] func neighbors(tile: Tile) -> Array: - var tiles: = [] - for i in [-1, 0, 1]: - for j in [-1, 0, 1]: - if i == 0 and j == 0: - continue - var t = Tile.new(tile.x + i, tile.y + j, tile.z) - t.fix_xy() - tiles.append(t) - return tiles + var tiles: = [] + for i in [-1, 0, 1]: + for j in [-1, 0, 1]: + if i == 0 and j == 0: + continue + var t = Tile.new(tile.x + i, tile.y + j, tile.z) + t.fix_xy() + tiles.append(t) + return tiles ## Возвращает все плитки в радиусе [param r] вокруг заданной плитки [param tile]. func get_allaround_tiles(tile: Tile, mr: int = 1) -> Array: - var xys = {} - var mi: int = 2 ** tile.z - for r in mr: - for a in range(0, 360, 180 / mi): - var ra = radians(a) - var ix = roundi(r * cos(ra)) + tile.x - var iy = roundi(r * sin(ra)) + tile.y - var t = Tile.new(ix, iy, tile.z) - t.fix_xy() - xys[[t.x, t.y]] = false - var tiles = [] - for txy in xys.keys(): - tiles.append(Tile.new(txy[0], txy[1], tile.z)) - return tiles + var xys = {} + var mi: int = 2 ** tile.z + for r in mr: + for a in range(0, 360, 180 / mi): + var ra = radians(a) + var ix = roundi(r * cos(ra)) + tile.x + var iy = roundi(r * sin(ra)) + tile.y + var t = Tile.new(ix, iy, tile.z) + t.fix_xy() + xys[[t.x, t.y]] = false + var tiles = [] + for txy in xys.keys(): + tiles.append(Tile.new(txy[0], txy[1], tile.z)) + return tiles ## Возвращает все плитки по периметру заданной плитки. func get_perimetr_tiles(tile: Tile, mr: int = 1) -> Array: - var tiles = [] - for i in range(-mr, mr + 1): - for j in range(-mr, mr + 1): - if i == 0 and j == 0: continue - var t = Tile.new(i + tile.x, j + tile.y, tile.z) - t.fix_xy() - tiles.append(t) - return tiles + var tiles = [] + for i in range(-mr, mr + 1): + for j in range(-mr, mr + 1): + if i == 0 and j == 0: continue + var t = Tile.new(i + tile.x, j + tile.y, tile.z) + t.fix_xy() + tiles.append(t) + return tiles ## Получение меркаторской граничной рамки веб-плитки.[br] ## [param tile] - Плитка.[br] ## [i]Эпсилон вычитается из правой границы и прибавляется к нижней.[/i] func xy_bounds(tile: Tile, zoom: int) -> Bbox: - var tile_size = CE / pow(2, zoom) - var left = tile.x * tile_size - CE / 2 - var right = left + tile_size - var top = CE / 2 - tile.y * tile_size - var bottom = top - tile_size - return Bbox.new(left, bottom, right, top) + var tile_size = CE / pow(2, zoom) + var left = tile.x * tile_size - CE / 2 + var right = left + tile_size + var top = CE / 2 - tile.y * tile_size + var bottom = top - tile_size + return Bbox.new(left, bottom, right, top) func _xy(lng: float, lat: float, truncate=false) -> Array: - var ll: = truncate_lnglat(lng, lat) if truncate else [lng, lat] - var x: float = ll[0] / 360.0 + 0.5 - var sinlat: = sin(radians(ll[1])) - var y: = 0.5 - 0.25 * log((1.0 + sinlat) / (1.0 - sinlat)) / PI - return [x, y] + var ll: = truncate_lnglat(lng, lat) if truncate else [lng, lat] + var x: float = ll[0] / 360.0 + 0.5 + var sinlat: = sin(radians(ll[1])) + var y: = 0.5 - 0.25 * log((1.0 + sinlat) / (1.0 - sinlat)) / PI + return [x, y] ## Получение плитки, содержащей долготу и широту. @@ -398,64 +398,64 @@ func _xy(lng: float, lat: float, truncate=false) -> Array: ## [param zoom] - Уровень масштабирования веб-меркатора.[br] ## [param truncate] - Нужно ли усекать вводимые данные до границ веб-меркатора.[br] func create_tile(lng: float, lat: float, zoom: int, truncate: bool=false) -> Tile: - var xy0: = _xy(lng, lat, truncate) - var z2 = pow(2, zoom) + var xy0: = _xy(lng, lat, truncate) + var z2 = pow(2, zoom) - # Чтобы устранить потерю точности при переходе от одной плитки к другой - # и lng/lat, точки в пределах EPSILON от правой стороны плитки - # считаются в следующей за ней плитке. - var tile: = Tile.new() - if xy0[0] <= 0: tile.x = 0 - elif xy0[0] >= 1: tile.x = int(z2 - 1) - else: tile.x = int(floor((xy0[0] + EPSILON) * z2)) + # Чтобы устранить потерю точности при переходе от одной плитки к другой + # и lng/lat, точки в пределах EPSILON от правой стороны плитки + # считаются в следующей за ней плитке. + var tile: = Tile.new() + if xy0[0] <= 0: tile.x = 0 + elif xy0[0] >= 1: tile.x = int(z2 - 1) + else: tile.x = int(floor((xy0[0] + EPSILON) * z2)) - if xy0[1] <= 0: tile.y = 0 - elif xy0[1] >= 1: tile.y = int(z2 - 1) - else: tile.y = int(floor((xy0[1] + EPSILON) * z2)) - tile.z = zoom - tile.lng = lng - tile.lat = lat - return tile + if xy0[1] <= 0: tile.y = 0 + elif xy0[1] >= 1: tile.y = int(z2 - 1) + else: tile.y = int(floor((xy0[1] + EPSILON) * z2)) + tile.z = zoom + tile.lng = lng + tile.lat = lat + return tile ## Получение четверного ключа плитки.[br] ## [param tile] - Плитка. func quadkey(tile: Tile, zoom: int) -> String: - var rs: = '' - for z in range(zoom, 0, -1): - var digit = 0 - var mask = 1 << (z - 1) - if tile.x & mask: - digit += 1 - if tile.y & mask: - digit += 2 - rs += str(digit) - return rs + var rs: = '' + for z in range(zoom, 0, -1): + var digit = 0 + var mask = 1 << (z - 1) + if tile.x & mask: + digit += 1 + if tile.y & mask: + digit += 2 + rs += str(digit) + return rs ## Получение плитки, соответствующей четверному ключу.[br] ## [param qk] - Строка четверного ключа. func quadkey_to_tile(qk: Array) -> Tile: - var tile: = Tile.new() - if len(qk) == 0: return tile - var i = 0 - qk.reverse() - for digit in qk: - var mask = 1 << i - i += 1 - if digit == '1': - tile.x |= mask - elif digit == '2': - tile.y |= mask - elif digit == '3': - tile.x |= mask - tile.y |= mask - elif digit == '0': - continue - else: - push_error('Не ожидаемый четырёхзначный знак: \"%s\"' % digit) - tile.z = i - return tile + var tile: = Tile.new() + if len(qk) == 0: return tile + var i = 0 + qk.reverse() + for digit in qk: + var mask = 1 << i + i += 1 + if digit == '1': + tile.x |= mask + elif digit == '2': + tile.y |= mask + elif digit == '3': + tile.x |= mask + tile.y |= mask + elif digit == '0': + continue + else: + push_error('Не ожидаемый четырёхзначный знак: \"%s\"' % digit) + tile.z = i + return tile ## Получение плиток, перекрываемых географической рамкой.[br] @@ -465,27 +465,27 @@ func quadkey_to_tile(qk: Array) -> Tile: ## [i]В параметрах south и east используется небольшой эпсилон, чтобы ## функция выдавала ровно одну плитку при задании границ этой же плитки.[/i] func create_tiles(west: float, south: float, east: float, north: float, zooms) -> Array[Tile]: - var bboxes = [[west, south, east, north]] if west <= east else [[-180.0, south, east, north], [west, south, 180.0, north]] - var tiles = [] - if not is_instance_of(zooms, TYPE_ARRAY): - zooms = [zooms] - for box in bboxes: - var w = box[0] - var s = box[1] - var e = box[2] - var n = box[3] - # Зажим граничных значений. - w = max(-180.0, w) - s = max(MAX_SOUTH, s) - e = min(180.0, e) - n = min(85.051129, n) - for z in zooms: - var ul_tile = create_tile(w, n, z) - var lr_tile = create_tile(e - LL_EPSILON, s + LL_EPSILON, z) - for i in range(ul_tile.x, lr_tile.x + 1): - for j in range(ul_tile.y, lr_tile.y + 1): - tiles.append(Tile.new(i, j, z)) - return tiles + var bboxes = [[west, south, east, north]] if west <= east else [[-180.0, south, east, north], [west, south, 180.0, north]] + var tiles = [] + if not is_instance_of(zooms, TYPE_ARRAY): + zooms = [zooms] + for box in bboxes: + var w = box[0] + var s = box[1] + var e = box[2] + var n = box[3] + # Зажим граничных значений. + w = max(-180.0, w) + s = max(MAX_SOUTH, s) + e = min(180.0, e) + n = min(85.051129, n) + for z in zooms: + var ul_tile = create_tile(w, n, z) + var lr_tile = create_tile(e - LL_EPSILON, s + LL_EPSILON, z) + for i in range(ul_tile.x, lr_tile.x + 1): + for j in range(ul_tile.y, lr_tile.y + 1): + tiles.append(Tile.new(i, j, z)) + return tiles ## Получение родителя плитки. Родительская плитка - это плитка на один уровень масштабирования ниже, которая @@ -500,28 +500,28 @@ func create_tiles(west: float, south: float, east: float, north: float, zooms) - ## Tile(x=0, y=0, z=0) ## [/codeblock] func parent(tile: Tile, zoom=null) -> Tile: - if tile.z == 0: return null + if tile.z == 0: return null - assert((zoom != null) and (tile.z > zoom or zoom == int(zoom)), 'Масштаб (zoom) должен быть целым числом и меньше масштаба входной плитки (tile).') - assert(tile.x == int(tile.x) and tile.y == int(tile.y) and tile.z == int(tile.z), 'Родитель нецелочисленной плитки (tile) является неуточнённым') + assert((zoom != null) and (tile.z > zoom or zoom == int(zoom)), 'Масштаб (zoom) должен быть целым числом и меньше масштаба входной плитки (tile).') + assert(tile.x == int(tile.x) and tile.y == int(tile.y) and tile.z == int(tile.z), 'Родитель нецелочисленной плитки (tile) является неуточнённым') - var target_zoom = tile.z - 1 if zoom == null else zoom + var target_zoom = tile.z - 1 if zoom == null else zoom - # Алгоритм, в значительной степени вдохновленный https://github.com/mapbox/tilebelt - var return_tile = Tile.new(tile.x, tile.y, tile.z) - while return_tile.z > target_zoom: - tile.x = return_tile.x - tile.y = return_tile.y - tile.z = return_tile.z - if tile.x % 2 == 0 and tile.y % 2 == 0: - return_tile = Tile.new(tile.x / 2, tile.y / 2, tile.z - 1) - elif tile.x % 2 == 0: - return_tile = Tile.new(tile.x / 2, (tile.y - 1) / 2, tile.z - 1) - elif not tile.x % 2 == 0 and tile.y % 2 == 0: - return_tile = Tile.new((tile.x - 1) / 2, tile.y / 2, tile.z - 1) - else: - return_tile = Tile.new((tile.x - 1) / 2, (tile.y - 1) / 2, tile.z - 1) - return return_tile + # Алгоритм, в значительной степени вдохновленный https://github.com/mapbox/tilebelt + var return_tile = Tile.new(tile.x, tile.y, tile.z) + while return_tile.z > target_zoom: + tile.x = return_tile.x + tile.y = return_tile.y + tile.z = return_tile.z + if tile.x % 2 == 0 and tile.y % 2 == 0: + return_tile = Tile.new(tile.x / 2, tile.y / 2, tile.z - 1) + elif tile.x % 2 == 0: + return_tile = Tile.new(tile.x / 2, (tile.y - 1) / 2, tile.z - 1) + elif not tile.x % 2 == 0 and tile.y % 2 == 0: + return_tile = Tile.new((tile.x - 1) / 2, tile.y / 2, tile.z - 1) + else: + return_tile = Tile.new((tile.x - 1) / 2, (tile.y - 1) / 2, tile.z - 1) + return return_tile ## Получение дочерних элементов плитки. Дочерние элементы упорядочены: @@ -538,16 +538,16 @@ func parent(tile: Tile, zoom=null) -> Tile: ## [Tile(x=0, y=0, z=2), Tile(x=0, y=1, z=2), Tile(x=0, y=2, z=2), Tile(x=0, y=3, z=2), ...] ## [/codeblock] func children(tile: Tile, zoom: int) -> Array: - var target_zoom = zoom if zoom != null else tile.z + 1 - var tiles: Array[Tile] = [tile] - while tiles[0].z >= target_zoom: - tile = tiles.pop_at(0) - tiles += [ - Tile.new(tile.x * 2, tile.y * 2, tile.z + 1), - Tile.new(tile.x * 2 + 1, tile.y * 2, tile.z + 1), - Tile.new(tile.x * 2 + 1, tile.y * 2 + 1, tile.z + 1), - Tile.new(tile.x * 2, tile.y * 2 + 1, tile.z + 1) ] - return tiles + var target_zoom = zoom if zoom != null else tile.z + 1 + var tiles: Array[Tile] = [tile] + while tiles[0].z >= target_zoom: + tile = tiles.pop_at(0) + tiles += [ + Tile.new(tile.x * 2, tile.y * 2, tile.z + 1), + Tile.new(tile.x * 2 + 1, tile.y * 2, tile.z + 1), + Tile.new(tile.x * 2 + 1, tile.y * 2 + 1, tile.z + 1), + Tile.new(tile.x * 2, tile.y * 2 + 1, tile.z + 1) ] + return tiles ## Проверяется, есть ли в [param merge_set] 4 плитки, которые могут быть объединены. @@ -555,52 +555,52 @@ func children(tile: Tile, zoom: int) -> Array: ## число, указывающее, были ли они объединены. При многократном применяя ## [method merge], можно упростить набор плиток. func merge(merge_set) -> Array: - var upwards_merge: = {} - for tile in merge_set.keys(): - var tile_parent = parent(tile) - if not upwards_merge.has(tile_parent): - upwards_merge[tile_parent] = {} - upwards_merge[tile_parent].update({tile: false}) - var current_tileset: = [] - var changed: = false - for supertile in upwards_merge.keys(): - var tile_children = upwards_merge[supertile] - if len(tile_children) == 4: - current_tileset += [supertile] - changed = true - else: - current_tileset += [tile_children] - return [current_tileset, changed] + var upwards_merge: = {} + for tile in merge_set.keys(): + var tile_parent = parent(tile) + if not upwards_merge.has(tile_parent): + upwards_merge[tile_parent] = {} + upwards_merge[tile_parent].update({tile: false}) + var current_tileset: = [] + var changed: = false + for supertile in upwards_merge.keys(): + var tile_children = upwards_merge[supertile] + if len(tile_children) == 4: + current_tileset += [supertile] + changed = true + else: + current_tileset += [tile_children] + return [current_tileset, changed] ## Максимально уменьшает размер набора плиток путем слияния листьев с родителями. ## [param tiles] - Последовательность плиток для слияния. func simplify(tiles: Array) -> Array: - # Проверьте, существует ли уже плитка и ее родитель. - # Убедитесь, что плитки отсортированы по масштабу, поэтому родительские - # плитки встречаются первыми. Если да, отбросьте дочернюю плитку - # (она покрыта родительской). - var root_set = {} - tiles.sort_custom(func(a, b): return a[2] < b[2]) - for tile in tiles: - var is_new_tile: bool = true - var supertiles = [] - for i in tile.z: - supertiles.append(parent(tile, i)) - for supertile in supertiles: - if supertile in root_set: - is_new_tile = false - continue - if is_new_tile: - root_set.update({tile: false}) + # Проверьте, существует ли уже плитка и ее родитель. + # Убедитесь, что плитки отсортированы по масштабу, поэтому родительские + # плитки встречаются первыми. Если да, отбросьте дочернюю плитку + # (она покрыта родительской). + var root_set = {} + tiles.sort_custom(func(a, b): return a[2] < b[2]) + for tile in tiles: + var is_new_tile: bool = true + var supertiles = [] + for i in tile.z: + supertiles.append(parent(tile, i)) + for supertile in supertiles: + if supertile in root_set: + is_new_tile = false + continue + if is_new_tile: + root_set.update({tile: false}) - # Повторять слияние до тех пор, пока дальнейшее упрощение перестанет быть невозможным. - var is_merging: bool = true - while is_merging: - var root_set_is_merging = merge(root_set) - root_set = root_set_is_merging[0] - is_merging = root_set_is_merging[1] - return root_set + # Повторять слияние до тех пор, пока дальнейшее упрощение перестанет быть невозможным. + var is_merging: bool = true + while is_merging: + var root_set_is_merging = merge(root_set) + root_set = root_set_is_merging[0] + is_merging = root_set_is_merging[1] + return root_set ## Получение наименьшей плитки, содержащей географическую границу. @@ -610,40 +610,40 @@ func simplify(tiles: Array) -> Array: ## севера в десятичных градусах. func bounding_tile(bbox: Bbox, truncate: bool = false) -> Tile: - if truncate: - var w_s = truncate_lnglat(bbox.left, bbox.bottom) - bbox.left = w_s[0] - bbox.bottom = w_s[1] - var e_n = truncate_lnglat(bbox.right, bbox.north) - bbox.right = e_n[0] - bbox.top = e_n[1] + if truncate: + var w_s = truncate_lnglat(bbox.left, bbox.bottom) + bbox.left = w_s[0] + bbox.bottom = w_s[1] + var e_n = truncate_lnglat(bbox.right, bbox.north) + bbox.right = e_n[0] + bbox.top = e_n[1] - bbox.right -= LL_EPSILON - bbox.bottom += LL_EPSILON + bbox.right -= LL_EPSILON + bbox.bottom += LL_EPSILON - var tmin = create_tile(bbox.left, bbox.top, 32) - var tmax = create_tile(bbox.right, bbox.bottom, 32) - assert(tmin.is_valid()) - assert(tmax.is_valid()) + var tmin = create_tile(bbox.left, bbox.top, 32) + var tmax = create_tile(bbox.right, bbox.bottom, 32) + assert(tmin.is_valid()) + assert(tmax.is_valid()) - var cell = [tmin.x, tmin.y, tmax.x, tmax.y] - var z = _get_bbox_zoom(cell) + var cell = [tmin.x, tmin.y, tmax.x, tmax.y] + var z = _get_bbox_zoom(cell) - if z == 0: - return Tile.new() + if z == 0: + return Tile.new() - var x = rshift(cell[0], 32 - z) - var y = rshift(cell[1], 32 - z) + var x = rshift(cell[0], 32 - z) + var y = rshift(cell[1], 32 - z) - return Tile.new(x, y, z) + return Tile.new(x, y, z) func _get_bbox_zoom(bbox: Array): - for z in MAX_ZOOM: - var mask: int = 1 << (32 - (z + 1)) - if (bbox[0] & mask) != (bbox[2] & mask) or (bbox[1] & mask) != (bbox[3] & mask): - return z - return MAX_ZOOM + for z in MAX_ZOOM: + var mask: int = 1 << (32 - (z + 1)) + if (bbox[0] & mask) != (bbox[2] & mask) or (bbox[1] & mask) != (bbox[3] & mask): + return z + return MAX_ZOOM ## Получение GeoJSON-функции, соответствующей плитке.[br] @@ -654,95 +654,95 @@ func _get_bbox_zoom(bbox: Array): ## [param buffer] - Необязательное буферное расстояние для полигона GeoJSON.[br] ## [param precision] - Координаты GeoJSON будут усечены до данного количества знаков после запятой.[br] func feature(tile: Tile, fid:String = 'null', props=null, projected='geographic', buffer=null) -> Dictionary: - var west_south_east_north = bounds(tile).as_array() - var west = west_south_east_north[0] - var south = west_south_east_north[1] - var east = west_south_east_north[2] - var north = west_south_east_north[3] - if projected == 'mercator': - var west_south = xy(west, south, false) - west = west_south[0] - south = west_south[1] - var east_north = xy(east, north, false) - east = east_north[0] - north = east_north[1] + var west_south_east_north = bounds(tile).as_array() + var west = west_south_east_north[0] + var south = west_south_east_north[1] + var east = west_south_east_north[2] + var north = west_south_east_north[3] + if projected == 'mercator': + var west_south = xy(west, south, false) + west = west_south[0] + south = west_south[1] + var east_north = xy(east, north, false) + east = east_north[0] + north = east_north[1] - if buffer: - west -= buffer - south -= buffer - east += buffer - north += buffer + if buffer: + west -= buffer + south -= buffer + east += buffer + north += buffer - west = round(west) - south = round(south) - east = round(east) - north = round(north) + west = round(west) + south = round(south) + east = round(east) + north = round(north) - var bbox: = [min(west, east), min(south, north), max(west, east), max(south, north)] - var geom: = {'type': 'Polygon', 'coordinates': [[[west, south], [west, north], [east, north], [east, south], [west, south]]] } - var xyz: = str(tile) - var feat: = { - 'type': 'Feature', - 'bbox': bbox, - 'id': xyz, - 'geometry': geom, - 'properties': {'title': 'XYZ tile %s' % xyz} } + var bbox: = [min(west, east), min(south, north), max(west, east), max(south, north)] + var geom: = {'type': 'Polygon', 'coordinates': [[[west, south], [west, north], [east, north], [east, south], [west, south]]] } + var xyz: = str(tile) + var feat: = { + 'type': 'Feature', + 'bbox': bbox, + 'id': xyz, + 'geometry': geom, + 'properties': {'title': 'XYZ tile %s' % xyz} } - if props: - feat['properties'].update(props) + if props: + feat['properties'].update(props) - if fid != 'null': - feat['id'] = fid + if fid != 'null': + feat['id'] = fid - return feat + return feat func isinstance(obj, types: Array) -> bool: - for type in types: - if is_instance_of(obj, type): - return true - return false + for type in types: + if is_instance_of(obj, type): + return true + return false ## Все кортежи координат из геометрии, объекта или коллекции.[br] ## [param lng] - Долгота.[br] ## [param lat] - широта.[br] func _coords(obj) -> Array: - var coordinates = [] - if is_instance_of(obj, TYPE_ARRAY): - coordinates = obj - elif 'features' in obj: - for feat in obj['features']: - coordinates = feat['geometry']['coordinates'] - elif 'geometry' in obj: - coordinates = obj['geometry']['coordinates'] - else: - coordinates = obj.get('coordinates', obj) + var coordinates = [] + if is_instance_of(obj, TYPE_ARRAY): + coordinates = obj + elif 'features' in obj: + for feat in obj['features']: + coordinates = feat['geometry']['coordinates'] + elif 'geometry' in obj: + coordinates = obj['geometry']['coordinates'] + else: + coordinates = obj.get('coordinates', obj) - var v: = [] - for e in coordinates: - if isinstance(e, [TYPE_FLOAT, TYPE_INT]): - v.append(coordinates) - break - else: - for f in _coords(e): - v.append([f[0], f[1]]) - return v + var v: = [] + for e in coordinates: + if isinstance(e, [TYPE_FLOAT, TYPE_INT]): + v.append(coordinates) + break + else: + for f in _coords(e): + v.append([f[0], f[1]]) + return v func _func1(bbox: Array, coords: Array) -> Array[float]: - var w = bbox[0] - var s = bbox[1] - var e = bbox[2] - var n = bbox[3] - var lng = coords[0] - var lat = coords[1] - return [min(w, lng), min(s, lat), max(e, lng), max(n, lat)] + var w = bbox[0] + var s = bbox[1] + var e = bbox[2] + var n = bbox[3] + var lng = coords[0] + var lat = coords[1] + return [min(w, lng), min(s, lat), max(e, lng), max(n, lat)] ## Возвращает граничную область объекта GeoJSON.[br] ## [param obj] - Отображение, геометрия GeoJSON, объект или коллекция объектов.[br] func geojson_bounds(obj) -> LngLatBbox: - var coords: Array = _coords(obj) - var wsen = coords.reduce(_func1, [180.0, 90.0, -180.0, -90.0]) - return LngLatBbox.new(wsen[0], wsen[1], wsen[2], wsen[3]) + var coords: Array = _coords(obj) + var wsen = coords.reduce(_func1, [180.0, 90.0, -180.0, -90.0]) + return LngLatBbox.new(wsen[0], wsen[1], wsen[2], wsen[3]) diff --git a/scripts/modbus.gd b/scripts/modbus.gd index 08ed3ed9..fe3c1fe3 100644 --- a/scripts/modbus.gd +++ b/scripts/modbus.gd @@ -4,128 +4,128 @@ const ONLINE_TIMEOUT = 5.0 ## Время ожидания пакета от я class ModbusClient: - ## Состояние автомата выполнения команды. - enum CmdState { - DONE, ## Команда выполнена. - FAIL, ## Команда не выполнена. - SEND, ## Буфер готов для отправки. - WAIT ## Ожидание выполнения. - } + ## Состояние автомата выполнения команды. + enum CmdState { + DONE, ## Команда выполнена. + FAIL, ## Команда не выполнена. + SEND, ## Буфер готов для отправки. + WAIT ## Ожидание выполнения. + } - var name: String - var unit: = 1 ## Адрес устройства Modbus. - var cmd_state: = CmdState.DONE ## Код текущей команды. - var tx_data: PackedByteArray ## Данные для отправки. - var online: = false ## Флаг. Если установлен, устройство на связи. Активный уровень - [b]true[/b]. - var tick_rx: = 0 ## Время приёма крайнего сообщения. - var tick_tx: = 0 ## Время отправки крайнего сообщения. + var name: String + var unit: = 1 ## Адрес устройства Modbus. + var cmd_state: = CmdState.DONE ## Код текущей команды. + var tx_data: PackedByteArray ## Данные для отправки. + var online: = false ## Флаг. Если установлен, устройство на связи. Активный уровень - [b]true[/b]. + var tick_rx: = 0 ## Время приёма крайнего сообщения. + var tick_tx: = 0 ## Время отправки крайнего сообщения. - static var json_conv: = JSON.new() + static var json_conv: = JSON.new() - signal command_done() ## Вызывается при успешном выполнении команды. - signal command_fail() ## Вызывается при возникновении ошибки во время выполнения команды. - signal line_changed() ## Вызывается при смене состояния связи с ячейкой. - signal data_received() ## Вызывается при приёме сообщения, до его разбора. - signal parse_failed(msg: String) ## Вызывается при возникновении ошибки во время разбора пакета. Передаёт сообщение об ошибке. + signal command_done() ## Вызывается при успешном выполнении команды. + signal command_fail() ## Вызывается при возникновении ошибки во время выполнения команды. + signal line_changed() ## Вызывается при смене состояния связи с ячейкой. + signal data_received() ## Вызывается при приёме сообщения, до его разбора. + signal parse_failed(msg: String) ## Вызывается при возникновении ошибки во время разбора пакета. Передаёт сообщение об ошибке. - signal holding_received(addr: int, data: Array) ## Вызывается при получении сообщения c данными из регистров хранения. - signal input_received(addr: int, data: Array) ## Вызывается при получении сообщения c данными из регистров входов. - signal info_received(msg: String) ## Вызывается при получении информационного сообщения. - signal except_received(msg: String) ## Вызывается при получении сообщения об исключительной ситуации. + signal holding_received(addr: int, data: Array) ## Вызывается при получении сообщения c данными из регистров хранения. + signal input_received(addr: int, data: Array) ## Вызывается при получении сообщения c данными из регистров входов. + signal info_received(msg: String) ## Вызывается при получении информационного сообщения. + signal except_received(msg: String) ## Вызывается при получении сообщения об исключительной ситуации. - func _init(nm: String, u: int = 1): - unit = u - name = nm + func _init(nm: String, u: int = 1): + unit = u + name = nm - ## Изменяет состояние. Вызывать периодически.[br] - ## [param tick] - Текущщее время в мс.[br] - func process(tick: int): - if online and ((tick - tick_rx) > ONLINE_TIMEOUT): - online = false - emit_signal('line_changed') - if (cmd_state == CmdState.WAIT) or (cmd_state == CmdState.SEND): - cmd_state = CmdState.FAIL - emit_signal('command_fail') - var rc = Error.ERR_BUSY - if cmd_state == CmdState.SEND: - rc = Error.OK - tick_tx = tick - cmd_state = CmdState.WAIT - return rc + ## Изменяет состояние. Вызывать периодически.[br] + ## [param tick] - Текущщее время в мс.[br] + func process(tick: int): + if online and ((tick - tick_rx) > ONLINE_TIMEOUT): + online = false + emit_signal('line_changed') + if (cmd_state == CmdState.WAIT) or (cmd_state == CmdState.SEND): + cmd_state = CmdState.FAIL + emit_signal('command_fail') + var rc = Error.ERR_BUSY + if cmd_state == CmdState.SEND: + rc = Error.OK + tick_tx = tick + cmd_state = CmdState.WAIT + return rc - ## Производит разбор принятых данных. Вызывать при приёме данных на сокете.[br] - ## [param data] - Данные принятые на сокете.[br] - ## [param tick] - Текущщее время в мс.[br] - func parse(data: PackedByteArray, tick: int) -> bool: - tick_rx = tick - if not online: - online = true - emit_signal('line_changed') - var utf8_data = data.get_string_from_utf8() - if Error.OK == json_conv.parse(utf8_data): - emit_signal('data_received') - var message: Dictionary = json_conv.get_data() - if message.has('code') and message.has('data'): - var rc: = true - var message_code = message['code'] - var message_data = message['data'] - match message_code: - 'read_input': - var addr = message_data[0] - var regs = message_data.slice(1) - emit_signal('input_received', addr, regs) - 'read_holding': - var addr = message_data[0] - var regs = message_data.slice(1) - emit_signal('holding_received', addr, regs) - 'info': - var msg = message['data'] - emit_signal('info_received', msg) - 'exception': - var msg = message['data'] - emit_signal('except_received', msg) - rc = false - _: - var msg: String = 'код команды \"%s\" - не распознан' % message_code - emit_signal('parse_failed', msg) - rc = false - if (cmd_state == CmdState.WAIT) and rc: - cmd_state = CmdState.DONE - emit_signal('command_done') - elif (cmd_state == CmdState.WAIT) and (not rc): - cmd_state = CmdState.FAIL - emit_signal('command_fail') - else: - var message = json_conv.get_error_message() - emit_signal('parse_failed', message) - return true + ## Производит разбор принятых данных. Вызывать при приёме данных на сокете.[br] + ## [param data] - Данные принятые на сокете.[br] + ## [param tick] - Текущщее время в мс.[br] + func parse(data: PackedByteArray, tick: int) -> bool: + tick_rx = tick + if not online: + online = true + emit_signal('line_changed') + var utf8_data = data.get_string_from_utf8() + if Error.OK == json_conv.parse(utf8_data): + emit_signal('data_received') + var message: Dictionary = json_conv.get_data() + if message.has('code') and message.has('data'): + var rc: = true + var message_code = message['code'] + var message_data = message['data'] + match message_code: + 'read_input': + var addr = message_data[0] + var regs = message_data.slice(1) + emit_signal('input_received', addr, regs) + 'read_holding': + var addr = message_data[0] + var regs = message_data.slice(1) + emit_signal('holding_received', addr, regs) + 'info': + var msg = message['data'] + emit_signal('info_received', msg) + 'exception': + var msg = message['data'] + emit_signal('except_received', msg) + rc = false + _: + var msg: String = 'код команды \"%s\" - не распознан' % message_code + emit_signal('parse_failed', msg) + rc = false + if (cmd_state == CmdState.WAIT) and rc: + cmd_state = CmdState.DONE + emit_signal('command_done') + elif (cmd_state == CmdState.WAIT) and (not rc): + cmd_state = CmdState.FAIL + emit_signal('command_fail') + else: + var message = json_conv.get_error_message() + emit_signal('parse_failed', message) + return true - ## Отправляет команду "записать в регистры".[br] - ## [param addr] - Начальный адрес регистров.[br] - ## [param count] - Данные для записи в регистры от начального адреса.[br] - func write(addr: int, data: Array) -> bool: - if cmd_state != CmdState.DONE: return false - var json_dict = {'code': 'write', 'data': [addr] + data} - tx_data = JSON.stringify(json_dict).to_utf8_buffer() - cmd_state = CmdState.SEND - return true + ## Отправляет команду "записать в регистры".[br] + ## [param addr] - Начальный адрес регистров.[br] + ## [param count] - Данные для записи в регистры от начального адреса.[br] + func write(addr: int, data: Array) -> bool: + if cmd_state != CmdState.DONE: return false + var json_dict = {'code': 'write', 'data': [addr] + data} + tx_data = JSON.stringify(json_dict).to_utf8_buffer() + cmd_state = CmdState.SEND + return true - ## Отправляет команду "читать регистры хранения".[br] - ## [param addr] - Начальный адрес регистров.[br] - ## [param count] - Количество регистров от начального адреса.[br] - func read_holdind(addr: int, count: int = 1) -> bool: - if cmd_state != CmdState.DONE: return false - var json_dict = {'code': 'read_holding', 'data': [addr, count]} - tx_data = JSON.stringify(json_dict).to_utf8_buffer() - cmd_state = CmdState.SEND - return true + ## Отправляет команду "читать регистры хранения".[br] + ## [param addr] - Начальный адрес регистров.[br] + ## [param count] - Количество регистров от начального адреса.[br] + func read_holdind(addr: int, count: int = 1) -> bool: + if cmd_state != CmdState.DONE: return false + var json_dict = {'code': 'read_holding', 'data': [addr, count]} + tx_data = JSON.stringify(json_dict).to_utf8_buffer() + cmd_state = CmdState.SEND + return true - ## Отправляет команду "читать регистры входов".[br] - ## [param addr] - Начальный адрес регистров.[br] - ## [param count] - Количество регистров от начального адреса.[br] - func read_input(addr: int, count: int = 1) -> bool: - if cmd_state != CmdState.DONE: return false - var json_dict = {'code': 'read_input', 'data': [addr, count]} - tx_data = JSON.stringify(json_dict).to_utf8_buffer() - cmd_state = CmdState.SEND - return true + ## Отправляет команду "читать регистры входов".[br] + ## [param addr] - Начальный адрес регистров.[br] + ## [param count] - Количество регистров от начального адреса.[br] + func read_input(addr: int, count: int = 1) -> bool: + if cmd_state != CmdState.DONE: return false + var json_dict = {'code': 'read_input', 'data': [addr, count]} + tx_data = JSON.stringify(json_dict).to_utf8_buffer() + cmd_state = CmdState.SEND + return true diff --git a/scripts/repnetwork.gd b/scripts/repnetwork.gd index 3dcf65a2..51bfef50 100644 --- a/scripts/repnetwork.gd +++ b/scripts/repnetwork.gd @@ -5,27 +5,27 @@ const PROTOCOL_TYPES: = {'yau07tx': yau07.YaU07, 'jsontx': capsrpb.CapsRpb, 'udp const TCP_PROTO: = {'5p28': tcp5p28.TCP5P28} class Socket extends PacketPeerUDP: - func send_to(addr, data): - self.set_dest_address(addr[0], addr[1]) - self.put_packet(data) + func send_to(addr, data): + self.set_dest_address(addr[0], addr[1]) + self.put_packet(data) class SocketTCP extends TCPServer: - const RX_TIMEOUT: int = 1000 ## Время сброса счётчика принятия части пакета. - var peerstream: = PacketPeerStream.new() - var unit_key: Array ## Клюс юнита. - var head_len: int = 8 ## Длинна заголовка команды. - var len_place: int = 4 ## Номер начального байта с длинной блока данных. - var type_len: int = 4 ## Количество байт в размере длинны. - var rx_tick: int ## Время приёма последнего пакета. - var rx_all: bool = true ## Флаг, что пакет принят полностью. - var rx_len: int ## Количество принятых байт. - var rx_data: PackedByteArray ## Принятые данные. - var data_len: int ## Размер ожидаемых данных. + const RX_TIMEOUT: int = 1000 ## Время сброса счётчика принятия части пакета. + var peerstream: = PacketPeerStream.new() + var unit_key: Array ## Клюс юнита. + var head_len: int = 8 ## Длинна заголовка команды. + var len_place: int = 4 ## Номер начального байта с длинной блока данных. + var type_len: int = 4 ## Количество байт в размере длинны. + var rx_tick: int ## Время приёма последнего пакета. + var rx_all: bool = true ## Флаг, что пакет принят полностью. + var rx_len: int ## Количество принятых байт. + var rx_data: PackedByteArray ## Принятые данные. + var data_len: int ## Размер ожидаемых данных. - func send_to(data): - var peer = peerstream.get_stream_peer() - peer.put_data(data) + func send_to(data): + var peer = peerstream.get_stream_peer() + peer.put_data(data) var poll_sockets: Array[Socket] @@ -38,129 +38,129 @@ var logger_page: Node func create_socket(nm) -> Socket: - var addr = repsettings.UnitProfiles[nm][1] - var port = repsettings.UnitProfiles[nm][2] - var broad = repsettings.UnitProfiles[nm][3] - var bind = repsettings.UnitProfiles[nm][4] - var sock: = Socket.new() - sock.set_broadcast_enabled(broad) - var rc = Error.OK if not bind else sock.bind(port, addr) - var errlevel = log.INFO if rc == Error.OK else log.ERROR - log.message(errlevel, '%s: сокет:%s:%d привязанный:%s %s' % [nm, addr, port, ['нет', 'да'][int(bind)], error_string(rc)]) - return sock + var addr = repsettings.UnitProfiles[nm][1] + var port = repsettings.UnitProfiles[nm][2] + var broad = repsettings.UnitProfiles[nm][3] + var bind = repsettings.UnitProfiles[nm][4] + var sock: = Socket.new() + sock.set_broadcast_enabled(broad) + var rc = Error.OK if not bind else sock.bind(port, addr) + var errlevel = log.INFO if rc == Error.OK else log.ERROR + log.message(errlevel, '%s: сокет:%s:%d привязанный:%s %s' % [nm, addr, port, ['нет', 'да'][int(bind)], error_string(rc)]) + return sock func create_tcpsocket(nm) -> SocketTCP: - var addr = repsettings.UnitProfiles[nm][1] - var port = repsettings.UnitProfiles[nm][2] - var sock = SocketTCP.new() - sock.unit_key = repsettings.get_unit_key(nm) - sock.listen(port, addr) - log.message(log.INFO, '%s: сокет:%s:%d привязанный:' % [nm, addr, port]) - return sock + var addr = repsettings.UnitProfiles[nm][1] + var port = repsettings.UnitProfiles[nm][2] + var sock = SocketTCP.new() + sock.unit_key = repsettings.get_unit_key(nm) + sock.listen(port, addr) + log.message(log.INFO, '%s: сокет:%s:%d привязанный:' % [nm, addr, port]) + return sock func _ready() -> void: - log.info('Модуль работы с сетью готов') - poll_sockets.append(create_socket('уарэп-яу07-частный')) - poll_sockets.append(create_socket('уарэп-яу07-общий')) - poll_sockets.append(create_socket('уарэп-капсрпб-пол')) - sock_unicast = poll_sockets[0] - sock_capsrpb = poll_sockets[2] + log.info('Модуль работы с сетью готов') + poll_sockets.append(create_socket('уарэп-яу07-частный')) + poll_sockets.append(create_socket('уарэп-яу07-общий')) + poll_sockets.append(create_socket('уарэп-капсрпб-пол')) + sock_unicast = poll_sockets[0] + sock_capsrpb = poll_sockets[2] - for unit_name in repsettings.UnitProfiles: - var setting = repsettings.UnitProfiles[unit_name] - var proto = setting[0] - if proto in PROTOCOL_TYPES: - var unit = PROTOCOL_TYPES[proto].new(unit_name) - var unit_key = repsettings.get_unit_key(unit_name) - units[unit_key] = unit - if logger_page: - unit.connect('line_changed', Callable(logger_page, 'on_line_changed').bind(unit_key)) - unit.connect('command_fail', Callable(logger_page, 'on_command_fail').bind(unit_key)) + for unit_name in repsettings.UnitProfiles: + var settings = repsettings.UnitProfiles[unit_name] + var proto = settings[0] + if proto in PROTOCOL_TYPES: + var unit = PROTOCOL_TYPES[proto].new(unit_name) + var unit_key = repsettings.get_unit_key(unit_name) + units[unit_key] = unit + if logger_page: + unit.connect('line_changed', Callable(logger_page, 'on_line_changed').bind(unit_key)) + unit.connect('command_fail', Callable(logger_page, 'on_command_fail').bind(unit_key)) - if proto in TCP_PROTO: - var unit = TCP_PROTO[proto].new(unit_name) - var unit_key = repsettings.get_unit_key(unit_name) - units_tcp[unit_key] = unit - if logger_page: - unit.connect('line_changed', Callable(logger_page, 'on_line_changed').bind(unit_key)) - unit.connect('command_fail', Callable(logger_page, 'on_command_fail').bind(unit_key)) + if proto in TCP_PROTO: + var unit = TCP_PROTO[proto].new(unit_name) + var unit_key = repsettings.get_unit_key(unit_name) + units_tcp[unit_key] = unit + if logger_page: + unit.connect('line_changed', Callable(logger_page, 'on_line_changed').bind(unit_key)) + unit.connect('command_fail', Callable(logger_page, 'on_command_fail').bind(unit_key)) - tcp_sockets.append([create_tcpsocket('уарэп-5п28'), units_tcp[repsettings.get_unit_key('уарэп-5п28')]]) + tcp_sockets.append([create_tcpsocket('уарэп-5п28'), units_tcp[repsettings.get_unit_key('уарэп-5п28')]]) - for key in units: log.info('%s %s:%d' % [units[key].name, key[0], key[1]]) - for key in units_tcp: log.info('%s %s:%d' % [units_tcp[key].name, key[0], key[1]]) + for key in units: log.info('%s %s:%d' % [units[key].name, key[0], key[1]]) + for key in units_tcp: log.info('%s %s:%d' % [units_tcp[key].name, key[0], key[1]]) func poll_receive(sock: Socket, tick: int) -> bool: - while sock.get_available_packet_count() > 0: - var data: = sock.get_packet() - var addr: = sock.get_packet_ip() - var port: = sock.get_packet_port() - var addr_port: = [addr, port] - if addr_port in units: units[addr_port].parse(data, tick) - return false + while sock.get_available_packet_count() > 0: + var data: = sock.get_packet() + var addr: = sock.get_packet_ip() + var port: = sock.get_packet_port() + var addr_port: = [addr, port] + if addr_port in units: units[addr_port].parse(data, tick) + return false func poll_receive_tcp(sock: SocketTCP, tick: int) -> bool: - if sock.is_connection_available(): # check if someone's trying to connect - var client = sock.take_connection() # accept connection - sock.peerstream.set_stream_peer(client) # bind peerstream to new client - print_debug('client_is_connected') - var peer = sock.peerstream.get_stream_peer() - if peer: - var peer_len_data = peer.get_available_bytes() - if peer_len_data > 0: - get_client_data(sock, peer_len_data, peer, tick) - if sock.unit_key in units_tcp and sock.rx_all: units_tcp[sock.unit_key].parse(sock.rx_data, tick) - return false + if sock.is_connection_available(): # check if someone's trying to connect + var client = sock.take_connection() # accept connection + sock.peerstream.set_stream_peer(client) # bind peerstream to new client + print_debug('client_is_connected') + var peer = sock.peerstream.get_stream_peer() + if peer: + var peer_len_data = peer.get_available_bytes() + if peer_len_data > 0: + get_client_data(sock, peer_len_data, peer, tick) + if sock.unit_key in units_tcp and sock.rx_all: units_tcp[sock.unit_key].parse(sock.rx_data, tick) + return false func get_client_data(sock: SocketTCP, peer_len_data, peer, tick): - if tick - sock.rx_tick > sock.RX_TIMEOUT: - sock.rx_all = true - sock.rx_tick = tick - if sock.rx_all: - if peer_len_data >= sock.head_len: - sock.rx_all = false - sock.rx_len = sock.head_len - var head = peer.get_data(sock.head_len) - sock.data_len = head[1].decode_u32(sock.len_place) - var len_data_in_buf = peer.get_available_bytes() - if len_data_in_buf > 0: - sock.rx_data = head[1] - var len_rx = len_data_in_buf - if len_data_in_buf >= sock.data_len: - len_rx = sock.data_len - get_tcp_data(sock, peer, len_rx) - else: - var len_rx = (sock.head_len + sock.data_len) - sock.rx_len - if peer_len_data < len_rx: - len_rx = peer_len_data - get_tcp_data(sock, peer, len_rx) + if tick - sock.rx_tick > sock.RX_TIMEOUT: + sock.rx_all = true + sock.rx_tick = tick + if sock.rx_all: + if peer_len_data >= sock.head_len: + sock.rx_all = false + sock.rx_len = sock.head_len + var head = peer.get_data(sock.head_len) + sock.data_len = head[1].decode_u32(sock.len_place) + var len_data_in_buf = peer.get_available_bytes() + if len_data_in_buf > 0: + sock.rx_data = head[1] + var len_rx = len_data_in_buf + if len_data_in_buf >= sock.data_len: + len_rx = sock.data_len + get_tcp_data(sock, peer, len_rx) + else: + var len_rx = (sock.head_len + sock.data_len) - sock.rx_len + if peer_len_data < len_rx: + len_rx = peer_len_data + get_tcp_data(sock, peer, len_rx) func get_tcp_data(sock: SocketTCP, peer, len_rx): - var temp_data = peer.get_data(len_rx)[1] - sock.rx_data.append_array(temp_data) - sock.rx_len += len_rx - if sock.rx_len >= sock.head_len + sock.data_len: - sock.rx_all = true + var temp_data = peer.get_data(len_rx)[1] + sock.rx_data.append_array(temp_data) + sock.rx_len += len_rx + if sock.rx_len >= sock.head_len + sock.data_len: + sock.rx_all = true func _process(_delta: float) -> void: - var tick = Time.get_ticks_msec() - for addr in units: - var unit = units[addr] - match unit.process(tick): - Error.OK: sock_unicast.send_to(addr, unit.tx_data) - Error.FAILED: emit_signal('socket_error', 'ошибка: %s %s:%d' % [unit, addr[0], addr[1]]) - for sock_unit in tcp_sockets: - var sock = sock_unit[0] - var unit = sock_unit[1] - match unit.process(tick): - Error.OK: sock.send_to(unit.tx_data) - Error.FAILED: emit_signal('socket_error', 'ошибка: %s' %unit) - poll_receive_tcp(sock, tick) - poll_sockets.any(func(sock): poll_receive(sock, tick)) + var tick = Time.get_ticks_msec() + for addr in units: + var unit = units[addr] + match unit.process(tick): + Error.OK: sock_unicast.send_to(addr, unit.tx_data) + Error.FAILED: emit_signal('socket_error', 'ошибка: %s %s:%d' % [unit, addr[0], addr[1]]) + for sock_unit in tcp_sockets: + var sock = sock_unit[0] + var unit = sock_unit[1] + match unit.process(tick): + Error.OK: sock.send_to(unit.tx_data) + Error.FAILED: emit_signal('socket_error', 'ошибка: %s' %unit) + poll_receive_tcp(sock, tick) + poll_sockets.any(func(sock): poll_receive(sock, tick)) diff --git a/scripts/repsettings.gd b/scripts/repsettings.gd index 131afebc..9c944422 100644 --- a/scripts/repsettings.gd +++ b/scripts/repsettings.gd @@ -3,40 +3,40 @@ class_name rep_settings extends Node ## Параметры. @export var UnitProfiles: = { \ - # Название | Протокол | Адрес | Порт | Широковещательное | Привязывать к адресу | Комментарий - 'уарэп-яу07-частный': ['yau07rx', '10.1.1.2', 50002, false, true, 'IP-адрес на котором принимают частные сообщения от ЯУ-07'], - 'уарэп-яу07-общий': ['yau07rx', '10.1.1.255', 50000, true, true, 'IP-адрес на котором принимают широковещательные сообщения от ЯУ-07'], - 'уарэп-капсрпб-пол': ['jsonrx', '127.0.0.1', 42023, false, true, 'IP-адрес на котором принимают сообщения от КАПС РПБ'], - 'уарэп-капсрпб-отп': ['jsontx', '127.0.0.1', 42022, false, false, 'IP-адрес на который отправляют сообщения для КАПС РПБ'], - 'уарэп-эмс': ['yau07tx', '10.1.1.27', 50027, false, false, 'IP-адрес ЯУ-07 прибора УФ'], - 'уарэп-яу07-2в': ['yau07tx', '10.1.1.34', 50034, false, false, 'IP-адрес ЯУ-07 прибора ПРД-В'], - 'уарэп-яу07-2н': ['yau07tx', '10.1.1.33', 50033, false, false, 'IP-адрес ЯУ-07 прибора ПРД-Н'], - 'уарэп-яу07-2к': ['yau07tx', '10.1.1.35', 50035, false, false, 'IP-адрес ЯУ-07 прибора ПРД-К'], - 'уарэп-яу07-4в': ['yau07tx', '10.1.1.66', 50066, false, false, 'IP-адрес ЯУ-07 прибора ПРД-В'], - 'уарэп-яу07-4н': ['yau07tx', '10.1.1.65', 50065, false, false, 'IP-адрес ЯУ-07 прибора ПРД-Н'], - 'уарэп-яу07-4к': ['yau07tx', '10.1.1.67', 50067, false, false, 'IP-адрес ЯУ-07 прибора ПРД-К'], - 'уарэп-яу07-1в': ['yau07tx', '10.1.1.18', 50018, false, false, 'IP-адрес ЯУ-07 прибора ПРД-В'], - 'уарэп-яу07-1н': ['yau07tx', '10.1.1.17', 50017, false, false, 'IP-адрес ЯУ-07 прибора ПРД-Н'], - 'уарэп-яу07-1к': ['yau07tx', '10.1.1.19', 50019, false, false, 'IP-адрес ЯУ-07 прибора ПРД-К'], - 'уарэп-яу07-3в': ['yau07tx', '10.1.1.50', 50050, false, false, 'IP-адрес ЯУ-07 прибора ПРД-В'], - 'уарэп-яу07-3н': ['yau07tx', '10.1.1.49', 50049, false, false, 'IP-адрес ЯУ-07 прибора ПРД-Н'], - 'уарэп-яу07-3к': ['yau07tx', '10.1.1.51', 50051, false, false, 'IP-адрес ЯУ-07 прибора ПРД-Н'], + # Название | Протокол | Адрес | Порт | Широковещательное | Привязывать к адресу | Комментарий + 'уарэп-яу07-частный': ['yau07rx', '10.1.1.2', 50002, false, true, 'IP-адрес на котором принимают частные сообщения от ЯУ-07'], + 'уарэп-яу07-общий': ['yau07rx', '10.1.1.255', 50000, true, true, 'IP-адрес на котором принимают широковещательные сообщения от ЯУ-07'], + 'уарэп-капсрпб-пол': ['jsonrx', '127.0.0.1', 42023, false, true, 'IP-адрес на котором принимают сообщения от КАПС РПБ'], + 'уарэп-капсрпб-отп': ['jsontx', '127.0.0.1', 42022, false, false, 'IP-адрес на который отправляют сообщения для КАПС РПБ'], + 'уарэп-эмс': ['yau07tx', '10.1.1.27', 50027, false, false, 'IP-адрес ЯУ-07 прибора УФ'], + 'уарэп-яу07-2в': ['yau07tx', '10.1.1.34', 50034, false, false, 'IP-адрес ЯУ-07 прибора ПРД-В'], + 'уарэп-яу07-2н': ['yau07tx', '10.1.1.33', 50033, false, false, 'IP-адрес ЯУ-07 прибора ПРД-Н'], + 'уарэп-яу07-2к': ['yau07tx', '10.1.1.35', 50035, false, false, 'IP-адрес ЯУ-07 прибора ПРД-К'], + 'уарэп-яу07-4в': ['yau07tx', '10.1.1.66', 50066, false, false, 'IP-адрес ЯУ-07 прибора ПРД-В'], + 'уарэп-яу07-4н': ['yau07tx', '10.1.1.65', 50065, false, false, 'IP-адрес ЯУ-07 прибора ПРД-Н'], + 'уарэп-яу07-4к': ['yau07tx', '10.1.1.67', 50067, false, false, 'IP-адрес ЯУ-07 прибора ПРД-К'], + 'уарэп-яу07-1в': ['yau07tx', '10.1.1.18', 50018, false, false, 'IP-адрес ЯУ-07 прибора ПРД-В'], + 'уарэп-яу07-1н': ['yau07tx', '10.1.1.17', 50017, false, false, 'IP-адрес ЯУ-07 прибора ПРД-Н'], + 'уарэп-яу07-1к': ['yau07tx', '10.1.1.19', 50019, false, false, 'IP-адрес ЯУ-07 прибора ПРД-К'], + 'уарэп-яу07-3в': ['yau07tx', '10.1.1.50', 50050, false, false, 'IP-адрес ЯУ-07 прибора ПРД-В'], + 'уарэп-яу07-3н': ['yau07tx', '10.1.1.49', 50049, false, false, 'IP-адрес ЯУ-07 прибора ПРД-Н'], + 'уарэп-яу07-3к': ['yau07tx', '10.1.1.51', 50051, false, false, 'IP-адрес ЯУ-07 прибора ПРД-Н'], - # Название | Протокол | Адрес | Порт получения | Комментарий - 'уарэп-5п28': ['5p28', '127.0.0.1', 50000, 'IP-адрес комплекса 5П-28'], + # Название | Протокол | Адрес | Порт получения | Комментарий + 'уарэп-5п28': ['5p28', '127.0.0.1', 50000, 'IP-адрес комплекса 5П-28'], - # Название | Протокол | Адрес | Порт получения | Порт отправки | Комментарий - 'уарэп-щ3': ['udp-json-mbcs', 'localhost', 48000, 44100, 'IP-адрес и порты через которые производят обмен сообщения с драйвером клиента Modbus Serial'], + # Название | Протокол | Адрес | Порт получения | Порт отправки | Комментарий + 'уарэп-щ3': ['udp-json-mbcs', 'localhost', 48000, 44100, 'IP-адрес и порты через которые производят обмен сообщения с драйвером клиента Modbus Serial'], - # Название | Протокол | Имя порта | Скорость, бод | Чётность | Комментарий - 'уарэп-спт25-1': ['uart', '/dev/tty2', 115200, false, 'Адрес прибора СПТ-25 (G1)'], - 'уарэп-спт25-2': ['uart', '/dev/tty3', 115200, false, 'Адрес прибора СПТ-25 (G2)'], + # Название | Протокол | Имя порта | Скорость, бод | Чётность | Комментарий + 'уарэп-спт25-1': ['uart', '/dev/tty2', 115200, false, 'Адрес прибора СПТ-25 (G1)'], + 'уарэп-спт25-2': ['uart', '/dev/tty3', 115200, false, 'Адрес прибора СПТ-25 (G2)'], } ## Параметры цели. @export var ThreatParams = { - 'time_lost': 5000 ## Время после последнего обновления в мс, через которое цель считается потерянной. - } + 'time_lost': 5000 ## Время после последнего обновления в мс, через которое цель считается потерянной. + } func _ready() -> void: log.info('Модуль настроек готов') @@ -44,6 +44,6 @@ func _ready() -> void: log.info('Модуль настроек готов') ## Возвращает ключ устройства. func get_unit_key(unit_name: String) -> Array: - assert(UnitProfiles.has(unit_name), 'ошибка: нет такого устройства \"%s\"' % unit_name) - var item = UnitProfiles[unit_name] - return [item[1], item[2]] + assert(UnitProfiles.has(unit_name), 'ошибка: нет такого устройства \"%s\"' % unit_name) + var item = UnitProfiles[unit_name] + return [item[1], item[2]] diff --git a/scripts/tcp5p28.gd b/scripts/tcp5p28.gd index 47bafdfc..63fca150 100644 --- a/scripts/tcp5p28.gd +++ b/scripts/tcp5p28.gd @@ -9,185 +9,185 @@ const BUFFER_SIZE = 2048 ## Размер приёмного буфера д ## Тип модуляции для запаковки в пакет для 5П-28. const MOD_TYPES: Array = ['', - 'am', - 'ook', - 'ask', - 'fm', - 'qpsk', - 'oqpsk', - 'pi4qpsk', - 'qam16', - 'qam32', - 'qam64', - 'qam128', - 'qam256', - 'qam512', - 'cpfsk', - 'psk', - 'ofdm',] + 'am', + 'ook', + 'ask', + 'fm', + 'qpsk', + 'oqpsk', + 'pi4qpsk', + 'qam16', + 'qam32', + 'qam64', + 'qam128', + 'qam256', + 'qam512', + 'cpfsk', + 'psk', + 'ofdm',] const CU_STATE: Dictionary = { - 0:2, # Подготовка к выполнению - 1:1, # Выполняется - 2:9, # Ошибка в команде - 3:7, # Занят выполнением другого ЦУ - 4:9, # Неисправность - } + 0:2, # Подготовка к выполнению + 1:1, # Выполняется + 2:9, # Ошибка в команде + 3:7, # Занят выполнением другого ЦУ + 4:9, # Неисправность + } class TCP5P28: - const PACK_TYPE_CMD: int = 1 - const PACK_TYPE_RES: int = 2 - const CMD_TYPE_STATE: int = 1 - const CMD_TYPE_TH: int = 2 - const CMD_TYPE_IN: int = 3 + const PACK_TYPE_CMD: int = 1 + const PACK_TYPE_RES: int = 2 + const CMD_TYPE_STATE: int = 1 + const CMD_TYPE_TH: int = 2 + const CMD_TYPE_IN: int = 3 - signal line_changed(unit: TCP5P28) ## Сигнал состояние связи изменилось. - signal data_received(unit: TCP5P28) ## Сигнал данные приняты. - signal command_fail(unit: TCP5P28) ## Сигнал при не выполнении команды. - signal parse_error(unit: TCP5P28, msg: String) ## Сигнал ошибки разбора сообщения. - signal get_threats(unit: TCP5P28) ## Сигнал запрос целей. - signal get_interfers(unit: TCP5P28) ## Запрос состояния выполнения целеуказания. + signal line_changed(unit: TCP5P28) ## Сигнал состояние связи изменилось. + signal data_received(unit: TCP5P28) ## Сигнал данные приняты. + signal command_fail(unit: TCP5P28) ## Сигнал при не выполнении команды. + signal parse_error(unit: TCP5P28, msg: String) ## Сигнал ошибки разбора сообщения. + signal get_threats(unit: TCP5P28) ## Сигнал запрос целей. + signal get_interfers(unit: TCP5P28) ## Запрос состояния выполнения целеуказания. - var online: = false ## Состояние связи с источником сообщений. - var name: String ## Имя экземпляра. - var rx_tick: = 0 ## Тик время последнего приёма, мс. - var tx_data: = PackedByteArray() ## Пакет для отправки. - var tx_stack: Array ## Массив пакетов для отправки. + var online: = false ## Состояние связи с источником сообщений. + var name: String ## Имя экземпляра. + var rx_tick: = 0 ## Тик время последнего приёма, мс. + var tx_data: = PackedByteArray() ## Пакет для отправки. + var tx_stack: Array ## Массив пакетов для отправки. - func _to_string() -> String: return String('капсрпб: "%s" %s ' % [self.name, ['отключен', 'на связи'][int(online)]]) - func _init(nm): self.name = nm + func _to_string() -> String: return String('капсрпб: "%s" %s ' % [self.name, ['отключен', 'на связи'][int(online)]]) + func _init(nm): self.name = nm - func parse(data: PackedByteArray, now_tick: int): - rx_tick = now_tick - if not online: - online = true - emit_signal('line_changed', self) - var pack_type = data.decode_u8(2) - if pack_type == PACK_TYPE_CMD: - var cmd_type = data.decode_u8(8) - if cmd_type == CMD_TYPE_TH: - emit_signal('get_threats') - if cmd_type == CMD_TYPE_STATE: - emit_signal('get_interfers') - var external_cu = ProjectSettings.get_setting('application/config/external_cu', false) - if cmd_type == CMD_TYPE_IN and external_cu: - set_interfer(data) + func parse(data: PackedByteArray, now_tick: int): + rx_tick = now_tick + if not online: + online = true + emit_signal('line_changed', self) + var pack_type = data.decode_u8(2) + if pack_type == PACK_TYPE_CMD: + var cmd_type = data.decode_u8(8) + if cmd_type == CMD_TYPE_TH: + emit_signal('get_threats') + if cmd_type == CMD_TYPE_STATE: + emit_signal('get_interfers') + var external_cu = ProjectSettings.get_setting('application/config/external_cu', false) + if cmd_type == CMD_TYPE_IN and external_cu: + set_interfer(data) - func process(now_tick: int): - if online and ((now_tick - rx_tick) > ONLINE_TIMEOUT): - online = false - emit_signal('line_changed', self) - if len(tx_stack): - tx_data = tx_stack[0] - tx_stack.remove_at(0) - return Error.OK - else: - return Error.ERR_UNAVAILABLE + func process(now_tick: int): + if online and ((now_tick - rx_tick) > ONLINE_TIMEOUT): + online = false + emit_signal('line_changed', self) + if len(tx_stack): + tx_data = tx_stack[0] + tx_stack.remove_at(0) + return Error.OK + else: + return Error.ERR_UNAVAILABLE - func pack_threats(ths): - var data = PackedByteArray() - var data_len = 4 + len(ths) * 80 # длинна блока данных в байтах - var tick = Time.get_ticks_msec() - data.resize(4) - data.encode_u16(0, 0) # Код ошибки - data.encode_u16(2, len(ths)) # Количество целей - for th in ths.values(): - data.append_array(get_threats_data(th, int(tick))) - var data_to_send: PackedByteArray = create_header(data_len) - data_to_send.append_array(data) - tx_stack.append(data_to_send) + func pack_threats(ths): + var data = PackedByteArray() + var data_len = 4 + len(ths) * 80 # длинна блока данных в байтах + var tick = Time.get_ticks_msec() + data.resize(4) + data.encode_u16(0, 0) # Код ошибки + data.encode_u16(2, len(ths)) # Количество целей + for th in ths.values(): + data.append_array(get_threats_data(th, int(tick))) + var data_to_send: PackedByteArray = create_header(data_len) + data_to_send.append_array(data) + tx_stack.append(data_to_send) - func pack_interfers(interfers, ecms): - var data = PackedByteArray() - var data_len = 12 # длинна блока данных в байтах - data.resize(data_len) - data.encode_u16(0, 0) # Код ошибки - data.encode_u16(2, len(interfers)) # Количество целей - data.encode_u32(4, 0) # Состояние УА-РЕП - data.encode_u32(8, 0) # Состояние линий связи - for id in interfers.values(): - var cu_data: PackedByteArray = [] - cu_data.resize(4) - var cu = ecms[id] - cu_data.encode_u16(0, cu.ispp) - var state: int - if CU_STATE.has(cu.svk): - state = CU_STATE[cu.svk] - else: - state = 9 - cu_data.encode_u8(2, state) # Состояние выполнения команды - cu_data.encode_u8(3, 0xFF) # Эффективность работы помехи - data.append_array(cu_data) - var data_to_send: PackedByteArray = create_header(data_len + 4 * len(interfers)) - data_to_send.append_array(data) - tx_stack.append(data_to_send) + func pack_interfers(interfers, ecms): + var data = PackedByteArray() + var data_len = 12 # длинна блока данных в байтах + data.resize(data_len) + data.encode_u16(0, 0) # Код ошибки + data.encode_u16(2, len(interfers)) # Количество целей + data.encode_u32(4, 0) # Состояние УА-РЕП + data.encode_u32(8, 0) # Состояние линий связи + for id in interfers.values(): + var cu_data: PackedByteArray = [] + cu_data.resize(4) + var cu = ecms[id] + cu_data.encode_u16(0, cu.ispp) + var state: int + if CU_STATE.has(cu.svk): + state = CU_STATE[cu.svk] + else: + state = 9 + cu_data.encode_u8(2, state) # Состояние выполнения команды + cu_data.encode_u8(3, 0xFF) # Эффективность работы помехи + data.append_array(cu_data) + var data_to_send: PackedByteArray = create_header(data_len + 4 * len(interfers)) + data_to_send.append_array(data) + tx_stack.append(data_to_send) - func create_header(data_len): - var head_data = PackedByteArray() - head_data.resize(8) # Размер заголовка - head_data.encode_u16(0, 0) # Код ошибки - head_data.encode_u8(2, 2) # Тип пакета, ответ на запрос - head_data.encode_u8(3, 5) # Версия протокола, всегда 5 - head_data.encode_u32(4, data_len) # Длинна пакета данных - return head_data + func create_header(data_len): + var head_data = PackedByteArray() + head_data.resize(8) # Размер заголовка + head_data.encode_u16(0, 0) # Код ошибки + head_data.encode_u8(2, 2) # Тип пакета, ответ на запрос + head_data.encode_u8(3, 5) # Версия протокола, всегда 5 + head_data.encode_u32(4, data_len) # Длинна пакета данных + return head_data - func get_threats_data(th, tick): - var th_data = PackedByteArray() - th_data.resize(80) # Количество байт в массиве - th_data.encode_u64(0, tick) - var flags: int = 0 # с 0 по 7 бит рекомендованная помеха - flags |= 0 << 8 # Флаг рекомендуемой помехи - flags |= int(th.fflags['aoa']) << 9 # Флаг доставерности пеленга - flags |= int(th.fflags['alt']) << 10 # Флаг доставерности высоты - flags |= int(th.fflags['power']) << 11 # Флаг доставерности мощности - flags |= int(th.fflags['baud']) << 12 # Флаг доставерности ск. мод. - flags |= int(th.fflags['slon']) << 13 # Флаг доставерности долготы ст. позиции - flags |= int(th.fflags['slat']) << 14 # Флаг доставерности широты ст. позиции - flags |= int(th.fflags['lon']) << 15 # Флаг доставерности долготы - flags |= int(th.fflags['lat']) << 16 # Флаг доставерности широты - flags |= int(0) << 30 # Флаг признак назначения помехи на цель - flags |= int(0) << 31 # Флаг признак назначения опасной цель - th_data.encode_u32(8, flags) # Флаги - th_data.encode_u16(12, th.id) # Идентификатор объекта - th_data.encode_u16(14, int(round(th.aoa/0.1))) # Пеленг объекта - th_data.encode_u16(16, int(th.alt)) # Высота объекта - th_data.encode_u16(18, int(th.freq)) # Частота объекта - th_data.encode_u16(20, int(th.width)) # Ширина частотной полосы объекта - th_data.encode_u8(22, int(th.power)) # Мощность излучения объекта - var mod = MOD_TYPES.find(th.tmod) - if mod == -1: mod = 0 - th_data.encode_u8(23, int(mod)) # Тип модуляции - th_data.encode_u32(24, int(th.baud)) # Скорость модуляции, бод - to_cp866(th.proto, th_data) - var c1 = pow(2, 30) / 180.0 - th_data.encode_s32(64, int(th.slon * c1)) - th_data.encode_s32(68, int(th.slat * c1)) - th_data.encode_s32(72, int(th.lon * c1)) - th_data.encode_s32(76, int(th.lat * c1)) - return th_data + func get_threats_data(th, tick): + var th_data = PackedByteArray() + th_data.resize(80) # Количество байт в массиве + th_data.encode_u64(0, tick) + var flags: int = 0 # с 0 по 7 бит рекомендованная помеха + flags |= 0 << 8 # Флаг рекомендуемой помехи + flags |= int(th.fflags['aoa']) << 9 # Флаг доставерности пеленга + flags |= int(th.fflags['alt']) << 10 # Флаг доставерности высоты + flags |= int(th.fflags['power']) << 11 # Флаг доставерности мощности + flags |= int(th.fflags['baud']) << 12 # Флаг доставерности ск. мод. + flags |= int(th.fflags['slon']) << 13 # Флаг доставерности долготы ст. позиции + flags |= int(th.fflags['slat']) << 14 # Флаг доставерности широты ст. позиции + flags |= int(th.fflags['lon']) << 15 # Флаг доставерности долготы + flags |= int(th.fflags['lat']) << 16 # Флаг доставерности широты + flags |= int(0) << 30 # Флаг признак назначения помехи на цель + flags |= int(0) << 31 # Флаг признак назначения опасной цель + th_data.encode_u32(8, flags) # Флаги + th_data.encode_u16(12, th.id) # Идентификатор объекта + th_data.encode_u16(14, int(round(th.aoa/0.1))) # Пеленг объекта + th_data.encode_u16(16, int(th.alt)) # Высота объекта + th_data.encode_u16(18, int(th.freq)) # Частота объекта + th_data.encode_u16(20, int(th.width)) # Ширина частотной полосы объекта + th_data.encode_u8(22, int(th.power)) # Мощность излучения объекта + var mod = MOD_TYPES.find(th.tmod) + if mod == -1: mod = 0 + th_data.encode_u8(23, int(mod)) # Тип модуляции + th_data.encode_u32(24, int(th.baud)) # Скорость модуляции, бод + to_cp866(th.proto, th_data) + var c1 = pow(2, 30) / 180.0 + th_data.encode_s32(64, int(th.slon * c1)) + th_data.encode_s32(68, int(th.slat * c1)) + th_data.encode_s32(72, int(th.lon * c1)) + th_data.encode_s32(76, int(th.lat * c1)) + return th_data - func to_cp866(proto: String, th_data): - var cp866_proto: PackedByteArray = proto.to_ascii_buffer() - cp866_proto.resize(36) - for i in len(cp866_proto): - th_data[28 + i] = cp866_proto[i] + func to_cp866(proto: String, th_data): + var cp866_proto: PackedByteArray = proto.to_ascii_buffer() + cp866_proto.resize(36) + for i in len(cp866_proto): + th_data[28 + i] = cp866_proto[i] - func set_interfer(data): - var mode = data.decode_u8(9) - var id = data.decode_u16(10) - var aoa = float(data.decode_u16(12)) / 10 - var ela = float(data.decode_u16(14)) / 10 - var freq = data.decode_u16(16) - var width = data.decode_u16(18) - var interfer_params: Array = [mode, id, aoa, ela, freq, width] - signaller.emit_signal('interfer_rcv', interfer_params) + func set_interfer(data): + var mode = data.decode_u8(9) + var id = data.decode_u16(10) + var aoa = float(data.decode_u16(12)) / 10 + var ela = float(data.decode_u16(14)) / 10 + var freq = data.decode_u16(16) + var width = data.decode_u16(18) + var interfer_params: Array = [mode, id, aoa, ela, freq, width] + signaller.emit_signal('interfer_rcv', interfer_params) - func on_interfer_accept(err: bool): - var data_tx = PackedByteArray() - var data_len = 2 # длинна блока данных в байтах - data_tx.resize(data_len) - data_tx.encode_u16(0, err) # Код ошибки - var data_to_send: PackedByteArray = create_header(data_len) - data_to_send.append_array(data_tx) - tx_stack.append(data_to_send) + func on_interfer_accept(err: bool): + var data_tx = PackedByteArray() + var data_len = 2 # длинна блока данных в байтах + data_tx.resize(data_len) + data_tx.encode_u16(0, err) # Код ошибки + var data_to_send: PackedByteArray = create_header(data_len) + data_to_send.append_array(data_tx) + tx_stack.append(data_to_send) diff --git a/scripts/threats.gd b/scripts/threats.gd index e5793da6..2c47cd51 100644 --- a/scripts/threats.gd +++ b/scripts/threats.gd @@ -5,36 +5,36 @@ extends Node ## Цель class Threat: - var alt: float ## Текущая высота, метры. - var aoa: float ## Курсовой угол на источник сигнала, градусы. - var atick: float ## Тик обнаружения. - var baud: float ## Модуляционная скорость. - var fflags: Dictionary ## Флаги достоверности полей. - var freq: float ## Частота несущей или центральная частота, МГц. - var freqm: String ## Режим смены частоты. - var id: int ## Идентификатор цели. - var lat: float ## Текущие координаты, широта в градусах. - var lon: float ## Текущие координаты, долгота в градусах. - var power: float ## Уровень принимаемого сигнала, дБм. - var proto: String ## Тип протокола источника сигнала. - var rkrp: String ## Рекомендуемый режим помехи. - var rparams: String ## Рекомендуемые параметры помехи. - var salt: float ## Координаты старта, высота от уровня моря, м. - var slat: float ## Координаты старта, широта в градусах. - var slon: float ## Координаты старта, долгота в градусах. - var tick: int ## Тик время приёма сигнала, мс. - var tick_rx: int ## Тик время обновления цели, мс. - var tmod: String ## Тип модуляции. - var width: float ## Ширина занимаемого диапазона частот, МГц. - var selected: bool ## Флаг, что цель выбрана. + var alt: float ## Текущая высота, метры. + var aoa: float ## Курсовой угол на источник сигнала, градусы. + var atick: float ## Тик обнаружения. + var baud: float ## Модуляционная скорость. + var fflags: Dictionary ## Флаги достоверности полей. + var freq: float ## Частота несущей или центральная частота, МГц. + var freqm: String ## Режим смены частоты. + var id: int ## Идентификатор цели. + var lat: float ## Текущие координаты, широта в градусах. + var lon: float ## Текущие координаты, долгота в градусах. + var power: float ## Уровень принимаемого сигнала, дБм. + var proto: String ## Тип протокола источника сигнала. + var rkrp: String ## Рекомендуемый режим помехи. + var rparams: String ## Рекомендуемые параметры помехи. + var salt: float ## Координаты старта, высота от уровня моря, м. + var slat: float ## Координаты старта, широта в градусах. + var slon: float ## Координаты старта, долгота в градусах. + var tick: int ## Тик время приёма сигнала, мс. + var tick_rx: int ## Тик время обновления цели, мс. + var tmod: String ## Тип модуляции. + var width: float ## Ширина занимаемого диапазона частот, МГц. + var selected: bool ## Флаг, что цель выбрана. - func _init(): clear_fflags() - func _to_string() -> String: return 'номер: %d пеленг: %0.1f частота: %0.1f МГц' % [id, aoa, freq] - func clear_fflags(): tools.get_members_names(self).any(func(vn): fflags[vn] = false) + func _init(): clear_fflags() + func _to_string() -> String: return 'номер: %d пеленг: %0.1f частота: %0.1f МГц' % [id, aoa, freq] + func clear_fflags(): tools.get_members_names(self).any(func(vn): fflags[vn] = false) - func set_from_dic(dic: Dictionary): - var items: = fflags.keys().filter(func(vn): return dic.has(vn)) - items.any(func(vn): set(vn, dic[vn]); fflags[vn] = true; return false) + func set_from_dic(dic: Dictionary): + var items: = fflags.keys().filter(func(vn): return dic.has(vn)) + items.any(func(vn): set(vn, dic[vn]); fflags[vn] = true; return false) signal threat_new(th: Threat) @@ -44,34 +44,34 @@ signal threats_resized(th: Dictionary) func _ready() -> void: - log.info('Модуль обработки целей готов') - var threats = {} ## Список целей. Индекс - <номер цели> - var unit_key = repsettings.get_unit_key('уарэп-капсрпб-отп') - var unit = repnetwork.units[unit_key] - var unit_5p28_key = repsettings.get_unit_key('уарэп-5п28') - var unit_5p28 = repnetwork.units_tcp[unit_5p28_key] - var timer_check_lost: = Timer.new() - add_child(timer_check_lost) - unit.connect('data_received', Callable(self, 'on_data_capsrpb_received').bind(threats)) - unit_5p28.connect('get_threats', Callable(self, 'on_get_threats').bind(unit_5p28, threats)) - timer_check_lost.connect('timeout', Callable(self, 'on_timer_check_lost').bind(repsettings.ThreatParams.time_lost, threats)) - timer_check_lost.start(repsettings.ThreatParams.time_lost / 1000.0) - signaller.connect('rto_threat_sel', Callable(self, 'on_rto_threat_sel').bind(threats)) - signaller.connect('rto_threat_unsel', Callable(self, 'on_rto_threat_unsel').bind(threats)) - signaller.connect('rto_threat_unsel_all', Callable(self, 'on_rto_threat_unsel_all').bind(threats)) - signaller.connect('threat_selected', Callable(self, 'on_threat_selected').bind(threats)) - signaller.connect('interfer_init', Callable(self, 'on_interfer_init').bind(threats)) + log.info('Модуль обработки целей готов') + var threats = {} ## Список целей. Индекс - <номер цели> + var unit_key = repsettings.get_unit_key('уарэп-капсрпб-отп') + var unit = repnetwork.units[unit_key] + var unit_5p28_key = repsettings.get_unit_key('уарэп-5п28') + var unit_5p28 = repnetwork.units_tcp[unit_5p28_key] + var timer_check_lost: = Timer.new() + add_child(timer_check_lost) + unit.connect('data_received', Callable(self, 'on_data_capsrpb_received').bind(threats)) + unit_5p28.connect('get_threats', Callable(self, 'on_get_threats').bind(unit_5p28, threats)) + timer_check_lost.connect('timeout', Callable(self, 'on_timer_check_lost').bind(repsettings.ThreatParams.time_lost, threats)) + timer_check_lost.start(repsettings.ThreatParams.time_lost / 1000.0) + signaller.connect('rto_threat_sel', Callable(self, 'on_rto_threat_sel').bind(threats)) + signaller.connect('rto_threat_unsel', Callable(self, 'on_rto_threat_unsel').bind(threats)) + signaller.connect('rto_threat_unsel_all', Callable(self, 'on_rto_threat_unsel_all').bind(threats)) + signaller.connect('threat_selected', Callable(self, 'on_threat_selected').bind(threats)) + signaller.connect('interfer_init', Callable(self, 'on_interfer_init').bind(threats)) ## Периодически удаляет устаревшие цели. func on_timer_check_lost(time_lost: int, threats: Dictionary): - var tick = Time.get_ticks_msec() - var sz: = threats.size() - for th in threats.values(): - if (tick - th.tick_rx) >= time_lost: - threats.erase(th.id) - call_deferred('emit_signal', 'threat_lost', th) - if sz != threats.size(): - call_deferred('emit_signal', 'threats_resized', threats) + var tick = Time.get_ticks_msec() + var sz: = threats.size() + for th in threats.values(): + if (tick - th.tick_rx) >= time_lost: + threats.erase(th.id) + call_deferred('emit_signal', 'threat_lost', th) + if sz != threats.size(): + call_deferred('emit_signal', 'threats_resized', threats) ## отправляет словарь целей в юнит, для запаковки и отправки в 5П28. @@ -84,8 +84,8 @@ func on_rto_threat_unsel(th_id, threats): threats[th_id].selected = false ## Снимает выделение со всех целей. func on_rto_threat_unsel_all(threats): - for th in threats.values(): - signaller.emit_signal('rto_threat_unsel', th.id) + for th in threats.values(): + signaller.emit_signal('rto_threat_unsel', th.id) ## Выбирает цель, если нажать по отметке цели. @@ -94,44 +94,44 @@ func on_rto_threat_sel(th_id, threats): on_threat_selected(threats[th_id], threa ## Выбирает цель, если выбрать в таблице. func on_threat_selected(th, threats): - for threat in threats.values(): - threat.selected = false - th.selected = true + for threat in threats.values(): + threat.selected = false + th.selected = true ## Формирует список выбранных целей для включения помехи. func on_interfer_init(btn, fs_arr, threats): - var sel_threats: Array = [] - for threat in threats.values(): - if threat.selected: - sel_threats.append(threat) - if len(sel_threats) or (len(fs_arr)): - signaller.emit_signal('interfer_create', btn, sel_threats, fs_arr) + var sel_threats: Array = [] + for threat in threats.values(): + if threat.selected: + sel_threats.append(threat) + if len(sel_threats) or (len(fs_arr)): + signaller.emit_signal('interfer_create', btn, sel_threats, fs_arr) ## Производит приём целей. func on_data_capsrpb_received(unit: capsrpb.CapsRpb, threats: Dictionary): - if not unit.json_dic.has('ts'): return - if unit.json_dic['ts'] != 5: return - if not unit.json_dic.has('mis'): return + if not unit.json_dic.has('ts'): return + if unit.json_dic['ts'] != 5: return + if not unit.json_dic.has('mis'): return - var mis = unit.json_dic['mis'] - var sz: = threats.size() - for dic_th in mis: - if dic_th is Dictionary: - if dic_th.has('id'): - var id = int(dic_th['id']) - if threats.has(id): - var th = threats[id] - th.clear_fflags() - th.set_from_dic(dic_th) - th.tick_rx = unit.tick - call_deferred('emit_signal', 'threat_update', th) - else: - var th = Threat.new() - th.set_from_dic(dic_th) - threats[id] = th - th.tick_rx = unit.tick - call_deferred('emit_signal', 'threat_new', th) - if sz != threats.size(): - call_deferred('emit_signal', 'threats_resized', threats) + var mis = unit.json_dic['mis'] + var sz: = threats.size() + for dic_th in mis: + if dic_th is Dictionary: + if dic_th.has('id'): + var id = int(dic_th['id']) + if threats.has(id): + var th = threats[id] + th.clear_fflags() + th.set_from_dic(dic_th) + th.tick_rx = unit.tick + call_deferred('emit_signal', 'threat_update', th) + else: + var th = Threat.new() + th.set_from_dic(dic_th) + threats[id] = th + th.tick_rx = unit.tick + call_deferred('emit_signal', 'threat_new', th) + if sz != threats.size(): + call_deferred('emit_signal', 'threats_resized', threats) diff --git a/scripts/tools.gd b/scripts/tools.gd index d253490e..d19a303a 100644 --- a/scripts/tools.gd +++ b/scripts/tools.gd @@ -30,14 +30,14 @@ func is_overlap(x1, x2, y1, y2) -> bool: return max(x1, y1) <= min(x2, y2) func _check_index(obj, index) -> bool: - if obj is Dictionary: - if obj.has(index): - return true - elif obj is Array: - if index is int: - if (index >= 0) and (index < obj.size()): - return true - return false + if obj is Dictionary: + if obj.has(index): + return true + elif obj is Array: + if index is int: + if (index >= 0) and (index < obj.size()): + return true + return false ## Возвращает значение поля хранящегося по указанному пути внутри объекта.[br] @@ -48,15 +48,15 @@ func _check_index(obj, index) -> bool: ## [param ret_val[1]] - устанвлено в [b]true[/b], если поле найдено, и установлено в [b]false[/b], ## если искомое поле не найдено. func get_value_by_path(obj, paths: Array, ret_val: Array): - var index = paths.pop_at(0) - ret_val[1] = false - if paths.size(): - if _check_index(obj, index): - get_value_by_path(obj[index], paths, ret_val) - else: - if _check_index(obj, index): - ret_val[0] = obj[index] - ret_val[1] = true + var index = paths.pop_at(0) + ret_val[1] = false + if paths.size(): + if _check_index(obj, index): + get_value_by_path(obj[index], paths, ret_val) + else: + if _check_index(obj, index): + ret_val[0] = obj[index] + ret_val[1] = true ## Возвращает часть от входной строки ограниченную индексами символов.[br] @@ -65,75 +65,75 @@ func get_value_by_path(obj, paths: Array, ret_val: Array): ## [param i0] - Индекс начала части строки.[br] ## [param i1] - Индекс конца части строки. Возвращает все символы до этого индекса. func get_string_slice(s_in: String, s_out: Array, i0: int, i1: int): - for i in range(i0, i1): - if s_in[i] != '\'': - s_out[0] += s_in[i] + for i in range(i0, i1): + if s_in[i] != '\'': + s_out[0] += s_in[i] ## Производит преобразование из декартовой в полярную систему координат. func pos_calc(freq: float, aoa: float, pos0: Vector2, r0: float, r1: float, f0: float, f1: float): - var r = ((freq - f0) / (f1 - f0)) * r1 + r0 - var aoa_rad = radians(aoa) - PI / 2.0 - return Vector2(cos(aoa_rad), sin(aoa_rad)) * r + pos0 + var r = ((freq - f0) / (f1 - f0)) * r1 + r0 + var aoa_rad = radians(aoa) - PI / 2.0 + return Vector2(cos(aoa_rad), sin(aoa_rad)) * r + pos0 ## Отсекает значение до заданного диапазона. func clip_value(val, val_min, val_max): - if val < val_min: val = val_min - if val > val_max: val = val_max - return val + if val < val_min: val = val_min + if val > val_max: val = val_max + return val ## Возвращает массив с именами членов [param obj]. func get_members_names(obj: Object) -> Array: - var pl = obj.get_property_list() - var names: = Array() - for item in pl: names.append(item.name) - return names + var pl = obj.get_property_list() + var names: = Array() + for item in pl: names.append(item.name) + return names ## Проверяет, что заданный массив [param val] похож на ip-адрес. func is_array_ipaddr(val: Array): - return (val.size() == 2) \ - and (val[0] is String) \ - and (val[1] is int) \ - and (0 < val[1]) \ - and (val[1] < 65536) + return (val.size() == 2) \ + and (val[0] is String) \ + and (val[1] is int) \ + and (0 < val[1]) \ + and (val[1] < 65536) ## Преобразует декартовые координаты в полярные.[br] ## [param pos0] - координаты центра.[br] ## [param pos] - координаты точки.[br] func map_dec_to_rad(pos0: Vector2, pos: Vector2) -> Vector2: - var x1: float = pos.x - pos0.x - var y1: float = pos.y - pos0.y - var ang: float = (atan2(y1, x1) * 180.0) / PI - return Vector2(ang + 360.0 * float(ang < 0.0), hypot(x1, y1)) + var x1: float = pos.x - pos0.x + var y1: float = pos.y - pos0.y + var ang: float = (atan2(y1, x1) * 180.0) / PI + return Vector2(ang + 360.0 * float(ang < 0.0), hypot(x1, y1)) ## Переводит значение val из диапазона a0...a1 в диапазон b0...b1. func map_range(val: float, a0: float, a1: float, b0: float, b1: float) -> float: - return (((val - a0) * (b1 - b0)) / (a1 - a0)) + b0 + return (((val - a0) * (b1 - b0)) / (a1 - a0)) + b0 ## Возвращает [b]true[/b], если точка внутри заданного полигона.[br] ## [param pos] - Проверяемая точка.[br] ## [param p] - Координаты вершин полигона.[br] func is_point_in_polygon(pos: Vector2, p: PackedVector2Array) -> bool: - var sz: = p.size() - assert(sz) - var j: int = sz - 1 - var odd_nodes: int = 0 - for i in sz: - if ((p[i].y < pos.y and p[j].y >= pos.y or p[j].y < pos.y and p[i].y >= pos.y) and (p[i].x <= pos.x or p[j].x <= pos.x)): - odd_nodes ^= int((p[i].x + (pos.y - p[i].y) / (p[j].y - p[i].y) * (p[j].x - p[i].x) < pos.x)) - j = i - return odd_nodes > 0 + var sz: = p.size() + assert(sz) + var j: int = sz - 1 + var odd_nodes: int = 0 + for i in sz: + if ((p[i].y < pos.y and p[j].y >= pos.y or p[j].y < pos.y and p[i].y >= pos.y) and (p[i].x <= pos.x or p[j].x <= pos.x)): + odd_nodes ^= int((p[i].x + (pos.y - p[i].y) / (p[j].y - p[i].y) * (p[j].x - p[i].x) < pos.x)) + j = i + return odd_nodes > 0 ## Записывает слово [param val] в элемент байтовго массива [param buf]. ## расположенный по индексу [param index] (младшее слово).[br] ## [i]Следует использовать встроенные в[/i] [b]PackedByteArray[/b] [i]методы вместо этой функции.[/i] func set_wordi(buf: PackedByteArray, index: int, val: int) -> void: - buf[index] = val & 0xff - buf[index + 1] = (val >> 8) & 0xff + buf[index] = val & 0xff + buf[index + 1] = (val >> 8) & 0xff diff --git a/scripts/yau07.gd b/scripts/yau07.gd index f5a43fa5..b4b6e2f4 100644 --- a/scripts/yau07.gd +++ b/scripts/yau07.gd @@ -11,239 +11,239 @@ const BUFFER_SIZE = 2048 ## Размер буфера для приёма д ## Ячейка ЯУ-07. class YaU07: - ## Коды выполняемых команд. - enum CmdCode { - READ_ISA = 0, ## Код команды "читать порт ISA" ячейки ЯУ-07. - WRITE_ISA = 1, ## Код команды "записать порт ISA" ячейки ЯУ-07. - WRITE_BLOCK = 60, ## Код команды "записать блок данных ISA" внешней шины ячейки ЯУ-07. - READ_BLOCK = 61, ## Код команды "читать блок данных ISA" внешней шины ячейки ЯУ-07. - } + ## Коды выполняемых команд. + enum CmdCode { + READ_ISA = 0, ## Код команды "читать порт ISA" ячейки ЯУ-07. + WRITE_ISA = 1, ## Код команды "записать порт ISA" ячейки ЯУ-07. + WRITE_BLOCK = 60, ## Код команды "записать блок данных ISA" внешней шины ячейки ЯУ-07. + READ_BLOCK = 61, ## Код команды "читать блок данных ISA" внешней шины ячейки ЯУ-07. + } - ## Состояние автомата выполнения команды. - enum CmdState { - UNCK, ## Номер команды не ининциирован. - SEND, ## Буфер готов для отправки. - WAIT, ## Ожидание выполнения. - DONE, ## Команда выполнена. - FAIL ## Команда не выполнена. - } + ## Состояние автомата выполнения команды. + enum CmdState { + UNCK, ## Номер команды не ининциирован. + SEND, ## Буфер готов для отправки. + WAIT, ## Ожидание выполнения. + DONE, ## Команда выполнена. + FAIL ## Команда не выполнена. + } - signal line_changed(unit: YaU07) ## Вызывается когда меняется состояние связи с ячейкой. - signal command_done(unit: YaU07) ## Вызывается после выполнения команды. - signal command_fail(unit: YaU07) ## Вызывается если возникла ошибка при выполнении команды и команда не выполнена. - signal parse_failed(unit: YaU07) + signal line_changed(unit: YaU07) ## Вызывается когда меняется состояние связи с ячейкой. + signal command_done(unit: YaU07) ## Вызывается после выполнения команды. + signal command_fail(unit: YaU07) ## Вызывается если возникла ошибка при выполнении команды и команда не выполнена. + signal parse_failed(unit: YaU07) - var name: = String() - var cmd_num_rx: = 0 ## Номер команды принятый. - var cmd_num_tx: = 0 ## Номер команды переданный. - var cmd_state = CmdState.UNCK ## Состояние выполнения команды. - var cmd_result: = 1 ## Результат выполнения команды. Устанавливается в ЯУ-07. - var cmd_data: = PackedByteArray() ## Данные команды. Устанавливается в ЯУ-07. - var cmd_retry = 0 ## Счётчик оставшихся попыток выполнить команду. - var tick = 0.0 ## Текущее тик-время в мс. - var cmd_tick_tx = 0.0 ## Время последней отправки команды. - var cmd_tick_rx = 0.0 ## Время последнего приёма команды. - var status: = PackedByteArray() ## Поле "состояние прибора". - var tx_data: = PackedByteArray() ## Буфер отправленный. - var tx_len = 0 ## Длина последнего отправленного пакета. - var isa_ports = {} ## Результат выполнения команды "читать порт". - var isa_block = {} ## Результат выполнения команды "читать массив из порта". - var online = false ## Состояние связи с ячейкой. + var name: = String() + var cmd_num_rx: = 0 ## Номер команды принятый. + var cmd_num_tx: = 0 ## Номер команды переданный. + var cmd_state = CmdState.UNCK ## Состояние выполнения команды. + var cmd_result: = 1 ## Результат выполнения команды. Устанавливается в ЯУ-07. + var cmd_data: = PackedByteArray() ## Данные команды. Устанавливается в ЯУ-07. + var cmd_retry = 0 ## Счётчик оставшихся попыток выполнить команду. + var tick = 0.0 ## Текущее тик-время в мс. + var cmd_tick_tx = 0.0 ## Время последней отправки команды. + var cmd_tick_rx = 0.0 ## Время последнего приёма команды. + var status: = PackedByteArray() ## Поле "состояние прибора". + var tx_data: = PackedByteArray() ## Буфер отправленный. + var tx_len = 0 ## Длина последнего отправленного пакета. + var isa_ports = {} ## Результат выполнения команды "читать порт". + var isa_block = {} ## Результат выполнения команды "читать массив из порта". + var online = false ## Состояние связи с ячейкой. - func _to_string() -> String: return String('яу07: %s %s' % [self.name, ['отключен', 'на связи'][int(online)]]) + func _to_string() -> String: return String('яу07: %s %s' % [self.name, ['отключен', 'на связи'][int(online)]]) - func _init(nm): - self.name = nm - tx_data.resize(BUFFER_SIZE) - tx_data.fill(0) - tick = 0.0 + func _init(nm): + self.name = nm + tx_data.resize(BUFFER_SIZE) + tx_data.fill(0) + tick = 0.0 - func process(delta: float): - tick += delta - if online and ((tick - cmd_tick_rx) > ONLINE_TIMEOUT): - online = false - emit_signal('line_changed', self) + func process(delta: float): + tick += delta + if online and ((tick - cmd_tick_rx) > ONLINE_TIMEOUT): + online = false + emit_signal('line_changed', self) - var rc = Error.ERR_BUSY - if cmd_state == CmdState.SEND: - rc = Error.OK - cmd_tick_tx = tick - cmd_state = CmdState.WAIT - cmd_retry = RETRY_COUNT - elif cmd_state == CmdState.WAIT: - if cmd_retry: - if (tick - cmd_tick_tx) > RESEND_TIMEOUT: - rc = Error.OK - cmd_tick_tx = tick - cmd_retry -= 1 - else: - cmd_state = CmdState.FAIL - emit_signal('command_fail', self) - rc = Error.FAILED - return rc + var rc = Error.ERR_BUSY + if cmd_state == CmdState.SEND: + rc = Error.OK + cmd_tick_tx = tick + cmd_state = CmdState.WAIT + cmd_retry = RETRY_COUNT + elif cmd_state == CmdState.WAIT: + if cmd_retry: + if (tick - cmd_tick_tx) > RESEND_TIMEOUT: + rc = Error.OK + cmd_tick_tx = tick + cmd_retry -= 1 + else: + cmd_state = CmdState.FAIL + emit_signal('command_fail', self) + rc = Error.FAILED + return rc - func send_isa(cmd_code: int, ports_data: Array) -> bool: - if (cmd_state == CmdState.WAIT) \ - or (cmd_state == CmdState.SEND) \ - or (cmd_state == CmdState.UNCK): - return false - if not ((cmd_code == CmdCode.READ_ISA) or (cmd_code == CmdCode.WRITE_ISA)): - return false + func send_isa(cmd_code: int, ports_data: Array) -> bool: + if (cmd_state == CmdState.WAIT) \ + or (cmd_state == CmdState.SEND) \ + or (cmd_state == CmdState.UNCK): + return false + if not ((cmd_code == CmdCode.READ_ISA) or (cmd_code == CmdCode.WRITE_ISA)): + return false - cmd_num_tx = (cmd_num_rx + 1) % 0xffff + cmd_num_tx = (cmd_num_rx + 1) % 0xffff - var ports_count = ports_data.size() - tx_len = ports_count * 2 + 8 - tx_data.encode_u16(0, cmd_retry) # Исправление для сетевого стека "nm-callback" процессоров STM - tx_data.encode_u16(2, cmd_num_tx) + var ports_count = ports_data.size() + tx_len = ports_count * 2 + 8 + tx_data.encode_u16(0, cmd_retry) # Исправление для сетевого стека "nm-callback" процессоров STM + tx_data.encode_u16(2, cmd_num_tx) - if cmd_code == CmdCode.WRITE_ISA: - ports_count /= 2 - tx_data[6] = cmd_code - tx_data[7] = ports_count + if cmd_code == CmdCode.WRITE_ISA: + ports_count /= 2 + tx_data[6] = cmd_code + tx_data[7] = ports_count - var i: = 0 - for word in ports_data: - tx_data.encode_u16(8 + i, word) - i += 2 - cmd_state = CmdState.SEND - return true + var i: = 0 + for word in ports_data: + tx_data.encode_u16(8 + i, word) + i += 2 + cmd_state = CmdState.SEND + return true - func send_block(cmd_code: int, block_len: int, port: Array) -> bool: - if (cmd_state == CmdState.WAIT) \ - or (cmd_state == CmdState.SEND) \ - or (cmd_state == CmdState.UNCK): - return false - if not ((cmd_code == CmdCode.READ_BLOCK) or (cmd_code == CmdCode.WRITE_BLOCK)): - return false + func send_block(cmd_code: int, block_len: int, port: Array) -> bool: + if (cmd_state == CmdState.WAIT) \ + or (cmd_state == CmdState.SEND) \ + or (cmd_state == CmdState.UNCK): + return false + if not ((cmd_code == CmdCode.READ_BLOCK) or (cmd_code == CmdCode.WRITE_BLOCK)): + return false - cmd_num_tx = (cmd_num_rx + 1) % 0xffff + cmd_num_tx = (cmd_num_rx + 1) % 0xffff - tx_len = 11 - tx_data.encode_u16(0, cmd_retry) # Исправление для сетевого стека "nm-callback" процессоров STM - tx_data.encode_u16(2, cmd_num_tx) + tx_len = 11 + tx_data.encode_u16(0, cmd_retry) # Исправление для сетевого стека "nm-callback" процессоров STM + tx_data.encode_u16(2, cmd_num_tx) - if cmd_code == CmdCode.WRITE_BLOCK: - block_len /= 2 - tx_data[6] = cmd_code - tx_data.encode_u16(7, block_len) + if cmd_code == CmdCode.WRITE_BLOCK: + block_len /= 2 + tx_data[6] = cmd_code + tx_data.encode_u16(7, block_len) - tx_data.encode_u16(9, int(port[0])) - cmd_state = CmdState.SEND - return true - ## Возвращает код команды ЯУ-07. - ## rx_data - Принятые данные. - ## Возвращает - Код команды не найден, 0...255 - код команды. - func get_cmd_code(rx_data: PackedByteArray): + tx_data.encode_u16(9, int(port[0])) + cmd_state = CmdState.SEND + return true + ## Возвращает код команды ЯУ-07. + ## rx_data - Принятые данные. + ## Возвращает - Код команды не найден, 0...255 - код команды. + func get_cmd_code(rx_data: PackedByteArray): - var rx_data_len = rx_data.size() - if rx_data_len < 7: - return null - var status_len = rx_data.decode_u16(6) # Длина массива байтового "состояние прибора" - if rx_data_len <= (status_len + 6): # Нет поля "ответ на команду", разбор пакета завершить - return null - var cmd_code_pos = status_len + 8 # Положение в приёмном буфере "кода команды" - if rx_data_len <= cmd_code_pos: - return null - return rx_data[cmd_code_pos] # "код команды" + var rx_data_len = rx_data.size() + if rx_data_len < 7: + return null + var status_len = rx_data.decode_u16(6) # Длина массива байтового "состояние прибора" + if rx_data_len <= (status_len + 6): # Нет поля "ответ на команду", разбор пакета завершить + return null + var cmd_code_pos = status_len + 8 # Положение в приёмном буфере "кода команды" + if rx_data_len <= cmd_code_pos: + return null + return rx_data[cmd_code_pos] # "код команды" - func parse_isa(rx_data: PackedByteArray, status_len) -> bool: - var ports_count_pos = status_len + 10 # Положение в приёмном буфере количества портов - if rx_data.size() < ports_count_pos: - return false - var ports_count = rx_data[ports_count_pos] # Количество портов - var counted_len = status_len + (ports_count * 4) + 11 # Вычисленная длина пакета - if rx_data.size() < counted_len: - return false - var addr_pos = status_len + 11 # Смещение положения адреса в приёмном буфере - var data_pos = status_len + 13 # Смещение положения данных в приёмном буфере - var i: = 0 - while i < ports_count: - var shift: = i * 4 - var isa_addr = rx_data.decode_u16(addr_pos + shift) # Адрес порта ISA яу-07 - var isa_data = rx_data.decode_u16(data_pos + shift) # Данные порта ISA яу-07 - isa_ports[isa_addr] = isa_data - i += 1 - cmd_data = rx_data.slice(addr_pos, (addr_pos + ports_count * 4) - 1) - return i > 0 + func parse_isa(rx_data: PackedByteArray, status_len) -> bool: + var ports_count_pos = status_len + 10 # Положение в приёмном буфере количества портов + if rx_data.size() < ports_count_pos: + return false + var ports_count = rx_data[ports_count_pos] # Количество портов + var counted_len = status_len + (ports_count * 4) + 11 # Вычисленная длина пакета + if rx_data.size() < counted_len: + return false + var addr_pos = status_len + 11 # Смещение положения адреса в приёмном буфере + var data_pos = status_len + 13 # Смещение положения данных в приёмном буфере + var i: = 0 + while i < ports_count: + var shift: = i * 4 + var isa_addr = rx_data.decode_u16(addr_pos + shift) # Адрес порта ISA яу-07 + var isa_data = rx_data.decode_u16(data_pos + shift) # Данные порта ISA яу-07 + isa_ports[isa_addr] = isa_data + i += 1 + cmd_data = rx_data.slice(addr_pos, (addr_pos + ports_count * 4) - 1) + return i > 0 - func parse_block(rx_data: PackedByteArray, status_len) -> bool: - var ports_count_pos = status_len + 7 # Положение в приёмном буфере количества портов - if rx_data.size() < ports_count_pos: - return false - var block_len = rx_data.decode_u16(ports_count_pos + 3) - var block_adr = rx_data.decode_u16(ports_count_pos + 5) - var block_data = [] - for i in block_len: - var shift: int = i * 2 - block_data.append(rx_data.decode_u16(ports_count_pos + 7 + shift)) - isa_block[block_adr] = block_data - return true + func parse_block(rx_data: PackedByteArray, status_len) -> bool: + var ports_count_pos = status_len + 7 # Положение в приёмном буфере количества портов + if rx_data.size() < ports_count_pos: + return false + var block_len = rx_data.decode_u16(ports_count_pos + 3) + var block_adr = rx_data.decode_u16(ports_count_pos + 5) + var block_data = [] + for i in block_len: + var shift: int = i * 2 + block_data.append(rx_data.decode_u16(ports_count_pos + 7 + shift)) + isa_block[block_adr] = block_data + return true - ## Заполняет состояние прибора данными прочитанными из портов ЯУ-07. - ## для команд "читать порты" или "записать порты".[br] - ## [param rx_data] - Принятые данные.[br] - ## Возвращает [b]true[/b] - Данные обработаны успешно.[br] - ## Возвращает [b]false[/b] - Данные не обработаны из за ошибки.[br] - func parse(rx_data: PackedByteArray) -> bool: + ## Заполняет состояние прибора данными прочитанными из портов ЯУ-07. + ## для команд "читать порты" или "записать порты".[br] + ## [param rx_data] - Принятые данные.[br] + ## Возвращает [b]true[/b] - Данные обработаны успешно.[br] + ## Возвращает [b]false[/b] - Данные не обработаны из за ошибки.[br] + func parse(rx_data: PackedByteArray) -> bool: - cmd_tick_rx = tick + cmd_tick_rx = tick - if not online: - online = true - emit_signal('line_changed', self) + if not online: + online = true + emit_signal('line_changed', self) - var rx_data_len = rx_data.size() - if rx_data_len < 10: - return false + var rx_data_len = rx_data.size() + if rx_data_len < 10: + return false - cmd_num_rx = rx_data.decode_u16(2) - if cmd_state == CmdState.UNCK: - cmd_state = CmdState.DONE + cmd_num_rx = rx_data.decode_u16(2) + if cmd_state == CmdState.UNCK: + cmd_state = CmdState.DONE - var status_len = rx_data.decode_u16(6) # Длина массива байтового "состояние прибора" - if rx_data_len < (status_len + 6): - return false - var i: = 0 - var len_rx_data: = rx_data.size() - status.resize(status_len) - while i < status_len: - var byte_i = i + 8 - if byte_i < len_rx_data: - status[i] = rx_data[byte_i] # Массив байтовый "состояние прибора" - i += 1 + var status_len = rx_data.decode_u16(6) # Длина массива байтового "состояние прибора" + if rx_data_len < (status_len + 6): + return false + var i: = 0 + var len_rx_data: = rx_data.size() + status.resize(status_len) + while i < status_len: + var byte_i = i + 8 + if byte_i < len_rx_data: + status[i] = rx_data[byte_i] # Массив байтовый "состояние прибора" + i += 1 - if rx_data_len == (status_len + 6): - return true # Нет поля "ответ на команду", разбор пакета завершить + if rx_data_len == (status_len + 6): + return true # Нет поля "ответ на команду", разбор пакета завершить - var cmd_code_pos = status_len + 8 # Положение в приёмном буфере "код команды" - if rx_data_len <= cmd_code_pos: - return false + var cmd_code_pos = status_len + 8 # Положение в приёмном буфере "код команды" + if rx_data_len <= cmd_code_pos: + return false - var cmd_status_pos = status_len + 9 # Положение в приёмном буфере "состояния выполнения команды" - if rx_data_len <= cmd_status_pos: - return false - cmd_result = rx_data[cmd_status_pos] - if cmd_result != 1: # Команда не выполнена - return false + var cmd_status_pos = status_len + 9 # Положение в приёмном буфере "состояния выполнения команды" + if rx_data_len <= cmd_status_pos: + return false + cmd_result = rx_data[cmd_status_pos] + if cmd_result != 1: # Команда не выполнена + return false - var cmd_code: CmdCode = rx_data[cmd_code_pos] as CmdCode # "код команды" - var rc = true - if (cmd_code == CmdCode.READ_ISA) or (cmd_code == CmdCode.WRITE_ISA): - rc = parse_isa(rx_data, status_len) + var cmd_code: CmdCode = rx_data[cmd_code_pos] as CmdCode # "код команды" + var rc = true + if (cmd_code == CmdCode.READ_ISA) or (cmd_code == CmdCode.WRITE_ISA): + rc = parse_isa(rx_data, status_len) - elif (cmd_code == CmdCode.READ_BLOCK) or (cmd_code == CmdCode.WRITE_BLOCK): - rc = parse_block(rx_data, status_len) + elif (cmd_code == CmdCode.READ_BLOCK) or (cmd_code == CmdCode.WRITE_BLOCK): + rc = parse_block(rx_data, status_len) - if rc: - if (cmd_state == CmdState.WAIT) and (cmd_num_rx == cmd_num_tx): - cmd_state = CmdState.DONE - emit_signal('command_done', self) - else: - emit_signal('parse_failed', self) - return true + if rc: + if (cmd_state == CmdState.WAIT) and (cmd_num_rx == cmd_num_tx): + cmd_state = CmdState.DONE + emit_signal('command_done', self) + else: + emit_signal('parse_failed', self) + return true diff --git a/scripts/yems-devs.gd b/scripts/yems-devs.gd index 4049bd5e..c97f201b 100644 --- a/scripts/yems-devs.gd +++ b/scripts/yems-devs.gd @@ -2,36 +2,36 @@ class_name yems_dev extends Node class InDev: - var id: int ## Идентификатор индекс устройства. - var dev_name: String ## Имя подключаемого комплекса. - var connector: String ## Номер разъёма в приборе. - var delay: float ## Задержка фронта импульса. - var offset: float ## Смещение фронта импульса. - var state: int ## Состояние входа. - var freq: int ## Предполагаемая частота на которой работает комплекс. - var ports_data: Dictionary ## Словарь состояния портов устройства. - var tbl_row_id: int ## Индекс устройства в таблице выходов. - var out_dots: Array ## Список выходных устройств в порядке, как в таблице и точки матрицы. - var connect_devs: Dictionary ## Список присоединённых устройств. - var board: String ## Плата в которой находится устройство. + var id: int ## Идентификатор индекс устройства. + var dev_name: String ## Имя подключаемого комплекса. + var connector: String ## Номер разъёма в приборе. + var delay: float ## Задержка фронта импульса. + var offset: float ## Смещение фронта импульса. + var state: int ## Состояние входа. + var freq: int ## Предполагаемая частота на которой работает комплекс. + var ports_data: Dictionary ## Словарь состояния портов устройства. + var tbl_row_id: int ## Индекс устройства в таблице выходов. + var out_dots: Array ## Список выходных устройств в порядке, как в таблице и точки матрицы. + var connect_devs: Dictionary ## Список присоединённых устройств. + var board: String ## Плата в которой находится устройство. - func _init(dev_id : int, params : Array, row_id : int): - id = dev_id - dev_name = String(params[0]) - connector = String(params[1]) - delay = float(params[2]) - offset = float(params[3]) - tbl_row_id = row_id + func _init(dev_id : int, params : Array, row_id : int): + id = dev_id + dev_name = String(params[0]) + connector = String(params[1]) + delay = float(params[2]) + offset = float(params[3]) + tbl_row_id = row_id class OutDev extends InDev: - var mode: String ## Режим работы выхода. + var mode: String ## Режим работы выхода. - func _init(dev_id : int, params : Array, row_id : int): - id = dev_id - dev_name = String(params[0]) - connector = String(params[1]) - delay = float(params[2]) - offset = float(params[3]) - tbl_row_id = row_id - mode = 'Работа' + func _init(dev_id : int, params : Array, row_id : int): + id = dev_id + dev_name = String(params[0]) + connector = String(params[1]) + delay = float(params[2]) + offset = float(params[3]) + tbl_row_id = row_id + mode = 'Работа' diff --git a/table/table.gd b/table/table.gd index cd1dd1fc..9d9363d1 100644 --- a/table/table.gd +++ b/table/table.gd @@ -57,94 +57,94 @@ func defaultselector_color_proc(_i_row: int): return selector_color ## [param scrl] - Прокрутка родитель таблицы.[br] ## [param prnt] - Родитель прокрутки.[br] func pin_header(scrl: ScrollContainer, prnt: Control): - scrl.remove_child(header) - prnt.add_child(header) - var v_separation = get_theme_constant('v_separation') - header.position = scrl.position - Vector2(0, header.size.y + v_separation) - scroll = scrl - scroll_adj = scrl.position + scrl.remove_child(header) + prnt.add_child(header) + var v_separation = get_theme_constant('v_separation') + header.position = scrl.position - Vector2(0, header.size.y + v_separation) + scroll = scrl + scroll_adj = scrl.position ## Перемещает указатель на одну строку вверх func selector_up(): - var i_rows: = [] - get_selected_rows(i_rows) - for i in i_rows: - set_row_selected(i, false) - var rows_count: = get_rows_count() - if not (i_rows.size() and (rows_count > 1)): - return - var i_row = i_rows[0] - i_row -= 1 - if i_row < 0: - i_row = rows_count - 1 - set_row_selected(i_row, true) - var node = get_node2(0, i_row) - if scroll is ScrollContainer: - scroll.ensure_control_visible(node) - emit_signal('selected_changed', i_row) + var i_rows: = [] + get_selected_rows(i_rows) + for i in i_rows: + set_row_selected(i, false) + var rows_count: = get_rows_count() + if not (i_rows.size() and (rows_count > 1)): + return + var i_row = i_rows[0] + i_row -= 1 + if i_row < 0: + i_row = rows_count - 1 + set_row_selected(i_row, true) + var node = get_node2(0, i_row) + if scroll is ScrollContainer: + scroll.ensure_control_visible(node) + emit_signal('selected_changed', i_row) ## Перемещает указатель на одну строку вниз func selector_down(): - var i_rows: = [] - get_selected_rows(i_rows) - for i in i_rows: - set_row_selected(i, false) - var rows_count: = get_rows_count() - if not (i_rows.size() and (rows_count > 1)): - return - var i_row = i_rows[0] - i_row += 1 - i_row %= rows_count - set_row_selected(i_row, true) - var node = get_node2(0, i_row) - if scroll is ScrollContainer: - scroll.ensure_control_visible(node) - emit_signal('selected_changed', i_row) + var i_rows: = [] + get_selected_rows(i_rows) + for i in i_rows: + set_row_selected(i, false) + var rows_count: = get_rows_count() + if not (i_rows.size() and (rows_count > 1)): + return + var i_row = i_rows[0] + i_row += 1 + i_row %= rows_count + set_row_selected(i_row, true) + var node = get_node2(0, i_row) + if scroll is ScrollContainer: + scroll.ensure_control_visible(node) + emit_signal('selected_changed', i_row) ## Делает ряд [param i_row] видимым. func ensure_row_visible(i_row: int): - assert(scroll is ScrollContainer, 'Таблица должна быть дочерней к ScrollContainer') - var key: = get_node_key(NODE_KEY_FORMAT, 0, i_row) - var node = nodes[key] - scroll.ensure_control_visible(node) + assert(scroll is ScrollContainer, 'Таблица должна быть дочерней к ScrollContainer') + var key: = get_node_key(NODE_KEY_FORMAT, 0, i_row) + var node = nodes[key] + scroll.ensure_control_visible(node) ## Устанавливает выравнивание из массива [param alignments] для всех колонок. func set_columns_alignments(alignments: Array): - assert(columns == alignments.size()) - var rows_count: = get_rows_count() - for i_row in rows_count: - for i_col in alignments.size(): - set_node_alligment(i_col, i_row, alignments[i_col]) + assert(columns == alignments.size()) + var rows_count: = get_rows_count() + for i_row in rows_count: + for i_col in alignments.size(): + set_node_alligment(i_col, i_row, alignments[i_col]) ## func load_selector_scene(file_name: String): - selector_type_file = file_name - ScnSelector = load(file_name) + selector_type_file = file_name + ScnSelector = load(file_name) ## func load_node_scene(file_name: String): - node_type_file = file_name - ScnNode = load(file_name) + node_type_file = file_name + ScnNode = load(file_name) ## func _ready(): - ScnSelector = load(selector_type_file) - ScnNode = load(node_type_file) - assert(ScnSelector is PackedScene) - assert(ScnNode is PackedScene) + ScnSelector = load(selector_type_file) + ScnNode = load(node_type_file) + assert(ScnSelector is PackedScene) + assert(ScnNode is PackedScene) ## Выравнивает по нижнему левому углу. Используется для выравнивания по заголовку. func align_to_left_bottompos(hdr: Control, gap: = Vector2(0, 0)): - position.x = hdr.position.x + gap.x - position.y = hdr.position.y + hdr.size.y + gap.y + position.x = hdr.position.x + gap.x + position.y = hdr.position.y + hdr.size.y + gap.y ## Устанавливает пользовательские данные для ячейки.[br] @@ -153,9 +153,9 @@ func align_to_left_bottompos(hdr: Control, gap: = Vector2(0, 0)): ## [param pname] - Имя пользовательских данных.[br] ## [param val] - Значение пользовательских данных.[br] func set_node_meta(i_col: int, i_row: int, pname: String, val) -> void: - var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) - var node = nodes[key] - node.set_meta(pname, val) + var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) + var node = nodes[key] + node.set_meta(pname, val) ## Возвращает пользовательские данные для ячейки.[br] @@ -163,10 +163,10 @@ func set_node_meta(i_col: int, i_row: int, pname: String, val) -> void: ## [param i_row] - Индекс ряда.[br] ## [param pname] - Имя пользовательских данных.[br] func get_node_meta(i_col: int, i_row: int, pname: String): - var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) - var node = nodes[key] - assert_meta(i_col, i_row, pname, node) - return node.get_meta(pname) + var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) + var node = nodes[key] + assert_meta(i_col, i_row, pname, node) + return node.get_meta(pname) ## Устанавливает свойство ячейки.[br] @@ -176,9 +176,9 @@ func get_node_meta(i_col: int, i_row: int, pname: String): ## [param val] - Значение свойства.[br] ## [i]Свойство[/i] [param pname] [i]ячейки должно существовать.[/i] func set_node_property(i_col: int, i_row: int, pname: String, val) -> void: - var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) - var node = nodes[key] - node.set(pname, val) + var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) + var node = nodes[key] + node.set(pname, val) ## Возвращает значение поля ячейки.[br] @@ -186,9 +186,9 @@ func set_node_property(i_col: int, i_row: int, pname: String, val) -> void: ## [param i_col] - Индекс колонки.[br] ## [param i_row] - Индекс ряда. func get_node_property(i_col: int, i_row: int, pname: String): - var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) - var node = nodes[key] - return node.get(pname) + var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) + var node = nodes[key] + return node.get(pname) ## Возвращает ключ для существующей ячейки. Если ячейки с заданными индексами нет, @@ -196,313 +196,313 @@ func get_node_property(i_col: int, i_row: int, pname: String): ## [param i_col] - Индекс колонки.[br] ## [param i_row] - Индекс ряда func get_node_key(format: String, i_col: int, i_row: int) -> String: - var key: String = format % [i_row, i_col] - assert(nodes.has(key), 'ошибка: нет такой ячейки - колонка:%d ряд:%d' % [i_col, i_row]) - return key + var key: String = format % [i_row, i_col] + assert(nodes.has(key), 'ошибка: нет такой ячейки - колонка:%d ряд:%d' % [i_col, i_row]) + return key ## Возвращает массив с индексом ячейки:[br] ## [b][0][/b] - индекс колонки,[br] ## [b][1][/b] - индекс строки. func get_key_indexes(key: String) -> Array: - var tokens: = key.split('_') - assert(tokens.size() == 3, 'ошибка: неверный ключ: \"%s\"' % key) - return [int(tokens[2]), int(tokens[1])] + var tokens: = key.split('_') + assert(tokens.size() == 3, 'ошибка: неверный ключ: \"%s\"' % key) + return [int(tokens[2]), int(tokens[1])] ## func set_columns_min_size(sizes: Array) -> void: - assert((columns == 1) or (columns == sizes.size())) - columns = sizes.size() - var rows_count: = get_rows_count() - columns_min_size.clear() - for sz in sizes: - columns_min_size.append(sz) - for i_row in rows_count: - set_nodes_min_size(i_row, sizes, NODE_KEY_FORMAT) - if nodes.has(HEAD_KEY_FORMAT % [0, 0]): - set_nodes_min_size(0, sizes, HEAD_KEY_FORMAT) + assert((columns == 1) or (columns == sizes.size())) + columns = sizes.size() + var rows_count: = get_rows_count() + columns_min_size.clear() + for sz in sizes: + columns_min_size.append(sz) + for i_row in rows_count: + set_nodes_min_size(i_row, sizes, NODE_KEY_FORMAT) + if nodes.has(HEAD_KEY_FORMAT % [0, 0]): + set_nodes_min_size(0, sizes, HEAD_KEY_FORMAT) ## func set_columns_aligment(alignments: Array) -> void: - var rows_count: = get_rows_count() - for i_row in rows_count: - for i_col in alignments.size(): - set_node_alligment(i_col, i_row, alignments[i_col]) + var rows_count: = get_rows_count() + for i_row in rows_count: + for i_col in alignments.size(): + set_node_alligment(i_col, i_row, alignments[i_col]) ## func _input(event) -> void: - if not is_visible_in_tree(): return - if event is InputEventKey: - if event.pressed and in_focus: - if event.keycode == selector_key_up: - selector_up() - elif event.keycode == selector_key_down: - selector_down() - elif event is InputEventMouseButton: - if not event.pressed: - return - if check_event_pos(event.position, scroll): - if not in_focus: - in_focus = true - emit_signal('focus_entered') - elif not check_event_pos(event.position, scroll): - if in_focus: - in_focus = false - emit_signal('focus_exited') + if not is_visible_in_tree(): return + if event is InputEventKey: + if event.pressed and in_focus: + if event.keycode == selector_key_up: + selector_up() + elif event.keycode == selector_key_down: + selector_down() + elif event is InputEventMouseButton: + if not event.pressed: + return + if check_event_pos(event.position, scroll): + if not in_focus: + in_focus = true + emit_signal('focus_entered') + elif not check_event_pos(event.position, scroll): + if in_focus: + in_focus = false + emit_signal('focus_exited') ## func _init() -> void: - selector_color_proc = Callable(self, 'defaultselector_color_proc') - assert(selector_color_proc.is_valid()) - selector_size_proc = Callable(self, 'default_selector_size_proc') - assert(selector_size_proc.is_valid()) - scroll = self + selector_color_proc = Callable(self, 'defaultselector_color_proc') + assert(selector_color_proc.is_valid()) + selector_size_proc = Callable(self, 'default_selector_size_proc') + assert(selector_size_proc.is_valid()) + scroll = self ## func check_event_pos(event_pos: Vector2, obj: Control): - return \ - (event_pos.x > obj.position.x) \ - and (event_pos.x < (obj.position.x + obj.size.x)) \ - and (event_pos.y > obj.position.y) \ - and (event_pos.y < (obj.position.y + obj.size.y)) + return \ + (event_pos.x > obj.position.x) \ + and (event_pos.x < (obj.position.x + obj.size.x)) \ + and (event_pos.y > obj.position.y) \ + and (event_pos.y < (obj.position.y + obj.size.y)) ## func setselector_color_proc(instance: Object, function: String) -> void: - selector_color_proc = Callable(instance, function) - assert(selector_color_proc.is_valid()) + selector_color_proc = Callable(instance, function) + assert(selector_color_proc.is_valid()) ## func set_selector_size_proc(instance: Object, function: String) -> void: - selector_size_proc = Callable(instance, function) - assert(selector_size_proc.is_valid()) + selector_size_proc = Callable(instance, function) + assert(selector_size_proc.is_valid()) ## [param i_row] - Индекс ряда func default_selector_size_proc(i_row: int, rect: Array) -> void: - get_row_rect(i_row, rect) - rect[0] += position.x - node_gap.x / 2.0 - rect[1] += position.y - node_gap.y / 2.0 - rect[2] += node_gap.x - rect[3] += node_gap.y + get_row_rect(i_row, rect) + rect[0] += position.x - node_gap.x / 2.0 + rect[1] += position.y - node_gap.y / 2.0 + rect[2] += node_gap.x + rect[3] += node_gap.y ## func clear_rows_selected(): - var rows_count: = get_rows_count() - for i_row in rows_count: - set_row_selected(i_row, false) + var rows_count: = get_rows_count() + for i_row in rows_count: + set_row_selected(i_row, false) ## [param i_row] - Индекс ряда. func set_row_selected(i_row: int, selected: bool = true) -> void: - call_deferred('_set_row_selected', i_row, selected) + call_deferred('_set_row_selected', i_row, selected) ## [param i_row] - Индекс ряда. func _set_row_selected(i_row: int, selected: bool = true) -> void: - var selector_key: = SELECTOR_KEY_FORMAT % [i_row, 0] - var node_key: = NODE_KEY_FORMAT % [i_row, 0] - if not nodes.has(node_key): - push_error('ошибка: нет такого ряда: %d' % i_row) - return - if nodes.has(selector_key) and (not selected): - var node = nodes[node_key] - var selector = nodes[selector_key] - node.remove_child(selector) - selector.queue_free() - nodes.erase(selector_key) - elif (not nodes.has(selector_key)) and selected: - var selector = ScnSelector.instantiate() - var rect: = [0, 0, 0, 0] - selector_size_proc.call(i_row, rect) - selector.size.x = rect[2] - selector.size.y = rect[3] - selector.modulate = selector_color_proc.call(i_row) - selector.z_index = z_index + 1 - nodes[selector_key] = selector - var node = nodes[node_key] - node.add_child(selector) + var selector_key: = SELECTOR_KEY_FORMAT % [i_row, 0] + var node_key: = NODE_KEY_FORMAT % [i_row, 0] + if not nodes.has(node_key): + push_error('ошибка: нет такого ряда: %d' % i_row) + return + if nodes.has(selector_key) and (not selected): + var node = nodes[node_key] + var selector = nodes[selector_key] + node.remove_child(selector) + selector.queue_free() + nodes.erase(selector_key) + elif (not nodes.has(selector_key)) and selected: + var selector = ScnSelector.instantiate() + var rect: = [0, 0, 0, 0] + selector_size_proc.call(i_row, rect) + selector.size.x = rect[2] + selector.size.y = rect[3] + selector.modulate = selector_color_proc.call(i_row) + selector.z_index = z_index + 1 + nodes[selector_key] = selector + var node = nodes[node_key] + node.add_child(selector) ## [param i_row] - Индекс ряда. func is_row_selected(i_row: int) -> bool: - var key: = SELECTOR_KEY_FORMAT % [i_row, 0] - return nodes.has(key) + var key: = SELECTOR_KEY_FORMAT % [i_row, 0] + return nodes.has(key) ## [param i_row] - Индексы рядов. func get_selected_rows(i_rows: Array) -> void: - for key in nodes.keys(): - if SLCT_PREFIX in key: - var indexes = get_key_indexes(key) - i_rows.append(indexes[1]) + for key in nodes.keys(): + if SLCT_PREFIX in key: + var indexes = get_key_indexes(key) + i_rows.append(indexes[1]) ## Возвращает количество рядов func get_rows_count() -> int: - var nodes_count: int = 0 - for key in nodes: - nodes_count += int(NODE_PREFIX in key) - return nodes_count / columns + var nodes_count: int = 0 + for key in nodes: + nodes_count += int(NODE_PREFIX in key) + return nodes_count / columns ## [param i_col] - Индекс колонки. [param i_row] - Индекс ряда. func get_node2(i_col: int, i_row: int) -> Node: - var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) - return nodes[key] + var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) + return nodes[key] ## Добавляет ряд. [param row_types] - Массив типов ячеек ряда func add_row(row_types: Array) -> void: - assert(row_types.size() < MAX_INDEX, 'ошибка: слишком много колонок, больше %d' % (MAX_INDEX + 1)) - if columns == 1: - set_columns(row_types.size()) - else: - assert(columns == row_types.size(), 'ошибка: %s количество колонок установлено ранее - %d, но требуется %d' % [self, columns, row_types.size()]) - var i_row: int = get_rows_count() - assert(i_row < MAX_INDEX, 'ошибка: слишком много рядов, больше %d' % (MAX_INDEX + 1)) - var allow_set_size = columns_min_size.size() >= row_types.size() - for i_col in columns: - var node_type = row_types[i_col] - var node = node_type.instantiate() - assert(node.name == node.name, 'ошибка: экземпляр не имеет поля \"name\"') - var node_key: String = NODE_KEY_FORMAT % [i_row, i_col] - node.name = node_key - add_child(node) - if allow_set_size: - node.custom_minimum_size.x = columns_min_size[i_col] - node.set_deferred('size.x', columns_min_size[i_col]) - nodes[node_key] = node - var conn = Callable(self, '_on_focus_entered').bind(node) - node.connect('focus_entered', conn) - set_node_meta(i_col, i_row, 'callable_focus_entered', conn) + assert(row_types.size() < MAX_INDEX, 'ошибка: слишком много колонок, больше %d' % (MAX_INDEX + 1)) + if columns == 1: + set_columns(row_types.size()) + else: + assert(columns == row_types.size(), 'ошибка: %s количество колонок установлено ранее - %d, но требуется %d' % [self, columns, row_types.size()]) + var i_row: int = get_rows_count() + assert(i_row < MAX_INDEX, 'ошибка: слишком много рядов, больше %d' % (MAX_INDEX + 1)) + var allow_set_size = columns_min_size.size() >= row_types.size() + for i_col in columns: + var node_type = row_types[i_col] + var node = node_type.instantiate() + assert(node.name == node.name, 'ошибка: экземпляр не имеет поля \"name\"') + var node_key: String = NODE_KEY_FORMAT % [i_row, i_col] + node.name = node_key + add_child(node) + if allow_set_size: + node.custom_minimum_size.x = columns_min_size[i_col] + node.set_deferred('size.x', columns_min_size[i_col]) + nodes[node_key] = node + var conn = Callable(self, '_on_focus_entered').bind(node) + node.connect('focus_entered', conn) + set_node_meta(i_col, i_row, 'callable_focus_entered', conn) ## func _on_focus_entered(node): - var indexes = get_key_indexes(node.name) - emit_signal('row_pressed', indexes[1]) + var indexes = get_key_indexes(node.name) + emit_signal('row_pressed', indexes[1]) ## func set_header(header_types: Array): - assert(header_types.size() < MAX_INDEX, 'ошибка: количество колонок в заголовке: %d больше допустимого: %d' % [header_types.size(), MAX_INDEX + 1]) - if columns == 1: - set_columns(header_types.size()) - else: - assert(columns == header_types.size(), 'ошибка: количество колонок в заголовке %d, но ранее установлено: %d' % [header_types.size(), columns]) - var do_adj: = false - if not header: - header = GridContainer.new() - var parent = scroll.get_parent() - parent.add_child(header) - header.position = position - header.columns = header_types.size() - do_adj = true - else: - var childrens = header.get_children() - for node in childrens: - childrens.erase(node.name) - childrens.remove_child(node) - node.queue_free() - for i_col in columns: - var node = header_types[i_col].instantiate() - var key: String = HEAD_KEY_FORMAT % [0, i_col] - node.name = key - nodes[key] = node - header.add_child(node) - if do_adj: - var rect = [0, 0, 0, 0] - get_node_rect(0, 0, rect, HEAD_KEY_FORMAT) - position.y += rect[3] + assert(header_types.size() < MAX_INDEX, 'ошибка: количество колонок в заголовке: %d больше допустимого: %d' % [header_types.size(), MAX_INDEX + 1]) + if columns == 1: + set_columns(header_types.size()) + else: + assert(columns == header_types.size(), 'ошибка: количество колонок в заголовке %d, но ранее установлено: %d' % [header_types.size(), columns]) + var do_adj: = false + if not header: + header = GridContainer.new() + var parent = scroll.get_parent() + parent.add_child(header) + header.position = position + header.columns = header_types.size() + do_adj = true + else: + var childrens = header.get_children() + for node in childrens: + childrens.erase(node.name) + childrens.remove_child(node) + node.queue_free() + for i_col in columns: + var node = header_types[i_col].instantiate() + var key: String = HEAD_KEY_FORMAT % [0, i_col] + node.name = key + nodes[key] = node + header.add_child(node) + if do_adj: + var rect = [0, 0, 0, 0] + get_node_rect(0, 0, rect, HEAD_KEY_FORMAT) + position.y += rect[3] ## func set_header_text(headers: Array): - assert(columns == headers.size()) - set_row_text(0, headers, 0, HEAD_KEY_FORMAT) + assert(columns == headers.size()) + set_row_text(0, headers, 0, HEAD_KEY_FORMAT) ## [param i_row] - индекс ряда. func remove_row(i_row: int): - var sel_key: String = SELECTOR_KEY_FORMAT % [i_row, 0] + var sel_key: String = SELECTOR_KEY_FORMAT % [i_row, 0] - if nodes.has(sel_key): - var selector = nodes[sel_key] - var prnt = selector.get_parent() - prnt.remove_child(selector) - selector.queue_free() - nodes.erase(sel_key) + if nodes.has(sel_key): + var selector = nodes[sel_key] + var prnt = selector.get_parent() + prnt.remove_child(selector) + selector.queue_free() + nodes.erase(sel_key) - for i_col in columns: + for i_col in columns: - var node_key: String = NODE_KEY_FORMAT % [i_row, i_col] - var node = nodes[node_key] + var node_key: String = NODE_KEY_FORMAT % [i_row, i_col] + var node = nodes[node_key] - var conn = get_node_meta(i_col, i_row, 'callable_focus_entered') - node.disconnect('focus_entered', conn) + var conn = get_node_meta(i_col, i_row, 'callable_focus_entered') + node.disconnect('focus_entered', conn) - if node.has_meta(META_CALLABLE): - conn = node.get_meta(META_CALLABLE) - var event_name = node.get_meta(META_EVENT_NAME) - node.disconnect(event_name, conn) + if node.has_meta(META_CALLABLE): + conn = node.get_meta(META_CALLABLE) + var event_name = node.get_meta(META_EVENT_NAME) + node.disconnect(event_name, conn) - remove_child(node) - nodes.erase(node_key) - node.queue_free() - rehash() + remove_child(node) + nodes.erase(node_key) + node.queue_free() + rehash() ## func next_index(indexes: Array) -> void: - indexes[0].x += 1 - if indexes[0].x == columns: - indexes[0].x = 0 - indexes[0].y += 1 + indexes[0].x += 1 + if indexes[0].x == columns: + indexes[0].x = 0 + indexes[0].y += 1 ## func swap_nodes(nodes_copy, node_key_format, i0, i1) -> void: - var key0 = node_key_format % [i0.y, i0.x] - var key1 = node_key_format % [i1.y, i1.x] - nodes_copy[key1] = nodes[key0] - nodes_copy[key1].name = key1 + var key0 = node_key_format % [i0.y, i0.x] + var key1 = node_key_format % [i1.y, i1.x] + nodes_copy[key1] = nodes[key0] + nodes_copy[key1].name = key1 ## Переназначает ключи в соответствии с размерами таблицы. func rehash(): - var i_node: = [Vector2i()] - var i_hdr: = [Vector2i()] - var i_slct: = [Vector2i()] - var nodes_copy = {} - for key in nodes: - var iv = get_key_indexes(key) - var i0 = Vector2(iv[0], iv[1]) - if HEAD_PREFIX in key: - swap_nodes(nodes_copy, HEAD_KEY_FORMAT, i0, i_hdr[0]) - next_index(i_hdr) - elif NODE_PREFIX in key: - swap_nodes(nodes_copy, NODE_KEY_FORMAT, i0, i_node[0]) - next_index(i_node) - elif SLCT_PREFIX in key: - swap_nodes(nodes_copy, SELECTOR_KEY_FORMAT, i0, i_slct[0]) - next_index(i_slct) - nodes.clear() - nodes.merge(nodes_copy) + var i_node: = [Vector2i()] + var i_hdr: = [Vector2i()] + var i_slct: = [Vector2i()] + var nodes_copy = {} + for key in nodes: + var iv = get_key_indexes(key) + var i0 = Vector2(iv[0], iv[1]) + if HEAD_PREFIX in key: + swap_nodes(nodes_copy, HEAD_KEY_FORMAT, i0, i_hdr[0]) + next_index(i_hdr) + elif NODE_PREFIX in key: + swap_nodes(nodes_copy, NODE_KEY_FORMAT, i0, i_node[0]) + next_index(i_node) + elif SLCT_PREFIX in key: + swap_nodes(nodes_copy, SELECTOR_KEY_FORMAT, i0, i_slct[0]) + next_index(i_slct) + nodes.clear() + nodes.merge(nodes_copy) ## Удаляет все строки таблицы func clear_rows() -> void: - var rows_count: int = get_rows_count() - for i_row in range(rows_count - 1, -1, -1): - remove_row(i_row) + var rows_count: int = get_rows_count() + for i_row in range(rows_count - 1, -1, -1): + remove_row(i_row) ## Настраевает обратные вызовы для колонок.[br] @@ -512,57 +512,57 @@ func clear_rows() -> void: ## [param clmns] - Индексы колонок[br] ## [param user_data] - Аргументы функции получателя события[br] func connect_columns(dest_node: Node, event_name: String, callback: String, clmns: Array, user_data: Array = []) -> void: - var rows_count = get_rows_count() - for i_row in rows_count: - for i_col in clmns: - assert(i_col >= 0 and i_col < columns, 'ошибка: нет такой колонки: %d' % i_col) - var node = get_node2(i_col, i_row) - if node.has_meta(META_CALLABLE): - var conn_prev = node.get_meta(META_CALLABLE) - node.disconnect(event_name, conn_prev) - var conn = Callable(dest_node, callback).bind([node] + user_data) - var rc = node.connect(event_name, conn) - if rc == Error.OK: - node.set_meta(META_CALLABLE, conn) - node.set_meta(META_EVENT_NAME, event_name) - else: - var es = error_string(rc) - push_error('ошибка: событие \"%s\" не может быть подключено к %s: %s' % [event_name, node, es]) + var rows_count = get_rows_count() + for i_row in rows_count: + for i_col in clmns: + assert(i_col >= 0 and i_col < columns, 'ошибка: нет такой колонки: %d' % i_col) + var node = get_node2(i_col, i_row) + if node.has_meta(META_CALLABLE): + var conn_prev = node.get_meta(META_CALLABLE) + node.disconnect(event_name, conn_prev) + var conn = Callable(dest_node, callback).bind([node] + user_data) + var rc = node.connect(event_name, conn) + if rc == Error.OK: + node.set_meta(META_CALLABLE, conn) + node.set_meta(META_EVENT_NAME, event_name) + else: + var es = error_string(rc) + push_error('ошибка: событие \"%s\" не может быть подключено к %s: %s' % [event_name, node, es]) ## func set_rows_text(rows: Array) -> void: - for i_row in rows.size(): - for i_col in columns: - var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) - var node = nodes[key] - node.text = rows[i_row][i_col] + for i_row in rows.size(): + for i_col in columns: + var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) + var node = nodes[key] + node.text = rows[i_row][i_col] ## [param i_row] - Индекс ряда. func set_nodes_min_size(i_row: int, sizes: Array, key_format: String = NODE_KEY_FORMAT) -> void: - columns_min_size.clear() - for sz in sizes: - columns_min_size.append(sz) - for i_col in columns: - var key: = get_node_key(key_format, i_col, i_row) - var node = nodes[key] - node.custom_minimum_size.x = sizes[i_col] - node.set_deferred('size.x', sizes[i_col]) + columns_min_size.clear() + for sz in sizes: + columns_min_size.append(sz) + for i_col in columns: + var key: = get_node_key(key_format, i_col, i_row) + var node = nodes[key] + node.custom_minimum_size.x = sizes[i_col] + node.set_deferred('size.x', sizes[i_col]) ## [param i_col] - Индекс колонки.[br][param i_row] - Индекс ряда. func set_node_text(i_col: int, i_row: int, val: String) -> void: - var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) - nodes[key].text = val + var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) + nodes[key].text = val ## func set_rows_state(rows: Array) -> void: - for i_row in rows.size(): - for i_col in columns: - var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) - nodes[key].button_pressed = rows[i_row][i_col] + for i_row in rows.size(): + for i_col in columns: + var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) + nodes[key].button_pressed = rows[i_row][i_col] ## Устанавливает состояние ячейки. @@ -571,59 +571,59 @@ func set_rows_state(rows: Array) -> void: ## [param val] - Новое состояние.[br] ## [i]Ячейка должна иметь свойство[/i] [param button_pressed] func set_node_state(i_col: int, i_row: int, val: bool) -> void: - var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) - nodes[key].button_pressed = val + var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) + nodes[key].button_pressed = val ## [param i_col] - Индекс колонки.[br] ## [param i_row] - Индекс ряда. func set_node_frame(i_col: int, i_row: int, node_name: String, val: int) -> void: - var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) - var node = nodes[key] - var sprite = node.get_node(node_name) - sprite.frame = val + var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) + var node = nodes[key] + var sprite = node.get_node(node_name) + sprite.frame = val ## Разрешает редактирование ячейки. Ячейка должна иметь свойство [param editable].[br] ## [param i_col] - Индекс колонки.[br] ## [param i_row] - Индекс ряда. func set_node_editable(i_col: int, i_row: int, val: bool) -> void: - var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) - nodes[key].editable = val + var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) + nodes[key].editable = val ## Разрешает редактирование ряда. Ячейки ряда должны иметь свойство [param editable].[br] ## [param i_row] - Индекс ряда. func set_row_editable(i_row: int, val: bool) -> void: - for i_col in columns: - var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) - nodes[key].editable = val + for i_col in columns: + var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) + nodes[key].editable = val ## [param i_col] - Индекс колонки.[br] ## [param i_row] - Индекс ряда. func set_node_alligment(i_col: int, i_row: int, val: int) -> void: - var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) - if 'horizontal_alignment' in nodes[key]: - nodes[key].horizontal_alignment = val - elif 'alignment' in nodes[key]: - nodes[key].alignment = val + var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) + if 'horizontal_alignment' in nodes[key]: + nodes[key].horizontal_alignment = val + elif 'alignment' in nodes[key]: + nodes[key].alignment = val ## [param i_row] - Индекс ряда. func set_row_alligments(i_row: int, aligns: Array) -> void: - for i_col in columns: - var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) - nodes[key].align = aligns[i_col] + for i_col in columns: + var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) + nodes[key].align = aligns[i_col] ## [param i_row] - Индекс ряда. func get_row_text(i_row: int) -> Array: - var row: = [] - for i_col in columns: - var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) - row.append(nodes[key].text) - return row + var row: = [] + for i_col in columns: + var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) + row.append(nodes[key].text) + return row ## Устанавливает текст ячеек ряда.[br] @@ -632,51 +632,51 @@ func get_row_text(i_row: int) -> Array: ## [param i_begin] - Начальный индекс колонки начиная с которого установить новые значения.[br] ## [i]Ячейки ряда должны иметь свойство[/i] [param text] func set_row_text(i_row: int, row: Array, i_begin: int = 0, key_format: String = NODE_KEY_FORMAT) -> void: - assert((i_begin >= 0) and (i_begin < columns)) - assert((columns - i_begin) >= row.size()) - for i_col in row.size(): - var key: = get_node_key(key_format, i_col + i_begin, i_row) - nodes[key].text = row[i_col] + assert((i_begin >= 0) and (i_begin < columns)) + assert((columns - i_begin) >= row.size()) + for i_col in row.size(): + var key: = get_node_key(key_format, i_col + i_begin, i_row) + nodes[key].text = row[i_col] ## Возвращает все строки таблицы в двумерном массиве func get_rows_text() -> Array: - var rows_count: = get_rows_count() - var rows: = [] - for i_row in rows_count: - var row = get_row_text(i_row) - rows.append(row) - return rows + var rows_count: = get_rows_count() + var rows: = [] + for i_row in rows_count: + var row = get_row_text(i_row) + rows.append(row) + return rows ## Возвращает массив ячеек из ряда [param i_row] func get_row(i_row: int) -> Array: - var row: = [] - for i_col in columns: - var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) - row.append(nodes[key]) - return row + var row: = [] + for i_col in columns: + var key: = get_node_key(NODE_KEY_FORMAT, i_col, i_row) + row.append(nodes[key]) + return row ## Возвращает прямоугольник [param rect] занимаемый ячейкой в ряду [param i_row] и колонке [param i_col]. ## [param key_format] - Формат ключа ячейки func get_node_rect(i_col: int, i_row: int, rect: Array, key_format: String = NODE_KEY_FORMAT) -> void: - assert(rect.size() >= 4, 'ошибка: мало элементов для возврата результата') - var key: = get_node_key(key_format, i_col, i_row) - var cell = nodes[key] - rect[0] = cell.position.x - rect[1] = cell.position.y - rect[2] = cell.size.x - rect[3] = cell.size.y + assert(rect.size() >= 4, 'ошибка: мало элементов для возврата результата') + var key: = get_node_key(key_format, i_col, i_row) + var cell = nodes[key] + rect[0] = cell.position.x + rect[1] = cell.position.y + rect[2] = cell.size.x + rect[3] = cell.size.y ## Возвращает прямоугольник [param rect] занимаемый рядом [param i_row].[br] ## [param key_format] - Формат ключа ячейки func get_row_rect(i_row: int, rect: Array, key_format: String = NODE_KEY_FORMAT) -> void: - assert(rect.size() >= 4, 'ошибка: мало элементов для возврата результата') - var cell0 = nodes[get_node_key(key_format, 0, i_row)] - var cell1 = nodes[get_node_key(key_format, columns - 1, i_row)] - rect[0] = cell0.position.x + scroll_adj.x - rect[1] = cell0.position.y + scroll_adj.y - rect[2] = (cell1.position.x - cell0.position.x) + cell1.size.x - rect[3] = cell0.size.y + assert(rect.size() >= 4, 'ошибка: мало элементов для возврата результата') + var cell0 = nodes[get_node_key(key_format, 0, i_row)] + var cell1 = nodes[get_node_key(key_format, columns - 1, i_row)] + rect[0] = cell0.position.x + scroll_adj.x + rect[1] = cell0.position.y + scroll_adj.y + rect[2] = (cell1.position.x - cell0.position.x) + cell1.size.x + rect[3] = cell0.size.y From a08b8192a210b6b709fdbe459959b0847d12757c Mon Sep 17 00:00:00 2001 From: sasha80 Date: Wed, 7 Aug 2024 16:36:14 +0300 Subject: [PATCH 12/20] =?UTF-8?q?=D0=A0=D0=B5=D0=B3=D1=80=D0=B5=D1=81?= =?UTF-8?q?=D1=81=D0=B8=D1=8F.=20=D0=9D=D0=B5=D0=B2=D0=B5=D1=80=D0=BD?= =?UTF-8?q?=D1=8B=D0=B9=20=D1=81=D0=B8=D0=BC=D0=B2=D0=BE=D0=BB=20=D0=BE?= =?UTF-8?q?=D1=82=D1=81=D1=82=D1=83=D0=BF=D0=B0,=20=D0=B4=D0=BE=D0=BB?= =?UTF-8?q?=D0=B6=D0=B5=D0=BD=20=D0=B1=D1=8B=D1=82=D1=8C=20"=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D0=B1=D0=B5=D0=BB".?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addons/godot-logger/plugin.gd | 44 +++---- scenes/bip/bip.gd | 18 +-- scenes/cell/cell-front.gd | 4 +- scenes/frame-ecm-list/scroll-ecm-list.gd | 1 - scenes/frame-ecm-params/scroll-ecm-params.gd | 114 +++++++++---------- scenes/frame-params/scroll-params.gd | 60 +++++----- scenes/tilemap/tilemap.gd | 1 - scenes/tilemap/tilemap.tscn | 27 +---- scripts/mercator.gd | 5 +- 9 files changed, 123 insertions(+), 151 deletions(-) diff --git a/addons/godot-logger/plugin.gd b/addons/godot-logger/plugin.gd index 116e5127..a6ff3f7f 100644 --- a/addons/godot-logger/plugin.gd +++ b/addons/godot-logger/plugin.gd @@ -14,36 +14,36 @@ const LOGGER_OUTPUT_ICON = "res://addons/godot-logger/icons/logger.svg" func _def_settings(pname: String, default: Variant) -> void: - if not ProjectSettings.has_setting(pname): - ProjectSettings.set_setting(pname, default) + if not ProjectSettings.has_setting(pname): + ProjectSettings.set_setting(pname, default) - ProjectSettings.set_initial_value(pname, default) + ProjectSettings.set_initial_value(pname, default) func _enter_tree() -> void: - _def_settings("plugins/logger/log_enabled", true) - _def_settings("plugins/logger/default_output", false) + _def_settings("plugins/logger/log_enabled", true) + _def_settings("plugins/logger/default_output", false) - _def_settings("plugins/logger/level", 31) # The magic number is - INFO | DEBUG | WARNING | ERROR | FATAL - ProjectSettings.add_property_info( - { - "name": "plugins/logger/level", - "type": TYPE_INT, - "hint": PROPERTY_HINT_FLAGS, - "hint_string": "Info,Debug,Warning,Error,Fatal", - } - ) + _def_settings("plugins/logger/level", 31) # The magic number is - INFO | DEBUG | WARNING | ERROR | FATAL + ProjectSettings.add_property_info( + { + "name": "plugins/logger/level", + "type": TYPE_INT, + "hint": PROPERTY_HINT_FLAGS, + "hint_string": "Info,Debug,Warning,Error,Fatal", + } + ) - _def_settings("plugins/logger/file/file_path", "user://uarep-ctl.log") - _def_settings("plugins/logger/file/log_file_write", true) + _def_settings("plugins/logger/file/file_path", "user://uarep-ctl.log") + _def_settings("plugins/logger/file/log_file_write", true) - _def_settings("plugins/logger/default_output_format", "[{year}-{month}-{day} {hour}:{minute}:{second}][{level}]{text}") - _def_settings("plugins/logger/file_format", "[{year}-{month}-{day} {hour}:{minute}:{second}][{level}]{text}") + _def_settings("plugins/logger/default_output_format", "[{year}-{month}-{day} {hour}:{minute}:{second}][{level}]{text}") + _def_settings("plugins/logger/file_format", "[{year}-{month}-{day} {hour}:{minute}:{second}][{level}]{text}") - add_autoload_singleton(AUTOLOAD_NAME, AUTOLOAD_PATH) - add_custom_type(LOGGER_OUTPUT, "RichTextLabel", load(LOGGER_OUTPUT_SCRIPT), load(LOGGER_OUTPUT_ICON)) + add_autoload_singleton(AUTOLOAD_NAME, AUTOLOAD_PATH) + add_custom_type(LOGGER_OUTPUT, "RichTextLabel", load(LOGGER_OUTPUT_SCRIPT), load(LOGGER_OUTPUT_ICON)) func _exit_tree() -> void: - remove_custom_type(LOGGER_OUTPUT) - remove_autoload_singleton(AUTOLOAD_NAME) + remove_custom_type(LOGGER_OUTPUT) + remove_autoload_singleton(AUTOLOAD_NAME) diff --git a/scenes/bip/bip.gd b/scenes/bip/bip.gd index f3c5a29b..da6b8e16 100644 --- a/scenes/bip/bip.gd +++ b/scenes/bip/bip.gd @@ -4,8 +4,8 @@ var selected: bool ## Функция присваивает заданое значение видимости селектору. func set_sel_visible(value: bool): - selected = value - $selector.visible = value + selected = value + $selector.visible = value ## Функция присваивает заданое значение видимости селектору. @@ -15,11 +15,11 @@ func set_ecm_visible(value: bool): $ecm_done.visible = value func _on_bip_gui_input(event): - if event is InputEventMouseButton and event.pressed: - match event.button_index: - MOUSE_BUTTON_LEFT: - signaller.emit_signal('bip_pressed', name.to_int()) - MOUSE_BUTTON_RIGHT: - if selected: - signaller.emit_signal('rto_threat_unsel', name.to_int()) + if event is InputEventMouseButton and event.pressed: + match event.button_index: + MOUSE_BUTTON_LEFT: + signaller.emit_signal('bip_pressed', name.to_int()) + MOUSE_BUTTON_RIGHT: + if selected: + signaller.emit_signal('rto_threat_unsel', name.to_int()) diff --git a/scenes/cell/cell-front.gd b/scenes/cell/cell-front.gd index f0382a2a..0623b7a9 100644 --- a/scenes/cell/cell-front.gd +++ b/scenes/cell/cell-front.gd @@ -1,5 +1,5 @@ extends Control @export var text: String = '': - set(val): $label.set_text(val) - get: return $label.get_text() + set(val): $label.set_text(val) + get: return $label.get_text() diff --git a/scenes/frame-ecm-list/scroll-ecm-list.gd b/scenes/frame-ecm-list/scroll-ecm-list.gd index 00bca853..59de10e8 100644 --- a/scenes/frame-ecm-list/scroll-ecm-list.gd +++ b/scenes/frame-ecm-list/scroll-ecm-list.gd @@ -84,7 +84,6 @@ func _ready(): interfer.connect('interfer_update', Callable(self, 'on_interfer_update').bind(table_index)) $table.connect('row_pressed', Callable(self, 'on_row_pressed')) call_deferred('init_table') - #$btn_cancel.button_connect('pressed', Callable(self, 'on_btn_cancel_pressed').bind($btn_cancel)) func init_table(): diff --git a/scenes/frame-ecm-params/scroll-ecm-params.gd b/scenes/frame-ecm-params/scroll-ecm-params.gd index 2b8294a6..5e90d516 100644 --- a/scenes/frame-ecm-params/scroll-ecm-params.gd +++ b/scenes/frame-ecm-params/scroll-ecm-params.gd @@ -11,82 +11,82 @@ const TABLE_HEADERS_TEXT = ['Параметр', 'Значение' ] ## const TABLE_COLUMN_SIZE = [ 370, 85 ] ## Ширины колонок. const TABLE_PARAMS = { - 'kni': [0, 'Курсовой угол, гр.', false, 0.0, 360.0], - 'powp': [1, 'Мощность, %', true, 0.0, 100.0], - 'freq': [2, 'Частота, МГц', true, 400.0, 6000.0], - 'width': [3, 'Занимая полоса частот, МГц', true, 0.0, 120.0], - 'dur': [4, 'Длительность импульса, мкс', true, 0.0, 1000000.0], - 'period': [5, 'Период повтора, мкс', true, 0.0, 1000000.0] } + 'kni': [0, 'Курсовой угол, гр.', false, 0.0, 360.0], + 'powp': [1, 'Мощность, %', true, 0.0, 100.0], + 'freq': [2, 'Частота, МГц', true, 400.0, 6000.0], + 'width': [3, 'Занимая полоса частот, МГц', true, 0.0, 120.0], + 'dur': [4, 'Длительность импульса, мкс', true, 0.0, 1000000.0], + 'period': [5, 'Период повтора, мкс', true, 0.0, 1000000.0] } func init_table(): - for i in TABLE_PARAMS.size(): $table.add_row(TABLE_ROW) - for key in TABLE_PARAMS.keys(): - var p = TABLE_PARAMS[key] - $table.set_node_text(0, p[0], p[1]) - $table.set_node_alligment(0, p[0], HORIZONTAL_ALIGNMENT_LEFT) - $table.set_node_editable(0, p[0], false) - $table.set_node_editable(1, p[0], false) - $table.set_node_user_data(1, p[0], key) - $table.connect_columns(self, 'text_submitted', 'on_edit_entry', [1], []) - $table.set_columns_min_size(TABLE_COLUMN_SIZE) - $table.set_meta('id', 0) + for i in TABLE_PARAMS.size(): $table.add_row(TABLE_ROW) + for key in TABLE_PARAMS.keys(): + var p = TABLE_PARAMS[key] + $table.set_node_text(0, p[0], p[1]) + $table.set_node_alligment(0, p[0], HORIZONTAL_ALIGNMENT_LEFT) + $table.set_node_editable(0, p[0], false) + $table.set_node_editable(1, p[0], false) + $table.set_node_user_data(1, p[0], key) + $table.connect_columns(self, 'text_submitted', 'on_edit_entry', [1], []) + $table.set_columns_min_size(TABLE_COLUMN_SIZE) + $table.set_meta('id', 0) ## Called when the node enters the scene tree for the first time. func _ready(): - signaller.connect('interfer_selected', Callable(self, 'on_interfer_selected')) - signaller.connect('rto_threat_unsel', Callable(self, 'on_rto_threat_unsel')) - call_deferred('init_table') + signaller.connect('interfer_selected', Callable(self, 'on_interfer_selected')) + signaller.connect('rto_threat_unsel', Callable(self, 'on_rto_threat_unsel')) + call_deferred('init_table') func on_edit_entry(text : String, entry): - var ecm = $table.get_meta('ecm') - if $table.has_meta('ecm') == false: return - var indexis = $table.get_key_indexes(entry[0].name) - var row_i = indexis[1] - var param = $table.get_node_user_data(1, row_i) - var p = TABLE_PARAMS[param] - var old_val = ecm.get_value(param) - var val = check_value(text, p[3], p[4], old_val) - ecm.set_value(val, param) - signaller.emit_signal('interfer_new', ecm) + var ecm = $table.get_meta('ecm') + if $table.has_meta('ecm') == false: return + var indexis = $table.get_key_indexes(entry[0].name) + var row_i = indexis[1] + var param = $table.get_node_user_data(1, row_i) + var p = TABLE_PARAMS[param] + var old_val = ecm.get_value(param) + var val = check_value(text, p[3], p[4], old_val) + ecm.set_value(val, param) + signaller.emit_signal('interfer_new', ecm) func check_value(val, min_val, max_val, old_val): - if val.is_valid_float(): - if float(val) < min_val: return min_val - if float(val) > max_val: return max_val - return float(val) - else: return old_val + if val.is_valid_float(): + if float(val) < min_val: return min_val + if float(val) > max_val: return max_val + return float(val) + else: return old_val func on_interfer_selected(ecm): - if ecm != null: - $table.set_meta('ecm', ecm) - else: - $table.set_meta('ecm', null) - on_ecm_update() + if ecm != null: + $table.set_meta('ecm', ecm) + else: + $table.set_meta('ecm', null) + on_ecm_update() func on_ecm_update(): - for item in len(TABLE_PARAMS): - $table.set_node_text(1, item, '') - $table.set_node_editable(1, item, false) - if $table.has_meta('ecm') == false: return - var ecm: interfer.Interfer = $table.get_meta('ecm') - var mnms = tools.get_members_names(ecm) - for item in ecm['params'].keys(): - mnms.append(item) - for member_name in mnms: - if not TABLE_PARAMS.has(member_name): continue - var p = TABLE_PARAMS[member_name] - var val = ecm.get_value(member_name) - $table.set_node_text(1, p[0], '%s' % val) - $table.set_node_editable(1, p[0], p[2]) + for item in len(TABLE_PARAMS): + $table.set_node_text(1, item, '') + $table.set_node_editable(1, item, false) + if $table.has_meta('ecm') == false: return + var ecm: interfer.Interfer = $table.get_meta('ecm') + var mnms = tools.get_members_names(ecm) + for item in ecm['params'].keys(): + mnms.append(item) + for member_name in mnms: + if not TABLE_PARAMS.has(member_name): continue + var p = TABLE_PARAMS[member_name] + var val = ecm.get_value(member_name) + $table.set_node_text(1, p[0], '%s' % val) + $table.set_node_editable(1, p[0], p[2]) func on_rto_threat_unsel(_th_id): - for item in range(len(TABLE_PARAMS)): - $table.set_node_text(1, item, '') - $table.set_node_editable(1, item, false) + for item in range(len(TABLE_PARAMS)): + $table.set_node_text(1, item, '') + $table.set_node_editable(1, item, false) diff --git a/scenes/frame-params/scroll-params.gd b/scenes/frame-params/scroll-params.gd index 5a7c689a..db0deac3 100644 --- a/scenes/frame-params/scroll-params.gd +++ b/scenes/frame-params/scroll-params.gd @@ -15,51 +15,51 @@ const TABLE_COLUMN_SIZE = [ 370, 85 ] ## Ширины к ## Параметры цели для отображения в таблице.[br] ## [b]val[0][/b] - Индекс строки.[br][b]val[1][/b] - Содержание. const TABLE_PARAMS = { - 'baud': [0, 'Модуляционная скорость, бод'], - 'freqm': [1, 'Режим смены частоты.'], - 'power': [2, 'Уровень сигнала, дБм.'], - 'tmod': [3, 'Тип модуляции'], - 'width': [4, 'Занимая полоса частот, МГц.'], - 'rkrp': [5, 'Рекомендуемая помеха'] } + 'baud': [0, 'Модуляционная скорость, бод'], + 'freqm': [1, 'Режим смены частоты.'], + 'power': [2, 'Уровень сигнала, дБм.'], + 'tmod': [3, 'Тип модуляции'], + 'width': [4, 'Занимая полоса частот, МГц.'], + 'rkrp': [5, 'Рекомендуемая помеха'] } ## Обрабатывает сигнал [signal threats.threat_selected]. func on_threat_selected(th): - $table.set_meta('id', th.id) - on_threat_update(th) + $table.set_meta('id', th.id) + on_threat_update(th) func init_table(): - for i in TABLE_PARAMS.size(): $table.add_row(TABLE_ROW) - for p in TABLE_PARAMS.values(): - $table.set_node_text(0, p[0], p[1]) - $table.set_node_alligment(0, p[0], HORIZONTAL_ALIGNMENT_LEFT) - $table.set_node_editable(0, p[0], false) - $table.set_node_editable(1, p[0], false) - $table.set_columns_min_size(TABLE_COLUMN_SIZE) - $table.set_meta('id', 0) + for i in TABLE_PARAMS.size(): $table.add_row(TABLE_ROW) + for p in TABLE_PARAMS.values(): + $table.set_node_text(0, p[0], p[1]) + $table.set_node_alligment(0, p[0], HORIZONTAL_ALIGNMENT_LEFT) + $table.set_node_editable(0, p[0], false) + $table.set_node_editable(1, p[0], false) + $table.set_columns_min_size(TABLE_COLUMN_SIZE) + $table.set_meta('id', 0) func _ready() -> void: - signaller.connect('threat_selected', Callable(self, 'on_threat_selected')) - signaller.connect('rto_threat_unsel', Callable(self, 'on_rto_threat_unsel')) - threats.connect('threat_update', Callable(self, 'on_threat_update')) - call_deferred('init_table') + signaller.connect('threat_selected', Callable(self, 'on_threat_selected')) + signaller.connect('rto_threat_unsel', Callable(self, 'on_rto_threat_unsel')) + threats.connect('threat_update', Callable(self, 'on_threat_update')) + call_deferred('init_table') ## Обрабатывает сигнал [signal threats.threat_update]. ## Обновляет отображение только для выбранной цели. func on_threat_update(th): - if th.selected == false: return - if th.id != $table.get_meta('id'): return - var mnms = tools.get_members_names(th) - for member_name in mnms: - if not TABLE_PARAMS.has(member_name): continue - var p = TABLE_PARAMS[member_name] - var val = th.get(member_name) - $table.set_node_text(1, p[0], '%s' % val) + if th.selected == false: return + if th.id != $table.get_meta('id'): return + var mnms = tools.get_members_names(th) + for member_name in mnms: + if not TABLE_PARAMS.has(member_name): continue + var p = TABLE_PARAMS[member_name] + var val = th.get(member_name) + $table.set_node_text(1, p[0], '%s' % val) func on_rto_threat_unsel(_th_id): - for item in range(len(TABLE_PARAMS)): - $table.set_node_text(1, item, '') + for item in range(len(TABLE_PARAMS)): + $table.set_node_text(1, item, '') diff --git a/scenes/tilemap/tilemap.gd b/scenes/tilemap/tilemap.gd index feef13e3..2445c07e 100644 --- a/scenes/tilemap/tilemap.gd +++ b/scenes/tilemap/tilemap.gd @@ -73,7 +73,6 @@ func _ready(): log.info('Модуль географической карты готов') - func _on_map_mode_changed(mode: int) -> void: var map_image_size = ProjectSettings.get_setting(MAP_MODES[mode]) material.set('shader_parameter/radius', MAP_RELATIVE_RADIUSES[mode]) diff --git a/scenes/tilemap/tilemap.tscn b/scenes/tilemap/tilemap.tscn index 9920581a..406784f3 100644 --- a/scenes/tilemap/tilemap.tscn +++ b/scenes/tilemap/tilemap.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=5 format=3 uid="uid://bbq6nklh36yij"] +[gd_scene load_steps=4 format=3 uid="uid://bbq6nklh36yij"] [ext_resource type="Script" path="res://scenes/tilemap/tilemap.gd" id="1_mkfgj"] [ext_resource type="Shader" path="res://shaders/tilemap.gdshader" id="1_tsdui"] @@ -8,32 +8,7 @@ resource_local_to_scene = true shader = ExtResource("1_tsdui") shader_parameter/radius = 0.39 -[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_ys5v4"] - [node name="tilemap" type="Sprite2D"] material = SubResource("ShaderMaterial_pmqvj") position = Vector2(560, 560) script = ExtResource("1_mkfgj") - -[node name="Panel" type="Panel" parent="."] -offset_left = -563.0 -offset_top = -557.0 -offset_right = 1022.0 -offset_bottom = 641.0 -theme_override_styles/panel = SubResource("StyleBoxEmpty_ys5v4") - -[node name="Node2D" type="Node2D" parent="."] - -[node name="LineEdit" type="LineEdit" parent="Node2D"] -offset_left = -490.0 -offset_top = 518.0 -offset_right = -244.0 -offset_bottom = 565.0 - -[node name="VBoxContainer" type="VBoxContainer" parent="Node2D"] -offset_left = -499.0 -offset_top = 200.0 -offset_right = -239.0 -offset_bottom = 430.0 - -[connection signal="gui_input" from="Panel" to="." method="_on_panel_gui_input"] diff --git a/scripts/mercator.gd b/scripts/mercator.gd index 55114866..47fca032 100644 --- a/scripts/mercator.gd +++ b/scripts/mercator.gd @@ -45,9 +45,8 @@ class Tile: func _bound_index(index: int) -> int: var mi: int = 2 ** z if index < 0: - index *= -1 - index %= mi - index = mi - index + index = -index - 1; + index = int(mi - 1) - (index % int(mi)) else: index %= mi return index From 8598f1bdd190c23615cf54e7b82dd5fe27e15994 Mon Sep 17 00:00:00 2001 From: MaD_CaT Date: Fri, 9 Aug 2024 10:41:07 +0300 Subject: [PATCH 13/20] =?UTF-8?q?=D0=94=D0=BE=D1=80=D0=B0=D0=B1=D0=BE?= =?UTF-8?q?=D1=82=D0=BA=D0=B0.=20=D0=9C=D0=B8=D0=B3=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=D1=86=D0=B5=D0=BB=D0=B5=D0=B9=20=D0=BF=D1=80=D0=B8=20?= =?UTF-8?q?=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B8.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- data/main-font.tres | 10 ++++++++++ scenes/bip/bip.gd | 4 ++++ scenes/bip/bip.tscn | 2 +- scenes/button-flat/button-flat.tscn | 2 +- scenes/работа/работа.gd | 1 + shaders/bip.gdshader | 9 +++++++-- 6 files changed, 24 insertions(+), 4 deletions(-) diff --git a/data/main-font.tres b/data/main-font.tres index 92f7276c..c54b9b0e 100644 --- a/data/main-font.tres +++ b/data/main-font.tres @@ -11,6 +11,7 @@ cache/0/16/0/underline_thickness = 0.0 cache/0/16/0/scale = 1.0 cache/0/16/0/kerning_overrides/16/0 = Vector2(0, 0) cache/0/16/0/kerning_overrides/14/0 = Vector2(0, 0) +cache/0/16/0/kerning_overrides/50/0 = Vector2(0, 0) cache/0/14/0/ascent = 0.0 cache/0/14/0/descent = 0.0 cache/0/14/0/underline_position = 0.0 @@ -18,3 +19,12 @@ cache/0/14/0/underline_thickness = 0.0 cache/0/14/0/scale = 1.0 cache/0/14/0/kerning_overrides/16/0 = Vector2(0, 0) cache/0/14/0/kerning_overrides/14/0 = Vector2(0, 0) +cache/0/14/0/kerning_overrides/50/0 = Vector2(0, 0) +cache/0/50/0/ascent = 0.0 +cache/0/50/0/descent = 0.0 +cache/0/50/0/underline_position = 0.0 +cache/0/50/0/underline_thickness = 0.0 +cache/0/50/0/scale = 1.0 +cache/0/50/0/kerning_overrides/16/0 = Vector2(0, 0) +cache/0/50/0/kerning_overrides/14/0 = Vector2(0, 0) +cache/0/50/0/kerning_overrides/50/0 = Vector2(0, 0) diff --git a/scenes/bip/bip.gd b/scenes/bip/bip.gd index ae49b23f..587e9de4 100644 --- a/scenes/bip/bip.gd +++ b/scenes/bip/bip.gd @@ -23,3 +23,7 @@ func _on_bip_gui_input(event): if selected: signaller.emit_signal('rto_threat_unsel', name.to_int()) + +func update(): + $bip.material.set('shader_parameter/tick_update', float(Time.get_ticks_msec()/1000)) + diff --git a/scenes/bip/bip.tscn b/scenes/bip/bip.tscn index f1601e52..4e64bd18 100644 --- a/scenes/bip/bip.tscn +++ b/scenes/bip/bip.tscn @@ -8,7 +8,7 @@ shader = ExtResource("2_rt0la") shader_parameter/color = Color(0.972549, 0.12549, 0.411765, 1) shader_parameter/color_up = Color(1, 1, 1, 1) -shader_parameter/tick_update = 0 +shader_parameter/tick_update = 0.0 [sub_resource type="PlaceholderTexture2D" id="PlaceholderTexture2D_w5pye"] diff --git a/scenes/button-flat/button-flat.tscn b/scenes/button-flat/button-flat.tscn index 74c54947..5e1913cd 100644 --- a/scenes/button-flat/button-flat.tscn +++ b/scenes/button-flat/button-flat.tscn @@ -13,7 +13,7 @@ shader_parameter/rotation = 0.0 shader_parameter/color = Vector4(1, 1, 1, 0.1) shader_parameter/width = 3.0 shader_parameter/speed = 0.0 -shader_parameter/disabled = false +shader_parameter/disabled = true shader_parameter/scale = Vector2(1, 1) [sub_resource type="PlaceholderTexture2D" id="PlaceholderTexture2D_6nup5"] diff --git a/scenes/работа/работа.gd b/scenes/работа/работа.gd index 6e04b6c3..33dc4f89 100644 --- a/scenes/работа/работа.gd +++ b/scenes/работа/работа.gd @@ -172,6 +172,7 @@ func map_threat_to_bip(th: threats.Threat, bip: Control): ## Обработчик сигнала обновления цели func on_threat_update(th): var bip = get_node('%d' % th.id) + bip.update() map_threat_to_bip(th, bip) diff --git a/shaders/bip.gdshader b/shaders/bip.gdshader index db41b3fe..177cef5a 100644 --- a/shaders/bip.gdshader +++ b/shaders/bip.gdshader @@ -5,8 +5,8 @@ shader_type canvas_item; /* Закрашенная окружность */ uniform vec4 color: source_color = vec4(1, 1, 1, 1); uniform vec4 color_up: source_color = vec4(1, 1, 1, 1); /* Цвет вспышки при обновлении */ -uniform uint tick_update = 0; /* Время последнего обновления, мс */ -const float time_delta = 0.1; /* Время вспышки при обновлении, c */ +uniform float tick_update = 0; /* Время последнего обновления, мс */ +const float time_delta = 0.5; /* Время вспышки при обновлении, c */ const float ds = 0.01; /* Относительное расстояние на котором выполняется сглаживание краёв */ const vec2 center = vec2(0.125, 0.125); /* Центр должен быть в (0.5, 0.5), но это не так */ const float radius = 0.075 - ds * 2.0; /* Радиус должен быть 0.5, но это не так */ @@ -16,4 +16,9 @@ const float radius = 0.075 - ds * 2.0; /* Радиус долж void fragment() { COLOR = color * smooth_px(length(UV - center), radius, ds); + float time_now = TIME; + if ((time_now - tick_update) < time_delta) + { + COLOR = COLOR - vec4(0.0, 0.0, 0.0, (time_delta - (time_now - tick_update)) * 0.5); + } } From 8e88eaf515386c0e08a0173f3ff818ff2a577c6d Mon Sep 17 00:00:00 2001 From: sasha80 Date: Wed, 21 Aug 2024 10:24:23 +0300 Subject: [PATCH 14/20] =?UTF-8?q?=D0=A7=D0=B8=D1=81=D1=82=D0=BA=D0=B0.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addons/godot-logger/scripts/logger.gd | 2 +- scenes/frame-ecm-list/frm-ecm-list.tscn | 4 ++++ scenes/pribor-rtr/pribor-rtr.gd | 2 +- scenes/настройки/настройки.tscn | 10 +++++----- scenes/работа/работа.gd | 1 - scenes/работа/работа.tscn | 8 ++++++-- scenes/эмс/эмс.tscn | 2 ++ 7 files changed, 19 insertions(+), 10 deletions(-) diff --git a/addons/godot-logger/scripts/logger.gd b/addons/godot-logger/scripts/logger.gd index 2a09e02e..81104665 100644 --- a/addons/godot-logger/scripts/logger.gd +++ b/addons/godot-logger/scripts/logger.gd @@ -206,7 +206,7 @@ func message(level: int, text: String) -> void: _file.call_deferred('flush') if _default_output_enabled: - print(format_stdout(msg)) + print_debug(format_stdout(msg)) func _open_file() -> void: diff --git a/scenes/frame-ecm-list/frm-ecm-list.tscn b/scenes/frame-ecm-list/frm-ecm-list.tscn index 4c898328..fceb9ed5 100644 --- a/scenes/frame-ecm-list/frm-ecm-list.tscn +++ b/scenes/frame-ecm-list/frm-ecm-list.tscn @@ -55,6 +55,7 @@ layout_mode = 2 size_flags_horizontal = 3 size_flags_vertical = 3 tooltip_text = "Включает принудительную помеху" +strips_rotation = 15.0 text = "СРН" metadata/rfi_name = "ozp1" @@ -63,6 +64,7 @@ editor_description = "Кнопка для принудительного вкл layout_mode = 2 size_flags_horizontal = 3 size_flags_vertical = 3 +strips_rotation = 15.0 text = "СПРС" metadata/rfi_name = "ozp1" @@ -71,6 +73,7 @@ layout_mode = 2 size_flags_horizontal = 3 size_flags_vertical = 3 tooltip_text = "Кнопка для принудительного включения группы секторов в режим КУПД" +strips_rotation = 15.0 text = "КУПД" metadata/rfi_name = "ozp1" @@ -78,5 +81,6 @@ metadata/rfi_name = "ozp1" layout_mode = 2 size_flags_horizontal = 3 size_flags_vertical = 3 +strips_rotation = 15.0 text = "Отключить все" metadata/rfi_name = "" diff --git a/scenes/pribor-rtr/pribor-rtr.gd b/scenes/pribor-rtr/pribor-rtr.gd index 71952df0..e7b10fce 100644 --- a/scenes/pribor-rtr/pribor-rtr.gd +++ b/scenes/pribor-rtr/pribor-rtr.gd @@ -130,4 +130,4 @@ func _on_item_pressed(block_a: Control): print_debug(block_a) for n in metas: var v = block_a.get_meta(n) - print('%s:%s' % [n, v]) + print_debug('%s:%s' % [n, v]) diff --git a/scenes/настройки/настройки.tscn b/scenes/настройки/настройки.tscn index a5f0eec8..e65b5d4a 100644 --- a/scenes/настройки/настройки.tscn +++ b/scenes/настройки/настройки.tscn @@ -244,9 +244,9 @@ text = "Управление элементами в режиме с огран [node name="Label2" type="Label" parent="."] layout_mode = 0 -offset_left = 16.0 +offset_left = 20.0 offset_top = 20.0 -offset_right = 476.0 +offset_right = 445.0 offset_bottom = 50.0 text = "Текущий режим доступа: полный" vertical_alignment = 1 @@ -295,13 +295,13 @@ text = "Кнопка «Подавление». layout_mode = 2 size_flags_horizontal = 6 size_flags_vertical = 6 -button_pressed = true +disabled = true [node name="lbl_commit" type="Label" parent="."] layout_mode = 0 -offset_left = 480.0 +offset_left = 500.0 offset_top = 20.0 -offset_right = 1040.0 +offset_right = 1060.0 offset_bottom = 50.0 text = "Текущая версия ПО УАРЭП:" vertical_alignment = 1 diff --git a/scenes/работа/работа.gd b/scenes/работа/работа.gd index 852bc9ec..59ee497c 100644 --- a/scenes/работа/работа.gd +++ b/scenes/работа/работа.gd @@ -162,7 +162,6 @@ func get_all_nodes(root): func on_enter_tree() -> void: call_deferred('_on_btn_scale_pressed') call_deferred('on_button_strobe_pressed') - $tilemap.position = $canvas.get_strob_center() ## Производит отображение цели на диаграмму. diff --git a/scenes/работа/работа.tscn b/scenes/работа/работа.tscn index 8c7d6a0f..9540a75c 100644 --- a/scenes/работа/работа.tscn +++ b/scenes/работа/работа.tscn @@ -49,7 +49,6 @@ offset_left = 1114.0 offset_top = 7.0 offset_right = 1594.0 offset_bottom = 1170.0 -metadata/_edit_lock_ = true [node name="grd_btns" parent="." instance=ExtResource("5_2jnbs")] layout_mode = 0 @@ -108,17 +107,19 @@ offset_right = 111.0 offset_bottom = 953.0 size_flags_horizontal = 3 size_flags_vertical = 3 +strips_rotation = 25.0 text = "Вид" toggle_mode = true [node name="btn_activate" parent="." instance=ExtResource("8_k0iv2")] layout_mode = 2 -offset_left = 954.0 +offset_left = 906.0 offset_top = 1060.0 offset_right = 1106.0 offset_bottom = 1112.0 size_flags_horizontal = 3 size_flags_vertical = 3 +strips_rotation = 15.0 text = "Подавление" toggle_mode = true @@ -169,6 +170,7 @@ layout_mode = 2 size_flags_horizontal = 3 size_flags_vertical = 3 tooltip_text = "Кнопка для выбора сектора при помощи клика мышкой" +strips_rotation = 25.0 text = "Строб" toggle_mode = true metadata/state = 0 @@ -178,6 +180,7 @@ layout_mode = 2 size_flags_horizontal = 3 size_flags_vertical = 3 tooltip_text = "Кнопка для выбора сектора при помощи клика мышкой" +strips_rotation = 25.0 text = "Цель" toggle_mode = true metadata/state = 1 @@ -187,6 +190,7 @@ layout_mode = 2 size_flags_horizontal = 3 size_flags_vertical = 3 tooltip_text = "Кнопка для выбора сектора при помощи клика мышкой" +strips_rotation = 25.0 text = "Сектор" toggle_mode = true metadata/state = 2 diff --git a/scenes/эмс/эмс.tscn b/scenes/эмс/эмс.tscn index 489af883..5be27bce 100644 --- a/scenes/эмс/эмс.tscn +++ b/scenes/эмс/эмс.tscn @@ -29,6 +29,7 @@ offset_left = 1446.0 offset_top = 55.0 offset_right = 1583.0 offset_bottom = 85.0 +strips_rotation = 10.0 text = "Прочитать" [node name="btn_flash_write" parent="." instance=ExtResource("2_eju8r")] @@ -37,6 +38,7 @@ offset_left = 1446.0 offset_top = 99.0 offset_right = 1583.0 offset_bottom = 129.0 +strips_rotation = 10.0 text = "Записать" [node name="btn_new" type="Button" parent="."] From c4f552ab1e4157df19e523d800a46fb388d3a838 Mon Sep 17 00:00:00 2001 From: sasha80 Date: Wed, 21 Aug 2024 10:27:53 +0300 Subject: [PATCH 15/20] =?UTF-8?q?=D0=94=D0=BE=D1=80=D0=B0=D0=B1=D0=BE?= =?UTF-8?q?=D1=82=D0=BA=D0=B0.=20=D0=A1=D0=BA=D1=80=D0=B8=D0=BF=D1=82=20?= =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20=D1=81=D0=B1=D0=BE=D1=80=D0=BA=D0=B8=20?= =?UTF-8?q?=D0=B8=D1=81=D0=BF=D0=BE=D0=BB=D0=BD=D1=8F=D0=B5=D0=BC=D0=BE?= =?UTF-8?q?=D0=B3=D0=BE=20=D1=84=D0=B0=D0=B9=D0=BB=D0=B0.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.sh | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 build.sh diff --git a/build.sh b/build.sh new file mode 100644 index 00000000..a02bbb37 --- /dev/null +++ b/build.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +# Описание +# +# Этот скрипт создаёт исполняемый файл из проекта + +# Имя выходного исполняемого файла (загружаемый модуль) +TARGET_BIN="uarep-ctl.linux.x86_64" + +# Целевой путь расположения исполняемого файла (загружаемого модуля) +TARGET_PATH="./bin" + +# Путь к исполняемому файлу редактора Godot +EDITOR="/opt/godot/Godot_v4.2.2-stable_linux.x86_64" + +# Путь к файлу проекта Godot +PROJECT_PATH="$(pwd)" + +${EDITOR} --path "${PROJECT_PATH}" --export-release "Linux-X11" "${TARGET_PATH}/${TARGET_BIN}" From 21d81ad8a203e621c33eed997e296fed480886b5 Mon Sep 17 00:00:00 2001 From: sasha80 Date: Wed, 21 Aug 2024 11:04:36 +0300 Subject: [PATCH 16/20] =?UTF-8?q?=D0=94=D0=BE=D1=80=D0=B0=D0=B1=D0=BE?= =?UTF-8?q?=D1=82=D0=BA=D0=B0.=20=D0=92=D0=B0=D0=B6=D0=BD=D1=8B=D0=B5=20?= =?UTF-8?q?=D0=BF=D0=BE=D1=8F=D1=81=D0=BD=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B4?= =?UTF-8?q?=D0=BB=D1=8F=20=D1=81=D0=B1=D0=BE=D1=80=D0=BA=D0=B8=20=D0=B7?= =?UTF-8?q?=D0=B0=D0=B3=D1=80=D1=83=D0=B6=D0=B0=D0=B5=D0=BC=D0=BE=D0=B3?= =?UTF-8?q?=D0=BE=20=D0=BC=D0=BE=D0=B4=D1=83=D0=BB=D1=8F.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.sh | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/build.sh b/build.sh index a02bbb37..c6f5925d 100644 --- a/build.sh +++ b/build.sh @@ -1,18 +1,23 @@ #!/usr/bin/env bash - +# # Описание # -# Этот скрипт создаёт исполняемый файл из проекта +# Этот скрипт создаёт исполняемый файл (загружаемый модуль) из файла проекта Godot +# # Имя выходного исполняемого файла (загружаемый модуль) TARGET_BIN="uarep-ctl.linux.x86_64" -# Целевой путь расположения исполняемого файла (загружаемого модуля) +# Целевой путь папки в которой будет расположен исполняемый файл (загружаемый модуль) TARGET_PATH="./bin" # Путь к исполняемому файлу редактора Godot EDITOR="/opt/godot/Godot_v4.2.2-stable_linux.x86_64" +# Если используется своя сборка движка, то соответствующий файл +# (исполняемый модуль движка, шаблон экспорта) нужно заменить +# в этой папке: "~/.local/share/godot/export_templates/4.2.2.stable/" + # Путь к файлу проекта Godot PROJECT_PATH="$(pwd)" From ff9bd426930fef0fd39f0bce34d53401eb4f0c6f Mon Sep 17 00:00:00 2001 From: sasha80 Date: Thu, 5 Sep 2024 08:35:48 +0300 Subject: [PATCH 17/20] =?UTF-8?q?=D0=94=D0=BE=D1=80=D0=B0=D0=B1=D0=BE?= =?UTF-8?q?=D1=82=D0=BA=D0=B0.=20=D0=A3=D0=BB=D1=83=D1=87=D1=88=D0=B5?= =?UTF-8?q?=D0=BD=D0=BE=20=D0=BE=D1=82=D0=BE=D0=B1=D1=80=D0=B0=D0=B6=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=BA=D0=B0=D1=80=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- project.godot | 40 ++ scenes/button-flat/button-flat.tscn | 3 +- scenes/diagram/diagram.gd | 6 +- scenes/tilemap/tilemap.gd | 777 +++++++++++++++++++++++----- scenes/tilemap/tilemap.tscn | 34 +- scenes/работа/работа.gd | 25 +- scenes/работа/работа.tscn | 13 +- 7 files changed, 724 insertions(+), 174 deletions(-) diff --git a/project.godot b/project.godot index 77b0bbbe..d6286c62 100644 --- a/project.godot +++ b/project.godot @@ -95,6 +95,46 @@ version_control/autoload_on_startup=true enabled=PackedStringArray() +[file_customization] + +folder_colors={ +"res://addons/": "blue", +"res://modbus/": "blue", +"res://scenes/": "teal", +"res://scenes/bip/": "blue", +"res://scenes/button-flat/": "blue", +"res://scenes/button-scale/": "blue", +"res://scenes/cell/": "blue", +"res://scenes/count-all/": "blue", +"res://scenes/count-danger/": "blue", +"res://scenes/diagram/": "blue", +"res://scenes/frame-ecm-list/": "blue", +"res://scenes/frame-ecm-params/": "blue", +"res://scenes/frame-params/": "blue", +"res://scenes/frame-threats/": "blue", +"res://scenes/frame/": "blue", +"res://scenes/grid-buttons-ecm/": "blue", +"res://scenes/grid-side-panel/": "blue", +"res://scenes/pribor-afsp/": "purple", +"res://scenes/pribor-prd-k/": "purple", +"res://scenes/pribor-prd-n/": "purple", +"res://scenes/pribor-prd-v/": "purple", +"res://scenes/pribor-rtr/": "purple", +"res://scenes/pribor-uf/": "purple", +"res://scenes/pribor-uyep/": "purple", +"res://scenes/tabs-switch/": "blue", +"res://scenes/tilemap/": "blue", +"res://scenes/журнал/": "green", +"res://scenes/контроль/": "green", +"res://scenes/настройки/": "green", +"res://scenes/работа/": "green", +"res://scenes/эмс/": "green", +"res://scenes/эмс2/": "green", +"res://scripts/": "pink", +"res://shaders/": "pink", +"res://table/": "blue" +} + [global] map_server_addr=false diff --git a/scenes/button-flat/button-flat.tscn b/scenes/button-flat/button-flat.tscn index 5e1913cd..955e3f4e 100644 --- a/scenes/button-flat/button-flat.tscn +++ b/scenes/button-flat/button-flat.tscn @@ -13,7 +13,7 @@ shader_parameter/rotation = 0.0 shader_parameter/color = Vector4(1, 1, 1, 0.1) shader_parameter/width = 3.0 shader_parameter/speed = 0.0 -shader_parameter/disabled = true +shader_parameter/disabled = false shader_parameter/scale = Vector2(1, 1) [sub_resource type="PlaceholderTexture2D" id="PlaceholderTexture2D_6nup5"] @@ -64,7 +64,6 @@ texture = SubResource("PlaceholderTexture2D_6nup5") centered = false script = SubResource("GDScript_uyl5d") speed = 0.0 -disabled = true [node name="state" type="NinePatchRect" parent="."] layout_mode = 2 diff --git a/scenes/diagram/diagram.gd b/scenes/diagram/diagram.gd index 3ae62cc8..a383a984 100644 --- a/scenes/diagram/diagram.gd +++ b/scenes/diagram/diagram.gd @@ -23,9 +23,9 @@ func get_strob_width() -> float: return $строб.material.get('shader_par func get_strob_band() -> float: return $строб.material.get('shader_parameter/band') * 125.0 func get_strob_radius() -> float: return $строб.material.get('shader_parameter/radius') * 125.0 -func get_radius_outter() -> float: return material.get('shader_parameter/radius_outter') -func get_radius_inner_0() -> float: return material.get('shader_parameter/radius_inner_0') -func get_radius_inner_1() -> float: return material.get('shader_parameter/radius_inner_1') +func get_radius_outter() -> float: return material.get('shader_parameter/radius_outter') +func get_radius_inner_0() -> float: return material.get('shader_parameter/radius_inner_0') +func get_radius_inner_1() -> float: return material.get('shader_parameter/radius_inner_1') func set_view_mode(val: int): material.set('shader_parameter/mode', val); func set_band_colors(val: Array): material.set('shader_parameter/ant_band_c', val) diff --git a/scenes/tilemap/tilemap.gd b/scenes/tilemap/tilemap.gd index 2445c07e..ca742a9e 100644 --- a/scenes/tilemap/tilemap.gd +++ b/scenes/tilemap/tilemap.gd @@ -1,173 +1,670 @@ -class_name main_tilemap extends Sprite2D +@tool +extends CanvasGroup -## Возможные состояния конечного автомата заданий. -enum TASK_STATE { - DONE, ## Выполнено. - WAIT, ## Ожидание плитки. - RUN, ## Запуск выполнения. - CHECK, ## Проверка пустого места. - ERROR ## Ошибка. -} +## Отображение карт WEB Mercator + +const MIN_ZOOM: float = 2.0 +const MAX_ZOOM: float = 19.0 +const TEST_COORDS: = [ Vector2(0, 0), Vector2(1, 0), Vector2(0, 1), Vector2(1, 1) ] +var test_coords_i: = 3 -## Координаты для проверок [lng, lat]. -const TEST_DATA: = { - 'Калиниград': [ 20.51095, 54.70649 ], - 'Москва': [ 37.6156, 55.7522 ], - 'Нью-Йорк': [ -74.006, 40.7143 ], - 'Сидней': [ 151.20732, -33.8678500 ], - 'Таганрог': [ 38.8969, 47.2362 ], - 'Хельсинки': [ 24.9354, 60.1695 ], - 'Азорские о-ва': [ 28.3996582, 38.5397568 ], - 'Мехико': [ -99.12766, 19.42847 ], - 'Белое море': [ 68.136606, 42.045546 ], - 'море Беллинсгаузена': [ -72.406587, -74.857997 ], - 'Фиджи': [ 178.065032, -17.713371 ], - 'Ноль': [ 0.0, 0.0 ], - 'Точка1': [ 170.0, 0.0 ], - 'Точка2': [-170.0, 0.0 ], - 'Море' : [ 19.4, 54.91 ] } +#http://10.1.1.220/osm_tiles/0/0/0.png +#https://tile.openstreetmap.org/{z}/{x}/{y}.png +@export var base_url: String = 'https://tile.openstreetmap.org/{z}/{x}/{y}.png': + set(v): + if base_url != v: + base_url = v + _clean_all() + queue_redraw() + +@export var zoom_step: float = 0.025 +@export var tile_width: float = 256.0 +@export var tile_height: float = 256.0 +@export_range(1, 1000, 1) var max_concurrent_requests:int = 5 +@export_range(100, 100000, 1) var max_cached_tiles: int = 200 +@export_range(0, 25, 1) var max_zoom_level: int = 21 +@export_color_no_alpha() var back_color: = Color.BLACK +@export var animation_time: float = 1.0 -const MAP_MODES: = { - 0: 'application/config/map_image_size_rto', - 1: 'application/config/map_image_size_rls' } +@export var size: Vector2i = Vector2i(400, 400): + set(v): + $canvas.size = v + _on_resize() + queue_redraw() + get: + return $canvas.size -const MAP_RELATIVE_RADIUSES: = { - 0: 0.39, - 1: 0.466 -} +@export var ax: float = 0.0: + set(v): + _xyz.x = v + queue_redraw() + get: + return _xyz.x -var map_image: Image ## Изображение с картой. -var map_tile_state: TASK_STATE ## Текущее состояние конечного автомата плитки. -var map_bcs: Vector3 ## Яркость, контраст насыщенность. -var map_server_addr: String ## Адрес сервера изображений карт в проекции Меркатора. -var map_tile_format: String ## -var map_tile_stack: Array ## Стек для хранения плиток карты. -var map_request: HTTPRequest ## Обработчик запросов http. -var map_zoom: int ## Уровень детализации карты. -var map_tile: Rect2i ## Область одной плитки: [x-координата области, y-координата области, x-индекс плитки, y-индекс плитки]. -var map_mode: int ## Режим карты: 0 - приоритет РТО, 1 - приоритет РЛС. -var prev_tiles_dic: Dictionary ## -var lnglat: Array ## Текущие координаты. -var root_rect: Rect2i +@export var ay: float = 0.0: + set(v): + _xyz.y = v + queue_redraw() + get: + return _xyz.y + + +@export var az: float = 0.0: + set(v): + _xyz.z = v + queue_redraw() + get: + return _xyz.z + + +var _xyz: Vector3 = Vector3(0, 0, MIN_ZOOM) +var _cache: Dictionary = {} +var _queue: Dictionary = {} +var _dragging: bool = false +var _drag_pos: Vector2 = Vector2.ZERO +var _last_cache_check: float = 0 +var _rollover: bool = false +var _marks: Dictionary = {} +var _req_count: = 0 +var image: = Image.new() +var image_load_proc: Callable +var now: = 0 +var spr_mask: = Sprite2D.new() +var visible_marks_count: = 0 +var mouse_wheel_count: = 0 +var map_server_available: = false + + +## Вызывается после того, как загрузка плиток завершена +signal load_completed() + +## Вызывается при запуске загрузки плиток +signal load_started() + +## Вызывается при получении ответа от сервера карт +signal map_server_online() + +## Вызывается при отсутствии ответа от сервера карт +signal map_server_offline() + +const image_load_proc_names = { + 'png': 'load_png_from_buffer', + 'jpg': 'load_jpg_from_buffer', + 'tga': 'load_tga_from_buffer', + 'webp': 'load_webp_from_buffer' } + + +@export_enum('png', 'jpg', 'jpeg', 'tga', 'webp') var image_type: String = 'png': + set(v): image_load_proc = Callable(image, image_load_proc_names[v]) + func _ready(): - lnglat = TEST_DATA['Таганрог'] - map_tile_state = TASK_STATE.DONE - map_request = HTTPRequest.new() - add_child(map_request) - map_request.request_completed.connect(self._map_request_completed) - map_bcs = ProjectSettings.get_setting('application/config/map_bcs', Vector3(1.0, 1.0, 1.0)) - map_server_addr = ProjectSettings.get_setting('application/config/map_server_addr') - map_tile_format = ProjectSettings.get_setting('application/config/map_tile_format', '%s/%d/%d/%d.png') - var map_image_size = ProjectSettings.get_setting(MAP_MODES[0], Vector2i(512, 512)) - var map_tile_size = ProjectSettings.get_setting('application/config/map_tile_size', 256) - map_image = Image.create(map_image_size.x, map_image_size.y, true, Image.FORMAT_RGB8) - map_tile = Rect2i(0, 0, map_tile_size, map_tile_size) - signaller.connect('zoom_changed', Callable(self, '_on_zoom_changed')) - signaller.connect('map_mode_changed', Callable(self, '_on_map_mode_changed')) - log.info('Адрес сервера поставщика карт: \"%s\"' % map_server_addr) - log.info('Модуль географической карты готов') + $canvas.connect('mouse_entered', func(): _rollover = true) + $canvas.connect('mouse_exited', func(): _rollover = false) + _on_resize() + image_type = 'png' + spr_mask.name = StringName('spr_mask') + spr_mask.material = CanvasItemMaterial.new() + spr_mask.material.blend_mode = CanvasItemMaterial.BLEND_MODE_SUB + spr_mask.add_to_group('gui_items') + $canvas.add_child(spr_mask) -func _on_map_mode_changed(mode: int) -> void: - var map_image_size = ProjectSettings.get_setting(MAP_MODES[mode]) - material.set('shader_parameter/radius', MAP_RELATIVE_RADIUSES[mode]) - map_image.fill(Color(0, 0, 0, 0)) - map_image.resize(map_image_size.x, map_image_size.y) - map_tile_state = TASK_STATE.DONE - map_mode = mode - var tile: = mercator.create_tile(lnglat[0], lnglat[1], map_zoom) - map_tile_stack.clear() - map_tile_stack.append(tile) - prev_tiles_dic.clear() +func _on_resize() -> void: + var img_mask: = Image.create($canvas.size.x, $canvas.size.y, false, Image.FORMAT_RGBA8) + var c = $canvas.size / 2.0 + var r = min($canvas.size.x, $canvas.size.y) / 2.0 + var color = Color.WHITE + var sz = img_mask.get_size() + for x in sz.x: + for y in sz.y: + var dist = c.distance_to(Vector2(x, y)) + color.a = smoothstep(r - 2.0, r + 2.0, dist) + img_mask.set_pixel(x, y, color) + spr_mask.texture = ImageTexture.create_from_image(img_mask) + spr_mask.position = $canvas.position + $canvas.size / 2.0 -func mark_image_center(image: Image, color: Color = Color.RED): - var c = image.get_size() / 2 - map_image.set_pixel(c.x - 1, c.y - 1, color) - map_image.set_pixel(c.x - 1, c.y, color) - map_image.set_pixel(c.x, c.y - 1, color) - map_image.set_pixel(c.x, c.y, color) +## Создаёт новую отметку из координат экрана +func add_mark_from_screen_pos(id: int, x: float, y: float, mark: Node): + if _marks.has(id): + return false + var wrld_pos = screen_to_world(_xyz, x, y) + var p = world_to_tile(wrld_pos.x, wrld_pos.y, _xyz.z) + var tile_index = xyz_to_idx(p.x, p.y, _xyz.z) + var dot = { 'position': wrld_pos, 'tile_index': tile_index, 'mark': mark } + _marks[id] = dot + add_child(mark) + fix_mark_position(dot) + return true -func _on_zoom_changed(zoom: int): - if map_tile_stack.size() != 0: +func get_marks_count(): return _marks.size() + + +func delete_mark(id: int): + if not _marks.has(id): + return false + _marks[id].mark.queue_free() + _marks.erase(id) + queue_redraw() + + +## Создаёт новую отметку из долготы и широты +func add_mark_from_lon_lat(id: int, lon: float, lat: float, mark: Node, gap_size: float = 32): + if _marks.has(id): + return false + var wrld_pos = lonlat_to_world(lon, lat) + var scr_pos = world_to_screen(wrld_pos.x, wrld_pos.y) + mark.set_global_position(scr_pos) + _marks[id] = {'position': wrld_pos, 'mark': mark, 'gap_size': gap_size } + add_child(mark) + queue_redraw() + return true + + +## Возвращает новую позицию для точки, если она выходит из поля видимости +func fix_mark_position(dot): + if is_mark_visible(dot): return - map_zoom = zoom - var tile: = mercator.create_tile(lnglat[0], lnglat[1], zoom) - map_tile_stack.clear() - map_tile_stack.append(tile) - prev_tiles_dic.clear() + var v = 2 * tile_width * pow(2, _xyz.z) + if dot.position.x < _xyz.x - $canvas.size.x / v: + dot.position.x = _xyz.x - $canvas.size.x / v + elif dot.position.x > _xyz.x + $canvas.size.x / v: + dot.position.x = _xyz.x + $canvas.size.x / v -func img_tile_to_map_image(image: Image, dst_pos: Vector2i): - image.adjust_bcs(map_bcs.x, map_bcs.y, map_bcs.z) - map_image.blit_rect(image, map_tile, dst_pos) +func is_mark_visible(dot): + var screen_pos = world_to_screen(dot.position.x, dot.position.y) + return screen_pos.x > 0 and screen_pos.x < $canvas.size.x -func _process(_delta: float): - if (map_tile_state == TASK_STATE.DONE) and map_tile_stack.size(): - var tile: mercator.Tile = map_tile_stack[-1] - var tile_addr: = map_tile_format % [map_server_addr, tile.z, tile.x , tile.y] - var rc = map_request.request(tile_addr) - if rc == OK: - map_tile_state = TASK_STATE.WAIT +func _input(e): + var lmp = get_local_mouse_position() + if not _rollover: + return + if e is InputEventMouseButton: + if e.pressed: + if e.button_index == MOUSE_BUTTON_WHEEL_DOWN: + mouse_wheel_count -= 1 + queue_redraw() + elif e.button_index == MOUSE_BUTTON_WHEEL_UP: + mouse_wheel_count += 1 + queue_redraw() + + if e.button_index == MOUSE_BUTTON_LEFT: + _dragging = e.pressed + _drag_pos = screen_to_world(_xyz, lmp.x, lmp.y) + + + elif e is InputEventMouseMotion: + if _dragging: + var wp = screen_to_world(_xyz, lmp.x, lmp.y) + var diff = _drag_pos - wp + _xyz.x += diff.x + _xyz.y += diff.y + queue_redraw() + + +## Возвращает true, если видна область за краем карты +func is_border_visible(): + var z = min(max_zoom_level, _xyz.z) + return screen_to_tile(0, 0, z).y != 0 + + +func multiply_zoom(multiplier: float, pivot: Vector2): + var p1: = screen_to_world(_xyz, pivot.x, pivot.y) + _xyz.z = clampf(_xyz.z * multiplier, MIN_ZOOM, MAX_ZOOM) + var p2: = screen_to_world(_xyz, pivot.x, pivot.y) + _xyz.x -= p2.x - p1.x + _xyz.y -= p2.y - p1.y + + +func set_zoom(zoom: float, pivot=null): + if pivot == null: + pivot = $canvas.size / 2.0 + + var p1: = screen_to_world(_xyz, pivot.x, pivot.y) + _xyz.z = clampf(zoom, MAX_ZOOM, MIN_ZOOM) + var p2: = screen_to_world(_xyz, pivot.x, pivot.y) + _xyz.x -= p2.x - p1.x + _xyz.y -= p2.y - p1.y + + +func _draw(): + + var lmp = get_local_mouse_position() + if mouse_wheel_count > 0: + multiply_zoom(1.0 + zoom_step, lmp) + mouse_wheel_count -= 1 + elif mouse_wheel_count < 0: + multiply_zoom(1.0 - zoom_step, lmp) + mouse_wheel_count += 1 + + var z = min(max_zoom_level, _xyz.z) + var t1 = screen_to_tile(0, 0, z) + var t2 = screen_to_tile($canvas.size.x, $canvas.size.y, z) + for tx in range(t1.x, t2.x + 1): + for ty in range(t1.y, t2.y + 1): + _draw_tile(tx, ty, z) + + var n = pow(2, z) + var dx = n * tile_width + var c = $canvas.size / 2.0 + var r = min($canvas.size.x, $canvas.size.y) / 2.0 + visible_marks_count = 0 + for dot in _marks.values(): + var pos = world_to_screen(dot.position.x, dot.position.y) + if pos.x >= 0: + pos.x = fmod(pos.x, dx) else: - log.error('%s: Невозможно сформировать запрос HTTP' % self) - map_tile_state = TASK_STATE.ERROR - elif map_tile_state == TASK_STATE.CHECK: - var root_tile: = mercator.Tile.new(root_rect.size.x, root_rect.size.y, map_zoom) - var neighbors_tiles = mercator.get_perimetr_tiles(root_tile, (map_image.get_size().x / map_tile.size.x) / 2) - var neighbors_tiles_dic = {} - for tile in neighbors_tiles: - neighbors_tiles_dic[[tile.x, tile.y]] = false - if neighbors_tiles_dic != prev_tiles_dic: - for tile in neighbors_tiles: - tile.is_root = false - map_tile_stack.push_front(tile) - prev_tiles_dic[[tile.x, tile.y]] = false - else: - if map_tile_stack.size() == 0: - signaller.emit_signal('zoom_complete', map_zoom) - map_tile_state = TASK_STATE.DONE + pos.x = -pos.x - 1 + pos.x = int(dx - 1) - fmod(pos.x, dx) + dot.mark.position = pos + dot.mark.visible = c.distance_to(pos) < (r - dot.gap_size) + visible_marks_count += int(dot.mark.visible) -func _map_request_completed(result, response_code, headers, data): +func _clean_all(): + _queue.clear() + _cache.clear() + _marks.clear() + _req_count = 0 + for c in get_children().filter(func (item): return not item.is_in_group('gui_items')): + remove_child(c) + c.queue_free() + + +# get the tile from queue with the newest timestamp +func get_next_in_queue(): + var tile = null + for idx in _queue: + var t = _queue.get(idx) + if not tile or t.t > tile.t: + tile = t + return tile + + +# delete oldest tiles from cache +func _clean_cache(): + var overflow = _cache.size() - max_cached_tiles + if overflow <= 0: + return + + var vals = _cache.values() + vals.sort_custom(func(t1, t2): return t1.t < t2.t) + for i in overflow: + _cache.erase(vals[i].i) + + +func _process(delta): + now += delta + + # if cache is overflowing - clean it + if not _last_cache_check or now - _last_cache_check > 5: + _last_cache_check = now + _clean_cache() + + while not _queue.is_empty() and (_req_count < max_concurrent_requests): + + var tile = get_next_in_queue() + if not tile: + break + + var req = HTTPRequest.new() + _req_count += 1 + req.set_meta('tile', tile) + add_child(req) + req.name = NodePath(str(tile.i)) + + _queue.erase(tile.i) + req.request_completed.connect(_response.bind(req, tile)) + + if req.request(tile.url) != OK: + tile.t = now + remove_child(req) + req.queue_free() + _req_count -= 1 + push_error('ошибка: запрос плитки \"%s\" не отправлен' % tile.url) + if _queue.is_empty(): + emit_signal('load_completed') + + +# result, response_code, headers, body +func _response(result, code, headers: Array, body: PackedByteArray, req, tile: Tile): + + _req_count -= 1 + remove_child(req) + req.queue_free() + if result != HTTPRequest.RESULT_SUCCESS: - log.error('%s: Запрос не обработан. result: \"%s\", response_code: \"%s\", headers: \"%s\"' % [self, result, response_code, headers]) - map_tile_state = TASK_STATE.ERROR + if map_server_available: + map_server_available = false + emit_signal('map_server_offline') + _queue[tile.i] = tile return - var img_tile = Image.new() - var error = img_tile.load_png_from_buffer(data) - if error != OK: - log.error('%s: Полученые данные невозможно использовать для формироания изображения. result: \"%s\", response_code: \"%s\", headers: \"%s\"' % [self, result, response_code, headers]) - map_tile_state = TASK_STATE.ERROR + if not map_server_available: + map_server_available = true + emit_signal('map_server_online') + + if code == 404: + push_error('ошибка: на сервере нет \"%s\"' % tile.url) return - var tile = map_tile_stack.pop_back() - map_tile_state = TASK_STATE.CHECK + if Error.OK != image_load_proc.call(body): + push_error('ошибка: не удалось получить изображение (неверный формат?) из \"%s\"' % tile.url) + return - if tile.is_root: - # Отображение корневой плитки - map_image.fill(Color(0, 0, 0, 0)) - var bounds: = mercator.bounds(tile) - var lng_c = (bounds.east + bounds.west) / 2.0 - var lat_c = (bounds.north + bounds.south) / 2.0 - var sx: float = float(map_tile.size.x) / (bounds.east - bounds.west) * (lng_c - tile.lng) + map_tile.size.x / 2.0 # Cмещение в пискселях центра плитки от заданной точки по горизонтали. - var sy: float = float(map_tile.size.y) / (bounds.north - bounds.south) * (lat_c - tile.lat) - map_tile.size.y / 2.0 # Cмещение в пискселях центра плитки от заданной точки по вертикали. - var dst_pos: Vector2i = Vector2i(int(sx), int(-sy)) + map_tile.size * map_mode - img_tile_to_map_image(img_tile, dst_pos) - root_rect = Rect2i(dst_pos.x, dst_pos.y, tile.x, tile.y) + # create texture from image and add it to the cache + tile.texture = ImageTexture.create_from_image(image) + _cache[tile.i] = tile + + # redraw the map + queue_redraw() + + +func _draw_subtile(tx: int, ty: int, tz: int, origx: int, origy: int, origz: float) -> bool: + var subtile = get_tile(tx, ty, tz) + if not subtile: + return false + + var p1 = tile_to_screen(origx, origy, origz) + var p2 = tile_to_screen(origx + 1, origy + 1, origz) + var x1 = tile_to_screen(tx, ty, tz) + var x2 = tile_to_screen(tx + 1, ty + 1, tz) + + var xdiff = x2.x - x1.x + var xrat1 = (p1.x - x1.x) / xdiff + var xrat2 = (p2.x - x1.x) / xdiff + var xwidth = xrat2 - xrat1 + + var ydiff = x2.y - x1.y + var yrat1 = (p1.y - x1.y) / ydiff + var yrat2 = (p2.y - x1.y) / ydiff + var yheight = yrat2 - yrat1 + + var rect = Rect2(xrat1 * tile_width, yrat1 * tile_height, xwidth * tile_width, yheight * tile_height) + if subtile.texture: + draw_texture_rect_region(subtile.texture, Rect2(p1, p2 - p1), rect) + return true + return false + + +func _draw_tile(tx: int, ty: int, z: float): + var tz = floor(z) + var p1 = tile_to_screen(tx, ty, z) + var p2 = tile_to_screen(tx + 1, ty + 1, z) + var tile = get_tile(tx, ty, tz) + if tile: + if tile.texture: + draw_texture_rect(tile.texture, Rect2(p1, p2 - p1), false, Color.WHITE, false) + else: + var zzz = tz + var txx = tx + var tyy = ty + while zzz > 1: + zzz -= 1 + txx = floor(txx / 2.0) + tyy = floor(tyy / 2.0) + if _draw_subtile(txx, tyy, zzz, tx, ty,z): + break + + +## convert lon/lat to world coords +func lonlat_to_world(lon: float, lat: float) -> Vector2: + var x = lon / 180.0 + var latsin = sin(deg_to_rad(lat) * sign(lat)) + var y = (sign(lat) * (log((1.0 + latsin) / (1.0 - latsin)) / 2.0)) / PI + return Vector2(x, y) + + +## convert lon/lat to screen coords +func lonlat_to_screen(lon: float, lat: float) -> Vector2: + var w = lonlat_to_world(lon, lat) + return world_to_screen(w.x, w.y) + + +## convert lon/lat to tile coords +func lonlat_to_tile(lon: float, lat: float, z: float) -> Vector2: + var w = lonlat_to_world(lon,lat) + return world_to_tile(w.x, w.y, z) + + +## convert world coords to lon/lat +func world_to_lonlat(wx: float, wy: float) -> Vector2: + var lon = wx * 180.0 + var lat = rad_to_deg(atan(sinh(wy * PI))) + return Vector2(lon, lat) + + +## convert screen coords to lon/lat +func screen_to_lonlat(sx: float, sy: float) -> Vector2: + var w = screen_to_world(_xyz, sx, sy) + return world_to_lonlat(w.x, w.y) + + +## convert tile coords to lon/lat +func tile_to_lonlat(tx: float, ty: float, tz: float) -> Vector2: + var w = tile_to_world(tx, ty, tz) + return world_to_lonlat(w.x, w.y) + + +## convert screen coords to world coords +func screen_to_world(xyz: Vector3, sx: float, sy: float) -> Vector2: + var n = pow(2.0, xyz.z) + var span_w = n * tile_width + var span_h = n * tile_height + var px = sx - $canvas.size.x / 2 + span_w / 2 + var py = sy - $canvas.size.y / 2 + span_h / 2 + var xr = px / span_w + var yr = py / span_h + var x = (xr * 2.0 - 1.0) + xyz.x + var y = ((-yr * 2.0) + 1.0) + xyz.y + return Vector2(x, y) + + +## convert screen coords to tile coords +func screen_to_tile(sx: float, sy: float, z: float) -> Vector2: + var world = screen_to_world(_xyz, sx, sy) + return world_to_tile(world.x, world.y, z) + + +## convert tile coords to screen coords +func tile_to_screen(tx: float, ty: float, tz: float) -> Vector2: + var w = tile_to_world(tx, ty, tz) + return world_to_screen(w.x, w.y) + + +## convert tile coords to world coords +func tile_to_world(tx: float, ty: float, tz: float) -> Vector2: + var n = pow(2.0, floor(tz)) + var x = (tx / n) * 2.0 - 1.0 + var y = -((ty / n) * 2.0 - 1.0) + return Vector2(x, y) + + +## convert world coords to tile coords +func world_to_tile(wx: float, wy: float, z: float) -> Vector2: + var n: = pow(2.0, floor(z)) + var tx: = ((wx + 1.0) / 2.0) * n + var ty: = ((-wy + 1.0) / 2.0) * n + tx = floor(tx) + ty = floor(ty) + return Vector2(tx, ty) + + +## convert world coords to screen coords +func world_to_screen(wx: float, wy: float) -> Vector2: + var n: = pow(2.0, _xyz.z) + var w: = n * tile_width + var h: = n * tile_height + var xr: = ((wx - _xyz.x) + 1.0) / 2.0 + var yr: = (-(wy - _xyz.y) + 1.0) / 2.0 + var x = w * xr - w / 2.0 + $canvas.size.x / 2.0 + var y = h * yr - h / 2.0 + $canvas.size.y / 2.0 + return Vector2(x, y) + + +## get the tile index from xyz tile coords +func xyz_to_idx(x: float, y: float, z: float) -> int: + var i: = (pow(4.0, z) - 1.0) / 3.0 + var n: = pow(2.0, z) + return int(i + (y * n + x)) + + +func get_tile(x: int, y: int, z: int, create: bool = true) -> Tile: + + var n = pow(2, z) + if x >= 0: + x %= int(n) else: - # Отображение соседних плиток - var delta: = Vector2i(tile.x - root_rect.size.x, tile.y - root_rect.size.y) - var dst_pos: Vector2i = root_rect.position + delta * map_tile.size - img_tile_to_map_image(img_tile, dst_pos) - mark_image_center(map_image) - texture = ImageTexture.create_from_image(map_image) + x = -x - 1 + x = int(n - 1) - (x % int(n)) + # out of bounds + if z < 0 or y < 0 or y >= n: + return null + + # get tile index + var idx = xyz_to_idx(x, y, z) + var tile = null + + # retrieve from current requests... + var req = get_node_or_null(NodePath(str(idx))) + if req: + tile = req.get_meta('tile') + tile.t = now + return tile + + # retrieve from cache + tile = _cache.get(idx) + if tile: + tile.t = now + return tile + + # retrieve from queue + tile = _queue.get(idx) + if tile: + tile.t = now + return tile + + # create a new tile - add it to queue + var queue_size: = _queue.size() + if create and (not $canvas/player.is_playing()): + tile = Tile.new(idx, x, y, z) + tile.url = base_url.replace('{x}', str(x)).replace('{y}', str(y)).replace('{z}', str(z)) + tile.t = now + _queue[idx] = tile + + if queue_size == 0 and _queue.size(): + emit_signal('load_started') + + return tile + + +func lonlat_to_dms(lon: float, lat: float) -> String: + var pf = 'N' if lat >= 0 else 'S' + lat = abs(lat) + var deg = floor(lat) + var minute = (lat - deg) * 60 + var second = (minute - floor(minute)) * 60 + var text = ('%02d' % deg) + ('%02d' % floor(minute)) + ('%02d' % floor(second)) + pf + ' ' + + pf = 'E' if lon >= 0 else 'W' + lon = abs(lon) + deg = floor(lon) + minute = (lon - deg) * 60 + second = (minute - floor(minute)) * 60 + text += ('%02d' % deg) + ('%02d' % floor(minute)) + ('%02d' % floor(second)) + pf + + return text + + +class Tile: + var i: int = 0 + var x: int = 0 + var y: int = 0 + var z: int = 0 + var t: int = 0 + var url: String = '' + var texture: Texture2D = null + + func _init(i0: int, x0: int, y0: int, z0: int): + self.i = i0 + self.x = x0 + self.y = y0 + self.z = z0 + + +func haversine_m(lat1, lon1, lat2, lon2): + # Радиус Земли в метрах + var R: = 6372795.0 + # Преобразование градусов в радианы + var rlat1: = deg_to_rad(lat1) + var rlon1: = deg_to_rad(lon1) + var rlat2: = deg_to_rad(lat2) + var rlon2: = deg_to_rad(lon2) + var cl1: = cos(rlat1) + var cl2: = cos(rlat2) + var sl1: = sin(rlat1) + var sl2: = sin(rlat2) + var delta: = rlon2 - rlon1 + var cdelta: = cos(delta) + var sdelta: = sin(delta) + # вычисления длины большого круга + var y: = sqrt(pow(cl2 * sdelta, 2) + pow(cl1 * sl2 - sl1 * cl2 * cdelta, 2)) + var x: = sl1 * sl2 + cl1 * cl2 * cdelta + var ad: = atan2(y, x) + var dist: = ad * R + return dist + + +func remove_closest_dot(screen_pos): + var closest_dot = find_at_screen_position(screen_pos) + if not closest_dot: + return + remove_child(closest_dot.mark) + closest_dot.mark.queue_free() + _marks.erase(closest_dot) + + +func find_at_screen_position(screen_pos: Vector2): + var closest_dot = null + var closest_l = null + var closest_distance = INF + for mark in _marks.values(): + var dot_screen_pos = world_to_screen(mark.position.x, mark.position.y) + var distance = dot_screen_pos.distance_to(screen_pos) + if distance < closest_distance: + closest_distance = distance + closest_dot = mark + return closest_dot + + +func clear() -> void: + _clean_all() + queue_redraw() + + +func set_coordinates(lon: float, lat: float): + var anim: Animation = $canvas/player.get_animation('goto') + var wrld_pos = lonlat_to_world(lon, lat) + + anim.track_insert_key(0, 0, _xyz.x) + anim.track_insert_key(1, 0, _xyz.y) + + anim.track_insert_key(0, animation_time, wrld_pos.x) + anim.track_insert_key(1, animation_time, wrld_pos.y) + + anim.length = animation_time + $canvas/player.play('goto') + + +func blend_mask(val: bool) -> void: + spr_mask.visible = val + queue_redraw() diff --git a/scenes/tilemap/tilemap.tscn b/scenes/tilemap/tilemap.tscn index 406784f3..b67f3306 100644 --- a/scenes/tilemap/tilemap.tscn +++ b/scenes/tilemap/tilemap.tscn @@ -1,14 +1,26 @@ -[gd_scene load_steps=4 format=3 uid="uid://bbq6nklh36yij"] +[gd_scene load_steps=3 format=3 uid="uid://ceknxxfu1loo5"] -[ext_resource type="Script" path="res://scenes/tilemap/tilemap.gd" id="1_mkfgj"] -[ext_resource type="Shader" path="res://shaders/tilemap.gdshader" id="1_tsdui"] +[ext_resource type="Script" path="res://scenes/tilemap/tilemap.gd" id="1_l64tp"] -[sub_resource type="ShaderMaterial" id="ShaderMaterial_pmqvj"] -resource_local_to_scene = true -shader = ExtResource("1_tsdui") -shader_parameter/radius = 0.39 +[sub_resource type="CanvasTexture" id="CanvasTexture_58cl1"] -[node name="tilemap" type="Sprite2D"] -material = SubResource("ShaderMaterial_pmqvj") -position = Vector2(560, 560) -script = ExtResource("1_mkfgj") +[node name="tilemap" type="CanvasGroup" groups=["gui_items"]] +script = ExtResource("1_l64tp") +az = 2.0 + +[node name="canvas" type="TextureRect" parent="." groups=["gui_items"]] +clip_contents = true +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_right = 400.0 +offset_bottom = 400.0 +grow_horizontal = 2 +grow_vertical = 2 +size_flags_horizontal = 4 +size_flags_vertical = 4 +texture = SubResource("CanvasTexture_58cl1") + +[node name="player" type="AnimationPlayer" parent="canvas" groups=["gui_items"]] + +[connection signal="resized" from="canvas" to="." method="_on_resize"] diff --git a/scenes/работа/работа.gd b/scenes/работа/работа.gd index 59ee497c..98c701d3 100644 --- a/scenes/работа/работа.gd +++ b/scenes/работа/работа.gd @@ -73,9 +73,14 @@ const fs_freq: Dictionary = { 5: [4400.0, 5200.0], 6: [5200.0, 6000.0] } -var fs_band = fs_band_rto # Текущий режим блоков ФС по частоте -var fs_selected: Dictionary # Словарь выбранных секторов -var fs_active: Dictionary # Словарь секторов назначенных на подавление + +## Размеры карты для разных режимов РТО и РЛС соответственно +const map_sizes = [ + Rect2i(360, 360, 400, 400), + Rect2i(82, 82, 955, 955)] +var fs_band = fs_band_rto ## Текущий режим блоков ФС по частоте +var fs_selected: Dictionary ## Словарь выбранных секторов +var fs_active: Dictionary ## Словарь секторов назначенных на подавление ## Состояние перетаскивания. enum DragFSM { @@ -88,9 +93,10 @@ enum DragFSM { enum BtnSel { SECTOR, ## В процессе. THREAT, ## Не активно, ожидание. - FS ## Выключено. + FS ## Выключено. } + ## Обработчик сигнала изменения списка целей. func on_threats_resized(threats: Dictionary): $count_all_pad/count_all.text = '%02d' % threats.size() @@ -102,13 +108,13 @@ func on_threat_selected(th, ecm_btns): on_rto_threat_sel(th.id, ecm_btns) ## Обработчик нажатия на кнопку вид. func on_button_view_toggled(toggled: bool): signaller.emit_signal('map_mode_changed', int(toggled)) - if toggled: - fs_band = fs_band_rls - else: - fs_band = fs_band_rto + fs_band = fs_band_rls if toggled else fs_band_rto + var s = map_sizes[int(toggled)] + $tilemap.size = s.size + $tilemap.position = s.position -## +## Встроенный обратный вызов. func _enter_tree() -> void: var timer1 = Timer.new() timer1.name = 'timer1' @@ -118,6 +124,7 @@ func _enter_tree() -> void: timer1.start(1.0) +## Встроенный обратный вызов. func _exit_tree() -> void: var timer1 = get_node('timer1') remove_child(timer1) diff --git a/scenes/работа/работа.tscn b/scenes/работа/работа.tscn index 9540a75c..c9dfd150 100644 --- a/scenes/работа/работа.tscn +++ b/scenes/работа/работа.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=15 format=3 uid="uid://b276iygic5itk"] +[gd_scene load_steps=13 format=3 uid="uid://b276iygic5itk"] [ext_resource type="Script" path="res://scenes/работа/работа.gd" id="1_niok4"] [ext_resource type="PackedScene" uid="uid://nl1vklubr5kr" path="res://scenes/bip/bip.tscn" id="2_br3s6"] @@ -8,16 +8,10 @@ [ext_resource type="PackedScene" uid="uid://bwti82byphl68" path="res://scenes/count-danger/count_danger_pad.tscn" id="7_2arls"] [ext_resource type="PackedScene" uid="uid://da7w3vkhadfwe" path="res://scenes/button-flat/button-flat.tscn" id="8_k0iv2"] [ext_resource type="PackedScene" uid="uid://0uqi1ghf7sma" path="res://scenes/button-scale/btn-scale.tscn" id="9_oey3x"] -[ext_resource type="PackedScene" uid="uid://bbq6nklh36yij" path="res://scenes/tilemap/tilemap.tscn" id="10_gtwyg"] +[ext_resource type="PackedScene" uid="uid://ceknxxfu1loo5" path="res://scenes/tilemap/tilemap.tscn" id="10_gtwyg"] [ext_resource type="PackedScene" uid="uid://b5kjdyxuwsot5" path="res://scenes/diagram/diagram.tscn" id="11_b6su4"] -[ext_resource type="Shader" path="res://shaders/tilemap.gdshader" id="11_ekapd"] [ext_resource type="Script" path="res://scenes/работа/sld-mode.gd" id="12_gqdkl"] -[sub_resource type="ShaderMaterial" id="ShaderMaterial_4p7y1"] -resource_local_to_scene = true -shader = ExtResource("11_ekapd") -shader_parameter/radius = 0.39 - [sub_resource type="ImageTexture" id="ImageTexture_cnmvt"] [node name="Работа" type="Panel"] @@ -41,7 +35,8 @@ metadata/_edit_vertical_guides_ = [] [node name="canvas" parent="." instance=ExtResource("11_b6su4")] [node name="tilemap" parent="." instance=ExtResource("10_gtwyg")] -material = SubResource("ShaderMaterial_4p7y1") +self_modulate = Color(1, 1, 1, 0.415686) +position = Vector2(360, 360) [node name="grd_side" parent="." instance=ExtResource("3_s2j0e")] layout_mode = 0 From 77fecfa55cf78d3472f188c9c1b79fc8f2ea68e1 Mon Sep 17 00:00:00 2001 From: sasha80 Date: Fri, 6 Sep 2024 10:07:55 +0300 Subject: [PATCH 18/20] =?UTF-8?q?=D0=A0=D0=B5=D1=84=D0=B0=D0=BA=D1=82?= =?UTF-8?q?=D0=BE=D1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- project.godot | 6 +++--- scenes/tilemap/tilemap.gd | 19 +++++++------------ scenes/tilemap/tilemap.tscn | 1 + scenes/работа/работа.gd | 26 +++++--------------------- scenes/работа/работа.tscn | 12 +----------- scripts/signaller.gd | 10 ---------- 6 files changed, 17 insertions(+), 57 deletions(-) diff --git a/project.godot b/project.godot index d6286c62..241fd596 100644 --- a/project.godot +++ b/project.godot @@ -24,8 +24,6 @@ config/map_tile_size=256 config/map_minimum_zoom=5 config/map_maximum_zoom=19 config/map_server_addr="https://b.tile.openstreetmap.org" -config/map_image_size_rto=Vector2i(512, 512) -config/map_image_size_rls=Vector2i(1024, 1024) interfer/center_offset=Vector2(0, 25) interfer/default_color0=Vector3(0.38, 0.47, 0.51) interfer/default_color1=Vector3(0.39, 0.44, 0.5) @@ -89,6 +87,8 @@ window/vsync/vsync_mode=false [editor] +movie_writer/mjpeg_quality=0.5 +movie_writer/fps=24 version_control/autoload_on_startup=true [editor_plugins] @@ -99,6 +99,7 @@ enabled=PackedStringArray() folder_colors={ "res://addons/": "blue", +"res://data/": "yellow", "res://modbus/": "blue", "res://scenes/": "teal", "res://scenes/bip/": "blue", @@ -192,7 +193,6 @@ common/enable_object_picking=false renderer/rendering_method="gl_compatibility" renderer/rendering_method.web="mobile" -gl_compatibility/item_buffer_size=8192 shader_compiler/shader_cache/strip_debug=true textures/default_filters/anisotropic_filtering_level=0 anti_aliasing/screen_space_roughness_limiter/enabled=false diff --git a/scenes/tilemap/tilemap.gd b/scenes/tilemap/tilemap.gd index ca742a9e..8634bd41 100644 --- a/scenes/tilemap/tilemap.gd +++ b/scenes/tilemap/tilemap.gd @@ -1,14 +1,9 @@ @tool extends CanvasGroup +class_name MercatorTileMap ## Отображение карт WEB Mercator -const MIN_ZOOM: float = 2.0 -const MAX_ZOOM: float = 19.0 -const TEST_COORDS: = [ Vector2(0, 0), Vector2(1, 0), Vector2(0, 1), Vector2(1, 1) ] -var test_coords_i: = 3 - - #http://10.1.1.220/osm_tiles/0/0/0.png #https://tile.openstreetmap.org/{z}/{x}/{y}.png @export var base_url: String = 'https://tile.openstreetmap.org/{z}/{x}/{y}.png': @@ -23,7 +18,8 @@ var test_coords_i: = 3 @export var tile_height: float = 256.0 @export_range(1, 1000, 1) var max_concurrent_requests:int = 5 @export_range(100, 100000, 1) var max_cached_tiles: int = 200 -@export_range(0, 25, 1) var max_zoom_level: int = 21 +@export_range(0, 25, 1) var max_zoom_level: int = 19 +@export_range(0, 25, 1) var min_zoom_level: int = 0 @export_color_no_alpha() var back_color: = Color.BLACK @export var animation_time: float = 1.0 @@ -61,7 +57,7 @@ var test_coords_i: = 3 return _xyz.z -var _xyz: Vector3 = Vector3(0, 0, MIN_ZOOM) +var _xyz: Vector3 = Vector3(0, 0, min_zoom_level) var _cache: Dictionary = {} var _queue: Dictionary = {} var _dragging: bool = false @@ -218,7 +214,7 @@ func is_border_visible(): func multiply_zoom(multiplier: float, pivot: Vector2): var p1: = screen_to_world(_xyz, pivot.x, pivot.y) - _xyz.z = clampf(_xyz.z * multiplier, MIN_ZOOM, MAX_ZOOM) + _xyz.z = clampf(_xyz.z * multiplier, min_zoom_level, max_zoom_level) var p2: = screen_to_world(_xyz, pivot.x, pivot.y) _xyz.x -= p2.x - p1.x _xyz.y -= p2.y - p1.y @@ -229,7 +225,7 @@ func set_zoom(zoom: float, pivot=null): pivot = $canvas.size / 2.0 var p1: = screen_to_world(_xyz, pivot.x, pivot.y) - _xyz.z = clampf(zoom, MAX_ZOOM, MIN_ZOOM) + _xyz.z = clampf(zoom, max_zoom_level, min_zoom_level) var p2: = screen_to_world(_xyz, pivot.x, pivot.y) _xyz.x -= p2.x - p1.x _xyz.y -= p2.y - p1.y @@ -335,7 +331,7 @@ func _process(delta): # result, response_code, headers, body -func _response(result, code, headers: Array, body: PackedByteArray, req, tile: Tile): +func _response(result, code, _headers: Array, body: PackedByteArray, req, tile: Tile): _req_count -= 1 remove_child(req) @@ -635,7 +631,6 @@ func remove_closest_dot(screen_pos): func find_at_screen_position(screen_pos: Vector2): var closest_dot = null - var closest_l = null var closest_distance = INF for mark in _marks.values(): var dot_screen_pos = world_to_screen(mark.position.x, mark.position.y) diff --git a/scenes/tilemap/tilemap.tscn b/scenes/tilemap/tilemap.tscn index b67f3306..6019bff4 100644 --- a/scenes/tilemap/tilemap.tscn +++ b/scenes/tilemap/tilemap.tscn @@ -6,6 +6,7 @@ [node name="tilemap" type="CanvasGroup" groups=["gui_items"]] script = ExtResource("1_l64tp") +max_zoom_level = 21 az = 2.0 [node name="canvas" type="TextureRect" parent="." groups=["gui_items"]] diff --git a/scenes/работа/работа.gd b/scenes/работа/работа.gd index 98c701d3..d439ea00 100644 --- a/scenes/работа/работа.gd +++ b/scenes/работа/работа.gd @@ -37,6 +37,7 @@ signal drag_begin ## Вызывается в момент начала пе signal drag_continue ## Вызывается во время продолжения перетаскивания. signal drag_end ## Вызывается в момент завершения перетаскивания. + ## Границы секторов блоков ФС по частоте в режиме РТО. const fs_band_rto: Dictionary = { 0: [200.0, 250.0], @@ -105,6 +106,10 @@ func on_threats_resized(threats: Dictionary): $count_all_pad/count_all.text = '% func on_threat_selected(th, ecm_btns): on_rto_threat_sel(th.id, ecm_btns) +## Встроенный обратный вызов. +func on_enter_tree() -> void: call_deferred('on_button_strobe_pressed') + + ## Обработчик нажатия на кнопку вид. func on_button_view_toggled(toggled: bool): signaller.emit_signal('map_mode_changed', int(toggled)) @@ -136,7 +141,6 @@ func _ready(): min_zoom = ProjectSettings.get_setting('application/config/map_minimum_zoom', 5) max_zoom = ProjectSettings.get_setting('application/config/map_maximum_zoom', 19) drag_fsm = DragFSM.OFF - $btn_zoom.set_meta('zoom', min_zoom - 1) var btns_select: Array = $btns_select.get_children() btns_select.any(func(btn): btn.button_connect('pressed', Callable(self, 'on_button_select').bind(btn, btns_select))) $btn_view.button_connect('toggled', Callable(self, 'on_button_view_toggled')) @@ -146,7 +150,6 @@ func _ready(): threats.connect('threat_lost', Callable(self, 'on_threat_lost')) threats.connect('threats_resized', Callable(self, 'on_threats_resized')) threats.connect('threat_update', Callable(self, 'on_threat_update')) - signaller.connect('zoom_complete', Callable(self, '_on_zoom_complete')) signaller.connect('rto_threat_sel', Callable(self, 'on_rto_threat_sel').bind(nodes)) signaller.connect('bip_pressed', Callable(self, 'on_bip_pressed').bind(nodes)) signaller.connect('rto_threat_unsel', Callable(self, 'on_rto_threat_unsel').bind(nodes)) @@ -166,11 +169,6 @@ func get_all_nodes(root): return nodes -func on_enter_tree() -> void: - call_deferred('_on_btn_scale_pressed') - call_deferred('on_button_strobe_pressed') - - ## Производит отображение цели на диаграмму. func map_threat_to_bip(th: threats.Threat, bip: Control): bip.position = tools.pos_calc( @@ -370,20 +368,6 @@ func _input(event) -> void: shift_pressed = event.pressed -## Вызывается по нажатию кнопки "Детализация". -func _on_btn_scale_pressed() -> void: - var zoom = $btn_zoom.get_meta('zoom') - zoom = zoom + 1 if zoom < max_zoom else min_zoom - signaller.emit_signal('zoom_changed', zoom) - - -## Вызывается по завершению изменения детализации карты. -func _on_zoom_complete(zoom: int) -> void: - var prefix = $btn_zoom.get_meta('prefix') - $btn_zoom/lbl.text = '%s %d' % [prefix, zoom] - $btn_zoom.set_meta('zoom', zoom) - - ## Обработчик нажатия по сектору формирователя сигналов. func sel_fs_sector(event): if (event.button_index == MOUSE_BUTTON_LEFT) or (event.button_index == MOUSE_BUTTON_RIGHT): diff --git a/scenes/работа/работа.tscn b/scenes/работа/работа.tscn index c9dfd150..357d0d0c 100644 --- a/scenes/работа/работа.tscn +++ b/scenes/работа/работа.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=13 format=3 uid="uid://b276iygic5itk"] +[gd_scene load_steps=12 format=3 uid="uid://b276iygic5itk"] [ext_resource type="Script" path="res://scenes/работа/работа.gd" id="1_niok4"] [ext_resource type="PackedScene" uid="uid://nl1vklubr5kr" path="res://scenes/bip/bip.tscn" id="2_br3s6"] @@ -7,7 +7,6 @@ [ext_resource type="PackedScene" uid="uid://dymo732qc2doa" path="res://scenes/count-all/count-all-pad.tscn" id="6_e3re7"] [ext_resource type="PackedScene" uid="uid://bwti82byphl68" path="res://scenes/count-danger/count_danger_pad.tscn" id="7_2arls"] [ext_resource type="PackedScene" uid="uid://da7w3vkhadfwe" path="res://scenes/button-flat/button-flat.tscn" id="8_k0iv2"] -[ext_resource type="PackedScene" uid="uid://0uqi1ghf7sma" path="res://scenes/button-scale/btn-scale.tscn" id="9_oey3x"] [ext_resource type="PackedScene" uid="uid://ceknxxfu1loo5" path="res://scenes/tilemap/tilemap.tscn" id="10_gtwyg"] [ext_resource type="PackedScene" uid="uid://b5kjdyxuwsot5" path="res://scenes/diagram/diagram.tscn" id="11_b6su4"] [ext_resource type="Script" path="res://scenes/работа/sld-mode.gd" id="12_gqdkl"] @@ -145,14 +144,6 @@ layout_mode = 0 offset_right = 40.0 offset_bottom = 40.0 -[node name="btn_zoom" parent="." instance=ExtResource("9_oey3x")] -layout_mode = 0 -offset_left = 12.0 -offset_top = 986.0 -offset_right = 112.0 -offset_bottom = 1006.0 -metadata/zoom = 4 - [node name="btns_select" type="GridContainer" parent="."] layout_mode = 0 offset_left = 1018.0 @@ -264,5 +255,4 @@ vertical_alignment = 1 [connection signal="drag_begin" from="." to="." method="_on_drag_begin"] [connection signal="drag_continue" from="." to="." method="_on_drag_continue"] -[connection signal="pressed" from="btn_zoom" to="." method="_on_btn_scale_pressed"] [connection signal="value_changed" from="vslider" to="vslider" method="_on_value_changed"] diff --git a/scripts/signaller.gd b/scripts/signaller.gd index 05f9591a..068b44fa 100644 --- a/scripts/signaller.gd +++ b/scripts/signaller.gd @@ -119,16 +119,6 @@ signal interfer_selected (ecm: interfer.Interfer) signal fs_selected(ecm: interfer.Interfer) -## Вызывается при запросе изменения уровня детализации карты.[br] -## [param zoom] - Вновь установленный уровень детализации. -signal zoom_changed(zoom: int) - - -## Вызывается при завершении изменения уровня детализации карты.[br] -## [param zoom] - Вновь установленный уровень детализации. -signal zoom_complete(zoom: int) - - ## Вызывается при изменении координат карты.[br] ## [param lan], [param lat] - Вновь установленные координаты. signal map_coords_changed(lan: float, lat: float) From cff97b2edc25870afd9f0e32a632825354e6f79e Mon Sep 17 00:00:00 2001 From: Maxim Date: Fri, 27 Sep 2024 10:43:56 +0300 Subject: [PATCH 19/20] =?UTF-8?q?=D0=94=D0=BE=D1=80=D0=B0=D0=B1=D0=BE?= =?UTF-8?q?=D1=82=D0=BA=D0=B0=20=D0=BC=D0=B5=D1=80=D1=86=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=D1=86=D0=B5=D0=BB=D0=B5=D0=B9=20=D0=BF=D1=80=D0=B8=20?= =?UTF-8?q?=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B8=20?= =?UTF-8?q?=D0=B4=D0=B0=D0=BD=D0=BD=D1=8B=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scenes/bip/bip.tscn | 2 -- shaders/bip.gdshader | 16 +++++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/scenes/bip/bip.tscn b/scenes/bip/bip.tscn index 4e64bd18..c296aff8 100644 --- a/scenes/bip/bip.tscn +++ b/scenes/bip/bip.tscn @@ -6,8 +6,6 @@ [sub_resource type="ShaderMaterial" id="ShaderMaterial_2dulj"] shader = ExtResource("2_rt0la") -shader_parameter/color = Color(0.972549, 0.12549, 0.411765, 1) -shader_parameter/color_up = Color(1, 1, 1, 1) shader_parameter/tick_update = 0.0 [sub_resource type="PlaceholderTexture2D" id="PlaceholderTexture2D_w5pye"] diff --git a/shaders/bip.gdshader b/shaders/bip.gdshader index 177cef5a..b2671d27 100644 --- a/shaders/bip.gdshader +++ b/shaders/bip.gdshader @@ -3,22 +3,24 @@ shader_type canvas_item; #include "res://shaders/tools.gdshaderinc" /* Закрашенная окружность */ -uniform vec4 color: source_color = vec4(1, 1, 1, 1); -uniform vec4 color_up: source_color = vec4(1, 1, 1, 1); /* Цвет вспышки при обновлении */ -uniform float tick_update = 0; /* Время последнего обновления, мс */ +uniform float tick_update = 0.0; /* Время последнего обновления, мс */ const float time_delta = 0.5; /* Время вспышки при обновлении, c */ const float ds = 0.01; /* Относительное расстояние на котором выполняется сглаживание краёв */ const vec2 center = vec2(0.125, 0.125); /* Центр должен быть в (0.5, 0.5), но это не так */ const float radius = 0.075 - ds * 2.0; /* Радиус должен быть 0.5, но это не так */ - - +const float alpha_offset = 0.15; /* Изменние скорости альфа канала */ void fragment() { - COLOR = color * smooth_px(length(UV - center), radius, ds); float time_now = TIME; + float alpha = 1.0; if ((time_now - tick_update) < time_delta) { - COLOR = COLOR - vec4(0.0, 0.0, 0.0, (time_delta - (time_now - tick_update)) * 0.5); + alpha = (time_delta - (time_now - tick_update)) * alpha_offset; } + else{ + alpha = 0.0; + } + vec4 color = vec4(0.972549, 0.12549, 0.411765, 1.0 - alpha); + COLOR = color * smooth_px(length(UV - center), radius, ds); } From e7a8f35b81666e8febf1ff034755e19741ee34ee Mon Sep 17 00:00:00 2001 From: Maxim Date: Fri, 27 Sep 2024 11:30:19 +0300 Subject: [PATCH 20/20] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B2=D1=8B=D0=B1=D0=BE=D1=80?= =?UTF-8?q?=D0=B0=20=D1=81=D0=B5=D0=BA=D1=82=D0=BE=D1=80=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- project.godot | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project.godot b/project.godot index 241fd596..9ca76b70 100644 --- a/project.godot +++ b/project.godot @@ -24,7 +24,7 @@ config/map_tile_size=256 config/map_minimum_zoom=5 config/map_maximum_zoom=19 config/map_server_addr="https://b.tile.openstreetmap.org" -interfer/center_offset=Vector2(0, 25) +interfer/center_offset=Vector2(200, 225) interfer/default_color0=Vector3(0.38, 0.47, 0.51) interfer/default_color1=Vector3(0.39, 0.44, 0.5) interfer/select_color=Vector3(1, 1, 1)