├── .gitignore ├── CMakeLists.txt ├── README.md ├── automator.cpp ├── automator.h ├── cameracalibrator.cpp ├── cameracalibrator.h ├── capturecontroller.cpp ├── capturecontroller.hpp ├── cvmatsurfacesource.cpp ├── cvmatsurfacesource.hpp ├── gcodeplayer.cpp ├── gcodeplayer.h ├── gcodeplayeritem.h ├── gcodeplayermodel.cpp ├── gcodeplayermodel.h ├── linedetector.cpp ├── linedetector.h ├── linedetectordatasource.cpp ├── linedetectordatasource.h ├── main.cpp ├── main.qml ├── qtquickcontrols2.conf ├── quick.qrc ├── rayreceiver.cpp └── rayreceiver.h /.gitignore: -------------------------------------------------------------------------------- 1 | # C++ objects and libs 2 | *.slo 3 | *.lo 4 | *.o 5 | *.a 6 | *.la 7 | *.lai 8 | *.so 9 | *.dll 10 | *.dylib 11 | 12 | # Qt-es 13 | object_script.*.Release 14 | object_script.*.Debug 15 | *_plugin_import.cpp 16 | /.qmake.cache 17 | /.qmake.stash 18 | *.pro.user 19 | *.pro.user.* 20 | *.qbs.user 21 | *.qbs.user.* 22 | *.moc 23 | moc_*.cpp 24 | moc_*.h 25 | qrc_*.cpp 26 | ui_*.h 27 | *.qmlc 28 | *.jsc 29 | Makefile* 30 | *build-* 31 | 32 | # Qt unit tests 33 | target_wrapper.* 34 | 35 | # QtCreator 36 | *.autosave 37 | 38 | # QtCreator Qml 39 | *.qmlproject.user 40 | *.qmlproject.user.* 41 | 42 | # QtCreator CMake 43 | CMakeLists.txt.user* 44 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.1) 2 | 3 | project(cnc-vision LANGUAGES CXX) 4 | 5 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 6 | set(CMAKE_AUTOMOC ON) 7 | set(CMAKE_AUTORCC ON) 8 | set(CMAKE_CXX_STANDARD 11) 9 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 10 | 11 | find_package(Qt5 COMPONENTS Core Quick Qml Multimedia Charts REQUIRED) 12 | #find_package(ZeroMQ REQUIRED) 13 | find_package(OpenCV REQUIRED) 14 | 15 | file(GLOB_RECURSE sources *.cpp *.h) 16 | 17 | add_executable(${PROJECT_NAME} 18 | ${sources} 19 | "quick.qrc" 20 | ) 21 | 22 | include_directories(${OpenCV_INCLUDE_DIRS}) 23 | target_compile_definitions(${PROJECT_NAME} PRIVATE $<$,$>:QY_QML_DEBUG>) 24 | target_link_libraries(${PROJECT_NAME} PUBLIC ${OpenCV_LIBS} PRIVATE Qt5::Core Qt5::Quick Qt5::Qml Qt5::Multimedia Qt5::Charts) 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cnc-vision 2 | Laser cutter autofocus, scanning and AOI module 3 | -------------------------------------------------------------------------------- /automator.cpp: -------------------------------------------------------------------------------- 1 | #include "automator.h" 2 | #include 3 | 4 | Automator::Automator(QObject *parent) : QObject(parent) 5 | { 6 | m_working = false; 7 | m_enabled = false; 8 | m_mcConnected = false; 9 | m_lastdzValid = false; 10 | m_lastCoordsValid = false; 11 | m_message = "Waiting for pause"; 12 | m_autosendB = false; 13 | m_autosendPower = false; 14 | m_minPower = 1.0; 15 | m_maxPower = 0.8; 16 | m_lastSentPower = 0.0; 17 | m_powerTimer.setInterval(1000); // maximum power update rate [ms] 18 | m_powerTimer.setSingleShot(true); 19 | 20 | m_mcs_b_initial = 0; 21 | } 22 | 23 | bool Automator::working() const 24 | { 25 | return m_working; 26 | } 27 | 28 | void Automator::ondzChanged(float dz) 29 | { 30 | m_lastdz = dz; 31 | } 32 | 33 | void Automator::ondzValidChanged(bool valid) 34 | { 35 | m_lastdzValid = valid; 36 | checkWorkingState(); 37 | } 38 | 39 | void Automator::onMcConnectionStateChanged(bool connected) 40 | { 41 | m_mcConnected = connected; 42 | checkWorkingState(); 43 | } 44 | 45 | void Automator::onRayConnectionStateChanged(bool connected) 46 | { 47 | m_lastCoordsValid = connected; 48 | } 49 | 50 | void Automator::onCoordsChanged(float x, float y, float z, float b) 51 | { 52 | Q_UNUSED(z); 53 | m_mcs_x = x; 54 | m_mcs_y = y; 55 | m_mcs_b = b; 56 | 57 | if (!m_autosendPower) 58 | return; 59 | 60 | if (m_maxPower < m_minPower) 61 | return; 62 | 63 | if (m_powerTimer.isActive()) 64 | return; 65 | 66 | const float maxX = 2200; 67 | const float minX = 600; 68 | const float maxY = 1500; 69 | const float minY = 0; 70 | const float dPower = 0.01; 71 | float airPathLength = (maxX - m_mcs_x) + (maxY - m_mcs_y); 72 | const float maximumAirPathLength = (maxX - minX) + (maxY - minY); 73 | float powerSpan = m_maxPower - m_minPower; 74 | float dist = airPathLength / maximumAirPathLength; 75 | float targetPower = m_minPower + powerSpan * dist; 76 | 77 | if (fabs(targetPower - m_lastSentPower) < dPower) 78 | return; 79 | 80 | m_lastSentPower = targetPower; 81 | emit changePower(m_lastSentPower / 5.0); 82 | 83 | m_powerTimer.start(); 84 | } 85 | 86 | void Automator::onMcStateChanged(RayReceiver::State s) 87 | { 88 | if (!m_working) 89 | return; 90 | if (s == RayReceiver::Paused) { 91 | float compensated = compensate(m_lastdz); 92 | if (compensated > 10) { 93 | m_message = "No entry in comp table"; 94 | emit messageChanged(); 95 | } else { 96 | float targetB = m_mcs_b_initial + compensated; 97 | QString correction = QString("G90 G0 B%1\n").arg(targetB); 98 | m_message = correction; 99 | if (m_autosendB) { 100 | emit sendToMC(correction); 101 | emit sendToMC("M24\n"); 102 | } 103 | } 104 | } 105 | } 106 | 107 | float Automator::compensate(float dz) const 108 | { 109 | // CAM ERROR 110 | const float map[] = {108, 2.11497190323067, 111 | 107.9, 2.11568618514411, 112 | 107.8, 2.11639574470582, 113 | 107.7, 2.11710058191579, 114 | 107.6, 2.11780069677404, 115 | 107.5, 2.11849608928055, 116 | 107.4, 2.11918675943534, 117 | 107.3, 2.11987270723839, 118 | 107.2, 2.12055393268971, 119 | 107.1, 2.1212304357893, 120 | 107, 2.12190221653716, 121 | 106.9, 2.12256927493329, 122 | 106.8, 2.12323161097769, 123 | 106.7, 2.12388922467036, 124 | 106.6, 2.12454211601129, 125 | 106.5, 2.1251902850005, 126 | 106.4, 2.12583373163797, 127 | 106.3, 2.12647245592371, 128 | 106.2, 2.12710645785772, 129 | 106.1, 2.12773573744, 130 | 106, 2.12836029467055, 131 | 105.9, 2.12898012954937, 132 | 105.8, 2.12959524207646, 133 | 105.7, 2.13020563225182, 134 | 105.6, 2.13081130007544, 135 | 105.5, 2.13141224554733, 136 | 105.4, 2.1320084686675, 137 | 105.3, 2.13259996943593, 138 | 105.2, 2.13318674785263, 139 | 105.1, 2.1337688039176, 140 | 105, 2.13434613763084, 141 | 104.9, 2.13491874899235, 142 | 104.8, 2.13548663800212, 143 | 104.7, 2.13604980466017, 144 | 104.6, 2.13660824896648, 145 | 104.5, 2.13716197092107, 146 | 104.4, 2.13771097052392, 147 | 104.3, 2.13825524777504, 148 | 104.2, 2.13879480267443, 149 | 104.1, 2.13932963522209, 150 | 104, 2.13985974541802, 151 | 103.9, 2.14038513326222, 152 | 103.8, 2.14090579875468, 153 | 103.7, 2.14142174189542, 154 | 103.6, 2.14193296268442, 155 | 103.5, 2.1424394611217, 156 | 103.4, 2.14294123720724, 157 | 103.3, 2.14343829094105, 158 | 103.2, 2.14393062232313, 159 | 103.1, 2.14441823135348, 160 | 103, 2.1449011180321, 161 | 102.9, 2.14537928235898, 162 | 102.8, 2.14585272433414, 163 | 102.7, 2.14632144395756, 164 | 102.6, 2.14678544122926, 165 | 102.5, 2.14724471614922, 166 | 102.4, 2.14769926871745, 167 | 102.3, 2.14814909893395, 168 | 102.2, 2.14859420679872, 169 | 102.1, 2.14903459231176, 170 | 102, 2.14947025547307, 171 | 101.9, 2.14990119628264, 172 | 101.8, 2.15032741474049, 173 | 101.7, 2.1507489108466, 174 | 101.6, 2.15116568460099, 175 | 101.5, 2.15157773600364, 176 | 101.4, 2.15198506505456, 177 | 101.3, 2.15238767175375, 178 | 101.2, 2.15278555610121, 179 | 101.1, 2.15317871809694, 180 | 101, 2.15356715774093, 181 | 100.9, 2.1539508750332, 182 | 100.8, 2.15432986997374, 183 | 100.7, 2.15470414256254, 184 | 100.6, 2.15507369279961, 185 | 100.5, 2.15543852068495, 186 | 100.4, 2.15579862621856, 187 | 100.3, 2.15615400940044, 188 | 100.2, 2.15650467023059, 189 | 100.1, 2.15685060870901, 190 | 100, 2.1571918248357, 191 | 99.9, 2.15752831861065, 192 | 99.8, 2.15786009003388, 193 | 99.7, 2.15818713910537, 194 | 99.6, 2.15850946582513, 195 | 99.5, 2.15882707019316, 196 | 99.4, 2.15913995220946, 197 | 99.3, 2.15944811187403, 198 | 99.2, 2.15975154918687, 199 | 99.1, 2.16005026414798, 200 | 99, 2.16034425675736, 201 | 98.9, 2.160633527015, 202 | 98.8, 2.16091807492091, 203 | 98.7, 2.1611979004751, 204 | 98.6, 2.16147300367755, 205 | 98.5, 2.16174338452827, 206 | 98.4, 2.16200904302726, 207 | 98.3, 2.16226997917452, 208 | 98.2, 2.16252619297005, 209 | 98.1, 2.16277768441384, 210 | 98, 2.16302445350591, 211 | 97.9, 2.16326650024624, 212 | 97.8, 2.16350382463485, 213 | 97.7, 2.16373642667172, 214 | 97.6, 2.16396430635686, 215 | 97.5, 2.16418746369027, 216 | 97.4, 2.16440589867195, 217 | 97.3, 2.1646196113019, 218 | 97.2, 2.16482860158011, 219 | 97.1, 2.1650328695066, 220 | 97, 2.16523241508136, 221 | 96.9, 2.16542723830438, 222 | 96.8, 2.16561733917567, 223 | 96.7, 2.16580271769524, 224 | 96.6, 2.16598337386307, 225 | 96.5, 2.16615930767917, 226 | 96.4, 2.16633051914353, 227 | 96.3, 2.16649700825617, 228 | 96.2, 2.16665877501708, 229 | 96.1, 2.16681581942626, 230 | 96, 2.1669681414837, 231 | 95.9, 2.16711574118941, 232 | 95.8, 2.1672586185434, 233 | 95.7, 2.16739677354565, 234 | 95.6, 2.16753020619617, 235 | 95.5, 2.16765891649496, 236 | 95.4, 2.16778290444202, 237 | 95.3, 2.16790217003734, 238 | 95.2, 2.16801671328094, 239 | 95.1, 2.1681265341728, 240 | 95, 2.16823163271294, 241 | 94.9, 2.16833200890134, 242 | 94.8, 2.16842766273801, 243 | 94.7, 2.16851859422295, 244 | 94.6, 2.16860480335617, 245 | 94.5, 2.16868629013764, 246 | 94.4, 2.16876305456739, 247 | 94.3, 2.16883509664541, 248 | 94.2, 2.16890241637169, 249 | 94.1, 2.16896501374625, 250 | 94, 2.16902288876907, 251 | 93.9, 2.16907604144017, 252 | 93.8, 2.16912447175953, 253 | 93.7, 2.16916817972716, 254 | 93.6, 2.16920716534306, 255 | 93.5, 2.16924142860723, 256 | 93.4, 2.16927096951966, 257 | 93.3, 2.16929578808037, 258 | 93.2, 2.16931588428934, 259 | 93.1, 2.16933125814659, 260 | 93, 2.1693419096521, 261 | 92.9, 2.16934783880588, 262 | 92.8, 2.16934904560794, 263 | 92.7, 2.16934553005826, 264 | 92.6, 2.16933729215685, 265 | 92.5, 2.1693243319037, 266 | 92.4, 2.16930664929883, 267 | 92.3, 2.16928424434223, 268 | 92.2, 2.16925711703389, 269 | 92.1, 2.16922526737382, 270 | 92, 2.16918869536203, 271 | 91.9, 2.1691474009985, 272 | 91.8, 2.16910138428324, 273 | 91.7, 2.16905064521625, 274 | 91.6, 2.16899518379753, 275 | 91.5, 2.16893500002708, 276 | 91.4, 2.16887009390489, 277 | 91.3, 2.16880046543098, 278 | 91.2, 2.16872611460533, 279 | 91.1, 2.16864704142796, 280 | 91, 2.16856324589885, 281 | 90.9, 2.16847472801801, 282 | 90.8, 2.16838148778544, 283 | 90.7, 2.16828352520114, 284 | 90.6, 2.16818084026511, 285 | 90.5, 2.16807343297734, 286 | 90.4, 2.16796130333785, 287 | 90.3, 2.16784445134663, 288 | 90.2, 2.16772287700367, 289 | 90.1, 2.16759658030898, 290 | 90, 2.16746556126256, 291 | 89.9, 2.16732981986441, 292 | 89.8, 2.16718935611453, 293 | 89.7, 2.16704417001292, 294 | 89.6, 2.16689426155958, 295 | 89.5, 2.16673963075451, 296 | 89.4, 2.1665802775977, 297 | 89.3, 2.16641620208917, 298 | 89.2, 2.1662474042289, 299 | 89.1, 2.1660738840169, 300 | 89, 2.16589564145317, 301 | 88.9, 2.16571267653771, 302 | 88.8, 2.16552498927052, 303 | 88.7, 2.1653325796516, 304 | 88.6, 2.16513544768095, 305 | 88.5, 2.16493359335857, 306 | 88.4, 2.16472701668445, 307 | 88.3, 2.1645157176586, 308 | 88.2, 2.16429969628103, 309 | 88.1, 2.16407895255172, 310 | 88, 2.16385348647068, 311 | 87.9, 2.16362329803791, 312 | 87.8, 2.16338838725341, 313 | 87.7, 2.16314875411718, 314 | 87.6, 2.16290439862921, 315 | 87.5, 2.16265532078952, 316 | 87.4, 2.16240152059809, 317 | 87.3, 2.16214299805494, 318 | 87.2, 2.16187975316005, 319 | 87.1, 2.16161178591343, 320 | 87, 2.16133909631508, 321 | 86.9, 2.161061684365, 322 | 86.8, 2.16077955006319, 323 | 86.7, 2.16049269340965, 324 | 86.6, 2.16020111440437, 325 | 86.5, 2.15990481304737, 326 | 86.4, 2.15960378933863, 327 | 86.3, 2.15929804327817, 328 | 86.2, 2.15898757486597, 329 | 86.1, 2.15867238410204, 330 | 86, 2.15835247098638, 331 | 85.9, 2.15802783551899, 332 | 85.8, 2.15769847769987, 333 | 85.7, 2.15736439752901, 334 | 85.6, 2.15702559500643, 335 | 85.5, 2.15668207013211, 336 | 85.4, 2.15633382290607, 337 | 85.3, 2.15598085332829, 338 | 85.2, 2.15562316139878, 339 | 85.1, 2.15526074711754, 340 | 85, 2.15489361048457, 341 | 84.9, 2.15452175149987, 342 | 84.8, 2.15414517016344, 343 | 84.7, 2.15376386647527, 344 | 84.6, 2.15337784043538, 345 | 84.5, 2.15298709204375, 346 | 84.4, 2.1525916213004, 347 | 84.3, 2.15219142820531, 348 | 84.2, 2.15178651275849, 349 | 84.1, 2.15137687495994, 350 | 84, 2.15096251480966, 351 | 83.9, 2.15054343230765, 352 | 83.8, 2.1501196274539, 353 | 83.7, 2.14969110024843, 354 | 83.6, 2.14925785069122, 355 | 83.5, 2.14881987878229, 356 | 83.4, 2.14837718452162, 357 | 83.3, 2.14792976790922, 358 | 83.2, 2.14747762894509, 359 | 83.1, 2.14702076762923, 360 | 83, 2.14655918396164, 361 | 82.9, 2.14609287794232, 362 | 82.8, 2.14562184957127, 363 | 82.7, 2.14514609884848, 364 | 82.6, 2.14466562577397, 365 | 82.5, 2.14418043034772, 366 | 82.4, 2.14369051256974, 367 | 82.3, 2.14319587244003, 368 | 82.2, 2.14269650995859, 369 | 82.1, 2.14219242512542, 370 | 82, 2.14168361794052, 371 | 81.9, 2.14117008840389, 372 | 81.8, 2.14065183651552, 373 | 81.7, 2.14012886227543, 374 | 81.6, 2.1396011656836, 375 | 81.5, 2.13906874674004, 376 | 81.4, 2.13853160544476, 377 | 81.3, 2.13798974179774, 378 | 81.2, 2.13744315579899, 379 | 81.1, 2.13689184744851, 380 | 81, 2.13633581674629, 381 | 80.9, 2.13577506369235, 382 | 80.8, 2.13520958828668, 383 | 80.7, 2.13463939052927, 384 | 80.6, 2.13406447042013, 385 | 80.5, 2.13348482795927, 386 | 80.4, 2.13290046314667, 387 | 80.3, 2.13231137598234, 388 | 80.2, 2.13171756646628, 389 | 80.1, 2.13111903459849, 390 | 80, 2.13051578037896, 391 | 79.9, 2.12990780380771, 392 | 79.8, 2.12929510488472, 393 | 79.7, 2.12867768361001, 394 | 79.6, 2.12805553998356, 395 | 79.5, 2.12742867400538, 396 | 79.4, 2.12679708567547, 397 | 79.3, 2.12616077499383, 398 | 79.2, 2.12551974196046, 399 | 79.1, 2.12487398657536, 400 | 79, 2.12422350883853, 401 | 78.9, 2.12356830874996, 402 | 78.8, 2.12290838630967, 403 | 78.7, 2.12224374151764, 404 | 78.6, 2.12157437437388, 405 | 78.5, 2.12090028487839, 406 | 78.4, 2.12022147303118, 407 | 78.3, 2.11953793883222, 408 | 78.2, 2.11884968228154, 409 | 78.1, 2.11815670337913, 410 | 78, 2.11745900212499, 411 | 77.9, 2.11675657851911, 412 | 77.8, 2.11604943256151, 413 | 77.7, 2.11533756425217, 414 | 77.6, 2.1146209735911, 415 | 77.5, 2.1138996605783, 416 | 77.4, 2.11317362521377, 417 | 77.3, 2.11244286749751, 418 | 77.2, 2.11170738742952, 419 | 77.1, 2.1109671850098, 420 | 77, 2.11022226023834, 421 | 76.9, 2.10947261311516, 422 | 76.8, 2.10871824364024, 423 | 76.7, 2.10795915181359, 424 | 76.6, 2.10719533763521, 425 | 76.5, 2.1064268011051, 426 | 76.4, 2.10565354222326, 427 | 76.3, 2.10487556098969, 428 | 76.2, 2.10409285740439, 429 | 76.1, 2.10330543146736, 430 | 76, 2.10251328317859, 431 | 75.9, 2.1017164125381, 432 | 75.8, 2.10091481954587, 433 | 75.7, 2.10010850420191, 434 | 75.6, 2.09929746650622, 435 | 75.5, 2.0984817064588, 436 | 75.4, 2.09766122405965, 437 | 75.3, 2.09683601930877, 438 | 75.2, 2.09600609220616, 439 | 75.1, 2.09517144275181, 440 | 75, 2.09433207094574, 441 | 74.9, 2.09348797678793, 442 | 74.8, 2.09263916027839, 443 | 74.7, 2.09178562141713, 444 | 74.6, 2.09092736020413, 445 | 74.5, 2.0900643766394, 446 | 74.4, 2.08919667072293, 447 | 74.3, 2.08832424245474, 448 | 74.2, 2.08744709183482, 449 | 74.1, 2.08656521886316, 450 | 74, 2.08567862353978, 451 | 73.9, 2.08478730586466, 452 | 73.8, 2.08389126583781, 453 | 73.7, 2.08299050345923, 454 | 73.6, 2.08208501872893, 455 | 73.5, 2.08117481164688, 456 | 73.4, 2.08025988221311, 457 | 73.3, 2.07934023042761, 458 | 73.2, 2.07841585629038, 459 | 73.1, 2.07748675980141, 460 | 73, 2.07655294096071, 461 | 72.9, 2.07561439976829, 462 | 72.8, 2.07467113622413, 463 | 72.7, 2.07372315032824, 464 | 72.6, 2.07277044208062, 465 | 72.5, 2.07181301148127, 466 | 72.4, 2.07085085853019, 467 | 72.3, 2.06988398322737, 468 | 72.2, 2.06891238557283, 469 | 72.1, 2.06793606556655, 470 | 72, 2.06695502320855, 471 | 71.9, 2.06596925849881, 472 | 71.8, 2.06497877143734, 473 | 71.7, 2.06398356202414, 474 | 71.6, 2.06298363025921, 475 | 71.5, 2.06197897614255, 476 | 71.4, 2.06096959967415, 477 | 71.3, 2.05995550085403, 478 | 71.2, 2.05893667968218, 479 | 71.1, 2.05791313615859, 480 | 71, 2.05688487028327, 481 | 70.9, 2.05585188205623, 482 | 70.8, 2.05481417147745, 483 | 70.7, 2.05377173854694, 484 | 70.6, 2.05272458326469, 485 | 70.5, 2.05167270563072, 486 | 70.4, 2.05061610564502, 487 | 70.3, 2.04955478330758, 488 | 70.2, 2.04848873861842, 489 | 70.1, 2.04741797157752, 490 | 70, 2.0463424821849, 491 | 69.9, 2.04526227044054, 492 | 69.8, 2.04417733634445, 493 | 69.7, 2.04308767989663, 494 | 69.6, 2.04199330109707, 495 | 69.5, 2.04089419994579, 496 | 69.4, 2.03979037644278, 497 | 69.3, 2.03868183058803, 498 | 69.2, 2.03756856238156, 499 | 69.1, 2.03645057182335, 500 | 69, 2.03532785891341, 501 | 68.9, 2.03420042365174, 502 | 68.8, 2.03306826603834, 503 | 68.7, 2.03193138607321, 504 | 68.6, 2.03078978375635, 505 | 68.5, 2.02964345908776, 506 | 68.4, 2.02849241206743, 507 | 68.3, 2.02733664269538, 508 | 68.2, 2.02617615097159, 509 | 68.1, 2.02501093689607, 510 | 68, 2.02384100046883, 511 | 67.9, 2.02266634168985, 512 | 67.8, 2.02148696055914, 513 | 67.7, 2.02030285707669, 514 | 67.6, 2.01911403124252, 515 | 67.5, 2.01792048305662, 516 | 67.4, 2.01672221251898, 517 | 67.3, 2.01551921962962, 518 | 67.2, 2.01431150438852, 519 | 67.1, 2.01309906679569, 520 | 67, 2.01188190685113, 521 | 66.9, 2.01066002455484, 522 | 66.8, 2.00943341990682, 523 | 66.7, 2.00820209290707, 524 | 66.6, 2.00696604355559, 525 | 66.5, 2.00572527185237, 526 | 66.4, 2.00447977779743, 527 | 66.3, 2.00322956139075, 528 | 66.2, 2.00197462263234, 529 | 66.1, 2.00071496152221, 530 | 66, 1.99945057806034, 531 | 65.9, 1.99818147224674, 532 | 65.8, 1.99690764408141, 533 | 65.7, 1.99562909356434, 534 | 65.6, 1.99434582069555, 535 | 65.5, 1.99305782547503, 536 | 65.4, 1.99176510790277, 537 | 65.3, 1.99046766797878, 538 | 65.2, 1.98916550570307, 539 | 65.1, 1.98785862107562, 540 | 65, 1.98654701409644, 541 | 64.9, 1.98523068476553, 542 | 64.8, 1.98390963308288, 543 | 64.7, 1.98258385904851, 544 | 64.6, 1.98125336266241, 545 | 64.5, 1.97991814392457, 546 | 64.4, 1.97857820283501, 547 | 64.3, 1.97723353939371, 548 | 64.2, 1.97588415360068, 549 | 64.1, 1.97453004545592, 550 | 64, 1.97317121495943, 551 | 63.9, 1.97180766211121, 552 | 63.8, 1.97043938691126, 553 | 63.7, 1.96906638935957, 554 | 63.6, 1.96768866945616, 555 | 63.5, 1.96630622720101, 556 | 63.4, 1.96491906259414, 557 | 63.3, 1.96352717563553, 558 | 63.2, 1.96213056632519, 559 | 63.1, 1.96072923466312, 560 | 63, 1.95932318064932, 561 | 62.9, 1.95791240428379, 562 | 62.8, 1.95649690556653, 563 | 62.7, 1.95507668449753, 564 | 62.6, 1.95365174107681, 565 | 62.5, 1.95222207530435, 566 | 62.4, 1.95078768718016, 567 | 62.3, 1.94934857670425, 568 | 62.2, 1.9479047438766, 569 | 62.1, 1.94645618869722, 570 | 62, 1.9450029111661, 571 | 61.9, 1.94354491128326, 572 | 61.8, 1.94208218904869, 573 | 61.7, 1.94061474446239, 574 | 61.6, 1.93914257752435, 575 | 61.5, 1.93766568823458, 576 | 61.4, 1.93618407659309, 577 | 61.3, 1.93469774259986, 578 | 61.2, 1.9332066862549, 579 | 61.1, 1.93171090755821, 580 | 61, 1.93021040650979, 581 | 60.9, 1.92870518310963, 582 | 60.8, 1.92719523735775, 583 | 60.7, 1.92568056925413, 584 | 60.6, 1.92416117879879, 585 | 60.5, 1.92263706599171, 586 | 60.4, 1.9211082308329, 587 | 60.3, 1.91957467332236, 588 | 60.2, 1.91803639346009, 589 | 60.1, 1.91649339124609, 590 | 60, 1.91494566668036, 591 | 59.9, 1.9133932197629, 592 | 59.8, 1.9118360504937, 593 | 59.7, 1.91027415887278, 594 | 59.6, 1.90870754490012, 595 | 59.5, 1.90713620857573, 596 | 59.4, 1.90556014989962, 597 | 59.3, 1.90397936887177, 598 | 59.2, 1.90239386549219, 599 | 59.1, 1.90080363976087, 600 | 59, 1.89920869167783, 601 | 58.9, 1.89760902124306, 602 | 58.8, 1.89600462845655, 603 | 58.7, 1.89439551331832, 604 | 58.6, 1.89278167582835, 605 | 58.5, 1.89116311598665, 606 | 58.4, 1.88953983379322, 607 | 58.3, 1.88791182924806, 608 | 58.2, 1.88627910235117, 609 | 58.1, 1.88464165310255, 610 | 58, 1.8829994815022, 611 | 57.9, 1.88135258755011, 612 | 57.8, 1.8797009712463, 613 | 57.7, 1.87804463259075, 614 | 57.6, 1.87638357158348, 615 | 57.5, 1.87471778822447, 616 | 57.4, 1.87304728251373, 617 | 57.3, 1.87137205445126, 618 | 57.2, 1.86969210403706, 619 | 57.1, 1.86800743127112, 620 | 57, 1.86631803615346, 621 | 56.9, 1.86462391868406, 622 | 56.8, 1.86292507886294, 623 | 56.7, 1.86122151669008, 624 | 56.6, 1.85951323216549, 625 | 56.5, 1.85780022528918, 626 | 56.4, 1.85608249606113, 627 | 56.3, 1.85436004448135, 628 | 56.2, 1.85263287054983, 629 | 56.1, 1.85090097426659, 630 | 56, 1.84916435563162, 631 | 55.9, 1.84742301464491, 632 | 55.8, 1.84567695130648, 633 | 55.7, 1.84392616561631, 634 | 55.6, 1.84217065757441, 635 | 55.5, 1.84041042718078, 636 | 55.4, 1.83864547443542, 637 | 55.3, 1.83687579933833, 638 | 55.2, 1.83510140188951, 639 | 55.1, 1.83332228208895, 640 | 55, 1.83153843993667, 641 | 54.9, 1.82974987543265, 642 | 54.8, 1.82795658857691, 643 | 54.7, 1.82615857936943, 644 | 54.6, 1.82435584781022, 645 | 54.5, 1.82254839389928, 646 | 54.4, 1.82073621763661, 647 | 54.3, 1.81891931902221, 648 | 54.2, 1.81709769805607, 649 | 54.1, 1.81527135473821, 650 | 54, 1.81344028906862, 651 | 53.9, 1.81160450104729, 652 | 53.8, 1.80976399067423, 653 | 53.7, 1.80791875794944, 654 | 53.6, 1.80606880287293, 655 | 53.5, 1.80421412544468, 656 | 53.4, 1.80235472566469, 657 | 53.3, 1.80049060353298, 658 | 53.2, 1.79862175904954, 659 | 53.1, 1.79674819221436, 660 | 53, 1.79486990302746, 661 | 52.9, 1.79298689148882, 662 | 52.8, 1.79109915759845, 663 | 52.7, 1.78920670135636, 664 | 52.6, 1.78730952276253, 665 | 52.5, 1.78540762181697, 666 | 52.4, 1.78350099851967, 667 | 52.3, 1.78158965287065, 668 | 52.2, 1.7796735848699, 669 | 52.1, 1.77775279451741, 670 | 52, 1.7758272818132, 671 | 51.9, 1.77389704675725, 672 | 51.8, 1.77196208934957, 673 | 51.7, 1.77002240959016, 674 | 51.6, 1.76807800747902, 675 | 51.5, 1.76612888301615, 676 | 51.4, 1.76417503620155, 677 | 51.3, 1.76221646703522, 678 | 51.2, 1.76025317551715, 679 | 51.1, 1.75828516164736, 680 | 51, 1.75631242542583, 681 | 50.9, 1.75433496685257, 682 | 50.8, 1.75235278592758, 683 | 50.7, 1.75036588265087, 684 | 50.6, 1.74837425702242, 685 | 50.5, 1.74637790904223, 686 | 50.4, 1.74437683871032, 687 | 50.3, 1.74237104602668, 688 | 50.2, 1.7403605309913, 689 | 50.1, 1.7383452936042, 690 | 50, 1.73632533386536, 691 | 49.9, 1.73430065177479, 692 | 49.8, 1.73227124733249, 693 | 49.7, 1.73023712053846, 694 | 49.6, 1.7281982713927, 695 | 49.5, 1.72615469989521, 696 | 49.4, 1.72410640604599, 697 | 49.3, 1.72205338984503, 698 | 49.2, 1.71999565129235, 699 | 49.1, 1.71793319038793, 700 | 49, 1.71586600713178, 701 | 48.9, 1.71379410152391, 702 | 48.8, 1.7117174735643, 703 | 48.7, 1.70963612325296, 704 | 48.6, 1.70755005058988, 705 | 48.5, 1.70545925557508, 706 | 48.4, 1.70336373820855, 707 | 48.3, 1.70126349849028, 708 | 48.2, 1.69915853642029, 709 | 48.1, 1.69704885199856, 710 | 48, 1.6949344452251, 711 | 47.9, 1.69281531609991, 712 | 47.8, 1.69069146462299, 713 | 47.7, 1.68856289079434, 714 | 47.6, 1.68642959461396, 715 | 47.5, 1.68429157608185, 716 | 47.4, 1.68214883519801, 717 | 47.3, 1.68000137196243, 718 | 47.2, 1.67784918637512, 719 | 47.1, 1.67569227843609, 720 | 47, 1.67353064814532, 721 | 46.9, 1.67136429550282, 722 | 46.8, 1.66919322050859, 723 | 46.7, 1.66701742316263, 724 | 46.6, 1.66483690346494, 725 | 46.5, 1.66265166141551, 726 | 46.4, 1.66046169701436, 727 | 46.3, 1.65826701026147, 728 | 46.2, 1.65606760115685, 729 | 46.1, 1.65386346970051, 730 | 46, 1.65165461589243, 731 | 45.9, 1.64944103973262, 732 | 45.8, 1.64722274122108, 733 | 45.7, 1.64499972035781, 734 | 45.6, 1.6427719771428, 735 | 45.5, 1.64053951157607, 736 | 45.4, 1.6383023236576, 737 | 45.3, 1.63606041338741, 738 | 45.2, 1.63381378076548, 739 | 45.1, 1.63156242579182, 740 | 45, 1.62930634846643, 741 | 44.9, 1.62704554878931, 742 | 44.8, 1.62478002676046, 743 | 44.7, 1.62250978237988, 744 | 44.6, 1.62023481564757, 745 | 44.5, 1.61795512656352, 746 | 44.4, 1.61567071512775, 747 | 44.3, 1.61338158134024, 748 | 44.2, 1.611087725201, 749 | 44.1, 1.60878914671003, 750 | 44, 1.60648584586734, 751 | 43.9, 1.6041778226729, 752 | 43.8, 1.60186507712674, 753 | 43.7, 1.59954760922885, 754 | 43.6, 1.59722541897923, 755 | 43.5, 1.59489850637787, 756 | 43.4, 1.59256687142479, 757 | 43.3, 1.59023051411997, 758 | 43.2, 1.58788943446342, 759 | 43.1, 1.58554363245514, 760 | 43, 1.58319310809513, 761 | 42.9, 1.58083786138339, 762 | 42.8, 1.57847789231992, 763 | 42.7, 1.57611320090471, 764 | 42.6, 1.57374378713778, 765 | 42.5, 1.57136965101911, 766 | 42.4, 1.56899079254872, 767 | 42.3, 1.56660721172659, 768 | 42.2, 1.56421890855273, 769 | 42.1, 1.56182588302714, 770 | 42, 1.55942813514982, 771 | 41.9, 1.55702566492077, 772 | 41.8, 1.55461847233999, 773 | 41.7, 1.55220655740747, 774 | 41.6, 1.54978992012323, 775 | 41.5, 1.54736856048725, 776 | 41.4, 1.54494247849955, 777 | 41.3, 1.54251167416011, 778 | 41.2, 1.54007614746894, 779 | 41.1, 1.53763589842604, 780 | 41, 1.53519092703141, 781 | 40.9, 1.53274123328505, 782 | 40.8, 1.53028681718695, 783 | 40.7, 1.52782767873713, 784 | 40.6, 1.52536381793558, 785 | 40.5, 1.52289523478229, 786 | 40.4, 1.52042192927727, 787 | 40.3, 1.51794390142052, 788 | 40.2, 1.51546115121204, 789 | 40.1, 1.51297367865183, 790 | 40, 1.51048148373989, 791 | 39.9, 1.50798456647622, 792 | 39.8, 1.50548292686082, 793 | 39.7, 1.50297656489368, 794 | 39.6, 1.50046548057482, 795 | 39.5, 1.49794967390422, 796 | 39.4, 1.49542914488189, 797 | 39.3, 1.49290389350783, 798 | 39.2, 1.49037391978204, 799 | 39.1, 1.48783922370452, 800 | 39, 1.48529980527527, 801 | 38.9, 1.48275566449429, 802 | 38.8, 1.48020680136157, 803 | 38.7, 1.47765321587713, 804 | 38.6, 1.47509490804095, 805 | 38.5, 1.47253187785304, 806 | 38.4, 1.46996412531341, 807 | 38.3, 1.46739165042204, 808 | 38.2, 1.46481445317894, 809 | 38.1, 1.4622325335841, 810 | 38, 1.45964589163754, 811 | 37.9, 1.45705452733925, 812 | 37.8, 1.45445844068922, 813 | 37.7, 1.45185763168747, 814 | 37.6, 1.44925210033398, 815 | 37.5, 1.44664184662876, 816 | 37.4, 1.44402687057182, 817 | 37.3, 1.44140717216314, 818 | 37.2, 1.43878275140273, 819 | 37.1, 1.43615360829058, 820 | 37, 1.43351974282671, 821 | 36.9, 1.43088115501111, 822 | 36.8, 1.42823784484377, 823 | 36.7, 1.42558981232471, 824 | 36.6, 1.42293705745391, 825 | 36.5, 1.42027958023138, 826 | 36.4, 1.41761738065712, 827 | 36.3, 1.41495045873113, 828 | 36.2, 1.41227881445341, 829 | 36.1, 1.40960244782396, 830 | 36, 1.40692135884277, 831 | 35.9, 1.40423554750986, 832 | 35.8, 1.40154501382521, 833 | 35.7, 1.39884975778884, 834 | 35.6, 1.39614977940073, 835 | 35.5, 1.39344507866089, 836 | 35.4, 1.39073565556932, 837 | 35.3, 1.38802151012602, 838 | 35.2, 1.38530264233099, 839 | 35.1, 1.38257905218423, 840 | 35, 1.37985073968573, 841 | 34.9, 1.37711770483551, 842 | 34.8, 1.37437994763355, 843 | 34.7, 1.37163746807987, 844 | 34.6, 1.36889026617445, 845 | 34.5, 1.3661383419173, 846 | 34.4, 1.36338169530842, 847 | 34.3, 1.36062032634781, 848 | 34.2, 1.35785423503546, 849 | 34.1, 1.35508342137139, 850 | 34, 1.35230788535559, 851 | 33.9, 1.34952762698805, 852 | 33.8, 1.34674264626879, 853 | 33.7, 1.34395294319779, 854 | 33.6, 1.34115851777506, 855 | 33.5, 1.3383593700006, 856 | 33.4, 1.33555549987441, 857 | 33.3, 1.33274690739649, 858 | 33.2, 1.32993359256684, 859 | 33.1, 1.32711555538545, 860 | 33, 1.32429279585234, 861 | 32.9, 1.32146531396749, 862 | 32.8, 1.31863310973091, 863 | 32.7, 1.31579618314261, 864 | 32.6, 1.31295453420257, 865 | 32.5, 1.3101081629108, 866 | 32.4, 1.3072570692673, 867 | 32.3, 1.30440125327206, 868 | 32.2, 1.3015407149251, 869 | 32.1, 1.29867545422641, 870 | 32, 1.29580547117598, 871 | 31.9, 1.29293076577383, 872 | 31.8, 1.29005133801994, 873 | 31.7, 1.28716718791432, 874 | 31.6, 1.28427831545697, 875 | 31.5, 1.28138472064789, 876 | 31.4, 1.27848640348708, 877 | 31.3, 1.27558336397454, 878 | 31.2, 1.27267560211026, 879 | 31.1, 1.26976311789426, 880 | 31, 1.26684591132652, 881 | 30.9, 1.26392398240705, 882 | 30.8, 1.26099733113586, 883 | 30.7, 1.25806595751293, 884 | 30.6, 1.25512986153827, 885 | 30.5, 1.25218904321188, 886 | 30.4, 1.24924350253376, 887 | 30.3, 1.2462932395039, 888 | 30.2, 1.24333825412232, 889 | 30.1, 1.240378546389, 890 | 30, 1.23741411630396, 891 | 29.9, 1.23444496386718, 892 | 29.8, 1.23147108907867, 893 | 29.7, 1.22849249193843, 894 | 29.6, 1.22550917244646, 895 | 29.5, 1.22252113060276, 896 | 29.4, 1.21952836640733, 897 | 29.3, 1.21653087986016, 898 | 29.2, 1.21352867096127, 899 | 29.1, 1.21052173971064, 900 | 29, 1.20751008610829, 901 | 28.9, 1.2044937101542, 902 | 28.8, 1.20147261184838, 903 | 28.7, 1.19844679119083, 904 | 28.6, 1.19541624818155, 905 | 28.5, 1.19238098282054, 906 | 28.4, 1.1893409951078, 907 | 28.3, 1.18629628504332, 908 | 28.2, 1.18324685262712, 909 | 28.1, 1.18019269785918, 910 | 28, 1.17713382073951, 911 | 27.9, 1.17407022126812, 912 | 27.8, 1.17100189944499, 913 | 27.7, 1.16792885527013, 914 | 27.6, 1.16485108874354, 915 | 27.5, 1.16176859986521, 916 | 27.4, 1.15868138863516, 917 | 27.3, 1.15558945505337, 918 | 27.2, 1.15249279911986, 919 | 27.1, 1.14939142083461, 920 | 27, 1.14628532019764, 921 | 26.9, 1.14317449720893, 922 | 26.8, 1.14005895186849, 923 | 26.7, 1.13693868417632, 924 | 26.6, 1.13381369413242, 925 | 26.5, 1.13068398173678, 926 | 26.4, 1.12754954698942, 927 | 26.3, 1.12441038989032, 928 | 26.2, 1.1212665104395, 929 | 26.1, 1.11811790863694, 930 | 26, 1.11496458448265, 931 | 25.9, 1.11180653797663, 932 | 25.8, 1.10864376911888, 933 | 25.7, 1.1054762779094, 934 | 25.6, 1.10230406434819, 935 | 25.5, 1.09912712843525, 936 | 25.4, 1.09594547017057, 937 | 25.3, 1.09275908955417, 938 | 25.2, 1.08956798658603, 939 | 25.1, 1.08637216126616, 940 | 25, 1.08317161359456, 941 | 24.9, 1.07996634357124, 942 | 24.8, 1.07675635119617, 943 | 24.7, 1.07354163646938, 944 | 24.6, 1.07032219939086, 945 | 24.5, 1.06709803996061, 946 | 24.4, 1.06386915817862, 947 | 24.3, 1.06063555404491, 948 | 24.2, 1.05739722755946, 949 | 24.1, 1.05415417872228, 950 | 24, 1.05090640753337, 951 | 23.9, 1.04765391399273, 952 | 23.8, 1.04439669810036, 953 | 23.7, 1.04113475985626, 954 | 23.6, 1.03786809926043, 955 | 23.5, 1.03459671631286, 956 | 23.4, 1.03132061101357, 957 | 23.3, 1.02803978336254, 958 | 23.2, 1.02475423335978, 959 | 23.1, 1.02146396100529, 960 | 23, 1.01816896629907, 961 | 22.9, 1.01486924924112, 962 | 22.8, 1.01156480983144, 963 | 22.7, 1.00825564807003, 964 | 22.6, 1.00494176395689, 965 | 22.5, 1.00162315749201, 966 | 22.4, 0.998299828675406, 967 | 22.3, 0.994971777507069, 968 | 22.2, 0.991639003987002, 969 | 22.1, 0.988301508115203, 970 | 22, 0.984959289891673, 971 | 21.9, 0.981612349316412, 972 | 21.8, 0.97826068638942, 973 | 21.7, 0.974904301110697, 974 | 21.6, 0.971543193480243, 975 | 21.5, 0.968177363498058, 976 | 21.4, 0.964806811164142, 977 | 21.3, 0.961431536478494, 978 | 21.2, 0.958051539441116, 979 | 21.1, 0.954666820052007, 980 | 21, 0.951277378311167, 981 | 20.9, 0.947883214218595, 982 | 20.8, 0.944484327774293, 983 | 20.7, 0.941080718978259, 984 | 20.6, 0.937672387830495, 985 | 20.5, 0.934259334330999, 986 | 20.4, 0.930841558479772, 987 | 20.3, 0.927419060276815, 988 | 20.2, 0.923991839722126, 989 | 20.1, 0.920559896815706, 990 | 20, 0.917123231557555, 991 | 19.9, 0.913681843947674, 992 | 19.8, 0.910235733986061, 993 | 19.7, 0.906784901672717, 994 | 19.6, 0.903329347007642, 995 | 19.5, 0.899869069990836, 996 | 19.4, 0.896404070622298, 997 | 19.3, 0.89293434890203, 998 | 19.2, 0.889459904830031, 999 | 19.1, 0.885980738406301, 1000 | 19, 0.88249684963084, 1001 | 18.9, 0.879008238503647, 1002 | 18.8, 0.875514905024724, 1003 | 18.7, 0.87201684919407, 1004 | 18.6, 0.868514071011684, 1005 | 18.5, 0.865006570477568, 1006 | 18.4, 0.86149434759172, 1007 | 18.3, 0.857977402354141, 1008 | 18.2, 0.854455734764832, 1009 | 18.1, 0.850929344823791, 1010 | 18, 0.847398232531019, 1011 | 17.9, 0.843862397886516, 1012 | 17.8, 0.840321840890282, 1013 | 17.7, 0.836776561542318, 1014 | 17.6, 0.833226559842622, 1015 | 17.5, 0.829671835791195, 1016 | 17.4, 0.826112389388037, 1017 | 17.3, 0.822548220633147, 1018 | 17.2, 0.818979329526528, 1019 | 17.1, 0.815405716068176, 1020 | 17, 0.811827380258094, 1021 | 16.9, 0.808244322096281, 1022 | 16.8, 0.804656541582736, 1023 | 16.7, 0.801064038717461, 1024 | 16.6, 0.797466813500455, 1025 | 16.5, 0.793864865931717, 1026 | 16.4, 0.790258196011249, 1027 | 16.3, 0.786646803739049, 1028 | 16.2, 0.783030689115119, 1029 | 16.1, 0.779409852139457, 1030 | 16, 0.775784292812064, 1031 | 15.9, 0.77215401113294, 1032 | 15.8, 0.768519007102086, 1033 | 15.7, 0.7648792807195, 1034 | 15.6, 0.761234831985183, 1035 | 15.5, 0.757585660899135, 1036 | 15.4, 0.753931767461356, 1037 | 15.3, 0.750273151671846, 1038 | 15.2, 0.746609813530605, 1039 | 15.1, 0.742941753037633, 1040 | 15, 0.73926897019293, 1041 | 14.9, 0.735591464996495, 1042 | 14.8, 0.73190923744833, 1043 | 14.7, 0.728222287548434, 1044 | 14.6, 0.724530615296807, 1045 | 14.5, 0.720834220693448, 1046 | 14.4, 0.717133103738359, 1047 | 14.3, 0.713427264431538, 1048 | 14.2, 0.709716702772987, 1049 | 14.1, 0.706001418762704, 1050 | 14, 0.70228141240069, 1051 | 13.9, 0.698556683686946, 1052 | 13.8, 0.69482723262147, 1053 | 13.7, 0.691093059204263, 1054 | 13.6, 0.687354163435326, 1055 | 13.5, 0.683610545314657, 1056 | 13.4, 0.679862204842257, 1057 | 13.3, 0.676109142018126, 1058 | 13.2, 0.672351356842264, 1059 | 13.1, 0.668588849314671, 1060 | 13, 0.664821619435347, 1061 | 12.9, 0.661049667204291, 1062 | 12.8, 0.657272992621505, 1063 | 12.7, 0.653491595686988, 1064 | 12.6, 0.64970547640074, 1065 | 12.5, 0.645914634762761, 1066 | 12.4, 0.64211907077305, 1067 | 12.3, 0.638318784431609, 1068 | 12.2, 0.634513775738436, 1069 | 12.1, 0.630704044693533, 1070 | 12, 0.626889591296898, 1071 | 11.9, 0.623070415548533, 1072 | 11.8, 0.619246517448436, 1073 | 11.7, 0.615417896996608, 1074 | 11.6, 0.611584554193049, 1075 | 11.5, 0.60774648903776, 1076 | 11.4, 0.603903701530739, 1077 | 11.3, 0.600056191671987, 1078 | 11.2, 0.596203959461504, 1079 | 11.1, 0.59234700489929, 1080 | 11, 0.588485327985345, 1081 | 10.9, 0.584618928719669, 1082 | 10.8, 0.580747807102262, 1083 | 10.7, 0.576871963133124, 1084 | 10.6, 0.572991396812254, 1085 | 10.5, 0.569106108139654, 1086 | 10.4, 0.565216097115323, 1087 | 10.3, 0.56132136373926, 1088 | 10.2, 0.557421908011467, 1089 | 10.1, 0.553517729931942, 1090 | 10, 0.549608829500687, 1091 | 9.9, 0.545695206717701, 1092 | 9.8, 0.541776861582983, 1093 | 9.7, 0.537853794096534, 1094 | 9.6, 0.533926004258355, 1095 | 9.5, 0.529993492068444, 1096 | 9.4, 0.526056257526802, 1097 | 9.3, 0.522114300633429, 1098 | 9.2, 0.518167621388326, 1099 | 9.1, 0.51421621979149, 1100 | 9, 0.510260095842924, 1101 | 8.9, 0.506299249542628, 1102 | 8.8, 0.502333680890599, 1103 | 8.7, 0.49836338988684, 1104 | 8.6, 0.49438837653135, 1105 | 8.5, 0.490408640824129, 1106 | 8.4, 0.486424182765177, 1107 | 8.3, 0.482435002354494, 1108 | 8.2, 0.478441099592079, 1109 | 8.1, 0.474442474477934, 1110 | 8, 0.470439127012057, 1111 | 7.9, 0.46643105719445, 1112 | 7.8, 0.462418265025111, 1113 | 7.7, 0.458400750504042, 1114 | 7.6, 0.454378513631241, 1115 | 7.5, 0.450351554406709, 1116 | 7.4, 0.446319872830447, 1117 | 7.3, 0.442283468902453, 1118 | 7.2, 0.438242342622728, 1119 | 7.1, 0.434196493991273, 1120 | 7, 0.430145923008086, 1121 | 6.9, 0.426090629673168, 1122 | 6.8, 0.422030613986519, 1123 | 6.7, 0.417965875948139, 1124 | 6.6, 0.413896415558027, 1125 | 6.5, 0.409822232816185, 1126 | 6.4, 0.405743327722612, 1127 | 6.3, 0.401659700277308, 1128 | 6.2, 0.397571350480273, 1129 | 6.1, 0.393478278331506, 1130 | 6, 0.389380483831009, 1131 | 5.9, 0.385277966978781, 1132 | 5.8, 0.381170727774821, 1133 | 5.7, 0.377058766219131, 1134 | 5.6, 0.372942082311709, 1135 | 5.5, 0.368820676052557, 1136 | 5.4, 0.364694547441673, 1137 | 5.3, 0.360563696479058, 1138 | 5.2, 0.356428123164712, 1139 | 5.1, 0.352287827498636, 1140 | 5, 0.348142809480828, 1141 | 4.9, 0.343993069111289, 1142 | 4.8, 0.339838606390019, 1143 | 4.7, 0.335679421317018, 1144 | 4.6, 0.331515513892286, 1145 | 4.5, 0.327346884115823, 1146 | 4.4, 0.323173531987629, 1147 | 4.3, 0.318995457507704, 1148 | 4.2, 0.314812660676048, 1149 | 4.1, 0.31062514149266, 1150 | 4, 0.306432899957542, 1151 | 3.9, 0.302235936070693, 1152 | 3.8, 0.298034249832112, 1153 | 3.7, 0.293827841241801, 1154 | 3.6, 0.289616710299758, 1155 | 3.5, 0.285400857005985, 1156 | 3.4, 0.28118028136048, 1157 | 3.3, 0.276954983363244, 1158 | 3.2, 0.272724963014278, 1159 | 3.1, 0.26849022031358, 1160 | 3, 0.264250755261152, 1161 | 2.9, 0.260006567856992, 1162 | 2.8, 0.255757658101101, 1163 | 2.7, 0.251504025993479, 1164 | 2.6, 0.247245671534126, 1165 | 2.5, 0.242982594723042, 1166 | 2.4, 0.238714795560227, 1167 | 2.3, 0.234442274045681, 1168 | 2.2, 0.230165030179404, 1169 | 2.1, 0.225883063961396, 1170 | 2, 0.221596375391656, 1171 | 1.9, 0.217304964470186, 1172 | 1.8, 0.213008831196985, 1173 | 1.7, 0.208707975572052, 1174 | 1.6, 0.204402397595389, 1175 | 1.5, 0.200092097266994, 1176 | 1.4, 0.195777074586869, 1177 | 1.3, 0.191457329555012, 1178 | 1.2, 0.187132862171425, 1179 | 1.1, 0.182803672436106, 1180 | 1, 0.178469760349057, 1181 | 0.900000000000002, 0.174131125910276, 1182 | 0.800000000000001, 0.169787769119764, 1183 | 0.700000000000001, 0.165439689977521, 1184 | 0.600000000000001, 0.161086888483547, 1185 | 0.500000000000002, 0.156729364637842, 1186 | 0.400000000000002, 0.152367118440406, 1187 | 0.300000000000001, 0.148000149891239, 1188 | 0.200000000000001, 0.143628458990341, 1189 | 0.100000000000001, 0.139252045737712, 1190 | 0, 0.134870910133352, 1191 | -0.0999999999999996, 0.130485052177261, 1192 | -0.199999999999999, 0.126094471869438, 1193 | -0.299999999999999, 0.121699169209885, 1194 | -0.399999999999999, 0.117299144198601, 1195 | -0.499999999999998, 0.112894396835585, 1196 | -0.6, 0.108484927120839, 1197 | -0.699999999999999, 0.104070735054361, 1198 | -0.799999999999999, 0.099651820636153, 1199 | -0.899999999999999, 0.0952281838662133, 1200 | -0.999999999999998, 0.0907998247445427, 1201 | -1.1, 0.0863667432711407, 1202 | -1.2, 0.0819289394460083, 1203 | -1.3, 0.0774864132691446, 1204 | -1.4, 0.0730391647405498, 1205 | -1.5, 0.0685871938602239, 1206 | -1.6, 0.064130500628167, 1207 | -1.7, 0.0596690850443788, 1208 | -1.8, 0.05520294710886, 1209 | -1.9, 0.0507320868216101, 1210 | -2, 0.0462565041826287, 1211 | -2.1, 0.0417761991919165, 1212 | -2.2, 0.0372911718494732, 1213 | -2.3, 0.0328014221552995, 1214 | -2.4, 0.0283069501093939, 1215 | -2.5, 0.0238077557117578, 1216 | -2.6, 0.0193038389623901, 1217 | -2.7, 0.0147951998612917, 1218 | -2.8, 0.0102818384084626, 1219 | -2.9, 0.00576375460390177, 1220 | -3, 0.00124094844761019, 1221 | -3.1, -0.00328658006041249, 1222 | -3.2, 0.00781883092016605, 1223 | -3.3, -0.0123558041316507, 1224 | -3.4, -0.0168974996948664, 1225 | -3.5, -0.0214439176098133, 1226 | -3.6, -0.0259950578764911, 1227 | -3.7, -0.0305509204949003, 1228 | -3.8, -0.0351115054650395, 1229 | -3.9, -0.0396768127869109, 1230 | -4, -0.0442468424605129, 1231 | -4.1, -0.0488215944858461, 1232 | -4.2, -0.0534010688629101, 1233 | -4.3, -0.0579852655917051, 1234 | -4.4, -0.0625741846722315, 1235 | -4.5, -0.0671678261044887, 1236 | -4.6, -0.0717661898884773, 1237 | -4.7, -0.0763692760241966, 1238 | -4.8, -0.0809770845116467, 1239 | -4.9, -0.0855896153508281, 1240 | -5, -0.0902068685417409, 1241 | -5.1, -0.0948288440843842, 1242 | -5.2, -0.0994555419787591, 1243 | -5.3, -0.104086962224864, 1244 | -5.4, -0.108723104822701, 1245 | -5.5, -0.113363969772269, 1246 | -5.6, -0.118009557073568, 1247 | -5.7, -0.122659866726598, 1248 | -5.8, -0.127314898731358, 1249 | -5.9, -0.13197465308785, 1250 | -6, -0.136639129796073, 1251 | -6.1, -0.141308328856027, 1252 | -6.2, -0.145982250267712, 1253 | -6.3, -0.150660894031128, 1254 | -6.4, -0.155344260146275, 1255 | -6.5, -0.160032348613154, 1256 | -6.6, -0.164725159431763, 1257 | -6.7, -0.169422692602103, 1258 | -6.8, -0.174124948124175, 1259 | -6.9, -0.178831925997977, 1260 | -7, -0.183543626223511, 1261 | -7.1, -0.188260048800775, 1262 | -7.2, -0.192981193729771, 1263 | -7.3, -0.197707061010497, 1264 | -7.4, -0.202437650642955, 1265 | -7.5, -0.207172962627143, 1266 | -7.6, -0.211912996963063, 1267 | -7.7, -0.216657753650714, 1268 | -7.8, -0.221407232690095, 1269 | -7.9, -0.226161434081208, 1270 | -8, -0.230920357824052, 1271 | -8.1, -0.235684003918627, 1272 | -8.2, -0.240452372364934, 1273 | -8.3, -0.24522546316297, 1274 | -8.4, -0.250003276312738, 1275 | -8.5, -0.254785811814238, 1276 | -8.6, -0.259573069667468, 1277 | -8.7, -0.264365049872429, 1278 | -8.8, -0.269161752429121, 1279 | -8.9, -0.273963177337545, 1280 | -9, -0.278769324597699, 1281 | -9.1, -0.283580194209584, 1282 | -9.2, -0.288395786173201 1283 | }; 1284 | const int mapSize = sizeof(map) / sizeof(map[0]); 1285 | int range = -1; 1286 | for (int i = 0; i < mapSize - 2; i += 2) { 1287 | if (dz >= map[i + 2] && dz < map[i]) { 1288 | range = i; 1289 | break; 1290 | } 1291 | } 1292 | if (range == -1) 1293 | return 1000; 1294 | float rangeSpan = map[range] - map[range + 2]; 1295 | float dist = (dz - map[range + 2]) / rangeSpan; 1296 | float valueSpan = map[range + 1] - map[range + 3]; 1297 | return map[range + 3] + valueSpan * dist; 1298 | } 1299 | 1300 | void Automator::checkWorkingState() 1301 | { 1302 | bool working = m_lastdzValid && m_mcConnected && m_lastCoordsValid && m_enabled; 1303 | if (working != m_working) { 1304 | m_working = working; 1305 | emit workingChanged(); 1306 | } 1307 | } 1308 | 1309 | float Automator::minPower() const 1310 | { 1311 | return m_minPower; 1312 | } 1313 | 1314 | void Automator::setMinPower(float minPower) 1315 | { 1316 | m_minPower = minPower; 1317 | } 1318 | 1319 | float Automator::lastSentPower() const 1320 | { 1321 | return m_lastSentPower; 1322 | } 1323 | 1324 | float Automator::maxPower() const 1325 | { 1326 | return m_maxPower; 1327 | } 1328 | 1329 | void Automator::setMaxPower(float maxPower) 1330 | { 1331 | m_maxPower = maxPower; 1332 | } 1333 | 1334 | bool Automator::autosendPower() const 1335 | { 1336 | return m_autosendPower; 1337 | } 1338 | 1339 | void Automator::setAutosendPower(bool autosendPower) 1340 | { 1341 | m_autosendPower = autosendPower; 1342 | } 1343 | 1344 | bool Automator::autosendB() const 1345 | { 1346 | return m_autosendB; 1347 | } 1348 | 1349 | void Automator::setAutosendB(bool autosendB) 1350 | { 1351 | m_autosendB = autosendB; 1352 | } 1353 | 1354 | bool Automator::enabled() const 1355 | { 1356 | return m_enabled; 1357 | } 1358 | 1359 | void Automator::setEnabled(bool enabled) 1360 | { 1361 | m_enabled = enabled; 1362 | emit enabledChanged(); 1363 | checkWorkingState(); 1364 | } 1365 | 1366 | QString Automator::message() const 1367 | { 1368 | return m_message; 1369 | } 1370 | -------------------------------------------------------------------------------- /automator.h: -------------------------------------------------------------------------------- 1 | #ifndef AUTOMATOR_H 2 | #define AUTOMATOR_H 3 | 4 | #include 5 | #include "rayreceiver.h" 6 | #include 7 | 8 | class Automator : public QObject 9 | { 10 | Q_OBJECT 11 | Q_PROPERTY(bool working READ working NOTIFY workingChanged) 12 | Q_PROPERTY(bool enabled READ enabled WRITE setEnabled NOTIFY enabledChanged) 13 | Q_PROPERTY(QString message READ message NOTIFY messageChanged) 14 | Q_PROPERTY(bool autosendB READ autosendB WRITE setAutosendB) 15 | Q_PROPERTY(bool autosendPower READ autosendPower WRITE setAutosendPower) 16 | Q_PROPERTY(float minPower READ minPower WRITE setMinPower) 17 | Q_PROPERTY(float maxPower READ maxPower WRITE setMaxPower) 18 | Q_PROPERTY(float lastSentPower READ lastSentPower NOTIFY changePower) 19 | public: 20 | explicit Automator(QObject *parent = nullptr); 21 | 22 | bool working() const; 23 | 24 | bool enabled() const; 25 | void setEnabled(bool enabled); 26 | 27 | QString message() const; 28 | Q_INVOKABLE float compensate(float dz) const; 29 | 30 | bool autosendB() const; 31 | void setAutosendB(bool autosendB); 32 | 33 | bool autosendPower() const; 34 | void setAutosendPower(bool autosendPower); 35 | 36 | float maxPower() const; 37 | void setMaxPower(float maxPower); 38 | 39 | float minPower() const; 40 | void setMinPower(float minPower); 41 | 42 | float lastSentPower() const; 43 | 44 | signals: 45 | void workingChanged(); 46 | void enabledChanged(); 47 | void messageChanged(); 48 | void sendToMC(const QString &command); 49 | void changePower(float power); 50 | 51 | public slots: 52 | void ondzChanged(float dz); 53 | void ondzValidChanged(bool valid); 54 | void onMcConnectionStateChanged(bool connected); 55 | void onRayConnectionStateChanged(bool connected); 56 | void onCoordsChanged(float x, float y, float z, float b); 57 | void onMcStateChanged(RayReceiver::State s); 58 | 59 | private: 60 | void checkWorkingState(); 61 | bool m_working; 62 | float m_lastdz; 63 | bool m_lastdzValid; 64 | bool m_lastCoordsValid; 65 | bool m_enabled; 66 | bool m_mcConnected; 67 | float m_mcs_x; 68 | float m_mcs_y; 69 | float m_mcs_b; 70 | float m_mcs_b_initial; 71 | QString m_message; 72 | bool m_autosendB; 73 | bool m_autosendPower; 74 | float m_maxPower; 75 | float m_minPower; 76 | float m_lastSentPower; 77 | QTimer m_powerTimer; 78 | }; 79 | 80 | #endif // AUTOMATOR_H 81 | -------------------------------------------------------------------------------- /cameracalibrator.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include "cameracalibrator.h" 6 | #include "capturecontroller.hpp" 7 | #include "cvmatsurfacesource.hpp" 8 | 9 | 10 | 11 | Q_LOGGING_CATEGORY(cameraCalibrator, "vhrd.vision.camera_calibrator"); 12 | 13 | CameraCalibrator::CameraCalibrator(CaptureController *captureController, QObject *parent) : QObject(parent), 14 | m_takePicture(false), m_captureController(captureController), 15 | m_horizontalCornersCount(9), m_verticalCornersCount(6) 16 | { 17 | } 18 | 19 | quint32 CameraCalibrator::horizontalCornersCount() const 20 | { 21 | return m_horizontalCornersCount; 22 | } 23 | 24 | void CameraCalibrator::setHorizontalCornersCount(quint32 count) 25 | { 26 | m_horizontalCornersCount = count; 27 | m_pictures.clear(); 28 | } 29 | 30 | quint32 CameraCalibrator::verticalCornersCount() const 31 | { 32 | return m_verticalCornersCount; 33 | } 34 | 35 | void CameraCalibrator::setVerticalCornersCount(quint32 count) 36 | { 37 | m_verticalCornersCount = count; 38 | m_pictures.clear(); 39 | } 40 | 41 | void CameraCalibrator::takePicture() 42 | { 43 | m_takePicture = true; 44 | } 45 | 46 | void CameraCalibrator::onFrameReady() 47 | { 48 | if (!m_takePicture) 49 | return; 50 | m_takePicture = false; 51 | 52 | qCDebug(cameraCalibrator) << "Processing frame"; 53 | cv::Mat frame = m_captureController->frameCopy(); 54 | findChessboard(frame); 55 | } 56 | 57 | void CameraCalibrator::calibrate() 58 | { 59 | if (m_pictures.length() <= 1) { 60 | qCWarning(cameraCalibrator) << "Not enough pictures for calibration"; 61 | return; 62 | } 63 | std::vector points; 64 | for (quint32 i = 0; i < m_horizontalCornersCount; ++i) { 65 | for (quint32 j = 0; j < m_verticalCornersCount; ++j) { 66 | points.push_back(cv::Point3f(i, j, 0)); 67 | } 68 | } 69 | std::vector> object_points; 70 | std::vector> image_points; 71 | 72 | foreach (const picture_t &p, m_pictures) { 73 | object_points.push_back(points); 74 | image_points.push_back(p.corners); 75 | } 76 | 77 | m_intrinsic = cv::Mat(3, 3, CV_32FC1); 78 | m_intrinsic.ptr(0)[0] = 1; // fx 79 | m_intrinsic.ptr(1)[1] = 1; // fy 80 | 81 | cv::Mat distCoeffs; 82 | std::vector rvecs; 83 | std::vector tvecs; 84 | 85 | float rms = cv::calibrateCamera(object_points, 86 | image_points, 87 | m_pictures[0].picture.size(), 88 | m_intrinsic, 89 | m_distCoeffs, 90 | rvecs, 91 | tvecs ); 92 | qCDebug(cameraCalibrator) << "rms: " << rms; 93 | } 94 | 95 | void CameraCalibrator::applyCalibrationData() 96 | { 97 | if (m_intrinsic.empty() || m_distCoeffs.empty()) { 98 | qCWarning(cameraCalibrator) << "not yet calibrated"; 99 | return; 100 | } 101 | qCDebug(cameraCalibrator) << "Enabling undistort"; 102 | m_captureController->enableUndistort(m_intrinsic, m_distCoeffs); 103 | } 104 | 105 | void CameraCalibrator::savePictures(const QString &toFolder) 106 | { 107 | QDir dir = QDir(toFolder); 108 | QString absPath = dir.absolutePath(); 109 | if (!dir.exists()) { 110 | qCDebug(cameraCalibrator) << "Creating" << absPath; 111 | dir.mkpath(absPath); 112 | } else { 113 | qCDebug(cameraCalibrator) << "Saving images to" << absPath; 114 | } 115 | quint32 i = 0; 116 | foreach (const picture_t &p, m_pictures) { 117 | QString filename = QString("%1/%2.jpg").arg(absPath).arg(i); 118 | bool ok = false; 119 | try { 120 | ok = cv::imwrite(filename.toStdString(), p.picture); 121 | } catch(...) { 122 | 123 | } 124 | qCDebug(cameraCalibrator) << "Saving" << i+1 << "of" << m_pictures.size() << ":" << ok; 125 | i += 1; 126 | } 127 | } 128 | 129 | void CameraCalibrator::loadPictures(const QString &fromFolder) 130 | { 131 | qCDebug(cameraCalibrator) << "Loading frames from" << fromFolder; 132 | QDir dir(fromFolder); 133 | QString absPath = dir.absolutePath(); 134 | QStringList images = dir.entryList(QStringList() << "*.jpg" << "*.JPG", QDir::Files); 135 | quint32 i = 0; 136 | foreach (const QString &image, images) { 137 | cv::Mat frame = cv::imread((absPath + "/" + image).toStdString()); 138 | qCDebug(cameraCalibrator) << "Loading" << i+1 << "of" << images.size() << ":" << !frame.empty(); 139 | findChessboard(frame); 140 | i += 1; 141 | } 142 | } 143 | 144 | void CameraCalibrator::saveCalibrationData(const QString &filename) 145 | { 146 | cv::FileStorage fs(filename.toStdString(), cv::FileStorage::WRITE); 147 | fs << "intrinsic" << m_intrinsic; 148 | fs << "distort" << m_distCoeffs; 149 | fs.release(); 150 | } 151 | 152 | void CameraCalibrator::loadCalibrationData(const QString &filename) 153 | { 154 | cv::FileStorage fs(filename.toStdString(), cv::FileStorage::READ); 155 | fs["intrinsic"] >> m_intrinsic; 156 | fs["distort"] >> m_distCoeffs; 157 | fs.release(); 158 | } 159 | 160 | void CameraCalibrator::findChessboard(const cv::Mat &frame) 161 | { 162 | if (frame.empty()) { 163 | qCWarning(cameraCalibrator) << "empty frame"; 164 | } 165 | cv::Mat gray = cv::Mat(frame.rows, frame.cols, CV_8UC1); 166 | cv::cvtColor(frame, gray, cv::COLOR_RGB2GRAY); 167 | //CVMatSurfaceSource::imshow("second", gray); 168 | 169 | auto board_size = cv::Size(m_verticalCornersCount, m_horizontalCornersCount); 170 | std::vector corners; 171 | bool found = cv::findChessboardCorners(frame, board_size, corners, cv::CALIB_CB_ADAPTIVE_THRESH | cv::CALIB_CB_FILTER_QUADS); 172 | 173 | if (found) { 174 | qCDebug(cameraCalibrator) << "Pattern found!"; 175 | m_pictures.append(picture_t { frame.clone(), corners }); 176 | 177 | cornerSubPix(gray, corners, cv::Size(11, 11), cv::Size(-1, -1), cv::TermCriteria(cv::TermCriteria::EPS | cv::TermCriteria::COUNT, 30, 0.1)); 178 | drawChessboardCorners(frame, board_size, corners, found); 179 | CVMatSurfaceSource::imshow("second", frame); 180 | 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /cameracalibrator.h: -------------------------------------------------------------------------------- 1 | #ifndef CAMERACALIBRATOR_H 2 | #define CAMERACALIBRATOR_H 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | class CaptureController; 10 | 11 | class CameraCalibrator : public QObject 12 | { 13 | Q_OBJECT 14 | Q_PROPERTY(quint32 horizontalCornersCount READ horizontalCornersCount WRITE setHorizontalCornersCount) 15 | Q_PROPERTY(quint32 verticalCornersCount READ verticalCornersCount WRITE setVerticalCornersCount) 16 | public: 17 | explicit CameraCalibrator(CaptureController *captureController, QObject *parent = nullptr); 18 | 19 | quint32 horizontalCornersCount() const; 20 | void setHorizontalCornersCount(quint32 count); 21 | quint32 verticalCornersCount() const; 22 | void setVerticalCornersCount(quint32 count); 23 | 24 | signals: 25 | 26 | public slots: 27 | void takePicture(); 28 | void onFrameReady(); 29 | void calibrate(); 30 | void applyCalibrationData(); 31 | void savePictures(const QString &toFolder); 32 | void loadPictures(const QString &fromFolder); 33 | void saveCalibrationData(const QString &filename); 34 | void loadCalibrationData(const QString &filename); 35 | 36 | private: 37 | void findChessboard(const cv::Mat &frame); 38 | 39 | struct picture_t { 40 | cv::Mat picture; 41 | std::vector corners; 42 | }; 43 | QList m_pictures; 44 | bool m_takePicture; 45 | CaptureController *m_captureController; 46 | 47 | quint32 m_horizontalCornersCount; 48 | quint32 m_verticalCornersCount; 49 | 50 | cv::Mat m_intrinsic; 51 | cv::Mat m_distCoeffs; 52 | }; 53 | 54 | Q_DECLARE_LOGGING_CATEGORY(cameraCalibrator) 55 | 56 | #endif // CAMERACALIBRATOR_H 57 | -------------------------------------------------------------------------------- /capturecontroller.cpp: -------------------------------------------------------------------------------- 1 | #include "capturecontroller.hpp" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | //remove 9 | #include "cvmatsurfacesource.hpp" 10 | #include 11 | 12 | CaptureWorker::CaptureWorker(const QString &device, CaptureController *captureController, QObject *parent) : 13 | QObject(parent), m_device(device), m_captureController(captureController), m_loopRunning(true), m_useUndistort(false) 14 | { 15 | } 16 | 17 | void CaptureWorker::stop() 18 | { 19 | m_loopRunning = false; 20 | } 21 | 22 | void CaptureWorker::doWork() 23 | { 24 | bool ok = false; 25 | int deviceId = m_device.toInt(&ok); 26 | if (ok) { 27 | m_capture = new cv::VideoCapture(deviceId); 28 | } else { 29 | m_capture = new cv::VideoCapture("http://192.168.88.235:8080/?action=stream"); 30 | } 31 | if (!m_capture->isOpened()) { 32 | qWarning() << "Can't capture" << m_device; 33 | m_captureController->setStatus(CaptureController::Status::Failed); 34 | emit workDone(); 35 | return; 36 | } 37 | bool isVideoFile = static_cast(m_capture->get(cv::CAP_PROP_FRAME_COUNT)) > 0; 38 | unsigned long sleepBetweenFrames = 0; 39 | if (isVideoFile) 40 | sleepBetweenFrames = static_cast((1.0 / m_capture->get(cv::CAP_PROP_FPS)) * 1000000); 41 | m_captureController->setStatus(CaptureController::Status::Started); 42 | while(m_loopRunning) { 43 | if (!m_capture->grab()) { 44 | m_captureController->setStatus(CaptureController::Status::EofOrDisconnected); 45 | break; 46 | } 47 | m_captureController->m_lock->lockForWrite(); 48 | m_capture->retrieve(m_frame); 49 | if (m_frame.empty()) { // last frame of video 50 | m_captureController->setStatus(CaptureController::Status::EofOrDisconnected); 51 | break; 52 | } 53 | 54 | QReadLocker lock(m_captureController->m_undistortLock); 55 | if (m_useUndistort) { 56 | cv::Mat undistorted(m_frame.rows, m_frame.cols, m_frame.type()); 57 | cv::undistort(m_frame, undistorted, m_intrinsic, m_distCoeffs); 58 | m_frame = undistorted; 59 | } 60 | lock.unlock(); 61 | 62 | m_captureController->m_lock->unlock(); 63 | 64 | emit frameReady(); 65 | CVMatSurfaceSource::imshow("main", m_frame); 66 | 67 | 68 | //qDebug() << "imshow" << m_frame.cols << m_frame.rows << m_frame.size; 69 | 70 | //qDebug() << QTime::currentTime(); 71 | 72 | if(isVideoFile) 73 | QThread::usleep(sleepBetweenFrames); 74 | } 75 | delete m_capture; 76 | emit workDone(); 77 | } 78 | 79 | CaptureController::CaptureController(QObject *parent) : 80 | QObject(parent), m_worker(nullptr), m_lock(nullptr), m_status(Status::Stopped) 81 | { 82 | m_undistortLock = new QReadWriteLock; 83 | } 84 | 85 | CaptureController::~CaptureController() 86 | { 87 | stop(); 88 | } 89 | 90 | const cv::Mat CaptureController::frameRef() const 91 | { 92 | if (!m_worker) 93 | return cv::Mat(); 94 | QReadLocker lock(m_lock); 95 | return m_worker->m_frame; 96 | } 97 | 98 | cv::Mat CaptureController::frameCopy() const 99 | { 100 | if (!m_worker) 101 | return cv::Mat(); 102 | QReadLocker lock(m_lock); 103 | cv::Mat frame; 104 | m_worker->m_frame.copyTo(frame); 105 | return frame; 106 | } 107 | 108 | CaptureController::Status CaptureController::status() const 109 | { 110 | return m_status; 111 | } 112 | 113 | void CaptureController::enableUndistort(const cv::Mat &intrinsic, const cv::Mat &distCoeffs) 114 | { 115 | if (m_status != Status::Started) { 116 | qWarning() << "Can't enable undistort before capture is started"; 117 | return; 118 | } 119 | QWriteLocker lock(m_undistortLock); 120 | m_worker->m_intrinsic = intrinsic; 121 | m_worker->m_distCoeffs = distCoeffs; 122 | m_worker->m_useUndistort = true; 123 | } 124 | 125 | void CaptureController::start(const QString &device) 126 | { 127 | if (m_status == Status::Started) 128 | stop(); 129 | else if (m_status == Status::Starting) { 130 | qWarning() << "start() called while capture was in Starting mode, ignoring"; 131 | return; 132 | } 133 | setStatus(Status::Starting); 134 | m_lock = new QReadWriteLock; 135 | m_worker = new CaptureWorker(device, this, nullptr); 136 | m_worker->moveToThread(&m_workerThread); 137 | connect(&m_workerThread, &QThread::started, m_worker, &CaptureWorker::doWork); 138 | // connect(&m_workerThread, &QThread::finished, this, &CaptureController::stop); 139 | connect(m_worker, &CaptureWorker::frameReady, this, &CaptureController::frameReady); 140 | connect(m_worker, &CaptureWorker::workDone, this, &CaptureController::stop); 141 | m_workerThread.start(); 142 | } 143 | 144 | void CaptureController::stop() 145 | { 146 | if (m_status == Status::Stopped) 147 | return; 148 | qDebug() << "Capture is stopping from" << m_status << "state ..."; 149 | 150 | disconnect(m_worker, &CaptureWorker::frameReady, this, &CaptureController::frameReady); 151 | m_worker->stop(); 152 | m_workerThread.quit(); 153 | if(!m_workerThread.wait(1000)) { 154 | qWarning() << "Capture thread did not terminate until timeout, trying terminate()"; 155 | m_workerThread.terminate(); 156 | } 157 | delete m_worker; 158 | m_worker = nullptr; 159 | delete m_lock; 160 | m_lock = nullptr; 161 | setStatus(Status::Stopped); 162 | } 163 | 164 | void CaptureController::setStatus(CaptureController::Status status) 165 | { 166 | if (status == m_status) 167 | return; 168 | m_status = status; 169 | emit statusChanged(); 170 | } 171 | 172 | -------------------------------------------------------------------------------- /capturecontroller.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CAPTURECONTROLLER_HPP 2 | #define CAPTURECONTROLLER_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace cv { 9 | class VideoCapture; 10 | } 11 | 12 | class QReadWriteLock; 13 | class CaptureController; 14 | class CaptureWorker : public QObject 15 | { 16 | Q_OBJECT 17 | public: 18 | explicit CaptureWorker(const QString &device, CaptureController *captureController, QObject *parent = nullptr); 19 | 20 | /** 21 | * @brief stop Stops capture and exits doWork() loop 22 | */ 23 | void stop(); 24 | signals: 25 | void frameReady(); 26 | void workDone(); 27 | 28 | public slots: 29 | void doWork(); 30 | 31 | private: 32 | friend class CaptureController; 33 | QString m_device; 34 | cv::VideoCapture *m_capture; 35 | CaptureController *m_captureController; 36 | cv::Mat m_frame; 37 | bool m_loopRunning; 38 | bool m_useUndistort; 39 | cv::Mat m_intrinsic; 40 | cv::Mat m_distCoeffs; 41 | }; 42 | 43 | class CaptureController : public QObject 44 | { 45 | Q_OBJECT 46 | Q_PROPERTY(Status status READ status NOTIFY statusChanged) 47 | public: 48 | explicit CaptureController(QObject *parent = nullptr); 49 | ~CaptureController(); 50 | 51 | const cv::Mat frameRef() const; 52 | cv::Mat frameCopy() const; 53 | 54 | /** 55 | * @brief The Status enum 56 | */ 57 | enum class Status { 58 | Starting, ///< Capture is in startup 59 | Started, ///< Capture successfully started and frameReady() signals is emitting 60 | Stopped, ///< Initial state or after @see stop() was called 61 | EofOrDisconnected, ///< Video file ended or camera was disconnected 62 | Failed ///< After Starting if it wasn't successful 63 | }; 64 | Q_ENUM(Status) 65 | Status status() const; 66 | 67 | void enableUndistort(const cv::Mat &intrinsic, const cv::Mat &distCoeffs); 68 | 69 | public slots: 70 | void start(const QString &device); 71 | void stop(); 72 | signals: 73 | void statusChanged(); 74 | void frameReady(); 75 | //void statisticsReady(QVariant ? 76 | 77 | private: 78 | friend class CaptureWorker; 79 | CaptureWorker* m_worker; 80 | QThread m_workerThread; 81 | mutable QReadWriteLock* m_lock; 82 | mutable QReadWriteLock* m_undistortLock; 83 | 84 | void setStatus(Status status); 85 | Status m_status; 86 | }; 87 | 88 | #endif // CAPTURECONTROLLER_HPP 89 | -------------------------------------------------------------------------------- /cvmatsurfacesource.cpp: -------------------------------------------------------------------------------- 1 | #include "cvmatsurfacesource.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | Q_DECLARE_METATYPE(cv::Mat) 11 | 12 | struct CVMatSurfaceSourcePrivate { 13 | CVMatSurfaceSourcePrivate() {} 14 | ~CVMatSurfaceSourcePrivate() {} 15 | QHash sources; 16 | }; 17 | Q_GLOBAL_STATIC(CVMatSurfaceSourcePrivate, g_sources) 18 | 19 | CVMatSurfaceSource::CVMatSurfaceSource(QObject *parent) : QObject(parent), 20 | m_surface(nullptr), m_videoFrame(nullptr) 21 | { 22 | } 23 | 24 | CVMatSurfaceSource::~CVMatSurfaceSource() 25 | { 26 | stopSurface(); 27 | CVMatSurfaceSourcePrivate *d = g_sources; 28 | if (d) { 29 | d->sources.remove(m_name); 30 | } 31 | } 32 | 33 | QAbstractVideoSurface *CVMatSurfaceSource::videoSurface() const 34 | { 35 | return m_surface; 36 | } 37 | 38 | void CVMatSurfaceSource::setVideoSurface(QAbstractVideoSurface *surface) 39 | { 40 | if (m_surface && m_surface->isActive()) 41 | m_surface->stop(); 42 | m_surface = surface; 43 | } 44 | 45 | void CVMatSurfaceSource::imshow(const cv::Mat &mat) 46 | { 47 | if (!m_surface) 48 | return; 49 | if (mat.empty()) 50 | return; 51 | if (QThread::currentThread() != this->thread()) { 52 | QVariant matVariant = QVariant::fromValue(mat.clone()); 53 | QMetaObject::invokeMethod(this, "imshowFromVariant", Qt::QueuedConnection, Q_ARG(QVariant, matVariant)); 54 | return; 55 | } 56 | if (!m_videoFrame) { 57 | createVideoFrame(mat); 58 | } 59 | if (matHasChanged(mat)) { 60 | if (m_videoFrame->isMapped()) 61 | m_videoFrame->unmap(); 62 | delete m_videoFrame; 63 | createVideoFrame(mat); 64 | } 65 | cv::Mat matFromSurface = cv::Mat(m_videoFrame->size().height(), 66 | m_videoFrame->size().width(), 67 | CV_8UC4, m_videoFrame->bits()); 68 | if (mat.channels() == 3) { 69 | cv::cvtColor(mat, matFromSurface, cv::COLOR_RGB2RGBA); 70 | } else if (mat.channels() == 1) { 71 | cv::cvtColor(mat, matFromSurface, cv::COLOR_GRAY2RGBA); 72 | } else { 73 | qDebug() << "Wrong channel count"; 74 | return; 75 | } 76 | if (!m_surface->present(*m_videoFrame)) { 77 | qDebug() << "Surface present() failed" << m_surface->error(); 78 | } 79 | } 80 | 81 | void CVMatSurfaceSource::imshowFromVariant(const QVariant &matVariant) 82 | { 83 | cv::Mat mat = matVariant.value(); 84 | imshow(mat); 85 | } 86 | 87 | void CVMatSurfaceSource::imshow(const QString &surfaceName, const cv::Mat &mat) 88 | { 89 | CVMatSurfaceSourcePrivate *d = g_sources; 90 | if (d) { 91 | CVMatSurfaceSource *source = d->sources.value(surfaceName, nullptr); 92 | if (source) 93 | source->imshow(mat); 94 | else 95 | qWarning() << "CVMatSurfaceSource with name" << surfaceName << "doesn't exist or not yet created"; 96 | } 97 | } 98 | 99 | void CVMatSurfaceSource::stopSurface() 100 | { 101 | if (m_surface && m_surface->isActive()) 102 | m_surface->stop(); 103 | } 104 | 105 | void CVMatSurfaceSource::createVideoFrame(const cv::Mat &mat) 106 | { 107 | m_videoFrame = new QVideoFrame(mat.rows * mat.cols * 4, 108 | QSize(mat.cols, mat.rows), 109 | mat.cols * 4, 110 | QVideoFrame::Format_ARGB32); 111 | m_format = QVideoSurfaceFormat(m_videoFrame->size(), m_videoFrame->pixelFormat()); 112 | m_surface->start(m_format); 113 | if (!m_videoFrame->map(QAbstractVideoBuffer::ReadOnly)) { 114 | qWarning() << "QVideoFrame::map() failed"; 115 | } 116 | emit dimensionsChanged(); 117 | } 118 | 119 | bool CVMatSurfaceSource::matHasChanged(const cv::Mat &mat) const 120 | { 121 | return m_videoFrame->size().width() != mat.cols || 122 | m_videoFrame->size().height() != mat.rows; 123 | } 124 | 125 | QString CVMatSurfaceSource::name() const 126 | { 127 | return m_name; 128 | } 129 | 130 | quint32 CVMatSurfaceSource::width() const 131 | { 132 | if (!m_format.isValid()) 133 | return 0; 134 | return m_format.frameWidth(); 135 | } 136 | 137 | quint32 CVMatSurfaceSource::height() const 138 | { 139 | if (!m_format.isValid()) 140 | return 0; 141 | return m_format.frameHeight(); 142 | } 143 | 144 | void CVMatSurfaceSource::setName(const QString &name) 145 | { 146 | CVMatSurfaceSourcePrivate *d = g_sources; 147 | if (d) { 148 | d->sources.remove(m_name); 149 | d->sources.insert(name, this); 150 | } 151 | m_name = name; 152 | } 153 | -------------------------------------------------------------------------------- /cvmatsurfacesource.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CVMATSURFACESOURCE_H 2 | #define CVMATSURFACESOURCE_H 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | class QAbstractVideoSurface; 10 | 11 | 12 | class CVMatSurfaceSource : public QObject 13 | { 14 | Q_OBJECT 15 | Q_PROPERTY(QAbstractVideoSurface* videoSurface READ videoSurface WRITE setVideoSurface) 16 | Q_PROPERTY(QString name READ name WRITE setName) 17 | Q_PROPERTY(quint32 width READ width NOTIFY dimensionsChanged) 18 | Q_PROPERTY(quint32 height READ height NOTIFY dimensionsChanged) 19 | public: 20 | explicit CVMatSurfaceSource(QObject *parent = nullptr); 21 | ~CVMatSurfaceSource(); 22 | 23 | QAbstractVideoSurface* videoSurface() const; 24 | void setVideoSurface(QAbstractVideoSurface *surface); 25 | 26 | /** 27 | * @brief imshow Sends mat to video surface setted by @ref setVideoSurface 28 | * @param mat 29 | */ 30 | void imshow(const cv::Mat &mat); 31 | /** 32 | * @brief imshow Finds surface source by name in global hashmap and calls imshow on it 33 | * @param surfaceName 34 | * @param mat 35 | */ 36 | static void imshow(const QString &surfaceName, const cv::Mat &mat); 37 | 38 | /** 39 | * @brief setName Sets name and mades this object accessible through static imshow() 40 | * @param name 41 | */ 42 | void setName(const QString &name); 43 | QString name() const; 44 | 45 | quint32 width() const; 46 | quint32 height() const; 47 | 48 | signals: 49 | void dimensionsChanged(); 50 | 51 | private slots: 52 | void imshowFromVariant(const QVariant &matVariant); 53 | 54 | private: 55 | void stopSurface(); 56 | void createVideoFrame(const cv::Mat &mat); 57 | bool matHasChanged(const cv::Mat &mat) const; 58 | QAbstractVideoSurface* m_surface; 59 | QVideoFrame* m_videoFrame; 60 | QVideoSurfaceFormat m_format; 61 | QString m_name; 62 | }; 63 | 64 | #endif // CVMATSURFACESOURCE_H 65 | -------------------------------------------------------------------------------- /gcodeplayer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "gcodeplayer.h" 9 | 10 | GcodePlayer::GcodePlayer(QObject *parent) : QObject(parent) 11 | { 12 | m_model = new GcodePlayerModel(this); 13 | m_currentLineNumber = 0; 14 | m_linesCount = 0; 15 | m_state = Stopped; 16 | 17 | m_tcp = new QTcpSocket(this); 18 | m_connectionState = Disconnected; 19 | emit connectionStateChanged(); 20 | connect(m_tcp, &QTcpSocket::stateChanged, 21 | this, &GcodePlayer::onSocketStateChanged); 22 | connect(m_tcp, QOverload::of(&QAbstractSocket::error), 23 | this, &GcodePlayer::onSocketError); 24 | connect(m_tcp, &QIODevice::readyRead, 25 | this, &GcodePlayer::onMCResponse); 26 | m_querySent = false; 27 | } 28 | 29 | void GcodePlayer::registerQmlTypes() 30 | { 31 | qmlRegisterType("tech.vhrd", 1, 0, "GcodePlayer"); 32 | } 33 | 34 | GcodePlayerModel *GcodePlayer::model() const 35 | { 36 | return m_model; 37 | } 38 | 39 | void GcodePlayer::loadFile(const QUrl &fileUrl) 40 | { 41 | if (m_state == Playing || m_state == Paused) { 42 | qWarning() << "Stop first"; 43 | return; 44 | } 45 | m_model->removeAll(); 46 | 47 | QString fileName; 48 | if (fileUrl.isLocalFile()) 49 | fileName = fileUrl.toLocalFile(); 50 | else 51 | return; 52 | QFile f(fileName); 53 | if (!f.open(QIODevice::ReadOnly)) { 54 | qWarning() << "Can't open" << fileName << f.errorString(); 55 | return; 56 | } 57 | int lineNumber = 1; 58 | while (!f.atEnd()) { 59 | QString code = QString::fromLocal8Bit(f.readLine()); 60 | m_model->addItem(GcodePlayerItem(GcodePlayerItem::Pending, lineNumber, code)); 61 | 62 | lineNumber++; 63 | } 64 | 65 | m_currentLineNumber = 1; 66 | emit currentLineChanged(); 67 | m_linesCount = lineNumber - 1; 68 | emit linesCountChanged(); 69 | m_state = Stopped; 70 | emit stateChanged(); 71 | } 72 | 73 | int GcodePlayer::currentLineNumber() const 74 | { 75 | return m_currentLineNumber; 76 | } 77 | 78 | void GcodePlayer::setCurrentLineNumber(int currentLineNumber) 79 | { 80 | m_currentLineNumber = currentLineNumber; 81 | } 82 | 83 | int GcodePlayer::linesCount() const 84 | { 85 | return m_linesCount; 86 | } 87 | 88 | GcodePlayer::State GcodePlayer::state() const 89 | { 90 | return m_state; 91 | } 92 | 93 | GcodePlayer::ConnectionState GcodePlayer::connectionState() const 94 | { 95 | return m_connectionState; 96 | } 97 | 98 | void GcodePlayer::send(const QString &command) 99 | { 100 | if (m_connectionState != Disconnected) { 101 | m_tcp->write(command.toLocal8Bit()); 102 | } 103 | } 104 | 105 | void GcodePlayer::connectToMC() 106 | { 107 | if (m_connectionState == Disconnected) { 108 | m_tcp->connectToHost("192.168.88.77", 23); 109 | QTimer::singleShot(2000, [=](){ 110 | if (this->m_connectionState != Connected) 111 | this->m_tcp->abort(); 112 | }); 113 | } 114 | } 115 | 116 | void GcodePlayer::play() 117 | { 118 | if (m_state == Stopped) { 119 | if (m_linesCount > 0) { 120 | m_currentLineNumber = 1; 121 | emit currentLineChanged(); 122 | m_model->changeAllStates(GcodePlayerItem::Pending); 123 | sendNextLine(); 124 | m_state = Playing; 125 | emit stateChanged(); 126 | } else { 127 | qWarning() << "Nothing to play"; 128 | } 129 | } else if (m_state == Paused) { 130 | sendNextLine(); 131 | m_state = Playing; 132 | emit stateChanged(); 133 | } else { 134 | qWarning() << "Can't play from state" << m_state; 135 | } 136 | } 137 | 138 | void GcodePlayer::pause() 139 | { 140 | m_state = Paused; 141 | emit stateChanged(); 142 | } 143 | 144 | void GcodePlayer::stop() 145 | { 146 | m_state = Stopped; 147 | emit stateChanged(); 148 | } 149 | 150 | void GcodePlayer::onSocketStateChanged(QAbstractSocket::SocketState state) 151 | { 152 | if (state == QAbstractSocket::ConnectedState) { 153 | m_connectionState = Connected; 154 | emit connectionStateChanged(true); 155 | } else if (state == QAbstractSocket::UnconnectedState) { 156 | m_connectionState = Disconnected; 157 | emit connectionStateChanged(false); 158 | } else { 159 | m_connectionState = Connecting; 160 | emit connectionStateChanged(false); 161 | } 162 | emit connectionStateChanged(); 163 | } 164 | 165 | void GcodePlayer::onSocketError(QAbstractSocket::SocketError error) 166 | { 167 | 168 | switch (error) { 169 | case QAbstractSocket::RemoteHostClosedError: 170 | break; 171 | case QAbstractSocket::HostNotFoundError: 172 | // QMessageBox::information(this, tr("Fortune Client"), 173 | // tr("The host was not found. Please check the " 174 | // "host name and port settings.")); 175 | break; 176 | case QAbstractSocket::ConnectionRefusedError: 177 | // QMessageBox::information(this, tr("Fortune Client"), 178 | // tr("The connection was refused by the peer. " 179 | // "Make sure the fortune server is running, " 180 | // "and check that the host name and port " 181 | // "settings are correct.")); 182 | break; 183 | default: 184 | // QMessageBox::information(this, tr("Fortune Client"), 185 | // tr("The following error occurred: %1.") 186 | // .arg(tcpSocket->errorString())); 187 | break; 188 | } 189 | 190 | } 191 | 192 | void GcodePlayer::onMCResponse() 193 | { 194 | QByteArray bytes = m_tcp->readAll(); 195 | for (int i = 0; i < bytes.length(); ++i) { 196 | if (bytes[i] == '\r') 197 | continue; 198 | if (bytes[i] == '\n') { 199 | if (!m_tcpLine.isEmpty()) { 200 | processMCResponse(m_tcpLine); 201 | m_tcpLine.clear(); 202 | } 203 | continue; 204 | } 205 | m_tcpLine.append(QChar(bytes[i])); 206 | } 207 | } 208 | 209 | void GcodePlayer::sendNextLine() 210 | { 211 | GcodePlayerItem line = m_model->getItem(m_currentLineNumber - 1); 212 | m_tcp->write(line.m_code.toLocal8Bit()); 213 | m_querySent = true; 214 | } 215 | 216 | void GcodePlayer::processMCResponse(const QString &line) 217 | { 218 | if (m_querySent) { 219 | qDebug() << "mc q:" << line; 220 | m_querySent = false; 221 | 222 | GcodePlayerItem item = m_model->getItem(m_currentLineNumber - 1); 223 | if (line == "ok") { 224 | item.m_status = GcodePlayerItem::Ok; 225 | m_model->replaceItem(m_currentLineNumber - 1, item); 226 | } else { 227 | item.m_response = line; 228 | item.m_status = GcodePlayerItem::Warning; 229 | m_model->replaceItem(m_currentLineNumber - 1, item); 230 | } 231 | m_currentLineNumber++; 232 | if (m_currentLineNumber > m_linesCount) { 233 | m_state = Stopped; 234 | emit stateChanged(); 235 | } else { 236 | emit currentLineChanged(); 237 | if (m_state == Playing) 238 | sendNextLine(); 239 | } 240 | } else { 241 | qDebug() << "mc:" << line; 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /gcodeplayer.h: -------------------------------------------------------------------------------- 1 | #ifndef GCODEPLAYER_H 2 | #define GCODEPLAYER_H 3 | 4 | #include 5 | #include 6 | #include "gcodeplayermodel.h" 7 | 8 | class GcodePlayer : public QObject 9 | { 10 | Q_OBJECT 11 | Q_PROPERTY(GcodePlayerModel* model READ model CONSTANT) 12 | Q_PROPERTY(int currentLineNumber READ currentLineNumber WRITE setCurrentLineNumber NOTIFY currentLineChanged) 13 | Q_PROPERTY(int linesCount READ linesCount NOTIFY linesCountChanged) 14 | Q_PROPERTY(State state READ state NOTIFY stateChanged); 15 | Q_PROPERTY(ConnectionState connectionState READ connectionState NOTIFY connectionStateChanged) 16 | 17 | public: 18 | explicit GcodePlayer(QObject *parent = nullptr); 19 | 20 | enum State { 21 | Stopped, 22 | Playing, 23 | Paused, 24 | Error 25 | }; 26 | Q_ENUM(State) 27 | 28 | enum ConnectionState { 29 | Disconnected, 30 | Connecting, 31 | Connected 32 | }; 33 | Q_ENUM(ConnectionState) 34 | 35 | static void registerQmlTypes(); 36 | 37 | GcodePlayerModel *model() const; 38 | Q_INVOKABLE void loadFile(const QUrl &fileUrl); 39 | 40 | int currentLineNumber() const; 41 | void setCurrentLineNumber(int currentLineNumber); 42 | 43 | int linesCount() const; 44 | 45 | State state() const; 46 | ConnectionState connectionState() const; 47 | 48 | 49 | 50 | signals: 51 | void currentLineChanged(); 52 | void linesCountChanged(); 53 | void stateChanged(); 54 | void connectionStateChanged(); 55 | void connectionStateChanged(bool connected); 56 | 57 | 58 | public slots: 59 | void connectToMC(); 60 | void play(); 61 | void pause(); 62 | void stop(); 63 | void send(const QString &command); 64 | 65 | private slots: 66 | void onSocketStateChanged(QAbstractSocket::SocketState state); 67 | void onSocketError(QAbstractSocket::SocketError error); 68 | void onMCResponse(); 69 | 70 | private: 71 | void sendNextLine(); 72 | void processMCResponse(const QString &line); 73 | 74 | GcodePlayerModel *m_model; 75 | int m_currentLineNumber; 76 | int m_linesCount; 77 | State m_state; 78 | ConnectionState m_connectionState; 79 | QTcpSocket *m_tcp; 80 | QString m_tcpLine; 81 | bool m_querySent; 82 | }; 83 | 84 | #endif // GCODEPLAYER_H 85 | -------------------------------------------------------------------------------- /gcodeplayeritem.h: -------------------------------------------------------------------------------- 1 | #ifndef GCODEPLAYERITEM_H 2 | #define GCODEPLAYERITEM_H 3 | 4 | #include 5 | 6 | class GcodePlayerItem 7 | { 8 | Q_GADGET 9 | Q_PROPERTY(Status status MEMBER m_status) 10 | Q_PROPERTY(int lineNumber MEMBER m_lineNumber) 11 | Q_PROPERTY(QString code MEMBER m_code) 12 | Q_PROPERTY(QString response MEMBER m_response) 13 | public: 14 | enum Status { 15 | Pending, 16 | Ok, 17 | Warning, 18 | InternalCommand 19 | }; 20 | Q_ENUM(Status) 21 | 22 | GcodePlayerItem(Status status, int lineNumber, QString code): 23 | m_status(status), m_lineNumber(lineNumber), m_code(code) 24 | { 25 | } 26 | 27 | GcodePlayerItem() : 28 | m_status(Pending), m_lineNumber(0) 29 | { 30 | } 31 | 32 | 33 | Status m_status; 34 | int m_lineNumber; 35 | QString m_code; 36 | QString m_response; 37 | }; 38 | Q_DECLARE_METATYPE(GcodePlayerItem) 39 | 40 | #endif // GCODEPLAYERITEM_H 41 | -------------------------------------------------------------------------------- /gcodeplayermodel.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "gcodeplayermodel.h" 4 | 5 | GcodePlayerModel::GcodePlayerModel(QObject *parent) 6 | { 7 | 8 | } 9 | 10 | void GcodePlayerModel::registerQmlTypes() 11 | { 12 | qmlRegisterType("tech.vhrd", 1, 0, "GcodePlayerModel"); 13 | qmlRegisterUncreatableType("tech.vhrd", 1, 0, "GcodePlayerItem", "enums only"); 14 | //qRegisterMetaType("GcodePlayerItem"); 15 | } 16 | 17 | int GcodePlayerModel::rowCount(const QModelIndex &parent) const 18 | { 19 | Q_UNUSED(parent); 20 | return m_items.count(); 21 | } 22 | 23 | QVariant GcodePlayerModel::data(const QModelIndex &index, int role) const 24 | { 25 | if (index.row() < 0 || index.row() >= m_items.count()) 26 | return QVariant(); 27 | 28 | const GcodePlayerItem& item = m_items[index.row()]; 29 | if (role == StatusRole) 30 | return item.m_status; 31 | else if (role == LineNumberRole) 32 | return item.m_lineNumber; 33 | else if (role == CodeRole) 34 | return item.m_code; 35 | else if (role == ResponseRole) 36 | return item.m_response; 37 | return QVariant(); 38 | } 39 | 40 | QHash GcodePlayerModel::roleNames() const 41 | { 42 | QHash roles; 43 | roles[StatusRole] = "status"; 44 | roles[LineNumberRole] = "lineNumber"; 45 | roles[CodeRole] = "code"; 46 | roles[ResponseRole] = "response"; 47 | return roles; 48 | } 49 | 50 | void GcodePlayerModel::addItem(const GcodePlayerItem &item) 51 | { 52 | beginInsertRows(QModelIndex(), rowCount(), rowCount()); 53 | m_items << item; 54 | endInsertRows(); 55 | } 56 | 57 | GcodePlayerItem GcodePlayerModel::getItem(int index) 58 | { 59 | if (index < 0 || index >= m_items.length()) 60 | return GcodePlayerItem(); 61 | return m_items[index]; 62 | } 63 | 64 | void GcodePlayerModel::replaceItem(int index, const GcodePlayerItem &item) 65 | { 66 | if (index < 0 || index > m_items.count()) 67 | return; 68 | QModelIndex modelIndex = createIndex(index, 0); 69 | m_items[index] = item; 70 | emit dataChanged(modelIndex, modelIndex); 71 | } 72 | 73 | void GcodePlayerModel::changeAllStates(GcodePlayerItem::Status to) 74 | { 75 | for (int i = 0; i < m_items.length(); ++i) 76 | m_items[i].m_status = to; 77 | QModelIndex startIndex = createIndex(0, 0); 78 | QModelIndex endIndex = createIndex(m_items.length() - 1, 0); 79 | emit dataChanged(startIndex, endIndex); 80 | } 81 | 82 | void GcodePlayerModel::removeAll() 83 | { 84 | beginRemoveRows(QModelIndex(), 0, m_items.count()); 85 | m_items.clear(); 86 | endRemoveRows(); 87 | } 88 | 89 | -------------------------------------------------------------------------------- /gcodeplayermodel.h: -------------------------------------------------------------------------------- 1 | #ifndef GCODEPLAYERMODEL_H 2 | #define GCODEPLAYERMODEL_H 3 | 4 | #include 5 | #include 6 | #include "gcodeplayeritem.h" 7 | 8 | class GcodePlayerModel : public QAbstractListModel 9 | { 10 | Q_OBJECT 11 | public: 12 | GcodePlayerModel(QObject *parent = nullptr); 13 | 14 | static void registerQmlTypes(); 15 | 16 | enum ItemRoles { 17 | StatusRole = Qt::UserRole + 1, 18 | LineNumberRole, 19 | CodeRole, 20 | ResponseRole 21 | }; 22 | 23 | int rowCount(const QModelIndex& parent = QModelIndex()) const override; 24 | QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; 25 | 26 | void addItem(const GcodePlayerItem &item); 27 | GcodePlayerItem getItem(int index); 28 | void replaceItem(int index, const GcodePlayerItem &item); 29 | void changeAllStates(GcodePlayerItem::Status to); 30 | void removeAll(); 31 | 32 | protected: 33 | QHash roleNames() const override; 34 | 35 | signals: 36 | 37 | private: 38 | 39 | QList m_items; 40 | }; 41 | 42 | #endif // GCODEPLAYERMODEL_H 43 | -------------------------------------------------------------------------------- /linedetector.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include "linedetector.h" 6 | #include "capturecontroller.hpp" 7 | #include "cvmatsurfacesource.hpp" 8 | 9 | Q_LOGGING_CATEGORY(lineDetector, "vhrd.vision.linedetector") 10 | 11 | LineDetector::LineDetector(CaptureController *captureController, QObject *parent) : QObject(parent), 12 | m_captureController(captureController) 13 | { 14 | m_timer = new QTimer(this); 15 | m_timer->setInterval(2000); 16 | m_timer->setSingleShot(true); 17 | connect(m_timer, &QTimer::timeout, 18 | this, &LineDetector::onTimeout); 19 | 20 | m_state = Unlocked; 21 | 22 | m_dz = 0; 23 | m_zerodxs = false; 24 | m_dxs0 = 0; 25 | m_ppmm = 9.8833333; 26 | m_s0 = 124; 27 | m_f = 3.6; 28 | m_lz = 36; 29 | m_lx = 243; 30 | 31 | m_angle = 0; 32 | 33 | m_hueLowRangeFrom = 0; 34 | m_hueLowRangeTo = 10; 35 | m_hueHighRangeFrom = 160; 36 | m_hueHighRangeTo = 179; 37 | m_saturationFrom = 10; 38 | m_saturationTo = 255; 39 | m_valueFrom = 10; 40 | m_valueTo = 255; 41 | m_integrateFrom = 0.2; 42 | m_integrateTo = 0.8; 43 | m_threshold = 0.6; 44 | } 45 | 46 | LineDetector::State LineDetector::state() const 47 | { 48 | return m_state; 49 | } 50 | 51 | void LineDetector::onFrameReady() 52 | { 53 | cv::Mat frame = m_captureController->frameCopy(); 54 | 55 | cv::Mat rm = cv::getRotationMatrix2D(cv::Point(frame.cols / 2, frame.rows / 2), m_angle + 90, 1.0); 56 | cv::Mat rotated; 57 | cv::warpAffine(frame, rotated, rm, cv::Size(frame.cols, frame.rows)); 58 | frame = rotated; 59 | 60 | // Convert to HSV, filter 61 | cv::Mat hsv; 62 | cv::cvtColor(frame, hsv, cv::COLOR_BGR2HSV); 63 | cv::Mat upper_red_hue; 64 | cv::Mat lower_red_hue; 65 | cv::inRange(hsv, 66 | cv::Scalar(m_hueLowRangeFrom, m_saturationFrom, m_valueFrom), 67 | cv::Scalar(m_hueLowRangeTo, m_saturationTo, m_valueTo), 68 | lower_red_hue); 69 | cv::inRange(hsv, 70 | cv::Scalar(m_hueHighRangeFrom, m_saturationFrom, m_valueFrom), 71 | cv::Scalar(m_hueHighRangeTo, m_saturationTo, m_valueTo), 72 | upper_red_hue); 73 | cv::Mat red_hue; 74 | cv::addWeighted(lower_red_hue, 1.0, upper_red_hue, 1.0, 0.0, red_hue); 75 | 76 | // Mask V channel 77 | std::vector hsvSplitted; 78 | cv::split(hsv, hsvSplitted); 79 | cv::Mat masked; 80 | hsvSplitted[2].copyTo(masked, red_hue); 81 | 82 | // Integration limits 83 | quint16 colfrom = m_integrateFrom * frame.cols; 84 | quint16 colto = m_integrateTo * frame.cols; 85 | 86 | // Show as red channel 87 | cv::Mat desaturateMask(frame.rows, frame.cols, CV_8UC1, cv::Scalar(0)); 88 | cv::rectangle(desaturateMask, cv::Point(0, 0), cv::Point(colfrom, frame.rows), cv::Scalar(255), -1); 89 | cv::rectangle(desaturateMask, cv::Point(colto, 0), cv::Point(frame.cols, frame.rows), cv::Scalar(255), -1); 90 | cv::subtract(masked, cv::Scalar(127), masked, desaturateMask); 91 | 92 | cv::Mat zero(frame.rows, frame.cols, CV_8UC1, cv::Scalar(0)); 93 | std::vector channels; 94 | channels.push_back(zero); 95 | channels.push_back(zero); 96 | channels.push_back(masked); 97 | cv::Mat merged; 98 | cv::merge(channels, merged); 99 | CVMatSurfaceSource::imshow("second", merged); 100 | 101 | // Integrate 102 | QVector linesSum; 103 | quint8 *mdata = static_cast(masked.data); 104 | for (quint16 row = frame.rows - 1; row > 0; row--) { 105 | float s = 0; 106 | for (quint16 col = colfrom; col < colto; col++) { 107 | s += mdata[masked.step * row + col]; 108 | } 109 | linesSum.push_back(s); 110 | } 111 | float max = 0; 112 | float thresholdAbs = m_threshold * frame.cols * 255; 113 | QVector overThreshold; 114 | for (int i = 0; i < linesSum.size(); i++) { 115 | if (linesSum[i] > max) { 116 | max = linesSum[i]; 117 | } 118 | if (linesSum[i] > thresholdAbs) { 119 | overThreshold.push_back(i); 120 | } 121 | } 122 | for (int i = 0; i < linesSum.size(); i++) { 123 | linesSum[i] = linesSum[i] / max; 124 | } 125 | emit integrationComplete(linesSum); 126 | 127 | // Find thicknes and center of a light beam 128 | if (overThreshold.count() >= 2) { 129 | int x1 = overThreshold.last(); 130 | QPointF pt1((float)x1 / float(frame.rows), linesSum[x1]); 131 | int x2 = overThreshold.first(); 132 | QPointF pt2((float)x2 / float(frame.rows), pt1.y()); 133 | emit lineDetected(pt1, pt2); 134 | 135 | // Find dz 136 | float dxs = x1 + (float)(x2 - x1) / 2.0f; 137 | if (m_zerodxs) { 138 | m_dxs0 = dxs; 139 | m_zerodxs = false; 140 | } 141 | dxs -= m_dxs0; 142 | //float M = m_f / (m_s0 - m_f); 143 | //float dx = dxs / (M * m_ppmm); 144 | m_dz = (m_lz * dxs * (m_s0 - m_f) ) / (m_lx * m_f * m_ppmm - m_lz * dxs); 145 | emit dzChanged(); 146 | emit dzChanged(m_dz); 147 | 148 | if (m_state != Locked) { 149 | m_state = Locked; 150 | m_timer->stop(); 151 | emit stateChanged(); 152 | emit dzValidChanged(true); 153 | } 154 | } else { 155 | if (m_state == Locked) { 156 | m_state = Hold; 157 | m_timer->start(); 158 | emit stateChanged(); 159 | } 160 | } 161 | } 162 | 163 | void LineDetector::onTimeout() 164 | { 165 | m_state = Unlocked; 166 | emit stateChanged(); 167 | emit dzValidChanged(false); 168 | } 169 | 170 | float LineDetector::rotation() const 171 | { 172 | return m_angle; 173 | } 174 | 175 | void LineDetector::setRotation(float angle) 176 | { 177 | m_angle = angle; 178 | } 179 | 180 | float LineDetector::threshold() const 181 | { 182 | return m_threshold; 183 | } 184 | 185 | void LineDetector::setThreshold(float threshold) 186 | { 187 | m_threshold = threshold; 188 | } 189 | 190 | float LineDetector::dz() const 191 | { 192 | return m_dz; 193 | } 194 | 195 | void LineDetector::zerodxs() 196 | { 197 | m_zerodxs = true; 198 | } 199 | 200 | float LineDetector::integrateTo() const 201 | { 202 | return m_integrateTo; 203 | } 204 | 205 | void LineDetector::setIntegrateTo(float integrateTo) 206 | { 207 | m_integrateTo = integrateTo; 208 | } 209 | 210 | float LineDetector::integrateFrom() const 211 | { 212 | return m_integrateFrom; 213 | } 214 | 215 | void LineDetector::setIntegrateFrom(float integrateFrom) 216 | { 217 | m_integrateFrom = integrateFrom; 218 | } 219 | 220 | quint8 LineDetector::valueTo() const 221 | { 222 | return m_valueTo; 223 | } 224 | 225 | void LineDetector::setValueTo(const quint8 &valueTo) 226 | { 227 | m_valueTo = valueTo; 228 | } 229 | 230 | quint8 LineDetector::valueFrom() const 231 | { 232 | return m_valueFrom; 233 | } 234 | 235 | void LineDetector::setValueFrom(const quint8 &valueFrom) 236 | { 237 | m_valueFrom = valueFrom; 238 | } 239 | 240 | quint8 LineDetector::saturationTo() const 241 | { 242 | return m_saturationTo; 243 | } 244 | 245 | void LineDetector::setSaturationTo(const quint8 &saturationTo) 246 | { 247 | m_saturationTo = saturationTo; 248 | } 249 | 250 | quint8 LineDetector::saturationFrom() const 251 | { 252 | return m_saturationFrom; 253 | } 254 | 255 | void LineDetector::setSaturationFrom(const quint8 &saturationFrom) 256 | { 257 | m_saturationFrom = saturationFrom; 258 | } 259 | 260 | quint8 LineDetector::hueHighRangeTo() const 261 | { 262 | return m_hueHighRangeTo; 263 | } 264 | 265 | void LineDetector::setHueHighRangeTo(const quint8 &hueHighRangeTo) 266 | { 267 | if (hueHighRangeTo > 179) 268 | m_hueLowRangeTo = 179; 269 | else 270 | m_hueHighRangeTo = hueHighRangeTo; 271 | } 272 | 273 | quint8 LineDetector::hueHighRangeFrom() const 274 | { 275 | return m_hueHighRangeFrom; 276 | } 277 | 278 | void LineDetector::setHueHighRangeFrom(const quint8 &hueHighRangeFrom) 279 | { 280 | m_hueHighRangeFrom = hueHighRangeFrom; 281 | } 282 | 283 | quint8 LineDetector::hueLowRangeTo() const 284 | { 285 | return m_hueLowRangeTo; 286 | } 287 | 288 | void LineDetector::setHueLowRangeTo(const quint8 &hueLowRangeTo) 289 | { 290 | if (hueLowRangeTo > 179) 291 | m_hueLowRangeTo = 179; 292 | else 293 | m_hueLowRangeTo = hueLowRangeTo; 294 | } 295 | 296 | quint8 LineDetector::hueLowRangeFrom() const 297 | { 298 | return m_hueLowRangeFrom; 299 | } 300 | 301 | void LineDetector::setHueLowRangeFrom(const quint8 &hueLowRangeFrom) 302 | { 303 | m_hueLowRangeFrom = hueLowRangeFrom; 304 | } 305 | -------------------------------------------------------------------------------- /linedetector.h: -------------------------------------------------------------------------------- 1 | #ifndef LINEDETECTOR_H 2 | #define LINEDETECTOR_H 3 | 4 | #include 5 | #include 6 | 7 | class CaptureController; 8 | class QTimer; 9 | 10 | class LineDetector : public QObject 11 | { 12 | Q_OBJECT 13 | Q_PROPERTY(quint8 hueLowRangeFrom READ hueLowRangeFrom WRITE setHueLowRangeFrom NOTIFY hsvThresholdsChanged) 14 | Q_PROPERTY(quint8 hueLowRangeTo READ hueLowRangeTo WRITE setHueLowRangeTo NOTIFY hsvThresholdsChanged) 15 | Q_PROPERTY(quint8 hueHighRangeFrom READ hueHighRangeFrom WRITE setHueHighRangeFrom NOTIFY hsvThresholdsChanged) 16 | Q_PROPERTY(quint8 hueHighRangeTo READ hueHighRangeTo WRITE setHueHighRangeTo NOTIFY hsvThresholdsChanged) 17 | Q_PROPERTY(quint8 saturationFrom READ saturationFrom WRITE setSaturationFrom NOTIFY hsvThresholdsChanged) 18 | Q_PROPERTY(quint8 saturationTo READ saturationTo WRITE setSaturationTo NOTIFY hsvThresholdsChanged) 19 | Q_PROPERTY(quint8 valueFrom READ valueFrom WRITE setValueFrom NOTIFY hsvThresholdsChanged) 20 | Q_PROPERTY(quint8 valueTo READ valueTo WRITE setValueTo NOTIFY hsvThresholdsChanged) 21 | Q_PROPERTY(float integrateFrom READ integrateFrom WRITE setIntegrateFrom NOTIFY integrationLimitsChanged) 22 | Q_PROPERTY(float integrateTo READ integrateTo WRITE setIntegrateTo NOTIFY integrationLimitsChanged) 23 | Q_PROPERTY(float threshold READ threshold WRITE setThreshold NOTIFY thresholdChanged) 24 | Q_PROPERTY(State state READ state NOTIFY stateChanged) 25 | Q_PROPERTY(float dz READ dz NOTIFY dzChanged) 26 | Q_PROPERTY(float rotation READ rotation WRITE setRotation) 27 | public: 28 | explicit LineDetector(CaptureController *captureController, QObject *parent = nullptr); 29 | 30 | enum State { 31 | Unlocked, 32 | Locked, 33 | Hold 34 | }; 35 | Q_ENUM(State) 36 | State state() const; 37 | 38 | quint8 hueLowRangeFrom() const; 39 | void setHueLowRangeFrom(const quint8 &hueLowRangeFrom); 40 | 41 | quint8 hueLowRangeTo() const; 42 | void setHueLowRangeTo(const quint8 &hueLowRangeTo); 43 | 44 | quint8 hueHighRangeFrom() const; 45 | void setHueHighRangeFrom(const quint8 &hueHighRangeFrom); 46 | 47 | quint8 hueHighRangeTo() const; 48 | void setHueHighRangeTo(const quint8 &hueHighRangeTo); 49 | 50 | quint8 saturationFrom() const; 51 | void setSaturationFrom(const quint8 &saturationFrom); 52 | 53 | quint8 saturationTo() const; 54 | void setSaturationTo(const quint8 &saturationTo); 55 | 56 | quint8 valueFrom() const; 57 | void setValueFrom(const quint8 &valueFrom); 58 | 59 | quint8 valueTo() const; 60 | void setValueTo(const quint8 &valueTo); 61 | 62 | float integrateFrom() const; 63 | void setIntegrateFrom(float integrateFrom); 64 | 65 | float integrateTo() const; 66 | void setIntegrateTo(float integrateTo); 67 | 68 | float threshold() const; 69 | void setThreshold(float threshold); 70 | 71 | float dz() const; 72 | 73 | Q_INVOKABLE void zerodxs(); 74 | 75 | float rotation() const; 76 | void setRotation(float angle); 77 | 78 | signals: 79 | void hsvThresholdsChanged(); 80 | void integrationLimitsChanged(); 81 | void thresholdChanged(); 82 | void integrationComplete(const QVector &data); 83 | void lineDetected(const QPointF &pt1, const QPointF &pt2); 84 | void stateChanged(); 85 | void dzChanged(); 86 | void dzChanged(float dz); 87 | void dzValidChanged(bool valid); 88 | 89 | public slots: 90 | void onFrameReady(); 91 | 92 | private slots: 93 | void onTimeout(); 94 | 95 | private: 96 | CaptureController *m_captureController; 97 | quint8 m_hueLowRangeFrom; 98 | quint8 m_hueLowRangeTo; 99 | quint8 m_hueHighRangeFrom; 100 | quint8 m_hueHighRangeTo; 101 | quint8 m_saturationFrom; 102 | quint8 m_saturationTo; 103 | quint8 m_valueFrom; 104 | quint8 m_valueTo; 105 | float m_integrateFrom; 106 | float m_integrateTo; 107 | float m_threshold; 108 | QTimer *m_timer; 109 | State m_state; 110 | float m_dz; 111 | bool m_zerodxs; 112 | float m_dxs0; 113 | float m_ppmm; 114 | float m_s0; 115 | float m_f; 116 | float m_lz; 117 | float m_lx; 118 | float m_angle; 119 | }; 120 | 121 | Q_DECLARE_LOGGING_CATEGORY(lineDetector) 122 | 123 | #endif // LINEDETECTOR_H 124 | -------------------------------------------------------------------------------- /linedetectordatasource.cpp: -------------------------------------------------------------------------------- 1 | #include "linedetectordatasource.h" 2 | #include 3 | 4 | LineDetectorDataSource::LineDetectorDataSource(QObject *parent) : QObject(parent), 5 | m_integratedSeries(nullptr), m_thresholdSeries(nullptr) 6 | { 7 | 8 | } 9 | 10 | void LineDetectorDataSource::setIntegratedPlotSeries(QAbstractSeries *series) 11 | { 12 | QXYSeries *xySeries = static_cast(series); 13 | if (xySeries) { 14 | m_integratedSeries = xySeries; 15 | connect(xySeries, &QObject::destroyed, 16 | this, &LineDetectorDataSource::integratedSeriesDestroyed); 17 | } 18 | } 19 | 20 | void LineDetectorDataSource::setThresholdPlotSeries(QAbstractSeries *series) 21 | { 22 | QXYSeries *xySeries = static_cast(series); 23 | if (xySeries) { 24 | m_thresholdSeries = xySeries; 25 | } 26 | } 27 | 28 | void LineDetectorDataSource::updateIntegratedPlot(const QVector &data) 29 | { 30 | if (!m_integratedSeries) 31 | return; 32 | 33 | float normalizedStep = 1.0f / data.size(); 34 | float x = 0.0; 35 | QVector points; 36 | for (int i = 0; i < data.size(); i++) { 37 | points.push_back(QPointF(x, data[i])); 38 | x += normalizedStep; 39 | } 40 | m_integratedSeries->replace(points); 41 | } 42 | 43 | void LineDetectorDataSource::drawBeamThickness(const QPointF &pt1, const QPointF &pt2) 44 | { 45 | if (!m_thresholdSeries) 46 | return; 47 | QVector points; 48 | points << pt1 << pt2; 49 | m_thresholdSeries->replace(points); 50 | } 51 | 52 | void LineDetectorDataSource::integratedSeriesDestroyed() 53 | { 54 | m_integratedSeries = nullptr; 55 | m_thresholdSeries = nullptr; 56 | } 57 | -------------------------------------------------------------------------------- /linedetectordatasource.h: -------------------------------------------------------------------------------- 1 | #ifndef LINEDETECTORDATASOURCE_H 2 | #define LINEDETECTORDATASOURCE_H 3 | 4 | #include 5 | #include 6 | 7 | QT_CHARTS_USE_NAMESPACE 8 | 9 | class LineDetectorDataSource : public QObject 10 | { 11 | Q_OBJECT 12 | public: 13 | explicit LineDetectorDataSource(QObject *parent = nullptr); 14 | 15 | Q_INVOKABLE void setIntegratedPlotSeries(QAbstractSeries *series); 16 | Q_INVOKABLE void setThresholdPlotSeries(QAbstractSeries *series); 17 | 18 | signals: 19 | 20 | public slots: 21 | void updateIntegratedPlot(const QVector &data); 22 | void drawBeamThickness(const QPointF &pt1, const QPointF &pt2); 23 | 24 | private slots: 25 | void integratedSeriesDestroyed(); 26 | 27 | private: 28 | QXYSeries *m_integratedSeries; 29 | QXYSeries *m_thresholdSeries; 30 | }; 31 | 32 | #endif // LINEDETECTORDATASOURCE_H 33 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "capturecontroller.hpp" 6 | #include "cvmatsurfacesource.hpp" 7 | #include "cameracalibrator.h" 8 | #include "linedetector.h" 9 | #include "linedetectordatasource.h" 10 | #include "gcodeplayer.h" 11 | #include "rayreceiver.h" 12 | #include "automator.h" 13 | 14 | int main(int argc, char *argv[]) 15 | { 16 | QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); 17 | 18 | QApplication app(argc, argv); 19 | 20 | QQmlApplicationEngine engine; 21 | 22 | qmlRegisterType("io.opencv", 1, 0, "CVMatSurfaceSource"); 23 | //qmlRegisterType("io.opencv", 1, 0, "CaptureController"); 24 | 25 | CaptureController captureController; 26 | 27 | CameraCalibrator cameraCalibrator(&captureController); 28 | QObject::connect(&captureController, &CaptureController::frameReady, 29 | &cameraCalibrator, &CameraCalibrator::onFrameReady); 30 | 31 | qmlRegisterUncreatableType("tech.vhrd.vision", 1, 0, "LineDetector", "Only for enums"); 32 | LineDetector lineDetector(&captureController); 33 | QObject::connect(&captureController, &CaptureController::frameReady, 34 | &lineDetector, &LineDetector::onFrameReady); 35 | LineDetectorDataSource lineDetectorDataSource; 36 | QObject::connect(&lineDetector, &LineDetector::integrationComplete, 37 | &lineDetectorDataSource, &LineDetectorDataSource::updateIntegratedPlot); 38 | QObject::connect(&lineDetector, &LineDetector::lineDetected, 39 | &lineDetectorDataSource, &LineDetectorDataSource::drawBeamThickness); 40 | 41 | engine.rootContext()->setContextProperty("captureController", &captureController); 42 | engine.rootContext()->setContextProperty("cameraCalibrator", &cameraCalibrator); 43 | engine.rootContext()->setContextProperty("lineDetector", &lineDetector); 44 | engine.rootContext()->setContextProperty("lineDetectorDataSource", &lineDetectorDataSource); 45 | 46 | GcodePlayer::registerQmlTypes(); 47 | GcodePlayerModel::registerQmlTypes(); 48 | GcodePlayer player; 49 | engine.rootContext()->setContextProperty("player", &player); 50 | 51 | RayReceiver receiver; 52 | engine.rootContext()->setContextProperty("ray", &receiver); 53 | 54 | Automator automator; 55 | engine.rootContext()->setContextProperty("automator", &automator); 56 | QObject::connect(&lineDetector, QOverload::of(&LineDetector::dzChanged), 57 | &automator, &Automator::ondzChanged); 58 | QObject::connect(&lineDetector, &LineDetector::dzValidChanged, 59 | &automator, &Automator::ondzValidChanged); 60 | QObject::connect(&player, QOverload::of(&GcodePlayer::connectionStateChanged), 61 | &automator, &Automator::onMcConnectionStateChanged); 62 | QObject::connect(&receiver, &RayReceiver::stateChanged, 63 | &automator, &Automator::onMcStateChanged); 64 | QObject::connect(&receiver, &RayReceiver::coordsChanged, 65 | &automator, &Automator::onCoordsChanged); 66 | QObject::connect(&receiver, &RayReceiver::connectionStateChanged, 67 | &automator, &Automator::onRayConnectionStateChanged); 68 | QObject::connect(&automator, &Automator::changePower, 69 | &receiver, &RayReceiver::setLaserPower); 70 | QObject::connect(&automator, &Automator::sendToMC, 71 | &player, &GcodePlayer::send); 72 | 73 | const QUrl url(QStringLiteral("qrc:/main.qml")); 74 | QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, 75 | &app, [url](QObject *obj, const QUrl &objUrl) { 76 | if (!obj && url == objUrl) 77 | QCoreApplication::exit(-1); 78 | }, Qt::QueuedConnection); 79 | engine.load(url); 80 | 81 | return app.exec(); 82 | } 83 | -------------------------------------------------------------------------------- /main.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.10 2 | import QtQuick.Window 2.10 3 | import QtQuick.Controls 2.12 4 | import QtQuick.Layouts 1.12 5 | import QtMultimedia 5.9 6 | import io.opencv 1.0 7 | import tech.vhrd.vision 1.0 8 | import tech.vhrd 1.0 9 | import QtCharts 2.3 10 | import QtQuick.Dialogs 1.2 11 | 12 | Window { 13 | visible: true 14 | width: 1920 15 | height: 1080 16 | title: qsTr("Hello World") 17 | color: "#161616" 18 | 19 | // CaptureController { 20 | // id: captureCtrl 21 | // onStatusChanged: console.log(status) 22 | // } 23 | 24 | VideoOutput { 25 | id: mainVideoOutput 26 | anchors.right: parent.right 27 | anchors.top: parent.top 28 | width: parent.width / 2 29 | height: parent.height / 2 30 | source: CVMatSurfaceSource { 31 | name: "main" 32 | } 33 | } 34 | 35 | VideoOutput { 36 | id: secondVideoOutput 37 | anchors.right: parent.right 38 | anchors.bottom: parent.bottom 39 | width: parent.width / 2 40 | height: parent.height / 2 41 | source: CVMatSurfaceSource { 42 | id: secondVideoSource 43 | name: "second" 44 | } 45 | onWidthChanged: osdRescale(testRect, secondVideoSource, secondVideoOutput) 46 | onHeightChanged: osdRescale(testRect, secondVideoSource, secondVideoOutput) 47 | } 48 | 49 | Item { 50 | id: testRect 51 | anchors.left: secondVideoOutput.left 52 | anchors.top: secondVideoOutput.top 53 | width: secondVideoOutput.width 54 | height: secondVideoOutput.height 55 | 56 | ColumnLayout { 57 | id: settingsLayout 58 | anchors.left: parent.left 59 | anchors.leftMargin: 16 60 | anchors.top: parent.top 61 | anchors.bottom: parent.bottom 62 | width: parent.width * 0.4 63 | spacing: 8 64 | 65 | RowLayout { 66 | Layout.maximumHeight: 24 67 | Text { 68 | text: "Hue range low:" 69 | color: "gray" 70 | Layout.preferredWidth: settingsLayout.width * 0.4 71 | } 72 | 73 | RangeSlider { 74 | from: 0 75 | to: 179 76 | stepSize: 1 77 | first.value: 0 78 | second.value: 10 79 | first.onMoved: lineDetector.hueLowRangeFrom = Math.floor(first.value) 80 | second.onMoved: lineDetector.hueLowRangeTo = Math.floor(second.value) 81 | } 82 | } 83 | RowLayout { 84 | Layout.maximumHeight: 24 85 | Text { 86 | text: "Hue range high:" 87 | color: "gray" 88 | Layout.preferredWidth: settingsLayout.width * 0.4 89 | } 90 | 91 | RangeSlider { 92 | from: 0 93 | to: 179 94 | stepSize: 1 95 | first.value: 170 96 | second.value: 179 97 | first.onMoved: lineDetector.hueHighRangeFrom = Math.floor(first.value) 98 | second.onMoved: lineDetector.hueHighRangeTo = Math.floor(second.value) 99 | } 100 | } 101 | RowLayout { 102 | Layout.maximumHeight: 24 103 | Text { 104 | text: "Saturation range:" 105 | color: "gray" 106 | Layout.preferredWidth: settingsLayout.width * 0.4 107 | } 108 | 109 | RangeSlider { 110 | from: 0 111 | to: 255 112 | stepSize: 1 113 | first.value: 40 114 | second.value: 255 115 | first.onMoved: lineDetector.saturationFrom = Math.floor(first.value) 116 | second.onMoved: lineDetector.saturationTo = Math.floor(second.value) 117 | } 118 | } 119 | RowLayout { 120 | Layout.maximumHeight: 24 121 | Text { 122 | text: "Value range:" 123 | color: "gray" 124 | Layout.preferredWidth: settingsLayout.width * 0.4 125 | } 126 | 127 | RangeSlider { 128 | from: 0 129 | to: 255 130 | stepSize: 1 131 | first.value: 40 132 | second.value: 255 133 | first.onMoved: lineDetector.valueFrom = Math.floor(first.value) 134 | second.onMoved: lineDetector.valueTo = Math.floor(second.value) 135 | } 136 | } 137 | RowLayout { 138 | Layout.maximumHeight: 24 139 | Text { 140 | text: "Integration range:" 141 | color: "gray" 142 | Layout.preferredWidth: settingsLayout.width * 0.4 143 | } 144 | 145 | RangeSlider { 146 | from: 0 147 | to: 1 148 | stepSize: 0.01 149 | first.value: 0.2 150 | second.value: 0.8 151 | first.onMoved: lineDetector.integrateFrom = first.value 152 | second.onMoved: lineDetector.integrateTo = second.value 153 | } 154 | } 155 | RowLayout { 156 | Layout.maximumHeight: 24 157 | Text { 158 | text: "Threshold:" 159 | color: "gray" 160 | Layout.preferredWidth: settingsLayout.width * 0.4 161 | } 162 | 163 | Slider { 164 | id: thresholdSlider 165 | from: 0 166 | to: 1 167 | stepSize: 0.01 168 | onValueChanged: lineDetector.threshold = value 169 | } 170 | } 171 | RowLayout { 172 | Layout.maximumHeight: 24 173 | Text { 174 | text: "Fine rotation:" 175 | color: "gray" 176 | Layout.preferredWidth: settingsLayout.width * 0.4 177 | } 178 | 179 | Slider { 180 | from: -15 181 | to: 15 182 | stepSize: 0.1 183 | onValueChanged: lineDetector.rotation = value 184 | } 185 | } 186 | 187 | 188 | Item { 189 | Layout.fillHeight: true 190 | } 191 | } 192 | 193 | ChartView { 194 | height: parent.width * 0.3 195 | width: parent.height + 112 196 | rotation: -90 197 | anchors.bottom: parent.top 198 | anchors.bottomMargin: 47 199 | anchors.right: parent.right 200 | anchors.rightMargin: -54 201 | transformOrigin: Item.BottomRight 202 | animationOptions: ChartView.NoAnimation 203 | backgroundColor: "transparent" 204 | legend.visible: false 205 | //antialiasing: true 206 | 207 | ValueAxis { 208 | id: axisX 209 | min: 0 210 | max: 1 211 | 212 | gridVisible: false 213 | lineVisible: false 214 | labelsVisible: false 215 | } 216 | ValueAxis { 217 | id: axisY 218 | min: 0 219 | max: 1 220 | gridVisible: false 221 | lineVisible: false 222 | labelsVisible: false 223 | } 224 | LineSeries { 225 | id: integratedPlotSeries 226 | name: "test" 227 | axisX: axisX 228 | axisY: axisY 229 | useOpenGL: true 230 | width: 1.0 231 | } 232 | LineSeries { 233 | id: thresholdPlotSeries 234 | axisX: axisX 235 | axisY: axisY 236 | useOpenGL: true 237 | width: 2.0 238 | color: "red" 239 | } 240 | 241 | Component.onCompleted: { 242 | lineDetectorDataSource.setIntegratedPlotSeries(integratedPlotSeries) 243 | lineDetectorDataSource.setThresholdPlotSeries(thresholdPlotSeries) 244 | } 245 | } 246 | 247 | Text { 248 | id: stateLabel 249 | text: "UNLOCKED" 250 | color: "#f44336" 251 | font.bold: true 252 | font.pointSize: 18 253 | anchors.top: parent.top 254 | anchors.right: parent.right 255 | anchors.topMargin: 32 256 | anchors.rightMargin: 32 257 | 258 | Connections { 259 | target: lineDetector 260 | onStateChanged: { 261 | var lstate = lineDetector.state; 262 | if (lstate === LineDetector.Locked) { 263 | stateLabel.text = "LOCKED" 264 | stateLabel.color = "#4caf50" 265 | } else if (lstate === LineDetector.Hold) { 266 | stateLabel.text = "HOLD" 267 | stateLabel.color = "#cddc39" 268 | } else { 269 | stateLabel.text = "UNLOCKED" 270 | stateLabel.color = "#f44336" 271 | } 272 | } 273 | } 274 | } 275 | 276 | Text { 277 | id: dzLabel 278 | font.bold: true 279 | font.pointSize: 18 280 | anchors.top: stateLabel.top 281 | anchors.right: stateLabel.left 282 | anchors.rightMargin: 8 283 | color: "gray" 284 | text: lineDetector.dz.toFixed(3) 285 | } 286 | 287 | Button { 288 | anchors.right: stateLabel.left 289 | anchors.top: dzLabel.bottom 290 | anchors.topMargin: 0 291 | anchors.rightMargin: 8 292 | text: "0" 293 | height: 34 294 | onClicked: lineDetector.zerodxs() 295 | } 296 | } 297 | 298 | 299 | function osdRescale(item, videoSource, videoOutput) { 300 | if (videoSource.width === 0) 301 | return 302 | var sourceAspectRatio = videoSource.width / videoSource.height 303 | var sourceVisibleWidth = videoOutput.width 304 | var sourceVisibleHeight = sourceVisibleWidth / sourceAspectRatio 305 | if (sourceVisibleHeight > videoOutput.height) { 306 | sourceVisibleHeight = videoOutput.height 307 | sourceVisibleWidth = sourceVisibleHeight * sourceAspectRatio 308 | item.anchors.leftMargin = (videoOutput.width - sourceVisibleWidth) / 2 309 | item.anchors.topMargin = 0 310 | } else { 311 | item.anchors.topMargin = (videoOutput.height - sourceVisibleHeight) / 2 312 | item.anchors.leftMargin = 0 313 | } 314 | 315 | item.width = sourceVisibleWidth 316 | item.height = sourceVisibleHeight 317 | } 318 | 319 | Connections { 320 | target: secondVideoSource 321 | onDimensionsChanged: osdRescale(testRect, secondVideoSource, secondVideoOutput) 322 | } 323 | 324 | // GcodePlayer { 325 | // id: player 326 | // onCurrentLineChanged: commandsListView.positionViewAtIndex(currentLineNumber, ListView.Center) 327 | // } 328 | 329 | FileDialog { 330 | id: playerFileDialog 331 | title: "Choose gcode file" 332 | folder: shortcuts.home 333 | onAccepted: { 334 | player.loadFile(fileUrl) 335 | } 336 | } 337 | 338 | Item { 339 | id: playerItem 340 | anchors.left: parent.left 341 | anchors.bottom: parent.bottom 342 | width: parent.width / 2 343 | height: parent.height / 2 344 | 345 | RowLayout { 346 | id: playerButtonsLayout 347 | anchors.leftMargin: 16 348 | anchors.left: parent.left 349 | anchors.top: parent.top 350 | anchors.right: parent.right 351 | height: 48 352 | spacing: 16 353 | 354 | Button { 355 | text: "Load" 356 | onClicked: playerFileDialog.visible = true 357 | } 358 | 359 | Button { 360 | text: "Play" 361 | onClicked: player.play(); 362 | } 363 | 364 | Button { 365 | text: "Pause" 366 | onClicked: player.pause(); 367 | } 368 | 369 | Button { 370 | text: "Stop" 371 | onClicked: player.stop(); 372 | } 373 | 374 | Text { 375 | id: connectionStatusLabel 376 | font.bold: true 377 | font.pointSize: 14 378 | 379 | function checkState() { 380 | var lstate = player.connectionState; 381 | if (lstate === GcodePlayer.Connected) { 382 | connectionStatusLabel.text = "MC:connected" 383 | connectionStatusLabel.color = "#4caf50" 384 | } else if (lstate === GcodePlayer.Connecting) { 385 | connectionStatusLabel.text = "MC:connecting" 386 | connectionStatusLabel.color = "#cddc39" 387 | } else if (lstate === GcodePlayer.Disconnected) { 388 | connectionStatusLabel.text = "MC:disconnected" 389 | connectionStatusLabel.color = "#f44336" 390 | } 391 | } 392 | 393 | Connections { 394 | id: c1 395 | target: player 396 | onConnectionStateChanged: connectionStatusLabel.checkState() 397 | } 398 | 399 | Component.onCompleted: connectionStatusLabel.checkState() 400 | 401 | MouseArea { 402 | anchors.fill: parent 403 | onClicked: player.connectToMC() 404 | } 405 | } 406 | 407 | Text { 408 | id: playerStatusLabel 409 | text: "STOPPED" 410 | color: "orange" 411 | font.bold: true 412 | font.pointSize: 14 413 | 414 | Connections { 415 | target: player 416 | onStateChanged: { 417 | var lstate = player.state; 418 | if (lstate === GcodePlayer.Playing) { 419 | playerStatusLabel.text = "PLAYING" 420 | playerStatusLabel.color = "#4caf50" 421 | } else if (lstate === GcodePlayer.Paused) { 422 | playerStatusLabel.text = "PAUSED" 423 | playerStatusLabel.color = "#cddc39" 424 | } else if (lstate === GcodePlayer.Stopped) { 425 | playerStatusLabel.text = "STOPPED" 426 | playerStatusLabel.color = "orange" 427 | } else if (lstate === GcodePlayer.Error) { 428 | playerStatusLabel.text = "ERROR" 429 | playerStatusLabel.color = "#f44336" 430 | } 431 | } 432 | } 433 | } 434 | 435 | Text { 436 | font.bold: true 437 | font.pointSize: 14 438 | color: "#ccc" 439 | text: player.currentLineNumber + " / " + player.linesCount + " (" + (player.currentLineNumber / player.linesCount).toFixed(1) + " %)" 440 | } 441 | 442 | Item { 443 | Layout.fillWidth: true 444 | } 445 | } 446 | 447 | ListView { 448 | id: commandsListView 449 | anchors.left: parent.left 450 | anchors.top: playerButtonsLayout.bottom 451 | anchors.right: parent.right 452 | anchors.bottom: parent.bottom 453 | clip: true 454 | 455 | model: player.model 456 | 457 | delegate: Rectangle { 458 | height: 16 459 | width: parent.width 460 | color: model.lineNumber % 2 === 0 ? "#222" : "#333" 461 | RowLayout { 462 | anchors.fill: parent 463 | spacing: 0 464 | 465 | Item { 466 | width: 6 467 | } 468 | 469 | Rectangle { 470 | width: parent.height * 0.6 471 | height: width 472 | //radius: height 473 | color: { 474 | if (model.status === GcodePlayerItem.Pending) { 475 | return "orange" 476 | } else if (model.status === GcodePlayerItem.Ok) { 477 | return "#4caf50" 478 | } else if (model.status === GcodePlayerItem.Warning) { 479 | return "red" 480 | } else if (model.status === GcodePlayerItem.InternalCommand) { 481 | return "blue" 482 | } 483 | 484 | return "gray" 485 | } 486 | } 487 | Item { 488 | width: 64 489 | height: parent.height 490 | //color: "#333" 491 | Text { 492 | anchors.fill: parent 493 | text: String(model.lineNumber).padStart(6, '0') 494 | color: "#666" 495 | horizontalAlignment: Text.AlignHCenter 496 | font.family: "Monaco" 497 | } 498 | } 499 | Item { 500 | height: parent.height 501 | Layout.fillWidth: true 502 | 503 | Text { 504 | anchors.fill: parent 505 | //anchors.leftMargin: 8 506 | text: { 507 | if (model.status === GcodePlayerItem.Warning) { 508 | return model.code.trim() + " (" + model.response + ")" 509 | } else { 510 | return model.code 511 | } 512 | } 513 | 514 | color: "#ccc" 515 | horizontalAlignment: Text.AlignLeft 516 | font.family: "Monaco" 517 | } 518 | } 519 | } 520 | } 521 | 522 | ScrollBar.vertical: ScrollBar { 523 | minimumSize: 0.1 524 | } 525 | } 526 | } 527 | 528 | Item { 529 | anchors.top: parent.top 530 | anchors.left: buttonsLayout.right 531 | anchors.bottom: playerItem.top 532 | anchors.right: mainVideoOutput.left 533 | anchors.margins: 20 534 | 535 | ColumnLayout { 536 | anchors.fill: parent 537 | 538 | RowLayout { 539 | Slider { 540 | property real lastValue: 0.4 541 | id: laserPowerSlider 542 | from: 0.4 543 | to: 5 544 | stepSize: 0.01 545 | onPressedChanged: { 546 | if (value !== lastValue) { 547 | lastValue = value 548 | var normalized = value / 5.0 549 | ray.setLaserPower(normalized) 550 | } 551 | } 552 | } 553 | 554 | Button { 555 | text: "+0.01" 556 | onClicked: { 557 | laserPowerSlider.value += 0.01 558 | laserPowerSlider.onPressedChanged() 559 | } 560 | } 561 | 562 | Button { 563 | text: "-0.01" 564 | onClicked: { 565 | laserPowerSlider.value -= 0.01 566 | laserPowerSlider.onPressedChanged() 567 | } 568 | } 569 | 570 | Text { 571 | color: "#ccc" 572 | font.bold: true 573 | text: laserPowerSlider.value.toFixed(2) 574 | } 575 | } 576 | 577 | RowLayout { 578 | Button { 579 | text: "TE" 580 | onClicked: ray.setTopExhaust(true) 581 | } 582 | Button { 583 | text: "TD" 584 | onClicked: ray.setTopExhaust(false) 585 | } 586 | Button { 587 | text: "BE" 588 | onClicked: ray.setBottomExhaust(true) 589 | } 590 | Button { 591 | text: "BD" 592 | onClicked: ray.setBottomExhaust(false) 593 | } 594 | } 595 | 596 | RowLayout { 597 | Text { 598 | color: "#ccc" 599 | font.bold: true 600 | text: "Ray:" 601 | } 602 | Text { 603 | font.bold: true 604 | text: ray.connected ? "connected" : "disconnected" 605 | color: ray.connected ? "green" : "red" 606 | } 607 | } 608 | 609 | RowLayout { 610 | Text { 611 | color: "#ccc" 612 | font.bold: true 613 | text: "Automator:" 614 | } 615 | 616 | Switch { 617 | id: automatorEnableSwitch 618 | onCheckedChanged: automator.enabled = checked 619 | } 620 | 621 | Text { 622 | id: automatorWorkingLabel 623 | font.bold: true 624 | text: automator.working ? "working" : "stopped" 625 | color: automator.working ? "green" : "red" 626 | } 627 | 628 | Text { 629 | color: "#ccc" 630 | font.bold: true 631 | text: automator.message 632 | } 633 | 634 | // Slider { 635 | // from: -100 636 | // to: 100 637 | // onValueChanged: { 638 | // compValue.text = value 639 | // compTest.text = automator.compensate(value) 640 | // } 641 | // } 642 | 643 | // Text { 644 | // id: compValue 645 | // color: "white" 646 | // } 647 | 648 | // Text { 649 | // id: compTest 650 | // color: "white" 651 | // } 652 | } 653 | 654 | RowLayout { 655 | Text { 656 | color: "#ccc" 657 | font.bold: true 658 | text: "Max PWR:" 659 | } 660 | 661 | Slider { 662 | id: maxPowerSlider 663 | property real lastValue: 1.0 664 | from: 0.4 665 | to: 5 666 | value: 1.0 667 | onPressedChanged: { 668 | if (value !== lastValue) { 669 | lastValue = value 670 | automator.maxPower = value 671 | } 672 | } 673 | } 674 | 675 | Text { 676 | color: "#ccc" 677 | font.bold: true 678 | text: maxPowerSlider.value.toFixed(2) 679 | } 680 | } 681 | 682 | RowLayout { 683 | Text { 684 | color: "#ccc" 685 | font.bold: true 686 | text: "Min PWR:" 687 | } 688 | 689 | Slider { 690 | id: minPowerSlider 691 | property real lastValue: 0.8 692 | from: 0.4 693 | to: 5 694 | value: 0.8 695 | onValueChanged: automator.minPower = value 696 | onPressedChanged: { 697 | if (value !== lastValue) { 698 | lastValue = value 699 | automator.minPower = value 700 | } 701 | } 702 | } 703 | 704 | Text { 705 | color: "#ccc" 706 | font.bold: true 707 | text: minPowerSlider.value.toFixed(2) 708 | } 709 | } 710 | 711 | RowLayout { 712 | Text { 713 | color: "#ccc" 714 | font.bold: true 715 | text: "Auto B:" 716 | } 717 | 718 | Switch { 719 | onCheckedChanged: automator.autosendB = checked 720 | } 721 | 722 | Text { 723 | color: "#ccc" 724 | font.bold: true 725 | text: "Auto PWR:" 726 | } 727 | 728 | Switch { 729 | onCheckedChanged: automator.autosendPower = checked 730 | } 731 | 732 | Text { 733 | color: "#ccc" 734 | font.bold: true 735 | text: "PWR:" 736 | } 737 | 738 | Text { 739 | color: "#ccc" 740 | font.bold: true 741 | text: automator.lastSentPower.toFixed(2) 742 | } 743 | } 744 | 745 | RowLayout { 746 | Layout.fillHeight: true 747 | } 748 | } 749 | } 750 | 751 | ColumnLayout { 752 | id: buttonsLayout 753 | anchors.left: parent.left 754 | anchors.top: parent.top 755 | anchors.bottom: parent.bottom 756 | anchors.margins: 16 757 | spacing: 10 758 | width: 200 759 | 760 | Button { 761 | text: "Capture" 762 | onClicked: { 763 | captureController.start("abcd") 764 | } 765 | } 766 | 767 | Button { 768 | text: "Take pic" 769 | onClicked: cameraCalibrator.takePicture() 770 | } 771 | 772 | Button { 773 | text: "Save pics" 774 | onClicked: cameraCalibrator.savePictures("./pictures") 775 | } 776 | 777 | Button { 778 | text: "Load pics" 779 | onClicked: cameraCalibrator.loadPictures("./pictures") 780 | } 781 | 782 | Button { 783 | text: "Calibrate" 784 | onClicked: cameraCalibrator.calibrate() 785 | } 786 | 787 | Button { 788 | text: "Save calib" 789 | onClicked: cameraCalibrator.saveCalibrationData("undistort.yaml") 790 | } 791 | 792 | Button { 793 | text: "Load calib" 794 | onClicked: cameraCalibrator.loadCalibrationData("undistort.yaml") 795 | } 796 | 797 | Button { 798 | text: "Apply calib" 799 | onClicked: cameraCalibrator.applyCalibrationData() 800 | } 801 | 802 | Item { 803 | Layout.fillHeight: true 804 | } 805 | } 806 | } 807 | -------------------------------------------------------------------------------- /qtquickcontrols2.conf: -------------------------------------------------------------------------------- 1 | [Controls] 2 | Style=Material 3 | 4 | [Material] 5 | Theme=Dark 6 | Variant=Dense 7 | Accent=#9c27b0 8 | Primary=#03a9f4 9 | Foreground=#ccc 10 | Background=Grey 11 | -------------------------------------------------------------------------------- /quick.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | main.qml 4 | qtquickcontrols2.conf 5 | 6 | 7 | -------------------------------------------------------------------------------- /rayreceiver.cpp: -------------------------------------------------------------------------------- 1 | #include "rayreceiver.h" 2 | #include 3 | 4 | RayReceiver::RayReceiver(QObject *parent) : QObject(parent) 5 | { 6 | m_udp = new QUdpSocket(this); 7 | m_udp->bind(QHostAddress::Any, 45454); 8 | connect(m_udp, &QUdpSocket::readyRead, 9 | this, &RayReceiver::onReadyRead); 10 | m_connected = false; 11 | m_timer.setInterval(500); 12 | m_timer.setSingleShot(false); 13 | connect(&m_timer, &QTimer::timeout, 14 | this, &RayReceiver::onTimerTimeout); 15 | } 16 | 17 | bool RayReceiver::connected() const 18 | { 19 | return m_connected; 20 | } 21 | 22 | void RayReceiver::setLaserPower(float pwr) 23 | { 24 | if (pwr < 0) 25 | pwr = 0; 26 | else if (pwr > 1.0) 27 | pwr = 1.0; 28 | QString l = QString("l(%1)").arg((int)(pwr * 4095)); 29 | m_udp->writeDatagram(l.toLocal8Bit(), QHostAddress("192.168.88.99"), 9999); 30 | } 31 | 32 | void RayReceiver::setTopExhaust(bool enabled) 33 | { 34 | QString l = QString("t(%1)").arg(enabled ? '0' : '1'); 35 | qDebug() << l; 36 | m_udp->writeDatagram(l.toLocal8Bit(), QHostAddress("192.168.88.99"), 9999); 37 | } 38 | 39 | void RayReceiver::setBottomExhaust(bool enabled) 40 | { 41 | QString l = QString("b(%1)").arg(enabled ? '0' : '1'); 42 | m_udp->writeDatagram(l.toLocal8Bit(), QHostAddress("192.168.88.99"), 9999); 43 | } 44 | 45 | void RayReceiver::onReadyRead() 46 | { 47 | while (m_udp->hasPendingDatagrams()) { 48 | QNetworkDatagram datagram = m_udp->receiveDatagram(); 49 | processPayload(datagram); 50 | } 51 | } 52 | 53 | void RayReceiver::onTimerTimeout() 54 | { 55 | emit connectionStateChanged(false); 56 | m_connected = false; 57 | } 58 | 59 | void RayReceiver::processPayload(QNetworkDatagram datagram) 60 | { 61 | QByteArray data = datagram.data(); 62 | if (data.size() != sizeof(ray_payload_t)) 63 | return; 64 | quint8 *p = (quint8 *)data.data(); 65 | memcpy((void *)&m_payload, p, sizeof(ray_payload_t)); 66 | //qDebug() << "x:" << m_payload.mcs_x << "\ty:" << m_payload.mcs_y << "\tz:" << m_payload.mcs_z << "\tb:" << m_payload.mcs_b; 67 | //qDebug() << "state: " << m_payload.state << "\tplayed:" << m_payload.played << "\ttotal:" << m_payload.total; 68 | if (m_lastState != (State)m_payload.state) { 69 | m_lastState = (State)m_payload.state; 70 | emit stateChanged(m_lastState); 71 | } 72 | 73 | emit coordsChanged(m_payload.mcs_x, 74 | m_payload.mcs_y, 75 | m_payload.mcs_z, 76 | m_payload.mcs_b); 77 | if (m_connected == false) { 78 | m_connected = true; 79 | emit connectionStateChanged(true); 80 | } 81 | m_timer.start(); 82 | } 83 | -------------------------------------------------------------------------------- /rayreceiver.h: -------------------------------------------------------------------------------- 1 | #ifndef RAYRECEIVER_H 2 | #define RAYRECEIVER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | typedef struct { 10 | float mcs_x; 11 | float mcs_y; 12 | float mcs_z; 13 | float mcs_b; 14 | uint32_t state; 15 | uint32_t played; 16 | int32_t total; 17 | } ray_payload_t; 18 | 19 | class RayReceiver : public QObject 20 | { 21 | Q_OBJECT 22 | Q_PROPERTY(bool connected READ connected NOTIFY connectionStateChanged) 23 | public: 24 | explicit RayReceiver(QObject *parent = nullptr); 25 | 26 | enum State { 27 | NotPlaying = 2, 28 | Paused = 0, 29 | Playing = 3 30 | }; 31 | 32 | bool connected() const; 33 | 34 | signals: 35 | void stateChanged(State s); 36 | void coordsChanged(float x, float y, float z, float b); 37 | void connectionStateChanged(bool connected); 38 | 39 | public slots: 40 | void setLaserPower(float pwr); 41 | void setTopExhaust(bool enabled); 42 | void setBottomExhaust(bool enabled); 43 | 44 | private slots: 45 | void onReadyRead(); 46 | void onTimerTimeout(); 47 | 48 | private: 49 | void processPayload(QNetworkDatagram datagram); 50 | 51 | QUdpSocket *m_udp; 52 | ray_payload_t m_payload; 53 | QTimer m_timer; 54 | bool m_connected; 55 | State m_lastState; 56 | }; 57 | 58 | #endif // RAYRECEIVER_H 59 | --------------------------------------------------------------------------------