ENH: Open Prinables.com Links and Zip Archives (#3823)

* Enable ability to open `prusaslicer://` links

* Add needed function to miniz

* Import Zip Functionality

Allows zip file to be drag and dropped or imported via the menu option

Based on prusa3d/PrusaSlicer@ce38e57 and current master branch files

* Update dialog style to match Orca

* Ensure link is from printables

* add toggle option in preferences

doesn't actually control anything yet

* Add Downloader classes

As-is from PS master

* Create Orca Styled Variant of Icons

* Add Icons to ImGui

* Use PS's Downloader impl for `prusaslicer://` links

* Implement URL Registering on Windows

* Implement prusaslicer:// link on macOS

* Remove unnecessary class name qualifier in Plater.hpp

* Add downloader desktop integration registration and undo

* Revert Info.plist

---------

Co-authored-by: SoftFever <softfeverever@gmail.com>
This commit is contained in:
Ocraftyone 2024-05-21 22:52:34 -04:00 committed by GitHub
parent 0dbf610226
commit a764d836e1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
39 changed files with 3109 additions and 41 deletions

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.0"
id="close_window"
x="0px"
y="0px"
viewBox="0 0 100 100"
enable-background="new 0 0 100 100"
xml:space="preserve"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"><metadata
id="metadata19"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
id="defs17" />
<g
id="open"
transform="matrix(4.05,0,0,4.05,15.4645,15.277381)"
style="fill:#323a3e;fill-opacity:1"><circle
style="fill:#eeeeee;fill-opacity:1;stroke-width:0.969441"
id="path1"
cx="50"
cy="50"
r="40.299999"
transform="matrix(0.24691358,0,0,0.24691358,-3.8183951,-3.7721928)" /><g
id="folder"><path
id="path2-7"
d="M 1.9606173,14 V 3 c 0,0 0,-1 1,-1 0.9999999,0 3.9999998,0 4.9999998,0 1,0 1,2 1.9999999,2 0.99999,0 3.99999,0 3.99999,0 0,0 1,0 1,1 v 2 h -1 c 0,0 0,0 0,-1 0,-1 -1,-1 -1,-1 H 9.460617 C 8.4606171,5 8.4606171,3 7.4606171,3 c -1,0 -3.4999999,0 -3.4999999,0 C 2.9606173,3 2.9606173,4 2.9606173,4 v 9 1 h 0.9999999 v 1 c 0,0 0,0 -0.9999999,0 -1,0 -1,-1 -1,-1 z"
fill="#808080"
style="fill:#acacac;fill-opacity:1" /><path
id="path4"
d="m 5.7406172,6 c -0.55,0 -1.14,0.43 -1.32,0.95 l -2.3699999,7.1 c -0.17,0.52 0.14,0.95 0.69,0.95 h 9.9999897 c 0.55,0 1.14,-0.43 1.32,-0.95 l 2.37,-7.1 c 0.17,-0.52 -0.14,-0.95 -0.69,-0.95 z"
fill="#323a3e"
style="fill:#acacac;fill-opacity:1" /></g></g></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.0"
id="close_window"
x="0px"
y="0px"
viewBox="0 0 100 100"
enable-background="new 0 0 100 100"
xml:space="preserve"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"><metadata
id="metadata19"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
id="defs17" />
<g
id="open"
transform="matrix(4.05,0,0,4.05,15.4645,15.277381)"
style="fill:#323a3e;fill-opacity:1"><circle
style="fill:#3e3e45;fill-opacity:1;stroke-width:0.969441"
id="path1"
cx="50"
cy="50"
r="40.299999"
transform="matrix(0.24691358,0,0,0.24691358,-3.8183951,-3.7721928)" /><g
id="folder"><path
id="path2-7"
d="M 1.9606173,14 V 3 c 0,0 0,-1 1,-1 0.9999999,0 3.9999998,0 4.9999998,0 1,0 1,2 1.9999999,2 0.99999,0 3.99999,0 3.99999,0 0,0 1,0 1,1 v 2 h -1 c 0,0 0,0 0,-1 0,-1 -1,-1 -1,-1 H 9.460617 C 8.4606171,5 8.4606171,3 7.4606171,3 c -1,0 -3.4999999,0 -3.4999999,0 C 2.9606173,3 2.9606173,4 2.9606173,4 v 9 1 h 0.9999999 v 1 c 0,0 0,0 -0.9999999,0 -1,0 -1,-1 -1,-1 z"
fill="#808080"
style="fill:#818183;fill-opacity:1" /><path
id="path4"
d="m 5.7406172,6 c -0.55,0 -1.14,0.43 -1.32,0.95 l -2.3699999,7.1 c -0.17,0.52 0.14,0.95 0.69,0.95 h 9.9999897 c 0.55,0 1.14,-0.43 1.32,-0.95 l 2.37,-7.1 c 0.17,-0.52 -0.14,-0.95 -0.69,-0.95 z"
fill="#323a3e"
style="fill:#818183;fill-opacity:1" /></g></g></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.0"
id="close_window"
x="0px"
y="0px"
viewBox="0 0 100 100"
enable-background="new 0 0 100 100"
xml:space="preserve"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"><metadata
id="metadata19"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
id="defs17" />
<g
id="open"
transform="matrix(4.7936726,0,0,4.7936726,9.1229931,8.9015147)"
style="fill:#323a3e;fill-opacity:1"><circle
style="fill:#eeeeee;fill-opacity:1;stroke-width:0.969441"
id="path1"
cx="50"
cy="50"
r="40.299999"
transform="matrix(0.24691358,0,0,0.24691358,-3.8183951,-3.7721928)" /><g
id="folder"><path
id="path2-7"
d="M 1.9606173,14 V 3 c 0,0 0,-1 1,-1 0.9999999,0 3.9999998,0 4.9999998,0 1,0 1,2 1.9999999,2 0.99999,0 3.99999,0 3.99999,0 0,0 1,0 1,1 v 2 h -1 c 0,0 0,0 0,-1 0,-1 -1,-1 -1,-1 H 9.460617 C 8.4606171,5 8.4606171,3 7.4606171,3 c -1,0 -3.4999999,0 -3.4999999,0 C 2.9606173,3 2.9606173,4 2.9606173,4 v 9 1 h 0.9999999 v 1 c 0,0 0,0 -0.9999999,0 -1,0 -1,-1 -1,-1 z"
fill="#808080"
style="fill:#acacac;fill-opacity:1" /><path
id="path4"
d="m 5.7406172,6 c -0.55,0 -1.14,0.43 -1.32,0.95 l -2.3699999,7.1 c -0.17,0.52 0.14,0.95 0.69,0.95 h 9.9999897 c 0.55,0 1.14,-0.43 1.32,-0.95 l 2.37,-7.1 c 0.17,-0.52 -0.14,-0.95 -0.69,-0.95 z"
fill="#323a3e"
style="fill:#acacac;fill-opacity:1" /></g></g></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
version="1.0"
id="close_window"
x="0px"
y="0px"
viewBox="0 0 100 100"
enable-background="new 0 0 100 100"
xml:space="preserve"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"><metadata
id="metadata19"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
id="defs17" />
<g
id="open"
transform="matrix(4.7936726,0,0,4.7936726,9.1229931,8.9015147)"
style="fill:#323a3e;fill-opacity:1"><circle
style="fill:#3e3e45;fill-opacity:1;stroke-width:0.969441"
id="path1"
cx="50"
cy="50"
r="40.299999"
transform="matrix(0.24691358,0,0,0.24691358,-3.8183951,-3.7721928)" /><g
id="folder"><path
id="path2-7"
d="M 1.9606173,14 V 3 c 0,0 0,-1 1,-1 0.9999999,0 3.9999998,0 4.9999998,0 1,0 1,2 1.9999999,2 0.99999,0 3.99999,0 3.99999,0 0,0 1,0 1,1 v 2 h -1 c 0,0 0,0 0,-1 0,-1 -1,-1 -1,-1 H 9.460617 C 8.4606171,5 8.4606171,3 7.4606171,3 c -1,0 -3.4999999,0 -3.4999999,0 C 2.9606173,3 2.9606173,4 2.9606173,4 v 9 1 h 0.9999999 v 1 c 0,0 0,0 -0.9999999,0 -1,0 -1,-1 -1,-1 z"
fill="#808080"
style="fill:#818183;fill-opacity:1" /><path
id="path4"
d="m 5.7406172,6 c -0.55,0 -1.14,0.43 -1.32,0.95 l -2.3699999,7.1 c -0.17,0.52 0.14,0.95 0.69,0.95 h 9.9999897 c 0.55,0 1.14,-0.43 1.32,-0.95 l 2.37,-7.1 c 0.17,-0.52 -0.14,-0.95 -0.69,-0.95 z"
fill="#323a3e"
style="fill:#818183;fill-opacity:1" /></g></g></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xml:space="preserve"
viewBox="0 0 800 800"
y="0px"
x="0px"
id="Layer_1"
version="1.1"
style="enable-background:new 0 0 800 800;"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"><metadata
id="metadata15"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
id="defs13" />
<style
id="style2"
type="text/css">
.st0{fill:#323a3e;}
</style>
<circle
style="fill:#eeeeee;fill-opacity:1;stroke-width:7.75552"
id="path1"
cx="400"
cy="400"
r="322.39999" /><path
id="path4"
d="m 317.95173,562.36128 h -60.775 v -330.565 h 60.775 v 330.565 m 48.195,27.88 v -386.24 c 0,-11.22 -9.095,-20.315 -20.315,-20.315 h -116.535 c -11.22,0 -20.315,9.095 -20.315,20.315 v 386.24 c 0,11.22 9.095,20.315 20.315,20.315 h 116.45 c 11.22,0 20.4,-9.095 20.4,-20.315 z"
class="st0"
style="stroke-width:0.85;fill:#acacac;fill-opacity:1" />
<path
id="path21"
d="m 210.06357,199.0892 c 1.10499,-4.08272 3.30912,-7.53117 6.63124,-10.37477 5.42019,-4.63948 2.10678,-4.43387 71.33297,-4.42657 l 62.44927,0.007 3.44194,1.60268 c 4.05635,1.88877 7.75734,5.3977 9.8769,9.36436 l 1.52243,2.84915 v 199.00794 199.00793 l -1.52243,2.84915 c -2.11956,3.96665 -5.82055,7.47559 -9.8769,9.36436 l -3.44194,1.60267 -62.44927,0.007 c -69.78764,0.008 -65.98231,0.26152 -71.72146,-4.79028 -1.69319,-1.4904 -3.87229,-4.37627 -4.9283,-6.52672 l -1.89304,-3.85513 -0.12602,-196.30953 c -0.0987,-153.67069 0.0544,-196.97613 0.70461,-199.37859 z m 77.67539,363.64446 H 318.4334 V 397.11858 231.5035 H 287.73896 257.0445 v 165.61508 165.61508 z"
style="fill:#acacac;fill-opacity:1;stroke-width:0.674603" /><path
style="fill:#acacac;stroke-width:0.85;fill-opacity:1"
class="st0"
d="m 533.78507,563.71023 h -60.775 v -330.565 h 60.775 v 330.565 m 48.195,27.88 v -386.24 c 0,-11.22 -9.095,-20.315 -20.315,-20.315 h -116.535 c -11.22,0 -20.315,9.095 -20.315,20.315 v 386.24 c 0,11.22 9.095,20.315 20.315,20.315 h 116.45 c 11.22,0 20.4,-9.095 20.4,-20.315 z"
id="path4-3" /><path
style="fill:#acacac;fill-opacity:1;stroke-width:0.674603"
d="m 425.89691,200.43815 c 1.10499,-4.08272 3.30912,-7.53117 6.63124,-10.37477 5.42019,-4.63948 2.10678,-4.43387 71.33297,-4.42657 l 62.44927,0.007 3.44194,1.60268 c 4.05635,1.88877 7.75734,5.3977 9.8769,9.36436 l 1.52243,2.84915 v 199.00794 199.00793 l -1.52243,2.84915 c -2.11956,3.96665 -5.82055,7.47559 -9.8769,9.36436 l -3.44194,1.60267 -62.44927,0.007 c -69.78764,0.008 -65.98231,0.26152 -71.72146,-4.79028 -1.69319,-1.4904 -3.87229,-4.37627 -4.9283,-6.52672 l -1.89304,-3.85513 -0.12602,-196.30953 c -0.0987,-153.67069 0.0544,-196.97613 0.70461,-199.37859 z m 77.67539,363.64446 h 30.69444 V 398.46753 232.85245 H 503.5723 472.87784 v 165.61508 165.61508 z"
id="path21-9" /></svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xml:space="preserve"
viewBox="0 0 800 800"
y="0px"
x="0px"
id="Layer_1"
version="1.1"
style="enable-background:new 0 0 800 800;"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"><metadata
id="metadata15"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
id="defs13" />
<style
id="style2"
type="text/css">
.st0{fill:#323a3e;}
</style>
<circle
style="fill:#3e3e45;fill-opacity:1;stroke-width:7.75552"
id="path1"
cx="400"
cy="400"
r="322.39999" /><path
id="path4"
d="m 317.95173,562.36128 h -60.775 v -330.565 h 60.775 v 330.565 m 48.195,27.88 v -386.24 c 0,-11.22 -9.095,-20.315 -20.315,-20.315 h -116.535 c -11.22,0 -20.315,9.095 -20.315,20.315 v 386.24 c 0,11.22 9.095,20.315 20.315,20.315 h 116.45 c 11.22,0 20.4,-9.095 20.4,-20.315 z"
class="st0"
style="stroke-width:0.85;fill:#818183;fill-opacity:1" />
<path
id="path21"
d="m 210.06357,199.0892 c 1.10499,-4.08272 3.30912,-7.53117 6.63124,-10.37477 5.42019,-4.63948 2.10678,-4.43387 71.33297,-4.42657 l 62.44927,0.007 3.44194,1.60268 c 4.05635,1.88877 7.75734,5.3977 9.8769,9.36436 l 1.52243,2.84915 v 199.00794 199.00793 l -1.52243,2.84915 c -2.11956,3.96665 -5.82055,7.47559 -9.8769,9.36436 l -3.44194,1.60267 -62.44927,0.007 c -69.78764,0.008 -65.98231,0.26152 -71.72146,-4.79028 -1.69319,-1.4904 -3.87229,-4.37627 -4.9283,-6.52672 l -1.89304,-3.85513 -0.12602,-196.30953 c -0.0987,-153.67069 0.0544,-196.97613 0.70461,-199.37859 z m 77.67539,363.64446 H 318.4334 V 397.11858 231.5035 H 287.73896 257.0445 v 165.61508 165.61508 z"
style="fill:#818183;fill-opacity:1;stroke-width:0.674603" /><path
style="fill:#818183;stroke-width:0.85;fill-opacity:1"
class="st0"
d="m 533.78507,563.71023 h -60.775 v -330.565 h 60.775 v 330.565 m 48.195,27.88 v -386.24 c 0,-11.22 -9.095,-20.315 -20.315,-20.315 h -116.535 c -11.22,0 -20.315,9.095 -20.315,20.315 v 386.24 c 0,11.22 9.095,20.315 20.315,20.315 h 116.45 c 11.22,0 20.4,-9.095 20.4,-20.315 z"
id="path4-3" /><path
style="fill:#818183;fill-opacity:1;stroke-width:0.674603"
d="m 425.89691,200.43815 c 1.10499,-4.08272 3.30912,-7.53117 6.63124,-10.37477 5.42019,-4.63948 2.10678,-4.43387 71.33297,-4.42657 l 62.44927,0.007 3.44194,1.60268 c 4.05635,1.88877 7.75734,5.3977 9.8769,9.36436 l 1.52243,2.84915 v 199.00794 199.00793 l -1.52243,2.84915 c -2.11956,3.96665 -5.82055,7.47559 -9.8769,9.36436 l -3.44194,1.60267 -62.44927,0.007 c -69.78764,0.008 -65.98231,0.26152 -71.72146,-4.79028 -1.69319,-1.4904 -3.87229,-4.37627 -4.9283,-6.52672 l -1.89304,-3.85513 -0.12602,-196.30953 c -0.0987,-153.67069 0.0544,-196.97613 0.70461,-199.37859 z m 77.67539,363.64446 h 30.69444 V 398.46753 232.85245 H 503.5723 472.87784 v 165.61508 165.61508 z"
id="path21-9" /></svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xml:space="preserve"
viewBox="0 0 800 800"
y="0px"
x="0px"
id="Layer_1"
version="1.1"
style="enable-background:new 0 0 800 800;"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"><metadata
id="metadata15"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
id="defs13" />
<style
id="style2"
type="text/css">
.st0{fill:#323a3e;}
</style>
<g
id="g1"
transform="matrix(1.1836229,0,0,1.1836229,-73.44914,-73.44914)"><circle
style="fill:#eeeeee;fill-opacity:1;stroke-width:7.75552"
id="path1"
cx="400"
cy="400"
r="322.39999" /><path
id="path4"
d="m 317.95173,562.36128 h -60.775 v -330.565 h 60.775 v 330.565 m 48.195,27.88 v -386.24 c 0,-11.22 -9.095,-20.315 -20.315,-20.315 h -116.535 c -11.22,0 -20.315,9.095 -20.315,20.315 v 386.24 c 0,11.22 9.095,20.315 20.315,20.315 h 116.45 c 11.22,0 20.4,-9.095 20.4,-20.315 z"
class="st0"
style="fill:#acacac;fill-opacity:1;stroke-width:0.85" /><path
id="path21"
d="m 210.06357,199.0892 c 1.10499,-4.08272 3.30912,-7.53117 6.63124,-10.37477 5.42019,-4.63948 2.10678,-4.43387 71.33297,-4.42657 l 62.44927,0.007 3.44194,1.60268 c 4.05635,1.88877 7.75734,5.3977 9.8769,9.36436 l 1.52243,2.84915 v 199.00794 199.00793 l -1.52243,2.84915 c -2.11956,3.96665 -5.82055,7.47559 -9.8769,9.36436 l -3.44194,1.60267 -62.44927,0.007 c -69.78764,0.008 -65.98231,0.26152 -71.72146,-4.79028 -1.69319,-1.4904 -3.87229,-4.37627 -4.9283,-6.52672 l -1.89304,-3.85513 -0.12602,-196.30953 c -0.0987,-153.67069 0.0544,-196.97613 0.70461,-199.37859 z m 77.67539,363.64446 H 318.4334 V 397.11858 231.5035 H 287.73896 257.0445 v 165.61508 165.61508 z"
style="fill:#acacac;fill-opacity:1;stroke-width:0.674603" /><path
style="fill:#acacac;fill-opacity:1;stroke-width:0.85"
class="st0"
d="m 533.78507,563.71023 h -60.775 v -330.565 h 60.775 v 330.565 m 48.195,27.88 v -386.24 c 0,-11.22 -9.095,-20.315 -20.315,-20.315 h -116.535 c -11.22,0 -20.315,9.095 -20.315,20.315 v 386.24 c 0,11.22 9.095,20.315 20.315,20.315 h 116.45 c 11.22,0 20.4,-9.095 20.4,-20.315 z"
id="path4-3" /><path
style="fill:#acacac;fill-opacity:1;stroke-width:0.674603"
d="m 425.89691,200.43815 c 1.10499,-4.08272 3.30912,-7.53117 6.63124,-10.37477 5.42019,-4.63948 2.10678,-4.43387 71.33297,-4.42657 l 62.44927,0.007 3.44194,1.60268 c 4.05635,1.88877 7.75734,5.3977 9.8769,9.36436 l 1.52243,2.84915 v 199.00794 199.00793 l -1.52243,2.84915 c -2.11956,3.96665 -5.82055,7.47559 -9.8769,9.36436 l -3.44194,1.60267 -62.44927,0.007 c -69.78764,0.008 -65.98231,0.26152 -71.72146,-4.79028 -1.69319,-1.4904 -3.87229,-4.37627 -4.9283,-6.52672 l -1.89304,-3.85513 -0.12602,-196.30953 c -0.0987,-153.67069 0.0544,-196.97613 0.70461,-199.37859 z m 77.67539,363.64446 h 30.69444 V 398.46753 232.85245 H 503.5723 472.87784 v 165.61508 165.61508 z"
id="path21-9" /></g></svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xml:space="preserve"
viewBox="0 0 800 800"
y="0px"
x="0px"
id="Layer_1"
version="1.1"
style="enable-background:new 0 0 800 800;"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"><metadata
id="metadata15"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
id="defs13" />
<style
id="style2"
type="text/css">
.st0{fill:#323a3e;}
</style>
<g
id="g1"
transform="matrix(1.1836229,0,0,1.1836229,-73.44914,-73.44914)"><circle
style="fill:#3e3e45;fill-opacity:1;stroke-width:7.75552"
id="path1"
cx="400"
cy="400"
r="322.39999" /><path
id="path4"
d="m 317.95173,562.36128 h -60.775 v -330.565 h 60.775 v 330.565 m 48.195,27.88 v -386.24 c 0,-11.22 -9.095,-20.315 -20.315,-20.315 h -116.535 c -11.22,0 -20.315,9.095 -20.315,20.315 v 386.24 c 0,11.22 9.095,20.315 20.315,20.315 h 116.45 c 11.22,0 20.4,-9.095 20.4,-20.315 z"
class="st0"
style="fill:#818183;fill-opacity:1;stroke-width:0.85" /><path
id="path21"
d="m 210.06357,199.0892 c 1.10499,-4.08272 3.30912,-7.53117 6.63124,-10.37477 5.42019,-4.63948 2.10678,-4.43387 71.33297,-4.42657 l 62.44927,0.007 3.44194,1.60268 c 4.05635,1.88877 7.75734,5.3977 9.8769,9.36436 l 1.52243,2.84915 v 199.00794 199.00793 l -1.52243,2.84915 c -2.11956,3.96665 -5.82055,7.47559 -9.8769,9.36436 l -3.44194,1.60267 -62.44927,0.007 c -69.78764,0.008 -65.98231,0.26152 -71.72146,-4.79028 -1.69319,-1.4904 -3.87229,-4.37627 -4.9283,-6.52672 l -1.89304,-3.85513 -0.12602,-196.30953 c -0.0987,-153.67069 0.0544,-196.97613 0.70461,-199.37859 z m 77.67539,363.64446 H 318.4334 V 397.11858 231.5035 H 287.73896 257.0445 v 165.61508 165.61508 z"
style="fill:#818183;fill-opacity:1;stroke-width:0.674603" /><path
style="fill:#818183;fill-opacity:1;stroke-width:0.85"
class="st0"
d="m 533.78507,563.71023 h -60.775 v -330.565 h 60.775 v 330.565 m 48.195,27.88 v -386.24 c 0,-11.22 -9.095,-20.315 -20.315,-20.315 h -116.535 c -11.22,0 -20.315,9.095 -20.315,20.315 v 386.24 c 0,11.22 9.095,20.315 20.315,20.315 h 116.45 c 11.22,0 20.4,-9.095 20.4,-20.315 z"
id="path4-3" /><path
style="fill:#818183;fill-opacity:1;stroke-width:0.674603"
d="m 425.89691,200.43815 c 1.10499,-4.08272 3.30912,-7.53117 6.63124,-10.37477 5.42019,-4.63948 2.10678,-4.43387 71.33297,-4.42657 l 62.44927,0.007 3.44194,1.60268 c 4.05635,1.88877 7.75734,5.3977 9.8769,9.36436 l 1.52243,2.84915 v 199.00794 199.00793 l -1.52243,2.84915 c -2.11956,3.96665 -5.82055,7.47559 -9.8769,9.36436 l -3.44194,1.60267 -62.44927,0.007 c -69.78764,0.008 -65.98231,0.26152 -71.72146,-4.79028 -1.69319,-1.4904 -3.87229,-4.37627 -4.9283,-6.52672 l -1.89304,-3.85513 -0.12602,-196.30953 c -0.0987,-153.67069 0.0544,-196.97613 0.70461,-199.37859 z m 77.67539,363.64446 h 30.69444 V 398.46753 232.85245 H 503.5723 472.87784 v 165.61508 165.61508 z"
id="path21-9" /></g></svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xml:space="preserve"
style="enable-background:new 0 0 800 800;"
viewBox="0 0 800 800"
y="0px"
x="0px"
id="Layer_1"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"><metadata
id="metadata15"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
id="defs13" />
<style
id="style2"
type="text/css">
.st0{fill:#323a3e;}
</style>
<circle
style="fill:#eeeeee;fill-opacity:1;stroke-width:7.75552"
id="path1"
cx="400"
cy="400"
r="322.39999" /><path
id="path6"
d="M 501.14231,400.1595 345.84732,517.11951 v -233.75 l 155.29499,116.79 m 66.63999,0 c 0,-6.12 -2.72,-12.155 -8.15999,-16.235 L 330.12233,211.2895 c -13.43,-10.115 -32.555,-0.51 -32.555,16.235 v 345.35501 c 0,16.745 19.125,26.35 32.555,16.235 l 229.49998,-172.805 c 5.44,-3.995 8.16,-10.03 8.15999,-16.15 z"
class="st0"
style="stroke-width:0.85;fill:#acacac;fill-opacity:1" />
<path
id="path19"
d="m 300.17824,218.44362 c 2.06443,-4.13128 4.83026,-6.86894 9.40917,-9.3133 3.43647,-1.83451 12.82007,-1.79344 16.52778,0.0724 3.22403,1.6224 232.7236,174.17375 235.79013,177.281 3.35767,3.4022 4.88323,7.0012 5.23218,12.34324 0.27168,4.159 0.0718,5.32914 -1.5138,8.86232 -1.00471,2.23886 -2.78731,4.97342 -3.96129,6.07682 -4.28583,4.02812 -232.27571,175.25818 -235.54572,176.9048 -4.6586,2.34584 -12.1025,2.3876 -16.52928,0.0928 -3.87486,-2.00878 -7.80538,-5.7435 -9.67029,-9.18862 l -1.46069,-2.69842 -0.1736,-178.1462 -0.1736,-178.14618 z m 45.66908,298.67589 c 1.00351,-0.0612 154.99009,-116.48775 154.99009,-117.18534 0,-0.71184 -154.02593,-116.84804 -154.99009,-116.86318 -0.27827,-0.004 -0.50595,52.66168 -0.50595,117.03568 0,64.37401 0.22768,117.0298 0.50595,117.01284 z"
style="fill:#acacac;fill-opacity:1;stroke-width:0.6746" /><path
style="fill:#acacac;stroke-width:0.85;fill-opacity:1"
class="st0"
d="M 501.14231,400.15949 345.84732,517.1195 v -233.75 l 155.29499,116.79 m 66.63999,0 c 0,-6.12 -2.72,-12.155 -8.15999,-16.235 L 330.12233,211.28949 c -13.43,-10.115 -32.555,-0.51 -32.555,16.235 V 572.8795 c 0,16.745 19.125,26.35 32.555,16.235 l 229.49998,-172.805 c 5.44,-3.995 8.16,-10.03 8.15999,-16.15 z"
id="path6-2" /><path
style="fill:#acacac;fill-opacity:1;stroke-width:0.6746"
d="m 300.17824,218.44361 c 2.06443,-4.13128 4.83026,-6.86894 9.40917,-9.3133 3.43647,-1.83451 12.82007,-1.79344 16.52778,0.0724 3.22403,1.6224 232.7236,174.17375 235.79013,177.281 3.35767,3.4022 4.88323,7.0012 5.23218,12.34324 0.27168,4.159 0.0718,5.32914 -1.5138,8.86232 -1.00471,2.23886 -2.78731,4.97342 -3.96129,6.07682 -4.28583,4.02812 -232.27571,175.25818 -235.54572,176.9048 -4.6586,2.34584 -12.1025,2.3876 -16.52928,0.0928 -3.87486,-2.00878 -7.80538,-5.7435 -9.67029,-9.18862 l -1.46069,-2.69842 -0.1736,-178.1462 -0.1736,-178.14618 z m 45.66908,298.67589 c 1.00351,-0.0612 154.99009,-116.48775 154.99009,-117.18534 0,-0.71184 -154.02593,-116.84804 -154.99009,-116.86318 -0.27827,-0.004 -0.50595,52.66168 -0.50595,117.03568 0,64.37401 0.22768,117.0298 0.50595,117.01284 z"
id="path19-3" /></svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xml:space="preserve"
style="enable-background:new 0 0 800 800;"
viewBox="0 0 800 800"
y="0px"
x="0px"
id="Layer_1"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"><metadata
id="metadata15"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
id="defs13" />
<style
id="style2"
type="text/css">
.st0{fill:#323a3e;}
</style>
<circle
style="fill:#3e3e45;fill-opacity:1;stroke-width:7.75552"
id="path1"
cx="400"
cy="400"
r="322.39999" /><path
id="path6"
d="M 501.14231,400.1595 345.84732,517.11951 v -233.75 l 155.29499,116.79 m 66.63999,0 c 0,-6.12 -2.72,-12.155 -8.15999,-16.235 L 330.12233,211.2895 c -13.43,-10.115 -32.555,-0.51 -32.555,16.235 v 345.35501 c 0,16.745 19.125,26.35 32.555,16.235 l 229.49998,-172.805 c 5.44,-3.995 8.16,-10.03 8.15999,-16.15 z"
class="st0"
style="stroke-width:0.85;fill:#818183;fill-opacity:1" />
<path
id="path19"
d="m 300.17824,218.44362 c 2.06443,-4.13128 4.83026,-6.86894 9.40917,-9.3133 3.43647,-1.83451 12.82007,-1.79344 16.52778,0.0724 3.22403,1.6224 232.7236,174.17375 235.79013,177.281 3.35767,3.4022 4.88323,7.0012 5.23218,12.34324 0.27168,4.159 0.0718,5.32914 -1.5138,8.86232 -1.00471,2.23886 -2.78731,4.97342 -3.96129,6.07682 -4.28583,4.02812 -232.27571,175.25818 -235.54572,176.9048 -4.6586,2.34584 -12.1025,2.3876 -16.52928,0.0928 -3.87486,-2.00878 -7.80538,-5.7435 -9.67029,-9.18862 l -1.46069,-2.69842 -0.1736,-178.1462 -0.1736,-178.14618 z m 45.66908,298.67589 c 1.00351,-0.0612 154.99009,-116.48775 154.99009,-117.18534 0,-0.71184 -154.02593,-116.84804 -154.99009,-116.86318 -0.27827,-0.004 -0.50595,52.66168 -0.50595,117.03568 0,64.37401 0.22768,117.0298 0.50595,117.01284 z"
style="fill:#818183;fill-opacity:1;stroke-width:0.6746" /><path
style="fill:#818183;stroke-width:0.85;fill-opacity:1"
class="st0"
d="M 501.14231,400.15949 345.84732,517.1195 v -233.75 l 155.29499,116.79 m 66.63999,0 c 0,-6.12 -2.72,-12.155 -8.15999,-16.235 L 330.12233,211.28949 c -13.43,-10.115 -32.555,-0.51 -32.555,16.235 V 572.8795 c 0,16.745 19.125,26.35 32.555,16.235 l 229.49998,-172.805 c 5.44,-3.995 8.16,-10.03 8.15999,-16.15 z"
id="path6-2" /><path
style="fill:#818183;fill-opacity:1;stroke-width:0.6746"
d="m 300.17824,218.44361 c 2.06443,-4.13128 4.83026,-6.86894 9.40917,-9.3133 3.43647,-1.83451 12.82007,-1.79344 16.52778,0.0724 3.22403,1.6224 232.7236,174.17375 235.79013,177.281 3.35767,3.4022 4.88323,7.0012 5.23218,12.34324 0.27168,4.159 0.0718,5.32914 -1.5138,8.86232 -1.00471,2.23886 -2.78731,4.97342 -3.96129,6.07682 -4.28583,4.02812 -232.27571,175.25818 -235.54572,176.9048 -4.6586,2.34584 -12.1025,2.3876 -16.52928,0.0928 -3.87486,-2.00878 -7.80538,-5.7435 -9.67029,-9.18862 l -1.46069,-2.69842 -0.1736,-178.1462 -0.1736,-178.14618 z m 45.66908,298.67589 c 1.00351,-0.0612 154.99009,-116.48775 154.99009,-117.18534 0,-0.71184 -154.02593,-116.84804 -154.99009,-116.86318 -0.27827,-0.004 -0.50595,52.66168 -0.50595,117.03568 0,64.37401 0.22768,117.0298 0.50595,117.01284 z"
id="path19-3" /></svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xml:space="preserve"
style="enable-background:new 0 0 800 800;"
viewBox="0 0 800 800"
y="0px"
x="0px"
id="Layer_1"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"><metadata
id="metadata15"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
id="defs13" />
<style
id="style2"
type="text/css">
.st0{fill:#323a3e;}
</style>
<circle
style="fill:#eeeeee;fill-opacity:1;stroke-width:9.17961"
id="path1"
cx="400"
cy="400"
r="381.60001" /><path
id="path6"
d="M 519.71435,400.1888 335.90365,538.62534 V 261.9535 l 183.8107,138.23531 m 78.87661,0 c 0,-7.24377 -3.21945,-14.38694 -9.65835,-19.21612 L 317.29119,176.63795 c -15.89606,-11.97235 -38.53284,-0.60365 -38.53284,19.21612 v 408.77008 c 0,19.81976 22.63678,31.18846 38.53284,19.21611 L 588.93261,419.30432 c 6.43891,-4.72858 9.65836,-11.87174 9.65835,-19.11551 z"
class="st0"
style="fill:#acacac;fill-opacity:1;stroke-width:1.00608" />
<path
id="path19"
d="m 281.84868,185.10573 c 2.44351,-4.88988 5.71721,-8.13024 11.13691,-11.02344 4.06748,-2.17136 15.17413,-2.12275 19.56266,0.0857 3.81603,1.92031 275.45697,206.15603 279.08658,209.83384 3.97422,4.02692 5.77991,8.28678 6.19293,14.60974 0.32157,4.92269 0.085,6.30769 -1.79177,10.48965 -1.1892,2.64996 -3.29912,5.88665 -4.68867,7.19266 -5.07281,4.76777 -274.92684,207.43959 -278.7973,209.38856 -5.51402,2.77659 -14.32479,2.82602 -19.56443,0.10984 -4.58637,-2.37764 -9.23863,-6.79814 -11.44598,-10.87586 l -1.7289,-3.19391 -0.20548,-210.85791 -0.20548,-210.85789 z m 54.05497,353.51961 c 1.18777,-0.0724 183.44981,-137.87757 183.44981,-138.70325 0,-0.84255 -182.30861,-138.30401 -183.44981,-138.32193 -0.32937,-0.005 -0.59886,62.33157 -0.59886,138.52611 0,76.19454 0.26949,138.51914 0.59886,138.49907 z"
style="fill:#acacac;fill-opacity:1;stroke-width:0.798472" /><path
style="fill:#acacac;fill-opacity:1;stroke-width:1.00608"
class="st0"
d="M 519.71435,400.18878 335.90365,538.62532 V 261.95348 l 183.8107,138.23532 m 78.87661,0 c 0,-7.24378 -3.21945,-14.38694 -9.65835,-19.21612 L 317.29119,176.63794 c -15.89606,-11.97235 -38.53284,-0.60365 -38.53284,19.21611 v 408.77009 c 0,19.81976 22.63678,31.18846 38.53284,19.21611 L 588.93261,419.30431 c 6.43891,-4.72858 9.65836,-11.87174 9.65835,-19.11551 z"
id="path6-2" /><path
style="fill:#acacac;fill-opacity:1;stroke-width:0.798472"
d="m 281.84868,185.10572 c 2.44351,-4.88988 5.71721,-8.13024 11.13691,-11.02344 4.06748,-2.17137 15.17413,-2.12275 19.56266,0.0857 3.81603,1.92031 275.45697,206.15603 279.08658,209.83384 3.97422,4.02692 5.77991,8.28678 6.19293,14.60974 0.32157,4.92269 0.085,6.30769 -1.79177,10.48964 -1.1892,2.64997 -3.29912,5.88666 -4.68867,7.19267 -5.07281,4.76777 -274.92684,207.43958 -278.7973,209.38856 -5.51402,2.77659 -14.32479,2.82602 -19.56443,0.10984 -4.58637,-2.37764 -9.23863,-6.79814 -11.44598,-10.87586 l -1.7289,-3.19391 -0.20548,-210.85791 -0.20548,-210.85789 z m 54.05497,353.5196 c 1.18777,-0.0724 183.44981,-137.87756 183.44981,-138.70324 0,-0.84255 -182.30861,-138.30401 -183.44981,-138.32193 -0.32937,-0.005 -0.59886,62.33157 -0.59886,138.5261 0,76.19455 0.26949,138.51915 0.59886,138.49907 z"
id="path19-3" /></svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xml:space="preserve"
style="enable-background:new 0 0 800 800;"
viewBox="0 0 800 800"
y="0px"
x="0px"
id="Layer_1"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"><metadata
id="metadata15"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
id="defs13" />
<style
id="style2"
type="text/css">
.st0{fill:#323a3e;}
</style>
<circle
style="fill:#3e3e45;fill-opacity:1;stroke-width:9.17961"
id="path1"
cx="400"
cy="400"
r="381.60001" /><path
id="path6"
d="M 519.71435,400.1888 335.90365,538.62534 V 261.9535 l 183.8107,138.23531 m 78.87661,0 c 0,-7.24377 -3.21945,-14.38694 -9.65835,-19.21612 L 317.29119,176.63795 c -15.89606,-11.97235 -38.53284,-0.60365 -38.53284,19.21612 v 408.77008 c 0,19.81976 22.63678,31.18846 38.53284,19.21611 L 588.93261,419.30432 c 6.43891,-4.72858 9.65836,-11.87174 9.65835,-19.11551 z"
class="st0"
style="fill:#818183;fill-opacity:1;stroke-width:1.00608" />
<path
id="path19"
d="m 281.84868,185.10573 c 2.44351,-4.88988 5.71721,-8.13024 11.13691,-11.02344 4.06748,-2.17136 15.17413,-2.12275 19.56266,0.0857 3.81603,1.92031 275.45697,206.15603 279.08658,209.83384 3.97422,4.02692 5.77991,8.28678 6.19293,14.60974 0.32157,4.92269 0.085,6.30769 -1.79177,10.48965 -1.1892,2.64996 -3.29912,5.88665 -4.68867,7.19266 -5.07281,4.76777 -274.92684,207.43959 -278.7973,209.38856 -5.51402,2.77659 -14.32479,2.82602 -19.56443,0.10984 -4.58637,-2.37764 -9.23863,-6.79814 -11.44598,-10.87586 l -1.7289,-3.19391 -0.20548,-210.85791 -0.20548,-210.85789 z m 54.05497,353.51961 c 1.18777,-0.0724 183.44981,-137.87757 183.44981,-138.70325 0,-0.84255 -182.30861,-138.30401 -183.44981,-138.32193 -0.32937,-0.005 -0.59886,62.33157 -0.59886,138.52611 0,76.19454 0.26949,138.51914 0.59886,138.49907 z"
style="fill:#818183;fill-opacity:1;stroke-width:0.798472" /><path
style="fill:#818183;fill-opacity:1;stroke-width:1.00608"
class="st0"
d="M 519.71435,400.18878 335.90365,538.62532 V 261.95348 l 183.8107,138.23532 m 78.87661,0 c 0,-7.24378 -3.21945,-14.38694 -9.65835,-19.21612 L 317.29119,176.63794 c -15.89606,-11.97235 -38.53284,-0.60365 -38.53284,19.21611 v 408.77009 c 0,19.81976 22.63678,31.18846 38.53284,19.21611 L 588.93261,419.30431 c 6.43891,-4.72858 9.65836,-11.87174 9.65835,-19.11551 z"
id="path6-2" /><path
style="fill:#818183;fill-opacity:1;stroke-width:0.798472"
d="m 281.84868,185.10572 c 2.44351,-4.88988 5.71721,-8.13024 11.13691,-11.02344 4.06748,-2.17137 15.17413,-2.12275 19.56266,0.0857 3.81603,1.92031 275.45697,206.15603 279.08658,209.83384 3.97422,4.02692 5.77991,8.28678 6.19293,14.60974 0.32157,4.92269 0.085,6.30769 -1.79177,10.48964 -1.1892,2.64997 -3.29912,5.88666 -4.68867,7.19267 -5.07281,4.76777 -274.92684,207.43958 -278.7973,209.38856 -5.51402,2.77659 -14.32479,2.82602 -19.56443,0.10984 -4.58637,-2.37764 -9.23863,-6.79814 -11.44598,-10.87586 l -1.7289,-3.19391 -0.20548,-210.85791 -0.20548,-210.85789 z m 54.05497,353.5196 c 1.18777,-0.0724 183.44981,-137.87756 183.44981,-138.70324 0,-0.84255 -182.30861,-138.30401 -183.44981,-138.32193 -0.32937,-0.005 -0.59886,62.33157 -0.59886,138.5261 0,76.19455 0.26949,138.51915 0.59886,138.49907 z"
id="path19-3" /></svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -210,6 +210,20 @@ namespace ImGui
const wchar_t ExpandArrowIcon = 0x0843;
const wchar_t CompleteIcon = 0x0844;
// void MyFunction(const char* name, const MyMatrix44& v);
// Orca
const wchar_t PlayButton = 0x0850;
const wchar_t PlayDarkButton = 0x0851;
const wchar_t PlayHoverButton = 0x0852;
const wchar_t PlayHoverDarkButton = 0x0853;
const wchar_t PauseButton = 0x0854;
const wchar_t PauseDarkButton = 0x0855;
const wchar_t PauseHoverButton = 0x0856;
const wchar_t PauseHoverDarkButton = 0x0857;
const wchar_t OpenButton = 0x0858;
const wchar_t OpenDarkButton = 0x0859;
const wchar_t OpenHoverButton = 0x085A;
const wchar_t OpenHoverDarkButton = 0x085B;
// void MyFunction(const char* name, const MyMatrix44& v);
}

View File

@ -333,6 +333,11 @@ void AppConfig::set_defaults()
set("download_path", "");
}
// Orca
if (get("ps_url_registered").empty()) {
set_bool("ps_url_registered", false);
}
if (get("mouse_wheel").empty()) {
set("mouse_wheel", "0");
}

View File

@ -145,6 +145,8 @@ set(lisbslic3r_sources
Format/SL1.cpp
Format/svg.hpp
Format/svg.cpp
Format/ZipperArchiveImport.hpp
Format/ZipperArchiveImport.cpp
GCode/ThumbnailData.cpp
GCode/ThumbnailData.hpp
GCode/CoolingBuffer.cpp

View File

@ -0,0 +1,147 @@
///|/ Copyright (c) Prusa Research 2022 - 2023 Tomáš Mészáros @tamasmeszaros, David Kocík @kocikdav
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#include "ZipperArchiveImport.hpp"
#include "libslic3r/miniz_extension.hpp"
#include "libslic3r/Exception.hpp"
#include "libslic3r/PrintConfig.hpp"
#include <boost/property_tree/ini_parser.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/algorithm/string.hpp>
namespace Slic3r {
namespace {
// Read an ini file into boost property tree
boost::property_tree::ptree read_ini(const mz_zip_archive_file_stat &entry,
MZ_Archive &zip)
{
std::string buf(size_t(entry.m_uncomp_size), '\0');
if (!mz_zip_reader_extract_to_mem(&zip.arch, entry.m_file_index,
buf.data(), buf.size(), 0))
throw Slic3r::FileIOError(zip.get_errorstr());
boost::property_tree::ptree tree;
std::stringstream ss(buf);
boost::property_tree::read_ini(ss, tree);
return tree;
}
// Read an arbitrary file into EntryBuffer
EntryBuffer read_entry(const mz_zip_archive_file_stat &entry,
MZ_Archive &zip,
const std::string &name)
{
std::vector<uint8_t> buf(entry.m_uncomp_size);
if (!mz_zip_reader_extract_to_mem(&zip.arch, entry.m_file_index,
buf.data(), buf.size(), 0))
throw Slic3r::FileIOError(zip.get_errorstr());
return {std::move(buf), (name.empty() ? entry.m_filename : name)};
}
} // namespace
ZipperArchive read_zipper_archive(const std::string &zipfname,
const std::vector<std::string> &includes,
const std::vector<std::string> &excludes)
{
ZipperArchive arch;
// Little RAII
struct Arch : public MZ_Archive
{
Arch(const std::string &fname)
{
if (!open_zip_reader(&arch, fname))
throw Slic3r::FileIOError(get_errorstr());
}
~Arch() { close_zip_reader(&arch); }
} zip(zipfname);
mz_uint num_entries = mz_zip_reader_get_num_files(&zip.arch);
for (mz_uint i = 0; i < num_entries; ++i) {
mz_zip_archive_file_stat entry;
if (mz_zip_reader_file_stat(&zip.arch, i, &entry)) {
std::string name = entry.m_filename;
boost::algorithm::to_lower(name);
if (!std::any_of(includes.begin(), includes.end(),
[&name](const std::string &incl) {
return boost::algorithm::contains(name, incl);
}))
continue;
if (std::any_of(excludes.begin(), excludes.end(),
[&name](const std::string &excl) {
return boost::algorithm::contains(name, excl);
}))
continue;
if (name == CONFIG_FNAME) {
arch.config = read_ini(entry, zip);
continue;
}
if (name == PROFILE_FNAME) {
arch.profile = read_ini(entry, zip);
continue;
}
auto it = std::lower_bound(
arch.entries.begin(), arch.entries.end(),
EntryBuffer{{}, name},
[](const EntryBuffer &r1, const EntryBuffer &r2) {
return std::less<std::string>()(r1.fname, r2.fname);
});
arch.entries.insert(it, read_entry(entry, zip, name));
}
}
return arch;
}
std::pair<DynamicPrintConfig, ConfigSubstitutions> extract_profile(
const ZipperArchive &arch, DynamicPrintConfig &profile_out)
{
DynamicPrintConfig profile_in, profile_use;
ConfigSubstitutions config_substitutions =
profile_in.load(arch.profile,
ForwardCompatibilitySubstitutionRule::Enable);
if (profile_in.empty()) { // missing profile... do guess work
// try to recover the layer height from the config.ini which was
// present in all versions of sl1 files.
if (auto lh_opt = arch.config.find("layerHeight");
lh_opt != arch.config.not_found()) {
auto lh_str = lh_opt->second.data();
size_t pos = 0;
double lh = string_to_double_decimal_point(lh_str, &pos);
if (pos) { // TODO: verify that pos is 0 when parsing fails
profile_out.set("layer_height", lh);
profile_out.set("initial_layer_height", lh);
}
}
}
// If the archive contains an empty profile, use the one that was passed
// as output argument then replace it with the readed profile to report
// that it was empty.
profile_use = profile_in.empty() ? profile_out : profile_in;
profile_out = profile_in;
return {profile_use, std::move(config_substitutions)};
}
} // namespace Slic3r

View File

@ -0,0 +1,58 @@
///|/ Copyright (c) Prusa Research 2022 Tomáš Mészáros @tamasmeszaros
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#ifndef ZIPPERARCHIVEIMPORT_HPP
#define ZIPPERARCHIVEIMPORT_HPP
#include <vector>
#include <string>
#include <cstdint>
#include <boost/property_tree/ptree.hpp>
#include "libslic3r/PrintConfig.hpp"
namespace Slic3r {
// Buffer for arbitraryfiles inside a zipper archive.
struct EntryBuffer
{
std::vector<uint8_t> buf;
std::string fname;
};
// Structure holding the data read from a zipper archive.
struct ZipperArchive
{
boost::property_tree::ptree profile, config;
std::vector<EntryBuffer> entries;
};
// Names of the files containing metadata inside the archive.
const constexpr char *CONFIG_FNAME = "config.ini";
const constexpr char *PROFILE_FNAME = "prusaslicer.ini";
// Read an archive that was written using the Zipper class.
// The includes parameter is a set of file name substrings that the entries
// must contain to be included in ZipperArchive.
// The excludes parameter may contain substrings that filenames must not
// contain.
// Every file in the archive is read into ZipperArchive::entries
// except the files CONFIG_FNAME, and PROFILE_FNAME which are read into
// ZipperArchive::config and ZipperArchive::profile structures.
ZipperArchive read_zipper_archive(const std::string &zipfname,
const std::vector<std::string> &includes,
const std::vector<std::string> &excludes);
// Extract the print profile form the archive into 'out'.
// Returns a profile that has correct parameters to use for model reconstruction
// even if the needed parameters were not fully found in the archive's metadata.
// The inout argument shall be a usable fallback profile if the archive
// has totally corrupted metadata.
std::pair<DynamicPrintConfig, ConfigSubstitutions> extract_profile(
const ZipperArchive &arch, DynamicPrintConfig &inout);
} // namespace Slic3r
#endif // ZIPPERARCHIVEIMPORT_HPP

View File

@ -8000,6 +8000,40 @@ mz_uint mz_zip_reader_get_extra(mz_zip_archive* pZip, mz_uint file_index, char*
return ne + 1;
}
mz_uint mz_zip_reader_get_filename_from_extra(mz_zip_archive* pZip, mz_uint file_index, char* buffer, mz_uint extra_buf_size)
{
if (extra_buf_size == 0)
return 0;
mz_uint nf;
mz_uint ne;
const mz_uint8* p = mz_zip_get_cdh(pZip, file_index);
if (!p)
{
if (extra_buf_size)
buffer[0] = '\0';
mz_zip_set_error(pZip, MZ_ZIP_INVALID_PARAMETER);
return 0;
}
nf = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS);
ne = MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS);
int copy = 0;
char const* p_nf = p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + nf;
char const* e = p_nf + ne + 1;
while (p_nf + 4 < e) {
mz_uint16 len = ((mz_uint16)p_nf[2]) | ((mz_uint16)p_nf[3] << 8);
if (p_nf[0] == '\x75' && p_nf[1] == '\x70' && len >= 5 && p_nf + 4 + len < e && p_nf[4] == '\x01') {
mz_uint length = MZ_MIN(len - 5, extra_buf_size - 1);
memcpy(buffer, p_nf + 9, length);
return length;
}
else {
p_nf += 4 + len;
}
}
return 0;
}
mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat)
{
return mz_zip_file_stat_internal(pZip, file_index, mz_zip_get_cdh(pZip, file_index), pStat, NULL);

View File

@ -1171,6 +1171,9 @@ mz_uint mz_zip_reader_get_extra(mz_zip_archive *pZip, mz_uint file_index, char *
int mz_zip_reader_locate_file(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags);
int mz_zip_reader_locate_file_v2(mz_zip_archive *pZip, const char *pName, const char *pComment, mz_uint flags, mz_uint32 *file_index);
/* Retrieves the filename of an archive file entry from EXTRA ID. */
mz_uint mz_zip_reader_get_filename_from_extra(mz_zip_archive * pZip, mz_uint file_index, char* buffer, mz_uint extra_buf_size);
/* Returns detailed information about an archive file entry. */
mz_bool mz_zip_reader_file_stat(mz_zip_archive *pZip, mz_uint file_index, mz_zip_archive_file_stat *pStat);

View File

@ -111,7 +111,7 @@ set(SLIC3R_GUI_SOURCES
GUI/GLCanvas3D.hpp
GUI/GLCanvas3D.cpp
GUI/SceneRaycaster.hpp
GUI/SceneRaycaster.cpp
GUI/SceneRaycaster.cpp
GUI/OpenGLManager.hpp
GUI/OpenGLManager.cpp
GUI/Selection.hpp
@ -290,6 +290,8 @@ set(SLIC3R_GUI_SOURCES
GUI/ConfigManipulation.hpp
GUI/Field.cpp
GUI/Field.hpp
GUI/FileArchiveDialog.cpp
GUI/FileArchiveDialog.hpp
GUI/OptionsGroup.cpp
GUI/OptionsGroup.hpp
GUI/OG_CustomCtrl.cpp
@ -342,6 +344,10 @@ set(SLIC3R_GUI_SOURCES
GUI/ConfigWizard_private.hpp
GUI/MsgDialog.cpp
GUI/MsgDialog.hpp
GUI/Downloader.hpp
GUI/Downloader.cpp
GUI/DownloaderFileGet.hpp
GUI/DownloaderFileGet.cpp
GUI/DownloadProgressDialog.hpp
GUI/DownloadProgressDialog.cpp
GUI/UpdateDialogs.cpp

View File

@ -455,6 +455,171 @@ void DesktopIntegrationDialog::undo_desktop_intgration()
wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::UndoDesktopIntegrationSuccess);
}
void DesktopIntegrationDialog::perform_downloader_desktop_integration()
{
BOOST_LOG_TRIVIAL(debug) << "performing downloader desktop integration.";
// Path to appimage
const char* appimage_env = std::getenv("APPIMAGE");
std::string excutable_path;
if (appimage_env) {
try {
excutable_path = boost::filesystem::canonical(boost::filesystem::path(appimage_env)).string();
}
catch (std::exception&) {
BOOST_LOG_TRIVIAL(error) << "Performing downloader desktop integration failed - boost::filesystem::canonical did not return appimage path.";
show_error(nullptr, _L("Performing downloader desktop integration failed - boost::filesystem::canonical did not return appimage path."));
return;
}
}
else {
// not appimage - find executable
excutable_path = boost::dll::program_location().string();
//excutable_path = wxStandardPaths::Get().GetExecutablePath().string();
BOOST_LOG_TRIVIAL(debug) << "non-appimage path to executable: " << excutable_path;
if (excutable_path.empty())
{
BOOST_LOG_TRIVIAL(error) << "Performing downloader desktop integration failed - no executable found.";
show_error(nullptr, _L("Performing downloader desktop integration failed - Could not find executable."));
return;
}
}
// Escape ' characters in appimage, other special symbols will be esacaped in desktop file by 'excutable_path'
//boost::replace_all(excutable_path, "'", "'\\''");
excutable_path = escape_string(excutable_path);
// Find directories icons and applications
// $XDG_DATA_HOME defines the base directory relative to which user specific data files should be stored.
// If $XDG_DATA_HOME is either not set or empty, a default equal to $HOME/.local/share should be used.
// $XDG_DATA_DIRS defines the preference-ordered set of base directories to search for data files in addition to the $XDG_DATA_HOME base directory.
// The directories in $XDG_DATA_DIRS should be seperated with a colon ':'.
// If $XDG_DATA_DIRS is either not set or empty, a value equal to /usr/local/share/:/usr/share/ should be used.
std::vector<std::string>target_candidates;
resolve_path_from_var("XDG_DATA_HOME", target_candidates);
resolve_path_from_var("XDG_DATA_DIRS", target_candidates);
AppConfig* app_config = wxGetApp().app_config;
// suffix string to create different desktop file for alpha, beta.
std::string version_suffix;
std::string name_suffix;
std::string version(SLIC3R_VERSION);
if (version.find("alpha") != std::string::npos)
{
version_suffix = "-alpha";
name_suffix = " - alpha";
}
else if (version.find("beta") != std::string::npos)
{
version_suffix = "-beta";
name_suffix = " - beta";
}
// theme path to icon destination
std::string icon_theme_path;
std::string icon_theme_dirs;
if (platform_flavor() == PlatformFlavor::LinuxOnChromium) {
icon_theme_path = "hicolor/96x96/apps/";
icon_theme_dirs = "/hicolor/96x96/apps";
}
std::string target_dir_desktop;
// desktop file
// iterate thru target_candidates to find applications folder
std::string desktop_file_downloader = GUI::format(
"[Desktop Entry]\n"
"Name=PrusaSlicer URL Protocol%1%\n"
"Exec=\"%2%\" %%u\n"
"Terminal=false\n"
"Type=Application\n"
"MimeType=x-scheme-handler/prusaslicer;\n"
"StartupNotify=false\n"
"NoDisplay=true\n"
, name_suffix, excutable_path);
// desktop file for downloader as part of main app
std::string desktop_path = GUI::format("%1%/applications/PrusaSlicerURLProtocol%2%.desktop", target_dir_desktop, version_suffix);
if (create_desktop_file(desktop_path, desktop_file_downloader)) {
// save path to desktop file
app_config->set("desktop_integration_URL_path", desktop_path);
// finish registration on mime type
std::string command = GUI::format("xdg-mime default PrusaSlicerURLProtocol%1%.desktop x-scheme-handler/prusaslicer", version_suffix);
BOOST_LOG_TRIVIAL(debug) << "system command: " << command;
int r = system(command.c_str());
BOOST_LOG_TRIVIAL(debug) << "system result: " << r;
}
bool candidate_found = false;
for (size_t i = 0; i < target_candidates.size(); ++i) {
if (contains_path_dir(target_candidates[i], "applications")) {
target_dir_desktop = target_candidates[i];
// Write slicer desktop file
std::string path = GUI::format("%1%/applications/PrusaSlicerURLProtocol%2%.desktop", target_dir_desktop, version_suffix);
if (create_desktop_file(path, desktop_file_downloader)) {
app_config->set("desktop_integration_URL_path", path);
candidate_found = true;
BOOST_LOG_TRIVIAL(debug) << "PrusaSlicerURLProtocol.desktop file installation success.";
break;
}
else {
// write failed - try another path
BOOST_LOG_TRIVIAL(debug) << "Attempt to PrusaSlicerURLProtocol.desktop file installation failed. failed path: " << target_candidates[i];
target_dir_desktop.clear();
}
}
}
// if all failed - try creating default home folder
if (!candidate_found) {
// create $HOME/.local/share
create_path(boost::nowide::narrow(wxFileName::GetHomeDir()), ".local/share/applications");
// create desktop file
target_dir_desktop = GUI::format("%1%/.local/share", wxFileName::GetHomeDir());
std::string path = GUI::format("%1%/applications/PrusaSlicerURLProtocol%2%.desktop", target_dir_desktop, version_suffix);
if (contains_path_dir(target_dir_desktop, "applications")) {
if (!create_desktop_file(path, desktop_file_downloader)) {
// Desktop file not written - end desktop integration
BOOST_LOG_TRIVIAL(error) << "Performing downloader desktop integration failed - could not create desktop file.";
return;
}
app_config->set("desktop_integration_URL_path", path);
}
else {
// Desktop file not written - end desktop integration
BOOST_LOG_TRIVIAL(error) << "Performing downloader desktop integration failed because the application directory was not found.";
return;
}
}
assert(!target_dir_desktop.empty());
if (target_dir_desktop.empty()) {
// Desktop file not written - end desktop integration
BOOST_LOG_TRIVIAL(error) << "Performing downloader desktop integration failed because the application directory was not found.";
show_error(nullptr, _L("Performing downloader desktop integration failed because the application directory was not found."));
return;
}
// finish registration on mime type
std::string command = GUI::format("xdg-mime default PrusaSlicerURLProtocol%1%.desktop x-scheme-handler/prusaslicer", version_suffix);
BOOST_LOG_TRIVIAL(debug) << "system command: " << command;
int r = system(command.c_str());
BOOST_LOG_TRIVIAL(debug) << "system result: " << r;
wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::DesktopIntegrationSuccess);
}
void DesktopIntegrationDialog::undo_downloader_registration()
{
const AppConfig *app_config = wxGetApp().app_config;
std::string path = std::string(app_config->get("desktop_integration_URL_path"));
if (!path.empty()) {
BOOST_LOG_TRIVIAL(debug) << "removing " << path;
std::remove(path.c_str());
}
// There is no need to undo xdg-mime default command. It is done automatically when desktop file is deleted.
}
DesktopIntegrationDialog::DesktopIntegrationDialog(wxWindow *parent)
: wxDialog(parent, wxID_ANY, _(L("Desktop Integration")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER)
{

View File

@ -29,6 +29,9 @@ public:
static void perform_desktop_integration();
// Deletes Desktop files and icons for both PrusaSlicer and GcodeViewer at paths stored in App Config.
static void undo_desktop_intgration();
static void perform_downloader_desktop_integration();
static void undo_downloader_registration();
private:
};

View File

@ -0,0 +1,270 @@
///|/ Copyright (c) Prusa Research 2023 David Kocík @kocikdav, Oleksandra Iushchenko @YuSanka
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#include "Downloader.hpp"
#include "GUI_App.hpp"
#include "NotificationManager.hpp"
#include "format.hpp"
#include "MainFrame.hpp"
#include <boost/algorithm/string.hpp>
#include <boost/log/trivial.hpp>
#include <boost/regex.hpp>
namespace Slic3r {
namespace GUI {
namespace {
void open_folder(const std::string& path)
{
// Code taken from NotificationManager.cpp
// Execute command to open a file explorer, platform dependent.
// FIXME: The const_casts aren't needed in wxWidgets 3.1, remove them when we upgrade.
#ifdef _WIN32
const wxString widepath = from_u8(path);
const wchar_t* argv[] = { L"explorer", widepath.GetData(), nullptr };
::wxExecute(const_cast<wchar_t**>(argv), wxEXEC_ASYNC, nullptr);
#elif __APPLE__
const char* argv[] = { "open", path.data(), nullptr };
::wxExecute(const_cast<char**>(argv), wxEXEC_ASYNC, nullptr);
#else
const char* argv[] = { "xdg-open", path.data(), nullptr };
// Check if we're running in an AppImage container, if so, we need to remove AppImage's env vars,
// because they may mess up the environment expected by the file manager.
// Mostly this is about LD_LIBRARY_PATH, but we remove a few more too for good measure.
if (wxGetEnv("APPIMAGE", nullptr)) {
// We're running from AppImage
wxEnvVariableHashMap env_vars;
wxGetEnvMap(&env_vars);
env_vars.erase("APPIMAGE");
env_vars.erase("APPDIR");
env_vars.erase("LD_LIBRARY_PATH");
env_vars.erase("LD_PRELOAD");
env_vars.erase("UNION_PRELOAD");
wxExecuteEnv exec_env;
exec_env.env = std::move(env_vars);
wxString owd;
if (wxGetEnv("OWD", &owd)) {
// This is the original work directory from which the AppImage image was run,
// set it as CWD for the child process:
exec_env.cwd = std::move(owd);
}
::wxExecute(const_cast<char**>(argv), wxEXEC_ASYNC, nullptr, &exec_env);
}
else {
// Looks like we're NOT running from AppImage, we'll make no changes to the environment.
::wxExecute(const_cast<char**>(argv), wxEXEC_ASYNC, nullptr, nullptr);
}
#endif
}
std::string filename_from_url(const std::string& url)
{
// TODO: can it be done with curl?
size_t slash = url.find_last_of("/");
if (slash == std::string::npos && slash != url.size() - 1)
return {};
return url.substr(slash + 1, url.size() - slash + 1);
}
}
Download::Download(int ID, std::string url, wxEvtHandler* evt_handler, const boost::filesystem::path& dest_folder)
: m_id(ID)
, m_filename(filename_from_url(url))
, m_dest_folder(dest_folder)
{
assert(boost::filesystem::is_directory(dest_folder));
m_final_path = dest_folder / m_filename;
m_file_get = std::make_shared<FileGet>(ID, std::move(url), m_filename, evt_handler, dest_folder);
}
void Download::start()
{
m_state = DownloadState::DownloadOngoing;
m_file_get->get();
}
void Download::cancel()
{
m_state = DownloadState::DownloadStopped;
m_file_get->cancel();
}
void Download::pause()
{
//assert(m_state == DownloadState::DownloadOngoing);
// if instead of assert - it can happen that user clicks on pause several times before the pause happens
if (m_state != DownloadState::DownloadOngoing)
return;
m_state = DownloadState::DownloadPaused;
m_file_get->pause();
}
void Download::resume()
{
//assert(m_state == DownloadState::DownloadPaused);
if (m_state != DownloadState::DownloadPaused)
return;
m_state = DownloadState::DownloadOngoing;
m_file_get->resume();
}
Downloader::Downloader()
: wxEvtHandler()
{
//Bind(EVT_DWNLDR_FILE_COMPLETE, [](const wxCommandEvent& evt) {});
//Bind(EVT_DWNLDR_FILE_PROGRESS, [](const wxCommandEvent& evt) {});
//Bind(EVT_DWNLDR_FILE_ERROR, [](const wxCommandEvent& evt) {});
//Bind(EVT_DWNLDR_FILE_NAME_CHANGE, [](const wxCommandEvent& evt) {});
Bind(EVT_DWNLDR_FILE_COMPLETE, &Downloader::on_complete, this);
Bind(EVT_DWNLDR_FILE_PROGRESS, &Downloader::on_progress, this);
Bind(EVT_DWNLDR_FILE_ERROR, &Downloader::on_error, this);
Bind(EVT_DWNLDR_FILE_NAME_CHANGE, &Downloader::on_name_change, this);
Bind(EVT_DWNLDR_FILE_PAUSED, &Downloader::on_paused, this);
Bind(EVT_DWNLDR_FILE_CANCELED, &Downloader::on_canceled, this);
}
void Downloader::start_download(const std::string& full_url)
{
assert(m_initialized);
// Orca: check if url is registered
if (!wxGetApp().app_config->has("ps_url_registered") || !wxGetApp().app_config->get_bool("ps_url_registered")) {
BOOST_LOG_TRIVIAL(error) << "PrusaSlicer links are not enabled. Download aborted: " << full_url;
show_error(nullptr, "PrusaSlicer links are not enabled in preferences. Download aborted.");
return;
}
// Orca: Move to the 3D view
MainFrame* mainframe = wxGetApp().mainframe;
Plater* plater = wxGetApp().plater();
mainframe->Freeze();
mainframe->select_tab((size_t)MainFrame::TabPosition::tp3DEditor);
plater->select_view_3D("3D");
plater->select_view("plate");
plater->get_current_canvas3D()->zoom_to_bed();
mainframe->Thaw();
// Orca: Replace PS workaround for "mysterious slash" with a more dynamic approach
// Windows seems to have fixed the issue and this provides backwards compatability for those it still affects
boost::regex re(R"(^prusaslicer:\/\/open[\/]?\?file=)", boost::regbase::icase);
boost::smatch results;
if (!boost::regex_search(full_url, results, re)) {
BOOST_LOG_TRIVIAL(error) << "Could not start download due to wrong URL: " << full_url;
// Orca: show error
NotificationManager* ntf_mngr = wxGetApp().notification_manager();
ntf_mngr->push_notification(NotificationType::CustomNotification, NotificationManager::NotificationLevel::ErrorNotificationLevel,
"Could not start download due to malformed URL");
return;
}
size_t id = get_next_id();
std::string escaped_url = FileGet::escape_url(full_url.substr(results.length()));
if (!boost::starts_with(escaped_url, "https://") || !FileGet::is_subdomain(escaped_url, "printables.com")) {
std::string msg = format(_L("Download won't start. Download URL doesn't point to https://printables.com : %1%"), escaped_url);
BOOST_LOG_TRIVIAL(error) << msg;
NotificationManager* ntf_mngr = wxGetApp().notification_manager();
ntf_mngr->push_notification(NotificationType::CustomNotification, NotificationManager::NotificationLevel::ErrorNotificationLevel,
"Download failed. Download URL doesn't point to https://printables.com.");
return;
}
std::string text(escaped_url);
m_downloads.emplace_back(std::make_unique<Download>(id, std::move(escaped_url), this, m_dest_folder));
NotificationManager* ntf_mngr = wxGetApp().notification_manager();
ntf_mngr->push_download_URL_progress_notification(id, m_downloads.back()->get_filename(), std::bind(&Downloader::user_action_callback, this, std::placeholders::_1, std::placeholders::_2));
m_downloads.back()->start();
BOOST_LOG_TRIVIAL(debug) << "started download";
}
void Downloader::on_progress(wxCommandEvent& event)
{
size_t id = event.GetInt();
float percent = (float)std::stoi(boost::nowide::narrow(event.GetString())) / 100.f;
//BOOST_LOG_TRIVIAL(error) << "progress " << id << ": " << percent;
NotificationManager* ntf_mngr = wxGetApp().notification_manager();
BOOST_LOG_TRIVIAL(trace) << "Download "<< id << ": " << percent;
ntf_mngr->set_download_URL_progress(id, percent);
}
void Downloader::on_error(wxCommandEvent& event)
{
size_t id = event.GetInt();
set_download_state(event.GetInt(), DownloadState::DownloadError);
BOOST_LOG_TRIVIAL(error) << "Download error: " << event.GetString();
NotificationManager* ntf_mngr = wxGetApp().notification_manager();
ntf_mngr->set_download_URL_error(id, boost::nowide::narrow(event.GetString()));
show_error(nullptr, format_wxstr(L"%1%\n%2%", _L("The download has failed") + ":", event.GetString()));
}
void Downloader::on_complete(wxCommandEvent& event)
{
// TODO: is this always true? :
// here we open the file itself, notification should get 1.f progress from on progress.
set_download_state(event.GetInt(), DownloadState::DownloadDone);
wxArrayString paths;
paths.Add(event.GetString());
wxGetApp().plater()->load_files(paths);
}
bool Downloader::user_action_callback(DownloaderUserAction action, int id)
{
for (size_t i = 0; i < m_downloads.size(); ++i) {
if (m_downloads[i]->get_id() == id) {
switch (action) {
case DownloadUserCanceled:
m_downloads[i]->cancel();
return true;
case DownloadUserPaused:
m_downloads[i]->pause();
return true;
case DownloadUserContinued:
m_downloads[i]->resume();
return true;
case DownloadUserOpenedFolder:
open_folder(m_downloads[i]->get_dest_folder());
return true;
default:
return false;
}
}
}
return false;
}
void Downloader::on_name_change(wxCommandEvent& event)
{
}
void Downloader::on_paused(wxCommandEvent& event)
{
size_t id = event.GetInt();
NotificationManager* ntf_mngr = wxGetApp().notification_manager();
ntf_mngr->set_download_URL_paused(id);
}
void Downloader::on_canceled(wxCommandEvent& event)
{
size_t id = event.GetInt();
NotificationManager* ntf_mngr = wxGetApp().notification_manager();
ntf_mngr->set_download_URL_canceled(id);
}
void Downloader::set_download_state(int id, DownloadState state)
{
for (size_t i = 0; i < m_downloads.size(); ++i) {
if (m_downloads[i]->get_id() == id) {
m_downloads[i]->set_state(state);
return;
}
}
}
}
}

View File

@ -0,0 +1,103 @@
///|/ Copyright (c) Prusa Research 2023 David Kocík @kocikdav
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#ifndef slic3r_Downloader_hpp_
#define slic3r_Downloader_hpp_
#include "DownloaderFileGet.hpp"
#include <boost/filesystem/path.hpp>
#include <wx/wx.h>
namespace Slic3r {
namespace GUI {
class NotificationManager;
enum DownloadState
{
DownloadPending = 0,
DownloadOngoing,
DownloadStopped,
DownloadDone,
DownloadError,
DownloadPaused,
DownloadStateUnknown
};
enum DownloaderUserAction
{
DownloadUserCanceled,
DownloadUserPaused,
DownloadUserContinued,
DownloadUserOpenedFolder
};
class Download {
public:
Download(int ID, std::string url, wxEvtHandler* evt_handler, const boost::filesystem::path& dest_folder);
void start();
void cancel();
void pause();
void resume();
int get_id() const { return m_id; }
boost::filesystem::path get_final_path() const { return m_final_path; }
std::string get_filename() const { return m_filename; }
DownloadState get_state() const { return m_state; }
void set_state(DownloadState state) { m_state = state; }
std::string get_dest_folder() { return m_dest_folder.string(); }
private:
const int m_id;
std::string m_filename;
boost::filesystem::path m_final_path;
boost::filesystem::path m_dest_folder;
std::shared_ptr<FileGet> m_file_get;
DownloadState m_state { DownloadState::DownloadPending };
};
class Downloader : public wxEvtHandler {
public:
Downloader();
bool get_initialized() { return m_initialized; }
void init(const boost::filesystem::path& dest_folder)
{
m_dest_folder = dest_folder;
m_initialized = true;
}
void start_download(const std::string& full_url);
// cancel = false -> just pause
bool user_action_callback(DownloaderUserAction action, int id);
private:
bool m_initialized { false };
std::vector<std::unique_ptr<Download>> m_downloads;
boost::filesystem::path m_dest_folder;
size_t m_next_id { 0 };
size_t get_next_id() { return ++m_next_id; }
void on_progress(wxCommandEvent& event);
void on_error(wxCommandEvent& event);
void on_complete(wxCommandEvent& event);
void on_name_change(wxCommandEvent& event);
void on_paused(wxCommandEvent& event);
void on_canceled(wxCommandEvent& event);
void set_download_state(int id, DownloadState state);
/*
bool is_in_state(int id, DownloadState state) const;
DownloadState get_download_state(int id) const;
bool cancel_download(int id);
bool pause_download(int id);
bool resume_download(int id);
bool delete_download(int id);
wxString get_path_of(int id) const;
wxString get_folder_path_of(int id) const;
*/
};
}
}
#endif

View File

@ -0,0 +1,395 @@
///|/ Copyright (c) Prusa Research 2023 Oleksandra Iushchenko @YuSanka, David Kocík @kocikdav
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#include "DownloaderFileGet.hpp"
#include <thread>
#include <curl/curl.h>
#include <boost/nowide/fstream.hpp>
#include <boost/format.hpp>
#include <boost/log/trivial.hpp>
#include <boost/algorithm/string.hpp>
#include <iostream>
#include "format.hpp"
#include "GUI.hpp"
#include "I18N.hpp"
namespace Slic3r {
namespace GUI {
const size_t DOWNLOAD_MAX_CHUNK_SIZE = 10 * 1024 * 1024;
const size_t DOWNLOAD_SIZE_LIMIT = 1024 * 1024 * 1024;
std::string FileGet::escape_url(const std::string& unescaped)
{
std::string ret_val;
CURL* curl = curl_easy_init();
if (curl) {
int decodelen;
char* decoded = curl_easy_unescape(curl, unescaped.c_str(), unescaped.size(), &decodelen);
if (decoded) {
ret_val = std::string(decoded);
curl_free(decoded);
}
curl_easy_cleanup(curl);
}
return ret_val;
}
bool FileGet::is_subdomain(const std::string& url, const std::string& domain)
{
// domain should be f.e. printables.com (.com including)
char* host;
std::string host_string;
CURLUcode rc;
CURLU* curl = curl_url();
if (!curl) {
BOOST_LOG_TRIVIAL(error) << "Failed to init Curl library in function is_domain.";
return false;
}
rc = curl_url_set(curl, CURLUPART_URL, url.c_str(), 0);
if (rc != CURLUE_OK) {
curl_url_cleanup(curl);
return false;
}
rc = curl_url_get(curl, CURLUPART_HOST, &host, 0);
if (rc != CURLUE_OK || !host) {
curl_url_cleanup(curl);
return false;
}
host_string = std::string(host);
curl_free(host);
// now host should be subdomain.domain or just domain
if (domain == host_string) {
curl_url_cleanup(curl);
return true;
}
if(boost::ends_with(host_string, "." + domain)) {
curl_url_cleanup(curl);
return true;
}
curl_url_cleanup(curl);
return false;
}
namespace {
unsigned get_current_pid()
{
#ifdef WIN32
return GetCurrentProcessId();
#else
return ::getpid();
#endif
}
}
// int = DOWNLOAD ID; string = file path
wxDEFINE_EVENT(EVT_DWNLDR_FILE_COMPLETE, wxCommandEvent);
// int = DOWNLOAD ID; string = error msg
wxDEFINE_EVENT(EVT_DWNLDR_FILE_ERROR, wxCommandEvent);
// int = DOWNLOAD ID; string = progress percent
wxDEFINE_EVENT(EVT_DWNLDR_FILE_PROGRESS, wxCommandEvent);
// int = DOWNLOAD ID; string = name
wxDEFINE_EVENT(EVT_DWNLDR_FILE_NAME_CHANGE, wxCommandEvent);
// int = DOWNLOAD ID;
wxDEFINE_EVENT(EVT_DWNLDR_FILE_PAUSED, wxCommandEvent);
// int = DOWNLOAD ID;
wxDEFINE_EVENT(EVT_DWNLDR_FILE_CANCELED, wxCommandEvent);
struct FileGet::priv
{
const int m_id;
std::string m_url;
std::string m_filename;
std::thread m_io_thread;
wxEvtHandler* m_evt_handler;
boost::filesystem::path m_dest_folder;
boost::filesystem::path m_tmp_path; // path when ongoing download
std::atomic_bool m_cancel { false };
std::atomic_bool m_pause { false };
std::atomic_bool m_stopped { false }; // either canceled or paused - download is not running
size_t m_written { 0 };
size_t m_absolute_size { 0 };
priv(int ID, std::string&& url, const std::string& filename, wxEvtHandler* evt_handler, const boost::filesystem::path& dest_folder);
void get_perform();
};
FileGet::priv::priv(int ID, std::string&& url, const std::string& filename, wxEvtHandler* evt_handler, const boost::filesystem::path& dest_folder)
: m_id(ID)
, m_url(std::move(url))
, m_filename(filename)
, m_evt_handler(evt_handler)
, m_dest_folder(dest_folder)
{
}
void FileGet::priv::get_perform()
{
assert(m_evt_handler);
assert(!m_url.empty());
assert(!m_filename.empty());
assert(boost::filesystem::is_directory(m_dest_folder));
m_stopped = false;
// open dest file
if (m_written == 0)
{
boost::filesystem::path dest_path = m_dest_folder / m_filename;
std::string extension = boost::filesystem::extension(dest_path);
std::string just_filename = m_filename.substr(0, m_filename.size() - extension.size());
std::string final_filename = just_filename;
// Find unsed filename
try {
size_t version = 0;
while (boost::filesystem::exists(m_dest_folder / (final_filename + extension)) || boost::filesystem::exists(m_dest_folder / (final_filename + extension + "." + std::to_string(get_current_pid()) + ".download")))
{
++version;
if (version > 999) {
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_ERROR);
evt->SetString(GUI::format_wxstr(L"Failed to find suitable filename. Last name: %1%." , (m_dest_folder / (final_filename + extension)).string()));
evt->SetInt(m_id);
m_evt_handler->QueueEvent(evt);
return;
}
final_filename = GUI::format("%1%(%2%)", just_filename, std::to_string(version));
}
} catch (const boost::filesystem::filesystem_error& e)
{
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_ERROR);
evt->SetString(e.what());
evt->SetInt(m_id);
m_evt_handler->QueueEvent(evt);
return;
}
m_filename = final_filename + extension;
m_tmp_path = m_dest_folder / (m_filename + "." + std::to_string(get_current_pid()) + ".download");
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_NAME_CHANGE);
evt->SetString(boost::nowide::widen(m_filename));
evt->SetInt(m_id);
m_evt_handler->QueueEvent(evt);
}
boost::filesystem::path dest_path = m_dest_folder / m_filename;
wxString temp_path_wstring(m_tmp_path.wstring());
//std::cout << "dest_path: " << dest_path.string() << std::endl;
//std::cout << "m_tmp_path: " << m_tmp_path.string() << std::endl;
BOOST_LOG_TRIVIAL(info) << GUI::format("Starting download from %1% to %2%. Temp path is %3%",m_url, dest_path, m_tmp_path);
FILE* file;
// open file for writting
if (m_written == 0)
file = fopen(temp_path_wstring.c_str(), "wb");
else
file = fopen(temp_path_wstring.c_str(), "ab");
//assert(file != NULL);
if (file == NULL) {
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_ERROR);
// TRN %1% = file path
evt->SetString(GUI::format_wxstr(_L("Can't create file at %1%"), temp_path_wstring));
evt->SetInt(m_id);
m_evt_handler->QueueEvent(evt);
return;
}
std:: string range_string = std::to_string(m_written) + "-";
size_t written_previously = m_written;
size_t written_this_session = 0;
Http::get(m_url)
.size_limit(DOWNLOAD_SIZE_LIMIT) //more?
.set_range(range_string)
.on_progress([&](Http::Progress progress, bool& cancel) {
// to prevent multiple calls into following ifs (m_cancel / m_pause)
if (m_stopped){
cancel = true;
return;
}
if (m_cancel) {
m_stopped = true;
fclose(file);
// remove canceled file
std::remove(m_tmp_path.string().c_str());
m_written = 0;
cancel = true;
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_CANCELED);
evt->SetInt(m_id);
m_evt_handler->QueueEvent(evt);
return;
// TODO: send canceled event?
}
if (m_pause) {
m_stopped = true;
fclose(file);
cancel = true;
if (m_written == 0)
std::remove(m_tmp_path.string().c_str());
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_PAUSED);
evt->SetInt(m_id);
m_evt_handler->QueueEvent(evt);
return;
}
if (m_absolute_size < progress.dltotal) {
m_absolute_size = progress.dltotal;
}
if (progress.dlnow != 0) {
if (progress.dlnow - written_this_session > DOWNLOAD_MAX_CHUNK_SIZE || progress.dlnow == progress.dltotal) {
try
{
std::string part_for_write = progress.buffer.substr(written_this_session, progress.dlnow);
fwrite(part_for_write.c_str(), 1, part_for_write.size(), file);
}
catch (const std::exception& e)
{
// fclose(file); do it?
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_ERROR);
evt->SetString(e.what());
evt->SetInt(m_id);
m_evt_handler->QueueEvent(evt);
cancel = true;
return;
}
written_this_session = progress.dlnow;
m_written = written_previously + written_this_session;
}
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_PROGRESS);
int percent_total = (written_previously + progress.dlnow) * 100 / m_absolute_size;
evt->SetString(std::to_string(percent_total));
evt->SetInt(m_id);
m_evt_handler->QueueEvent(evt);
}
})
.on_error([&](std::string body, std::string error, unsigned http_status) {
if (file != NULL)
fclose(file);
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_ERROR);
if (!error.empty())
evt->SetString(GUI::from_u8(error));
else
evt->SetString(GUI::from_u8(body));
evt->SetInt(m_id);
m_evt_handler->QueueEvent(evt);
})
.on_complete([&](std::string body, unsigned /* http_status */) {
// TODO: perform a body size check
//
//size_t body_size = body.size();
//if (body_size != expected_size) {
// return;
//}
try
{
/*
if (m_written < body.size())
{
// this code should never be entered. As there should be on_progress call after last bit downloaded.
std::string part_for_write = body.substr(m_written);
fwrite(part_for_write.c_str(), 1, part_for_write.size(), file);
}
*/
fclose(file);
boost::filesystem::rename(m_tmp_path, dest_path);
}
catch (const std::exception& /*e*/)
{
//TODO: report?
//error_message = GUI::format("Failed to write and move %1% to %2%", tmp_path, dest_path);
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_ERROR);
evt->SetString("Failed to write and move.");
evt->SetInt(m_id);
m_evt_handler->QueueEvent(evt);
return;
}
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_COMPLETE);
evt->SetString(dest_path.wstring());
evt->SetInt(m_id);
m_evt_handler->QueueEvent(evt);
})
.perform_sync();
}
FileGet::FileGet(int ID, std::string url, const std::string& filename, wxEvtHandler* evt_handler, const boost::filesystem::path& dest_folder)
: p(new priv(ID, std::move(url), filename, evt_handler, dest_folder))
{}
FileGet::FileGet(FileGet&& other) : p(std::move(other.p)) {}
FileGet::~FileGet()
{
if (p && p->m_io_thread.joinable()) {
p->m_cancel = true;
p->m_io_thread.join();
}
}
void FileGet::get()
{
assert(p);
if (p->m_io_thread.joinable()) {
// This will stop transfers being done by the thread, if any.
// Cancelling takes some time, but should complete soon enough.
p->m_cancel = true;
p->m_io_thread.join();
}
p->m_cancel = false;
p->m_pause = false;
p->m_io_thread = std::thread([this]() {
p->get_perform();
});
}
void FileGet::cancel()
{
if(p && p->m_stopped) {
if (p->m_io_thread.joinable()) {
p->m_cancel = true;
p->m_io_thread.join();
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_CANCELED);
evt->SetInt(p->m_id);
p->m_evt_handler->QueueEvent(evt);
}
}
if (p)
p->m_cancel = true;
}
void FileGet::pause()
{
if (p) {
p->m_pause = true;
}
}
void FileGet::resume()
{
assert(p);
if (p->m_io_thread.joinable()) {
// This will stop transfers being done by the thread, if any.
// Cancelling takes some time, but should complete soon enough.
p->m_cancel = true;
p->m_io_thread.join();
}
p->m_cancel = false;
p->m_pause = false;
p->m_io_thread = std::thread([this]() {
p->get_perform();
});
}
}
}

