ENH: support parts skipping function

Jira: STUDIO-12687
Change-Id: I244cb611954590bd5e741f0d2701f359426a33a2
(cherry picked from commit e7e90e0f8ca0106a51d18d83efa0de56b332ddc0)
This commit is contained in:
hemai
2025-06-20 17:59:30 +08:00
committed by Noisyfox
parent 10428f71c0
commit df5bf0ae29
26 changed files with 3262 additions and 9 deletions

3
.gitignore vendored
View File

@@ -39,3 +39,6 @@ src/OrcaSlicer-doc/
resources/profiles/user/default
*.code-workspace
deps_src/build/
test.js
/.cache/
.clangd

View File

@@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.99838 1.66675C9.17481 1.66675 8.46192 2.14526 8.12338 2.83862C7.89551 2.75399 7.65137 2.70841 7.39421 2.70841C6.24512 2.70841 5.31088 3.64266 5.31088 4.79175V10.1791L5.22299 10.0912C4.40918 9.27742 3.09083 9.27742 2.27702 10.0912C1.46322 10.905 1.46322 12.2234 2.27702 13.0372L5.13184 15.892C6.69434 17.4545 8.81348 18.3334 11.0238 18.3334H11.3005H11.5609C11.6097 18.3334 11.6585 18.3302 11.7074 18.3204C14.6924 18.1186 17.0785 15.7358 17.277 12.7507C17.2868 12.7019 17.29 12.6531 17.29 12.6042V6.87508C17.29 5.72599 16.3558 4.79175 15.2067 4.79175C15.0277 4.79175 14.8519 4.81453 14.6859 4.85685V4.79175C14.6859 3.64266 13.7516 2.70841 12.6025 2.70841C12.3454 2.70841 12.1012 2.75399 11.8734 2.83862C11.5348 2.14526 10.8219 1.66675 9.99838 1.66675ZM9.47754 4.795V4.79175V3.75008C9.47754 3.46362 9.71192 3.22925 9.99838 3.22925C10.2848 3.22925 10.5192 3.46362 10.5192 3.75008V4.78849V4.79175V9.21883C10.5192 9.65177 10.8675 10.0001 11.3005 10.0001C11.7334 10.0001 12.0817 9.65177 12.0817 9.21883V4.79175C12.0817 4.79175 12.0817 4.79175 12.0817 4.78849C12.0817 4.50203 12.3161 4.26766 12.6025 4.26766C12.889 4.26766 13.1234 4.50203 13.1234 4.78849V6.60815V6.61141V9.21558C13.1234 9.64852 13.4717 9.99683 13.9046 9.99683C14.3376 9.99683 14.6859 9.64852 14.6859 9.21558V6.87834V6.87508C14.6859 6.58862 14.9203 6.35425 15.2067 6.35425C15.4932 6.35425 15.7275 6.58862 15.7275 6.87508V12.5033C15.7243 12.5229 15.7243 12.5457 15.721 12.5652C15.6104 14.8341 13.7907 16.6537 11.5218 16.7644C11.5023 16.7644 11.4795 16.7677 11.46 16.7709H11.3005H11.0238C9.22689 16.7709 7.50489 16.058 6.23536 14.7885L3.38054 11.9304C3.17872 11.7286 3.17872 11.3966 3.38054 11.1947C3.58236 10.9929 3.91439 10.9929 4.11622 11.1947L5.53874 12.6173C5.76335 12.8419 6.09864 12.907 6.39161 12.7865C6.68458 12.6661 6.87338 12.3796 6.87338 12.0639V4.79175C6.87338 4.50529 7.10775 4.27091 7.39421 4.27091C7.68067 4.27091 7.91504 4.50203 7.91504 4.78849V9.21883C7.91504 9.65177 8.26335 10.0001 8.69629 10.0001C9.12924 10.0001 9.47754 9.65177 9.47754 9.21883V4.795Z" fill="#5C5C5C"/>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.99839 1.66675C9.17483 1.66675 8.46193 2.14526 8.12339 2.83862C7.89553 2.75399 7.65139 2.70841 7.39423 2.70841C6.24514 2.70841 5.31089 3.64266 5.31089 4.79175V10.1791L5.223 10.0912C4.4092 9.27742 3.09084 9.27742 2.27704 10.0912C1.46324 10.905 1.46324 12.2234 2.27704 13.0372L5.13186 15.892C6.69436 17.4545 8.8135 18.3334 11.0238 18.3334H11.3005H11.5609C11.6097 18.3334 11.6586 18.3302 11.7074 18.3204C14.6924 18.1186 17.0785 15.7358 17.277 12.7507C17.2868 12.7019 17.2901 12.6531 17.2901 12.6042V6.87508C17.2901 5.72599 16.3558 4.79175 15.2067 4.79175C15.0277 4.79175 14.8519 4.81453 14.6859 4.85685V4.79175C14.6859 3.64266 13.7516 2.70841 12.6026 2.70841C12.3454 2.70841 12.1013 2.75399 11.8734 2.83862C11.5349 2.14526 10.822 1.66675 9.99839 1.66675ZM9.47756 4.795V4.79175V3.75008C9.47756 3.46362 9.71194 3.22925 9.99839 3.22925C10.2849 3.22925 10.5192 3.46362 10.5192 3.75008V4.78849V4.79175V9.21883C10.5192 9.65177 10.8675 10.0001 11.3005 10.0001C11.7334 10.0001 12.0817 9.65177 12.0817 9.21883V4.79175C12.0817 4.79175 12.0817 4.79175 12.0817 4.78849C12.0817 4.50203 12.3161 4.26766 12.6026 4.26766C12.889 4.26766 13.1234 4.50203 13.1234 4.78849V6.60815V6.61141V9.21558C13.1234 9.64852 13.4717 9.99683 13.9046 9.99683C14.3376 9.99683 14.6859 9.64852 14.6859 9.21558V6.87834V6.87508C14.6859 6.58862 14.9203 6.35425 15.2067 6.35425C15.4932 6.35425 15.7276 6.58862 15.7276 6.87508V12.5033C15.7243 12.5229 15.7243 12.5457 15.7211 12.5652C15.6104 14.8341 13.7907 16.6537 11.5218 16.7644C11.5023 16.7644 11.4795 16.7677 11.46 16.7709H11.3005H11.0238C9.22691 16.7709 7.5049 16.058 6.23537 14.7885L3.38055 11.9304C3.17873 11.7286 3.17873 11.3966 3.38055 11.1947C3.58238 10.9929 3.91441 10.9929 4.11623 11.1947L5.53876 12.6173C5.76337 12.8419 6.09865 12.907 6.39162 12.7865C6.68459 12.6661 6.87339 12.3796 6.87339 12.0639V4.79175C6.87339 4.50529 7.10777 4.27091 7.39423 4.27091C7.68068 4.27091 7.91506 4.50203 7.91506 4.78849V9.21883C7.91506 9.65177 8.26337 10.0001 8.69631 10.0001C9.12925 10.0001 9.47756 9.65177 9.47756 9.21883V4.795Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.20618 1.91333C13.2338 1.91338 16.4991 5.17868 16.4991 9.2063C16.4991 10.9499 15.8852 12.5492 14.8644 13.804L17.8673 16.8059C18.1602 17.0988 18.1602 17.5745 17.8673 17.8674C17.5744 18.1603 17.0987 18.1603 16.8058 17.8674L13.8038 14.8645C12.5491 15.8853 10.9498 16.4992 9.20618 16.4993C5.17855 16.4993 1.91326 13.2339 1.91321 9.2063C1.91321 5.17865 5.17852 1.91333 9.20618 1.91333ZM9.20618 3.41333C6.00695 3.41333 3.41321 6.00707 3.41321 9.2063C3.41326 12.4055 6.00698 14.9993 9.20618 14.9993C12.4053 14.9992 14.9991 12.4055 14.9991 9.2063C14.9991 6.0071 12.4054 3.41338 9.20618 3.41333ZM9.20618 5.29517C9.62017 5.29531 9.95602 5.63118 9.95618 6.04517V8.4563H12.3663C12.7805 8.4563 13.1163 8.79209 13.1163 9.2063C13.1163 9.62047 12.7805 9.9563 12.3663 9.9563H9.95618V12.3674C9.95592 12.7813 9.62011 13.1173 9.20618 13.1174C8.79212 13.1174 8.45644 12.7814 8.45618 12.3674V9.9563H6.04504C5.63089 9.95627 5.29509 9.62045 5.29504 9.2063C5.29504 8.7921 5.63086 8.45633 6.04504 8.4563H8.45618V6.04517C8.45634 5.63109 8.79206 5.29517 9.20618 5.29517Z" fill="#5C5C5C"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.20618 1.91333C13.2338 1.91338 16.4991 5.17868 16.4991 9.2063C16.4991 10.9499 15.8852 12.5492 14.8644 13.804L17.8673 16.8059C18.1602 17.0988 18.1602 17.5745 17.8673 17.8674C17.5744 18.1603 17.0987 18.1603 16.8058 17.8674L13.8038 14.8645C12.5491 15.8853 10.9498 16.4992 9.20618 16.4993C5.17855 16.4993 1.91326 13.2339 1.91321 9.2063C1.91321 5.17865 5.17852 1.91333 9.20618 1.91333ZM9.20618 3.41333C6.00695 3.41333 3.41321 6.00707 3.41321 9.2063C3.41326 12.4055 6.00698 14.9993 9.20618 14.9993C12.4053 14.9992 14.9991 12.4055 14.9991 9.2063C14.9991 6.0071 12.4054 3.41338 9.20618 3.41333ZM9.20618 5.29517C9.62017 5.29531 9.95602 5.63118 9.95618 6.04517V8.4563H12.3663C12.7805 8.4563 13.1163 8.79209 13.1163 9.2063C13.1163 9.62047 12.7805 9.9563 12.3663 9.9563H9.95618V12.3674C9.95592 12.7813 9.62011 13.1173 9.20618 13.1174C8.79212 13.1174 8.45644 12.7814 8.45618 12.3674V9.9563H6.04504C5.63089 9.95627 5.29509 9.62045 5.29504 9.2063C5.29504 8.7921 5.63086 8.45633 6.04504 8.4563H8.45618V6.04517C8.45634 5.63109 8.79206 5.29517 9.20618 5.29517Z" fill="#CECECE"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.20618 1.91333C13.2338 1.91338 16.4991 5.17868 16.4991 9.2063C16.4991 10.9499 15.8852 12.5492 14.8644 13.804L17.8673 16.8059C18.1602 17.0988 18.1602 17.5745 17.8673 17.8674C17.5744 18.1603 17.0987 18.1603 16.8058 17.8674L13.8038 14.8645C12.5491 15.8853 10.9498 16.4992 9.20618 16.4993C5.17855 16.4993 1.91326 13.2339 1.91321 9.2063C1.91321 5.17865 5.17852 1.91333 9.20618 1.91333ZM9.20618 3.41333C6.00695 3.41333 3.41321 6.00707 3.41321 9.2063C3.41326 12.4055 6.00698 14.9993 9.20618 14.9993C12.4053 14.9992 14.9991 12.4055 14.9991 9.2063C14.9991 6.0071 12.4054 3.41338 9.20618 3.41333ZM12.3663 8.4563C12.7805 8.4563 13.1163 8.79209 13.1163 9.2063C13.1163 9.62047 12.7805 9.9563 12.3663 9.9563H6.04504C5.63089 9.95627 5.29509 9.62045 5.29504 9.2063C5.29504 8.7921 5.63086 8.45633 6.04504 8.4563H12.3663Z" fill="#5C5C5C"/>
</svg>

After

Width:  |  Height:  |  Size: 936 B

View File

@@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.20618 1.91333C13.2338 1.91338 16.4991 5.17868 16.4991 9.2063C16.4991 10.9499 15.8852 12.5492 14.8644 13.804L17.8673 16.8059C18.1602 17.0988 18.1602 17.5745 17.8673 17.8674C17.5744 18.1603 17.0987 18.1603 16.8058 17.8674L13.8038 14.8645C12.5491 15.8853 10.9498 16.4992 9.20618 16.4993C5.17855 16.4993 1.91326 13.2339 1.91321 9.2063C1.91321 5.17865 5.17852 1.91333 9.20618 1.91333ZM9.20618 3.41333C6.00695 3.41333 3.41321 6.00707 3.41321 9.2063C3.41326 12.4055 6.00698 14.9993 9.20618 14.9993C12.4053 14.9992 14.9991 12.4055 14.9991 9.2063C14.9991 6.0071 12.4054 3.41338 9.20618 3.41333ZM12.3663 8.4563C12.7805 8.4563 13.1163 8.79209 13.1163 9.2063C13.1163 9.62047 12.7805 9.9563 12.3663 9.9563H6.04504C5.63089 9.95627 5.29509 9.62045 5.29504 9.2063C5.29504 8.7921 5.63086 8.45633 6.04504 8.4563H12.3663Z" fill="#CECECE"/>
</svg>

After

Width:  |  Height:  |  Size: 936 B

View File

@@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.25 16H4.75V18.5H7.25V16ZM4.75 15C4.48478 15 4.23043 15.1054 4.04289 15.2929C3.85536 15.4804 3.75 15.7348 3.75 16V18.5C3.75 18.7652 3.85536 19.0196 4.04289 19.2071C4.23043 19.3946 4.48478 19.5 4.75 19.5H7.25C7.51522 19.5 7.76957 19.3946 7.95711 19.2071C8.14464 19.0196 8.25 18.7652 8.25 18.5V16C8.25 15.7348 8.14464 15.4804 7.95711 15.2929C7.76957 15.1054 7.51522 15 7.25 15H4.75ZM13.25 16H10.75V18.5H13.25V16ZM10.75 15C10.4848 15 10.2304 15.1054 10.0429 15.2929C9.85536 15.4804 9.75 15.7348 9.75 16V18.5C9.75 18.7652 9.85536 19.0196 10.0429 19.2071C10.2304 19.3946 10.4848 19.5 10.75 19.5H13.25C13.5152 19.5 13.7696 19.3946 13.9571 19.2071C14.1446 19.0196 14.25 18.7652 14.25 18.5V16C14.25 15.7348 14.1446 15.4804 13.9571 15.2929C13.7696 15.1054 13.5152 15 13.25 15H10.75ZM19.25 16H16.75V18.5H19.25V16ZM16.75 15C16.4848 15 16.2304 15.1054 16.0429 15.2929C15.8554 15.4804 15.75 15.7348 15.75 16V18.5C15.75 18.7652 15.8554 19.0196 16.0429 19.2071C16.2304 19.3946 16.4848 19.5 16.75 19.5H19.25C19.5152 19.5 19.7696 19.3946 19.9571 19.2071C20.1446 19.0196 20.25 18.7652 20.25 18.5V16C20.25 15.7348 20.1446 15.4804 19.9571 15.2929C19.7696 15.1054 19.5152 15 19.25 15H16.75ZM6.367 8.244C5.181 9.537 4.559 11.069 4.499 12.047C4.48654 12.2458 4.39562 12.4315 4.24624 12.5632C4.09687 12.695 3.90128 12.762 3.7025 12.7495C3.50372 12.737 3.31803 12.6461 3.18629 12.4967C3.05455 12.3474 2.98754 12.1518 3 11.953C3.087 10.563 3.894 8.719 5.26 7.23C6.646 5.72 8.673 4.5 11.25 4.5C13.823 4.5 15.886 5.717 17.29 7.226C18.671 8.71 19.5 10.572 19.5 12H18C18 11.06 17.4 9.546 16.192 8.248C15.007 6.974 13.32 6 11.25 6C9.184 6 7.535 6.97 6.367 8.244Z" fill="#262E30"/>
<path d="M20.25 12H14.25L20.25 6V12Z" fill="#262E30"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.25001 16H4.75001V18.5H7.25001V16ZM4.75001 15C4.48479 15 4.23044 15.1054 4.0429 15.2929C3.85537 15.4804 3.75001 15.7348 3.75001 16V18.5C3.75001 18.7652 3.85537 19.0196 4.0429 19.2071C4.23044 19.3946 4.48479 19.5 4.75001 19.5H7.25001C7.51522 19.5 7.76958 19.3946 7.95712 19.2071C8.14465 19.0196 8.25001 18.7652 8.25001 18.5V16C8.25001 15.7348 8.14465 15.4804 7.95712 15.2929C7.76958 15.1054 7.51522 15 7.25001 15H4.75001ZM13.25 16H10.75V18.5H13.25V16ZM10.75 15C10.4848 15 10.2304 15.1054 10.0429 15.2929C9.85537 15.4804 9.75001 15.7348 9.75001 16V18.5C9.75001 18.7652 9.85537 19.0196 10.0429 19.2071C10.2304 19.3946 10.4848 19.5 10.75 19.5H13.25C13.5152 19.5 13.7696 19.3946 13.9571 19.2071C14.1447 19.0196 14.25 18.7652 14.25 18.5V16C14.25 15.7348 14.1447 15.4804 13.9571 15.2929C13.7696 15.1054 13.5152 15 13.25 15H10.75ZM19.25 16H16.75V18.5H19.25V16ZM16.75 15C16.4848 15 16.2304 15.1054 16.0429 15.2929C15.8554 15.4804 15.75 15.7348 15.75 16V18.5C15.75 18.7652 15.8554 19.0196 16.0429 19.2071C16.2304 19.3946 16.4848 19.5 16.75 19.5H19.25C19.5152 19.5 19.7696 19.3946 19.9571 19.2071C20.1447 19.0196 20.25 18.7652 20.25 18.5V16C20.25 15.7348 20.1447 15.4804 19.9571 15.2929C19.7696 15.1054 19.5152 15 19.25 15H16.75ZM6.36701 8.244C5.18101 9.537 4.55901 11.069 4.49901 12.047C4.48654 12.2458 4.39562 12.4315 4.24625 12.5632C4.09688 12.695 3.90129 12.762 3.70251 12.7495C3.50373 12.737 3.31804 12.6461 3.1863 12.4967C3.05455 12.3474 2.98754 12.1518 3.00001 11.953C3.08701 10.563 3.89401 8.719 5.26001 7.23C6.64601 5.72 8.67301 4.5 11.25 4.5C13.823 4.5 15.886 5.717 17.29 7.226C18.671 8.71 19.5 10.572 19.5 12H18C18 11.06 17.4 9.546 16.192 8.248C15.007 6.974 13.32 6 11.25 6C9.18401 6 7.53501 6.97 6.36701 8.244Z" fill="#ACACAC"/>
<path d="M20.25 12H14.25L20.25 6V12Z" fill="#ACACAC"/>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

27
src/earcut/CHANGELOG.md Normal file
View File

