diff --git a/tools/graphviz_render/.gitignore b/tools/graphviz_render/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..bee8a64b79a99590d5303307144172cfe824fbf7 --- /dev/null +++ b/tools/graphviz_render/.gitignore @@ -0,0 +1 @@ +__pycache__ diff --git a/tools/graphviz_render/README.md b/tools/graphviz_render/README.md new file mode 100644 index 0000000000000000000000000000000000000000..f3453cf32d012fc792cc83bd663e8310f4ceb167 --- /dev/null +++ b/tools/graphviz_render/README.md @@ -0,0 +1,16 @@ +第一步:安装python3.12及以上 + +第二步:安装依赖 + `pip3 install -r requirements.txt` + +第三步:运行工具`graphviz_render/src/` + `python3 main.py /tmp/gviz` (备注: /tmp/gviz是管道名,可自定义) + 如果运行报以下错误 + ![alt text](images/image.png) + + 请安装: + `sudo apt-get install libxcb-cursor0 libxcb-xinerama0 libxcb-randr0 libxcb-util0-dev` + +第四步:执行监听shell脚本 + ![alt text](images/image-1.png) + 示例:`while true; do echo '/cc/brain/graph_work; quit' | nc 10.8.11.62 3000 > /tmp/gviz; sleep 1; done` diff --git a/tools/graphviz_render/giv.txt b/tools/graphviz_render/giv.txt new file mode 100644 index 0000000000000000000000000000000000000000..7ce047b7e443af2458f491c26d407fdf424ef857 --- /dev/null +++ b/tools/graphviz_render/giv.txt @@ -0,0 +1,401 @@ +digraph { +rankdir=LR; +node [shape="rect",style="filled"]; +action_24 [fillcolor="orange",label="24.Clean\nclean_time = 0\ncollect_enable = true\ndidnot_continue_work_after_leave_ground = false\ndrying_enable = true\nhas_it_been_first_weeting_fill_water = false\nis_do_stop_clean = false\nis_fault_pause = false\nis_need_back_wash = false\nis_need_drying = false\nis_need_sync_task_desc = false\nis_only_discharge_dust = false\nis_pause_task_timeout = false\nis_quiet_mode = false\nis_relocation_fail = false\nis_timeout_recharge = false\nis_user_click_recharge = false\nlast_collect_dust_clean_timepoint = 0\npause_task_timeout_times = 3\nremain_clean_area = 0.0"]; +action_25 [fillcolor="green",label="25.BackupMapBag"]; +action_24->action_25 [label="0.prework"]; +action_26 [fillcolor="white",label="26.FirstWettingAndFillWaterAction\nis_allow_interrupt = false"]; +action_27 [fillcolor="white",label="27.BackWashOnly\nis_allow_interrupt = false\nis_self_clean_finish = false\nis_succ_all_action = true\nis_water_empty_during_self_clean = false\nself_clean_time = 10"]; +action_28 [fillcolor="white",label="28.SetPowerToWork"]; +action_27->action_28 [label="0.conncet_bt_action"]; +action_69 [fillcolor="white",label="69.ChangeWater\ndischarge_sewage_remain_times = 3\ndiscontacted_return_times = 3\nis_force_fill_water = false\nis_need_discharge_sewage = true\nis_need_fill_water = true\nopen_22v_fail_retry_times = 3\npending_times = 3"]; +action_71 [fillcolor="white",label="71.Sequence\nindex = 0\nmode = \"AnyFail\""]; +action_72 [fillcolor="white",label="72.Prepare"]; +action_78 [fillcolor="white",label="78.Sequence\nindex = 0\nmode = \"AnyFail\""]; +action_79 [fillcolor="white",label="79.Function\n[clean fault]"]; +action_78->action_79 [label="[0]"]; +action_73 [fillcolor="white",label="73.Check"]; +action_74 [fillcolor="white",label="74.Sequence\nindex = 0\nmode = \"AnyFail\""]; +action_75 [fillcolor="white",label="75.Function"]; +action_74->action_75 [label="[0]"]; +action_76 [fillcolor="white",label="76.SetPowerToWork"]; +action_74->action_76 [label="[1]"]; +action_77 [fillcolor="white",label="77.Function"]; +action_74->action_77 [label="[2]"]; +action_73->action_74; +action_78->action_73 [label="[1]"]; +action_81 [fillcolor="white",label="81.Parallel\n[set power mode and wait 500ms]\nmode = \"AllFinish\""]; +action_80 [fillcolor="white",label="80.WaitLidarReady"]; +action_81->action_80 [label="[0]"]; +action_82 [fillcolor="white",label="82.Sleep"]; +action_81->action_82 [label="[1]"]; +action_78->action_81 [label="[2]"]; +action_83 [fillcolor="white",label="83.Function"]; +action_78->action_83 [label="[3]"]; +action_84 [fillcolor="white",label="84.ExitBase\nis_force_exit = true\nis_recv_te_exit_succ = false\nis_te_task_started = false\nis_te_task_stoped = false"]; +action_78->action_84 [label="[4]"]; +action_72->action_78; +action_71->action_72 [label="[0]"]; +action_69->action_71 [label="1.exit_base_seq"]; +action_70 [fillcolor="white",label="70.SetPowerToWork"]; +action_69->action_70 [label="3.set_power_mode"]; +action_125 [fillcolor="white",label="125.Sequence\nindex = 0\nmode = \"AnyFail\""]; +action_126 [fillcolor="white",label="126.SetMucPowerManageMode"]; +action_125->action_126 [label="[0]"]; +action_127 [fillcolor="white",label="127.Open22VoltChannel"]; +action_125->action_127 [label="[1]"]; +action_69->action_125 [label="4.open_22v_channel"]; +action_129 [fillcolor="white",label="129.WaitBtUploadOnBase"]; +action_69->action_129 [label="5.pending_bt_upload_on_base"]; +action_128 [fillcolor="white",label="128.SetMucPowerManageMode"]; +action_69->action_128 [label="6.close_mcu_charge_mode"]; +action_131 [fillcolor="white",label="131.DoFillWater\nis_need_bt_interaction = true\nwater_base_fill_water_seconds = 0"]; +action_69->action_131 [label="7.do_dill_water"]; +action_130 [fillcolor="white",label="130.DischargeSewage"]; +action_69->action_130 [label="8.discharge_sewage"]; +action_27->action_69 [label="3.change_water"]; +action_132 [fillcolor="white",label="132.DoSelfClean"]; +action_133 [fillcolor="white",label="133.Sequence\nindex = 0\nmode = \"AnyFail\""]; +action_134 [fillcolor="white",label="134.LoopIf"]; +action_135 [fillcolor="white",label="135.Function"]; +action_134->action_135 [label="0.if"]; +action_136 [fillcolor="white",label="136.Sleep"]; +action_134->action_136 [label="1.exec"]; +action_133->action_134 [label="[0]"]; +action_132->action_133; +action_27->action_132 [label="4.do_self_clean"]; +action_137 [fillcolor="white",label="137.RinseFilter"]; +action_138 [fillcolor="white",label="138.Sequence\nindex = 0\nmode = \"AnyFail\""]; +action_139 [fillcolor="white",label="139.Function"]; +action_138->action_139 [label="[0]"]; +action_140 [fillcolor="white",label="140.SendOpenBaseValveCmdAction"]; +action_138->action_140 [label="[1]"]; +action_141 [fillcolor="white",label="141.Sleep"]; +action_138->action_141 [label="[2]"]; +action_142 [fillcolor="white",label="142.SendDischargeSewageCmd"]; +action_138->action_142 [label="[3]"]; +action_143 [fillcolor="white",label="143.Sleep"]; +action_138->action_143 [label="[4]"]; +action_137->action_138; +action_27->action_137 [label="5.rinse_filter"]; +action_26->action_27 [label="1.fully_wetting_action"]; +action_144 [fillcolor="white",label="144.DoFillWater\nis_need_bt_interaction = true\nwater_base_fill_water_seconds = 0"]; +action_26->action_144 [label="2.host_fill_water_action"]; +action_24->action_26 [label="1.prework"]; +action_145 [fillcolor="lightblue",label="145.Clean\nhas_it_been_wetted = false\nis_clean_with_explore = false\nlast_state_before_pause = 1"]; +action_191 [fillcolor="white",label="191.IfElse"]; +action_190 [fillcolor="white",label="190.Function\n[is_switch_to_clean_with_explore]"]; +action_191->action_190 [label="0.if"]; +action_176 [fillcolor="white",label="176.Prepare"]; +action_182 [fillcolor="white",label="182.Sequence\nindex = 0\nmode = \"AnyFail\""]; +action_183 [fillcolor="white",label="183.Function\n[clean fault]"]; +action_182->action_183 [label="[0]"]; +action_177 [fillcolor="white",label="177.Check"]; +action_178 [fillcolor="white",label="178.Sequence\nindex = 0\nmode = \"AnyFail\""]; +action_179 [fillcolor="white",label="179.Function"]; +action_178->action_179 [label="[0]"]; +action_180 [fillcolor="white",label="180.SetPowerToWork"]; +action_178->action_180 [label="[1]"]; +action_181 [fillcolor="white",label="181.Function"]; +action_178->action_181 [label="[2]"]; +action_177->action_178; +action_182->action_177 [label="[1]"]; +action_185 [fillcolor="white",label="185.Parallel\n[set power mode and wait 500ms]\nmode = \"AllFinish\""]; +action_184 [fillcolor="white",label="184.WaitLidarReady"]; +action_185->action_184 [label="[0]"]; +action_186 [fillcolor="white",label="186.Sleep"]; +action_185->action_186 [label="[1]"]; +action_182->action_185 [label="[2]"]; +action_187 [fillcolor="white",label="187.Function"]; +action_182->action_187 [label="[3]"]; +action_188 [fillcolor="white",label="188.ExitBase\nis_force_exit = false\nis_recv_te_exit_succ = false\nis_te_task_started = false\nis_te_task_stoped = false"]; +action_182->action_188 [label="[4]"]; +action_189 [fillcolor="white",label="189.StartSlam\nmode = 2\nrelocate_state = 0"]; +action_182->action_189 [label="[5]"]; +action_176->action_182; +action_191->action_176 [label="1.then"]; +action_146 [fillcolor="white",label="146.IfElse\n[prepare_clean_by_is_need_mop]"]; +action_147 [fillcolor="white",label="147.Function"]; +action_146->action_147 [label="0.if"]; +action_162 [fillcolor="white",label="162.Prepare"]; +action_168 [fillcolor="white",label="168.Sequence\nindex = 0\nmode = \"AnyFail\""]; +action_169 [fillcolor="white",label="169.Function\n[clean fault]"]; +action_168->action_169 [label="[0]"]; +action_163 [fillcolor="white",label="163.Check"]; +action_164 [fillcolor="white",label="164.Sequence\nindex = 0\nmode = \"AnyFail\""]; +action_165 [fillcolor="white",label="165.Function"]; +action_164->action_165 [label="[0]"]; +action_166 [fillcolor="white",label="166.SetPowerToWork"]; +action_164->action_166 [label="[1]"]; +action_167 [fillcolor="white",label="167.Function"]; +action_164->action_167 [label="[2]"]; +action_163->action_164; +action_168->action_163 [label="[1]"]; +action_171 [fillcolor="white",label="171.Parallel\n[set power mode and wait 500ms]\nmode = \"AllFinish\""]; +action_170 [fillcolor="white",label="170.WaitLidarReady"]; +action_171->action_170 [label="[0]"]; +action_172 [fillcolor="white",label="172.Sleep"]; +action_171->action_172 [label="[1]"]; +action_168->action_171 [label="[2]"]; +action_173 [fillcolor="white",label="173.Function"]; +action_168->action_173 [label="[3]"]; +action_174 [fillcolor="white",label="174.ExitBase\nis_force_exit = false\nis_recv_te_exit_succ = false\nis_te_task_started = false\nis_te_task_stoped = false"]; +action_168->action_174 [label="[4]"]; +action_175 [fillcolor="white",label="175.StartSlam\nmode = 2\nrelocate_state = 0"]; +action_168->action_175 [label="[5]"]; +action_162->action_168; +action_146->action_162 [label="1.then"]; +action_148 [fillcolor="white",label="148.Prepare"]; +action_154 [fillcolor="white",label="154.Sequence\nindex = 0\nmode = \"AnyFail\""]; +action_155 [fillcolor="white",label="155.Function\n[clean fault]"]; +action_154->action_155 [label="[0]"]; +action_149 [fillcolor="white",label="149.Check"]; +action_150 [fillcolor="white",label="150.Sequence\nindex = 0\nmode = \"AnyFail\""]; +action_151 [fillcolor="white",label="151.Function"]; +action_150->action_151 [label="[0]"]; +action_152 [fillcolor="white",label="152.SetPowerToWork"]; +action_150->action_152 [label="[1]"]; +action_153 [fillcolor="white",label="153.Function"]; +action_150->action_153 [label="[2]"]; +action_149->action_150; +action_154->action_149 [label="[1]"]; +action_157 [fillcolor="white",label="157.Parallel\n[set power mode and wait 500ms]\nmode = \"AllFinish\""]; +action_156 [fillcolor="white",label="156.WaitLidarReady"]; +action_157->action_156 [label="[0]"]; +action_158 [fillcolor="white",label="158.Sleep"]; +action_157->action_158 [label="[1]"]; +action_154->action_157 [label="[2]"]; +action_159 [fillcolor="white",label="159.Function"]; +action_154->action_159 [label="[3]"]; +action_160 [fillcolor="white",label="160.ExitBase\nis_force_exit = false\nis_recv_te_exit_succ = false\nis_te_task_started = false\nis_te_task_stoped = false"]; +action_154->action_160 [label="[4]"]; +action_161 [fillcolor="white",label="161.StartSlam\nmode = 2\nrelocate_state = 0"]; +action_154->action_161 [label="[5]"]; +action_148->action_154; +action_146->action_148 [label="2.else"]; +action_191->action_146 [label="2.else"]; +action_145->action_191 [label="1.prepare"]; +action_200 [fillcolor="white",label="200.RollerWetting"]; +action_145->action_200 [label="2.wetting"]; +action_198 [fillcolor="white",label="198.DoClean"]; +action_199 [fillcolor="white",label="199.WaitTeFinish"]; +action_198->action_199; +action_145->action_198 [label="3.do_clean"]; +action_192 [fillcolor="white",label="192.CleanTaskRelocateFail"]; +action_145->action_192 [label="4.relocate_fail"]; +action_24->action_145 [label="2.clean"]; +action_241 [fillcolor="orange",label="241.BackBaseChangeWater"]; +action_282 [fillcolor="orange",label="282.ChangeWater\ndischarge_sewage_remain_times = 3\ndiscontacted_return_times = 3\nis_force_fill_water = false\nis_need_discharge_sewage = true\nis_need_fill_water = true\nopen_22v_fail_retry_times = 3\npending_times = 3"]; +action_284 [fillcolor="white",label="284.Sequence\nindex = 0\nmode = \"AnyFail\""]; +action_285 [fillcolor="white",label="285.Prepare"]; +action_291 [fillcolor="white",label="291.Sequence\nindex = 0\nmode = \"AnyFail\""]; +action_292 [fillcolor="white",label="292.Function\n[clean fault]"]; +action_291->action_292 [label="[0]"]; +action_286 [fillcolor="white",label="286.Check"]; +action_287 [fillcolor="white",label="287.Sequence\nindex = 0\nmode = \"AnyFail\""]; +action_288 [fillcolor="white",label="288.Function"]; +action_287->action_288 [label="[0]"]; +action_289 [fillcolor="white",label="289.SetPowerToWork"]; +action_287->action_289 [label="[1]"]; +action_290 [fillcolor="white",label="290.Function"]; +action_287->action_290 [label="[2]"]; +action_286->action_287; +action_291->action_286 [label="[1]"]; +action_294 [fillcolor="white",label="294.Parallel\n[set power mode and wait 500ms]\nmode = \"AllFinish\""]; +action_293 [fillcolor="white",label="293.WaitLidarReady"]; +action_294->action_293 [label="[0]"]; +action_295 [fillcolor="white",label="295.Sleep"]; +action_294->action_295 [label="[1]"]; +action_291->action_294 [label="[2]"]; +action_296 [fillcolor="white",label="296.Function"]; +action_291->action_296 [label="[3]"]; +action_297 [fillcolor="white",label="297.ExitBase\nis_force_exit = true\nis_recv_te_exit_succ = false\nis_te_task_started = false\nis_te_task_stoped = false"]; +action_291->action_297 [label="[4]"]; +action_285->action_291; +action_284->action_285 [label="[0]"]; +action_282->action_284 [label="1.exit_base_seq"]; +action_283 [fillcolor="green",label="283.SetPowerToWork"]; +action_282->action_283 [label="3.set_power_mode"]; +action_338 [fillcolor="white",label="338.Sequence\nindex = 0\nmode = \"AnyFail\""]; +action_339 [fillcolor="white",label="339.SetMucPowerManageMode"]; +action_338->action_339 [label="[0]"]; +action_340 [fillcolor="white",label="340.Open22VoltChannel"]; +action_338->action_340 [label="[1]"]; +action_282->action_338 [label="4.open_22v_channel"]; +action_342 [fillcolor="white",label="342.WaitBtUploadOnBase"]; +action_282->action_342 [label="5.pending_bt_upload_on_base"]; +action_341 [fillcolor="green",label="341.SetMucPowerManageMode"]; +action_282->action_341 [label="6.close_mcu_charge_mode"]; +action_344 [fillcolor="orange",label="344.DoFillWater\nis_need_bt_interaction = true\nwater_base_fill_water_seconds = 12"]; +action_491 [fillcolor="orange",label="491.Sequence\nindex = 2\nmode = \"AnyFail\""]; +action_515 [fillcolor="green",label="515.ConnectBt"]; +action_491->action_515 [label="[0]"]; +action_516 [fillcolor="green",label="516.WaitCombinedBaseStatusSync"]; +action_491->action_516 [label="[1]"]; +action_494 [fillcolor="orange",label="494.IfElse"]; +action_495 [fillcolor="green",label="495.Function\n[check_is_need_send_water_base_fill_water_cmd]"]; +action_494->action_495 [label="0.if"]; +action_497 [fillcolor="orange",label="497.Sequence\nindex = 3\nmode = \"AnyFail\""]; +action_493 [fillcolor="green",label="493.Function\n[update_water_base_fill_water_seconds_action]"]; +action_497->action_493 [label="[0]"]; +action_492 [fillcolor="green",label="492.SendOpenBaseValveCmdAction"]; +action_497->action_492 [label="[1]"]; +action_496 [fillcolor="green",label="496.Sleep"]; +action_497->action_496 [label="[2]"]; +action_498 [fillcolor="orange",label="498.Sequence\nindex = 2\nmode = \"AnyFail\""]; +action_499 [fillcolor="green",label="499.SetMucPowerManageMode"]; +action_498->action_499 [label="[0]"]; +action_500 [fillcolor="green",label="500.SendFillWaterWithCleaningCiquidCmd"]; +action_498->action_500 [label="[1]"]; +action_501 [fillcolor="orange",label="501.Sleep"]; +action_498->action_501 [label="[2]"]; +action_502 [fillcolor="white",label="502.Sequence\nindex = 0\nmode = \"AnyFail\""]; +action_503 [fillcolor="white",label="503.Function"]; +action_502->action_503 [label="[0]"]; +action_504 [fillcolor="white",label="504.Sleep"]; +action_502->action_504 [label="[1]"]; +action_505 [fillcolor="white",label="505.Function"]; +action_502->action_505 [label="[2]"]; +action_498->action_502 [label="[3]"]; +action_497->action_498 [label="[3]"]; +action_494->action_497 [label="1.then"]; +action_506 [fillcolor="white",label="506.Sequence\nindex = 0\nmode = \"AnyFail\""]; +action_507 [fillcolor="white",label="507.Sequence\nindex = 0\nmode = \"AnyFail\""]; +action_508 [fillcolor="white",label="508.SetMucPowerManageMode"]; +action_507->action_508 [label="[0]"]; +action_509 [fillcolor="white",label="509.SendFillWaterWithCleaningCiquidCmd"]; +action_507->action_509 [label="[1]"]; +action_510 [fillcolor="white",label="510.Sleep"]; +action_507->action_510 [label="[2]"]; +action_511 [fillcolor="white",label="511.Sequence\nindex = 0\nmode = \"AnyFail\""]; +action_512 [fillcolor="white",label="512.Function"]; +action_511->action_512 [label="[0]"]; +action_513 [fillcolor="white",label="513.Sleep"]; +action_511->action_513 [label="[1]"]; +action_514 [fillcolor="white",label="514.Function"]; +action_511->action_514 [label="[2]"]; +action_507->action_511 [label="[3]"]; +action_506->action_507 [label="[0]"]; +action_494->action_506 [label="2.else"]; +action_491->action_494 [label="[2]"]; +action_344->action_491 [label="1.filling_water"]; +action_485 [fillcolor="white",label="485.Sequence\nindex = 0\nmode = \"AnyFail\""]; +action_486 [fillcolor="white",label="486.SetMucPowerManageMode"]; +action_485->action_486 [label="[0]"]; +action_487 [fillcolor="white",label="487.SendCloseBaseValveCmd"]; +action_485->action_487 [label="[1]"]; +action_488 [fillcolor="white",label="488.SendCloseBaseValveCmd"]; +action_485->action_488 [label="[2]"]; +action_489 [fillcolor="white",label="489.Function"]; +action_485->action_489 [label="[3]"]; +action_490 [fillcolor="white",label="490.Sleep"]; +action_485->action_490 [label="[4]"]; +action_344->action_485 [label="2.fill_water_fulled"]; +action_282->action_344 [label="7.do_dill_water"]; +action_343 [fillcolor="green",label="343.DischargeSewage"]; +action_282->action_343 [label="8.discharge_sewage"]; +action_241->action_282 [label="2.change_water"]; +action_24->action_241 [label="3.change_water"]; +action_345 [fillcolor="white",label="345.BackWashExtend\nis_allow_interrupt = false\nis_succ_all_action = true"]; +action_346 [fillcolor="white",label="346.BackWashOnly\nis_allow_interrupt = false\nis_self_clean_finish = false\nis_succ_all_action = true\nis_water_empty_during_self_clean = false\nself_clean_time = 60"]; +action_347 [fillcolor="white",label="347.SetPowerToWork"]; +action_346->action_347 [label="0.conncet_bt_action"]; +action_388 [fillcolor="white",label="388.ChangeWater\ndischarge_sewage_remain_times = 3\ndiscontacted_return_times = 3\nis_force_fill_water = false\nis_need_discharge_sewage = true\nis_need_fill_water = true\nopen_22v_fail_retry_times = 3\npending_times = 3"]; +action_390 [fillcolor="white",label="390.Sequence\nindex = 0\nmode = \"AnyFail\""]; +action_391 [fillcolor="white",label="391.Prepare"]; +action_397 [fillcolor="white",label="397.Sequence\nindex = 0\nmode = \"AnyFail\""]; +action_398 [fillcolor="white",label="398.Function\n[clean fault]"]; +action_397->action_398 [label="[0]"]; +action_392 [fillcolor="white",label="392.Check"]; +action_393 [fillcolor="white",label="393.Sequence\nindex = 0\nmode = \"AnyFail\""]; +action_394 [fillcolor="white",label="394.Function"]; +action_393->action_394 [label="[0]"]; +action_395 [fillcolor="white",label="395.SetPowerToWork"]; +action_393->action_395 [label="[1]"]; +action_396 [fillcolor="white",label="396.Function"]; +action_393->action_396 [label="[2]"]; +action_392->action_393; +action_397->action_392 [label="[1]"]; +action_400 [fillcolor="white",label="400.Parallel\n[set power mode and wait 500ms]\nmode = \"AllFinish\""]; +action_399 [fillcolor="white",label="399.WaitLidarReady"]; +action_400->action_399 [label="[0]"]; +action_401 [fillcolor="white",label="401.Sleep"]; +action_400->action_401 [label="[1]"]; +action_397->action_400 [label="[2]"]; +action_402 [fillcolor="white",label="402.Function"]; +action_397->action_402 [label="[3]"]; +action_403 [fillcolor="white",label="403.ExitBase\nis_force_exit = true\nis_recv_te_exit_succ = false\nis_te_task_started = false\nis_te_task_stoped = false"]; +action_397->action_403 [label="[4]"]; +action_391->action_397; +action_390->action_391 [label="[0]"]; +action_388->action_390 [label="1.exit_base_seq"]; +action_389 [fillcolor="white",label="389.SetPowerToWork"]; +action_388->action_389 [label="3.set_power_mode"]; +action_444 [fillcolor="white",label="444.Sequence\nindex = 0\nmode = \"AnyFail\""]; +action_445 [fillcolor="white",label="445.SetMucPowerManageMode"]; +action_444->action_445 [label="[0]"]; +action_446 [fillcolor="white",label="446.Open22VoltChannel"]; +action_444->action_446 [label="[1]"]; +action_388->action_444 [label="4.open_22v_channel"]; +action_448 [fillcolor="white",label="448.WaitBtUploadOnBase"]; +action_388->action_448 [label="5.pending_bt_upload_on_base"]; +action_447 [fillcolor="white",label="447.SetMucPowerManageMode"]; +action_388->action_447 [label="6.close_mcu_charge_mode"]; +action_450 [fillcolor="white",label="450.DoFillWater\nis_need_bt_interaction = true\nwater_base_fill_water_seconds = 0"]; +action_388->action_450 [label="7.do_dill_water"]; +action_449 [fillcolor="white",label="449.DischargeSewage"]; +action_388->action_449 [label="8.discharge_sewage"]; +action_346->action_388 [label="3.change_water"]; +action_451 [fillcolor="white",label="451.DoSelfClean"]; +action_452 [fillcolor="white",label="452.Sequence\nindex = 0\nmode = \"AnyFail\""]; +action_453 [fillcolor="white",label="453.LoopIf"]; +action_454 [fillcolor="white",label="454.Function"]; +action_453->action_454 [label="0.if"]; +action_455 [fillcolor="white",label="455.Sleep"]; +action_453->action_455 [label="1.exec"]; +action_452->action_453 [label="[0]"]; +action_451->action_452; +action_346->action_451 [label="4.do_self_clean"]; +action_456 [fillcolor="white",label="456.RinseFilter"]; +action_457 [fillcolor="white",label="457.Sequence\nindex = 0\nmode = \"AnyFail\""]; +action_458 [fillcolor="white",label="458.Function"]; +action_457->action_458 [label="[0]"]; +action_459 [fillcolor="white",label="459.SendOpenBaseValveCmdAction"]; +action_457->action_459 [label="[1]"]; +action_460 [fillcolor="white",label="460.Sleep"]; +action_457->action_460 [label="[2]"]; +action_461 [fillcolor="white",label="461.SendDischargeSewageCmd"]; +action_457->action_461 [label="[3]"]; +action_462 [fillcolor="white",label="462.Sleep"]; +action_457->action_462 [label="[4]"]; +action_456->action_457; +action_346->action_456 [label="5.rinse_filter"]; +action_345->action_346 [label="1.back_wash_only_"]; +action_463 [fillcolor="white",label="463.Sequence\nindex = 0\nmode = \"AnyFail\""]; +action_464 [fillcolor="white",label="464.SetMucPowerManageMode"]; +action_463->action_464 [label="[0]"]; +action_465 [fillcolor="white",label="465.Open22VoltChannel"]; +action_463->action_465 [label="[1]"]; +action_466 [fillcolor="white",label="466.Sleep"]; +action_463->action_466 [label="[2]"]; +action_345->action_463 [label="2.discharge_to_water_base"]; +action_24->action_345 [label="4.back_wash"]; +action_467 [fillcolor="white",label="467.SaveMapWaitLabelChange\nis_auto_save = false"]; +action_468 [fillcolor="white",label="468.Sequence\nindex = 0\nmode = \"AnyFail\""]; +action_469 [fillcolor="white",label="469.SaveMap"]; +action_468->action_469 [label="[0]"]; +action_474 [fillcolor="white",label="474.IfElse"]; +action_470 [fillcolor="white",label="470.Function"]; +action_474->action_470 [label="0.if"]; +action_471 [fillcolor="white",label="471.Sequence\nindex = 0\nmode = \"AnyFail\""]; +action_472 [fillcolor="white",label="472.Function"]; +action_471->action_472 [label="[0]"]; +action_473 [fillcolor="white",label="473.WaitLabelChange"]; +action_471->action_473 [label="[1]"]; +action_474->action_471 [label="1.then"]; +action_468->action_474 [label="[1]"]; +action_467->action_468; +action_24->action_467 [label="6.save_map"]; +} + + diff --git a/tools/graphviz_render/images/image-1.png b/tools/graphviz_render/images/image-1.png new file mode 100644 index 0000000000000000000000000000000000000000..014867aaf1281bee4b1e510b53dc30cf15dba913 Binary files /dev/null and b/tools/graphviz_render/images/image-1.png differ diff --git a/tools/graphviz_render/images/image.png b/tools/graphviz_render/images/image.png new file mode 100644 index 0000000000000000000000000000000000000000..b7c6890cad636fbcdacbc2f4f9993f266fbb7c82 Binary files /dev/null and b/tools/graphviz_render/images/image.png differ diff --git a/tools/graphviz_render/src/data_processing.py b/tools/graphviz_render/src/data_processing.py new file mode 100644 index 0000000000000000000000000000000000000000..a03ccb3e24323c4ed40847d467e4bfceedec5fbc --- /dev/null +++ b/tools/graphviz_render/src/data_processing.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python3 + +import os +import subprocess +import threading +from io import BytesIO + +from PIL import Image +from PyQt5.QtGui import QPixmap, QImage +from PyQt5.QtCore import QThread, pyqtSignal + +class DataProcessing(QThread): + data_received = pyqtSignal(QPixmap) + + def __init__(self): + super().__init__() + # 共享数据保护 + self._lock = threading.Lock() + self._cond = threading.Condition(self._lock) # 数据变更条件变量 + + # 共享状态变量 + self._last_dot_data = '' # 最后接收的DOT数据 + self._running = True # 线程运行标志 + self._original_image = None # 原始图像缓存 + self._pending_update = False # 更新标记 + + def run(self): + """线程主循环(条件变量优化版)""" + while True: + with self._cond: + # 等待数据变更 + self._cond.wait_for(lambda: self._pending_update or (not self._running)) + + if not self._running: + break + + # 处理待更新数据 + self._process_update() + self._pending_update = False + + def _process_update(self): + """处理数据更新(受保护方法)""" + current_data = self._last_dot_data + + try: + # 生成图像(耗时操作) + image = self._render_dot(current_data) + if image: + self._original_image = image + self._update_display() + except Exception as e: + print(f"Render error: {e}") + + def _render_dot(self, dot_data: str) -> Image.Image: + """DOT数据渲染方法""" + proc = subprocess.Popen( + ['dot', '-Tpng'], + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE + ) + stdout, stderr = proc.communicate(dot_data.encode()) + + if proc.returncode != 0: + raise RuntimeError(f"Graphviz error: {stderr.decode()}") + + return Image.open(BytesIO(stdout)) + + + def _update_display(self): + """图像显示更新""" + if self._original_image is None: + return + + try: + # 转换为Qt兼容格式 + qimg = self._pil_to_qimage(self._original_image) + pixmap = QPixmap.fromImage(qimg) + + # 发射信号(跨线程安全) + self.data_received.emit(pixmap) + except Exception as e: + print(f"Display error: {e}") + + @staticmethod + def _pil_to_qimage(pil_img: Image.Image) -> QImage: + """PIL图像转QImage(优化版)""" + if pil_img.mode == 'RGBA': + fmt = QImage.Format_RGBA8888 + else: + pil_img = pil_img.convert('RGBA') + fmt = QImage.Format_RGBA8888 + + return QImage( + pil_img.tobytes(), + pil_img.width, + pil_img.height, + pil_img.width * 4, + fmt + ) + + def receive_data(self, dot_data: str): + """接收新数据(线程安全入口)""" + with self._cond: + if dot_data != self._last_dot_data: + self._last_dot_data = dot_data + self._pending_update = True + self._cond.notify() + + def stop(self): + """安全停止线程""" + with self._cond: + self._running = False + self._cond.notify_all() # 唤醒可能处于等待的线程 \ No newline at end of file diff --git a/tools/graphviz_render/src/data_source.py b/tools/graphviz_render/src/data_source.py new file mode 100644 index 0000000000000000000000000000000000000000..386a806b26e3a93e5ec2ca2808170bb7d32c91c5 --- /dev/null +++ b/tools/graphviz_render/src/data_source.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 + +from PyQt5.QtCore import QThread, pyqtSignal + +exit_str = "pipe_read_exit" + +class DataSource(QThread): + data_received = pyqtSignal(str) + + def __init__(self, pipe_name): + super().__init__() + self.pipe_name = pipe_name + self.running = True + + def run(self): + while self.running: + try: + with open(self.pipe_name, 'r') as pipe: + while self.running: + data = pipe.read() + if data: + self.data_received.emit(data) + self.msleep(10) + + except Exception as e: + print(f"Error reading from pipe: {e}") + self.msleep(1000) + + def stop(self): + self.running = False + with open(self.pipe_name, 'w') as pipe: + pipe.write(exit_str) \ No newline at end of file diff --git a/tools/graphviz_render/src/main.py b/tools/graphviz_render/src/main.py new file mode 100755 index 0000000000000000000000000000000000000000..14e092c8e43bd6e1e95449fa133948e797ce8d6e --- /dev/null +++ b/tools/graphviz_render/src/main.py @@ -0,0 +1,41 @@ +import os +import sys +import signal + +from ui.viewer import GraphvizViewer +from signal_handler import SignalHandler +from PyQt5.QtWidgets import (QApplication) + +def create_pipe(pipe_name): + if os.path.exists(pipe_name) : + os.remove(pipe_name) + os.mkfifo(pipe_name) + +def print_usage(proc_name): + print("This is a real-time rendering tool for Graphviz graphics.") + print("Author: Hevake Lee\n") + print(f"Usage: {proc_name} /some/where/you_pipe_file\n") + +def main(): + if '-h' in sys.argv or '--help' in sys.argv: + print_usage(sys.argv[0]) + sys.exit(0) + + if len(sys.argv) != 2: + print_usage(sys.argv[0]) + sys.exit(1) + + pipe_name = sys.argv[1] + create_pipe(pipe_name) + + app = QApplication(sys.argv) + viewer = GraphvizViewer(pipe_name) + handler = SignalHandler() + handler.sig_interrupt.connect(app.quit) + viewer.show() + sys.exit(app.exec()) + + os.remove(pipe_name) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/tools/graphviz_render/src/requirements.txt b/tools/graphviz_render/src/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..72e03b1c518ae7e4d7f2616a2bde70158d529d50 --- /dev/null +++ b/tools/graphviz_render/src/requirements.txt @@ -0,0 +1,4 @@ +Pillow==9.0.0 +Pillow==7.0.0 +Pillow==11.1.0 +PyQt5==5.15.11 diff --git a/tools/graphviz_render/src/signal_handler.py b/tools/graphviz_render/src/signal_handler.py new file mode 100644 index 0000000000000000000000000000000000000000..eb97aa53ce638703d636f565d4d47c3a0518d03b --- /dev/null +++ b/tools/graphviz_render/src/signal_handler.py @@ -0,0 +1,14 @@ +import signal +from PyQt5.QtCore import QObject, pyqtSignal, QTimer + +class SignalHandler(QObject): + sig_interrupt = pyqtSignal() + + def __init__(self): + super().__init__() + # 注册系统信号到Qt信号转发 + signal.signal(signal.SIGINT, self.handle_signal) + + def handle_signal(self, signum, _): + print(f"捕获到信号 {signum}") + self.sig_interrupt.emit() # 通过Qt信号触发主线程退出 \ No newline at end of file diff --git a/tools/graphviz_render/src/transform/__init__.py b/tools/graphviz_render/src/transform/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..7849877bb37870fcd2399ea532067521a25b8d78 --- /dev/null +++ b/tools/graphviz_render/src/transform/__init__.py @@ -0,0 +1 @@ +from .zoomable import ZoomableGraphicsView \ No newline at end of file diff --git a/tools/graphviz_render/src/transform/zoomable.py b/tools/graphviz_render/src/transform/zoomable.py new file mode 100644 index 0000000000000000000000000000000000000000..087bdbea0039d725dfb522625d0d351d7b1f3d88 --- /dev/null +++ b/tools/graphviz_render/src/transform/zoomable.py @@ -0,0 +1,238 @@ +from PyQt5.QtCore import pyqtProperty +from PyQt5.QtGui import QPixmap, QWheelEvent, QMouseEvent, QKeyEvent, QPainter, QColor +from PyQt5.QtCore import Qt, QPointF, QPropertyAnimation, QTimer, QEasingCurve +from PyQt5.QtWidgets import (QGraphicsView, QGraphicsScene, QGraphicsPixmapItem, QOpenGLWidget, QGraphicsEllipseItem) + + +class ZoomableGraphicsView(QGraphicsView): + def __init__(self, parent=None): + super().__init__(parent) + + # 初始化视图设置 + self._setup_view() + self._setup_scene() + self._setup_interaction() + self._setup_indicators() + + # 初始化参数 + self._zoom_factor = 1.0 + self.min_zoom = 0.1 + self.max_zoom = 20.0 + self.dragging = False + self.fisrt_refresh = True + self.last_mouse_pos = QPointF() + + # 添加属性声明 + @pyqtProperty(float) + def zoom_factor(self): + return self._zoom_factor + + @zoom_factor.setter + def zoom_factor(self, value): + # 计算缩放比例差异 + ratio = value / self._zoom_factor + self._zoom_factor = value + # 应用缩放变换 + self.scale(ratio, ratio) + + def _setup_view(self): + """配置视图参数""" + self.setViewport(QOpenGLWidget()) # OpenGL加速 + self.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform | QPainter.TextAntialiasing) + self.setCacheMode(QGraphicsView.CacheBackground) + self.setViewportUpdateMode(QGraphicsView.FullViewportUpdate) + + def _setup_scene(self): + """配置场景和图像项""" + self.scene = QGraphicsScene(self) + self.scene.setSceneRect(-1e6, -1e6, 2e6, 2e6) # 超大场景范围 + self.setScene(self.scene) + + self.pixmap_item = QGraphicsPixmapItem() + self.pixmap_item.setTransformationMode(Qt.SmoothTransformation) + self.pixmap_item.setShapeMode(QGraphicsPixmapItem.BoundingRectShape) + self.scene.addItem(self.pixmap_item) + + def _setup_interaction(self): + """配置交互参数""" + self.setDragMode(QGraphicsView.NoDrag) + self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse) + self.setResizeAnchor(QGraphicsView.AnchorUnderMouse) + self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self.setMouseTracking(True) + + def _setup_indicators(self): + """配置视觉指示器""" + # 居中指示器 + self.center_indicator = QGraphicsEllipseItem(-8, -8, 16, 16) + self.center_indicator.setPen(QColor(255, 0, 0, 150)) + self.center_indicator.setBrush(QColor(255, 0, 0, 50)) + self.center_indicator.setZValue(100) + self.center_indicator.setVisible(False) + self.scene.addItem(self.center_indicator) + + def update_image(self, pixmap: QPixmap): + """更新图像并自适应视图""" + self.pixmap_item.setPixmap(pixmap) + self._center_pixmap(pixmap) + if self.fisrt_refresh: + self.fisrt_refresh = False + self.fit_to_view() + + def _center_pixmap(self, pixmap: QPixmap): + """居中放置图元""" + self.pixmap_item.setPos(-pixmap.width()/2, -pixmap.height()/2) + self.scene.setSceneRect(-1e6, -1e6, 2e6, 2e6) # 重置场景范围 + + def fit_to_view(self): + """自适应窗口显示""" + if self.pixmap_item.pixmap().isNull(): + return + + # 自动计算场景中的图元边界 + rect = self.pixmap_item.sceneBoundingRect() + self.fitInView(rect, Qt.KeepAspectRatio) + self._zoom_factor = 1.0 + + def wheelEvent(self, event: QWheelEvent): + """滚轮缩放处理""" + zoom_in = event.angleDelta().y() > 0 + factor = 1.25 if zoom_in else 0.8 + new_zoom = self._zoom_factor * factor + + # 应用缩放限制 + if self.min_zoom <= new_zoom <= self.max_zoom: + self.scale(factor, factor) + self._zoom_factor = new_zoom + + def keyPressEvent(self, event: QKeyEvent): + """增加键盘快捷键支持""" + new_zoom = 0.0 + factor = 0.0 + if event.modifiers() == Qt.ControlModifier: + if event.key() == Qt.Key_Left: # Ctrl + Left + event.accept() + factor = 0.8 + new_zoom = self._zoom_factor * factor + + elif event.key() == Qt.Key_Right: # Ctrl + right + event.accept() + factor = 1.25 + new_zoom = self._zoom_factor * factor + + # 应用缩放限制 + if self.min_zoom <= new_zoom <= self.max_zoom: + self.scale(factor, factor) + self._zoom_factor = new_zoom + return + + super().keyPressEvent(event) + + def mousePressEvent(self, event: QMouseEvent): + """鼠标按下事件处理""" + # 左键拖拽 + if event.button() in (Qt.RightButton, Qt.LeftButton): + self.dragging = True + self.last_mouse_pos = event.pos() + self.setCursor(Qt.ClosedHandCursor) + + super().mousePressEvent(event) + + def mouseDoubleClickEvent(self, event: QMouseEvent): + """鼠标双击事件处理(增强版)""" + if event.button() == Qt.RightButton: + event.accept() + self._fit_and_center_animation() # 改为新的组合动画方法 + return + elif event.button() == Qt.LeftButton: + event.accept() + factor = 2 + new_zoom = self._zoom_factor * factor + + # 应用缩放限制 + if self.min_zoom <= new_zoom <= self.max_zoom: + self.scale(factor, factor) + self._zoom_factor = new_zoom + return + + super().mouseDoubleClickEvent(event) + + def mouseMoveEvent(self, event: QMouseEvent): + """鼠标移动事件处理""" + if self.dragging: + delta = event.pos() - self.last_mouse_pos + self.last_mouse_pos = event.pos() + + # 更新滚动条实现拖拽 + self.horizontalScrollBar().setValue( + self.horizontalScrollBar().value() - delta.x()) + self.verticalScrollBar().setValue( + self.verticalScrollBar().value() - delta.y()) + + super().mouseMoveEvent(event) + + def mouseReleaseEvent(self, event: QMouseEvent): + """鼠标释放事件处理""" + if event.button() in (Qt.RightButton, Qt.LeftButton): + self.dragging = False + self.setCursor(Qt.ArrowCursor) + super().mouseReleaseEvent(event) + + def _fit_and_center_animation(self): + """自适应并居中动画组合""" + if self.pixmap_item.pixmap().isNull(): + return + + # 先执行自适应调整 + self.fit_to_view() + + # 获取最终场景中心坐标 + final_center = self.pixmap_item.sceneBoundingRect().center() + + # 创建组合动画 + self._create_center_animation(final_center) + + def _create_center_animation(self, target_center: QPointF): + """创建居中动画序列""" + # 平移动画 + anim_h = QPropertyAnimation(self.horizontalScrollBar(), b"value") + anim_v = QPropertyAnimation(self.verticalScrollBar(), b"value") + + # 缩放动画 + current_zoom = self._zoom_factor + anim_zoom = QPropertyAnimation(self, b"zoom_factor") + anim_zoom.setDuration(400) + anim_zoom.setStartValue(current_zoom) + anim_zoom.setEndValue(1.0) # 自适应后的标准缩放值 + + # 配置动画参数 + for anim in [anim_h, anim_v]: + anim.setDuration(400) + anim.setEasingCurve(QEasingCurve.OutQuad) + + # 计算目标滚动值 + view_center = self.mapToScene(self.viewport().rect().center()) + delta = target_center - view_center + + # 设置动画参数 + anim_h.setStartValue(self.horizontalScrollBar().value()) + anim_h.setEndValue(self.horizontalScrollBar().value() + delta.x()) + + anim_v.setStartValue(self.verticalScrollBar().value()) + anim_v.setEndValue(self.verticalScrollBar().value() + delta.y()) + + # 启动动画 + anim_zoom.start() + anim_h.start() + anim_v.start() + + # 显示指示器 + self.center_indicator.setPos(target_center) + self.center_indicator.setVisible(True) + QTimer.singleShot(800, lambda: self.center_indicator.setVisible(False)) + + def resizeEvent(self, event): + """窗口大小变化时保持自适应""" + self.fit_to_view() + super().resizeEvent(event) \ No newline at end of file diff --git a/tools/graphviz_render/src/transform/zoomable_cpu.py b/tools/graphviz_render/src/transform/zoomable_cpu.py new file mode 100644 index 0000000000000000000000000000000000000000..7f4e214f654d7536e259a7ac38518e438fb3e00f --- /dev/null +++ b/tools/graphviz_render/src/transform/zoomable_cpu.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 + +from PyQt5.QtWidgets import QLabel, QApplication, QOpenGLWidget +from PyQt5.QtCore import Qt, QPoint, QTime, QTimer +from PyQt5.QtGui import QPixmap, QPainter, QMouseEvent, QWheelEvent, QImage + +class ZoomableLabel(QLabel): + def __init__(self, parent=None): + super().__init__(parent) + self.zoom_factor = 1.0 + self.min_zoom = 0.1 + self.max_zoom = 10.0 + self.zoom_step = 1.2 + self.panning = False + self.last_pos = None + self.offset = QPoint(0, 0) + self.current_pixmap = None + self.setMouseTracking(True) + self.initial_fit = True + self.last_click_time = 0 + self.last_click_pos = None + self.zoom_cache = {} + self.current_cache_key = None + self.redraw_timer = QTimer(self) + self.redraw_timer.setSingleShot(True) + self.redraw_timer.timeout.connect(self.force_redraw) + self.redraw_pending = False + self.zoom_timer = QTimer(self) + self.zoom_timer.setSingleShot(True) + self.zoom_timer.timeout.connect(self.high_quality_redraw) + + def wheelEvent(self, event: QWheelEvent): + if self.current_pixmap is None: + return + cursor_pos = event.pos() + if event.angleDelta().y() > 0: + self.zoom_at_position(cursor_pos, self.zoom_step) + else: + self.zoom_at_position(cursor_pos, 1.0 / self.zoom_step) + self.zoom_timer.start(500) # 延迟高质量重绘 + + def update_image(self, pixmap: QPixmap, use_fast=False): + if pixmap is None: + return + cache_key = (round(self.zoom_factor, 2), pixmap.size().width(), pixmap.size().height(), use_fast) + if cache_key in self.zoom_cache: + scaled_pixmap = self.zoom_cache[cache_key] + else: + new_width = int(pixmap.width() * self.zoom_factor) + new_height = int(pixmap.height() * self.zoom_factor) + transformation = Qt.FastTransformation if use_fast else Qt.SmoothTransformation + scaled_pixmap = pixmap.scaled(new_width, new_height, Qt.KeepAspectRatio, transformation) + self.zoom_cache[cache_key] = scaled_pixmap + if len(self.zoom_cache) > 10: + self.zoom_cache.pop(next(iter(self.zoom_cache))) + result_pixmap = QPixmap(self.size()) + result_pixmap.fill(Qt.transparent) + painter = QPainter(result_pixmap) + x = (self.width() - scaled_pixmap.width()) // 2 + self.offset.x() + y = (self.height() - scaled_pixmap.height()) // 2 + self.offset.y() + painter.drawPixmap(x, y, scaled_pixmap) + painter.end() + super().setPixmap(result_pixmap) + + def high_quality_redraw(self): + if self.current_pixmap: + self.update_image(self.current_pixmap, use_fast=False) + + def mouseMoveEvent(self, event: QMouseEvent): + if self.panning and self.last_pos is not None: + delta = event.pos() - self.last_pos + self.offset += delta + self.last_pos = event.pos() + if not self.redraw_pending: + self.redraw_pending = True + self.redraw_timer.start(30) + + def force_redraw(self): + if self.redraw_pending and self.current_pixmap: + self.update_image(self.current_pixmap, use_fast=True) + self.redraw_pending = False \ No newline at end of file diff --git a/tools/graphviz_render/src/ui/__init__.py b/tools/graphviz_render/src/ui/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..650d98543e6a937f07ef91662f8290c5deeef131 --- /dev/null +++ b/tools/graphviz_render/src/ui/__init__.py @@ -0,0 +1 @@ +from .viewer import GraphvizViewer \ No newline at end of file diff --git a/tools/graphviz_render/src/ui/viewer.py b/tools/graphviz_render/src/ui/viewer.py new file mode 100644 index 0000000000000000000000000000000000000000..24e7b161812046b40ba0a8c0c60b4c88759a5f01 --- /dev/null +++ b/tools/graphviz_render/src/ui/viewer.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 + +import os +import subprocess + +from io import BytesIO +from PIL import Image +from data_processing import DataProcessing +from data_source import DataSource +from transform.zoomable import ZoomableGraphicsView + + +from PIL import Image +from datetime import datetime +from PyQt5.QtWidgets import (QMainWindow, QLabel, QVBoxLayout,QWidget) +from PyQt5.QtCore import Qt, QTimer +from PyQt5.QtGui import QImage, QPixmap, QFont + +class GraphvizViewer(QMainWindow): + def __init__(self, pipe_name): + super().__init__() + self.pipe_name = pipe_name + self.current_pixmap = None + self.setup_ui() + + def setup_ui(self): + self.setWindowTitle(f"Graphviz: {self.pipe_name}") + self.showMaximized() + + # Create central widget and layout + central_widget = QWidget() + self.setCentralWidget(central_widget) + layout = QVBoxLayout(central_widget) + layout.setContentsMargins(0, 0, 0, 0) # Remove margins + layout.setSpacing(0) # Remove spacing + + # Create timestamp label with minimal height + self.timestamp_label = QLabel() + self.timestamp_label.setAlignment(Qt.AlignmentFlag.AlignCenter) + self.timestamp_label.setStyleSheet("background-color: #f0f0f0; padding: 2px;") + self.timestamp_label.setFont(QFont("Arial", 8)) # Smaller font + self.timestamp_label.setFixedHeight(20) # Fixed height for timestamp + + # Create zoomable image label + self.image_label = ZoomableGraphicsView() + # self.image_label.setAlignment(Qt.AlignmentFlag.AlignCenter) + # self.image_label.setMinimumSize(1, 1) # Allow shrinking + + # Add widgets to main layout + layout.addWidget(self.timestamp_label) + layout.addWidget(self.image_label) + + self.data_processing = DataProcessing() + self.data_processing.data_received.connect(self.update_graph) + self.data_processing.start() + + # Setup pipe reader + self.data_source = DataSource(self.pipe_name) + self.data_source.data_received.connect(self.receive_graph_data) + self.data_source.start() + + # Setup resize timer for debouncing + self.resize_timer = QTimer() + self.resize_timer.setSingleShot(True) + self.resize_timer.timeout.connect(self.update_image) + + # Connect resize event + self.resizeEvent = self.on_resize + + def on_resize(self, event): + super().resizeEvent(event) + # Debounce resize events + # self.resize_timer.start(100) + + def update_graph(self, pixmap): + if pixmap != self.current_pixmap: + self.current_pixmap = pixmap + self.image_label.update_image(self.current_pixmap) + + def updateTime(self): + # Update timestamp + current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + self.timestamp_label.setText(current_time) + + def receive_graph_data(self, data): + self.updateTime() + self.data_processing.receive_data(data) + + def update_image(self): + if self.update_image is None: + return + self.image_label.update_image(self.current_pixmap) + + def closeEvent(self, event): + self.data_source.stop() + self.data_processing.stop() + self.data_source.wait() + self.data_processing.wait() + if os.path.exists(self.pipe_name): + os.remove(self.pipe_name) + event.accept() \ No newline at end of file