View File

@ -0,0 +1,49 @@
///|/ Copyright (c) Prusa Research 2023 David Kocík @kocikdav
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#ifndef slic3r_DownloaderFileGet_hpp_
#define slic3r_DownloaderFileGet_hpp_
#include "../Utils/Http.hpp"
#include <memory>
#include <string>
#include <wx/event.h>
#include <wx/frame.h>
#include <boost/filesystem.hpp>
namespace Slic3r {
namespace GUI {
class FileGet : public std::enable_shared_from_this<FileGet> {
private:
struct priv;
public:
FileGet(int ID, std::string url, const std::string& filename, wxEvtHandler* evt_handler,const boost::filesystem::path& dest_folder);
FileGet(FileGet&& other);
~FileGet();
void get();
void cancel();
void pause();
void resume();
static std::string escape_url(const std::string& url);
static bool is_subdomain(const std::string& url, const std::string& domain);
private:
std::unique_ptr<priv> p;
};
// int = DOWNLOAD ID; string = file path
wxDECLARE_EVENT(EVT_DWNLDR_FILE_COMPLETE, wxCommandEvent);
// int = DOWNLOAD ID; string = error msg
wxDECLARE_EVENT(EVT_DWNLDR_FILE_PROGRESS, wxCommandEvent);
// int = DOWNLOAD ID; string = progress percent
wxDECLARE_EVENT(EVT_DWNLDR_FILE_ERROR, wxCommandEvent);
// int = DOWNLOAD ID; string = name
wxDECLARE_EVENT(EVT_DWNLDR_FILE_NAME_CHANGE, wxCommandEvent);
// int = DOWNLOAD ID;
wxDECLARE_EVENT(EVT_DWNLDR_FILE_PAUSED, wxCommandEvent);
// int = DOWNLOAD ID;
wxDECLARE_EVENT(EVT_DWNLDR_FILE_CANCELED, wxCommandEvent);
}
}
#endif