@@ -0,0 +1,27 @@
## Earcut.hpp changelog
### master
- Fixed a bunch of rare edge cases that led to bad triangulation (parity with Earcut v2.2.2)
- Removed use of deprecated `std::allocator::construct`
- Fixed a minor z-order hashing bug
- Improved visualization app, better docs
### v0.12.4
- Fixed a crash in Crash in Earcut::findHoleBridge
- Added coverage checks
- Added macOS, MinGW builds
### v0.12.3
- Fixed -Wunused-lambda-capture
### v0.12.2
- Fixed potential division by zero
- Fixed -fsanitize=integer warning
### v0.12.1
- Fixed cast precision warning

151
src/earcut/CMakeLists.txt Normal file
View File

@@ -0,0 +1,151 @@
cmake_minimum_required(VERSION 3.2)
project(earcut_hpp LANGUAGES CXX C)
option(EARCUT_BUILD_TESTS "Build the earcut test program" ON)
option(EARCUT_BUILD_BENCH "Build the earcut benchmark program" ON)
option(EARCUT_BUILD_VIZ "Build the earcut visualizer program" ON)
option(EARCUT_WARNING_IS_ERROR "Treat warnings as errors" OFF)
if (NOT CMAKE_BUILD_TYPE AND NOT GENERATOR_IS_MULTI_CONFIG)
message(STATUS "No build type specified. Setting to 'Release'")
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "The type of build." FORCE)
endif()
include(GNUInstallDirs)
add_library(earcut_hpp INTERFACE)
add_library(earcut_hpp::earcut_hpp ALIAS earcut_hpp)
target_include_directories(earcut_hpp INTERFACE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
)
set(CMAKE_CXX_STANDARD 11)
if("${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}" GREATER 3.7)
# Allow C++11 requirements to propagate when using recent CMake versions
target_compile_features(earcut_hpp INTERFACE cxx_std_11)
endif()
file(GLOB FIXTURE_SOURCE_FILES test/fixtures/*.cpp test/fixtures/*.hpp)
source_group(fixtures FILES ${FIXTURE_SOURCE_FILES})
add_library(fixtures OBJECT ${FIXTURE_SOURCE_FILES})
target_compile_options(fixtures PRIVATE $<$<CXX_COMPILER_ID:MSVC>:/Od>)
# In CMake 3.12, use target_link_libraries(fixtures PUBLIC earcut_hpp libtess2).
# Since we support down to CMake 3.2, we need to manually propagate usage requirements of earcut_hpp
target_include_directories(fixtures PRIVATE "$<TARGET_PROPERTY:earcut_hpp,INTERFACE_INCLUDE_DIRECTORIES>")
target_compile_features(fixtures PRIVATE "$<TARGET_PROPERTY:earcut_hpp,INTERFACE_COMPILE_FEATURES>")
file(GLOB COMPARISON_SOURCE_FILES test/comparison/*.cpp test/comparison/*.hpp)
source_group(comparison FILES ${COMPARISON_SOURCE_FILES})
# this is interface since there is no cpp files in the comparison directory
add_library(comparison INTERFACE)
file(GLOB LIBTESS2_SOURCE_FILES test/comparison/libtess2/*.c test/comparison/libtess2/*.h)
source_group(comparison/libtess2 FILES ${LIBTESS2_SOURCE_FILES})
add_library(libtess2 ${LIBTESS2_SOURCE_FILES})
target_compile_options(libtess2 PRIVATE
$<$<CXX_COMPILER_ID:MSVC>:/wd4244 /wd4267>
$<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-w>
)
add_library(common INTERFACE)
target_link_libraries(common INTERFACE libtess2 comparison)
# optional: -march=native (builds with the optimizations available on the build machine (only for local use!))
target_compile_options(common INTERFACE
$<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-pipe -Wall -Wextra -Wconversion -Wpedantic>
)
if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang$" OR CMAKE_COMPILER_IS_GNUCXX)
if ("${CMAKE_CXX_FLAGS}" MATCHES "--coverage")
# We disable debug code for the coverage so it won't see assertion and other things only enabled for debugging
target_compile_definitions(common INTERFACE NDEBUG)
else()
# Here we enable the undefined behavior sanitizer for the tests, benchmarks and the viz
include(CheckCXXCompilerFlag)
check_cxx_compiler_flag("-fsanitize=undefined" HAVE_FLAG_SANITIZE_UNDEFINED)
if(HAVE_FLAG_SANITIZE_UNDEFINED)
target_compile_options(common INTERFACE $<$<CONFIG:Debug>:-fsanitize=undefined>)
# TODO: Replace with target link option once we support CMake 3.13
target_link_libraries(common INTERFACE $<$<CONFIG:Debug>:-fsanitize=undefined>)
endif()
endif()
endif()
if (EARCUT_WARNING_IS_ERROR)
target_compile_options(common INTERFACE
$<$<CXX_COMPILER_ID:MSVC>:/WX>
$<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-Werror>
)
endif()
if (EARCUT_BUILD_TESTS)
enable_testing()
add_executable(tests test/tap.cpp test/tap.hpp test/test.cpp $<TARGET_OBJECTS:fixtures>)
target_link_libraries(tests PRIVATE earcut_hpp common)
add_test(NAME earcut_tests COMMAND tests)
endif()
if (EARCUT_BUILD_BENCH)
add_executable(bench test/bench.cpp $<TARGET_OBJECTS:fixtures>)
target_link_libraries(bench PRIVATE earcut_hpp common)
endif()
if (EARCUT_BUILD_VIZ)
add_executable(viz test/viz.cpp $<TARGET_OBJECTS:fixtures>)
# Setup viz target
# OpenGL
# linux: xorg-dev libgl1-mesa-glx libgl1-mesa-dev
# windows: in the windows sdk
find_package(OpenGL REQUIRED)
# GLFW3
find_package(glfw3 QUIET) # try to use the system default
if (NOT glfw3_FOUND)
if(EXISTS "${PROJECT_SOURCE_DIR}/.gitmodules")
find_package(Git REQUIRED)
execute_process(
COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
OUTPUT_QUIET
ERROR_QUIET
)
endif()
set(GLFW_BUILD_EXAMPLES OFF CACHE BOOL "Build the GLFW example programs" FORCE)
set(GLFW_BUILD_TESTS OFF CACHE BOOL "Build the GLFW test programs" FORCE)
set(GLFW_BUILD_DOCS OFF CACHE BOOL "Build the GLFW documentation" FORCE)
set(GLFW_INSTALL OFF CACHE BOOL "Generate installation target" FORCE)
add_subdirectory(glfw)
endif()
target_compile_definitions(viz PRIVATE GL_SILENCE_DEPRECATION)
# TODO: Using old variables for OpenGL package since they were added in CMake 3.8
target_link_libraries(viz PRIVATE earcut_hpp common glfw ${OPENGL_LIBRARIES})
target_include_directories(viz PRIVATE ${OPENGL_INCLUDE_DIR})
endif()
install(
DIRECTORY include/mapbox
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} FILES_MATCHING PATTERN "*.hpp"
)
install(TARGETS earcut_hpp EXPORT earcut_hpp-config)
# Since there is two projects, we need to export into the parent directory
export(
TARGETS earcut_hpp
NAMESPACE earcut_hpp::
FILE "${PROJECT_BINARY_DIR}/earcut_hpp-config.cmake"
)
install(EXPORT earcut_hpp-config
DESTINATION "${CMAKE_INSTALL_DATADIR}/cmake/earcut_hpp"
NAMESPACE earcut_hpp::
)

15
src/earcut/LICENSE Normal file
View File

@@ -0,0 +1,15 @@
ISC License
Copyright (c) 2015, Mapbox
Permission to use, copy, modify, and/or distribute this software for any purpose
with or without fee is hereby granted, provided that the above copyright notice
and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.

131
src/earcut/README.md Normal file
View File

@@ -0,0 +1,131 @@
## Earcut
A C++ port of [earcut.js](https://github.com/mapbox/earcut), a fast, [header-only](https://github.com/mapbox/earcut.hpp/blob/master/include/mapbox/earcut.hpp) polygon triangulation library.
[![Travis](https://img.shields.io/travis/com/mapbox/earcut.hpp.svg)](https://travis-ci.com/github/mapbox/earcut.hpp)
[![AppVeyor](https://ci.appveyor.com/api/projects/status/a1ysrqd69mqn7coo/branch/master?svg=true)](https://ci.appveyor.com/project/Mapbox/earcut-hpp-8wm4o/branch/master)
[![Coverage](https://img.shields.io/coveralls/github/mapbox/earcut.hpp.svg)](https://coveralls.io/github/mapbox/earcut.hpp)
[![Coverity Scan](https://img.shields.io/coverity/scan/14000.svg)](https://scan.coverity.com/projects/14000)
[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/mapbox/earcut.hpp.svg)](http://isitmaintained.com/project/mapbox/earcut.hpp "Average time to resolve an issue")
[![Percentage of issues still open](http://isitmaintained.com/badge/open/mapbox/earcut.hpp.svg)](http://isitmaintained.com/project/mapbox/earcut.hpp "Percentage of issues still open")
[![Mourner](https://img.shields.io/badge/simply-awesome-brightgreen.svg)](https://github.com/mourner/projects)
The library implements a modified ear slicing algorithm, optimized by [z-order curve](http://en.wikipedia.org/wiki/Z-order_curve) hashing and extended to handle holes, twisted polygons, degeneracies and self-intersections in a way that doesn't _guarantee_ correctness of triangulation, but attempts to always produce acceptable results for practical data like geographical shapes.
It's based on ideas from [FIST: Fast Industrial-Strength Triangulation of Polygons](http://www.cosy.sbg.ac.at/~held/projects/triang/triang.html) by Martin Held and [Triangulation by Ear Clipping](http://www.geometrictools.com/Documentation/TriangulationByEarClipping.pdf) by David Eberly.
## Usage
```cpp
#include <earcut.hpp>
```
```cpp
// The number type to use for tessellation
using Coord = double;
// The index type. Defaults to uint32_t, but you can also pass uint16_t if you know that your
// data won't have more than 65536 vertices.
using N = uint32_t;
// Create array
using Point = std::array<Coord, 2>;
std::vector<std::vector<Point>> polygon;
// Fill polygon structure with actual data. Any winding order works.
// The first polyline defines the main polygon.
polygon.push_back({{100, 0}, {100, 100}, {0, 100}, {0, 0}});
// Following polylines define holes.
polygon.push_back({{75, 25}, {75, 75}, {25, 75}, {25, 25}});
// Run tessellation
// Returns array of indices that refer to the vertices of the input polygon.
// e.g: the index 6 would refer to {25, 75} in this example.
// Three subsequent indices form a triangle. Output triangles are clockwise.
std::vector<N> indices = mapbox::earcut<N>(polygon);
```
Earcut can triangulate a simple, planar polygon of any winding order including holes. It will even return a robust, acceptable solution for non-simple poygons. Earcut works on a 2D plane. If you have three or more dimensions, you can project them onto a 2D surface before triangulation, or use a more suitable library for the task (e.g [CGAL](https://doc.cgal.org/latest/Triangulation_3/index.html)).
It is also possible to use your custom point type as input. There are default accessors defined for `std::tuple`, `std::pair`, and `std::array`. For a custom type (like Clipper's `IntPoint` type), do this:
```cpp
// struct IntPoint {
// int64_t X, Y;
// };
namespace mapbox {
namespace util {
template <>
struct nth<0, IntPoint> {
inline static auto get(const IntPoint &t) {
return t.X;
};
};
template <>
struct nth<1, IntPoint> {
inline static auto get(const IntPoint &t) {
return t.Y;
};
};
} // namespace util
} // namespace mapbox
```
You can also use a custom container type for your polygon. Similar to std::vector<T>, it has to meet the requirements of [Container](https://en.cppreference.com/w/cpp/named_req/Container), in particular `size()`, `empty()` and `operator[]`.
<p align="center">
<img src="https://camo.githubusercontent.com/01836f8ba21af844c93d8d3145f4e9976025a696/68747470733a2f2f692e696d6775722e636f6d2f67314e704c54712e706e67" alt="example triangulation"/>
</p>
## Additional build instructions
In case you just want to use the earcut triangulation library; copy and include the header file [`<earcut.hpp>`](https://github.com/mapbox/earcut.hpp/blob/master/include/mapbox/earcut.hpp) in your project and follow the steps documented in the section [Usage](#usage).
If you want to build the test, benchmark and visualization programs instead, follow these instructions:
### Dependencies
Before you continue, make sure to have the following tools and libraries installed:
* git ([Ubuntu](https://help.ubuntu.com/lts/serverguide/git.html)/[Windows/macOS](http://git-scm.com/downloads))
* cmake 3.2+ ([Ubuntu](https://launchpad.net/~george-edison55/+archive/ubuntu/cmake-3.x)/[Windows/macOS](https://cmake.org/download/))
* OpenGL SDK ([Ubuntu](http://packages.ubuntu.com/de/trusty/libgl1-mesa-dev)/[Windows](https://dev.windows.com/en-us/downloads/windows-10-sdk)/[macOS](https://developer.apple.com/opengl/))
* Compiler such as [GCC 4.9+, Clang 3.4+](https://launchpad.net/~ubuntu-toolchain-r/+archive/ubuntu/test), [MSVC12+](https://www.visualstudio.com/)
Note: On some operating systems such as Windows, manual steps are required to add cmake and [git](http://blog.countableset.ch/2012/06/07/adding-git-to-windows-7-path/) to your PATH environment variable.
### Manual compilation
```bash
git clone --recursive https://github.com/mapbox/earcut.hpp.git
cd earcut.hpp
mkdir build
cd build
cmake ..
make
# ./tests
# ./bench
# ./viz
```
### [Visual Studio](https://www.visualstudio.com/), [Eclipse](https://eclipse.org/), [XCode](https://developer.apple.com/xcode/), ...
```batch
git clone --recursive https://github.com/mapbox/earcut.hpp.git
cd earcut.hpp
mkdir project
cd project
cmake .. -G "Visual Studio 14 2015"
::you can also generate projects for "Visual Studio 12 2013", "XCode", "Eclipse CDT4 - Unix Makefiles"
```
After completion, open the generated project with your IDE.
### [CLion](https://www.jetbrains.com/clion/), [Visual Studio 2017+](https://www.visualstudio.com/)
Import the project from https://github.com/mapbox/earcut.hpp.git and you should be good to go!
## Status
This is currently based on [earcut 2.2.4](https://github.com/mapbox/earcut#224-jul-5-2022).

814
src/earcut/earcut.hpp Normal file
View File

@@ -0,0 +1,814 @@
#pragma once
#include <algorithm>
#include <cassert>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <memory>
#include <utility>
#include <vector>
namespace mapbox {
namespace util {
template <std::size_t I, typename T> struct nth {
inline static typename std::tuple_element<I, T>::type
get(const T& t) { return std::get<I>(t); };
};
}
namespace detail {
template <typename N = uint32_t>
class Earcut {
public:
std::vector<N> indices;
std::size_t vertices = 0;
template <typename Polygon>
void operator()(const Polygon& points);
private:
struct Node {
Node(N index, double x_, double y_) : i(index), x(x_), y(y_) {}
Node(const Node&) = delete;
Node& operator=(const Node&) = delete;
Node(Node&&) = delete;
Node& operator=(Node&&) = delete;
const N i;
const double x;
const double y;
// previous and next vertice nodes in a polygon ring
Node* prev = nullptr;
Node* next = nullptr;
// z-order curve value
int32_t z = 0;
// previous and next nodes in z-order
Node* prevZ = nullptr;
Node* nextZ = nullptr;
// indicates whether this is a steiner point
bool steiner = false;
};
template <typename Ring> Node* linkedList(const Ring& points, const bool clockwise);
Node* filterPoints(Node* start, Node* end = nullptr);
void earcutLinked(Node* ear, int pass = 0);
bool isEar(Node* ear);
bool isEarHashed(Node* ear);
Node* cureLocalIntersections(Node* start);
void splitEarcut(Node* start);
template <typename Polygon> Node* eliminateHoles(const Polygon& points, Node* outerNode);
Node* eliminateHole(Node* hole, Node* outerNode);
Node* findHoleBridge(Node* hole, Node* outerNode);
bool sectorContainsSector(const Node* m, const Node* p);
void indexCurve(Node* start);
Node* sortLinked(Node* list);
int32_t zOrder(const double x_, const double y_);
Node* getLeftmost(Node* start);
bool pointInTriangle(double ax, double ay, double bx, double by, double cx, double cy, double px, double py) const;
bool isValidDiagonal(Node* a, Node* b);
double area(const Node* p, const Node* q, const Node* r) const;
bool equals(const Node* p1, const Node* p2);
bool intersects(const Node* p1, const Node* q1, const Node* p2, const Node* q2);
bool onSegment(const Node* p, const Node* q, const Node* r);
int sign(double val);
bool intersectsPolygon(const Node* a, const Node* b);
bool locallyInside(const Node* a, const Node* b);
bool middleInside(const Node* a, const Node* b);
Node* splitPolygon(Node* a, Node* b);
template <typename Point> Node* insertNode(std::size_t i, const Point& p, Node* last);
void removeNode(Node* p);
bool hashing;
double minX, maxX;
double minY, maxY;
double inv_size = 0;
template <typename T, typename Alloc = std::allocator<T>>
class ObjectPool {
public:
ObjectPool() { }
ObjectPool(std::size_t blockSize_) {
reset(blockSize_);
}
~ObjectPool() {
clear();
}
template <typename... Args>
T* construct(Args&&... args) {
if (currentIndex >= blockSize) {
currentBlock = alloc_traits::allocate(alloc, blockSize);
allocations.emplace_back(currentBlock);
currentIndex = 0;
}
T* object = &currentBlock[currentIndex++];
alloc_traits::construct(alloc, object, std::forward<Args>(args)...);
return object;
}
void reset(std::size_t newBlockSize) {
for (auto allocation : allocations) {
alloc_traits::deallocate(alloc, allocation, blockSize);
}
allocations.clear();
blockSize = std::max<std::size_t>(1, newBlockSize);
currentBlock = nullptr;
currentIndex = blockSize;
}
void clear() { reset(blockSize); }
private:
T* currentBlock = nullptr;
std::size_t currentIndex = 1;
std::size_t blockSize = 1;
std::vector<T*> allocations;
Alloc alloc;
typedef typename std::allocator_traits<Alloc> alloc_traits;
};
ObjectPool<Node> nodes;
};
template <typename N> template <typename Polygon>
void Earcut<N>::operator()(const Polygon& points) {
// reset
indices.clear();
vertices = 0;
if (points.empty()) return;
double x;
double y;
int threshold = 80;
std::size_t len = 0;
for (size_t i = 0; threshold >= 0 && i < points.size(); i++) {
threshold -= static_cast<int>(points[i].size());
len += points[i].size();
}
//estimate size of nodes and indices
nodes.reset(len * 3 / 2);
indices.reserve(len + points[0].size());
Node* outerNode = linkedList(points[0], true);
if (!outerNode || outerNode->prev == outerNode->next) return;
if (points.size() > 1) outerNode = eliminateHoles(points, outerNode);
// if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox
hashing = threshold < 0;
if (hashing) {
Node* p = outerNode->next;
minX = maxX = outerNode->x;
minY = maxY = outerNode->y;
do {
x = p->x;
y = p->y;
minX = std::min<double>(minX, x);
minY = std::min<double>(minY, y);
maxX = std::max<double>(maxX, x);
maxY = std::max<double>(maxY, y);
p = p->next;
} while (p != outerNode);
// minX, minY and inv_size are later used to transform coords into integers for z-order calculation
inv_size = std::max<double>(maxX - minX, maxY - minY);
inv_size = inv_size != .0 ? (32767. / inv_size) : .0;
}
earcutLinked(outerNode);
nodes.clear();
}
// create a circular doubly linked list from polygon points in the specified winding order
template <typename N> template <typename Ring>
typename Earcut<N>::Node*
Earcut<N>::linkedList(const Ring& points, const bool clockwise) {
using Point = typename Ring::value_type;
double sum = 0;
const std::size_t len = points.size();
std::size_t i, j;
Node* last = nullptr;
// calculate original winding order of a polygon ring
for (i = 0, j = len > 0 ? len - 1 : 0; i < len; j = i++) {
const auto& p1 = points[i];
const auto& p2 = points[j];
const double p20 = util::nth<0, Point>::get(p2);
const double p10 = util::nth<0, Point>::get(p1);
const double p11 = util::nth<1, Point>::get(p1);
const double p21 = util::nth<1, Point>::get(p2);
sum += (p20 - p10) * (p11 + p21);
}
// link points into circular doubly-linked list in the specified winding order
if (clockwise == (sum > 0)) {
for (i = 0; i < len; i++) last = insertNode(vertices + i, points[i], last);
} else {
for (i = len; i-- > 0;) last = insertNode(vertices + i, points[i], last);
}
if (last && equals(last, last->next)) {
removeNode(last);
last = last->next;
}
vertices += len;
return last;
}
// eliminate colinear or duplicate points
template <typename N>
typename Earcut<N>::Node*
Earcut<N>::filterPoints(Node* start, Node* end) {
if (!end) end = start;
Node* p = start;
bool again;
do {
again = false;
if (!p->steiner && (equals(p, p->next) || area(p->prev, p, p->next) == 0)) {
removeNode(p);
p = end = p->prev;
if (p == p->next) break;
again = true;
} else {
p = p->next;
}
} while (again || p != end);
return end;
}
// main ear slicing loop which triangulates a polygon (given as a linked list)
template <typename N>
void Earcut<N>::earcutLinked(Node* ear, int pass) {
if (!ear) return;
// interlink polygon nodes in z-order
if (!pass && hashing) indexCurve(ear);
Node* stop = ear;
Node* prev;
Node* next;
// iterate through ears, slicing them one by one
while (ear->prev != ear->next) {
prev = ear->prev;
next = ear->next;
if (hashing ? isEarHashed(ear) : isEar(ear)) {
// cut off the triangle
indices.emplace_back(prev->i);
indices.emplace_back(ear->i);
indices.emplace_back(next->i);
removeNode(ear);
// skipping the next vertice leads to less sliver triangles
ear = next->next;
stop = next->next;
continue;
}
ear = next;
// if we looped through the whole remaining polygon and can't find any more ears
if (ear == stop) {
// try filtering points and slicing again
if (!pass) earcutLinked(filterPoints(ear), 1);
// if this didn't work, try curing all small self-intersections locally
else if (pass == 1) {
ear = cureLocalIntersections(filterPoints(ear));
earcutLinked(ear, 2);
// as a last resort, try splitting the remaining polygon into two
} else if (pass == 2) splitEarcut(ear);
break;
}
}
}
// check whether a polygon node forms a valid ear with adjacent nodes
template <typename N>
bool Earcut<N>::isEar(Node* ear) {
const Node* a = ear->prev;
const Node* b = ear;
const Node* c = ear->next;
if (area(a, b, c) >= 0) return false; // reflex, can't be an ear
// now make sure we don't have other points inside the potential ear
Node* p = ear->next->next;
while (p != ear->prev) {
if (pointInTriangle(a->x, a->y, b->x, b->y, c->x, c->y, p->x, p->y) &&
area(p->prev, p, p->next) >= 0) return false;
p = p->next;
}
return true;
}
template <typename N>
bool Earcut<N>::isEarHashed(Node* ear) {
const Node* a = ear->prev;
const Node* b = ear;
const Node* c = ear->next;
if (area(a, b, c) >= 0) return false; // reflex, can't be an ear
// triangle bbox; min & max are calculated like this for speed
const double minTX = std::min<double>(a->x, std::min<double>(b->x, c->x));
const double minTY = std::min<double>(a->y, std::min<double>(b->y, c->y));
const double maxTX = std::max<double>(a->x, std::max<double>(b->x, c->x));
const double maxTY = std::max<double>(a->y, std::max<double>(b->y, c->y));
// z-order range for the current triangle bbox;
const int32_t minZ = zOrder(minTX, minTY);
const int32_t maxZ = zOrder(maxTX, maxTY);
// first look for points inside the triangle in increasing z-order
Node* p = ear->nextZ;
while (p && p->z <= maxZ) {
if (p != ear->prev && p != ear->next &&
pointInTriangle(a->x, a->y, b->x, b->y, c->x, c->y, p->x, p->y) &&
area(p->prev, p, p->next) >= 0) return false;
p = p->nextZ;
}
// then look for points in decreasing z-order
p = ear->prevZ;
while (p && p->z >= minZ) {
if (p != ear->prev && p != ear->next &&
pointInTriangle(a->x, a->y, b->x, b->y, c->x, c->y, p->x, p->y) &&
area(p->prev, p, p->next) >= 0) return false;
p = p->prevZ;
}
return true;
}
// go through all polygon nodes and cure small local self-intersections
template <typename N>
typename Earcut<N>::Node*
Earcut<N>::cureLocalIntersections(Node* start) {
Node* p = start;
do {
Node* a = p->prev;
Node* b = p->next->next;
// a self-intersection where edge (v[i-1],v[i]) intersects (v[i+1],v[i+2])
if (!equals(a, b) && intersects(a, p, p->next, b) && locallyInside(a, b) && locallyInside(b, a)) {
indices.emplace_back(a->i);
indices.emplace_back(p->i);
indices.emplace_back(b->i);
// remove two nodes involved
removeNode(p);
removeNode(p->next);
p = start = b;
}
p = p->next;
} while (p != start);
return filterPoints(p);
}
// try splitting polygon into two and triangulate them independently
template <typename N>
void Earcut<N>::splitEarcut(Node* start) {
// look for a valid diagonal that divides the polygon into two
Node* a = start;
do {
Node* b = a->next->next;
while (b != a->prev) {
if (a->i != b->i && isValidDiagonal(a, b)) {
// split the polygon in two by the diagonal
Node* c = splitPolygon(a, b);
// filter colinear points around the cuts
a = filterPoints(a, a->next);
c = filterPoints(c, c->next);
// run earcut on each half
earcutLinked(a);
earcutLinked(c);
return;
}
b = b->next;
}
a = a->next;
} while (a != start);
}
// link every hole into the outer loop, producing a single-ring polygon without holes
template <typename N> template <typename Polygon>
typename Earcut<N>::Node*
Earcut<N>::eliminateHoles(const Polygon& points, Node* outerNode) {
const size_t len = points.size();
std::vector<Node*> queue;
for (size_t i = 1; i < len; i++) {
Node* list = linkedList(points[i], false);
if (list) {
if (list == list->next) list->steiner = true;
queue.push_back(getLeftmost(list));
}
}
std::sort(queue.begin(), queue.end(), [](const Node* a, const Node* b) {
return a->x < b->x;
});
// process holes from left to right
for (size_t i = 0; i < queue.size(); i++) {
outerNode = eliminateHole(queue[i], outerNode);
}
return outerNode;
}
// find a bridge between vertices that connects hole with an outer ring and and link it
template <typename N>
typename Earcut<N>::Node*
Earcut<N>::eliminateHole(Node* hole, Node* outerNode) {
Node* bridge = findHoleBridge(hole, outerNode);
if (!bridge) {
return outerNode;
}
Node* bridgeReverse = splitPolygon(bridge, hole);
// filter collinear points around the cuts
filterPoints(bridgeReverse, bridgeReverse->next);
// Check if input node was removed by the filtering
return filterPoints(bridge, bridge->next);
}
// David Eberly's algorithm for finding a bridge between hole and outer polygon
template <typename N>
typename Earcut<N>::Node*
Earcut<N>::findHoleBridge(Node* hole, Node* outerNode) {
Node* p = outerNode;
double hx = hole->x;
double hy = hole->y;
double qx = -std::numeric_limits<double>::infinity();
Node* m = nullptr;
// find a segment intersected by a ray from the hole's leftmost Vertex to the left;
// segment's endpoint with lesser x will be potential connection Vertex
do {
if (hy <= p->y && hy >= p->next->y && p->next->y != p->y) {
double x = p->x + (hy - p->y) * (p->next->x - p->x) / (p->next->y - p->y);
if (x <= hx && x > qx) {
qx = x;
m = p->x < p->next->x ? p : p->next;
if (x == hx) return m; // hole touches outer segment; pick leftmost endpoint
}
}
p = p->next;
} while (p != outerNode);
if (!m) return 0;
// look for points inside the triangle of hole Vertex, segment intersection and endpoint;
// if there are no points found, we have a valid connection;
// otherwise choose the Vertex of the minimum angle with the ray as connection Vertex
const Node* stop = m;
double tanMin = std::numeric_limits<double>::infinity();
double tanCur = 0;
p = m;
double mx = m->x;
double my = m->y;
do {
if (hx >= p->x && p->x >= mx && hx != p->x &&
pointInTriangle(hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p->x, p->y)) {
tanCur = std::abs(hy - p->y) / (hx - p->x); // tangential
if (locallyInside(p, hole) &&
(tanCur < tanMin || (tanCur == tanMin && (p->x > m->x || sectorContainsSector(m, p))))) {
m = p;
tanMin = tanCur;
}
}
p = p->next;
} while (p != stop);
return m;
}
// whether sector in vertex m contains sector in vertex p in the same coordinates
template <typename N>
bool Earcut<N>::sectorContainsSector(const Node* m, const Node* p) {
return area(m->prev, m, p->prev) < 0 && area(p->next, m, m->next) < 0;
}
// interlink polygon nodes in z-order
template <typename N>
void Earcut<N>::indexCurve(Node* start) {
assert(start);
Node* p = start;
do {
p->z = p->z ? p->z : zOrder(p->x, p->y);
p->prevZ = p->prev;
p->nextZ = p->next;
p = p->next;
} while (p != start);
p->prevZ->nextZ = nullptr;
p->prevZ = nullptr;
sortLinked(p);
}
// Simon Tatham's linked list merge sort algorithm
// http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html
template <typename N>
typename Earcut<N>::Node*
Earcut<N>::sortLinked(Node* list) {
assert(list);
Node* p;
Node* q;
Node* e;
Node* tail;
int i, numMerges, pSize, qSize;
int inSize = 1;
for (;;) {
p = list;
list = nullptr;
tail = nullptr;
numMerges = 0;
while (p) {
numMerges++;
q = p;
pSize = 0;
for (i = 0; i < inSize; i++) {
pSize++;
q = q->nextZ;
if (!q) break;
}
qSize = inSize;
while (pSize > 0 || (qSize > 0 && q)) {
if (pSize == 0) {
e = q;
q = q->nextZ;
qSize--;
} else if (qSize == 0 || !q) {
e = p;
p = p->nextZ;
pSize--;
} else if (p->z <= q->z) {
e = p;
p = p->nextZ;
pSize--;
} else {
e = q;
q = q->nextZ;
qSize--;
}
if (tail) tail->nextZ = e;
else list = e;
e->prevZ = tail;
tail = e;
}
p = q;
}
tail->nextZ = nullptr;
if (numMerges <= 1) return list;
inSize *= 2;
}
}
// z-order of a Vertex given coords and size of the data bounding box
template <typename N>
int32_t Earcut<N>::zOrder(const double x_, const double y_) {
// coords are transformed into non-negative 15-bit integer range
int32_t x = static_cast<int32_t>((x_ - minX) * inv_size);
int32_t y = static_cast<int32_t>((y_ - minY) * inv_size);
x = (x | (x << 8)) & 0x00FF00FF;
x = (x | (x << 4)) & 0x0F0F0F0F;
x = (x | (x << 2)) & 0x33333333;
x = (x | (x << 1)) & 0x55555555;
y = (y | (y << 8)) & 0x00FF00FF;
y = (y | (y << 4)) & 0x0F0F0F0F;
y = (y | (y << 2)) & 0x33333333;
y = (y | (y << 1)) & 0x55555555;
return x | (y << 1);
}
// find the leftmost node of a polygon ring
template <typename N>
typename Earcut<N>::Node*
Earcut<N>::getLeftmost(Node* start) {
Node* p = start;
Node* leftmost = start;
do {
if (p->x < leftmost->x || (p->x == leftmost->x && p->y < leftmost->y))
leftmost = p;
p = p->next;
} while (p != start);
return leftmost;
}
// check if a point lies within a convex triangle
template <typename N>
bool Earcut<N>::pointInTriangle(double ax, double ay, double bx, double by, double cx, double cy, double px, double py) const {
return (cx - px) * (ay - py) >= (ax - px) * (cy - py) &&
(ax - px) * (by - py) >= (bx - px) * (ay - py) &&
(bx - px) * (cy - py) >= (cx - px) * (by - py);
}
// check if a diagonal between two polygon nodes is valid (lies in polygon interior)
template <typename N>
bool Earcut<N>::isValidDiagonal(Node* a, Node* b) {
return a->next->i != b->i && a->prev->i != b->i && !intersectsPolygon(a, b) && // dones't intersect other edges
((locallyInside(a, b) && locallyInside(b, a) && middleInside(a, b) && // locally visible
(area(a->prev, a, b->prev) != 0.0 || area(a, b->prev, b) != 0.0)) || // does not create opposite-facing sectors
(equals(a, b) && area(a->prev, a, a->next) > 0 && area(b->prev, b, b->next) > 0)); // special zero-length case
}
// signed area of a triangle
template <typename N>
double Earcut<N>::area(const Node* p, const Node* q, const Node* r) const {
return (q->y - p->y) * (r->x - q->x) - (q->x - p->x) * (r->y - q->y);
}
// check if two points are equal
template <typename N>
bool Earcut<N>::equals(const Node* p1, const Node* p2) {
return p1->x == p2->x && p1->y == p2->y;
}
// check if two segments intersect
template <typename N>
bool Earcut<N>::intersects(const Node* p1, const Node* q1, const Node* p2, const Node* q2) {
int o1 = sign(area(p1, q1, p2));
int o2 = sign(area(p1, q1, q2));
int o3 = sign(area(p2, q2, p1));
int o4 = sign(area(p2, q2, q1));
if (o1 != o2 && o3 != o4) return true; // general case
if (o1 == 0 && onSegment(p1, p2, q1)) return true; // p1, q1 and p2 are collinear and p2 lies on p1q1
if (o2 == 0 && onSegment(p1, q2, q1)) return true; // p1, q1 and q2 are collinear and q2 lies on p1q1
if (o3 == 0 && onSegment(p2, p1, q2)) return true; // p2, q2 and p1 are collinear and p1 lies on p2q2
if (o4 == 0 && onSegment(p2, q1, q2)) return true; // p2, q2 and q1 are collinear and q1 lies on p2q2
return false;
}
// for collinear points p, q, r, check if point q lies on segment pr
template <typename N>
bool Earcut<N>::onSegment(const Node* p, const Node* q, const Node* r) {
return q->x <= std::max<double>(p->x, r->x) &&
q->x >= std::min<double>(p->x, r->x) &&
q->y <= std::max<double>(p->y, r->y) &&
q->y >= std::min<double>(p->y, r->y);
}
template <typename N>
int Earcut<N>::sign(double val) {
return (0.0 < val) - (val < 0.0);
}
// check if a polygon diagonal intersects any polygon segments
template <typename N>
bool Earcut<N>::intersectsPolygon(const Node* a, const Node* b) {
const Node* p = a;
do {
if (p->i != a->i && p->next->i != a->i && p->i != b->i && p->next->i != b->i &&
intersects(p, p->next, a, b)) return true;
p = p->next;
} while (p != a);
return false;
}
// check if a polygon diagonal is locally inside the polygon
template <typename N>
bool Earcut<N>::locallyInside(const Node* a, const Node* b) {
return area(a->prev, a, a->next) < 0 ?
area(a, b, a->next) >= 0 && area(a, a->prev, b) >= 0 :
area(a, b, a->prev) < 0 || area(a, a->next, b) < 0;
}
// check if the middle Vertex of a polygon diagonal is inside the polygon
template <typename N>
bool Earcut<N>::middleInside(const Node* a, const Node* b) {
const Node* p = a;
bool inside = false;
double px = (a->x + b->x) / 2;
double py = (a->y + b->y) / 2;
do {
if (((p->y > py) != (p->next->y > py)) && p->next->y != p->y &&
(px < (p->next->x - p->x) * (py - p->y) / (p->next->y - p->y) + p->x))
inside = !inside;
p = p->next;
} while (p != a);
return inside;
}
// link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits
// polygon into two; if one belongs to the outer ring and another to a hole, it merges it into a
// single ring
template <typename N>
typename Earcut<N>::Node*
Earcut<N>::splitPolygon(Node* a, Node* b) {
Node* a2 = nodes.construct(a->i, a->x, a->y);
Node* b2 = nodes.construct(b->i, b->x, b->y);
Node* an = a->next;
Node* bp = b->prev;
a->next = b;
b->prev = a;
a2->next = an;
an->prev = a2;
b2->next = a2;
a2->prev = b2;
bp->next = b2;
b2->prev = bp;
return b2;
}
// create a node and util::optionally link it with previous one (in a circular doubly linked list)
template <typename N> template <typename Point>
typename Earcut<N>::Node*
Earcut<N>::insertNode(std::size_t i, const Point& pt, Node* last) {
Node* p = nodes.construct(static_cast<N>(i), util::nth<0, Point>::get(pt), util::nth<1, Point>::get(pt));
if (!last) {
p->prev = p;
p->next = p;
} else {
assert(last);
p->next = last->next;
p->prev = last;
last->next->prev = p;
last->next = p;
}
return p;
}
template <typename N>
void Earcut<N>::removeNode(Node* p) {
p->next->prev = p->prev;
p->prev->next = p->next;
if (p->prevZ) p->prevZ->nextZ = p->nextZ;
if (p->nextZ) p->nextZ->prevZ = p->prevZ;
}
}
template <typename N = uint32_t, typename Polygon>
std::vector<N> earcut(const Polygon& poly) {
mapbox::detail::Earcut<N> earcut;
earcut(poly);
return std::move(earcut.indices);
}
}

View File

@@ -373,6 +373,11 @@ set(SLIC3R_GUI_SOURCES
GUI/SavePresetDialog.hpp
GUI/SceneRaycaster.cpp
GUI/SceneRaycaster.hpp
GUI/PartSkipCommon.hpp
GUI/PartSkipDialog.cpp
GUI/PartSkipDialog.hpp
GUI/SkipPartCanvas.cpp
GUI/SkipPartCanvas.hpp
GUI/Search.cpp
GUI/Search.hpp
GUI/Selection.cpp

View File

@@ -1860,6 +1860,17 @@ int MachineObject::command_control_fan_val(FanType fan_type, int val)
}
int MachineObject::command_task_partskip(std::vector<int> part_ids)
{
BOOST_LOG_TRIVIAL(trace) << "command_task_partskip: ";
json j;
j["print"]["command"] = "skip_objects";
j["print"]["obj_list"] = part_ids;
j["print"]["sequence_id"] = std::to_string(MachineObject::m_sequence_id++);
return this->publish_json(j.dump(), 1);
}
int MachineObject::command_task_abort()
{
BOOST_LOG_TRIVIAL(trace) << "command_task_abort: ";
@@ -2730,7 +2741,7 @@ void MachineObject::reset()
vt_tray.reset();
subtask_ = nullptr;
m_partskip_ids.clear();
}
void MachineObject::set_print_state(std::string status)
@@ -2966,8 +2977,20 @@ int MachineObject::parse_json(std::string payload, bool key_field_only)
print_json.load_compatible_settings(printer_type, "");
print_json.diff2all_base_reset(j_pre);
}
if (j_pre["print"].contains("s_obj")){
if(j_pre["print"]["s_obj"].is_array()){
m_partskip_ids.clear();
for(auto it=j_pre["print"]["s_obj"].begin(); it!=j_pre["print"]["s_obj"].end(); it++){
m_partskip_ids.push_back(it.value().get<int>());
}
}
}
}
}
if (j_pre["print"].contains("plate_idx") && m_plate_index == -1){
m_plate_index = j_pre["print"]["plate_idx"].get<int>();
}
}
if (j_pre.contains("system")) {
if (j_pre["system"].contains("command")) {

View File

@@ -833,6 +833,9 @@ public:
int xcam_filament_tangle_detect_count = 0;
int ams_print_option_count = 0;
// part skip
std::vector<int> m_partskip_ids;
//supported features
bool is_support_chamber_edit{false};
bool is_support_extrusion_cali{false};
@@ -955,6 +958,7 @@ public:
int command_control_fan_val(FanType fan_type, int val);
int command_task_abort();
/* cancelled the job_id */
int command_task_partskip(std::vector<int> part_ids);
int command_task_cancel(std::string job_id);
int command_task_pause();
int command_task_resume();

View File

@@ -0,0 +1,18 @@
#ifndef PARTSKIPCOMMON_H
#define PARTSKIPCOMMON_H
namespace Slic3r { namespace GUI {
enum PartState {
psUnCheck,
psChecked,
psSkipped
};
typedef std::vector<std::pair<int, PartState>> PartsInfo;
}}
#endif // PARTSKIPCOMMON_H

View File

@@ -0,0 +1,863 @@
#include "GUI_Utils.hpp"
#include "GUI_App.hpp"
#include <wx/panel.h>
#include <wx/bitmap.h>
#include <wx/image.h>
#include <wx/sizer.h>
#include <wx/gbsizer.h>
#include <wx/webrequest.h>
#include <wx/control.h>
#include <wx/dcclient.h>
#include <wx/display.h>
#include <wx/mstream.h>
#include <wx/sstream.h>
#include <wx/zstream.h>
#include <wx/window.h>
#include <wx/dcgraph.h>
#include <wx/glcanvas.h>
#include <wx/utils.h>
#include <boost/format.hpp>
#include "Widgets/CheckBox.hpp"
#include "Widgets/Label.hpp"
#include "MsgDialog.hpp"
#include "Printer/PrinterFileSystem.h"
#include "PartSkipDialog.hpp"
#include "SkipPartCanvas.hpp"
#include "MediaPlayCtrl.h"
namespace Slic3r { namespace GUI {
extern wxString hide_passwd(wxString url, std::vector<wxString> const &passwords);
extern void refresh_agora_url(char const *device, char const *dev_ver, char const *channel, void *context, void (*callback)(void *context, char const *url));
PartSkipDialog::PartSkipDialog(wxWindow* parent): DPIDialog(parent, wxID_ANY, _L("Skip Objects"), wxDefaultPosition, wxDefaultSize, wxCAPTION | wxCLOSE_BOX)
{
std::time_t t = std::time(0);
std::stringstream buf;
buf << put_time(std::localtime(&t), "%a_%b_%d_%H_%M_%S/");
m_timestamp = buf.str();
std::string icon_path = (boost::format("%1%/images/BambuStudioTitle.ico") % Slic3r::resources_dir()).str();
SetIcon(wxIcon(Slic3r::encode_path(icon_path.c_str()), wxBITMAP_TYPE_ICO));
SetBackgroundColour(*wxWHITE);
SetSizeHints( wxDefaultSize, wxDefaultSize );
m_sizer = new wxBoxSizer( wxVERTICAL );
auto m_line_top = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(-1, 1), wxTAB_TRAVERSAL);
m_line_top->SetBackgroundColour(wxColour(0xA6, 0xa9, 0xAA));
m_sizer->Add(m_line_top, 0, wxEXPAND, FromDIP(5));
m_sizer->Add(0, 0, 0, wxTOP, FromDIP(5));
m_simplebook = new wxSimplebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0);
m_book_first_panel = new wxPanel( m_simplebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
m_book_third_panel = new wxPanel(m_simplebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
m_book_third_panel->SetBackgroundColour(*wxWHITE);
m_dlg_sizer = new wxBoxSizer( wxVERTICAL );
m_dlg_content_sizer = new wxBoxSizer( wxHORIZONTAL );
m_canvas_sizer = new wxBoxSizer( wxVERTICAL );
// page 3
wxGLAttributes canvasAttrs;
canvasAttrs.PlatformDefaults().Defaults().Stencil(8).EndList();
m_canvas = new SkipPartCanvas(m_book_third_panel, canvasAttrs);
m_canvas->SetPosition(wxPoint(FromDIP(10), FromDIP(10)));
m_canvas->SetMinSize( wxSize( FromDIP(400),FromDIP(400) ) );
m_canvas_sizer->Add( m_canvas, 0, wxALL, FromDIP(10) );
m_canvas_btn_sizer = new wxBoxSizer( wxHORIZONTAL );
m_canvas_btn_sizer->SetMinSize( wxSize( FromDIP(214),FromDIP(28) ) );
StateColor zoom_bg(std::pair<wxColour, int>(wxColour(255, 255, 255), StateColor::Disabled), std::pair<wxColour, int>(wxColour(206, 206, 206), StateColor::Pressed),
std::pair<wxColour, int>(wxColour(238, 238, 238), StateColor::Hovered), std::pair<wxColour, int>(wxColour(255, 255, 255), StateColor::Enabled),
std::pair<wxColour, int>(wxColour(255, 255, 255), StateColor::Normal));
zoom_bg.setTakeFocusedAsHovered(false);
StateColor zoom_bd(std::pair<wxColour, int>(wxColour(144, 144, 144), StateColor::Disabled), std::pair<wxColour, int>(wxColour(38, 46, 48), StateColor::Enabled));
StateColor zoom_text(std::pair<wxColour, int>(wxColour(144, 144, 144), StateColor::Disabled), std::pair<wxColour, int>(wxColour(38, 46, 48), StateColor::Enabled));
m_zoom_out_btn = new Button(m_book_third_panel, _L(""));
m_zoom_out_btn->SetIcon("canvas_zoom_out");
m_zoom_out_btn->SetToolTip(_L("Zoom Out"));
m_zoom_out_btn->SetBackgroundColor(zoom_bg);
m_zoom_out_btn->SetBorderColor(wxColour(238, 238, 238));
m_zoom_out_btn->SetCornerRadius(0);
m_zoom_out_btn->SetMinSize(wxSize(FromDIP(54), FromDIP(26)));
m_canvas_btn_sizer->Add(m_zoom_out_btn, 0, wxEXPAND, 0);
StateColor percent_bg(std::pair<wxColour, int>(wxColour(255, 255, 255), StateColor::Disabled), std::pair<wxColour, int>(wxColour(255, 255, 255), StateColor::Pressed),
std::pair<wxColour, int>(wxColour(255, 255, 255), StateColor::Hovered), std::pair<wxColour, int>(wxColour(255, 255, 255), StateColor::Enabled),
std::pair<wxColour, int>(wxColour(255, 255, 255), StateColor::Normal));
m_percent_label = new Button(m_book_third_panel, _L("100 %"));
m_percent_label->SetBackgroundColor(percent_bg);
m_percent_label->SetBorderColor(wxColour(238, 238, 238));
m_percent_label->SetMinSize(wxSize(FromDIP(54), FromDIP(26)));
m_percent_label->SetCornerRadius(0);
m_canvas_btn_sizer->Add( m_percent_label, 0, wxEXPAND, 0 );
m_zoom_in_btn = new Button(m_book_third_panel, _L(""));
m_zoom_in_btn->SetIcon("canvas_zoom_in");
m_zoom_in_btn->SetToolTip(_L("Zoom In"));
m_zoom_in_btn->SetBackgroundColor(zoom_bg);
m_zoom_in_btn->SetBorderColor(wxColour(238, 238, 238));
m_zoom_in_btn->SetMinSize(wxSize(FromDIP(54), FromDIP(26)));
m_zoom_in_btn->SetCornerRadius(0);
m_canvas_btn_sizer->Add(m_zoom_in_btn, 0, wxEXPAND, 0);
m_switch_drag_btn = new Button(m_book_third_panel, _L(""));
m_switch_drag_btn->SetIcon("canvas_drag");
m_switch_drag_btn->SetToolTip(_L("Drag"));
m_switch_drag_btn->SetBackgroundColor(*wxWHITE);
m_switch_drag_btn->SetBorderColor(wxColour(238, 238, 238));
m_switch_drag_btn->SetMinSize(wxSize(FromDIP(54),FromDIP(26)));
m_switch_drag_btn->SetCornerRadius(0);
m_canvas_btn_sizer->Add( m_switch_drag_btn, 0, wxEXPAND, FromDIP(5) );
m_canvas_sizer->Add(m_canvas_btn_sizer, 0, wxALIGN_CENTER_HORIZONTAL, FromDIP(10));
m_dlg_content_sizer->Add( m_canvas_sizer, 0, wxALL, FromDIP(10) );
m_list_sizer = new wxBoxSizer( wxVERTICAL );
m_list_sizer->SetMinSize( wxSize( FromDIP(267),FromDIP(422) ) );
auto all_checkbox_sizer = new wxBoxSizer(wxHORIZONTAL);
m_all_checkbox = new CheckBox(m_book_third_panel, wxID_ANY);
m_all_checkbox->SetValue(false);
m_all_checkbox->SetBackgroundColour(*wxWHITE);
m_all_label = new Label(m_book_third_panel, _L("Select All"));
m_all_label->Wrap(-1);
m_all_label->SetBackgroundColour(*wxWHITE);
m_all_label->SetMinSize(wxSize(267, -1));
m_all_label->SetMaxSize(wxSize(267, -1));
all_checkbox_sizer->Add(m_all_checkbox, 0, wxALL | wxALIGN_CENTER_VERTICAL, 0);
all_checkbox_sizer->Add(m_all_label, 0, wxALL | wxALIGN_CENTER_VERTICAL, 0);
m_list_sizer->Add(all_checkbox_sizer, 0, wxALL, FromDIP(5));
m_line = new wxPanel(m_book_third_panel, wxID_ANY, wxDefaultPosition, wxSize(FromDIP(267), FromDIP(1)), wxTAB_TRAVERSAL);
m_line->SetBackgroundColour( wxColor(238, 238, 238) );
m_list_sizer->Add(m_line, 0, wxEXPAND, 0);
m_list_view = new wxScrolledWindow( m_book_third_panel, wxID_ANY, wxDefaultPosition, wxSize( 267,-1 ), wxHSCROLL|wxVSCROLL );
m_list_view->SetScrollRate( 5, 5 );
m_list_view->SetMinSize( wxSize( FromDIP(267),FromDIP(410) ) );
m_list_view->SetMaxSize(wxSize(FromDIP(267), FromDIP(410)));
m_list_view->SetBackgroundColour(*wxWHITE);
m_scroll_sizer = new wxBoxSizer(wxVERTICAL);
m_list_view->SetSizer(m_scroll_sizer);
m_list_view->Layout();
m_list_sizer->Add(m_list_view, 0, wxEXPAND, 0);
m_dlg_content_sizer->Add( m_list_sizer, 0, wxALL, FromDIP(15) );
m_dlg_sizer->Add( m_dlg_content_sizer, 0, wxEXPAND, FromDIP(5) );
m_dlg_btn_sizer = new wxBoxSizer( wxHORIZONTAL );
m_dlg_btn_sizer->SetMinSize(wxSize(FromDIP(720), FromDIP(54)));
m_cnt_label = new Label(m_book_third_panel, _L("cnt 1"));
m_cnt_label->Wrap( -1 );
m_cnt_label->SetBackgroundColour(*wxWHITE);
m_cnt_label->SetForegroundColour(wxColour(0, 174, 66));
m_cnt_label->SetFont(Label::Head_15);
m_dlg_btn_sizer->Add(0, 0, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, FromDIP(5));
m_dlg_btn_sizer->Add( m_cnt_label, 0, wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(0) );
m_tot_label = new Label(m_book_third_panel, _L("tot 15"));
m_tot_label->Wrap( -1 );
m_tot_label->SetBackgroundColour(*wxWHITE);
m_dlg_btn_sizer->Add(m_tot_label, 0, wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(5));
m_dlg_btn_sizer->Add(0, 0, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL, FromDIP(5));
StateColor btn_bg_white(std::pair<wxColour, int>(wxColour(206, 206, 206), StateColor::Pressed), std::pair<wxColour, int>(wxColour(238, 238, 238), StateColor::Hovered),
std::pair<wxColour, int>(*wxWHITE, StateColor::Normal));
StateColor btn_bg_green(std::pair<wxColour, int>(wxColour(27, 136, 68), StateColor::Pressed), std::pair<wxColour, int>(wxColour(61, 203, 115), StateColor::Hovered),
std::pair<wxColour, int>(wxColour(0, 177, 66), StateColor::Normal));
m_apply_btn = new Button(m_book_third_panel, wxString(_L("Skip")));
m_apply_btn->SetBackgroundColor(btn_bg_green);
m_apply_btn->SetTextColor(*wxWHITE);
// m_apply_btn->SetBorderColor(wxColour(38, 46, 48));
m_apply_btn->SetFont(Label::Body_14);
m_apply_btn->SetSize(wxSize(-1, FromDIP(32)));
m_apply_btn->SetMinSize(wxSize(FromDIP(80), FromDIP(32)));
m_apply_btn->SetCornerRadius(FromDIP(16));
m_dlg_btn_sizer->Add(m_apply_btn, 0, wxALL | wxALIGN_CENTER_VERTICAL, FromDIP(15));
m_dlg_sizer->Add( m_dlg_btn_sizer, 0, wxEXPAND, FromDIP(5) );
m_book_third_panel->SetSizer( m_dlg_sizer );
m_book_third_panel->Layout();
m_dlg_sizer->Fit( m_book_third_panel );
// page 2
m_book_second_panel = new wxPanel( m_simplebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
m_book_second_panel->SetBackgroundColour(*wxWHITE);
m_book_second_sizer = new wxBoxSizer( wxVERTICAL );
m_book_second_btn_sizer = new wxBoxSizer( wxHORIZONTAL );
m_retry_label = new Label( m_book_second_panel, _L("Load Skipping Objects Information Failed. \nPlease try again."));
m_retry_label->Wrap( -1 );
m_retry_label->SetBackgroundColour(*wxWHITE);
m_book_second_sizer->Add(0, 0, 1, wxEXPAND, 0);
m_book_second_sizer->Add(m_retry_label, 0, wxALIGN_CENTER_VERTICAL | wxALIGN_CENTER_HORIZONTAL, 0);
m_book_second_sizer->Add(0, 0, 1, wxEXPAND, 0);
m_second_retry_btn = new Button(m_book_second_panel, _L("Retry"));
m_second_retry_btn->SetBackgroundColor(btn_bg_green);
// m_second_retry_btn->SetBorderColor(wxColour(38, 46, 48));
m_second_retry_btn->SetTextColor(*wxWHITE);
m_second_retry_btn->SetFont(Label::Body_14);
m_second_retry_btn->SetSize(wxSize(FromDIP(80), FromDIP(32)));
m_second_retry_btn->SetMinSize(wxSize(FromDIP(80), FromDIP(32)));
m_second_retry_btn->SetCornerRadius(FromDIP(16));
m_second_retry_btn->Bind(wxEVT_BUTTON, &PartSkipDialog::OnRetryButton, this);
m_book_second_btn_sizer->Add(m_second_retry_btn, 0, wxALL, FromDIP(24));
m_book_second_sizer->Add(m_book_second_btn_sizer, 0, wxALIGN_RIGHT, 0);
m_book_second_panel->SetSizer( m_book_second_sizer );
m_book_second_panel->Layout();
m_book_second_sizer->Fit( m_book_second_panel );
// page 1
m_book_first_panel = new wxPanel( m_simplebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
m_book_first_sizer = new wxBoxSizer( wxVERTICAL );
m_book_first_sizer->SetMinSize(wxSize(FromDIP(720), FromDIP(500)));
// auto m_loading_sizer = new wxBoxSizer(wxHORIZONTAL);
// std::vector<std::string> list{"ams_rfid_1", "ams_rfid_2", "ams_rfid_3", "ams_rfid_4"};
// m_loading_icon = new AnimaIcon(m_book_first_panel, wxID_ANY, list, "refresh_printer", 100);
m_loading_label = new Label( m_book_first_panel, wxString(_L("Skipping Objects Information Loading ...")));
m_loading_label->Wrap( -1 );
m_loading_label->SetBackgroundColour(*wxWHITE);
// m_loading_sizer->Add(0, 0, 1, wxEXPAND, 0);
// m_loading_sizer->Add(m_loading_icon, 0, wxALL, FromDIP(5));
// m_loading_sizer->Add(m_loading_label, 0, wxALL, FromDIP(5));
// m_loading_sizer->Add(0, 0, 1, wxEXPAND, 0);
m_book_first_sizer->Add(0, 0, 1, wxEXPAND, 0);
m_book_first_sizer->Add( m_loading_label, 0, wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER_HORIZONTAL, 0);
m_book_first_sizer->Add(0, 0, 1, wxEXPAND, 0);
m_book_first_panel->SetSizer( m_book_first_sizer );
m_book_first_panel->Layout();
m_book_first_sizer->Fit( m_book_first_panel );
m_simplebook->AddPage( m_book_first_panel, _("loading page"), false );
m_simplebook->AddPage( m_book_second_panel, _("retry page"), false );
m_simplebook->AddPage( m_book_third_panel, _("dialog page"), false );
m_sizer->Add( m_simplebook, 1, wxEXPAND | wxALL, 5 );
SetSizer( m_sizer );
m_zoom_in_btn->Bind(wxEVT_BUTTON, &PartSkipDialog::OnZoomIn, this);
m_zoom_out_btn->Bind(wxEVT_BUTTON, &PartSkipDialog::OnZoomOut, this);
m_switch_drag_btn->Bind(wxEVT_BUTTON, &PartSkipDialog::OnSwitchDrag, this);
m_canvas->Bind(EVT_ZOOM_PERCENT, &PartSkipDialog::OnZoomPercent, this);
m_canvas->Bind(EVT_CANVAS_PART, &PartSkipDialog::UpdatePartsStateFromCanvas, this);
m_apply_btn->Bind(wxEVT_BUTTON, &PartSkipDialog::OnApplyDialog, this);
m_all_checkbox->Bind(wxEVT_TOGGLEBUTTON, &PartSkipDialog::OnAllCheckbox, this);
Fit();
Layout();
CentreOnParent();
}
PartSkipDialog::~PartSkipDialog() {}
void PartSkipDialog::on_dpi_changed(const wxRect& suggested_rect) { Fit(); }
std::string PartSkipDialog::create_tmp_path(){
boost::filesystem::path parent_path(temporary_dir());
std::stringstream buf;
buf << "/bamboo_task/";
buf << m_timestamp;
if (m_obj) {
buf << m_obj->dev_id << "_";
buf << m_obj->job_id_ << "/";
} else {
buf << 1 << "_" << 1 << "/";
}
std::string tmp_path = (parent_path / buf.str() ).string();
if (!std::filesystem::exists(tmp_path + "Metadata/") && !fs::create_directories(tmp_path + "Metadata/")) {
wxMessageBox("create file failed.");
}
return tmp_path;
}
bool PartSkipDialog::is_local_file_existed(const std::vector<string> &local_paths)
{
for (auto path : local_paths) {
if (!std::filesystem::exists(path)) {
return false;
}
}
return true;
}
void PartSkipDialog::DownloadPartsFile()
{
m_tmp_path = create_tmp_path(); //wxGetApp().app_config->get("download_path");
m_local_paths.clear();
m_target_paths.clear();
int plate_idx = m_obj ? m_obj->m_plate_index : 1;
m_local_paths.push_back(m_tmp_path + "Metadata/pick_" + std::to_string(plate_idx) + ".png");
m_local_paths.push_back(m_tmp_path + "Metadata/model_settings.config");
m_local_paths.push_back(m_tmp_path + "Metadata/slice_info.config");
m_target_paths.push_back("Metadata/pick_" + std::to_string(plate_idx) + ".png");
m_target_paths.push_back("Metadata/model_settings.config");
m_target_paths.push_back("Metadata/slice_info.config");
if (!is_local_file_existed(m_local_paths)) {
m_file_sys = boost::make_shared<PrinterFileSystem>();
m_file_sys->Attached();
m_file_sys->Bind(EVT_STATUS_CHANGED, &PartSkipDialog::OnFileSystemEvent, this);
m_file_sys->Bind(EVT_RAMDOWNLOAD, &PartSkipDialog::OnFileSystemResult, this);
m_file_sys->Start();
} else {
m_file_sys->SendExistedFile();
}
}
void PartSkipDialog::fetchUrl(boost::weak_ptr<PrinterFileSystem> wfs)
{
boost::shared_ptr fs(wfs.lock());
if (!fs) return;
DeviceManager *dm = GUI::wxGetApp().getDeviceManager();
MachineObject *obj = dm->get_selected_machine();
if (obj == nullptr) {
fs->SetUrl("0");
return;
}
std::string dev_ver = obj->get_ota_version();
std::string dev_id = obj->dev_id;
int remote_proto = obj->get_file_remote();
NetworkAgent *agent = wxGetApp().getAgent();
std::string agent_version = agent ? agent->get_version() : "";
if (agent) {
switch (m_url_state) {
case URL_TCP: {
std::string devIP = obj->dev_ip;
std::string accessCode = obj->get_access_code();
std::string tcp_url = "bambu:///local/" + devIP + "?port=6000&user=" + "bblp" + "&passwd=" + accessCode;
CallAfter([=] {
boost::shared_ptr fs(wfs.lock());
if (!fs) return;
if (boost::algorithm::starts_with(tcp_url, "bambu:///")) {
fs->SetUrl(tcp_url);
} else {
fs->SetUrl("3");
}
});
break;
}
case URL_TUTK: {
std::string protocols[] = {"", "\"tutk\"", "\"agora\"", "\"tutk\",\"agora\""};
agent->get_camera_url(obj->dev_id + "|" + dev_ver + "|" + protocols[1], [this, wfs, m = dev_id, v = agent->get_version(), dv = dev_ver](std::string url) {
if (boost::algorithm::starts_with(url, "bambu:///")) {
url += "&device=" + m;
url += "&net_ver=" + v;
url += "&dev_ver=" + dv;
url += "&refresh_url=" + boost::lexical_cast<std::string>(&refresh_agora_url);
url += "&cli_id=" + wxGetApp().app_config->get("slicer_uuid");
url += "&cli_ver=" + std::string(SLIC3R_VERSION);
}
BOOST_LOG_TRIVIAL(info) << "SendToPrinter::fetchUrl: camera_url: " << hide_passwd(url, {"?uid=", "authkey=", "passwd="});
std::cout << "SendToPrinter::fetchUrl: camera_url: " << hide_passwd(url, {"?uid=", "authkey=", "passwd="});
CallAfter([=] {
boost::shared_ptr fs(wfs.lock());
if (!fs) return;
if (boost::algorithm::starts_with(url, "bambu:///")) {
fs->SetUrl(url);
} else {
fs->SetUrl("3");
}
});
});
break;
}
default: break;
}
}
}
void PartSkipDialog::OnFileSystemEvent(wxCommandEvent &e)
{
e.Skip();
auto wfs = boost::weak_ptr(m_file_sys);
boost::shared_ptr fs(wfs.lock());
if (!fs) return;
wxString msg;
int status = e.GetInt();
int extra = e.GetExtraLong();
switch (status) {
case PrinterFileSystem::Initializing:
case PrinterFileSystem::Connecting: break;
case PrinterFileSystem::ListSyncing: {
m_file_sys->GetPickImages(m_local_paths, m_target_paths);
break;
}
case PrinterFileSystem::Failed: {
m_file_sys->SendConnectFail();
break;
}
case PrinterFileSystem::Reconnecting: break;
}
if (e.GetInt() == PrinterFileSystem::Initializing) {
CallAfter([=] {
boost::shared_ptr fs(wfs.lock());
if (!fs) return;
fetchUrl(boost::weak_ptr(fs));
});
}
}
void PartSkipDialog::OnFileSystemResult(wxCommandEvent &event){
int result = event.GetInt();
if (result == 0) {
InitDialogUI();
SetSimplebookPage(2);
}else{
switch (m_url_state) {
case URL_TCP: {
m_url_state = URL_TUTK;
break;
}
case URL_TUTK: {
m_url_state = URL_TCP;
break;
}
}
SetSimplebookPage(1);
}
// wxMilliSleep(1000);
// m_loading_icon->Stop();
Refresh();
}
void PartSkipDialog::InitSchedule(MachineObject *obj){
m_obj = obj;
SetSimplebookPage(0);
// m_loading_icon->Play();
DownloadPartsFile();
}
void PartSkipDialog::OnRetryButton(wxCommandEvent &event) {
event.Skip();
InitSchedule(m_obj);
Refresh();
}
bool PartSkipDialog::is_drag_mode(){
return m_is_drag == true;
}
PartsInfo PartSkipDialog::GetPartsInfo(){
PartsInfo parts_info;
for (auto [part_id, part_state] : this->m_parts_state)
{
parts_info.push_back(std::pair<int, PartState>(part_id, part_state));
}
return parts_info;
}
void PartSkipDialog::OnZoomIn(wxCommandEvent &event){
m_canvas->ZoomIn(20);
UpdateZoomPercent();
}
void PartSkipDialog::OnZoomOut(wxCommandEvent &event){
m_canvas->ZoomOut(20);
UpdateZoomPercent();
}
void PartSkipDialog::OnSwitchDrag(wxCommandEvent& event){
if (this->is_drag_mode()) {
m_is_drag = false;
m_switch_drag_btn->SetBackgroundColor(*wxWHITE);
m_switch_drag_btn->SetIcon("canvas_drag");
} else {
m_is_drag = true;
m_switch_drag_btn->SetBackgroundColor(wxColour(0, 174, 66));
m_switch_drag_btn->SetIcon("canvas_drag_active");
}
m_canvas->SwitchDrag(m_is_drag);
}
void PartSkipDialog::OnZoomPercent(wxCommandEvent &event) {
m_zoom_percent = event.GetInt();
if (m_zoom_percent >= 1000) {
m_zoom_percent = 1000;
m_zoom_in_btn->Enable(false);
m_zoom_in_btn->SetIcon("canvas_zoom_in_disable");
} else if (m_zoom_percent <= 100) {
m_zoom_percent = 100;
m_zoom_out_btn->Enable(false);
m_zoom_out_btn->SetIcon("canvas_zoom_out_disable");
} else {
m_zoom_in_btn->Enable(true);
m_zoom_out_btn->Enable(true);
m_zoom_in_btn->SetIcon("canvas_zoom_in");
m_zoom_out_btn->SetIcon("canvas_zoom_out");
}
UpdateZoomPercent();
}
void PartSkipDialog::UpdatePartsStateFromCanvas(wxCommandEvent &event) {
int part_id = event.GetExtraLong();
PartState part_state = PartState(event.GetInt());
m_parts_state[part_id] = part_state;
if(part_state == psUnCheck){
m_all_checkbox->SetValue(false);
}
if(IsAllChecked()){
m_all_checkbox->SetValue(true);
}
UpdateDialogUI();
}
void PartSkipDialog::UpdateZoomPercent() {
m_percent_label->SetLabel(wxString::Format(_L("%d%%"), m_zoom_percent));
}
void PartSkipDialog::UpdateCountLabel() {
int check_cnt = 0;
int tot_cnt = 0;
for (auto [part_id, part_state] : m_parts_state) {
if (part_state == PartState::psChecked) check_cnt++;
if (part_state != PartState::psSkipped) tot_cnt++;
}
m_cnt_label->SetLabel(wxString::Format(_L("%d"), check_cnt));
m_tot_label->SetLabel(wxString::Format(_L("/%d Selected"), tot_cnt));
}
bool PartSkipDialog::Show(bool show)
{
if (show) {
wxGetApp().UpdateDlgDarkUI(this);
CentreOnParent();
Layout();
Fit();
}
return DPIDialog::Show(show);
}
void PartSkipDialog::InitDialogUI() {
m_print_lock = true;
m_scroll_sizer->Clear(true);
m_all_checkbox->SetValue(false);
m_parts_state.clear();
m_parts_name.clear();
string pick_img = m_local_paths[0];
string model_settings = m_local_paths[1];
m_switch_drag_btn->SetIcon("canvas_drag");
m_switch_drag_btn->SetBackgroundColor(*wxWHITE);
m_is_drag = false;
m_canvas->SwitchDrag(false);
m_canvas->SetZoomPercent(100);
m_canvas->SetOffset(wxPoint(0, 0));
m_canvas->LoadPickImage(pick_img);
ModelSettingHelper helper(model_settings);
if (helper.Parse()) {
auto parse_result = helper.GetResults();
for (const auto& part : parse_result) {
m_parts_state[part.identify_id] = part.state;
m_parts_name[part.identify_id] = part.name;
}
if (m_obj) {
std::vector<int> partskip_ids = m_obj->m_partskip_ids;
for (auto part_id : partskip_ids) {
m_parts_state[part_id] = PartState::psSkipped;
}
}
for(const auto& [part_id, part_state] : m_parts_state) {
auto line_sizer = new wxBoxSizer(wxHORIZONTAL);
auto checkbox = new CheckBox(m_list_view);
auto label = new Label(m_list_view, _L("file 0"));
checkbox->Bind(wxEVT_TOGGLEBUTTON, [this, part_id=part_id](wxCommandEvent& event) {
m_parts_state[part_id] = event.IsChecked() ? PartState::psChecked : PartState::psUnCheck;
if(!event.IsChecked()){
m_all_checkbox->SetValue(false);
}else if(IsAllChecked()){
m_all_checkbox->SetValue(true);
}
m_canvas->UpdatePartsInfo(GetPartsInfo());
UpdateCountLabel();
event.Skip();
}, checkbox->GetId());
if (part_state == PartState::psChecked) {
checkbox->SetValue(true);
checkbox->Enable(true);
} else if(part_state == PartState::psUnCheck) {
checkbox->SetValue(false);
checkbox->Enable(true);
} else if (part_state == PartState::psSkipped) {
checkbox->SetValue(true);
checkbox->Enable(false);
}
label->SetLabel(wxString::FromUTF8(m_parts_name[part_id]));
label->SetBackgroundColour(*wxWHITE);
label->SetForegroundColour(wxColor(107,107,107));
label->Wrap(-1);
checkbox->SetBackgroundColour(*wxWHITE);
line_sizer->Add(checkbox, 0, wxALL | wxALIGN_CENTER_VERTICAL, 0);
line_sizer->Add(label, 1, wxALL | wxALIGN_CENTER_VERTICAL, 0);
m_scroll_sizer->Add(line_sizer, 0, wxALL, FromDIP(5));
}
m_canvas->UpdatePartsInfo(GetPartsInfo());
}
m_scroll_sizer->Layout();
UpdateCountLabel();
Refresh();
m_print_lock = false;
}
void PartSkipDialog::UpdatePartsStateFromPrinter(MachineObject *obj) {
if (m_print_lock) return;
m_obj = obj;
if (m_obj) {
std::vector<int> partskip_ids = m_obj->m_partskip_ids;
for(auto part_id : partskip_ids) {
m_parts_state[part_id] = PartState::psSkipped;
}
m_canvas->UpdatePartsInfo(GetPartsInfo());
UpdateDialogUI();
}
}
void PartSkipDialog::UpdateDialogUI(){
if(m_parts_state.size() != m_scroll_sizer->GetItemCount()){
BOOST_LOG_TRIVIAL(warning) << "m_parts_state and m_scroll_sizer mismatch.";
return;
}
for (auto it = m_parts_state.begin(); it != m_parts_state.end(); ++it) {
int idx = std::distance(m_parts_state.begin(), it);
auto part_id = it->first;
auto part_state = it->second;
wxSizerItem *item = m_scroll_sizer->GetItem(idx);
if (item && item->IsSizer()) {
wxSizer* sizer = item->GetSizer();
auto check_item = sizer->GetItem((size_t)0);
if (check_item && check_item->IsWindow())
{
wxWindow *window = check_item->GetWindow();
CheckBox *checkbox = dynamic_cast<CheckBox *>(window);
if (part_state == PartState::psChecked) {
checkbox->SetValue(true);
} else if (part_state == PartState::psUnCheck) {
checkbox->SetValue(false);
} else {
checkbox->SetValue(true);
checkbox->Enable(false);
}
}
}
}
UpdateCountLabel();
Refresh();
}
void PartSkipDialog::SetSimplebookPage(int page) {
m_simplebook->SetSelection(page);
}
bool PartSkipDialog::IsAllChecked() {
for (auto& [part_id, part_state] : m_parts_state) {
if (part_state == PartState::psUnCheck)
return false;
}
return true;
}
void PartSkipDialog::OnAllCheckbox(wxCommandEvent &event)
{
if (m_all_checkbox->GetValue()) {
for (auto& [part_id, part_state] : m_parts_state) {
if (part_state == PartState::psUnCheck)
part_state = PartState::psChecked;
}
}
else{
for (auto& [part_id, part_state] : m_parts_state) {
if (part_state == PartState::psChecked)
part_state = PartState::psUnCheck;
}
}
m_canvas->UpdatePartsInfo(GetPartsInfo());
UpdateDialogUI();
event.Skip();
}
void PartSkipDialog::OnApplyDialog(wxCommandEvent &event)
{
event.Skip();
m_partskip_ids.clear();
for (const auto& [part_id, part_state] : m_parts_state) {
if (part_state == PartState::psChecked) {
m_partskip_ids.push_back(part_id);
}
}
PartSkipConfirmDialog confirm_dialog(this);
confirm_dialog.SetMsgLabel(wxString::Format(_L("Skipping %d objects."), m_partskip_ids.size()));
if(confirm_dialog.ShowModal() == wxID_OK){
if (m_obj) {
BOOST_LOG_TRIVIAL(info) << "monitor: skipping "<< m_partskip_ids.size() <<" parts dev_id =" << m_obj->dev_id;
bool all_skipped = true;
for (auto [part_id, part_state] : m_parts_state) {
if (part_state == PartState::psUnCheck) all_skipped = false;
}
if (all_skipped) {
m_obj->command_task_abort();
} else {
m_obj->command_task_partskip(m_partskip_ids);
}
EndModal(wxID_OK);
} else {
BOOST_LOG_TRIVIAL(warning) << "machine object is null.";
}
}
}
PartSkipConfirmDialog::PartSkipConfirmDialog(wxWindow *parent) : DPIDialog(parent, wxID_ANY, _L("Skip Objects"), wxDefaultPosition, wxDefaultSize, wxCAPTION | wxCLOSE_BOX)
{
std::string icon_path = (boost::format("%1%/images/BambuStudioTitle.ico") % Slic3r::resources_dir()).str();
SetIcon(wxIcon(Slic3r::encode_path(icon_path.c_str()), wxBITMAP_TYPE_ICO));
SetBackgroundColour(*wxWHITE);
SetSizeHints(wxDefaultSize, wxDefaultSize);
wxBoxSizer* m_sizer;
m_sizer = new wxBoxSizer( wxVERTICAL );
auto m_line_top = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxSize(-1, 1), wxTAB_TRAVERSAL);
m_line_top->SetBackgroundColour(wxColour(0xA6, 0xa9, 0xAA));
m_sizer->Add(m_line_top, 0, wxEXPAND, 0);
m_sizer->Add(0, 0, 0, wxALL, FromDIP(15));
m_msg_label = new Label( this, wxString(_L("Skipping objects.")));
m_msg_label->Wrap( -1 );
m_msg_label->SetBackgroundColour(*wxWHITE);
m_sizer->Add(m_msg_label, 0, wxLEFT, FromDIP(15));
auto m_tip_label = new Label(this, wxString(_("This action cannot be undone. Continue?")));
m_tip_label->Wrap(-1);
m_tip_label->SetBackgroundColour(*wxWHITE);
m_tip_label->SetForegroundColour(wxColor(92,92,92));
m_sizer->Add(m_tip_label, 0, wxLEFT | wxTOP, FromDIP(15));
wxBoxSizer* m_button_sizer;
m_button_sizer = new wxBoxSizer(wxHORIZONTAL);
m_button_sizer->SetMinSize(wxSize(FromDIP(480), FromDIP(54)));
m_button_sizer->Add(0, 0, 1, wxEXPAND, 0);
StateColor btn_bg_white(std::pair<wxColour, int>(wxColour(206, 206, 206), StateColor::Pressed), std::pair<wxColour, int>(wxColour(238, 238, 238), StateColor::Hovered),
std::pair<wxColour, int>(*wxWHITE, StateColor::Normal));
StateColor btn_bg_green(std::pair<wxColour, int>(wxColour(27, 136, 68), StateColor::Pressed), std::pair<wxColour, int>(wxColour(61, 203, 115), StateColor::Hovered),
std::pair<wxColour, int>(wxColour(0, 177, 66), StateColor::Normal));
m_apply_button = new Button(this, wxString(_L("Continue")));
m_apply_button->SetBackgroundColor(btn_bg_green);
m_apply_button->SetTextColor(*wxWHITE);
// m_apply_button->SetBorderColor(wxColour(38, 46, 48));
m_apply_button->SetFont(Label::Body_14);
m_apply_button->SetSize(wxSize(FromDIP(80), FromDIP(32)));
m_apply_button->SetMinSize(wxSize(FromDIP(80), FromDIP(32)));
m_apply_button->SetCornerRadius(FromDIP(16));
m_apply_button->Bind(wxEVT_BUTTON, [this](auto& e){
EndModal(wxID_OK);
e.Skip();
});
m_button_sizer->Add(m_apply_button, 0, wxALL, FromDIP(15));
m_sizer->Add(m_button_sizer, 1, wxEXPAND, FromDIP(5));
m_sizer->Fit( this );
SetSizer( m_sizer );
Layout();
Fit();
}
PartSkipConfirmDialog::~PartSkipConfirmDialog()
{
}
bool PartSkipConfirmDialog::Show(bool show)
{
if (show) {
wxGetApp().UpdateDlgDarkUI(this);
CentreOnParent();
Layout();
Fit();
}
return DPIDialog::Show(show);
}
void PartSkipConfirmDialog::on_dpi_changed(const wxRect& suggested_rect) { Fit(); }
Button* PartSkipConfirmDialog::GetConfirmButton()
{
return m_apply_button;
}
void PartSkipConfirmDialog::SetMsgLabel(wxString msg){
m_msg_label->SetLabel(msg);
}
}} // namespace Slic3r::GUI

View File

@@ -0,0 +1,153 @@
#include <wx/panel.h>
#include <wx/bitmap.h>
#include <wx/image.h>
#include <wx/sizer.h>
#include <wx/gbsizer.h>
#include <wx/webrequest.h>
#include <wx/control.h>
#include <wx/dcclient.h>
#include <wx/display.h>
#include <wx/mstream.h>
#include <wx/sstream.h>
#include <wx/zstream.h>
#include <wx/window.h>
#include <wx/dcgraph.h>
#include <wx/simplebook.h>
#include "Widgets/Label.hpp"
#include "Widgets/CheckBox.hpp"
#include "Widgets/Button.hpp"
#include "Widgets/AnimaController.hpp"
#include "DeviceManager.hpp"
#include "PartSkipCommon.hpp"
#include "Printer/PrinterFileSystem.h"
#include "I18N.hpp"
#include "GUI_Utils.hpp"
namespace Slic3r { namespace GUI {
class SkipPartCanvas;
enum URL_STATE {
URL_TCP,
URL_TUTK,
};
class PartSkipConfirmDialog : public DPIDialog
{
private:
protected:
Label *m_msg_label;
Button *m_apply_button;
public:
PartSkipConfirmDialog(wxWindow *parent);
~PartSkipConfirmDialog();
void on_dpi_changed(const wxRect &suggested_rect);
Button* GetConfirmButton();
void SetMsgLabel(wxString msg);
bool Show(bool show);
};
class PartSkipDialog : public DPIDialog
{
public:
PartSkipDialog(wxWindow *parent);
~PartSkipDialog();
void on_dpi_changed(const wxRect &suggested_rect);
bool Show(bool show);
void UpdatePartsStateFromPrinter(MachineObject *obj_);
void SetSimplebookPage(int page);
void InitSchedule(MachineObject *obj_);
void InitDialogUI();
MachineObject *m_obj{nullptr};
wxSimplebook* m_simplebook;
wxPanel* m_book_third_panel;
wxPanel* m_book_second_panel;
wxPanel* m_book_first_panel;
SkipPartCanvas* m_canvas;
Button* m_zoom_in_btn;
Button* m_zoom_out_btn;
Button* m_switch_drag_btn;
CheckBox* m_all_checkbox;
Button* m_percent_label;
Label * m_all_label;
wxPanel* m_line;
wxScrolledWindow* m_list_view;
Label* m_cnt_label;
Label* m_tot_label;
Button* m_apply_btn;
Label* m_loading_label;
Label* m_retry_label;
wxBoxSizer* m_sizer;
wxBoxSizer* m_dlg_sizer;
wxBoxSizer* m_dlg_content_sizer;
wxBoxSizer* m_dlg_btn_sizer;
wxBoxSizer* m_canvas_sizer;
wxBoxSizer* m_canvas_btn_sizer;
wxBoxSizer* m_list_sizer;
wxBoxSizer* m_scroll_sizer;
wxBoxSizer* m_book_first_sizer;
wxBoxSizer* m_book_second_sizer;
wxBoxSizer* m_book_second_btn_sizer;
Button* m_second_retry_btn;
AnimaIcon* m_loading_icon;
private:
int m_zoom_percent{100};
bool m_is_drag{false};
bool m_print_lock{true};
std::map<uint32_t, PartState> m_parts_state;
std::map<uint32_t, std::string> m_parts_name;
std::vector<int> m_partskip_ids;
enum URL_STATE m_url_state = URL_STATE::URL_TCP;
PartsInfo GetPartsInfo();
bool is_drag_mode();
boost::shared_ptr<PrinterFileSystem> m_file_sys;
bool m_file_sys_result{false};
std::string m_timestamp;
std::string m_tmp_path;
std::vector<string> m_local_paths;
std::vector<string> m_target_paths;
std::string create_tmp_path();
bool is_local_file_existed(const std::vector<string> &local_paths);
void DownloadPartsFile();
void OnFileSystemEvent(wxCommandEvent &event);
void OnFileSystemResult(wxCommandEvent &event);
void fetchUrl(boost::weak_ptr<PrinterFileSystem> wfs);
void OnZoomIn(wxCommandEvent &event);
void OnZoomOut(wxCommandEvent &event);
void OnSwitchDrag(wxCommandEvent &event);
void OnZoomPercent(wxCommandEvent &event);
void UpdatePartsStateFromCanvas(wxCommandEvent &event);
void UpdateZoomPercent();
void UpdateCountLabel();
void UpdateDialogUI();
bool IsAllChecked();
void OnRetryButton(wxCommandEvent &event);
void OnAllCheckbox(wxCommandEvent &event);
void OnApplyDialog(wxCommandEvent &event);
};
}}

View File

@@ -39,6 +39,7 @@ wxDEFINE_EVENT(EVT_FILE_CHANGED, wxCommandEvent);
wxDEFINE_EVENT(EVT_SELECT_CHANGED, wxCommandEvent);
wxDEFINE_EVENT(EVT_THUMBNAIL, wxCommandEvent);
wxDEFINE_EVENT(EVT_DOWNLOAD, wxCommandEvent);
wxDEFINE_EVENT(EVT_RAMDOWNLOAD, wxCommandEvent);
wxDEFINE_EVENT(EVT_FILE_CALLBACK, wxCommandEvent);
@@ -248,6 +249,131 @@ struct PrinterFileSystem::Download : Progress
boost::uuids::detail::md5 boost_md5;
};
void PrinterFileSystem::GetPickImages(const std::vector<std::string> &local_paths, const std::vector<std::string> &targetpaths)
{
GetPickImage(1, local_paths[0], targetpaths[0]);
GetPickImage(2, local_paths[1], targetpaths[1]);
GetPickImage(3, local_paths[2], targetpaths[2]);
}
void PrinterFileSystem::GetPickImage(int id, const std::string &local_path, const std::string &targetpath)
{
json j;
j["sequence_id"] = id;
j["version"] = 1;
j["peer_host"] = "studio";
j["command"] = "get_project_file";
j["file_rel_path"] = targetpath;
std::string param = j.dump();
DownloadRamFile(16, local_path, param);
}
void PrinterFileSystem::DownloadRamFile(int index, const std::string &local_path, const std::string & param)
{
std::shared_ptr<Download> download(new Download);
download->local_path = local_path;
json req;
req["path"] = "mem:/" + std::to_string(index);
req["offset"] = 0;
req["mem_dl_param_size"] = param.size();
m_download_seq = SendRequest<Progress>(
FILE_DOWNLOAD, req,
[download](json const &resp, Progress &prog, unsigned char const *data) -> int {
size_t size = resp.value("size", 0);
prog.size = resp["offset"];
prog.total = resp["total"];
if (resp.contains("mem_dl_param_size")) {
size_t s = resp["mem_dl_param_size"].get<size_t>();
std::string json_str(reinterpret_cast<const char *>(data), s);
// OutputDebugStringA(json_str.c_str());
// OutputDebugStringA("\n");
json mem_dl_json = json::parse(json_str);
// download->mem_dl_param_size = size;
if (!mem_dl_json.contains("result") || mem_dl_json["result"] == 1 ) {
wxLogWarning("Download failed: result = 1");
return ERROR_JSON;
}
return CONTINUE;
}
if (prog.size == 0 ) {
download->ofs.open(download->local_path, std::ios::binary);
if (!download->ofs) {
download->error = last_system_error();
wxLogWarning("DownloadImageFromRam open error: %s\n", wxString::FromUTF8(download->error));
return FILE_OPEN_ERR;
}
}
download->ofs.write(reinterpret_cast<const char *>(data), size);
if (!download->ofs) {
download->error = last_system_error();
wxLogWarning("DownloadImageFromRam write error: %s\n", wxString::FromUTF8(download->error));
return FILE_READ_WRITE_ERR;
}
download->boost_md5.process_bytes(data, size);
prog.size += size;
download->total = prog.total;
download->size = prog.size;
if (prog.size < prog.total) {
return 0;
}
download->ofs.close();
std::string md5 = resp["file_md5"];
boost::uuids::detail::md5::digest_type digest;
download->boost_md5.get_digest(digest);
for (int i = 0; i < 4; ++i) digest[i] = boost::endian::endian_reverse(digest[i]);
std::string str_md5;
const auto char_digest = reinterpret_cast<const char *>(&digest[0]);
boost::algorithm::hex(char_digest, char_digest + sizeof(digest), std::back_inserter(str_md5));
if (!boost::iequals(str_md5, md5)) {
wxLogWarning("DownloadImageFromRam checksum error: %s != %s\n", str_md5, md5);
boost::system::error_code ec;
boost::filesystem::rename(download->local_path, download->local_path + ".tmp", ec);
return FILE_CHECK_ERR;
}
return SUCCESS;
},
[this, download](int result, Progress const &data) {
//OutputDebugStringA(std::to_string(result).c_str());
//OutputDebugStringA("\n");
if (result == CONTINUE) { return; }
std::string msg;
if (result == SUCCESS) {
wxLogMessage("DownloadImageFromRam finished: %s", download->local_path);
msg = "SUCCESS";
SendChangedEvent(EVT_RAMDOWNLOAD, result, result ? download->error : download->local_path);
} else if (result != CONTINUE) {
wxLogWarning("DownloadImageFromRam failed: %s", download->error);
msg = "ERROR";
SendChangedEvent(EVT_RAMDOWNLOAD, result, result ? download->error : download->local_path);
}
},param);
}
void PrinterFileSystem::SendExistedFile(){
SendChangedEvent(EVT_RAMDOWNLOAD, SUCCESS);
}
void PrinterFileSystem::SendConnectFail(){
SendChangedEvent(EVT_RAMDOWNLOAD, ERROR_PIPE);
}
void PrinterFileSystem::DownloadFiles(size_t index, std::string const &path)
{
if (index == (size_t) -1) {
@@ -281,6 +407,10 @@ void PrinterFileSystem::DownloadFiles(size_t index, std::string const &path)
DownloadNextFile();
}
void PrinterFileSystem::DownloadCheckFiles(std::string const &path)
{
for (size_t i = 0; i < m_file_list.size(); ++i) {
@@ -706,7 +836,7 @@ void PrinterFileSystem::DownloadNextFile()
file.download.reset(), file.flags &= ~FF_DOWNLOAD;
else // FAILED
file.download.reset();
if (&file_index.first == &m_file_list)
if (&file_index.first == &m_file_list)
SendChangedEvent(EVT_DOWNLOAD, download->index, result ? download->error : file.local_path, result);
}
}
@@ -1030,7 +1160,7 @@ void PrinterFileSystem::DumpLog(void * thiz, int, tchar const *msg)
static_cast<PrinterFileSystem*>(thiz)->Bambu_FreeLogMsg(msg);
}
boost::uint32_t PrinterFileSystem::SendRequest(int type, json const &req, callback_t2 const &callback)
boost::uint32_t PrinterFileSystem::SendRequest(int type, json const &req, callback_t2 const &callback,const std::string& param)
{
if (m_session.tunnel == nullptr) {
Retry();
@@ -1044,6 +1174,13 @@ boost::uint32_t PrinterFileSystem::SendRequest(int type, json const &req, callba
root["req"] = req;
std::ostringstream oss;
oss << root;
if (!param.empty()) {
oss << "\n\n";
oss << param;
}
// OutputDebugStringA(oss.str().c_str());
// OutputDebugStringA("\n");
auto msg = oss.str();
boost::unique_lock l(m_mutex);
m_messages.push_back(msg);

View File

@@ -22,6 +22,7 @@ wxDECLARE_EVENT(EVT_FILE_CHANGED, wxCommandEvent);
wxDECLARE_EVENT(EVT_SELECT_CHANGED, wxCommandEvent);
wxDECLARE_EVENT(EVT_THUMBNAIL, wxCommandEvent);
wxDECLARE_EVENT(EVT_DOWNLOAD, wxCommandEvent);
wxDECLARE_EVENT(EVT_RAMDOWNLOAD, wxCommandEvent);
class PrinterFileSystem : public wxEvtHandler, public boost::enable_shared_from_this<PrinterFileSystem>, BambuLib
{
@@ -138,6 +139,17 @@ public:
void DownloadFiles(size_t index, std::string const &path);
void GetPickImage(int id, const std::string &local_path, const std::string &path);
void GetPickImages(const std::vector<std::string> &local_paths, const std::vector<std::string> &targetpaths);
void DownloadRamFile(int index, const std::string &local_path, const std::string &param);
void SendExistedFile();
void SendConnectFail();
void DownloadCheckFiles(std::string const &path);
bool DownloadCheckFile(size_t index);
@@ -218,8 +230,7 @@ private:
typedef std::function<int(int, json const &resp, unsigned char const *data)> callback_t2;
template <typename T>
boost::uint32_t SendRequest(int type, json const& req, Translator<T> const& translator, Callback<T> const& callback)
template<typename T> boost::uint32_t SendRequest(int type, json const &req, Translator<T> const &translator, Callback<T> const &callback, const std::string &param = "")
{
auto c = [translator, callback, this](int result, json const &resp, unsigned char const *data) -> int
{
@@ -236,7 +247,7 @@ private:
PostCallback<T>(callback, result, t);
return result;
};
return SendRequest(type, req, c);
return SendRequest(type, req, c, param);
}
template<typename T> using Applier = std::function<void(T const &)>;
@@ -266,7 +277,7 @@ private:
InstallNotify(type, c);
}
boost::uint32_t SendRequest(int type, json const &req, callback_t2 const &callback);
boost::uint32_t SendRequest(int type, json const &req, callback_t2 const &callback, const std::string &param = "");
void InstallNotify(int type, callback_t2 const &callback);

View File

@@ -0,0 +1,628 @@
#include <GL/glew.h>
#include "SkipPartCanvas.hpp"
#include <opencv2/opencv.hpp>
#include <opencv2/core.hpp>
#include <opencv2/imgproc.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/nowide/fstream.hpp>
#include <expat.h>
#include <earcut/earcut.hpp>
#include <libslic3r/Color.hpp>
wxDEFINE_EVENT(EVT_ZOOM_PERCENT, wxCommandEvent);
wxDEFINE_EVENT(EVT_CANVAS_PART, wxCommandEvent);
namespace Slic3r {
namespace GUI {
SkipPartCanvas::SkipPartCanvas(wxWindow *parent, const wxGLAttributes& dispAttrs)
: wxGLCanvas(parent, dispAttrs) {
context_ = new wxGLContext(this);
this->Bind(wxEVT_PAINT, &SkipPartCanvas::OnPaint, this);
this->Bind(wxEVT_MOUSEWHEEL, &SkipPartCanvas::OnMouseWheel, this);
this->Bind(wxEVT_LEFT_DOWN, &SkipPartCanvas::OnMouseLeftDown, this);
this->Bind(wxEVT_LEFT_DCLICK, &SkipPartCanvas::OnMouseLeftDown, this);
this->Bind(wxEVT_LEFT_UP, &SkipPartCanvas::OnMouseLeftUp, this);
this->Bind(wxEVT_RIGHT_DOWN, &SkipPartCanvas::OnMouseRightDown, this);
this->Bind(wxEVT_RIGHT_UP, &SkipPartCanvas::OnMouseRightUp, this);
this->Bind(wxEVT_SIZE, &SkipPartCanvas::OnSize, this);
this->Bind(wxEVT_MOTION, &SkipPartCanvas::OnMouseMotion, this);
}
void SkipPartCanvas::LoadPickImage(const std::string & path)
{
parts_state_.clear();
int preffered_w{FromDIP(400)}, preffered_h{FromDIP(400)};
cv::Mat src_image = cv::imread(path, cv::IMREAD_UNCHANGED);
float zoom_x{static_cast<float>(preffered_w) / src_image.cols};
float zoom_y{static_cast<float>(preffered_h) / src_image.rows};
float image_scale{0};
if (abs(zoom_x - 1) > abs(zoom_y - 1))
image_scale = zoom_x;
else
image_scale = zoom_y;
cv::resize(src_image, pick_image_, cv::Size(), image_scale, image_scale);
std::vector<cv::Mat> channels;
cv::split(pick_image_, channels); // channels[3] 是 Alpha
cv::Mat alpha = channels[3];
cv::Mat mask;
cv::threshold(alpha, mask, 0, 255, cv::THRESH_BINARY);
std::vector<std::vector<cv::Point>> pick_counters;
cv::findContours(mask, pick_counters, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_TC89_KCOS);
std::vector<std::vector<FloatPoint>> polygon;
for (const auto& counter : pick_counters) {
cv::Point center_pos{0,0};
for (const auto& pt : counter) {
center_pos += pt;
}
center_pos = center_pos / static_cast<int>(counter.size());
auto id = GetIdAtImagePt(wxPoint(center_pos.x, center_pos.y));
if (id > 0) {
polygon.clear();
polygon.emplace_back(std::vector<FloatPoint>{});
for (const auto& pt : counter) {
polygon[0].push_back(FloatPoint{pt.x * 1.0f, pt.y * 1.0f});
}
std::vector<uint32_t> indices = mapbox::earcut<uint32_t>(polygon);
std::vector<FloatPoint> final_counter{};
for (size_t i = 0; i < indices.size(); i += 3) {
FloatPoint a = polygon[0][indices[i]];
FloatPoint b = polygon[0][indices[i+1]];
FloatPoint c = polygon[0][indices[i+2]];
final_counter.push_back(FloatPoint{a[0], a[1]});
final_counter.push_back(FloatPoint{b[0], b[1]});
final_counter.push_back(FloatPoint{c[0], c[1]});
}
parts_triangles_.emplace(id, final_counter);
pick_parts_.emplace(id, counter);
if (parts_state_.find(id) == parts_state_.end())
parts_state_.emplace(id, psUnCheck);
}
}
}
void SkipPartCanvas::ZoomIn(const int zoom_percent)
{
SetZoomPercent(zoom_percent_ + zoom_percent);
Refresh();
}
void SkipPartCanvas::ZoomOut(const int zoom_percent)
{
SetZoomPercent(zoom_percent_ - zoom_percent);
Refresh();
}
void SkipPartCanvas::SwitchDrag(const bool drag_on)
{
fixed_draging_ = drag_on;
AutoSetCursor();
}
void SkipPartCanvas::UpdatePartsInfo(const PartsInfo& parts)
{
for (auto const& part : parts) {
if (auto res = parts_state_.find(part.first); res != parts_state_.end())
res->second = part.second;
}
Refresh();
}
void DrawRoundedRect(float x, float y, float width, float height, float radius, const ColorRGB& color, int segments = 16)
{
glColor3f(color.r(), color.g(), color.b());
// 1. Draw center rectangle
glBegin(GL_QUADS);
glVertex2f(x + radius, y + radius);
glVertex2f(x + width - radius, y + radius);
glVertex2f(x + width - radius, y + height - radius);
glVertex2f(x + radius, y + height - radius);
glEnd();
// 2. Draw side rectangles (excluding corners)
glBegin(GL_QUADS);
// Left
glVertex2f(x, y + radius);
glVertex2f(x + radius, y + radius);
glVertex2f(x + radius, y + height - radius);
glVertex2f(x, y + height - radius);
// Right
glVertex2f(x + width - radius, y + radius);
glVertex2f(x + width, y + radius);
glVertex2f(x + width, y + height - radius);
glVertex2f(x + width - radius, y + height - radius);
// Top
glVertex2f(x + radius, y + height - radius);
glVertex2f(x + width - radius, y + height - radius);
glVertex2f(x + width - radius, y + height);
glVertex2f(x + radius, y + height);
// Bottom
glVertex2f(x + radius, y);
glVertex2f(x + width - radius, y);
glVertex2f(x + width - radius, y + radius);
glVertex2f(x + radius, y + radius);
glEnd();
// 3. Draw corners
auto drawCorner = [&](float cx, float cy, float startAngle) {
glBegin(GL_TRIANGLE_FAN);
glVertex2f(cx, cy);
for (int i = 0; i <= segments; ++i) {
float angle = startAngle + (M_PI * 0.5f) * (float)i / segments;
glVertex2f(cx + cosf(angle) * radius, cy + sinf(angle) * radius);
}
glEnd();
};
drawCorner(x + radius, y + radius, M_PI); // bottom-left
drawCorner(x + width - radius, y + radius, 1.5f * M_PI); // bottom-right
drawCorner(x + width - radius, y + height - radius, 0.0f); // top-right
drawCorner(x + radius, y + height - radius, 0.5f * M_PI); // top-left
}
void SkipPartCanvas::Render()
{
constexpr float border_w = 3.f;
constexpr int uncheckd_stencil =1;
constexpr int checkd_stencil = 2;
constexpr int skipped_stencil = 3;
SetCurrent(*context_);
glPushAttrib(GL_ALL_ATTRIB_BITS);
int w, h;
GetClientSize(&w, &h);
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
auto view_rect = ViewPtToImagePt(wxPoint(w, h));
glOrtho(offset_.x, view_rect.x, view_rect.y, offset_.y, -1, 1);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glClearColor(parent_color_.r(), parent_color_.g(), parent_color_.b(), 1.f);
glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
float rx = offset_.x;
float ry = offset_.y;
float rw = view_rect.x - offset_.x;
float rh = view_rect.y - offset_.y;
float radius = std::min(rw, rh) * 0.05f;
DrawRoundedRect(rx, ry, rw, rh, radius, ColorRGB{0.9f, 0.9f, 0.9f});
glEnable(GL_BLEND);
glEnable(GL_MULTISAMPLE);
glEnable(GL_LINE_SMOOTH);
glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glDisable(GL_CULL_FACE);
glEnable(GL_STENCIL_TEST);
auto draw_shape = [this, border_w](const int stencil, const PartState part_type, const ColorRGB& rgb) {
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glStencilFunc(GL_ALWAYS, stencil, 0xFF);
glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
for (const auto& contour : parts_triangles_) {
auto part_info = parts_state_.find(contour.first);
if (part_info == parts_state_.end() || part_info->second != part_type)
continue;
glColor3f(1, 1, 1);
glBegin(GL_TRIANGLES);
for (size_t i = 0; i < contour.second.size(); i += 3) {
glVertex2f(contour.second[i][0], contour.second[i][1]);
glVertex2f(contour.second[i+1][0], contour.second[i+1][1]);
glVertex2f(contour.second[i+2][0], contour.second[i+2][1]);
}
glEnd();
}
for (const auto& contour : pick_parts_) {
if (contour.first != this->hover_id_) continue;
auto part_info = parts_state_.find(contour.first);
if (part_info == parts_state_.end() || part_info->second != part_type)
continue;
glColor3f(rgb.r(), rgb.g(), rgb.b());
glLineWidth(border_w);
glBegin(GL_LINE_LOOP);
for (const auto& pt : contour.second) {
glVertex2f(pt.x, pt.y);
}
glEnd();
}
};
// draw unchecked shapes
// stencil1 => unchecked
draw_shape(uncheckd_stencil, psUnCheck, ColorRGB{0, 174 / 255.f, 66 / 255.f});
// draw checked shapes
// stencil2 => checked
draw_shape(checkd_stencil, psChecked, ColorRGB{208 / 255.f, 27 / 255.f, 66 / 255.f});
// draw skipped shapes
// stencil3 => skipped
draw_shape(skipped_stencil, psSkipped, ColorRGB{95 / 255.f, 95 / 255.f, 95 / 255.f});
auto draw_mask = [this, view_rect, border_w, w, h](const int stencil, const PartState part_type,
const ColorRGB& background, const ColorRGB& line, const ColorRGB& bound) {
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glStencilFunc(GL_EQUAL, stencil, 0xFF);
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); // Don't change stencil
glColor3f(background.r(), background.g(), background.b());
glBegin(GL_POLYGON);
glVertex2f(offset_.x, offset_.y);
glVertex2f(offset_.x, view_rect.y);
glVertex2f(view_rect.x, view_rect.y);
glVertex2f(view_rect.x, offset_.y);
glEnd();
// draw main color
glColor3f(line.r(), line.g(), line.b());
// re-draw shape bound
for (const auto& contour : pick_parts_) {
auto part_info = parts_state_.find(contour.first);
if (part_info == parts_state_.end() || part_info->second != part_type)
continue;
glColor3f(bound.r(), bound.g(), bound.b());
glLineWidth(border_w);
glBegin(GL_LINE_LOOP);
for (const auto& pt : contour.second) {
glVertex2f(pt.x, pt.y);
}
glEnd();
}
};
draw_mask(checkd_stencil, psChecked, ColorRGB{239 / 255.f, 175 / 255.f, 175 / 255.f},
ColorRGB{225 / 255.f, 71 / 255.f, 71 / 255.f}, ColorRGB{208 / 255.f, 27 / 255.f, 27 / 255.f});
draw_mask(skipped_stencil, psSkipped, ColorRGB{159 / 255.f, 159 / 255.f, 159 / 255.f},
ColorRGB{95 / 255.f, 95 / 255.f, 95 / 255.f}, ColorRGB{95 / 255.f, 95 / 255.f, 95 / 255.f});
glDisable(GL_STENCIL_TEST);
glPopAttrib();
glFlush();
}
void SkipPartCanvas::DebugLogLine(std::string str)
{
//if (!log_ctrl)
// return;
//log_ctrl->AppendText(str + "\n");
}
void SkipPartCanvas::SendSelectEvent(int id, PartState state) {
wxCommandEvent evt(EVT_CANVAS_PART);
evt.SetExtraLong(id);
evt.SetInt(static_cast<int>(state));
wxPostEvent(this, evt);
}
void SkipPartCanvas::SendZoomEvent(int zoom_percent) {
wxCommandEvent evt(EVT_ZOOM_PERCENT);
evt.SetInt(zoom_percent_);
wxPostEvent(this, evt);
}
inline double SkipPartCanvas::Zoom() const
{
return zoom_percent_ / 100.0f;
}
inline wxPoint SkipPartCanvas::ViewPtToImagePt(const wxPoint& view_pt) const
{
return wxPoint(view_pt.x / Zoom(), view_pt.y / Zoom()) + offset_;
}
uint32_t SkipPartCanvas::GetIdAtImagePt(const wxPoint& image_pt) const
{
if (image_pt.x >= 0 && image_pt.x < pick_image_.cols
&& image_pt.y >= 0 && image_pt.y < pick_image_.rows) {
// at(row, col)=>at(y, x)
cv::Vec4b bgr = pick_image_.at<cv::Vec4b>(image_pt.y, image_pt.x);
SkipIdHelper helper{bgr[2], bgr[1], bgr[0]};
helper.reverse();
return helper.value;
} else {
return 0;
}
}
inline uint32_t SkipPartCanvas::GetIdAtViewPt(const wxPoint& view_pt) const
{
wxPoint pt_at_image = ViewPtToImagePt(view_pt);
return GetIdAtImagePt(pt_at_image);
}
void SkipPartCanvas::SetZoomPercent(const int value)
{
zoom_percent_ = std::clamp(value, 100, 1000);
std::ostringstream oss;
oss << "zoom to " << zoom_percent_;
DebugLogLine(oss.str());
SendZoomEvent(zoom_percent_);
}
void SkipPartCanvas::SetOffset(const wxPoint& value)
{
int w, h;
GetClientSize(&w, &h);
int max_w = static_cast<int>(w * (1 - 1 / Zoom())) >= 0 ? static_cast<int>(w * (1 - 1 / Zoom())) : 0;
int max_h = static_cast<int>(w * (1 - 1 / Zoom())) >= 0 ? static_cast<int>(h * (1 - 1 / Zoom())) : 0;
offset_.x = std::clamp(value.x, 0, max_w);
offset_.y = std::clamp(value.y, 0, max_h);
}
void SkipPartCanvas::AutoSetCursor()
{
if(is_draging_ || fixed_draging_)
SetCursor(wxCursor(wxCURSOR_HAND));
else
SetCursor(wxCursor(wxCURSOR_NONE));
}
void SkipPartCanvas::StartDrag(const wxPoint& mouse_pt)
{
drag_start_pt_ = mouse_pt;
drag_start_offset_ = offset_;
is_draging_ = true;
AutoSetCursor();
}
void SkipPartCanvas::ProcessDrag(const wxPoint& mouse_pt)
{
wxPoint drag_offset = mouse_pt - drag_start_pt_;
SetOffset(- wxPoint(drag_offset.x / Zoom(), drag_offset.y / Zoom()) + drag_start_offset_);
Refresh();
}
void SkipPartCanvas::ProcessHover(const wxPoint& mouse_pt)
{
auto id_at_mouse = GetIdAtViewPt(mouse_pt);
int new_hover_id { -1 };
auto part_state = parts_state_.find(id_at_mouse);
if (part_state != parts_state_.end() && part_state->second == psUnCheck) {
new_hover_id = id_at_mouse;
};
if (new_hover_id != this->hover_id_) {
this->hover_id_ = new_hover_id;
Refresh();
}
}
void SkipPartCanvas::EndDrag()
{
is_draging_ = false;
AutoSetCursor();
}
void SkipPartCanvas::OnPaint(wxPaintEvent &event)
{
wxPaintDC dc(this);
if (!IsShown()) return;
SetCurrent(*context_);
Render();
SwapBuffers();
}
void SkipPartCanvas::OnSize(wxSizeEvent& event)
{
event.Skip();
}
void SkipPartCanvas::OnMouseLeftDown(wxMouseEvent& event)
{
DebugLogLine("OnMouseLeftDown");
if (!event.LeftIsDown()) {
event.Skip();
DebugLogLine("skip----OnMouseLeftDown");
return;
}
if (fixed_draging_)
StartDrag(wxPoint(event.GetX(), event.GetY()));
left_down_ = true;
}
void SkipPartCanvas::OnMouseLeftUp(wxMouseEvent& event)
{
DebugLogLine("OnMouseLeftUp");
if (event.LeftIsDown() || !left_down_) {
event.Skip();
DebugLogLine("skip----OnMouseLeftUp");
return;
}
auto id_at_mouse = GetIdAtViewPt(wxPoint(event.GetX(), event.GetY()));
auto part_state = parts_state_.find(id_at_mouse);
if (part_state != parts_state_.end() && part_state->second != psSkipped) {
if (part_state->second == psUnCheck)
part_state = parts_state_.insert_or_assign(part_state->first, psChecked).first;
else
part_state = parts_state_.insert_or_assign(part_state->first, psUnCheck).first;
// if (select_callback_)
// select_callback_(part_state->first, part_state->second);
SendSelectEvent(part_state->first, part_state->second);
}
left_down_ = false;
if (fixed_draging_)
EndDrag();
else {
Refresh();
}
}
void SkipPartCanvas::OnMouseRightDown(wxMouseEvent& event)
{
DebugLogLine("OnMouseRightDown");
if (!event.RightIsDown()) {
event.Skip();
DebugLogLine("skip----OnMouseRightDown");
return;
}
StartDrag(wxPoint(event.GetX(), event.GetY()));
}
void SkipPartCanvas::OnMouseRightUp(wxMouseEvent& event)
{
DebugLogLine("OnMouseRightUp");
if (event.RightIsDown() || !is_draging_) {
event.Skip();
DebugLogLine("skip----OnMouseRightUp");
return;
}
EndDrag();
}
void SkipPartCanvas::OnMouseMotion(wxMouseEvent& event)
{
ProcessHover(wxPoint(event.GetX(), event.GetY()));
if (!event.RightIsDown() && !(event.LeftIsDown() && fixed_draging_)) {
event.Skip();
return;
}
ProcessDrag(wxPoint(event.GetX(), event.GetY()));
}
void SkipPartCanvas::OnMouseWheel(wxMouseEvent& event)
{
wxPoint view_mouse = wxPoint(event.GetX(), event.GetY());
auto pre_image_pos = ViewPtToImagePt(view_mouse);
SetZoomPercent(zoom_percent_ + 10 * (event.GetWheelRotation() / 120.0));
auto now_image_pos = ViewPtToImagePt(view_mouse);
SetOffset(offset_ - (now_image_pos - pre_image_pos));
Refresh();
}
// Base class with error messages management
void _BBS_3MF_Base::add_error(const std::string &error) const
{
boost::unique_lock l(mutex);
m_errors.push_back(error);
}
void _BBS_3MF_Base::clear_errors() { m_errors.clear(); }
void _BBS_3MF_Base::log_errors()
{
for (const std::string &error : m_errors) BOOST_LOG_TRIVIAL(error) << error;
}
ModelSettingHelper::ModelSettingHelper(const std::string &path) : path_(path) {}
bool ModelSettingHelper::Parse()
{
boost::nowide::fstream fs(path_);
if (!fs) {
add_error("Failed to open file\n");
return false;
}
XML_Parser parser = XML_ParserCreate(nullptr);
if (!parser) {
add_error("Unable to create parser");
return false;
}
XML_SetUserData(parser, this);
XML_SetElementHandler(parser, ModelSettingHelper::StartElementHandler, ModelSettingHelper::EndElementHandler);
try {
char buffer[4000];
while (fs.read(buffer, sizeof(buffer)) || fs.gcount() > 0) {
auto ret = XML_Parse(parser, buffer, static_cast<int>(fs.gcount()), fs.eof());
if (ret != XML_STATUS_OK) {
add_error("return value of XML_Parse doesn't match XM_STATUS_OK");
XML_ParserFree(parser);
return false;
}
}
}
catch (std::exception& e) {
add_error(std::string("exception:") + e.what());
XML_ParserFree(parser);
return false;
}
XML_ParserFree(parser);
return true;
}
std::vector<ObjectInfo> ModelSettingHelper::GetResults() { return context_.objects; }
void XMLCALL ModelSettingHelper::StartElementHandler(void *userData, const XML_Char *name, const XML_Char **atts)
{
ModelSettingHelper* self = static_cast<ModelSettingHelper*>(userData);
if (strcmp(name, "object") == 0) {
for (int i = 0; atts[i]; i += 2) {
if (strcmp(atts[i], "id") == 0) {
self->context_.current_object_id = atoi(atts[i+1]);
}
}
} else if ((self->context_.current_object_id != -1)
&& (strcmp(name, "metadata") == 0)
&& (self->context_.node_name.top() == "object")) {
const XML_Char *key{nullptr}, *value{nullptr};
for (int i = 0; atts[i]; i += 2) {
if (strcmp(atts[i], "key") == 0) key = atts[i+1];
if (strcmp(atts[i], "value") == 0) value = atts[i+1];
}
if (key && value && (strcmp(key, "name") == 0)) {
self->context_.temp_object.id = self->context_.current_object_id;
self->context_.temp_object.name = std::string(value);
}
} else if ((strcmp(name, "metadata") == 0)
&& (self->context_.node_name.top() == "model_instance")) {
const XML_Char *key{nullptr}, *value{nullptr};
for (int i = 0; atts[i]; i += 2) {
if (strcmp(atts[i], "key") == 0) key = atts[i+1];
if (strcmp(atts[i], "value") == 0) value = atts[i+1];
}
if (key && value && (strcmp(key, "object_id") == 0)) {
self->context_.current_instance_object_id = atoi(value);
} else if (key && value && (strcmp(key, "identify_id") == 0)) {
self->context_.current_instance_identify_id = atoi(value);
}
}
self->context_.node_name.push(std::string(name));
}
void XMLCALL ModelSettingHelper::EndElementHandler(void *userData, const XML_Char *name)
{
ModelSettingHelper* self = static_cast<ModelSettingHelper*>(userData);
if (strcmp(name, "object") == 0) {
self->context_.objects.push_back(self->context_.temp_object);
self->context_.current_object_id = -1;
}
else if ((strcmp(name, "model_instance") == 0)
&& (self->context_.current_instance_identify_id != -1)
&& (self->context_.current_instance_object_id != -1)) {
for (auto& obj : self->context_.objects) {
if (obj.id == self->context_.current_instance_object_id) {
obj.identify_id = self->context_.current_instance_identify_id;
break;
}
}
self->context_.current_instance_identify_id = -1;
self->context_.current_instance_object_id = -1;
} else if (strcmp(name, "object") == 0) {
self->context_.current_object_id = -1;
}
self->context_.node_name.pop();
}
void ModelSettingHelper::DataHandler(const XML_Char *s, int len)
{
// do nothing
}
}
}

View File

@@ -0,0 +1,163 @@
#ifndef SKIPPARTCANVAS_H
#define SKIPPARTCANVAS_H
#include <wx/wx.h>
#include <wx/glcanvas.h>
#include <opencv2/opencv.hpp>
#include <wx/textctrl.h>
#include <vector>
#include <stack>
#include <expat.h>
#include <libslic3r/Color.hpp>
#include <boost/thread/mutex.hpp>
#include "PartSkipCommon.hpp"
wxDECLARE_EVENT(EVT_ZOOM_PERCENT, wxCommandEvent);
wxDECLARE_EVENT(EVT_CANVAS_PART, wxCommandEvent);
namespace Slic3r {
namespace GUI {
using Coord = float;
using FloatPoint = std::array<Coord, 2>;
struct ObjectInfo {
int id{-1};
std::string name{""};
int identify_id{-1};
PartState state{psUnCheck};
};
class SkipPartCanvas : public wxGLCanvas
{
union SkipIdHelper
{
uint32_t value = 0;
struct
{
uint8_t b;
uint8_t g;
uint8_t r;
uint8_t _padding;
};
uint8_t bytes[4];
SkipIdHelper() = default;
SkipIdHelper(uint8_t red, uint8_t green, uint8_t blue)
: r(red), g(green), b(blue), _padding(0) {}
SkipIdHelper(uint32_t val): value(val){}
void reverse() {
uint8_t tmp{r};
r = b;
b = tmp;
}
};
public:
SkipPartCanvas(wxWindow *parent, const wxGLAttributes& dispAttrs);
~SkipPartCanvas() = default;
void SetParentBackground(const ColorRGB& color) {
parent_color_ = color;
}
void LoadPickImage(const std::string& path);
void ZoomIn(const int zoom_percent);
void ZoomOut(const int zoom_percent);
void SwitchDrag(const bool drag_on);
void UpdatePartsInfo(const PartsInfo& parts);
void SetZoomPercent(const int value);
void SetOffset(const wxPoint &value);
wxTextCtrl* log_ctrl;
protected:
void OnPaint(wxPaintEvent& event);
void OnSize(wxSizeEvent& event);
void OnMouseLeftDown(wxMouseEvent& event);
void OnMouseLeftUp(wxMouseEvent& event);
void OnMouseRightDown(wxMouseEvent& event);
void OnMouseRightUp(wxMouseEvent& event);
void OnMouseMotion(wxMouseEvent& event);
void OnMouseWheel(wxMouseEvent& event);
private:
wxGLContext* context_;
cv::Mat pick_image_;
std::unordered_map<uint32_t, std::vector<FloatPoint>> parts_triangles_;
std::unordered_map<uint32_t, std::vector<cv::Point>> pick_parts_;
std::unordered_map<uint32_t, PartState> parts_state_;
bool gl_inited_{false};
int zoom_percent_{100};
wxPoint offset_{0,0};
wxPoint drag_start_offset_{0,0};
wxPoint drag_start_pt_{0,0};
bool is_draging_{false};
bool fixed_draging_{false};
bool left_down_{false};
ColorRGB parent_color_ = ColorRGB();
int hover_id_{-1};
void SendSelectEvent(int id, PartState state);
void SendZoomEvent(int zoom_percent);
inline double Zoom() const;
inline wxPoint ViewPtToImagePt(const wxPoint& view_pt) const;
uint32_t GetIdAtImagePt(const wxPoint& image_pt) const;
inline uint32_t GetIdAtViewPt(const wxPoint& view_pt) const;
void ProcessHover(const wxPoint& mouse_pt);
void AutoSetCursor();
void StartDrag(const wxPoint& mouse_pt);
void ProcessDrag(const wxPoint& mouse_pt);
void EndDrag();
void Render();
void DebugLogLine(std::string str);
};
class _BBS_3MF_Base
{
mutable boost::mutex mutex;
mutable std::vector<std::string> m_errors;
protected:
void add_error(const std::string& error) const;
void clear_errors();
public:
void log_errors();
};
class ModelSettingHelper : public _BBS_3MF_Base {
struct ParseContext{
std::vector<ObjectInfo> objects;
ObjectInfo temp_object;
std::stack<std::string> node_name;
int current_object_id{-1};
int current_instance_identify_id{-1};
int current_instance_object_id{-1};
};
public:
ModelSettingHelper(const std::string& path);
bool Parse();
std::vector<ObjectInfo> GetResults();
private:
std::string path_;
ParseContext context_;
void static XMLCALL StartElementHandler(void* userData, const XML_Char* name, const XML_Char** atts);
void static XMLCALL EndElementHandler(void* userData, const XML_Char* name);
void DataHandler(const XML_Char* s, int len);
};
}
}
#endif //SKIPPARTCANVAS_H

View File

@@ -278,6 +278,20 @@ void PrintingTaskPanel::create_panel(wxWindow* parent)
bSizer_task_btn->Add(FromDIP(10), 0, 0);
StateColor white_bg(std::pair<wxColour, int>(wxColour(255, 255, 255), StateColor::Disabled), std::pair<wxColour, int>(wxColour(255, 255, 255), StateColor::Pressed),
std::pair<wxColour, int>(wxColour(255, 255, 255), StateColor::Hovered), std::pair<wxColour, int>(wxColour(255, 255, 255), StateColor::Enabled),
std::pair<wxColour, int>(wxColour(255, 255, 255), StateColor::Normal));
m_button_partskip = new Button(m_panel_progress, "");
m_button_partskip->Enable(false);
m_button_partskip->SetBackgroundColor(white_bg);
m_button_partskip->SetIcon("print_control_partskip_disable");
m_button_partskip->SetBorderColor(*wxWHITE);
m_button_partskip->SetFont(Label::Body_12);
m_button_partskip->SetCornerRadius(0);
m_button_partskip->SetToolTip(_L("Parts Skip"));
m_button_partskip->Bind(wxEVT_ENTER_WINDOW, [this](auto &e) { m_button_partskip->SetIcon("print_control_partskip"); });
m_button_partskip->Bind(wxEVT_LEAVE_WINDOW, [this](auto &e) { m_button_partskip->SetIcon("print_control_partskip"); });
m_button_pause_resume = new ScalableButton(m_panel_progress, wxID_ANY, "print_control_pause", wxEmptyString, wxDefaultSize, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER,true);
m_button_pause_resume->Bind(wxEVT_ENTER_WINDOW, [this](auto &e) {
@@ -314,6 +328,8 @@ void PrintingTaskPanel::create_panel(wxWindow* parent)
m_sizer_progressbar->Add(m_gauge_progress, 1, wxALIGN_CENTER_VERTICAL, 0);
m_sizer_progressbar->Add(0, 0, 0, wxEXPAND|wxLEFT, FromDIP(18));
m_sizer_progressbar->Add(m_button_partskip, 0, wxALL, FromDIP(5));
m_sizer_progressbar->Add(0, 0, 0, wxEXPAND | wxLEFT, FromDIP(18));
m_sizer_progressbar->Add(m_button_pause_resume, 0, wxALL, FromDIP(5));
m_sizer_progressbar->Add(0, 0, 0, wxEXPAND|wxLEFT, FromDIP(18));
m_sizer_progressbar->Add(m_button_abort, 0, wxALL, FromDIP(5));
@@ -638,6 +654,22 @@ void PrintingTaskPanel::reset_printing_value()
this->set_plate_index(-1);
}
void PrintingTaskPanel::update_machine_object(MachineObject* obj){
if(obj) m_obj = obj;
}
void PrintingTaskPanel::enable_partskip_button(bool enable)
{
if (!enable) {
m_button_partskip->Enable(false);
m_button_partskip->SetLabel("");
m_button_partskip->SetIcon("print_control_partskip_disable");
}else if(m_obj && m_obj->is_support_brtc){
m_button_partskip->Enable(true);
m_button_partskip->SetIcon("print_control_partskip");
}
}
void PrintingTaskPanel::enable_pause_resume_button(bool enable, std::string type)
{
if (!enable) {
@@ -1731,6 +1763,7 @@ StatusPanel::StatusPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos, co
m_switch_cham_fan->SetValue(false);
/* set default enable state */
m_project_task_panel->enable_partskip_button(false);
m_project_task_panel->enable_pause_resume_button(false, "resume_disable");
m_project_task_panel->enable_abort_button(false);
@@ -1751,6 +1784,7 @@ StatusPanel::StatusPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos, co
// Connect Events
m_project_task_panel->get_bitmap_thumbnail()->Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(StatusPanel::refresh_thumbnail_webrequest), NULL, this);
m_project_task_panel->get_partskip_button()->Connect(wxEVT_LEFT_DOWN, wxCommandEventHandler(StatusPanel::on_subtask_partskip), NULL, this);
m_project_task_panel->get_pause_resume_button()->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_subtask_pause_resume), NULL, this);
m_project_task_panel->get_abort_button()->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_subtask_abort), NULL, this);
m_project_task_panel->get_market_scoring_button()->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_market_scoring), NULL, this);
@@ -1810,6 +1844,7 @@ StatusPanel::~StatusPanel()
{
// Disconnect Events
m_project_task_panel->get_bitmap_thumbnail()->Disconnect(wxEVT_LEFT_DOWN, wxMouseEventHandler(StatusPanel::refresh_thumbnail_webrequest), NULL, this);
m_project_task_panel->get_partskip_button()->Disconnect(wxEVT_LEFT_DOWN, wxCommandEventHandler(StatusPanel::on_subtask_partskip), NULL, this);
m_project_task_panel->get_pause_resume_button()->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_subtask_pause_resume), NULL, this);
m_project_task_panel->get_abort_button()->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_subtask_abort), NULL, this);
m_project_task_panel->get_market_scoring_button()->Disconnect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(StatusPanel::on_market_scoring), NULL, this);
@@ -1940,6 +1975,17 @@ void StatusPanel::on_market_retry(wxCommandEvent &event)
}
}
void StatusPanel::on_subtask_partskip(wxCommandEvent &event)
{
if (m_partskip_dlg == nullptr) {
m_partskip_dlg = new PartSkipDialog(this->GetParent());
}
auto dm = GUI::wxGetApp().getDeviceManager();
m_partskip_dlg->InitSchedule(dm->get_selected_machine());
m_partskip_dlg->ShowModal();
}
void StatusPanel::on_subtask_pause_resume(wxCommandEvent &event)
{
if (obj) {
@@ -3089,9 +3135,12 @@ void StatusPanel::update_subtask(MachineObject *obj)
|| obj->is_in_calibration()) {
reset_printing_values();
} else if (obj->is_in_printing() || obj->print_status == "FINISH") {
update_partskip_subtask(obj);
if (obj->is_in_prepare() || obj->print_status == "SLICING") {
m_project_task_panel->market_scoring_hide();
m_project_task_panel->get_request_failed_panel()->Hide();
m_project_task_panel->enable_partskip_button(false);
m_project_task_panel->enable_abort_button(false);
m_project_task_panel->enable_pause_resume_button(false, "pause_disable");
wxString prepare_text;
@@ -3133,7 +3182,7 @@ void StatusPanel::update_subtask(MachineObject *obj)
} else {
m_project_task_panel->enable_pause_resume_button(true, "pause");
}
m_project_task_panel->enable_partskip_button(true);
// update printing stage
m_project_task_panel->update_left_time(obj->mc_left_time);
if (obj->subtask_) {
@@ -3150,6 +3199,7 @@ void StatusPanel::update_subtask(MachineObject *obj)
if (obj->is_printing_finished()) {
obj->update_model_task();
m_project_task_panel->enable_abort_button(false);
m_project_task_panel->enable_partskip_button(false);
m_project_task_panel->enable_pause_resume_button(false, "resume_disable");
// is makeworld subtask
if (wxGetApp().has_model_mall() && obj->is_makeworld_subtask()) {
@@ -3217,6 +3267,26 @@ void StatusPanel::update_subtask(MachineObject *obj)
Layout();
}
void StatusPanel::update_partskip_subtask(MachineObject *obj){
if (!obj) return;
if (!obj->subtask_) return;
m_project_task_panel->update_machine_object(obj);
auto partskip_button = m_project_task_panel->get_partskip_button();
if (partskip_button) {
int part_cnt = obj->m_partskip_ids.size();
if (part_cnt > 0)
partskip_button->SetLabel(wxString::Format(_L("(%d)"), part_cnt));
else
partskip_button->SetLabel("");
}
if(m_partskip_dlg && m_partskip_dlg->IsShown()) {
m_partskip_dlg->UpdatePartsStateFromPrinter(obj);
}
}
void StatusPanel::update_cloud_subtask(MachineObject *obj)
{
if (!obj) return;
@@ -3281,6 +3351,7 @@ void StatusPanel::update_sdcard_subtask(MachineObject *obj)
void StatusPanel::reset_printing_values()
{
m_project_task_panel->enable_partskip_button(false);
m_project_task_panel->enable_pause_resume_button(false, "pause_disable");
m_project_task_panel->enable_abort_button(false);
m_project_task_panel->reset_printing_value();

View File

@@ -32,6 +32,7 @@
#include "Widgets/AMSControl.hpp"
#include "Widgets/FanControl.hpp"
#include "HMS.hpp"
#include "PartSkipDialog.hpp"
class StepIndicator;
@@ -160,7 +161,7 @@ public:
private:
MachineObject* m_obj;
MachineObject* m_obj{nullptr};
ScalableBitmap m_thumbnail_placeholder;
wxBitmap m_thumbnail_bmp_display;
ScalableBitmap m_bitmap_use_time;
@@ -192,6 +193,7 @@ private:
wxStaticBitmap* m_bitmap_static_use_weight;
ScalableButton* m_button_pause_resume;
ScalableButton* m_button_abort;
Button* m_button_partskip;
Button* m_button_market_scoring;
Button* m_button_clean;
Button * m_button_market_retry;
@@ -217,6 +219,7 @@ public:
void msw_rescale();
public:
void enable_partskip_button(bool enable);
void enable_pause_resume_button(bool enable, std::string type);
void enable_abort_button(bool enable);
void update_subtask_name(wxString name);
@@ -232,10 +235,12 @@ public:
void set_plate_index(int plate_idx = -1);
void market_scoring_show();
void market_scoring_hide();
void update_machine_object(MachineObject* obj);
public:
ScalableButton* get_abort_button() {return m_button_abort;};
ScalableButton* get_pause_resume_button() {return m_button_pause_resume;};
Button* get_partskip_button() { return m_button_partskip; };
Button* get_market_scoring_button() {return m_button_market_scoring;};
Button * get_market_retry_buttom() { return m_button_market_retry; };
Button* get_clean_button() {return m_button_clean;};
@@ -328,6 +333,7 @@ protected:
wxStaticText * m_staticText_progress_left;
wxStaticText * m_staticText_layers;
Button * m_button_report;
Button * m_button_partskip;
ScalableButton *m_button_pause_resume;
ScalableButton *m_button_abort;
Button * m_button_clean;
@@ -402,6 +408,7 @@ protected:
PrintingTaskPanel * m_project_task_panel;
// Virtual event handlers, override them in your derived class
virtual void on_subtask_partskip(wxCommandEvent &event) { event.Skip(); }
virtual void on_subtask_pause_resume(wxCommandEvent &event) { event.Skip(); }
virtual void on_subtask_abort(wxCommandEvent &event) { event.Skip(); }
virtual void on_lamp_switch(wxCommandEvent &event) { event.Skip(); }
@@ -484,6 +491,7 @@ protected:
FanControlPopup* m_fan_control_popup{nullptr};
ExtrusionCalibration *m_extrusion_cali_dlg{nullptr};
PartSkipDialog *m_partskip_dlg{nullptr};
wxString m_request_url;
bool m_start_loading_thumbnail = false;
@@ -525,6 +533,7 @@ protected:
void on_market_scoring(wxCommandEvent &event);
void on_market_retry(wxCommandEvent &event);
void on_subtask_partskip(wxCommandEvent &event);
void on_subtask_pause_resume(wxCommandEvent &event);
void on_subtask_abort(wxCommandEvent &event);
void on_print_error_clean(wxCommandEvent &event);
@@ -602,6 +611,7 @@ protected:
void update_basic_print_data(bool def = false);
void update_model_info();
void update_subtask(MachineObject* obj);
void update_partskip_subtask(MachineObject *obj);
void update_cloud_subtask(MachineObject *obj);
void update_sdcard_subtask(MachineObject *obj);
void update_temp_ctrl(MachineObject *obj);