View File

@ -0,0 +1,447 @@
///|/ Copyright (c) Prusa Research 2023 David Kocík @kocikdav
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#include "FileArchiveDialog.hpp"
#include "I18N.hpp"
#include "GUI_App.hpp"
#include "GUI.hpp"
#include "MainFrame.hpp"
#include "ExtraRenderers.hpp"
#include "format.hpp"
#include <regex>
#include <boost/log/trivial.hpp>
#include <boost/nowide/convert.hpp>
namespace Slic3r {
namespace GUI {
#define BTN_SIZE wxSize(FromDIP(58), FromDIP(24))
#define BTN_GAP FromDIP(20)
ArchiveViewModel::ArchiveViewModel(wxWindow* parent)
:m_parent(parent)
{}
ArchiveViewModel::~ArchiveViewModel()
{}
std::shared_ptr<ArchiveViewNode> ArchiveViewModel::AddFile(std::shared_ptr<ArchiveViewNode> parent, const wxString& name, bool container)
{
std::shared_ptr<ArchiveViewNode> node = std::make_shared<ArchiveViewNode>(ArchiveViewNode(name));
node->set_container(container);
if (parent.get() != nullptr) {
parent->get_children().push_back(node);
node->set_parent(parent);
parent->set_is_folder(true);
} else {
m_top_children.emplace_back(node);
}
wxDataViewItem child = wxDataViewItem((void*)node.get());
wxDataViewItem parent_item= wxDataViewItem((void*)parent.get());
ItemAdded(parent_item, child);
if (parent)
m_ctrl->Expand(parent_item);
return node;
}
wxString ArchiveViewModel::GetColumnType(unsigned int col) const
{
if (col == 0)
return "bool";
return "string";//"DataViewBitmapText";
}
void ArchiveViewModel::Rescale()
{
// There should be no pictures rendered
}
void ArchiveViewModel::Delete(const wxDataViewItem& item)
{
assert(item.IsOk());
ArchiveViewNode* node = static_cast<ArchiveViewNode*>(item.GetID());
assert(node->get_parent() != nullptr);
for (std::shared_ptr<ArchiveViewNode> child : node->get_children())
{
Delete(wxDataViewItem((void*)child.get()));
}
delete [] node;
}
void ArchiveViewModel::Clear()
{
}
wxDataViewItem ArchiveViewModel::GetParent(const wxDataViewItem& item) const
{
assert(item.IsOk());
ArchiveViewNode* node = static_cast<ArchiveViewNode*>(item.GetID());
return wxDataViewItem((void*)node->get_parent().get());
}
unsigned int ArchiveViewModel::GetChildren(const wxDataViewItem& parent, wxDataViewItemArray& array) const
{
if (!parent.IsOk()) {
for (std::shared_ptr<ArchiveViewNode>child : m_top_children) {
array.push_back(wxDataViewItem((void*)child.get()));
}
return m_top_children.size();
}
ArchiveViewNode* node = static_cast<ArchiveViewNode*>(parent.GetID());
for (std::shared_ptr<ArchiveViewNode> child : node->get_children()) {
array.push_back(wxDataViewItem((void*)child.get()));
}
return node->get_children().size();
}
void ArchiveViewModel::GetValue(wxVariant& variant, const wxDataViewItem& item, unsigned int col) const
{
assert(item.IsOk());
ArchiveViewNode* node = static_cast<ArchiveViewNode*>(item.GetID());
if (col == 0) {
variant = node->get_toggle();
} else {
variant = node->get_name();
}
}
void ArchiveViewModel::untoggle_folders(const wxDataViewItem& item)
{
assert(item.IsOk());
ArchiveViewNode* node = static_cast<ArchiveViewNode*>(item.GetID());
node->set_toggle(false);
if (node->get_parent().get() != nullptr)
untoggle_folders(wxDataViewItem((void*)node->get_parent().get()));
}
bool ArchiveViewModel::SetValue(const wxVariant& variant, const wxDataViewItem& item, unsigned int col)
{
assert(item.IsOk());
ArchiveViewNode* node = static_cast<ArchiveViewNode*>(item.GetID());
if (col == 0) {
node->set_toggle(variant.GetBool());
// if folder recursivelly check all children
for (std::shared_ptr<ArchiveViewNode> child : node->get_children()) {
SetValue(variant, wxDataViewItem((void*)child.get()), col);
}
if(!variant.GetBool() && node->get_parent())
untoggle_folders(wxDataViewItem((void*)node->get_parent().get()));
} else {
node->set_name(variant.GetString());
}
m_parent->Refresh();
return true;
}
bool ArchiveViewModel::IsEnabled(const wxDataViewItem& item, unsigned int col) const
{
// As of now, all items are always enabled.
// Returning false for col 1 would gray out text.
return true;
}
bool ArchiveViewModel::IsContainer(const wxDataViewItem& item) const
{
if(!item.IsOk())
return true;
ArchiveViewNode* node = static_cast<ArchiveViewNode*>(item.GetID());
return node->is_container();
}
ArchiveViewCtrl::ArchiveViewCtrl(wxWindow* parent, wxSize size)
: wxDataViewCtrl(parent, wxID_ANY, wxDefaultPosition, size, wxDV_VARIABLE_LINE_HEIGHT | wxDV_ROW_LINES
#ifdef _WIN32
| wxBORDER_SIMPLE
#endif
)
//, m_em_unit(em_unit(parent))
{
wxGetApp().UpdateDVCDarkUI(this);
m_model = new ArchiveViewModel(parent);
this->AssociateModel(m_model);
m_model->SetAssociatedControl(this);
}
ArchiveViewCtrl::~ArchiveViewCtrl()
{
if (m_model) {
m_model->Clear();
m_model->DecRef();
}
}
FileArchiveDialog::FileArchiveDialog(wxWindow* parent_window, mz_zip_archive* archive, std::vector<std::pair<boost::filesystem::path, size_t>>& selected_paths_w_size)
: DPIDialog(parent_window, wxID_ANY, _(L("Archive preview")), wxDefaultPosition,
wxSize(45 * wxGetApp().em_unit(), 40 * wxGetApp().em_unit()),
wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxMAXIMIZE_BOX)
, m_selected_paths_w_size (selected_paths_w_size)
{
#ifdef _WIN32
SetBackgroundColour(*wxWHITE);
wxGetApp().UpdateDarkUI(this);
wxGetApp().UpdateDlgDarkUI(this);
#else
SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
#endif
int em = em_unit();
wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL);
m_avc = new ArchiveViewCtrl(this, wxSize(45 * em, 30 * em));
wxDataViewColumn* toggle_column = m_avc->AppendToggleColumn(L"\u2714", 0, wxDATAVIEW_CELL_ACTIVATABLE, 6 * em);
m_avc->AppendTextColumn("filename", 1);
std::vector<std::shared_ptr<ArchiveViewNode>> stack;
std::function<void(std::vector<std::shared_ptr<ArchiveViewNode> >&, size_t)> reduce_stack = [] (std::vector<std::shared_ptr<ArchiveViewNode>>& stack, size_t size) {
if (size == 0) {
stack.clear();
return;
}
while (stack.size() > size)
stack.pop_back();
};
// recursively stores whole structure of file onto function stack and synchoronize with stack object.
std::function<size_t(const boost::filesystem::path&, std::vector<std::shared_ptr<ArchiveViewNode>>&)> adjust_stack = [&adjust_stack, &reduce_stack, &avc = m_avc](const boost::filesystem::path& const_file, std::vector<std::shared_ptr<ArchiveViewNode>>& stack)->size_t {
boost::filesystem::path file(const_file);
size_t struct_size = file.has_parent_path() ? adjust_stack(file.parent_path(), stack) : 0;
if (stack.size() > struct_size && (file.has_extension() || file.filename().string() != stack[struct_size]->get_name()))
{
reduce_stack(stack, struct_size);
}
if (!file.has_extension() && stack.size() == struct_size)
stack.push_back(avc->get_model()->AddFile((stack.empty() ? std::shared_ptr<ArchiveViewNode>(nullptr) : stack.back()), boost::nowide::widen(file.filename().string()), true)); // filename string to wstring?
return struct_size + 1;
};
const std::regex pattern_drop(".*[.](stl|obj|amf|3mf|step|stp)", std::regex::icase);
mz_uint num_entries = mz_zip_reader_get_num_files(archive);
mz_zip_archive_file_stat stat;
std::vector<std::pair<boost::filesystem::path, size_t>> filtered_entries; // second is unzipped size
for (mz_uint i = 0; i < num_entries; ++i) {
if (mz_zip_reader_file_stat(archive, i, &stat)) {
std::string extra(1024, 0);
boost::filesystem::path path;
size_t extra_size = mz_zip_reader_get_filename_from_extra(archive, i, extra.data(), extra.size());
if (extra_size > 0) {
path = boost::filesystem::path(extra.substr(0, extra_size));
} else {
wxString wname = boost::nowide::widen(stat.m_filename);
std::string name = boost::nowide::narrow(wname);
path = boost::filesystem::path(name);
}
assert(!path.empty());
if (!path.has_extension())
continue;
// filter out MACOS specific hidden files
if (boost::algorithm::starts_with(path.string(), "__MACOSX"))
continue;
filtered_entries.emplace_back(std::move(path), stat.m_uncomp_size);
}
}
// sorting files will help adjust_stack function to not create multiple same folders
std::sort(filtered_entries.begin(), filtered_entries.end(), [](const std::pair<boost::filesystem::path, size_t>& p1, const std::pair<boost::filesystem::path, size_t>& p2){ return p1.first.string() < p2.first.string(); });
size_t entry_count = 0;
size_t depth = 1;
for (const auto& entry : filtered_entries)
{
const boost::filesystem::path& path = entry.first;
std::shared_ptr<ArchiveViewNode> parent(nullptr);
depth = std::max(depth, adjust_stack(path, stack));
if (!stack.empty())
parent = stack.back();
if (std::regex_match(path.extension().string(), pattern_drop)) { // this leaves out non-compatible files
std::shared_ptr<ArchiveViewNode> new_node = m_avc->get_model()->AddFile(parent, boost::nowide::widen(path.filename().string()), false);
new_node->set_fullpath(/*std::move(path)*/path); // filename string to wstring?
new_node->set_size(entry.second);
entry_count++;
}
}
if (entry_count == 1)
on_all_button();
toggle_column->SetWidth((4 + depth) * em);
wxBoxSizer* btn_sizer = create_btn_sizer();
topSizer->Add(m_avc, 1, wxEXPAND | wxALL, 10);
topSizer->Add(btn_sizer, 0, wxEXPAND | wxALL, 10);
this->SetSizer(topSizer);
SetMinSize(wxSize(40 * em, 30 * em));
for (auto btn : m_button_list)
wxGetApp().UpdateDarkUI(btn);
}
void FileArchiveDialog::on_dpi_changed(const wxRect& suggested_rect)
{
int em = em_unit();
BOOST_LOG_TRIVIAL(error) << "on_dpi_changed";
for (auto btn : m_button_list) {
btn->SetMinSize(BTN_SIZE);
btn->SetCornerRadius(FromDIP(12));
}
const wxSize& size = wxSize(45 * em, 40 * em);
SetSize(size);
//m_tree->Rescale(em);
Fit();
Refresh();
}
void FileArchiveDialog::on_open_button()
{
wxDataViewItemArray top_items;
m_avc->get_model()->GetChildren(wxDataViewItem(nullptr), top_items);
std::function<void(ArchiveViewNode*)> deep_fill = [&paths = m_selected_paths_w_size, &deep_fill](ArchiveViewNode* node){
if (node == nullptr)
return;
if (node->get_children().empty()) {
if (node->get_toggle())
paths.emplace_back(node->get_fullpath(), node->get_size());
} else {
for (std::shared_ptr<ArchiveViewNode> child : node->get_children())
deep_fill(child.get());
}
};
for (const auto& item : top_items)
{
ArchiveViewNode* node = static_cast<ArchiveViewNode*>(item.GetID());
deep_fill(node);
}
this->EndModal(wxID_OK);
}
void FileArchiveDialog::on_all_button()
{
wxDataViewItemArray top_items;
m_avc->get_model()->GetChildren(wxDataViewItem(nullptr), top_items);
std::function<void(ArchiveViewNode*)> deep_fill = [&deep_fill](ArchiveViewNode* node) {
if (node == nullptr)
return;
node->set_toggle(true);
if (!node->get_children().empty()) {
for (std::shared_ptr<ArchiveViewNode> child : node->get_children())
deep_fill(child.get());
}
};
for (const auto& item : top_items)
{
ArchiveViewNode* node = static_cast<ArchiveViewNode*>(item.GetID());
deep_fill(node);
// Fix for linux, where Refresh or Update wont help to redraw toggle checkboxes.
// It should be enough to call ValueChanged for top items.
m_avc->get_model()->ValueChanged(item, 0);
}
Refresh();
}
void FileArchiveDialog::on_none_button()
{
wxDataViewItemArray top_items;
m_avc->get_model()->GetChildren(wxDataViewItem(nullptr), top_items);
std::function<void(ArchiveViewNode*)> deep_fill = [&deep_fill](ArchiveViewNode* node) {
if (node == nullptr)
return;
node->set_toggle(false);
if (!node->get_children().empty()) {
for (std::shared_ptr<ArchiveViewNode> child : node->get_children())
deep_fill(child.get());
}
};
for (const auto& item : top_items)
{
ArchiveViewNode* node = static_cast<ArchiveViewNode*>(item.GetID());
deep_fill(node);
// Fix for linux, where Refresh or Update wont help to redraw toggle checkboxes.
// It should be enough to call ValueChanged for top items.
m_avc->get_model()->ValueChanged(item, 0);
}
this->Refresh();
}
//Orca: Apply buttons style
wxBoxSizer* FileArchiveDialog::create_btn_sizer()
{
auto btn_sizer = new wxBoxSizer(wxHORIZONTAL);
auto apply_highlighted_btn_colors = [](Button* btn) {
btn->SetBackgroundColor(StateColor(std::pair<wxColour, int>(wxColour(0, 137, 123), StateColor::Pressed),
std::pair<wxColour, int>(wxColour(38, 166, 154), StateColor::Hovered),
std::pair<wxColour, int>(wxColour(0, 150, 136), StateColor::Normal)));
btn->SetBorderColor(StateColor(std::pair<wxColour, int>(wxColour(0, 150, 136), StateColor::Normal)));
btn->SetTextColor(StateColor(std::pair<wxColour, int>(wxColour(255, 255, 254), StateColor::Normal)));
};
auto apply_std_btn_colors = [](Button* btn) {
btn->SetBackgroundColor(StateColor(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::Normal)));
btn->SetBorderColor(StateColor(std::pair<wxColour, int>(wxColour(38, 46, 48), StateColor::Normal)));
btn->SetTextColor(StateColor(std::pair<wxColour, int>(wxColour(38, 46, 48), StateColor::Normal)));
};
auto style_btn = [this, apply_highlighted_btn_colors, apply_std_btn_colors](Button* btn, bool highlight) {
btn->SetMinSize(BTN_SIZE);
btn->SetCornerRadius(FromDIP(12));
if (highlight)
apply_highlighted_btn_colors(btn);
else
apply_std_btn_colors(btn);
};
Button* all_btn = new Button(this, _L("All"));
style_btn(all_btn, false);
all_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& evt) { on_all_button(); });
btn_sizer->Add(all_btn, 0, wxALIGN_CENTER_VERTICAL);
m_button_list.push_back(all_btn);
Button* none_btn = new Button(this, _L("None"));
style_btn(none_btn, false);
none_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& evt) { on_none_button(); });
btn_sizer->Add(none_btn, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, BTN_GAP);
m_button_list.push_back(none_btn);
btn_sizer->AddStretchSpacer();
Button* open_btn = new Button(this, _L("Open"));
style_btn(open_btn, true);
open_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& evt) { on_open_button(); });
open_btn->SetFocus();
open_btn->SetId(wxID_OK);
btn_sizer->Add(open_btn, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, BTN_GAP);
m_button_list.push_back(open_btn);
Button* cancel_btn = new Button(this, _L("Cancel"));
style_btn(cancel_btn, false);
cancel_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& evt) { this->EndModal(wxID_CANCEL); });
cancel_btn->SetId(wxID_CANCEL);
btn_sizer->Add(cancel_btn, 0, wxRIGHT | wxALIGN_CENTER_VERTICAL, BTN_GAP);
m_button_list.push_back(cancel_btn);
return btn_sizer;
}
} // namespace GUI
} // namespace Slic3r

View File

@ -0,0 +1,131 @@
///|/ Copyright (c) Prusa Research 2023 David Kocík @kocikdav
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#ifndef slic3r_GUI_FileArchiveDialog_hpp_
#define slic3r_GUI_FileArchiveDialog_hpp_
#include "GUI_Utils.hpp"
#include "libslic3r/miniz_extension.hpp"
#include <boost/filesystem/path.hpp>
#include <wx/wx.h>
#include <wx/dataview.h>
#include <slic3r/GUI/Widgets/Button.hpp>
#include "wxExtensions.hpp"
namespace Slic3r {
namespace GUI {
class ArchiveViewCtrl;
class ArchiveViewNode
{
public:
ArchiveViewNode(const wxString& name) : m_name(name) {}
std::vector<std::shared_ptr<ArchiveViewNode>>& get_children() { return m_children; }
void set_parent(std::shared_ptr<ArchiveViewNode> parent) { m_parent = parent; }
// On Linux, get_parent cannot just return size of m_children. ItemAdded would than crash.
std::shared_ptr<ArchiveViewNode> get_parent() const { return m_parent; }
bool is_container() const { return m_container; }
void set_container(bool is_container) { m_container = is_container; }
wxString get_name() const { return m_name; }
void set_name(const wxString& name) { m_name = name; }
bool get_toggle() const { return m_toggle; }
void set_toggle(bool toggle) { m_toggle = toggle; }
bool get_is_folder() const { return m_folder; }
void set_is_folder(bool is_folder) { m_folder = is_folder; }
void set_fullpath(boost::filesystem::path path) { m_fullpath = path; }
boost::filesystem::path get_fullpath() const { return m_fullpath; }
void set_size(size_t size) { m_size = size; }
size_t get_size() const { return m_size; }
private:
wxString m_name;
std::shared_ptr<ArchiveViewNode> m_parent { nullptr };
std::vector<std::shared_ptr<ArchiveViewNode>> m_children;
bool m_toggle { false };
bool m_folder { false };
boost::filesystem::path m_fullpath;
bool m_container { false };
size_t m_size { 0 };
};
class ArchiveViewModel : public wxDataViewModel
{
public:
ArchiveViewModel(wxWindow* parent);
~ArchiveViewModel();
/* wxDataViewItem AddFolder(wxDataViewItem& parent, wxString name);
wxDataViewItem AddFile(wxDataViewItem& parent, wxString name);*/
std::shared_ptr<ArchiveViewNode> AddFile(std::shared_ptr<ArchiveViewNode> parent,const wxString& name, bool container);
wxString GetColumnType(unsigned int col) const override;
unsigned int GetColumnCount() const override { return 2; }
void Rescale();
void Delete(const wxDataViewItem& item);
void Clear();
wxDataViewItem GetParent(const wxDataViewItem& item) const override;
unsigned int GetChildren(const wxDataViewItem& parent, wxDataViewItemArray& array) const override;
void SetAssociatedControl(ArchiveViewCtrl* ctrl) { m_ctrl = ctrl; }
void GetValue(wxVariant& variant, const wxDataViewItem& item, unsigned int col) const override;
bool SetValue(const wxVariant& variant, const wxDataViewItem& item, unsigned int col) override;
void untoggle_folders(const wxDataViewItem& item);
bool IsEnabled(const wxDataViewItem& item, unsigned int col) const override;
bool IsContainer(const wxDataViewItem& item) const override;
// Is the container just a header or an item with all columns
// In our case it is an item with all columns
bool HasContainerColumns(const wxDataViewItem& WXUNUSED(item)) const override { return true; }
protected:
wxWindow* m_parent { nullptr };
ArchiveViewCtrl* m_ctrl { nullptr };
std::vector<std::shared_ptr<ArchiveViewNode>> m_top_children;
};
class ArchiveViewCtrl : public wxDataViewCtrl
{
public:
ArchiveViewCtrl(wxWindow* parent, wxSize size);
~ArchiveViewCtrl();
ArchiveViewModel* get_model() const {return m_model; }
protected:
ArchiveViewModel* m_model;
};
class FileArchiveDialog : public DPIDialog
{
public:
FileArchiveDialog(wxWindow* parent_window, mz_zip_archive* archive, std::vector<std::pair<boost::filesystem::path, size_t>>& selected_paths_w_size);
protected:
void on_dpi_changed(const wxRect& suggested_rect) override;
void on_open_button();
void on_all_button();
void on_none_button();
wxBoxSizer* create_btn_sizer();
// chosen files are written into this vector and returned to caller via reference.
// path in archive and decompressed size. The size can be used to distinguish between files with same path.
std::vector<std::pair<boost::filesystem::path,size_t>>& m_selected_paths_w_size;
ArchiveViewCtrl* m_avc;
std::vector<Button*> m_button_list;
};
} // namespace GU
} // namespace Slic3r
#endif // slic3r_GUI_FileArchiveDialog_hpp_

View File

@ -7,6 +7,7 @@
#include "slic3r/GUI/TaskManager.hpp"
#include "format.hpp"
#include "libslic3r_version.h"
#include "Downloader.hpp"
// Localization headers: include libslic3r version first so everything in this file
// uses the slic3r/GUI version (the macros will take precedence over the functions).
@ -517,6 +518,7 @@ static const FileWildcards file_wildcards_by_type[FT_SIZE] = {
/* FT_MODEL */
{"Supported files"sv, {".3mf"sv, ".stl"sv, ".oltp"sv, ".stp"sv, ".step"sv, ".svg"sv, ".amf"sv, ".obj"sv}},
#endif
/* FT_ZIP */ { "ZIP files"sv, { ".zip"sv } },
/* FT_PROJECT */ { "Project files"sv, { ".3mf"sv} },
/* FT_GALLERY */ { "Known files"sv, { ".stl"sv, ".obj"sv } },
@ -816,18 +818,25 @@ void GUI_App::post_init()
if (this->init_params->input_files.size() == 1 &&
boost::starts_with(this->init_params->input_files.front(), "orcaslicer://open")) {
auto input_str_arr = split_str(this->init_params->input_files.front(), "orcaslicer://open/?file=");
(boost::starts_with(this->init_params->input_files.front(), "orcaslicer://open") ||
boost::starts_with(this->init_params->input_files.front(), "prusaslicer://open"))) {
std::string download_origin_url;
for (auto input_str:input_str_arr) {
if (!input_str.empty()) download_origin_url = input_str;
}
if (boost::starts_with(this->init_params->input_files.front(), "prusaslicer://open")) {
switch_to_3d = true;
start_download(this->init_params->input_files.front());
} else if (vector<string> input_str_arr = split_str(this->init_params->input_files.front(), "orcaslicer://open/?file="); input_str_arr.size() > 1) {
std::string download_origin_url;
for (auto input_str : input_str_arr) {
if (!input_str.empty())
download_origin_url = input_str;
}
std::string download_file_url = url_decode(download_origin_url);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << download_file_url;
if (!download_file_url.empty() && ( boost::starts_with(download_file_url, "http://") || boost::starts_with(download_file_url, "https://")) ) {
request_model_download(download_origin_url);
std::string download_file_url = url_decode(download_origin_url);
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << download_file_url;
if (!download_file_url.empty() &&
(boost::starts_with(download_file_url, "http://") || boost::starts_with(download_file_url, "https://"))) {
request_model_download(download_file_url);
}
}
m_open_method = "makerworld";
}
@ -1089,6 +1098,7 @@ GUI_App::GUI_App()
, m_em_unit(10)
, m_imgui(new ImGuiWrapper())
, m_removable_drive_manager(std::make_unique<RemovableDriveManager>())
, m_downloader(std::make_unique<Downloader>())
, m_other_instance_message_handler(std::make_unique<OtherInstanceMessageHandler>())
{
//app config initializes early becasuse it is used in instance checking in OrcaSlicer.cpp
@ -2361,6 +2371,10 @@ bool GUI_App::on_init_inner()
associate_files(L"step");
associate_files(L"stp");
}
// Orca: add PS url functionality
if (app_config->get_bool("ps_url_registered")) {
associate_url(L"prusaslicer");
}
if (app_config->get("associate_gcode") == "true")
associate_files(L"gcode");
#endif // __WXMSW__
@ -3576,6 +3590,17 @@ void GUI_App::import_model(wxWindow *parent, wxArrayString& input_files) const
dialog.GetPaths(input_files);
}
void GUI_App::import_zip(wxWindow* parent, wxString& input_file) const
{
wxFileDialog dialog(parent ? parent : GetTopWindow(),
_L("Choose ZIP file") + ":",
from_u8(app_config->get_last_dir()), "",
file_wildcards(FT_ZIP), wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if (dialog.ShowModal() == wxID_OK)
input_file = dialog.GetPath();
}
void GUI_App::load_gcode(wxWindow* parent, wxString& input_file) const
{
input_file.Clear();
@ -5508,6 +5533,10 @@ void GUI_App::open_preferences(size_t open_on_tab, const std::string& highlight_
associate_files(L"step");
associate_files(L"stp");
}
// Orca: add PS url functionality
if (app_config->get_bool("ps_url_registered")) {
associate_url(L"prusaslicer");
}
}
else {
if (app_config->get("associate_gcode") == "true")
@ -5861,7 +5890,10 @@ void GUI_App::MacOpenURL(const wxString& url)
{
BOOST_LOG_TRIVIAL(trace) << __FUNCTION__ << "get mac url " << url;
if (!url.empty() && boost::starts_with(url, "orcasliceropen://")) {
if (url.empty())
return;
if (boost::starts_with(url, "orcasliceropen://")) {
auto input_str_arr = split_str(url.ToStdString(), "orcasliceropen://");
std::string download_origin_url;
@ -5880,7 +5912,8 @@ void GUI_App::MacOpenURL(const wxString& url)
m_download_file_url = download_file_url;
}
}
}
} else if (boost::starts_with(url, "prusasliceropen://"))
start_download(boost::nowide::narrow(url));
}
// wxWidgets override to get an event on open files.
@ -6004,6 +6037,11 @@ Model& GUI_App::model()
return plater_->model();
}
Downloader* GUI_App::downloader()
{
return m_downloader.get();
}
void GUI_App::load_url(wxString url)
{
if (mainframe)
@ -6593,9 +6631,60 @@ void GUI_App::disassociate_files(std::wstring extend)
::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr);
}
void GUI_App::associate_url(std::wstring url_prefix)
{
// Registry key creation for "prusaslicer://" URL
boost::filesystem::path binary_path(boost::filesystem::canonical(boost::dll::program_location()));
// the path to binary needs to be correctly saved in string with respect to localized characters
wxString wbinary = wxString::FromUTF8(binary_path.string());
std::string binary_string = (boost::format("%1%") % wbinary).str();
BOOST_LOG_TRIVIAL(info) << "Downloader registration: Path of binary: " << binary_string;
std::string key_string = "\"" + binary_string + "\" \"%1\"";
wxRegKey key_first(wxRegKey::HKCU, "Software\\Classes\\" + url_prefix);
wxRegKey key_full(wxRegKey::HKCU, "Software\\Classes\\" + url_prefix + "\\shell\\open\\command");
if (!key_first.Exists()) {
key_first.Create(false);
}
key_first.SetValue("URL Protocol", "");
if (!key_full.Exists()) {
key_full.Create(false);
}
key_full = key_string;
}
void GUI_App::disassociate_url(std::wstring url_prefix)
{
wxRegKey key_full(wxRegKey::HKCU, "Software\\Classes\\" + url_prefix + "\\shell\\open\\command");
if (!key_full.Exists()) {
return;
}
key_full = "";
}
#endif // __WXMSW__
void GUI_App::start_download(std::string url)
{
if (!plater_) {
BOOST_LOG_TRIVIAL(error) << "Could not start URL download: plater is nullptr.";
return;
}
//lets always init so if the download dest folder was changed, new dest is used
boost::filesystem::path dest_folder(app_config->get("download_path"));
if (dest_folder.empty() || !boost::filesystem::is_directory(dest_folder)) {
std::string msg = _u8L("Could not start URL download. Destination folder is not set. Please choose destination folder in Configuration Wizard.");
BOOST_LOG_TRIVIAL(error) << msg;
show_error(nullptr, msg);
return;
}
m_downloader->init(dest_folder);
m_downloader->start_download(url);
}
bool is_support_filament(int extruder_id)
{
auto &filament_presets = Slic3r::GUI::wxGetApp().preset_bundle->filament_presets;

View File

@ -74,6 +74,7 @@ class ObjectLayers;
class Plater;
class ParamsPanel;
class NotificationManager;
class Downloader;
struct GUI_InitParams;
class ParamsDialog;
class HMSQuery;
@ -90,6 +91,7 @@ enum FileType
FT_3MF,
FT_GCODE,
FT_MODEL,
FT_ZIP,
FT_PROJECT,
FT_GALLERY,
@ -275,6 +277,8 @@ private:
std::string m_instance_hash_string;
size_t m_instance_hash_int;
std::unique_ptr<Downloader> m_downloader;
//BBS
bool m_is_closing {false};
Slic3r::DeviceManager* m_device_manager { nullptr };
@ -423,6 +427,7 @@ private:
void keyboard_shortcuts();
void load_project(wxWindow *parent, wxString& input_file) const;
void import_model(wxWindow *parent, wxArrayString& input_files) const;
void import_zip(wxWindow* parent, wxString& input_file) const;
void load_gcode(wxWindow* parent, wxString& input_file) const;
wxString transition_tridid(int trid_id);
@ -559,6 +564,7 @@ private:
ParamsDialog* params_dialog();
Model& model();
NotificationManager * notification_manager();
Downloader* downloader();
std::string m_mall_model_download_url;
@ -644,7 +650,13 @@ private:
// extend is stl/3mf/gcode/step etc
void associate_files(std::wstring extend);
void disassociate_files(std::wstring extend);
void associate_url(std::wstring url_prefix);
void disassociate_url(std::wstring url_prefix);
#endif // __WXMSW__
// URL download - PrusaSlicer gets system call to open prusaslicer:// URL which should contain address of download
void start_download(std::string url);
std::string get_plugin_url(std::string name, std::string country_code);
int download_plugin(std::string name, std::string package_name, InstallProgressFn pro_fn = nullptr, WasCancelledFn cancel_fn = nullptr);
int install_plugin(std::string name, std::string package_name, InstallProgressFn pro_fn = nullptr, WasCancelledFn cancel_fn = nullptr);

View File

@ -136,6 +136,19 @@ static const std::map<const wchar_t, std::string> font_icons_large = {
{ImGui::PrevArrowBtnIcon, "notification_arrow_left" },
{ImGui::NextArrowBtnIcon, "notification_arrow_right" },
{ImGui::CompleteIcon, "notification_slicing_complete" },
{ImGui::PlayButton, "notification_play" },
{ImGui::PlayDarkButton, "notification_play_dark" },
{ImGui::PlayHoverButton, "notification_play_hover" },
{ImGui::PlayHoverDarkButton, "notification_play_hover_dark" },
{ImGui::PauseButton, "notification_pause" },
{ImGui::PauseDarkButton, "notification_pause_dark" },
{ImGui::PauseHoverButton, "notification_pause_hover" },
{ImGui::PauseHoverDarkButton, "notification_pause_hover_dark" },
{ImGui::OpenButton, "notification_open" },
{ImGui::OpenDarkButton, "notification_open_dark" },
{ImGui::OpenHoverButton, "notification_open_hover" },
{ImGui::OpenHoverDarkButton, "notification_open_hover_dark" },
};
static const std::map<const wchar_t, std::string> font_icons_extra_large = {

View File

@ -588,15 +588,9 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_
if (evt.CmdDown() && evt.GetKeyCode() == 'P')
#endif
{
PreferencesDialog dlg(this);
dlg.ShowModal();
// Orca: Use GUI_App::open_preferences instead of direct call so windows associations are updated on exit
wxGetApp().open_preferences();
plater()->get_current_canvas3D()->force_set_focus();
#if ENABLE_GCODE_LINES_ID_IN_H_SLIDER
if (dlg.seq_top_layer_only_changed() || dlg.seq_seq_top_gcode_indices_changed())
#else
if (dlg.seq_top_layer_only_changed())
#endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER
plater()->refresh_print();
return;
}
@ -2314,6 +2308,9 @@ void MainFrame::init_menubar_as_editor()
[this](wxCommandEvent&) { if (m_plater) { m_plater->add_model(); } }, "", nullptr,
[this](){return can_add_models(); }, this);
#endif
append_menu_item(import_menu, wxID_ANY, _L("Import Zip Archive") + dots, _L("Load models contained within a zip archive"),
[this](wxCommandEvent&) { if (m_plater) m_plater->import_zip_archive(); }, "menu_import", nullptr,
[this]() { return can_add_models(); });
append_menu_item(import_menu, wxID_ANY, _L("Import Configs") + dots /*+ "\tCtrl+I"*/, _L("Load configs"),
[this](wxCommandEvent&) { load_config_file(); }, "menu_import", nullptr,
[this](){return true; }, this);
@ -2787,15 +2784,9 @@ void MainFrame::init_menubar_as_editor()
append_menu_item(
m_topbar->GetTopMenu(), wxID_ANY, _L("Preferences") + "\t" + ctrl + "P", "",
[this](wxCommandEvent &) {
PreferencesDialog dlg(this);
dlg.ShowModal();
// Orca: Use GUI_App::open_preferences instead of direct call so windows associations are updated on exit
wxGetApp().open_preferences();
plater()->get_current_canvas3D()->force_set_focus();
#if ENABLE_GCODE_LINES_ID_IN_H_SLIDER
if (dlg.seq_top_layer_only_changed() || dlg.seq_seq_top_gcode_indices_changed())
#else
if (dlg.seq_top_layer_only_changed())
#endif
plater()->refresh_print();
},
"", nullptr, []() { return true; }, this);
//m_topbar->AddDropDownMenuItem(preference_item);

View File

@ -1283,6 +1283,219 @@ void NotificationManager::UpdatedItemsInfoNotification::add_type(InfoItemType ty
update(data);
}
//------URLDownloadNotification----------------
void NotificationManager::URLDownloadNotification::render_close_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
{
if (m_percentage < 0.f || m_percentage >= 1.f) {
render_close_button_inner(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y);
if (m_percentage >= 1.f)
render_open_button_inner(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y);
} else
render_pause_cancel_buttons_inner(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y);
}
void NotificationManager::URLDownloadNotification::render_close_button_inner(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
{
ImVec2 win_size(win_size_x, win_size_y);
ImVec2 win_pos(win_pos_x, win_pos_y);
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity);
push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity);
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f));
std::string button_text;
// Orca: Change based on dark mode
button_text = m_is_dark ? ImGui::CloseNotifDarkButton : ImGui::CloseNotifButton;
if (ImGui::IsMouseHoveringRect(ImVec2(win_pos.x - win_size.x / 10.f, win_pos.y),
ImVec2(win_pos.x, win_pos.y + win_size.y - (m_minimize_b_visible ? 2 * m_line_height : 0)),
true))
{
// Orca: Change based on dark mode
button_text = m_is_dark ? ImGui::CloseNotifHoverDarkButton : ImGui::CloseNotifHoverButton;
}
ImVec2 button_pic_size = ImGui::CalcTextSize(button_text.c_str());
ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f);
ImGui::SetCursorPosX(win_size.x - m_line_height * 2.75f);
ImGui::SetCursorPosY(win_size.y / 2 - button_size.y);
if (imgui.button(button_text.c_str(), button_size.x, button_size.y))
{
close();
}
//invisible large button
ImGui::SetCursorPosX(win_size.x - m_line_height * 2.35f);
ImGui::SetCursorPosY(0);
if (imgui.button(" ", m_line_height * 2.125, win_size.y - (m_minimize_b_visible ? 2 * m_line_height : 0)))
{
close();
}
ImGui::PopStyleColor(5);
}
void NotificationManager::URLDownloadNotification::render_pause_cancel_buttons_inner(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
{
render_cancel_button_inner(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y);
render_pause_button_inner(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y);
}
void NotificationManager::URLDownloadNotification::render_pause_button_inner(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
{
ImVec2 win_size(win_size_x, win_size_y);
ImVec2 win_pos(win_pos_x, win_pos_y);
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity);
push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity);
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f));
std::wstring button_text;
// Orca: Change based on dark mode
button_text = m_is_dark ? (m_download_paused ? ImGui::PlayDarkButton : ImGui::PauseDarkButton) : (m_download_paused ? ImGui::PlayButton : ImGui::PauseButton);
if (ImGui::IsMouseHoveringRect(ImVec2(win_pos.x - m_line_height * 5.f, win_pos.y),
ImVec2(win_pos.x - m_line_height * 2.5f, win_pos.y + win_size.y),
true))
{
// Orca: Change based on dark mode
button_text = m_is_dark ? (m_download_paused ? ImGui::PlayHoverDarkButton : ImGui::PauseHoverDarkButton) : (m_download_paused ? ImGui::PlayHoverButton : ImGui::PauseHoverButton);
}
ImVec2 button_pic_size = ImGui::CalcTextSize(boost::nowide::narrow(button_text).c_str());
ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f);
ImGui::SetCursorPosX(win_size.x - m_line_height * 5.0f);
ImGui::SetCursorPosY(win_size.y / 2 - button_size.y);
if (imgui.button(button_text.c_str(), button_size.x, button_size.y))
{
trigger_user_action_callback(m_download_paused ? DownloaderUserAction::DownloadUserContinued : DownloaderUserAction::DownloadUserPaused);
}
//invisible large button
ImGui::SetCursorPosX(win_size.x - m_line_height * 4.625f);
ImGui::SetCursorPosY(0);
if (imgui.button(" ", m_line_height * 2.f, win_size.y))
{
trigger_user_action_callback(m_download_paused ? DownloaderUserAction::DownloadUserContinued : DownloaderUserAction::DownloadUserPaused);
}
ImGui::PopStyleColor(5);
}
void NotificationManager::URLDownloadNotification::render_open_button_inner(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
{
ImVec2 win_size(win_size_x, win_size_y);
ImVec2 win_pos(win_pos_x, win_pos_y);
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity);
push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity);
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f));
std::wstring button_text;
// Orca: Change based on dark mode
button_text = m_is_dark ? ImGui::OpenDarkButton : ImGui::OpenButton;
if (ImGui::IsMouseHoveringRect(ImVec2(win_pos.x - m_line_height * 5.f, win_pos.y),
ImVec2(win_pos.x - m_line_height * 2.5f, win_pos.y + win_size.y),
true))
{
// Orca: Change based on dark mode
button_text = m_is_dark ? ImGui::OpenHoverDarkButton : ImGui::OpenHoverButton;
}
ImVec2 button_pic_size = ImGui::CalcTextSize(boost::nowide::narrow(button_text).c_str());
ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f);
ImGui::SetCursorPosX(win_size.x - m_line_height * 5.0f);
ImGui::SetCursorPosY(win_size.y / 2 - button_size.y);
if (imgui.button(button_text.c_str(), button_size.x, button_size.y))
{
trigger_user_action_callback(DownloaderUserAction::DownloadUserOpenedFolder);
}
//invisible large button
ImGui::SetCursorPosX(win_size.x - m_line_height * 4.625f);
ImGui::SetCursorPosY(0);
if (imgui.button(" ", m_line_height * 2.f, win_size.y))
{
trigger_user_action_callback(DownloaderUserAction::DownloadUserOpenedFolder);
}
ImGui::PopStyleColor(5);
}
void NotificationManager::URLDownloadNotification::render_cancel_button_inner(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
{
ImVec2 win_size(win_size_x, win_size_y);
ImVec2 win_pos(win_pos_x, win_pos_y);
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity);
push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity);
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f));
std::string button_text;
button_text = ImGui::CancelButton;
if (ImGui::IsMouseHoveringRect(ImVec2(win_pos.x - win_size.x / 10.f, win_pos.y),
ImVec2(win_pos.x, win_pos.y + win_size.y - (m_minimize_b_visible ? 2 * m_line_height : 0)),
true))
{
button_text = ImGui::CancelHoverButton;
}
ImVec2 button_pic_size = ImGui::CalcTextSize(button_text.c_str());
ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f);
ImGui::SetCursorPosX(win_size.x - m_line_height * 2.75f);
ImGui::SetCursorPosY(win_size.y / 2 - button_size.y);
if (imgui.button(button_text.c_str(), button_size.x, button_size.y))
{
trigger_user_action_callback(DownloaderUserAction::DownloadUserCanceled);
}
//invisible large button
ImGui::SetCursorPosX(win_size.x - m_line_height * 2.35f);
ImGui::SetCursorPosY(0);
if (imgui.button(" ", m_line_height * 2.125, win_size.y - (m_minimize_b_visible ? 2 * m_line_height : 0)))
{
trigger_user_action_callback(DownloaderUserAction::DownloadUserCanceled);
}
ImGui::PopStyleColor(5);
}
void NotificationManager::URLDownloadNotification::trigger_user_action_callback(DownloaderUserAction action)
{
if (m_user_action_callback) {
if (m_user_action_callback(action, m_download_id)) {}
}
}
void NotificationManager::URLDownloadNotification::render_bar(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
{
ProgressBarNotification::render_bar(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y);
std::string text;
if (m_percentage < 0.f) {
text = _u8L("ERROR") + ": " + m_error_message;
} else if (m_percentage >= 1.f) {
text = _u8L("COMPLETED");
} else {
std::stringstream stream;
stream << std::fixed << std::setprecision(2) << (int)(m_percentage * 100) << "%";
text = stream.str();
}
ImGui::SetCursorPosX(m_left_indentation);
ImGui::SetCursorPosY(win_size_y / 2 + win_size_y / 6 - (m_multiline ? 0 : m_line_height / 4));
imgui.text(text.c_str());
}
void NotificationManager::URLDownloadNotification::count_spaces()
{
ProgressBarNotification::count_spaces();
m_window_width_offset = m_line_height * 6;
}
//------PrintHostUploadNotification----------------
void NotificationManager::PrintHostUploadNotification::init()
{
@ -1841,6 +2054,81 @@ void NotificationManager::push_import_finished_notification(const std::string& p
set_slicing_progress_hidden();
}
void NotificationManager::push_download_URL_progress_notification(size_t id, const std::string& text, std::function<bool(DownloaderUserAction, int)> user_action_callback)
{
// If already exists
for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) {
if (notification->get_type() == NotificationType::URLDownload && dynamic_cast<URLDownloadNotification*>(notification.get())->get_download_id() == id) {
return;
}
}
// push new one
NotificationData data{ NotificationType::URLDownload, NotificationLevel::ProgressBarNotificationLevel, 5, _u8L("Download") + ": " + text };
push_notification_data(std::make_unique<NotificationManager::URLDownloadNotification>(data, m_id_provider, m_evt_handler, id, user_action_callback), 0);
}
void NotificationManager::set_download_URL_progress(size_t id, float percentage)
{
for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) {
if (notification->get_type() == NotificationType::URLDownload) {
URLDownloadNotification* ntf = dynamic_cast<URLDownloadNotification*>(notification.get());
if (ntf->get_download_id() != id)
continue;
// if this changes the percentage, it should be shown now
float percent_b4 = ntf->get_percentage();
ntf->set_percentage(percentage);
ntf->set_paused(false);
if (ntf->get_percentage() != percent_b4)
wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0);
return;
}
}
}
void NotificationManager::set_download_URL_paused(size_t id)
{
for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) {
if (notification->get_type() == NotificationType::URLDownload) {
URLDownloadNotification* ntf = dynamic_cast<URLDownloadNotification*>(notification.get());
if (ntf->get_download_id() != id)
continue;
ntf->set_paused(true);
wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0);
return;
}
}
}
void NotificationManager::set_download_URL_canceled(size_t id)
{
for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) {
if (notification->get_type() == NotificationType::URLDownload) {
URLDownloadNotification* ntf = dynamic_cast<URLDownloadNotification*>(notification.get());
if (ntf->get_download_id() != id)
continue;
ntf->close();
wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0);
return;
}
}
}
void NotificationManager::set_download_URL_error(size_t id, const std::string& text)
{
for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) {
if (notification->get_type() == NotificationType::URLDownload) {
URLDownloadNotification* ntf = dynamic_cast<URLDownloadNotification*>(notification.get());
if (ntf->get_download_id() != id)
continue;
float percent_b4 = ntf->get_percentage();
ntf->set_percentage(-1.f);
ntf->set_error_message(text);
if (ntf->get_percentage() != percent_b4)
wxGetApp().plater()->get_current_canvas3D()->schedule_extra_frame(0);
return;
}
}
}
void NotificationManager::push_upload_job_notification(int id, float filesize, const std::string& filename, const std::string& host, float percentage)
{
// find if upload with same id was not already in notification

View File

@ -11,6 +11,7 @@
#include "Event.hpp"
#include "I18N.hpp"
#include "Jobs/ProgressIndicator.hpp"
#include "Downloader.hpp"
#include <libslic3r/ObjectID.hpp>
#include <libslic3r/Technologies.hpp>
@ -129,6 +130,8 @@ enum class NotificationType
NetfabbFinished,
// Short meesage to fill space between start and finish of export
ExportOngoing,
// Progressbar of download from prusaslicer://url
URLDownload,
// BBS: Short meesage to fill space between start and finish of arranging
ArrangeOngoing,
// BBL: Plate Info ,Design For @YangLeDuo
@ -247,6 +250,14 @@ public:
// Exporting finished, show this information with path, button to open containing folder and if ejectable - eject button
void push_exporting_finished_notification(const std::string& path, const std::string& dir_path, bool on_removable);
void push_import_finished_notification(const std::string& path, const std::string& dir_path, bool on_removable);
// Download URL progress notif
void push_download_URL_progress_notification(size_t id, const std::string& text, std::function<bool(DownloaderUserAction, int)> user_action_callback);
void set_download_URL_progress(size_t id, float percentage);
void set_download_URL_paused(size_t id);
void set_download_URL_canceled(size_t id);
void set_download_URL_error(size_t id, const std::string& text);
// notifications with progress bar
// slicing progress
void init_slicing_progress_notification(std::function<bool()> cancel_callback);
@ -593,6 +604,7 @@ private:
ProgressBarNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler) : PopNotification(n, id_provider, evt_handler) { }
virtual void set_percentage(float percent) { m_percentage = percent; }
float get_percentage() const { return m_percentage; }
protected:
virtual void init() override;
virtual void render_text(ImGuiWrapper& imgui,
@ -615,6 +627,62 @@ private:
};
class URLDownloadNotification : public ProgressBarNotification
{
public:
URLDownloadNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, size_t download_id, std::function<bool(DownloaderUserAction, int)> user_action_callback)
//: ProgressBarWithCancelNotification(n, id_provider, evt_handler, cancel_callback)
: ProgressBarNotification(n, id_provider, evt_handler)
, m_download_id(download_id)
, m_user_action_callback(user_action_callback)
{
}
void set_percentage(float percent) override
{
m_percentage = percent;
if (m_percentage >= 1.f) {
m_notification_start = GLCanvas3D::timestamp_now();
m_state = EState::Shown;
} else
m_state = EState::NotFading;
}
size_t get_download_id() { return m_download_id; }
void set_user_action_callback(std::function<bool(DownloaderUserAction, int)> user_action_callback) { m_user_action_callback = user_action_callback; }
void set_paused(bool paused) { m_download_paused = paused; }
void set_error_message(const std::string& message) { m_error_message = message; }
bool compare_text(const std::string& text) const override { return false; };
protected:
void render_close_button(ImGuiWrapper& imgui,
const float win_size_x, const float win_size_y,
const float win_pos_x, const float win_pos_y) override;
void render_close_button_inner(ImGuiWrapper& imgui,
const float win_size_x, const float win_size_y,
const float win_pos_x, const float win_pos_y);
void render_pause_cancel_buttons_inner(ImGuiWrapper& imgui,
const float win_size_x, const float win_size_y,
const float win_pos_x, const float win_pos_y);
void render_open_button_inner(ImGuiWrapper& imgui,
const float win_size_x, const float win_size_y,
const float win_pos_x, const float win_pos_y);
void render_cancel_button_inner(ImGuiWrapper& imgui,
const float win_size_x, const float win_size_y,
const float win_pos_x, const float win_pos_y);
void render_pause_button_inner(ImGuiWrapper& imgui,
const float win_size_x, const float win_size_y,
const float win_pos_x, const float win_pos_y);
void render_bar(ImGuiWrapper& imgui,
const float win_size_x, const float win_size_y,
const float win_pos_x, const float win_pos_y) override;
void trigger_user_action_callback(DownloaderUserAction action);
void count_spaces() override;
size_t m_download_id;
std::function<bool(DownloaderUserAction, int)> m_user_action_callback;
bool m_download_paused {false};
std::string m_error_message;
};
class PrintHostUploadNotification : public ProgressBarNotification
{
public:

View File

@ -156,6 +156,7 @@
#include <libslic3r/CutUtils.hpp>
#include <wx/glcanvas.h> // Needs to be last because reasons :-/
#include <libslic3r/miniz_extension.hpp>
#include "WipeTowerDialog.hpp"
#include "ObjColorDialog.hpp"
@ -168,6 +169,7 @@
#include "PlateSettingsDialog.hpp"
#include "DailyTips.hpp"
#include "CreatePresetsDialog.hpp"
#include "FileArchiveDialog.hpp"
using boost::optional;
namespace fs = boost::filesystem;
@ -8996,8 +8998,9 @@ void Plater::import_model_id(wxString download_info)
/* prepare project and profile */
boost::thread import_thread = Slic3r::create_thread([&percent, &cont, &cancel, &retry_count, max_retries, &msg, &target_path, &download_ok, download_url, &filename] {
NetworkAgent* m_agent = Slic3r::GUI::wxGetApp().getAgent();
if (!m_agent) return;
// Orca: NetworkAgent is not needed and only prevents this from running
// NetworkAgent* m_agent = Slic3r::GUI::wxGetApp().getAgent();
// if (!m_agent) return;
int res = 0;
unsigned int http_code;
@ -9148,7 +9151,11 @@ void Plater::import_model_id(wxString download_info)
if (download_ok) {
BOOST_LOG_TRIVIAL(trace) << "import_model_id: target_path = " << target_path.string();
/* load project */
this->load_project(target_path.wstring());
// Orca: If download is a zip file, treat it as if file has been drag and dropped on the plater
if (target_path.extension() == ".zip")
this->load_files(wxArrayString(1, target_path.string()));
else
this->load_project(target_path.wstring());
/*BBS set project info after load project, project info is reset in load project */
//p->project.project_model_id = model_id;
//p->project.project_design_id = design_id;
@ -9818,6 +9825,19 @@ void Plater::calib_VFA(const Calib_Params& params)
p->background_process.fff_print()->set_calib_params(params);
}
BuildVolume_Type Plater::get_build_volume_type() const { return p->bed.get_build_volume_type(); }
void Plater::import_zip_archive()
{
wxString input_file;
wxGetApp().import_zip(this, input_file);
if (input_file.empty())
return;
wxArrayString arr;
arr.Add(input_file);
load_files(arr);
}
void Plater::import_sl1_archive()
{
auto &w = get_ui_job_worker();
@ -10003,6 +10023,186 @@ std::vector<size_t> Plater::load_files(const std::vector<std::string>& input_fil
return p->load_files(paths, strategy, ask_multi);
}
bool Plater::preview_zip_archive(const boost::filesystem::path& archive_path)
{
//std::vector<fs::path> unzipped_paths;
std::vector<fs::path> non_project_paths;
std::vector<fs::path> project_paths;
try
{
mz_zip_archive archive;
mz_zip_zero_struct(&archive);
if (!open_zip_reader(&archive, archive_path.string())) {
// TRN %1% is archive path
std::string err_msg = GUI::format(_u8L("Loading of a ZIP archive on path %1% has failed."), archive_path.string());
throw Slic3r::FileIOError(err_msg);
}
mz_uint num_entries = mz_zip_reader_get_num_files(&archive);
mz_zip_archive_file_stat stat;
// selected_paths contains paths and its uncompressed size. The size is used to distinguish between files with same path.
std::vector<std::pair<fs::path, size_t>> selected_paths;
FileArchiveDialog dlg(static_cast<wxWindow*>(wxGetApp().mainframe), &archive, selected_paths);
if (dlg.ShowModal() == wxID_OK)
{
std::string archive_path_string = archive_path.string();
archive_path_string = archive_path_string.substr(0, archive_path_string.size() - 4);
fs::path archive_dir(wxStandardPaths::Get().GetTempDir().utf8_str().data());
for (auto& path_w_size : selected_paths) {
const fs::path& path = path_w_size.first;
size_t size = path_w_size.second;
// find path in zip archive
for (mz_uint i = 0; i < num_entries; ++i) {
if (mz_zip_reader_file_stat(&archive, i, &stat)) {
if (size != stat.m_uncomp_size) // size must fit
continue;
wxString wname = boost::nowide::widen(stat.m_filename);
std::string name = boost::nowide::narrow(wname);
fs::path archive_path(name);
std::string extra(1024, 0);
size_t extra_size = mz_zip_reader_get_filename_from_extra(&archive, i, extra.data(), extra.size());
if (extra_size > 0) {
archive_path = fs::path(extra.substr(0, extra_size));
name = archive_path.string();
}
if (archive_path.empty())
continue;
if (path != archive_path)
continue;
// decompressing
try
{
std::replace(name.begin(), name.end(), '\\', '/');
// rename if file exists
std::string filename = path.filename().string();
std::string extension = boost::filesystem::extension(path);
std::string just_filename = filename.substr(0, filename.size() - extension.size());
std::string final_filename = just_filename;
size_t version = 0;
while (fs::exists(archive_dir / (final_filename + extension)))
{
++version;
final_filename = just_filename + "(" + std::to_string(version) + ")";
}
filename = final_filename + extension;
fs::path final_path = archive_dir / filename;
std::string buffer((size_t)stat.m_uncomp_size, 0);
// Decompress action. We already has correct file index in stat structure.
mz_bool res = mz_zip_reader_extract_to_mem(&archive, stat.m_file_index, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0);
if (res == 0) {
// TRN: First argument = path to file, second argument = error description
wxString error_log = GUI::format_wxstr(_L("Failed to unzip file to %1%: %2%"), final_path.string(), mz_zip_get_error_string(mz_zip_get_last_error(&archive)));
BOOST_LOG_TRIVIAL(error) << error_log;
show_error(nullptr, error_log);
break;
}
// write buffer to file
fs::fstream file(final_path, std::ios::out | std::ios::binary | std::ios::trunc);
file.write(buffer.c_str(), buffer.size());
file.close();
if (!fs::exists(final_path)) {
wxString error_log = GUI::format_wxstr(_L("Failed to find unzipped file at %1%. Unzipping of file has failed."), final_path.string());
BOOST_LOG_TRIVIAL(error) << error_log;
show_error(nullptr, error_log);
break;
}
BOOST_LOG_TRIVIAL(info) << "Unzipped " << final_path;
if (!boost::algorithm::iends_with(filename, ".3mf") && !boost::algorithm::iends_with(filename, ".amf")) {
non_project_paths.emplace_back(final_path);
break;
}
// if 3mf - read archive headers to find project file
if (/*(boost::algorithm::iends_with(filename, ".3mf") && !is_project_3mf(final_path.string())) ||*/
(boost::algorithm::iends_with(filename, ".amf") && !boost::algorithm::iends_with(filename, ".zip.amf"))) {
non_project_paths.emplace_back(final_path);
break;
}
project_paths.emplace_back(final_path);
break;
}
catch (const std::exception& e)
{
// ensure the zip archive is closed and rethrow the exception
close_zip_reader(&archive);
throw Slic3r::FileIOError(e.what());
}
}
}
}
close_zip_reader(&archive);
if (non_project_paths.size() + project_paths.size() != selected_paths.size())
BOOST_LOG_TRIVIAL(error) << "Decompresing of archive did not retrieve all files. Expected files: "
<< selected_paths.size()
<< " Decopressed files: "
<< non_project_paths.size() + project_paths.size();
} else {
close_zip_reader(&archive);
return false;
}
}
catch (const Slic3r::FileIOError& e) {
// zip reader should be already closed or not even opened
GUI::show_error(this, e.what());
return false;
}
// none selected
if (project_paths.empty() && non_project_paths.empty())
{
return false;
}
// 1 project file and some models - behave like drag n drop of 3mf and then load models
if (project_paths.size() == 1)
{
wxArrayString aux;
aux.Add(from_u8(project_paths.front().string()));
bool loaded3mf = load_files(aux);
load_files(non_project_paths, LoadStrategy::LoadModel);
boost::system::error_code ec;
if (loaded3mf) {
fs::remove(project_paths.front(), ec);
if (ec)
BOOST_LOG_TRIVIAL(error) << ec.message();
}
for (const fs::path& path : non_project_paths) {
// Delete file from temp file (path variable), it will stay only in app memory.
boost::system::error_code ec;
fs::remove(path, ec);
if (ec)
BOOST_LOG_TRIVIAL(error) << ec.message();
}
return true;
}
// load all projects and all models as geometry
load_files(project_paths, LoadStrategy::LoadModel);
load_files(non_project_paths, LoadStrategy::LoadModel);
for (const fs::path& path : project_paths) {
// Delete file from temp file (path variable), it will stay only in app memory.
boost::system::error_code ec;
fs::remove(path, ec);
if (ec)
BOOST_LOG_TRIVIAL(error) << ec.message();
}
for (const fs::path& path : non_project_paths) {
// Delete file from temp file (path variable), it will stay only in app memory.
boost::system::error_code ec;
fs::remove(path, ec);
if (ec)
BOOST_LOG_TRIVIAL(error) << ec.message();
}
return true;
}
class RadioBox;
class RadioSelector
{
@ -10341,7 +10541,7 @@ void ProjectDropDialog::on_dpi_changed(const wxRect& suggested_rect)
//BBS: remove GCodeViewer as seperate APP logic
bool Plater::load_files(const wxArrayString& filenames)
{
const std::regex pattern_drop(".*[.](stp|step|stl|oltp|obj|amf|3mf|svg)", std::regex::icase);
const std::regex pattern_drop(".*[.](stp|step|stl|oltp|obj|amf|3mf|svg|zip)", std::regex::icase);
const std::regex pattern_gcode_drop(".*[.](gcode|g)", std::regex::icase);
std::vector<fs::path> normal_paths;
@ -10431,6 +10631,21 @@ bool Plater::load_files(const wxArrayString& filenames)
}
}
// Orca: Iters through given paths and imports files from zip then remove zip from paths
// returns true if zip files were found
auto handle_zips = [this](vector<fs::path>& paths) { // NOLINT(*-no-recursion) - Recursion is intended and should be managed properly
bool res = false;
for (auto it = paths.begin(); it != paths.end();) {
if (boost::algorithm::iends_with(it->string(), ".zip")) {
res = true;
preview_zip_archive(*it);
it = paths.erase(it);
} else
it++;
}
return res;
};
switch (loadfiles_type) {
case LoadFilesType::Single3MF:
open_3mf_file(normal_paths[0]);
@ -10438,6 +10653,7 @@ bool Plater::load_files(const wxArrayString& filenames)
case LoadFilesType::SingleOther: {
Plater::TakeSnapshot snapshot(this, snapshot_label);
if (handle_zips(normal_paths)) return true;
if (load_files(normal_paths, LoadStrategy::LoadModel, false).empty()) { res = false; }
break;
}
@ -10453,6 +10669,9 @@ bool Plater::load_files(const wxArrayString& filenames)
case LoadFilesType::MultipleOther: {
Plater::TakeSnapshot snapshot(this, snapshot_label);
if (handle_zips(normal_paths)) {
if (normal_paths.empty()) return true;
}
if (load_files(normal_paths, LoadStrategy::LoadModel, true).empty()) { res = false; }
break;
}
@ -10471,6 +10690,9 @@ bool Plater::load_files(const wxArrayString& filenames)
open_3mf_file(first_file[0]);
if (load_files(tmf_file, LoadStrategy::LoadModel).empty()) { res = false; }
if (res && handle_zips(other_file)) {
if (normal_paths.empty()) return true;
}
if (load_files(other_file, LoadStrategy::LoadModel, false).empty()) { res = false; }
break;
default: break;

View File

@ -262,6 +262,7 @@ public:
int get_3mf_file_count(std::vector<fs::path> paths);
void add_file();
void add_model(bool imperial_units = false, std::string fname = "");
void import_zip_archive();
void import_sl1_archive();
void extract_config_from_project();
void load_gcode();
@ -298,6 +299,8 @@ public:
static wxColour get_next_color_for_filament();
static wxString get_slice_warning_string(GCodeProcessorResult::SliceWarning& warning);
bool preview_zip_archive(const boost::filesystem::path& archive_path);
// BBS: restore
std::vector<size_t> load_files(const std::vector<boost::filesystem::path>& input_files, LoadStrategy strategy = LoadStrategy::LoadModel | LoadStrategy::LoadConfig, bool ask_multi = false);
// To be called when providing a list of files to the GUI slic3r on command line.
@ -803,7 +806,7 @@ private:
bool m_only_gcode { false };
bool m_exported_file { false };
bool skip_thumbnail_invalid { false };
bool m_loading_project {false };
bool m_loading_project { false };
std::string m_preview_only_filename;
int m_valid_plates_count { 0 };

View File

@ -1088,6 +1088,8 @@ wxWindow* PreferencesDialog::create_general_page()
//downloads
auto title_downloads = create_item_title(_L("Downloads"), page, _L("Downloads"));
auto item_downloads = create_item_downloads(page,50,"download_path");
auto ps_download_url_registered = create_item_checkbox(_L("Allow downloads from Printables.com"), page,
_L("Allow downloads from Printables.com"), 50, "ps_url_registered");
//dark mode
#ifdef _WIN32
@ -1150,6 +1152,7 @@ wxWindow* PreferencesDialog::create_general_page()
sizer_page->Add(title_downloads, 0, wxTOP| wxEXPAND, FromDIP(20));
sizer_page->Add(item_downloads, 0, wxEXPAND, FromDIP(3));
sizer_page->Add(ps_download_url_registered, 0, wxTOP | wxEXPAND, FromDIP(20));
#ifdef _WIN32
sizer_page->Add(title_darkmode, 0, wxTOP | wxEXPAND, FromDIP(20));

View File

@ -138,6 +138,7 @@ struct Http::priv
void set_post_body(const std::string &body);
void set_put_body(const fs::path &path);
void set_del_body(const std::string& body);
void set_range(const std::string &range);
std::string curl_error(CURLcode curlcode);
std::string body_size_error();
@ -237,7 +238,7 @@ int Http::priv::xfercb(void *userp, curl_off_t dltotal, curl_off_t dlnow, curl_o
curl_easy_getinfo(self->curl, CURLINFO_SPEED_UPLOAD, &speed);
if (speed > 0.01)
speed = speed;
Progress progress(dltotal, dlnow, ultotal, ulnow, speed);
Progress progress(dltotal, dlnow, ultotal, ulnow, self->buffer, speed);
self->progressfn(progress, cb_cancel);
}
@ -370,6 +371,11 @@ void Http::priv::set_del_body(const std::string& body)
postfields = body;
}
void Http::priv::set_range(const std::string& range)
{
::curl_easy_setopt(curl, CURLOPT_RANGE, range.c_str());
}
std::string Http::priv::curl_error(CURLcode curlcode)
{
return (boost::format("curl:%1%:\n%2%\n[Error %3%]")
@ -434,7 +440,7 @@ void Http::priv::http_perform()
if (res == CURLE_ABORTED_BY_CALLBACK) {
if (cancel) {
// The abort comes from the request being cancelled programatically
Progress dummyprogress(0, 0, 0, 0);
Progress dummyprogress(0, 0, 0, 0, std::string());
bool cancel = true;
if (progressfn) { progressfn(dummyprogress, cancel); }
} else {
@ -510,6 +516,12 @@ Http& Http::size_limit(size_t sizeLimit)
return *this;
}
Http& Http::set_range(const std::string& range)
{
if (p) { p->set_range(range); }
return *this;
}
Http& Http::header(std::string name, const std::string &value)
{
if (!p) { return * this; }

View File

@ -42,14 +42,15 @@ public:
size_t dlnow; // Bytes downloaded so far
size_t ultotal; // Total bytes to upload
size_t ulnow; // Bytes uploaded so far
const std::string& buffer; // reference to buffer containing all data
double upload_spd{0.0f};
Progress(size_t dltotal, size_t dlnow, size_t ultotal, size_t ulnow) :
dltotal(dltotal), dlnow(dlnow), ultotal(ultotal), ulnow(ulnow)
Progress(size_t dltotal, size_t dlnow, size_t ultotal, size_t ulnow, const std::string& buffer) :
dltotal(dltotal), dlnow(dlnow), ultotal(ultotal), ulnow(ulnow), buffer(buffer)
{}
Progress(size_t dltotal, size_t dlnow, size_t ultotal, size_t ulnow, double ulspd) :
dltotal(dltotal), dlnow(dlnow), ultotal(ultotal), ulnow(ulnow), upload_spd(ulspd)
Progress(size_t dltotal, size_t dlnow, size_t ultotal, size_t ulnow, const std::string& buffer, double ulspd) :
dltotal(dltotal), dlnow(dlnow), ultotal(ultotal), ulnow(ulnow), buffer(buffer), upload_spd(ulspd)
{}
};
@ -102,6 +103,8 @@ public:
// Sets a maximum size of the data that can be received.
// A value of zero sets the default limit, which is is 5MB.
Http& size_limit(size_t sizeLimit);
// range of donloaded bytes. example: curl_easy_setopt(curl, CURLOPT_RANGE, "0-199");
Http& set_range(const std::string& range);
// Sets a HTTP header field.
Http& header(std::string name, const std::string &value);
// Removes a header field.