Merge remote-tracking branch 'origin/ys_ph_printers'
This commit is contained in:
commit
dae37a8c0d
10
resources/icons/cancel.svg
Normal file
10
resources/icons/cancel.svg
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
|
||||
<g id="resin">
|
||||
<rect x="4" y="7" fill="#ED6B21" width="8" height="8"/>
|
||||
<path fill="none" stroke="#808080" stroke-linecap="round" stroke-miterlimit="10" d="M4.5,15h6.99c0.28,0,0.5-0.23,0.5-0.5V6
|
||||
c0-1-2-1-2-2s0-1,0-1h1V1.5C11,1.23,10.77,1,10.5,1H5.5C5.23,1,5,1.23,5,1.5V3h1v1c0,1-2,1-2,2v8.5C4,14.77,4.23,15,4.5,15z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 671 B |
81
resources/icons/cross_focus_large.svg
Normal file
81
resources/icons/cross_focus_large.svg
Normal file
@ -0,0 +1,81 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
|
||||
sodipodi:docname="cross_megafocus.svg"
|
||||
xml:space="preserve"
|
||||
enable-background="new 0 0 16 16"
|
||||
viewBox="0 0 16 16"
|
||||
y="0px"
|
||||
x="0px"
|
||||
id="Layer_1"
|
||||
version="1.0"><metadata
|
||||
id="metadata16"><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="defs14" /><sodipodi:namedview
|
||||
inkscape:current-layer="Layer_1"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:window-y="-9"
|
||||
inkscape:window-x="-9"
|
||||
inkscape:cy="8"
|
||||
inkscape:cx="8"
|
||||
inkscape:zoom="47.0625"
|
||||
showgrid="false"
|
||||
id="namedview12"
|
||||
inkscape:window-height="1721"
|
||||
inkscape:window-width="3200"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0"
|
||||
guidetolerance="10"
|
||||
gridtolerance="10"
|
||||
objecttolerance="10"
|
||||
borderopacity="1"
|
||||
bordercolor="#666666"
|
||||
pagecolor="#ffffff" />
|
||||
<g
|
||||
style="opacity:1;fill-opacity:1"
|
||||
transform="matrix(1.1,0,0,1.1,-0.8,-0.8)"
|
||||
id="cross">
|
||||
<g
|
||||
style="fill-opacity:1"
|
||||
id="g4">
|
||||
|
||||
<line
|
||||
style="fill-opacity:1"
|
||||
id="line2"
|
||||
y2="14"
|
||||
x2="2"
|
||||
y1="2"
|
||||
x1="14"
|
||||
stroke-miterlimit="10"
|
||||
stroke-linecap="round"
|
||||
stroke-width="3"
|
||||
stroke="#ed6b21"
|
||||
fill="none" />
|
||||
</g>
|
||||
<g
|
||||
style="fill-opacity:1"
|
||||
id="g8">
|
||||
|
||||
<line
|
||||
style="fill-opacity:1"
|
||||
id="line6"
|
||||
y2="14"
|
||||
x2="14"
|
||||
y1="2"
|
||||
x1="2"
|
||||
stroke-miterlimit="10"
|
||||
stroke-linecap="round"
|
||||
stroke-width="3"
|
||||
stroke="#ed6b21"
|
||||
fill="none" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.0 KiB |
27
resources/icons/ironing.svg
Normal file
27
resources/icons/ironing.svg
Normal file
@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
|
||||
<g id="ironing">
|
||||
<g>
|
||||
<path fill="#ED6B21" d="M14,9.42H2c-0.39,0-0.71-0.32-0.71-0.71C1.29,7.08,2.07,4,5,4h8c0.33,0,0.61,0.22,0.69,0.54l1,4
|
||||
c0.05,0.21,0,0.44-0.13,0.61C14.42,9.32,14.22,9.42,14,9.42z M2.77,8h10.32l-0.65-2.58H5C3.39,5.42,2.91,7.03,2.77,8z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#ED6B21" d="M13,5.42c-0.39,0-0.71-0.32-0.71-0.71v-1c0-1.18-0.99-1.29-1.3-1.29H6c-0.39,0-0.71-0.32-0.71-0.71
|
||||
S5.61,1,6,1h5c1.05,0,2.61,0.68,2.7,2.52c0,0.03,0,0.06,0,0.08v1.1C13.71,5.1,13.39,5.42,13,5.42z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#808080" d="M14.65,15H1.35C1.16,15,1,14.84,1,14.65s0.16-0.35,0.35-0.35h13.29c0.2,0,0.35,0.16,0.35,0.35
|
||||
S14.84,15,14.65,15z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#808080" d="M14.65,13H1.35C1.16,13,1,12.84,1,12.65s0.16-0.35,0.35-0.35h13.29c0.2,0,0.35,0.16,0.35,0.35
|
||||
S14.84,13,14.65,13z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#808080" d="M14.65,11H1.35C1.16,11,1,10.84,1,10.65s0.16-0.35,0.35-0.35h13.29c0.2,0,0.35,0.16,0.35,0.35
|
||||
S14.84,11,14.65,11z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
6
resources/icons/tick_mark.svg
Normal file
6
resources/icons/tick_mark.svg
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
|
||||
<g transform="translate(0 -292.77)">
|
||||
<path d="m2.2728 301.7 3.2932 4.8789 8.3542-11.435" fill="none" stroke="#00af00" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.9994"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 319 B |
72
resources/icons/timer_dot.svg
Normal file
72
resources/icons/timer_dot.svg
Normal file
@ -0,0 +1,72 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
|
||||
sodipodi:docname="timer_dot.svg"
|
||||
xml:space="preserve"
|
||||
enable-background="new 0 0 16 16"
|
||||
viewBox="0 0 16 16"
|
||||
y="0px"
|
||||
x="0px"
|
||||
id="Layer_1"
|
||||
version="1.0"><metadata
|
||||
id="metadata11"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs9"><linearGradient
|
||||
id="linearGradient830"
|
||||
inkscape:collect="always"><stop
|
||||
id="stop826"
|
||||
offset="0"
|
||||
style="stop-color:#000000;stop-opacity:1;" /><stop
|
||||
id="stop828"
|
||||
offset="1"
|
||||
style="stop-color:#000000;stop-opacity:0;" /></linearGradient><radialGradient
|
||||
gradientUnits="userSpaceOnUse"
|
||||
r="3.5"
|
||||
fy="8"
|
||||
fx="8"
|
||||
cy="8"
|
||||
cx="8"
|
||||
id="radialGradient832"
|
||||
xlink:href="#linearGradient830"
|
||||
inkscape:collect="always" /></defs><sodipodi:namedview
|
||||
inkscape:document-rotation="0"
|
||||
inkscape:current-layer="Layer_1"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:window-y="-11"
|
||||
inkscape:window-x="-11"
|
||||
inkscape:cy="6.66147"
|
||||
inkscape:cx="7.0304602"
|
||||
inkscape:zoom="83.4386"
|
||||
showgrid="false"
|
||||
id="namedview7"
|
||||
inkscape:window-height="2066"
|
||||
inkscape:window-width="3840"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0"
|
||||
guidetolerance="10"
|
||||
gridtolerance="10"
|
||||
objecttolerance="10"
|
||||
borderopacity="1"
|
||||
bordercolor="#666666"
|
||||
pagecolor="#ffffff" />
|
||||
<g
|
||||
transform="matrix(0.7,0,0,0.7,2.4,2.4)"
|
||||
style="fill:#bf6637;fill-opacity:1;stroke:none;stroke-opacity:1"
|
||||
id="g4">
|
||||
<circle
|
||||
style="fill:#bf6637;fill-opacity:1;stroke:none;stroke-opacity:1"
|
||||
id="circle2"
|
||||
r="3"
|
||||
cy="8"
|
||||
cx="8" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
73
resources/icons/timer_dot_empty.svg
Normal file
73
resources/icons/timer_dot_empty.svg
Normal file
@ -0,0 +1,73 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.0"
|
||||
id="Layer_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 16 16"
|
||||
enable-background="new 0 0 16 16"
|
||||
xml:space="preserve"
|
||||
sodipodi:docname="timer_dot_empty.svg"
|
||||
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"><metadata
|
||||
id="metadata11"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs9"><linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient830"><stop
|
||||
style="stop-color:#000000;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop826" /><stop
|
||||
style="stop-color:#000000;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop828" /></linearGradient><radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient830"
|
||||
id="radialGradient832"
|
||||
cx="8"
|
||||
cy="8"
|
||||
fx="8"
|
||||
fy="8"
|
||||
r="3.5"
|
||||
gradientUnits="userSpaceOnUse" /></defs><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="3840"
|
||||
inkscape:window-height="2066"
|
||||
id="namedview7"
|
||||
showgrid="false"
|
||||
inkscape:zoom="83.4386"
|
||||
inkscape:cx="7.0304602"
|
||||
inkscape:cy="6.66147"
|
||||
inkscape:window-x="-11"
|
||||
inkscape:window-y="-11"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="Layer_1" />
|
||||
<g
|
||||
id="g4"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-opacity:1"
|
||||
transform="matrix(0.7,0,0,0.7,2.4,2.4)">
|
||||
<circle
|
||||
cx="8"
|
||||
cy="8"
|
||||
r="3"
|
||||
id="circle2"
|
||||
style="fill:#000000;fill-opacity:1;stroke:none;stroke-opacity:1" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.3 KiB |
@ -113,7 +113,12 @@ namespace ImGui
|
||||
const char PrinterSlaIconMarker = 0x6;
|
||||
const char FilamentIconMarker = 0x7;
|
||||
const char MaterialIconMarker = 0x8;
|
||||
|
||||
const char CloseIconMarker = 0xB;
|
||||
const char CloseIconHoverMarker = 0xC;
|
||||
const char TimerDotMarker = 0xE;
|
||||
const char TimerDotEmptyMarker = 0xF;
|
||||
const char WarningMarker = 0x10;
|
||||
const char ErrorMarker = 0x11;
|
||||
// void MyFunction(const char* name, const MyMatrix44& v);
|
||||
|
||||
}
|
||||
|
@ -1,661 +1,165 @@
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
our General Public Licenses are intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing under
|
||||
this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your version
|
||||
supports such interaction) an opportunity to receive the Corresponding
|
||||
Source of your version by providing access to the Corresponding Source
|
||||
from a network server at no charge, through some standard or customary
|
||||
means of facilitating copying of software. This Corresponding Source
|
||||
shall include the Corresponding Source for any work covered by version 3
|
||||
of the GNU General Public License that is incorporated pursuant to the
|
||||
following paragraph.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU Affero General Public License from time to time. Such new versions
|
||||
will be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU Affero General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU Affero General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU Affero General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
This version of the GNU Lesser General Public License incorporates
|
||||
the terms and conditions of version 3 of the GNU General Public
|
||||
License, supplemented by the additional permissions listed below.
|
||||
|
||||
0. Additional Definitions.
|
||||
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||
General Public License.
|
||||
|
||||
"The Library" refers to a covered work governed by this License,
|
||||
other than an Application or a Combined Work as defined below.
|
||||
|
||||
An "Application" is any work that makes use of an interface provided
|
||||
by the Library, but which is not otherwise based on the Library.
|
||||
Defining a subclass of a class defined by the Library is deemed a mode
|
||||
of using an interface provided by the Library.
|
||||
|
||||
A "Combined Work" is a work produced by combining or linking an
|
||||
Application with the Library. The particular version of the Library
|
||||
with which the Combined Work was made is also called the "Linked
|
||||
Version".
|
||||
|
||||
The "Minimal Corresponding Source" for a Combined Work means the
|
||||
Corresponding Source for the Combined Work, excluding any source code
|
||||
for portions of the Combined Work that, considered in isolation, are
|
||||
based on the Application, and not on the Linked Version.
|
||||
|
||||
The "Corresponding Application Code" for a Combined Work means the
|
||||
object code and/or source code for the Application, including any data
|
||||
and utility programs needed for reproducing the Combined Work from the
|
||||
Application, but excluding the System Libraries of the Combined Work.
|
||||
|
||||
1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License
|
||||
without being bound by section 3 of the GNU GPL.
|
||||
|
||||
2. Conveying Modified Versions.
|
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a
|
||||
facility refers to a function or data to be supplied by an Application
|
||||
that uses the facility (other than as an argument passed when the
|
||||
facility is invoked), then you may convey a copy of the modified
|
||||
version:
|
||||
|
||||
a) under this License, provided that you make a good faith effort to
|
||||
ensure that, in the event an Application does not supply the
|
||||
function or data, the facility still operates, and performs
|
||||
whatever part of its purpose remains meaningful, or
|
||||
|
||||
b) under the GNU GPL, with none of the additional permissions of
|
||||
this License applicable to that copy.
|
||||
|
||||
3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
The object code form of an Application may incorporate material from
|
||||
a header file that is part of the Library. You may convey such object
|
||||
code under terms of your choice, provided that, if the incorporated
|
||||
material is not limited to numerical parameters, data structure
|
||||
layouts and accessors, or small macros, inline functions and templates
|
||||
(ten or fewer lines in length), you do both of the following:
|
||||
|
||||
a) Give prominent notice with each copy of the object code that the
|
||||
Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
4. Combined Works.
|
||||
|
||||
You may convey a Combined Work under terms of your choice that,
|
||||
taken together, effectively do not restrict modification of the
|
||||
portions of the Library contained in the Combined Work and reverse
|
||||
engineering for debugging such modifications, if you also do each of
|
||||
the following:
|
||||
|
||||
a) Give prominent notice with each copy of the Combined Work that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
c) For a Combined Work that displays copyright notices during
|
||||
execution, include the copyright notice for the Library among
|
||||
these notices, as well as a reference directing the user to the
|
||||
copies of the GNU GPL and this license document.
|
||||
|
||||
d) Do one of the following:
|
||||
|
||||
0) Convey the Minimal Corresponding Source under the terms of this
|
||||
License, and the Corresponding Application Code in a form
|
||||
suitable for, and under terms that permit, the user to
|
||||
recombine or relink the Application with a modified version of
|
||||
the Linked Version to produce a modified Combined Work, in the
|
||||
manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.
|
||||
|
||||
1) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (a) uses at run time
|
||||
a copy of the Library already present on the user's computer
|
||||
system, and (b) will operate properly with a modified version
|
||||
of the Library that is interface-compatible with the Linked
|
||||
Version.
|
||||
|
||||
e) Provide Installation Information, but only if you would otherwise
|
||||
be required to provide such information under section 6 of the
|
||||
GNU GPL, and only to the extent that such information is
|
||||
necessary to install and execute a modified version of the
|
||||
Combined Work produced by recombining or relinking the
|
||||
Application with a modified version of the Linked Version. (If
|
||||
you use option 4d0, the Installation Information must accompany
|
||||
the Minimal Corresponding Source and Corresponding Application
|
||||
Code. If you use option 4d1, you must provide the Installation
|
||||
Information in the manner specified by section 6 of the GNU GPL
|
||||
for conveying Corresponding Source.)
|
||||
|
||||
5. Combined Libraries.
|
||||
|
||||
You may place library facilities that are a work based on the
|
||||
Library side by side in a single library together with other library
|
||||
facilities that are not Applications and are not covered by this
|
||||
License, and convey such a combined library under terms of your
|
||||
choice, if you do both of the following:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work based
|
||||
on the Library, uncombined with any other library facilities,
|
||||
conveyed under the terms of this License.
|
||||
|
||||
b) Give prominent notice with the combined library that part of it
|
||||
is a work based on the Library, and explaining where to find the
|
||||
accompanying uncombined form of the same work.
|
||||
|
||||
6. Revised Versions of the GNU Lesser General Public License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Lesser General Public License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Library as you received it specifies that a certain numbered version
|
||||
of the GNU Lesser General Public License "or any later version"
|
||||
applies to it, you have the option of following the terms and
|
||||
conditions either of that published version or of any later version
|
||||
published by the Free Software Foundation. If the Library as you
|
||||
received it does not specify a version number of the GNU Lesser
|
||||
General Public License, you may choose any version of the GNU Lesser
|
||||
General Public License ever published by the Free Software Foundation.
|
||||
|
||||
If the Library as you received it specifies that a proxy can decide
|
||||
whether future versions of the GNU Lesser General Public License shall
|
||||
apply, that proxy's public statement of acceptance of any version is
|
||||
permanent authorization for you to choose that version for the
|
||||
Library.
|
@ -147,6 +147,10 @@ add_library(libslic3r STATIC
|
||||
PolygonTrimmer.hpp
|
||||
Polyline.cpp
|
||||
Polyline.hpp
|
||||
Preset.cpp
|
||||
Preset.hpp
|
||||
PresetBundle.cpp
|
||||
PresetBundle.hpp
|
||||
Print.cpp
|
||||
Print.hpp
|
||||
PrintBase.cpp
|
||||
@ -187,6 +191,8 @@ add_library(libslic3r STATIC
|
||||
Utils.hpp
|
||||
Time.cpp
|
||||
Time.hpp
|
||||
TriangleSelector.cpp
|
||||
TriangleSelector.hpp
|
||||
MTUtils.hpp
|
||||
VoronoiOffset.cpp
|
||||
VoronoiOffset.hpp
|
||||
@ -201,12 +207,13 @@ add_library(libslic3r STATIC
|
||||
SimplifyMeshImpl.hpp
|
||||
SimplifyMesh.cpp
|
||||
MarchingSquares.hpp
|
||||
Optimizer.hpp
|
||||
${OpenVDBUtils_SOURCES}
|
||||
SLA/Common.hpp
|
||||
SLA/Common.cpp
|
||||
SLA/Pad.hpp
|
||||
SLA/Pad.cpp
|
||||
SLA/SupportTreeBuilder.hpp
|
||||
SLA/SupportTreeMesher.hpp
|
||||
SLA/SupportTreeMesher.cpp
|
||||
SLA/SupportTreeBuildsteps.hpp
|
||||
SLA/SupportTreeBuildsteps.cpp
|
||||
SLA/SupportTreeBuilder.cpp
|
||||
@ -218,6 +225,7 @@ add_library(libslic3r STATIC
|
||||
SLA/Rotfinder.cpp
|
||||
SLA/BoostAdapter.hpp
|
||||
SLA/SpatIndex.hpp
|
||||
SLA/SpatIndex.cpp
|
||||
SLA/RasterBase.hpp
|
||||
SLA/RasterBase.cpp
|
||||
SLA/AGGRaster.hpp
|
||||
@ -233,8 +241,10 @@ add_library(libslic3r STATIC
|
||||
SLA/SupportPointGenerator.cpp
|
||||
SLA/Contour3D.hpp
|
||||
SLA/Contour3D.cpp
|
||||
SLA/EigenMesh3D.hpp
|
||||
SLA/IndexedMesh.hpp
|
||||
SLA/IndexedMesh.cpp
|
||||
SLA/Clustering.hpp
|
||||
SLA/Clustering.cpp
|
||||
SLA/ReprojectPointsOnMesh.hpp
|
||||
)
|
||||
|
||||
|
@ -86,6 +86,7 @@ const char* OBJECTID_ATTR = "objectid";
|
||||
const char* TRANSFORM_ATTR = "transform";
|
||||
const char* PRINTABLE_ATTR = "printable";
|
||||
const char* INSTANCESCOUNT_ATTR = "instances_count";
|
||||
const char* CUSTOM_SUPPORTS_ATTR = "slic3rpe:custom_supports";
|
||||
|
||||
const char* KEY_ATTR = "key";
|
||||
const char* VALUE_ATTR = "value";
|
||||
@ -283,6 +284,7 @@ namespace Slic3r {
|
||||
{
|
||||
std::vector<float> vertices;
|
||||
std::vector<unsigned int> triangles;
|
||||
std::vector<std::string> custom_supports;
|
||||
|
||||
bool empty()
|
||||
{
|
||||
@ -293,6 +295,7 @@ namespace Slic3r {
|
||||
{
|
||||
vertices.clear();
|
||||
triangles.clear();
|
||||
custom_supports.clear();
|
||||
}
|
||||
};
|
||||
|
||||
@ -1539,6 +1542,8 @@ namespace Slic3r {
|
||||
m_curr_object.geometry.triangles.push_back((unsigned int)get_attribute_value_int(attributes, num_attributes, V1_ATTR));
|
||||
m_curr_object.geometry.triangles.push_back((unsigned int)get_attribute_value_int(attributes, num_attributes, V2_ATTR));
|
||||
m_curr_object.geometry.triangles.push_back((unsigned int)get_attribute_value_int(attributes, num_attributes, V3_ATTR));
|
||||
|
||||
m_curr_object.geometry.custom_supports.push_back(get_attribute_value_string(attributes, num_attributes, CUSTOM_SUPPORTS_ATTR));
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1872,6 +1877,14 @@ namespace Slic3r {
|
||||
volume->source.transform = Slic3r::Geometry::Transformation(volume_matrix_to_object);
|
||||
volume->calculate_convex_hull();
|
||||
|
||||
// recreate custom supports from previously loaded attribute
|
||||
for (unsigned i=0; i<triangles_count; ++i) {
|
||||
size_t index = src_start_id/3 + i;
|
||||
assert(index < geometry.custom_supports.size());
|
||||
if (! geometry.custom_supports[index].empty())
|
||||
volume->m_supported_facets.set_triangle_from_string(i, geometry.custom_supports[index]);
|
||||
}
|
||||
|
||||
// apply the remaining volume's metadata
|
||||
for (const Metadata& metadata : volume_data.metadata)
|
||||
{
|
||||
@ -2383,6 +2396,11 @@ namespace Slic3r {
|
||||
{
|
||||
stream << "v" << j + 1 << "=\"" << its.indices[i][j] + volume_it->second.first_vertex_id << "\" ";
|
||||
}
|
||||
|
||||
std::string custom_supports_data_string = volume->m_supported_facets.get_triangle_as_string(i);
|
||||
if (! custom_supports_data_string.empty())
|
||||
stream << CUSTOM_SUPPORTS_ATTR << "=\"" << custom_supports_data_string << "\" ";
|
||||
|
||||
stream << "/>\n";
|
||||
}
|
||||
}
|
||||
|
@ -686,6 +686,7 @@ std::vector<std::pair<coordf_t, std::vector<GCode::LayerToPrint>>> GCode::collec
|
||||
std::sort(ordering.begin(), ordering.end(), [](const OrderingItem &oi1, const OrderingItem &oi2) { return oi1.print_z < oi2.print_z; });
|
||||
|
||||
std::vector<std::pair<coordf_t, std::vector<LayerToPrint>>> layers_to_print;
|
||||
|
||||
// Merge numerically very close Z values.
|
||||
for (size_t i = 0; i < ordering.size();) {
|
||||
// Find the last layer with roughly the same print_z.
|
||||
|
@ -2,6 +2,7 @@
|
||||
#include "ModelArrange.hpp"
|
||||
#include "Geometry.hpp"
|
||||
#include "MTUtils.hpp"
|
||||
#include "TriangleSelector.hpp"
|
||||
|
||||
#include "Format/AMF.hpp"
|
||||
#include "Format/OBJ.hpp"
|
||||
@ -1830,28 +1831,25 @@ arrangement::ArrangePolygon ModelInstance::get_arrange_polygon() const
|
||||
}
|
||||
|
||||
|
||||
std::vector<int> FacetsAnnotation::get_facets(FacetSupportType type) const
|
||||
indexed_triangle_set FacetsAnnotation::get_facets(const ModelVolume& mv, FacetSupportType type) const
|
||||
{
|
||||
std::vector<int> out;
|
||||
for (auto& [facet_idx, this_type] : m_data)
|
||||
if (this_type == type)
|
||||
out.push_back(facet_idx);
|
||||
TriangleSelector selector(mv.mesh());
|
||||
selector.deserialize(m_data);
|
||||
indexed_triangle_set out = selector.get_facets(type);
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void FacetsAnnotation::set_facet(int idx, FacetSupportType type)
|
||||
bool FacetsAnnotation::set(const TriangleSelector& selector)
|
||||
{
|
||||
bool changed = true;
|
||||
|
||||
if (type == FacetSupportType::NONE)
|
||||
changed = m_data.erase(idx) != 0;
|
||||
else
|
||||
m_data[idx] = type;
|
||||
|
||||
if (changed)
|
||||
std::map<int, std::vector<bool>> sel_map = selector.serialize();
|
||||
if (sel_map != m_data) {
|
||||
m_data = sel_map;
|
||||
update_timestamp();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@ -1864,6 +1862,64 @@ void FacetsAnnotation::clear()
|
||||
|
||||
|
||||
|
||||
// Following function takes data from a triangle and encodes it as string
|
||||
// of hexadecimal numbers (one digit per triangle). Used for 3MF export,
|
||||
// changing it may break backwards compatibility !!!!!
|
||||
std::string FacetsAnnotation::get_triangle_as_string(int triangle_idx) const
|
||||
{
|
||||
std::string out;
|
||||
|
||||
auto triangle_it = m_data.find(triangle_idx);
|
||||
if (triangle_it != m_data.end()) {
|
||||
const std::vector<bool>& code = triangle_it->second;
|
||||
int offset = 0;
|
||||
while (offset < int(code.size())) {
|
||||
int next_code = 0;
|
||||
for (int i=3; i>=0; --i) {
|
||||
next_code = next_code << 1;
|
||||
next_code |= int(code[offset + i]);
|
||||
}
|
||||
offset += 4;
|
||||
|
||||
assert(next_code >=0 && next_code <= 15);
|
||||
char digit = next_code < 10 ? next_code + '0' : (next_code-10)+'A';
|
||||
out.insert(out.begin(), digit);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Recover triangle splitting & state from string of hexadecimal values previously
|
||||
// generated by get_triangle_as_string. Used to load from 3MF.
|
||||
void FacetsAnnotation::set_triangle_from_string(int triangle_id, const std::string& str)
|
||||
{
|
||||
assert(! str.empty());
|
||||
m_data[triangle_id] = std::vector<bool>(); // zero current state or create new
|
||||
std::vector<bool>& code = m_data[triangle_id];
|
||||
|
||||
for (auto it = str.crbegin(); it != str.crend(); ++it) {
|
||||
const char ch = *it;
|
||||
int dec = 0;
|
||||
if (ch >= '0' && ch<='9')
|
||||
dec = int(ch - '0');
|
||||
else if (ch >='A' && ch <= 'F')
|
||||
dec = 10 + int(ch - 'A');
|
||||
else
|
||||
assert(false);
|
||||
|
||||
// Convert to binary and append into code.
|
||||
for (int i=0; i<4; ++i) {
|
||||
code.insert(code.end(), bool(dec & (1 << i)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Test whether the two models contain the same number of ModelObjects with the same set of IDs
|
||||
// ordered in the same order. In that case it is not necessary to kill the background processing.
|
||||
bool model_object_list_equal(const Model &model_old, const Model &model_new)
|
||||
@ -1935,7 +1991,7 @@ bool model_custom_supports_data_changed(const ModelObject& mo, const ModelObject
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
extern bool model_has_multi_part_objects(const Model &model)
|
||||
{
|
||||
|
@ -39,6 +39,7 @@ class ModelVolume;
|
||||
class ModelWipeTower;
|
||||
class Print;
|
||||
class SLAPrint;
|
||||
class TriangleSelector;
|
||||
|
||||
namespace UndoRedo {
|
||||
class StackImpl;
|
||||
@ -394,6 +395,7 @@ enum class ModelVolumeType : int {
|
||||
};
|
||||
|
||||
enum class FacetSupportType : int8_t {
|
||||
// Maximum is 3. The value is serialized in TriangleSelector into 2 bits!
|
||||
NONE = 0,
|
||||
ENFORCER = 1,
|
||||
BLOCKER = 2
|
||||
@ -403,9 +405,12 @@ class FacetsAnnotation {
|
||||
public:
|
||||
using ClockType = std::chrono::steady_clock;
|
||||
|
||||
std::vector<int> get_facets(FacetSupportType type) const;
|
||||
void set_facet(int idx, FacetSupportType type);
|
||||
const std::map<int, std::vector<bool>>& get_data() const { return m_data; }
|
||||
bool set(const TriangleSelector& selector);
|
||||
indexed_triangle_set get_facets(const ModelVolume& mv, FacetSupportType type) const;
|
||||
void clear();
|
||||
std::string get_triangle_as_string(int i) const;
|
||||
void set_triangle_from_string(int triangle_id, const std::string& str);
|
||||
|
||||
ClockType::time_point get_timestamp() const { return timestamp; }
|
||||
bool is_same_as(const FacetsAnnotation& other) const {
|
||||
@ -418,7 +423,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
std::map<int, FacetSupportType> m_data;
|
||||
std::map<int, std::vector<bool>> m_data;
|
||||
|
||||
ClockType::time_point timestamp;
|
||||
void update_timestamp() {
|
||||
|
@ -2,7 +2,6 @@
|
||||
#define OPENVDBUTILS_HPP
|
||||
|
||||
#include <libslic3r/TriangleMesh.hpp>
|
||||
#include <libslic3r/SLA/Common.hpp>
|
||||
#include <libslic3r/SLA/Contour3D.hpp>
|
||||
#include <openvdb/openvdb.h>
|
||||
|
||||
|
380
src/libslic3r/Optimizer.hpp
Normal file
380
src/libslic3r/Optimizer.hpp
Normal file
@ -0,0 +1,380 @@
|
||||
#ifndef NLOPTOPTIMIZER_HPP
|
||||
#define NLOPTOPTIMIZER_HPP
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4244)
|
||||
#pragma warning(disable: 4267)
|
||||
#endif
|
||||
#include <nlopt.h>
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
#include <utility>
|
||||
#include <tuple>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <cassert>
|
||||
|
||||
namespace Slic3r { namespace opt {
|
||||
|
||||
// A type to hold the complete result of the optimization.
|
||||
template<size_t N> struct Result {
|
||||
int resultcode;
|
||||
std::array<double, N> optimum;
|
||||
double score;
|
||||
};
|
||||
|
||||
// An interval of possible input values for optimization
|
||||
class Bound {
|
||||
double m_min, m_max;
|
||||
|
||||
public:
|
||||
Bound(double min = std::numeric_limits<double>::min(),
|
||||
double max = std::numeric_limits<double>::max())
|
||||
: m_min(min), m_max(max)
|
||||
{}
|
||||
|
||||
double min() const noexcept { return m_min; }
|
||||
double max() const noexcept { return m_max; }
|
||||
};
|
||||
|
||||
// Helper types for optimization function input and bounds
|
||||
template<size_t N> using Input = std::array<double, N>;
|
||||
template<size_t N> using Bounds = std::array<Bound, N>;
|
||||
|
||||
// A type for specifying the stop criteria. Setter methods can be concatenated
|
||||
class StopCriteria {
|
||||
|
||||
// If the absolute value difference between two scores.
|
||||
double m_abs_score_diff = std::nan("");
|
||||
|
||||
// If the relative value difference between two scores.
|
||||
double m_rel_score_diff = std::nan("");
|
||||
|
||||
// Stop if this value or better is found.
|
||||
double m_stop_score = std::nan("");
|
||||
|
||||
// A predicate that if evaluates to true, the optimization should terminate
|
||||
// and the best result found prior to termination should be returned.
|
||||
std::function<bool()> m_stop_condition = [] { return false; };
|
||||
|
||||
// The max allowed number of iterations.
|
||||
unsigned m_max_iterations = 0;
|
||||
|
||||
public:
|
||||
|
||||
StopCriteria & abs_score_diff(double val)
|
||||
{
|
||||
m_abs_score_diff = val; return *this;
|
||||
}
|
||||
|
||||
double abs_score_diff() const { return m_abs_score_diff; }
|
||||
|
||||
StopCriteria & rel_score_diff(double val)
|
||||
{
|
||||
m_rel_score_diff = val; return *this;
|
||||
}
|
||||
|
||||
double rel_score_diff() const { return m_rel_score_diff; }
|
||||
|
||||
StopCriteria & stop_score(double val)
|
||||
{
|
||||
m_stop_score = val; return *this;
|
||||
}
|
||||
|
||||
double stop_score() const { return m_stop_score; }
|
||||
|
||||
StopCriteria & max_iterations(double val)
|
||||
{
|
||||
m_max_iterations = val; return *this;
|
||||
}
|
||||
|
||||
double max_iterations() const { return m_max_iterations; }
|
||||
|
||||
template<class Fn> StopCriteria & stop_condition(Fn &&cond)
|
||||
{
|
||||
m_stop_condition = cond; return *this;
|
||||
}
|
||||
|
||||
bool stop_condition() { return m_stop_condition(); }
|
||||
};
|
||||
|
||||
// Helper class to use optimization methods involving gradient.
|
||||
template<size_t N> struct ScoreGradient {
|
||||
double score;
|
||||
std::optional<std::array<double, N>> gradient;
|
||||
|
||||
ScoreGradient(double s, const std::array<double, N> &grad)
|
||||
: score{s}, gradient{grad}
|
||||
{}
|
||||
};
|
||||
|
||||
// Helper to be used in static_assert.
|
||||
template<class T> struct always_false { enum { value = false }; };
|
||||
|
||||
// Basic interface to optimizer object
|
||||
template<class Method, class Enable = void> class Optimizer {
|
||||
public:
|
||||
|
||||
Optimizer(const StopCriteria &)
|
||||
{
|
||||
static_assert (always_false<Method>::value,
|
||||
"Optimizer unimplemented for given method!");
|
||||
}
|
||||
|
||||
Optimizer<Method> &to_min() { return *this; }
|
||||
Optimizer<Method> &to_max() { return *this; }
|
||||
Optimizer<Method> &set_criteria(const StopCriteria &) { return *this; }
|
||||
StopCriteria get_criteria() const { return {}; };
|
||||
|
||||
template<class Func, size_t N>
|
||||
Result<N> optimize(Func&& func,
|
||||
const Input<N> &initvals,
|
||||
const Bounds<N>& bounds) { return {}; }
|
||||
|
||||
// optional for randomized methods:
|
||||
void seed(long /*s*/) {}
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
|
||||
// Helper types for NLopt algorithm selection in template contexts
|
||||
template<nlopt_algorithm alg> struct NLoptAlg {};
|
||||
|
||||
// NLopt can combine multiple algorithms if one is global an other is a local
|
||||
// method. This is how template specializations can be informed about this fact.
|
||||
template<nlopt_algorithm gl_alg, nlopt_algorithm lc_alg = NLOPT_LN_NELDERMEAD>
|
||||
struct NLoptAlgComb {};
|
||||
|
||||
template<class M> struct IsNLoptAlg {
|
||||
static const constexpr bool value = false;
|
||||
};
|
||||
|
||||
template<nlopt_algorithm a> struct IsNLoptAlg<NLoptAlg<a>> {
|
||||
static const constexpr bool value = true;
|
||||
};
|
||||
|
||||
template<nlopt_algorithm a1, nlopt_algorithm a2>
|
||||
struct IsNLoptAlg<NLoptAlgComb<a1, a2>> {
|
||||
static const constexpr bool value = true;
|
||||
};
|
||||
|
||||
template<class M, class T = void>
|
||||
using NLoptOnly = std::enable_if_t<IsNLoptAlg<M>::value, T>;
|
||||
|
||||
// Helper to convert C style array to std::array. The copy should be optimized
|
||||
// away with modern compilers.
|
||||
template<size_t N, class T> auto to_arr(const T *a)
|
||||
{
|
||||
std::array<T, N> r;
|
||||
std::copy(a, a + N, std::begin(r));
|
||||
return r;
|
||||
}
|
||||
|
||||
template<size_t N, class T> auto to_arr(const T (&a) [N])
|
||||
{
|
||||
return to_arr<N>(static_cast<const T *>(a));
|
||||
}
|
||||
|
||||
enum class OptDir { MIN, MAX }; // Where to optimize
|
||||
|
||||
struct NLopt { // Helper RAII class for nlopt_opt
|
||||
nlopt_opt ptr = nullptr;
|
||||
|
||||
template<class...A> explicit NLopt(A&&...a)
|
||||
{
|
||||
ptr = nlopt_create(std::forward<A>(a)...);
|
||||
}
|
||||
|
||||
NLopt(const NLopt&) = delete;
|
||||
NLopt(NLopt&&) = delete;
|
||||
NLopt& operator=(const NLopt&) = delete;
|
||||
NLopt& operator=(NLopt&&) = delete;
|
||||
|
||||
~NLopt() { nlopt_destroy(ptr); }
|
||||
};
|
||||
|
||||
template<class Method> class NLoptOpt {};
|
||||
|
||||
// Optimizers based on NLopt.
|
||||
template<nlopt_algorithm alg> class NLoptOpt<NLoptAlg<alg>> {
|
||||
protected:
|
||||
StopCriteria m_stopcr;
|
||||
OptDir m_dir;
|
||||
|
||||
template<class Fn> using TOptData =
|
||||
std::tuple<std::remove_reference_t<Fn>*, NLoptOpt*, nlopt_opt>;
|
||||
|
||||
template<class Fn, size_t N>
|
||||
static double optfunc(unsigned n, const double *params,
|
||||
double *gradient,
|
||||
void *data)
|
||||
{
|
||||
assert(n >= N);
|
||||
|
||||
auto tdata = static_cast<TOptData<Fn>*>(data);
|
||||
|
||||
if (std::get<1>(*tdata)->m_stopcr.stop_condition())
|
||||
nlopt_force_stop(std::get<2>(*tdata));
|
||||
|
||||
auto fnptr = std::get<0>(*tdata);
|
||||
auto funval = to_arr<N>(params);
|
||||
|
||||
double scoreval = 0.;
|
||||
using RetT = decltype((*fnptr)(funval));
|
||||
if constexpr (std::is_convertible_v<RetT, ScoreGradient<N>>) {
|
||||
ScoreGradient<N> score = (*fnptr)(funval);
|
||||
for (size_t i = 0; i < n; ++i) gradient[i] = (*score.gradient)[i];
|
||||
scoreval = score.score;
|
||||
} else {
|
||||
scoreval = (*fnptr)(funval);
|
||||
}
|
||||
|
||||
return scoreval;
|
||||
}
|
||||
|
||||
template<size_t N>
|
||||
void set_up(NLopt &nl, const Bounds<N>& bounds)
|
||||
{
|
||||
std::array<double, N> lb, ub;
|
||||
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
lb[i] = bounds[i].min();
|
||||
ub[i] = bounds[i].max();
|
||||
}
|
||||
|
||||
nlopt_set_lower_bounds(nl.ptr, lb.data());
|
||||
nlopt_set_upper_bounds(nl.ptr, ub.data());
|
||||
|
||||
double abs_diff = m_stopcr.abs_score_diff();
|
||||
double rel_diff = m_stopcr.rel_score_diff();
|
||||
double stopval = m_stopcr.stop_score();
|
||||
if(!std::isnan(abs_diff)) nlopt_set_ftol_abs(nl.ptr, abs_diff);
|
||||
if(!std::isnan(rel_diff)) nlopt_set_ftol_rel(nl.ptr, rel_diff);
|
||||
if(!std::isnan(stopval)) nlopt_set_stopval(nl.ptr, stopval);
|
||||
|
||||
if(this->m_stopcr.max_iterations() > 0)
|
||||
nlopt_set_maxeval(nl.ptr, this->m_stopcr.max_iterations());
|
||||
}
|
||||
|
||||
template<class Fn, size_t N>
|
||||
Result<N> optimize(NLopt &nl, Fn &&fn, const Input<N> &initvals)
|
||||
{
|
||||
Result<N> r;
|
||||
|
||||
TOptData<Fn> data = std::make_tuple(&fn, this, nl.ptr);
|
||||
|
||||
switch(m_dir) {
|
||||
case OptDir::MIN:
|
||||
nlopt_set_min_objective(nl.ptr, optfunc<Fn, N>, &data); break;
|
||||
case OptDir::MAX:
|
||||
nlopt_set_max_objective(nl.ptr, optfunc<Fn, N>, &data); break;
|
||||
}
|
||||
|
||||
r.optimum = initvals;
|
||||
r.resultcode = nlopt_optimize(nl.ptr, r.optimum.data(), &r.score);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
template<class Func, size_t N>
|
||||
Result<N> optimize(Func&& func,
|
||||
const Input<N> &initvals,
|
||||
const Bounds<N>& bounds)
|
||||
{
|
||||
NLopt nl{alg, N};
|
||||
set_up(nl, bounds);
|
||||
|
||||
return optimize(nl, std::forward<Func>(func), initvals);
|
||||
}
|
||||
|
||||
explicit NLoptOpt(StopCriteria stopcr = {}) : m_stopcr(stopcr) {}
|
||||
|
||||
void set_criteria(const StopCriteria &cr) { m_stopcr = cr; }
|
||||
const StopCriteria &get_criteria() const noexcept { return m_stopcr; }
|
||||
void set_dir(OptDir dir) noexcept { m_dir = dir; }
|
||||
|
||||
void seed(long s) { nlopt_srand(s); }
|
||||
};
|
||||
|
||||
template<nlopt_algorithm glob, nlopt_algorithm loc>
|
||||
class NLoptOpt<NLoptAlgComb<glob, loc>>: public NLoptOpt<NLoptAlg<glob>>
|
||||
{
|
||||
using Base = NLoptOpt<NLoptAlg<glob>>;
|
||||
public:
|
||||
|
||||
template<class Fn, size_t N>
|
||||
Result<N> optimize(Fn&& f,
|
||||
const Input<N> &initvals,
|
||||
const Bounds<N>& bounds)
|
||||
{
|
||||
NLopt nl_glob{glob, N}, nl_loc{loc, N};
|
||||
|
||||
Base::set_up(nl_glob, bounds);
|
||||
Base::set_up(nl_loc, bounds);
|
||||
nlopt_set_local_optimizer(nl_glob.ptr, nl_loc.ptr);
|
||||
|
||||
return Base::optimize(nl_glob, std::forward<Fn>(f), initvals);
|
||||
}
|
||||
|
||||
explicit NLoptOpt(StopCriteria stopcr = {}) : Base{stopcr} {}
|
||||
};
|
||||
|
||||
} // namespace detail;
|
||||
|
||||
// Optimizers based on NLopt.
|
||||
template<class M> class Optimizer<M, detail::NLoptOnly<M>> {
|
||||
detail::NLoptOpt<M> m_opt;
|
||||
|
||||
public:
|
||||
|
||||
Optimizer& to_max() { m_opt.set_dir(detail::OptDir::MAX); return *this; }
|
||||
Optimizer& to_min() { m_opt.set_dir(detail::OptDir::MIN); return *this; }
|
||||
|
||||
template<class Func, size_t N>
|
||||
Result<N> optimize(Func&& func,
|
||||
const Input<N> &initvals,
|
||||
const Bounds<N>& bounds)
|
||||
{
|
||||
return m_opt.optimize(std::forward<Func>(func), initvals, bounds);
|
||||
}
|
||||
|
||||
explicit Optimizer(StopCriteria stopcr = {}) : m_opt(stopcr) {}
|
||||
|
||||
Optimizer &set_criteria(const StopCriteria &cr)
|
||||
{
|
||||
m_opt.set_criteria(cr); return *this;
|
||||
}
|
||||
|
||||
const StopCriteria &get_criteria() const { return m_opt.get_criteria(); }
|
||||
|
||||
void seed(long s) { m_opt.seed(s); }
|
||||
};
|
||||
|
||||
template<size_t N> Bounds<N> bounds(const Bound (&b) [N]) { return detail::to_arr(b); }
|
||||
template<size_t N> Input<N> initvals(const double (&a) [N]) { return detail::to_arr(a); }
|
||||
template<size_t N> auto score_gradient(double s, const double (&grad)[N])
|
||||
{
|
||||
return ScoreGradient<N>(s, detail::to_arr(grad));
|
||||
}
|
||||
|
||||
// Predefinded NLopt algorithms that are used in the codebase
|
||||
using AlgNLoptGenetic = detail::NLoptAlgComb<NLOPT_GN_ESCH>;
|
||||
using AlgNLoptSubplex = detail::NLoptAlg<NLOPT_LN_SBPLX>;
|
||||
using AlgNLoptSimplex = detail::NLoptAlg<NLOPT_LN_NELDERMEAD>;
|
||||
|
||||
// TODO: define others if needed...
|
||||
|
||||
// Helper defs for pre-crafted global and local optimizers that work well.
|
||||
using DefaultGlobalOptimizer = Optimizer<AlgNLoptGenetic>;
|
||||
using DefaultLocalOptimizer = Optimizer<AlgNLoptSubplex>;
|
||||
|
||||
}} // namespace Slic3r::opt
|
||||
|
||||
#endif // NLOPTOPTIMIZER_HPP
|
@ -60,10 +60,13 @@ inline int64_t cross2(const Vec2i64 &v1, const Vec2i64 &v2) { return v1(0) * v2(
|
||||
inline float cross2(const Vec2f &v1, const Vec2f &v2) { return v1(0) * v2(1) - v1(1) * v2(0); }
|
||||
inline double cross2(const Vec2d &v1, const Vec2d &v2) { return v1(0) * v2(1) - v1(1) * v2(0); }
|
||||
|
||||
inline Vec2i32 to_2d(const Vec2i32 &pt3) { return Vec2i32(pt3(0), pt3(1)); }
|
||||
inline Vec2i64 to_2d(const Vec3i64 &pt3) { return Vec2i64(pt3(0), pt3(1)); }
|
||||
inline Vec2f to_2d(const Vec3f &pt3) { return Vec2f (pt3(0), pt3(1)); }
|
||||
inline Vec2d to_2d(const Vec3d &pt3) { return Vec2d (pt3(0), pt3(1)); }
|
||||
template<class T, int N> Eigen::Matrix<T, 2, 1, Eigen::DontAlign>
|
||||
to_2d(const Eigen::Matrix<T, N, 1, Eigen::DontAlign> &ptN) { return {ptN(0), ptN(1)}; }
|
||||
|
||||
//inline Vec2i32 to_2d(const Vec3i32 &pt3) { return Vec2i32(pt3(0), pt3(1)); }
|
||||
//inline Vec2i64 to_2d(const Vec3i64 &pt3) { return Vec2i64(pt3(0), pt3(1)); }
|
||||
//inline Vec2f to_2d(const Vec3f &pt3) { return Vec2f (pt3(0), pt3(1)); }
|
||||
//inline Vec2d to_2d(const Vec3d &pt3) { return Vec2d (pt3(0), pt3(1)); }
|
||||
|
||||
inline Vec3d to_3d(const Vec2d &v, double z) { return Vec3d(v(0), v(1), z); }
|
||||
inline Vec3f to_3d(const Vec2f &v, float z) { return Vec3f(v(0), v(1), z); }
|
||||
|
@ -1,10 +1,7 @@
|
||||
#include <cassert>
|
||||
|
||||
#include "Preset.hpp"
|
||||
#include "AppConfig.hpp"
|
||||
#include "BitmapCache.hpp"
|
||||
#include "I18N.hpp"
|
||||
#include "wxExtensions.hpp"
|
||||
#include "slic3r/GUI/AppConfig.hpp"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
@ -12,6 +9,16 @@
|
||||
#include <Windows.h>
|
||||
#endif /* _MSC_VER */
|
||||
|
||||
// instead of #include "slic3r/GUI/I18N.hpp" :
|
||||
#ifndef L
|
||||
// !!! If you needed to translate some string,
|
||||
// !!! please use _L(string)
|
||||
// !!! _() - is a standard wxWidgets macro to translate
|
||||
// !!! L() is used only for marking localizable string
|
||||
// !!! It will be used in "xgettext" to create a Locating Message Catalog.
|
||||
#define L(s) s
|
||||
#endif /* L */
|
||||
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <stdexcept>
|
||||
@ -19,6 +26,7 @@
|
||||
#include <boost/format.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/filesystem/fstream.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
|
||||
#include <boost/nowide/cenv.hpp>
|
||||
@ -30,15 +38,9 @@
|
||||
#include <boost/locale.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#include <wx/image.h>
|
||||
#include <wx/choice.h>
|
||||
#include <wx/bmpcbox.h>
|
||||
#include <wx/wupdlock.h>
|
||||
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include "libslic3r/PlaceholderParser.hpp"
|
||||
#include "Plater.hpp"
|
||||
#include "libslic3r.h"
|
||||
#include "Utils.hpp"
|
||||
#include "PlaceholderParser.hpp"
|
||||
|
||||
using boost::property_tree::ptree;
|
||||
|
||||
@ -245,9 +247,9 @@ const std::string& Preset::suffix_modified()
|
||||
return g_suffix_modified;
|
||||
}
|
||||
|
||||
void Preset::update_suffix_modified()
|
||||
void Preset::update_suffix_modified(const std::string& new_suffix_modified)
|
||||
{
|
||||
g_suffix_modified = (" (" + _(L("modified")) + ")").ToUTF8().data();
|
||||
g_suffix_modified = new_suffix_modified;
|
||||
}
|
||||
// Remove an optional "(modified)" suffix from a name.
|
||||
// This converts a UI name to a unique preset identifier.
|
||||
@ -496,6 +498,7 @@ const std::vector<std::string>& Preset::sla_print_options()
|
||||
"support_head_penetration",
|
||||
"support_head_width",
|
||||
"support_pillar_diameter",
|
||||
"support_small_pillar_diameter_percent",
|
||||
"support_max_bridges_on_pillar",
|
||||
"support_pillar_connection_mode",
|
||||
"support_buildplate_only",
|
||||
@ -590,10 +593,7 @@ const std::vector<std::string>& Preset::sla_printer_options()
|
||||
PresetCollection::PresetCollection(Preset::Type type, const std::vector<std::string> &keys, const Slic3r::StaticPrintConfig &defaults, const std::string &default_name) :
|
||||
m_type(type),
|
||||
m_edited_preset(type, "", false),
|
||||
m_idx_selected(0),
|
||||
m_bitmap_main_frame(new wxBitmap),
|
||||
m_bitmap_add(new wxBitmap),
|
||||
m_bitmap_cache(new GUI::BitmapCache)
|
||||
m_idx_selected(0)
|
||||
{
|
||||
// Insert just the default preset.
|
||||
this->add_default_preset(keys, defaults, default_name);
|
||||
@ -602,12 +602,6 @@ PresetCollection::PresetCollection(Preset::Type type, const std::vector<std::str
|
||||
|
||||
PresetCollection::~PresetCollection()
|
||||
{
|
||||
delete m_bitmap_main_frame;
|
||||
m_bitmap_main_frame = nullptr;
|
||||
delete m_bitmap_add;
|
||||
m_bitmap_add = nullptr;
|
||||
delete m_bitmap_cache;
|
||||
m_bitmap_cache = nullptr;
|
||||
}
|
||||
|
||||
void PresetCollection::reset(bool delete_files)
|
||||
@ -951,16 +945,6 @@ bool PresetCollection::delete_preset(const std::string& name)
|
||||
return true;
|
||||
}
|
||||
|
||||
void PresetCollection::load_bitmap_default(const std::string &file_name)
|
||||
{
|
||||
*m_bitmap_main_frame = create_scaled_bitmap(file_name);
|
||||
}
|
||||
|
||||
void PresetCollection::load_bitmap_add(const std::string &file_name)
|
||||
{
|
||||
*m_bitmap_add = create_scaled_bitmap(file_name);
|
||||
}
|
||||
|
||||
const Preset* PresetCollection::get_selected_preset_parent() const
|
||||
{
|
||||
if (this->get_selected_idx() == size_t(-1))
|
||||
@ -1119,279 +1103,15 @@ size_t PresetCollection::update_compatible_internal(const PresetWithVendorProfil
|
||||
// Delete the current preset, activate the first visible preset.
|
||||
//void PresetCollection::delete_current_preset();
|
||||
|
||||
// Update the wxChoice UI component from this list of presets.
|
||||
// Hide the
|
||||
void PresetCollection::update_plater_ui(GUI::PresetComboBox *ui)
|
||||
{
|
||||
if (ui == nullptr)
|
||||
return;
|
||||
|
||||
// Otherwise fill in the list from scratch.
|
||||
ui->Freeze();
|
||||
ui->Clear();
|
||||
size_t selected_preset_item = INT_MAX; // some value meaning that no one item is selected
|
||||
|
||||
const Preset &selected_preset = this->get_selected_preset();
|
||||
// Show wide icons if the currently selected preset is not compatible with the current printer,
|
||||
// and draw a red flag in front of the selected preset.
|
||||
bool wide_icons = ! selected_preset.is_compatible && m_bitmap_incompatible != nullptr;
|
||||
|
||||
/* It's supposed that standard size of an icon is 16px*16px for 100% scaled display.
|
||||
* So set sizes for solid_colored icons used for filament preset
|
||||
* and scale them in respect to em_unit value
|
||||
*/
|
||||
const float scale_f = ui->em_unit() * 0.1f;
|
||||
const int icon_height = 16 * scale_f + 0.5f;
|
||||
const int icon_width = 16 * scale_f + 0.5f;
|
||||
const int thin_space_icon_width = 4 * scale_f + 0.5f;
|
||||
const int wide_space_icon_width = 6 * scale_f + 0.5f;
|
||||
|
||||
std::map<wxString, wxBitmap*> nonsys_presets;
|
||||
wxString selected = "";
|
||||
wxString tooltip = "";
|
||||
if (!this->m_presets.front().is_visible)
|
||||
ui->set_label_marker(ui->Append(PresetCollection::separator(L("System presets")), wxNullBitmap));
|
||||
for (size_t i = this->m_presets.front().is_visible ? 0 : m_num_default_presets; i < this->m_presets.size(); ++ i) {
|
||||
const Preset &preset = this->m_presets[i];
|
||||
if (! preset.is_visible || (! preset.is_compatible && i != m_idx_selected))
|
||||
continue;
|
||||
|
||||
std::string bitmap_key = "";
|
||||
// !!! Temporary solution, till refactoring: create and use "sla_printer" icon instead of m_bitmap_main_frame
|
||||
wxBitmap main_bmp = m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap;
|
||||
if (m_type == Preset::TYPE_PRINTER && preset.printer_technology()==ptSLA ) {
|
||||
bitmap_key = "sla_printer";
|
||||
main_bmp = create_scaled_bitmap("sla_printer");
|
||||
}
|
||||
|
||||
// If the filament preset is not compatible and there is a "red flag" icon loaded, show it left
|
||||
// to the filament color image.
|
||||
if (wide_icons)
|
||||
bitmap_key += preset.is_compatible ? ",cmpt" : ",ncmpt";
|
||||
bitmap_key += (preset.is_system || preset.is_default) ? ",syst" : ",nsyst";
|
||||
wxBitmap *bmp = m_bitmap_cache->find(bitmap_key);
|
||||
if (bmp == nullptr) {
|
||||
// Create the bitmap with color bars.
|
||||
std::vector<wxBitmap> bmps;
|
||||
if (wide_icons)
|
||||
// Paint a red flag for incompatible presets.
|
||||
bmps.emplace_back(preset.is_compatible ? m_bitmap_cache->mkclear(icon_width, icon_height) : *m_bitmap_incompatible);
|
||||
// Paint the color bars.
|
||||
bmps.emplace_back(m_bitmap_cache->mkclear(thin_space_icon_width, icon_height));
|
||||
bmps.emplace_back(main_bmp);
|
||||
// Paint a lock at the system presets.
|
||||
bmps.emplace_back(m_bitmap_cache->mkclear(wide_space_icon_width, icon_height));
|
||||
bmps.emplace_back((preset.is_system || preset.is_default) ? *m_bitmap_lock : m_bitmap_cache->mkclear(icon_width, icon_height));
|
||||
bmp = m_bitmap_cache->insert(bitmap_key, bmps);
|
||||
}
|
||||
|
||||
const std::string name = preset.alias.empty() ? preset.name : preset.alias;
|
||||
if (preset.is_default || preset.is_system) {
|
||||
ui->Append(wxString::FromUTF8((/*preset.*/name + (preset.is_dirty ? g_suffix_modified : "")).c_str()),
|
||||
(bmp == 0) ? main_bmp : *bmp);
|
||||
if (i == m_idx_selected ||
|
||||
// just in case: mark selected_preset_item as a first added element
|
||||
selected_preset_item == INT_MAX) {
|
||||
selected_preset_item = ui->GetCount() - 1;
|
||||
tooltip = wxString::FromUTF8(preset.name.c_str());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
nonsys_presets.emplace(wxString::FromUTF8((/*preset.*/name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), bmp/*preset.is_compatible*/);
|
||||
if (i == m_idx_selected) {
|
||||
selected = wxString::FromUTF8((/*preset.*/name + (preset.is_dirty ? g_suffix_modified : "")).c_str());
|
||||
tooltip = wxString::FromUTF8(preset.name.c_str());
|
||||
}
|
||||
}
|
||||
if (i + 1 == m_num_default_presets)
|
||||
ui->set_label_marker(ui->Append(PresetCollection::separator(L("System presets")), wxNullBitmap));
|
||||
}
|
||||
if (!nonsys_presets.empty())
|
||||
{
|
||||
ui->set_label_marker(ui->Append(PresetCollection::separator(L("User presets")), wxNullBitmap));
|
||||
for (std::map<wxString, wxBitmap*>::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) {
|
||||
ui->Append(it->first, *it->second);
|
||||
if (it->first == selected ||
|
||||
// just in case: mark selected_preset_item as a first added element
|
||||
selected_preset_item == INT_MAX)
|
||||
selected_preset_item = ui->GetCount() - 1;
|
||||
}
|
||||
}
|
||||
if (m_type == Preset::TYPE_PRINTER || m_type == Preset::TYPE_SLA_MATERIAL) {
|
||||
std::string bitmap_key = "";
|
||||
// If the filament preset is not compatible and there is a "red flag" icon loaded, show it left
|
||||
// to the filament color image.
|
||||
if (wide_icons)
|
||||
bitmap_key += "wide,";
|
||||
bitmap_key += "edit_preset_list";
|
||||
wxBitmap *bmp = m_bitmap_cache->find(bitmap_key);
|
||||
if (bmp == nullptr) {
|
||||
// Create the bitmap with color bars.
|
||||
std::vector<wxBitmap> bmps;
|
||||
if (wide_icons)
|
||||
// Paint a red flag for incompatible presets.
|
||||
bmps.emplace_back(m_bitmap_cache->mkclear(icon_width, icon_height));
|
||||
// Paint the color bars.
|
||||
bmps.emplace_back(m_bitmap_cache->mkclear(thin_space_icon_width, icon_height));
|
||||
bmps.emplace_back(*m_bitmap_main_frame);
|
||||
// Paint a lock at the system presets.
|
||||
bmps.emplace_back(m_bitmap_cache->mkclear(wide_space_icon_width, icon_height));
|
||||
// bmps.emplace_back(m_bitmap_add ? *m_bitmap_add : wxNullBitmap);
|
||||
bmps.emplace_back(create_scaled_bitmap("edit_uni"));
|
||||
bmp = m_bitmap_cache->insert(bitmap_key, bmps);
|
||||
}
|
||||
if (m_type == Preset::TYPE_SLA_MATERIAL)
|
||||
ui->set_label_marker(ui->Append(PresetCollection::separator(L("Add/Remove materials")), *bmp), GUI::PresetComboBox::LABEL_ITEM_WIZARD_MATERIALS);
|
||||
else
|
||||
ui->set_label_marker(ui->Append(PresetCollection::separator(L("Add/Remove printers")), *bmp), GUI::PresetComboBox::LABEL_ITEM_WIZARD_PRINTERS);
|
||||
}
|
||||
|
||||
/* But, if selected_preset_item is still equal to INT_MAX, it means that
|
||||
* there is no presets added to the list.
|
||||
* So, select last combobox item ("Add/Remove preset")
|
||||
*/
|
||||
if (selected_preset_item == INT_MAX)
|
||||
selected_preset_item = ui->GetCount() - 1;
|
||||
|
||||
ui->SetSelection(selected_preset_item);
|
||||
ui->SetToolTip(tooltip.IsEmpty() ? ui->GetString(selected_preset_item) : tooltip);
|
||||
ui->check_selection(selected_preset_item);
|
||||
ui->Thaw();
|
||||
|
||||
// Update control min size after rescale (changed Display DPI under MSW)
|
||||
if (ui->GetMinWidth() != 20 * ui->em_unit())
|
||||
ui->SetMinSize(wxSize(20 * ui->em_unit(), ui->GetSize().GetHeight()));
|
||||
}
|
||||
|
||||
size_t PresetCollection::update_tab_ui(wxBitmapComboBox *ui, bool show_incompatible, const int em/* = 10*/)
|
||||
{
|
||||
if (ui == nullptr)
|
||||
return 0;
|
||||
ui->Freeze();
|
||||
ui->Clear();
|
||||
size_t selected_preset_item = INT_MAX; // some value meaning that no one item is selected
|
||||
|
||||
/* It's supposed that standard size of an icon is 16px*16px for 100% scaled display.
|
||||
* So set sizes for solid_colored(empty) icons used for preset
|
||||
* and scale them in respect to em_unit value
|
||||
*/
|
||||
const float scale_f = em * 0.1f;
|
||||
const int icon_height = 16 * scale_f + 0.5f;
|
||||
const int icon_width = 16 * scale_f + 0.5f;
|
||||
|
||||
std::map<wxString, wxBitmap*> nonsys_presets;
|
||||
wxString selected = "";
|
||||
if (!this->m_presets.front().is_visible)
|
||||
ui->Append(PresetCollection::separator(L("System presets")), wxNullBitmap);
|
||||
for (size_t i = this->m_presets.front().is_visible ? 0 : m_num_default_presets; i < this->m_presets.size(); ++i) {
|
||||
const Preset &preset = this->m_presets[i];
|
||||
if (! preset.is_visible || (! show_incompatible && ! preset.is_compatible && i != m_idx_selected))
|
||||
continue;
|
||||
std::string bitmap_key = "tab";
|
||||
|
||||
// !!! Temporary solution, till refactoring: create and use "sla_printer" icon instead of m_bitmap_main_frame
|
||||
wxBitmap main_bmp = m_bitmap_main_frame ? *m_bitmap_main_frame : wxNullBitmap;
|
||||
if (m_type == Preset::TYPE_PRINTER && preset.printer_technology() == ptSLA) {
|
||||
bitmap_key = "sla_printer";
|
||||
main_bmp = create_scaled_bitmap("sla_printer");
|
||||
}
|
||||
|
||||
bitmap_key += preset.is_compatible ? ",cmpt" : ",ncmpt";
|
||||
bitmap_key += (preset.is_system || preset.is_default) ? ",syst" : ",nsyst";
|
||||
wxBitmap *bmp = m_bitmap_cache->find(bitmap_key);
|
||||
if (bmp == nullptr) {
|
||||
// Create the bitmap with color bars.
|
||||
std::vector<wxBitmap> bmps;
|
||||
const wxBitmap* tmp_bmp = preset.is_compatible ? m_bitmap_compatible : m_bitmap_incompatible;
|
||||
bmps.emplace_back((tmp_bmp == 0) ? main_bmp : *tmp_bmp);
|
||||
// Paint a lock at the system presets.
|
||||
bmps.emplace_back((preset.is_system || preset.is_default) ? *m_bitmap_lock : m_bitmap_cache->mkclear(icon_width, icon_height));
|
||||
bmp = m_bitmap_cache->insert(bitmap_key, bmps);
|
||||
}
|
||||
|
||||
if (preset.is_default || preset.is_system) {
|
||||
ui->Append(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()),
|
||||
(bmp == 0) ? main_bmp : *bmp);
|
||||
if (i == m_idx_selected ||
|
||||
// just in case: mark selected_preset_item as a first added element
|
||||
selected_preset_item == INT_MAX)
|
||||
selected_preset_item = ui->GetCount() - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
nonsys_presets.emplace(wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str()), bmp/*preset.is_compatible*/);
|
||||
if (i == m_idx_selected)
|
||||
selected = wxString::FromUTF8((preset.name + (preset.is_dirty ? g_suffix_modified : "")).c_str());
|
||||
}
|
||||
if (i + 1 == m_num_default_presets)
|
||||
ui->Append(PresetCollection::separator(L("System presets")), wxNullBitmap);
|
||||
}
|
||||
if (!nonsys_presets.empty())
|
||||
{
|
||||
ui->Append(PresetCollection::separator(L("User presets")), wxNullBitmap);
|
||||
for (std::map<wxString, wxBitmap*>::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) {
|
||||
ui->Append(it->first, *it->second);
|
||||
if (it->first == selected ||
|
||||
// just in case: mark selected_preset_item as a first added element
|
||||
selected_preset_item == INT_MAX)
|
||||
selected_preset_item = ui->GetCount() - 1;
|
||||
}
|
||||
}
|
||||
if (m_type == Preset::TYPE_PRINTER) {
|
||||
wxBitmap *bmp = m_bitmap_cache->find("edit_printer_list");
|
||||
if (bmp == nullptr) {
|
||||
// Create the bitmap with color bars.
|
||||
std::vector<wxBitmap> bmps;
|
||||
bmps.emplace_back(*m_bitmap_main_frame);
|
||||
// bmps.emplace_back(m_bitmap_add ? *m_bitmap_add : wxNullBitmap);
|
||||
bmps.emplace_back(create_scaled_bitmap("edit_uni"));
|
||||
bmp = m_bitmap_cache->insert("add_printer_tab", bmps);
|
||||
}
|
||||
ui->Append(PresetCollection::separator("Add a new printer"), *bmp);
|
||||
}
|
||||
|
||||
/* But, if selected_preset_item is still equal to INT_MAX, it means that
|
||||
* there is no presets added to the list.
|
||||
* So, select last combobox item ("Add/Remove preset")
|
||||
*/
|
||||
if (selected_preset_item == INT_MAX)
|
||||
selected_preset_item = ui->GetCount() - 1;
|
||||
|
||||
ui->SetSelection(selected_preset_item);
|
||||
ui->SetToolTip(ui->GetString(selected_preset_item));
|
||||
ui->Thaw();
|
||||
return selected_preset_item;
|
||||
}
|
||||
|
||||
// Update a dirty floag of the current preset, update the labels of the UI component accordingly.
|
||||
// Update a dirty flag of the current preset
|
||||
// Return true if the dirty flag changed.
|
||||
bool PresetCollection::update_dirty_ui(wxBitmapComboBox *ui)
|
||||
bool PresetCollection::update_dirty()
|
||||
{
|
||||
wxWindowUpdateLocker noUpdates(ui);
|
||||
// 1) Update the dirty flag of the current preset.
|
||||
bool was_dirty = this->get_selected_preset().is_dirty;
|
||||
bool is_dirty = current_is_dirty();
|
||||
this->get_selected_preset().is_dirty = is_dirty;
|
||||
this->get_edited_preset().is_dirty = is_dirty;
|
||||
// 2) Update the labels.
|
||||
for (unsigned int ui_id = 0; ui_id < ui->GetCount(); ++ ui_id) {
|
||||
std::string old_label = ui->GetString(ui_id).utf8_str().data();
|
||||
std::string preset_name = Preset::remove_suffix_modified(old_label);
|
||||
const Preset *preset = this->find_preset(preset_name, false);
|
||||
// The old_label could be the "----- system presets ------" or the "------- user presets --------" separator.
|
||||
// assert(preset != nullptr);
|
||||
if (preset != nullptr) {
|
||||
std::string new_label = preset->is_dirty ? preset->name + g_suffix_modified : preset->name;
|
||||
if (old_label != new_label)
|
||||
ui->SetString(ui_id, wxString::FromUTF8(new_label.c_str()));
|
||||
}
|
||||
}
|
||||
#ifdef __APPLE__
|
||||
// wxWidgets on OSX do not upload the text of the combo box line automatically.
|
||||
// Force it to update by re-selecting.
|
||||
ui->SetSelection(ui->GetSelection());
|
||||
#endif /* __APPLE __ */
|
||||
|
||||
return was_dirty != is_dirty;
|
||||
}
|
||||
|
||||
@ -1605,16 +1325,6 @@ std::string PresetCollection::path_from_name(const std::string &new_name) const
|
||||
return (boost::filesystem::path(m_dir_path) / file_name).make_preferred().string();
|
||||
}
|
||||
|
||||
void PresetCollection::clear_bitmap_cache()
|
||||
{
|
||||
m_bitmap_cache->clear();
|
||||
}
|
||||
|
||||
wxString PresetCollection::separator(const std::string &label)
|
||||
{
|
||||
return wxString::FromUTF8(PresetCollection::separator_head()) + _(label) + wxString::FromUTF8(PresetCollection::separator_tail());
|
||||
}
|
||||
|
||||
const Preset& PrinterPresetCollection::default_preset_for(const DynamicPrintConfig &config) const
|
||||
{
|
||||
const ConfigOptionEnumGeneric *opt_printer_technology = config.opt<ConfigOptionEnumGeneric>("printer_technology");
|
||||
@ -1632,6 +1342,462 @@ const Preset* PrinterPresetCollection::find_by_model_id(const std::string &model
|
||||
return it != cend() ? &*it : nullptr;
|
||||
}
|
||||
|
||||
// -------------------------
|
||||
// *** PhysicalPrinter ***
|
||||
// -------------------------
|
||||
|
||||
std::string PhysicalPrinter::separator()
|
||||
{
|
||||
return " * ";
|
||||
}
|
||||
|
||||
const std::vector<std::string>& PhysicalPrinter::printer_options()
|
||||
{
|
||||
static std::vector<std::string> s_opts;
|
||||
if (s_opts.empty()) {
|
||||
s_opts = {
|
||||
"preset_name",
|
||||
"printer_technology",
|
||||
"printer_model",
|
||||
"host_type",
|
||||
"print_host",
|
||||
"printhost_apikey",
|
||||
"printhost_cafile",
|
||||
"authorization_type",
|
||||
"login",
|
||||
"password"
|
||||
};
|
||||
}
|
||||
return s_opts;
|
||||
}
|
||||
|
||||
const std::vector<std::string>& PhysicalPrinter::print_host_options()
|
||||
{
|
||||
static std::vector<std::string> s_opts;
|
||||
if (s_opts.empty()) {
|
||||
s_opts = {
|
||||
"print_host",
|
||||
"printhost_apikey",
|
||||
"printhost_cafile"
|
||||
};
|
||||
}
|
||||
return s_opts;
|
||||
}
|
||||
|
||||
std::vector<std::string> PhysicalPrinter::presets_with_print_host_information(const PrinterPresetCollection& printer_presets)
|
||||
{
|
||||
std::vector<std::string> presets;
|
||||
for (const Preset& preset : printer_presets)
|
||||
if (has_print_host_information(preset.config))
|
||||
presets.emplace_back(preset.name);
|
||||
|
||||
return presets;
|
||||
}
|
||||
|
||||
bool PhysicalPrinter::has_print_host_information(const DynamicPrintConfig& config)
|
||||
{
|
||||
for (const std::string& opt : print_host_options())
|
||||
if (!config.opt_string(opt).empty())
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::set<std::string>& PhysicalPrinter::get_preset_names() const
|
||||
{
|
||||
return preset_names;
|
||||
}
|
||||
|
||||
bool PhysicalPrinter::has_empty_config() const
|
||||
{
|
||||
return config.opt_string("print_host" ).empty() &&
|
||||
config.opt_string("printhost_apikey").empty() &&
|
||||
config.opt_string("printhost_cafile").empty() &&
|
||||
config.opt_string("login" ).empty() &&
|
||||
config.opt_string("password" ).empty();
|
||||
}
|
||||
|
||||
void PhysicalPrinter::update_preset_names_in_config()
|
||||
{
|
||||
if (!preset_names.empty()) {
|
||||
std::string name;
|
||||
for (auto el : preset_names)
|
||||
name += el + ";";
|
||||
name.pop_back();
|
||||
config.set_key_value("preset_name", new ConfigOptionString(name));
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicalPrinter::save(const std::string& file_name_from, const std::string& file_name_to)
|
||||
{
|
||||
// rename the file
|
||||
boost::nowide::rename(file_name_from.data(), file_name_to.data());
|
||||
this->file = file_name_to;
|
||||
// save configuration
|
||||
this->config.save(this->file);
|
||||
}
|
||||
|
||||
void PhysicalPrinter::update_from_preset(const Preset& preset)
|
||||
{
|
||||
config.apply_only(preset.config, printer_options(), false);
|
||||
// add preset names to the options list
|
||||
auto ret = preset_names.emplace(preset.name);
|
||||
update_preset_names_in_config();
|
||||
}
|
||||
|
||||
void PhysicalPrinter::update_from_config(const DynamicPrintConfig& new_config)
|
||||
{
|
||||
config.apply_only(new_config, printer_options(), false);
|
||||
|
||||
std::string str = config.opt_string("preset_name");
|
||||
std::set<std::string> values{};
|
||||
if (!str.empty()) {
|
||||
boost::split(values, str, boost::is_any_of(";"));
|
||||
for (const std::string& val : values)
|
||||
preset_names.emplace(val);
|
||||
}
|
||||
preset_names = values;
|
||||
}
|
||||
|
||||
void PhysicalPrinter::reset_presets()
|
||||
{
|
||||
return preset_names.clear();
|
||||
}
|
||||
|
||||
bool PhysicalPrinter::add_preset(const std::string& preset_name)
|
||||
{
|
||||
return preset_names.emplace(preset_name).second;
|
||||
}
|
||||
|
||||
bool PhysicalPrinter::delete_preset(const std::string& preset_name)
|
||||
{
|
||||
return preset_names.erase(preset_name) > 0;
|
||||
}
|
||||
|
||||
PhysicalPrinter::PhysicalPrinter(const std::string& name, const Preset& preset) :
|
||||
name(name)
|
||||
{
|
||||
update_from_preset(preset);
|
||||
}
|
||||
|
||||
void PhysicalPrinter::set_name(const std::string& name)
|
||||
{
|
||||
this->name = name;
|
||||
}
|
||||
|
||||
std::string PhysicalPrinter::get_full_name(std::string preset_name) const
|
||||
{
|
||||
return name + separator() + preset_name;
|
||||
}
|
||||
|
||||
std::string PhysicalPrinter::get_short_name(std::string full_name)
|
||||
{
|
||||
int pos = full_name.find(separator());
|
||||
if (pos > 0)
|
||||
boost::erase_tail(full_name, full_name.length() - pos);
|
||||
return full_name;
|
||||
}
|
||||
|
||||
std::string PhysicalPrinter::get_preset_name(std::string name)
|
||||
{
|
||||
int pos = name.find(separator());
|
||||
boost::erase_head(name, pos + 3);
|
||||
return Preset::remove_suffix_modified(name);
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------
|
||||
// *** PhysicalPrinterCollection ***
|
||||
// -----------------------------------
|
||||
|
||||
PhysicalPrinterCollection::PhysicalPrinterCollection( const std::vector<std::string>& keys)
|
||||
{
|
||||
}
|
||||
|
||||
// Load all printers found in dir_path.
|
||||
// Throws an exception on error.
|
||||
void PhysicalPrinterCollection::load_printers(const std::string& dir_path, const std::string& subdir)
|
||||
{
|
||||
boost::filesystem::path dir = boost::filesystem::canonical(boost::filesystem::path(dir_path) / subdir).make_preferred();
|
||||
m_dir_path = dir.string();
|
||||
std::string errors_cummulative;
|
||||
// Store the loaded printers into a new vector, otherwise the binary search for already existing presets would be broken.
|
||||
std::deque<PhysicalPrinter> printers_loaded;
|
||||
for (auto& dir_entry : boost::filesystem::directory_iterator(dir))
|
||||
if (Slic3r::is_ini_file(dir_entry)) {
|
||||
std::string name = dir_entry.path().filename().string();
|
||||
// Remove the .ini suffix.
|
||||
name.erase(name.size() - 4);
|
||||
if (this->find_printer(name, false)) {
|
||||
// This happens when there's is a preset (most likely legacy one) with the same name as a system preset
|
||||
// that's already been loaded from a bundle.
|
||||
BOOST_LOG_TRIVIAL(warning) << "Printer already present, not loading: " << name;
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
PhysicalPrinter printer(name);
|
||||
printer.file = dir_entry.path().string();
|
||||
// Load the preset file, apply preset values on top of defaults.
|
||||
try {
|
||||
DynamicPrintConfig config;
|
||||
config.load_from_ini(printer.file);
|
||||
printer.update_from_config(config);
|
||||
printer.loaded = true;
|
||||
}
|
||||
catch (const std::ifstream::failure& err) {
|
||||
throw std::runtime_error(std::string("The selected preset cannot be loaded: ") + printer.file + "\n\tReason: " + err.what());
|
||||
}
|
||||
catch (const std::runtime_error& err) {
|
||||
throw std::runtime_error(std::string("Failed loading the preset file: ") + printer.file + "\n\tReason: " + err.what());
|
||||
}
|
||||
printers_loaded.emplace_back(printer);
|
||||
}
|
||||
catch (const std::runtime_error& err) {
|
||||
errors_cummulative += err.what();
|
||||
errors_cummulative += "\n";
|
||||
}
|
||||
}
|
||||
m_printers.insert(m_printers.end(), std::make_move_iterator(printers_loaded.begin()), std::make_move_iterator(printers_loaded.end()));
|
||||
std::sort(m_printers.begin(), m_printers.end());
|
||||
if (!errors_cummulative.empty())
|
||||
throw std::runtime_error(errors_cummulative);
|
||||
}
|
||||
|
||||
// if there is saved user presets, contains information about "Print Host upload",
|
||||
// Create default printers with this presets
|
||||
// Note! "Print Host upload" options will be cleared after physical printer creations
|
||||
void PhysicalPrinterCollection::load_printers_from_presets(PrinterPresetCollection& printer_presets)
|
||||
{
|
||||
int cnt=0;
|
||||
for (Preset& preset: printer_presets) {
|
||||
DynamicPrintConfig& config = preset.config;
|
||||
const std::vector<std::string>& options = PhysicalPrinter::print_host_options();
|
||||
|
||||
for(const std::string& option : options) {
|
||||
if (!config.opt_string(option).empty()) {
|
||||
// check if printer with those "Print Host upload" options already exist
|
||||
PhysicalPrinter* existed_printer = find_printer_with_same_config(config);
|
||||
if (existed_printer)
|
||||
// just add preset for this printer
|
||||
existed_printer->add_preset(preset.name);
|
||||
else {
|
||||
std::string new_printer_name = (boost::format("Printer %1%") % ++cnt ).str();
|
||||
while (find_printer(new_printer_name))
|
||||
new_printer_name = (boost::format("Printer %1%") % ++cnt).str();
|
||||
|
||||
// create new printer from this preset
|
||||
PhysicalPrinter printer(new_printer_name, preset);
|
||||
printer.loaded = true;
|
||||
save_printer(printer);
|
||||
}
|
||||
|
||||
// erase "Print Host upload" information from the preset
|
||||
for (const std::string& opt : options)
|
||||
config.opt_string(opt).clear();
|
||||
// save changes for preset
|
||||
preset.save();
|
||||
|
||||
// update those changes for edited preset if it's equal to the preset
|
||||
Preset& edited = printer_presets.get_edited_preset();
|
||||
if (preset.name == edited.name) {
|
||||
for (const std::string& opt : options)
|
||||
edited.config.opt_string(opt).clear();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PhysicalPrinter* PhysicalPrinterCollection::find_printer( const std::string& name, bool first_visible_if_not_found)
|
||||
{
|
||||
auto it = this->find_printer_internal(name);
|
||||
// Ensure that a temporary copy is returned if the preset found is currently selected.
|
||||
return (it != m_printers.end() && it->name == name) ? &this->printer(it - m_printers.begin()) :
|
||||
first_visible_if_not_found ? &this->printer(0) : nullptr;
|
||||
}
|
||||
|
||||
PhysicalPrinter* PhysicalPrinterCollection::find_printer_with_same_config(const DynamicPrintConfig& config)
|
||||
{
|
||||
for (const PhysicalPrinter& printer :*this) {
|
||||
bool is_equal = true;
|
||||
for (const std::string& opt : PhysicalPrinter::print_host_options())
|
||||
if (is_equal && printer.config.opt_string(opt) != config.opt_string(opt))
|
||||
is_equal = false;
|
||||
|
||||
if (is_equal)
|
||||
return find_printer(printer.name);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Generate a file path from a profile name. Add the ".ini" suffix if it is missing.
|
||||
std::string PhysicalPrinterCollection::path_from_name(const std::string& new_name) const
|
||||
{
|
||||
std::string file_name = boost::iends_with(new_name, ".ini") ? new_name : (new_name + ".ini");
|
||||
return (boost::filesystem::path(m_dir_path) / file_name).make_preferred().string();
|
||||
}
|
||||
|
||||
void PhysicalPrinterCollection::save_printer(PhysicalPrinter& edited_printer, const std::string& renamed_from/* = ""*/)
|
||||
{
|
||||
// controll and update preset_names in edited_printer config
|
||||
edited_printer.update_preset_names_in_config();
|
||||
|
||||
std::string name = renamed_from.empty() ? edited_printer.name : renamed_from;
|
||||
// 1) Find the printer with a new_name or create a new one,
|
||||
// initialize it with the edited config.
|
||||
auto it = this->find_printer_internal(name);
|
||||
if (it != m_printers.end() && it->name == name) {
|
||||
// Printer with the same name found.
|
||||
// Overwriting an existing preset.
|
||||
it->config = std::move(edited_printer.config);
|
||||
it->name = edited_printer.name;
|
||||
it->preset_names = edited_printer.preset_names;
|
||||
}
|
||||
else {
|
||||
// Creating a new printer.
|
||||
it = m_printers.insert(it, edited_printer);
|
||||
}
|
||||
assert(it != m_printers.end());
|
||||
|
||||
// 2) Save printer
|
||||
PhysicalPrinter& printer = *it;
|
||||
if (printer.file.empty())
|
||||
printer.file = this->path_from_name(printer.name);
|
||||
|
||||
if (printer.file == this->path_from_name(printer.name))
|
||||
printer.save();
|
||||
else
|
||||
// if printer was renamed, we should rename a file and than save the config
|
||||
printer.save(printer.file, this->path_from_name(printer.name));
|
||||
|
||||
// update idx_selected
|
||||
m_idx_selected = it - m_printers.begin();
|
||||
}
|
||||
|
||||
bool PhysicalPrinterCollection::delete_printer(const std::string& name)
|
||||
{
|
||||
auto it = this->find_printer_internal(name);
|
||||
if (it == m_printers.end())
|
||||
return false;
|
||||
|
||||
const PhysicalPrinter& printer = *it;
|
||||
// Erase the preset file.
|
||||
boost::nowide::remove(printer.file.c_str());
|
||||
m_printers.erase(it);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PhysicalPrinterCollection::delete_selected_printer()
|
||||
{
|
||||
if (!has_selection())
|
||||
return false;
|
||||
const PhysicalPrinter& printer = this->get_selected_printer();
|
||||
|
||||
// Erase the preset file.
|
||||
boost::nowide::remove(printer.file.c_str());
|
||||
// Remove the preset from the list.
|
||||
m_printers.erase(m_printers.begin() + m_idx_selected);
|
||||
// unselect all printers
|
||||
unselect_printer();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PhysicalPrinterCollection::delete_preset_from_printers( const std::string& preset_name)
|
||||
{
|
||||
std::vector<std::string> printers_for_delete;
|
||||
for (PhysicalPrinter& printer : m_printers) {
|
||||
if (printer.preset_names.size() == 1 && *printer.preset_names.begin() == preset_name)
|
||||
printers_for_delete.emplace_back(printer.name);
|
||||
else if (printer.delete_preset(preset_name))
|
||||
save_printer(printer);
|
||||
}
|
||||
|
||||
if (!printers_for_delete.empty())
|
||||
for (const std::string& printer_name : printers_for_delete)
|
||||
delete_printer(printer_name);
|
||||
|
||||
unselect_printer();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get list of printers which have more than one preset and "preset_name" preset is one of them
|
||||
std::vector<std::string> PhysicalPrinterCollection::get_printers_with_preset(const std::string& preset_name)
|
||||
{
|
||||
std::vector<std::string> printers;
|
||||
|
||||
for (auto printer : m_printers) {
|
||||
if (printer.preset_names.size() == 1)
|
||||
continue;
|
||||
if (printer.preset_names.find(preset_name) != printer.preset_names.end())
|
||||
printers.emplace_back(printer.name);
|
||||
}
|
||||
|
||||
return printers;
|
||||
}
|
||||
|
||||
// Get list of printers which has only "preset_name" preset
|
||||
std::vector<std::string> PhysicalPrinterCollection::get_printers_with_only_preset(const std::string& preset_name)
|
||||
{
|
||||
std::vector<std::string> printers;
|
||||
|
||||
for (auto printer : m_printers)
|
||||
if (printer.preset_names.size() == 1 && *printer.preset_names.begin() == preset_name)
|
||||
printers.emplace_back(printer.name);
|
||||
|
||||
return printers;
|
||||
}
|
||||
|
||||
std::string PhysicalPrinterCollection::get_selected_full_printer_name() const
|
||||
{
|
||||
return (m_idx_selected == size_t(-1)) ? std::string() : this->get_selected_printer().get_full_name(m_selected_preset);
|
||||
}
|
||||
|
||||
void PhysicalPrinterCollection::select_printer(const std::string& full_name)
|
||||
{
|
||||
std::string printer_name = PhysicalPrinter::get_short_name(full_name);
|
||||
auto it = this->find_printer_internal(printer_name);
|
||||
if (it == m_printers.end()) {
|
||||
unselect_printer();
|
||||
return;
|
||||
}
|
||||
|
||||
// update idx_selected
|
||||
m_idx_selected = it - m_printers.begin();
|
||||
|
||||
// update name of the currently selected preset
|
||||
if (printer_name == full_name)
|
||||
// use first preset in the list
|
||||
m_selected_preset = *it->preset_names.begin();
|
||||
else
|
||||
m_selected_preset = it->get_preset_name(full_name);
|
||||
}
|
||||
|
||||
void PhysicalPrinterCollection::select_printer(const PhysicalPrinter& printer)
|
||||
{
|
||||
return select_printer(printer.name);
|
||||
}
|
||||
|
||||
bool PhysicalPrinterCollection::has_selection() const
|
||||
{
|
||||
return m_idx_selected != size_t(-1);
|
||||
}
|
||||
|
||||
void PhysicalPrinterCollection::unselect_printer()
|
||||
{
|
||||
m_idx_selected = size_t(-1);
|
||||
m_selected_preset.clear();
|
||||
}
|
||||
|
||||
bool PhysicalPrinterCollection::is_selected(PhysicalPrinterCollection::ConstIterator it, const std::string& preset_name) const
|
||||
{
|
||||
return m_idx_selected == it - m_printers.begin() &&
|
||||
m_selected_preset == preset_name;
|
||||
}
|
||||
|
||||
|
||||
namespace PresetUtils {
|
||||
const VendorProfile::PrinterModel* system_printer_model(const Preset &preset)
|
||||
{
|
@ -8,27 +8,14 @@
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <boost/property_tree/ptree_fwd.hpp>
|
||||
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "libslic3r/Semver.hpp"
|
||||
|
||||
class wxBitmap;
|
||||
class wxBitmapComboBox;
|
||||
class wxChoice;
|
||||
class wxItemContainer;
|
||||
class wxString;
|
||||
class wxWindow;
|
||||
#include "PrintConfig.hpp"
|
||||
#include "Semver.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class AppConfig;
|
||||
class PresetBundle;
|
||||
|
||||
namespace GUI {
|
||||
class BitmapCache;
|
||||
class PresetComboBox;
|
||||
}
|
||||
|
||||
enum ConfigFileType
|
||||
{
|
||||
CONFIG_FILE_TYPE_UNKNOWN,
|
||||
@ -236,7 +223,7 @@ public:
|
||||
static const std::vector<std::string>& sla_material_options();
|
||||
static const std::vector<std::string>& sla_print_options();
|
||||
|
||||
static void update_suffix_modified();
|
||||
static void update_suffix_modified(const std::string& new_suffix_modified);
|
||||
static const std::string& suffix_modified();
|
||||
static std::string remove_suffix_modified(const std::string& name);
|
||||
static void normalize(DynamicPrintConfig &config);
|
||||
@ -322,18 +309,6 @@ public:
|
||||
// returns true if the preset was deleted successfully.
|
||||
bool delete_preset(const std::string& name);
|
||||
|
||||
// Load default bitmap to be placed at the wxBitmapComboBox of a MainFrame.
|
||||
void load_bitmap_default(const std::string &file_name);
|
||||
|
||||
// Load "add new printer" bitmap to be placed at the wxBitmapComboBox of a MainFrame.
|
||||
void load_bitmap_add(const std::string &file_name);
|
||||
|
||||
// Compatible & incompatible marks, to be placed at the wxBitmapComboBox items.
|
||||
void set_bitmap_compatible (const wxBitmap *bmp) { m_bitmap_compatible = bmp; }
|
||||
void set_bitmap_incompatible(const wxBitmap *bmp) { m_bitmap_incompatible = bmp; }
|
||||
void set_bitmap_lock (const wxBitmap *bmp) { m_bitmap_lock = bmp; }
|
||||
void set_bitmap_lock_open (const wxBitmap *bmp) { m_bitmap_lock_open = bmp; }
|
||||
|
||||
// Enable / disable the "- default -" preset.
|
||||
void set_default_suppressed(bool default_suppressed);
|
||||
bool is_default_suppressed() const { return m_default_suppressed; }
|
||||
@ -446,18 +421,9 @@ public:
|
||||
// Return a sorted list of system preset names.
|
||||
std::vector<std::string> system_preset_names() const;
|
||||
|
||||
// Update the choice UI from the list of presets.
|
||||
// If show_incompatible, all presets are shown, otherwise only the compatible presets are shown.
|
||||
// If an incompatible preset is selected, it is shown as well.
|
||||
size_t update_tab_ui(wxBitmapComboBox *ui, bool show_incompatible, const int em = 10);
|
||||
// Update the choice UI from the list of presets.
|
||||
// Only the compatible presets are shown.
|
||||
// If an incompatible preset is selected, it is shown as well.
|
||||
void update_plater_ui(GUI::PresetComboBox *ui);
|
||||
|
||||
// Update a dirty floag of the current preset, update the labels of the UI component accordingly.
|
||||
// Update a dirty flag of the current preset
|
||||
// Return true if the dirty flag changed.
|
||||
bool update_dirty_ui(wxBitmapComboBox *ui);
|
||||
bool update_dirty();
|
||||
|
||||
// Select a profile by its name. Return true if the selection changed.
|
||||
// Without force, the selection is only updated if the index changes.
|
||||
@ -467,16 +433,7 @@ public:
|
||||
// Generate a file path from a profile name. Add the ".ini" suffix if it is missing.
|
||||
std::string path_from_name(const std::string &new_name) const;
|
||||
|
||||
void clear_bitmap_cache();
|
||||
|
||||
#ifdef __linux__
|
||||
static const char* separator_head() { return "------- "; }
|
||||
static const char* separator_tail() { return " -------"; }
|
||||
#else /* __linux__ */
|
||||
static const char* separator_head() { return "————— "; }
|
||||
static const char* separator_tail() { return " —————"; }
|
||||
#endif /* __linux__ */
|
||||
static wxString separator(const std::string &label);
|
||||
size_t num_default_presets() { return m_num_default_presets; }
|
||||
|
||||
protected:
|
||||
// Select a preset, if it exists. If it does not exist, select an invalid (-1) index.
|
||||
@ -547,23 +504,10 @@ private:
|
||||
// Is the "- default -" preset suppressed?
|
||||
bool m_default_suppressed = true;
|
||||
size_t m_num_default_presets = 0;
|
||||
// Compatible & incompatible marks, to be placed at the wxBitmapComboBox items of a Plater.
|
||||
// These bitmaps are not owned by PresetCollection, but by a PresetBundle.
|
||||
const wxBitmap *m_bitmap_compatible = nullptr;
|
||||
const wxBitmap *m_bitmap_incompatible = nullptr;
|
||||
const wxBitmap *m_bitmap_lock = nullptr;
|
||||
const wxBitmap *m_bitmap_lock_open = nullptr;
|
||||
// Marks placed at the wxBitmapComboBox of a MainFrame.
|
||||
// These bitmaps are owned by PresetCollection.
|
||||
wxBitmap *m_bitmap_main_frame;
|
||||
// "Add printer profile" icon, owned by PresetCollection.
|
||||
wxBitmap *m_bitmap_add;
|
||||
|
||||
// Path to the directory to store the config files into.
|
||||
std::string m_dir_path;
|
||||
|
||||
// Caching color bitmaps for the filament combo box.
|
||||
GUI::BitmapCache *m_bitmap_cache = nullptr;
|
||||
|
||||
// to access select_preset_by_name_strict()
|
||||
friend class PresetBundle;
|
||||
};
|
||||
@ -585,6 +529,206 @@ namespace PresetUtils {
|
||||
const VendorProfile::PrinterModel* system_printer_model(const Preset &preset);
|
||||
} // namespace PresetUtils
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
class PhysicalPrinter
|
||||
{
|
||||
public:
|
||||
PhysicalPrinter() {}
|
||||
PhysicalPrinter(const std::string& name) : name(name){}
|
||||
PhysicalPrinter(const std::string& name, const Preset& preset);
|
||||
void set_name(const std::string &name);
|
||||
|
||||
// Name of the Physical Printer, usually derived form the file name.
|
||||
std::string name;
|
||||
// File name of the Physical Printer.
|
||||
std::string file;
|
||||
// Configuration data, loaded from a file, or set from the defaults.
|
||||
DynamicPrintConfig config;
|
||||
// set of presets used with this physical printer
|
||||
std::set<std::string> preset_names;
|
||||
|
||||
// Has this profile been loaded?
|
||||
bool loaded = false;
|
||||
|
||||
static std::string separator();
|
||||
static const std::vector<std::string>& printer_options();
|
||||
static const std::vector<std::string>& print_host_options();
|
||||
static std::vector<std::string> presets_with_print_host_information(const PrinterPresetCollection& printer_presets);
|
||||
static bool has_print_host_information(const DynamicPrintConfig& config);
|
||||
|
||||
const std::set<std::string>& get_preset_names() const;
|
||||
|
||||
bool has_empty_config() const;
|
||||
void update_preset_names_in_config();
|
||||
|
||||
void save() { this->config.save(this->file); }
|
||||
void save(const std::string& file_name_from, const std::string& file_name_to);
|
||||
|
||||
void update_from_preset(const Preset& preset);
|
||||
void update_from_config(const DynamicPrintConfig &new_config);
|
||||
|
||||
// add preset to the preset_names
|
||||
// return false, if preset with this name is already exist in the set
|
||||
bool add_preset(const std::string& preset_name);
|
||||
bool delete_preset(const std::string& preset_name);
|
||||
void reset_presets();
|
||||
|
||||
// Return a printer technology, return ptFFF if the printer technology is not set.
|
||||
static PrinterTechnology printer_technology(const DynamicPrintConfig& cfg) {
|
||||
auto* opt = cfg.option<ConfigOptionEnum<PrinterTechnology>>("printer_technology");
|
||||
// The following assert may trigger when importing some legacy profile,
|
||||
// but it is safer to keep it here to capture the cases where the "printer_technology" key is queried, where it should not.
|
||||
return (opt == nullptr) ? ptFFF : opt->value;
|
||||
}
|
||||
PrinterTechnology printer_technology() const { return printer_technology(this->config); }
|
||||
|
||||
// Sort lexicographically by a preset name. The preset name shall be unique across a single PresetCollection.
|
||||
bool operator<(const PhysicalPrinter& other) const { return this->name < other.name; }
|
||||
|
||||
// get full printer name included a name of the preset
|
||||
std::string get_full_name(std::string preset_name) const;
|
||||
|
||||
// get printer name from the full name uncluded preset name
|
||||
static std::string get_short_name(std::string full_name);
|
||||
|
||||
// get preset name from the full name uncluded printer name
|
||||
static std::string get_preset_name(std::string full_name);
|
||||
|
||||
protected:
|
||||
friend class PhysicalPrinterCollection;
|
||||
};
|
||||
|
||||
|
||||
// ---------------------------------
|
||||
// *** PhysicalPrinterCollection ***
|
||||
// ---------------------------------
|
||||
|
||||
// Collections of physical printers
|
||||
class PhysicalPrinterCollection
|
||||
{
|
||||
public:
|
||||
PhysicalPrinterCollection(const std::vector<std::string>& keys);
|
||||
~PhysicalPrinterCollection() {}
|
||||
|
||||
typedef std::deque<PhysicalPrinter>::iterator Iterator;
|
||||
typedef std::deque<PhysicalPrinter>::const_iterator ConstIterator;
|
||||
Iterator begin() { return m_printers.begin(); }
|
||||
ConstIterator begin() const { return m_printers.cbegin(); }
|
||||
ConstIterator cbegin() const { return m_printers.cbegin(); }
|
||||
Iterator end() { return m_printers.end(); }
|
||||
ConstIterator end() const { return m_printers.cend(); }
|
||||
ConstIterator cend() const { return m_printers.cend(); }
|
||||
|
||||
bool empty() const {return m_printers.empty(); }
|
||||
|
||||
void reset(bool delete_files) {};
|
||||
|
||||
const std::deque<PhysicalPrinter>& operator()() const { return m_printers; }
|
||||
|
||||
// Load ini files of the particular type from the provided directory path.
|
||||
void load_printers(const std::string& dir_path, const std::string& subdir);
|
||||
void load_printers_from_presets(PrinterPresetCollection &printer_presets);
|
||||
|
||||
// Save the printer under a new name. If the name is different from the old one,
|
||||
// a new printer is stored into the list of printers.
|
||||
// New printer is activated.
|
||||
void save_printer(PhysicalPrinter& printer, const std::string& renamed_from = "");
|
||||
|
||||
// Delete the current preset, activate the first visible preset.
|
||||
// returns true if the preset was deleted successfully.
|
||||
bool delete_printer(const std::string& name);
|
||||
// Delete the selected preset
|
||||
// returns true if the preset was deleted successfully.
|
||||
bool delete_selected_printer();
|
||||
// Delete preset_name preset from all printers:
|
||||
// If there is last preset for the printer and first_check== false, then delete this printer
|
||||
// returns true if all presets were deleted successfully.
|
||||
bool delete_preset_from_printers(const std::string& preset_name);
|
||||
|
||||
// Get list of printers which have more than one preset and "preset_name" preset is one of them
|
||||
std::vector<std::string> get_printers_with_preset( const std::string &preset_name);
|
||||
// Get list of printers which has only "preset_name" preset
|
||||
std::vector<std::string> get_printers_with_only_preset( const std::string &preset_name);
|
||||
|
||||
// Return the selected preset, without the user modifications applied.
|
||||
PhysicalPrinter& get_selected_printer() { return m_printers[m_idx_selected]; }
|
||||
const PhysicalPrinter& get_selected_printer() const { return m_printers[m_idx_selected]; }
|
||||
|
||||
size_t get_selected_idx() const { return m_idx_selected; }
|
||||
// Returns the name of the selected preset, or an empty string if no preset is selected.
|
||||
std::string get_selected_printer_name() const { return (m_idx_selected == size_t(-1)) ? std::string() : this->get_selected_printer().name; }
|
||||
// Returns the config of the selected printer, or nullptr if no printer is selected.
|
||||
DynamicPrintConfig* get_selected_printer_config() { return (m_idx_selected == size_t(-1)) ? nullptr : &(this->get_selected_printer().config); }
|
||||
// Returns the config of the selected printer, or nullptr if no printer is selected.
|
||||
PrinterTechnology get_selected_printer_technology() { return (m_idx_selected == size_t(-1)) ? PrinterTechnology::ptAny : this->get_selected_printer().printer_technology(); }
|
||||
|
||||
// Each physical printer can have a several related preset,
|
||||
// so, use the next functions to get an exact names of selections in the list:
|
||||
// Returns the full name of the selected printer, or an empty string if no preset is selected.
|
||||
std::string get_selected_full_printer_name() const;
|
||||
// Returns the printer model of the selected preset, or an empty string if no preset is selected.
|
||||
std::string get_selected_printer_preset_name() const { return (m_idx_selected == size_t(-1)) ? std::string() : m_selected_preset; }
|
||||
|
||||
// Select printer by the full printer name, which contains name of printer, separator and name of selected preset
|
||||
// If full_name doesn't contain name of selected preset, then select first preset in the list for this printer
|
||||
void select_printer(const std::string& full_name);
|
||||
void select_printer(const PhysicalPrinter& printer);
|
||||
bool has_selection() const;
|
||||
void unselect_printer() ;
|
||||
bool is_selected(ConstIterator it, const std::string &preset_name) const;
|
||||
|
||||
// Return a printer by an index. If the printer is active, a temporary copy is returned.
|
||||
PhysicalPrinter& printer(size_t idx) { return m_printers[idx]; }
|
||||
const PhysicalPrinter& printer(size_t idx) const { return const_cast<PhysicalPrinterCollection*>(this)->printer(idx); }
|
||||
|
||||
// Return a preset by its name. If the preset is active, a temporary copy is returned.
|
||||
// If a preset is not found by its name, null is returned.
|
||||
PhysicalPrinter* find_printer(const std::string& name, bool first_visible_if_not_found = false);
|
||||
const PhysicalPrinter* find_printer(const std::string& name, bool first_visible_if_not_found = false) const
|
||||
{
|
||||
return const_cast<PhysicalPrinterCollection*>(this)->find_printer(name, first_visible_if_not_found);
|
||||
}
|
||||
|
||||
// Generate a file path from a profile name. Add the ".ini" suffix if it is missing.
|
||||
std::string path_from_name(const std::string& new_name) const;
|
||||
|
||||
private:
|
||||
PhysicalPrinterCollection& operator=(const PhysicalPrinterCollection& other);
|
||||
|
||||
// Find a preset position in the sorted list of presets.
|
||||
// The "-- default -- " preset is always the first, so it needs
|
||||
// to be handled differently.
|
||||
// If a preset does not exist, an iterator is returned indicating where to insert a preset with the same name.
|
||||
std::deque<PhysicalPrinter>::iterator find_printer_internal(const std::string& name)
|
||||
{
|
||||
PhysicalPrinter printer(name);
|
||||
auto it = std::lower_bound(m_printers.begin(), m_printers.end(), printer);
|
||||
return it;
|
||||
}
|
||||
std::deque<PhysicalPrinter>::const_iterator find_printer_internal(const std::string& name) const
|
||||
{
|
||||
return const_cast<PhysicalPrinterCollection*>(this)->find_printer_internal(name);
|
||||
}
|
||||
|
||||
PhysicalPrinter* find_printer_with_same_config( const DynamicPrintConfig &config);
|
||||
|
||||
// List of printers
|
||||
// Use deque to force the container to allocate an object per each entry,
|
||||
// so that the addresses of the presets don't change during resizing of the container.
|
||||
std::deque<PhysicalPrinter> m_printers;
|
||||
|
||||
// Selected printer.
|
||||
size_t m_idx_selected = size_t(-1);
|
||||
// The name of the preset which is currently select for this printer
|
||||
std::string m_selected_preset;
|
||||
|
||||
// Path to the directory to store the config files into.
|
||||
std::string m_dir_path;
|
||||
};
|
||||
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_Preset_hpp_ */
|
@ -1,12 +1,12 @@
|
||||
#include <cassert>
|
||||
|
||||
#include "PresetBundle.hpp"
|
||||
#include "BitmapCache.hpp"
|
||||
#include "Plater.hpp"
|
||||
#include "I18N.hpp"
|
||||
#include "wxExtensions.hpp"
|
||||
#include "libslic3r.h"
|
||||
#include "Utils.hpp"
|
||||
#include "Model.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <set>
|
||||
#include <fstream>
|
||||
#include <unordered_set>
|
||||
#include <boost/filesystem.hpp>
|
||||
@ -21,17 +21,6 @@
|
||||
#include <boost/locale.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#include <wx/dcmemory.h>
|
||||
#include <wx/image.h>
|
||||
#include <wx/choice.h>
|
||||
#include <wx/bmpcbox.h>
|
||||
#include <wx/wupdlock.h>
|
||||
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
|
||||
|
||||
// Store the print/filament/printer presets into a "presets" subdirectory of the Slic3rPE config dir.
|
||||
// This breaks compatibility with the upstream Slic3r if the --datadir is used to switch between the two versions.
|
||||
@ -53,15 +42,8 @@ PresetBundle::PresetBundle() :
|
||||
sla_materials(Preset::TYPE_SLA_MATERIAL, Preset::sla_material_options(), static_cast<const SLAMaterialConfig&>(SLAFullPrintConfig::defaults())),
|
||||
sla_prints(Preset::TYPE_SLA_PRINT, Preset::sla_print_options(), static_cast<const SLAPrintObjectConfig&>(SLAFullPrintConfig::defaults())),
|
||||
printers(Preset::TYPE_PRINTER, Preset::printer_options(), static_cast<const HostConfig&>(FullPrintConfig::defaults()), "- default FFF -"),
|
||||
m_bitmapCompatible(new wxBitmap),
|
||||
m_bitmapIncompatible(new wxBitmap),
|
||||
m_bitmapLock(new wxBitmap),
|
||||
m_bitmapLockOpen(new wxBitmap),
|
||||
m_bitmapCache(new GUI::BitmapCache)
|
||||
physical_printers(PhysicalPrinter::printer_options())
|
||||
{
|
||||
if (wxImage::FindHandler(wxBITMAP_TYPE_PNG) == nullptr)
|
||||
wxImage::AddHandler(new wxPNGHandler);
|
||||
|
||||
// The following keys are handled by the UI, they do not have a counterpart in any StaticPrintConfig derived classes,
|
||||
// therefore they need to be handled differently. As they have no counterpart in StaticPrintConfig, they are not being
|
||||
// initialized based on PrintConfigDef(), but to empty values (zeros, empty vectors, empty strings).
|
||||
@ -112,16 +94,6 @@ PresetBundle::PresetBundle() :
|
||||
preset.inherits();
|
||||
}
|
||||
|
||||
// Load the default preset bitmaps.
|
||||
// #ys_FIXME_to_delete we'll load them later, using em_unit()
|
||||
// this->prints .load_bitmap_default("cog");
|
||||
// this->sla_prints .load_bitmap_default("package_green.png");
|
||||
// this->filaments .load_bitmap_default("spool.png");
|
||||
// this->sla_materials.load_bitmap_default("package_green.png");
|
||||
// this->printers .load_bitmap_default("printer_empty.png");
|
||||
// this->printers .load_bitmap_add("add.png");
|
||||
// this->load_compatible_bitmaps();
|
||||
|
||||
// Re-activate the default presets, so their "edited" preset copies will be updated with the additional configuration values above.
|
||||
this->prints .select_preset(0);
|
||||
this->sla_prints .select_preset(0);
|
||||
@ -134,20 +106,6 @@ PresetBundle::PresetBundle() :
|
||||
|
||||
PresetBundle::~PresetBundle()
|
||||
{
|
||||
assert(m_bitmapCompatible != nullptr);
|
||||
assert(m_bitmapIncompatible != nullptr);
|
||||
assert(m_bitmapLock != nullptr);
|
||||
assert(m_bitmapLockOpen != nullptr);
|
||||
delete m_bitmapCompatible;
|
||||
m_bitmapCompatible = nullptr;
|
||||
delete m_bitmapIncompatible;
|
||||
m_bitmapIncompatible = nullptr;
|
||||
delete m_bitmapLock;
|
||||
m_bitmapLock = nullptr;
|
||||
delete m_bitmapLockOpen;
|
||||
m_bitmapLockOpen = nullptr;
|
||||
delete m_bitmapCache;
|
||||
m_bitmapCache = nullptr;
|
||||
}
|
||||
|
||||
void PresetBundle::reset(bool delete_files)
|
||||
@ -182,14 +140,16 @@ void PresetBundle::setup_directories()
|
||||
data_dir / "presets" / "filament",
|
||||
data_dir / "presets" / "sla_print",
|
||||
data_dir / "presets" / "sla_material",
|
||||
data_dir / "presets" / "printer"
|
||||
data_dir / "presets" / "printer",
|
||||
data_dir / "presets" / "physical_printer"
|
||||
#else
|
||||
// Store the print/filament/printer presets at the same location as the upstream Slic3r.
|
||||
data_dir / "print",
|
||||
data_dir / "filament",
|
||||
data_dir / "sla_print",
|
||||
data_dir / "sla_material",
|
||||
data_dir / "printer"
|
||||
data_dir / "printer",
|
||||
data_dir / "physical_printer"
|
||||
#endif
|
||||
};
|
||||
for (const boost::filesystem::path &path : paths) {
|
||||
@ -239,6 +199,11 @@ void PresetBundle::load_presets(AppConfig &config, const std::string &preferred_
|
||||
} catch (const std::runtime_error &err) {
|
||||
errors_cummulative += err.what();
|
||||
}
|
||||
try {
|
||||
this->physical_printers.load_printers(dir_user_presets, "physical_printer");
|
||||
} catch (const std::runtime_error &err) {
|
||||
errors_cummulative += err.what();
|
||||
}
|
||||
this->update_multi_material_filament_presets();
|
||||
this->update_compatible(PresetSelectCompatibleType::Never);
|
||||
if (! errors_cummulative.empty())
|
||||
@ -465,6 +430,13 @@ void PresetBundle::load_selections(AppConfig &config, const std::string &preferr
|
||||
// exist.
|
||||
this->update_compatible(PresetSelectCompatibleType::Always);
|
||||
this->update_multi_material_filament_presets();
|
||||
|
||||
// Parse the initial physical printer name.
|
||||
std::string initial_physical_printer_name = remove_ini_suffix(config.get("extras", "physical_printer"));
|
||||
|
||||
// Activate physical printer from the config
|
||||
if (!initial_physical_printer_name.empty())
|
||||
physical_printers.select_printer(initial_physical_printer_name);
|
||||
}
|
||||
|
||||
// Export selections (current print, current filaments, current printer) into config.ini
|
||||
@ -484,36 +456,8 @@ void PresetBundle::export_selections(AppConfig &config)
|
||||
config.set("presets", "sla_print", sla_prints.get_selected_preset_name());
|
||||
config.set("presets", "sla_material", sla_materials.get_selected_preset_name());
|
||||
config.set("presets", "printer", printers.get_selected_preset_name());
|
||||
}
|
||||
|
||||
void PresetBundle::load_compatible_bitmaps()
|
||||
{
|
||||
*m_bitmapCompatible = create_scaled_bitmap("flag_green");
|
||||
*m_bitmapIncompatible = create_scaled_bitmap("flag_red");
|
||||
*m_bitmapLock = create_scaled_bitmap("lock_closed");
|
||||
*m_bitmapLockOpen = create_scaled_bitmap("lock_open");
|
||||
|
||||
prints .set_bitmap_compatible(m_bitmapCompatible);
|
||||
filaments .set_bitmap_compatible(m_bitmapCompatible);
|
||||
sla_prints .set_bitmap_compatible(m_bitmapCompatible);
|
||||
sla_materials.set_bitmap_compatible(m_bitmapCompatible);
|
||||
|
||||
prints .set_bitmap_incompatible(m_bitmapIncompatible);
|
||||
filaments .set_bitmap_incompatible(m_bitmapIncompatible);
|
||||
sla_prints .set_bitmap_incompatible(m_bitmapIncompatible);
|
||||
sla_materials.set_bitmap_incompatible(m_bitmapIncompatible);
|
||||
|
||||
prints .set_bitmap_lock(m_bitmapLock);
|
||||
filaments .set_bitmap_lock(m_bitmapLock);
|
||||
sla_prints .set_bitmap_lock(m_bitmapLock);
|
||||
sla_materials.set_bitmap_lock(m_bitmapLock);
|
||||
printers .set_bitmap_lock(m_bitmapLock);
|
||||
|
||||
prints .set_bitmap_lock_open(m_bitmapLock);
|
||||
filaments .set_bitmap_lock_open(m_bitmapLock);
|
||||
sla_prints .set_bitmap_lock_open(m_bitmapLock);
|
||||
sla_materials.set_bitmap_lock_open(m_bitmapLock);
|
||||
printers .set_bitmap_lock_open(m_bitmapLock);
|
||||
config.set("extras", "physical_printer", physical_printers.get_selected_full_printer_name());
|
||||
}
|
||||
|
||||
DynamicPrintConfig PresetBundle::full_config() const
|
||||
@ -886,8 +830,6 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
|
||||
// 4) Load the project config values (the per extruder wipe matrix etc).
|
||||
this->project_config.apply_only(config, s_project_options);
|
||||
|
||||
update_custom_gcode_per_print_z_from_config(GUI::wxGetApp().plater()->model().custom_gcode_per_print_z, &this->project_config);
|
||||
|
||||
break;
|
||||
}
|
||||
case ptSLA:
|
||||
@ -1544,207 +1486,11 @@ void PresetBundle::export_configbundle(const std::string &path, bool export_syst
|
||||
// an optional "(modified)" suffix will be removed from the filament name.
|
||||
void PresetBundle::set_filament_preset(size_t idx, const std::string &name)
|
||||
{
|
||||
if (name.find_first_of(PresetCollection::separator_head()) == 0)
|
||||
return;
|
||||
|
||||
if (idx >= filament_presets.size())
|
||||
if (idx >= filament_presets.size())
|
||||
filament_presets.resize(idx + 1, filaments.default_preset().name);
|
||||
filament_presets[idx] = Preset::remove_suffix_modified(name);
|
||||
}
|
||||
|
||||
void PresetBundle::load_default_preset_bitmaps()
|
||||
{
|
||||
// Clear bitmap cache, before load new scaled default preset bitmaps
|
||||
m_bitmapCache->clear();
|
||||
this->prints.clear_bitmap_cache();
|
||||
this->sla_prints.clear_bitmap_cache();
|
||||
this->filaments.clear_bitmap_cache();
|
||||
this->sla_materials.clear_bitmap_cache();
|
||||
this->printers.clear_bitmap_cache();
|
||||
|
||||
this->prints.load_bitmap_default("cog");
|
||||
this->sla_prints.load_bitmap_default("cog");
|
||||
this->filaments.load_bitmap_default("spool.png");
|
||||
this->sla_materials.load_bitmap_default("resin");
|
||||
this->printers.load_bitmap_default("printer");
|
||||
this->printers.load_bitmap_add("add.png");
|
||||
this->load_compatible_bitmaps();
|
||||
}
|
||||
|
||||
void PresetBundle::update_plater_filament_ui(unsigned int idx_extruder, GUI::PresetComboBox *ui)
|
||||
{
|
||||
if (ui == nullptr || this->printers.get_edited_preset().printer_technology() == ptSLA ||
|
||||
this->filament_presets.size() <= idx_extruder )
|
||||
return;
|
||||
|
||||
unsigned char rgb[3];
|
||||
std::string extruder_color = this->printers.get_edited_preset().config.opt_string("extruder_colour", idx_extruder);
|
||||
if (!m_bitmapCache->parse_color(extruder_color, rgb))
|
||||
// Extruder color is not defined.
|
||||
extruder_color.clear();
|
||||
|
||||
// Fill in the list from scratch.
|
||||
ui->Freeze();
|
||||
ui->Clear();
|
||||
size_t selected_preset_item = INT_MAX; // some value meaning that no one item is selected
|
||||
|
||||
const Preset *selected_preset = this->filaments.find_preset(this->filament_presets[idx_extruder]);
|
||||
// Show wide icons if the currently selected preset is not compatible with the current printer,
|
||||
// and draw a red flag in front of the selected preset.
|
||||
bool wide_icons = selected_preset != nullptr && ! selected_preset->is_compatible && m_bitmapIncompatible != nullptr;
|
||||
assert(selected_preset != nullptr);
|
||||
std::map<wxString, wxBitmap*> nonsys_presets;
|
||||
wxString selected_str = "";
|
||||
if (!this->filaments().front().is_visible)
|
||||
ui->set_label_marker(ui->Append(PresetCollection::separator(L("System presets")), wxNullBitmap));
|
||||
|
||||
/* It's supposed that standard size of an icon is 16px*16px for 100% scaled display.
|
||||
* So set sizes for solid_colored icons used for filament preset
|
||||
* and scale them in respect to em_unit value
|
||||
*/
|
||||
const float scale_f = ui->em_unit() * 0.1f;
|
||||
|
||||
// To avoid the errors of number rounding for different combination of monitor configuration,
|
||||
// let use scaled 8px, as a smallest icon unit
|
||||
const int icon_unit = 8 * scale_f + 0.5f;
|
||||
const int normal_icon_width = 2 * icon_unit; //16 * scale_f + 0.5f;
|
||||
const int thin_icon_width = icon_unit; //8 * scale_f + 0.5f;
|
||||
const int wide_icon_width = 3 * icon_unit; //24 * scale_f + 0.5f;
|
||||
|
||||
const int space_icon_width = 2 * scale_f + 0.5f;
|
||||
|
||||
// To avoid asserts, each added bitmap to wxBitmapCombobox should be the same size, so
|
||||
// set a bitmap height to m_bitmapLock->GetHeight()
|
||||
//
|
||||
// To avoid asserts, each added bitmap to wxBitmapCombobox should be the same size.
|
||||
// But for some display scaling (for example 125% or 175%) normal_icon_width differs from icon width.
|
||||
// So:
|
||||
// for nonsystem presets set a width of empty bitmap to m_bitmapLock->GetWidth()
|
||||
// for compatible presets set a width of empty bitmap to m_bitmapIncompatible->GetWidth()
|
||||
//
|
||||
// Note, under OSX we should use a Scaled Height/Width because of Retina scale
|
||||
#ifdef __APPLE__
|
||||
const int icon_height = m_bitmapLock->GetScaledHeight();
|
||||
const int lock_icon_width = m_bitmapLock->GetScaledWidth();
|
||||
const int flag_icon_width = m_bitmapIncompatible->GetScaledWidth();
|
||||
#else
|
||||
const int icon_height = m_bitmapLock->GetHeight();
|
||||
const int lock_icon_width = m_bitmapLock->GetWidth();
|
||||
const int flag_icon_width = m_bitmapIncompatible->GetWidth();
|
||||
#endif
|
||||
|
||||
wxString tooltip = "";
|
||||
|
||||
for (int i = this->filaments().front().is_visible ? 0 : 1; i < int(this->filaments().size()); ++i) {
|
||||
const Preset &preset = this->filaments.preset(i);
|
||||
bool selected = this->filament_presets[idx_extruder] == preset.name;
|
||||
if (! preset.is_visible || (! preset.is_compatible && ! selected))
|
||||
continue;
|
||||
// Assign an extruder color to the selected item if the extruder color is defined.
|
||||
std::string filament_rgb = preset.config.opt_string("filament_colour", 0);
|
||||
std::string extruder_rgb = (selected && !extruder_color.empty()) ? extruder_color : filament_rgb;
|
||||
bool single_bar = filament_rgb == extruder_rgb;
|
||||
std::string bitmap_key = single_bar ? filament_rgb : filament_rgb + extruder_rgb;
|
||||
// If the filament preset is not compatible and there is a "red flag" icon loaded, show it left
|
||||
// to the filament color image.
|
||||
if (wide_icons)
|
||||
bitmap_key += preset.is_compatible ? ",cmpt" : ",ncmpt";
|
||||
bitmap_key += (preset.is_system || preset.is_default) ? ",syst" : ",nsyst";
|
||||
if (preset.is_dirty)
|
||||
bitmap_key += ",drty";
|
||||
wxBitmap *bitmap = m_bitmapCache->find(bitmap_key);
|
||||
if (bitmap == nullptr) {
|
||||
// Create the bitmap with color bars.
|
||||
std::vector<wxBitmap> bmps;
|
||||
if (wide_icons)
|
||||
// Paint a red flag for incompatible presets.
|
||||
bmps.emplace_back(preset.is_compatible ? m_bitmapCache->mkclear(flag_icon_width, icon_height) : *m_bitmapIncompatible);
|
||||
// Paint the color bars.
|
||||
m_bitmapCache->parse_color(filament_rgb, rgb);
|
||||
bmps.emplace_back(m_bitmapCache->mksolid(single_bar ? wide_icon_width : normal_icon_width, icon_height, rgb));
|
||||
if (! single_bar) {
|
||||
m_bitmapCache->parse_color(extruder_rgb, rgb);
|
||||
bmps.emplace_back(m_bitmapCache->mksolid(thin_icon_width, icon_height, rgb));
|
||||
}
|
||||
// Paint a lock at the system presets.
|
||||
bmps.emplace_back(m_bitmapCache->mkclear(space_icon_width, icon_height));
|
||||
bmps.emplace_back((preset.is_system || preset.is_default) ? *m_bitmapLock : m_bitmapCache->mkclear(lock_icon_width, icon_height));
|
||||
// (preset.is_dirty ? *m_bitmapLockOpen : *m_bitmapLock) : m_bitmapCache->mkclear(16, 16));
|
||||
bitmap = m_bitmapCache->insert(bitmap_key, bmps);
|
||||
}
|
||||
|
||||
const std::string name = preset.alias.empty() ? preset.name : preset.alias;
|
||||
if (preset.is_default || preset.is_system) {
|
||||
ui->Append(wxString::FromUTF8((/*preset.*/name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()),
|
||||
(bitmap == 0) ? wxNullBitmap : *bitmap);
|
||||
if (selected ||
|
||||
// just in case: mark selected_preset_item as a first added element
|
||||
selected_preset_item == INT_MAX ) {
|
||||
selected_preset_item = ui->GetCount() - 1;
|
||||
tooltip = wxString::FromUTF8(preset.name.c_str());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
nonsys_presets.emplace(wxString::FromUTF8((/*preset.*/name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str()),
|
||||
(bitmap == 0) ? &wxNullBitmap : bitmap);
|
||||
if (selected) {
|
||||
selected_str = wxString::FromUTF8((/*preset.*/name + (preset.is_dirty ? Preset::suffix_modified() : "")).c_str());
|
||||
tooltip = wxString::FromUTF8(preset.name.c_str());
|
||||
}
|
||||
}
|
||||
if (preset.is_default)
|
||||
ui->set_label_marker(ui->Append(PresetCollection::separator(L("System presets")), wxNullBitmap));
|
||||
}
|
||||
|
||||
if (!nonsys_presets.empty())
|
||||
{
|
||||
ui->set_label_marker(ui->Append(PresetCollection::separator(L("User presets")), wxNullBitmap));
|
||||
for (std::map<wxString, wxBitmap*>::iterator it = nonsys_presets.begin(); it != nonsys_presets.end(); ++it) {
|
||||
ui->Append(it->first, *it->second);
|
||||
if (it->first == selected_str ||
|
||||
// just in case: mark selected_preset_item as a first added element
|
||||
selected_preset_item == INT_MAX) {
|
||||
selected_preset_item = ui->GetCount() - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string bitmap_key = "";
|
||||
if (wide_icons)
|
||||
bitmap_key += "wide,";
|
||||
bitmap_key += "edit_preset_list";
|
||||
wxBitmap* bmp = m_bitmapCache->find(bitmap_key);
|
||||
if (bmp == nullptr) {
|
||||
// Create the bitmap with color bars.
|
||||
std::vector<wxBitmap> bmps;
|
||||
if (wide_icons)
|
||||
// Paint a red flag for incompatible presets.
|
||||
bmps.emplace_back(m_bitmapCache->mkclear(flag_icon_width, icon_height));
|
||||
// Paint the color bars + a lock at the system presets.
|
||||
bmps.emplace_back(m_bitmapCache->mkclear(wide_icon_width+space_icon_width, icon_height));
|
||||
bmps.emplace_back(create_scaled_bitmap("edit_uni"));
|
||||
bmp = m_bitmapCache->insert(bitmap_key, bmps);
|
||||
}
|
||||
ui->set_label_marker(ui->Append(PresetCollection::separator(L("Add/Remove filaments")), *bmp), GUI::PresetComboBox::LABEL_ITEM_WIZARD_FILAMENTS);
|
||||
|
||||
/* But, if selected_preset_item is still equal to INT_MAX, it means that
|
||||
* there is no presets added to the list.
|
||||
* So, select last combobox item ("Add/Remove filaments")
|
||||
*/
|
||||
if (selected_preset_item == INT_MAX)
|
||||
selected_preset_item = ui->GetCount() - 1;
|
||||
|
||||
ui->SetSelection(selected_preset_item);
|
||||
ui->SetToolTip(tooltip.IsEmpty() ? ui->GetString(selected_preset_item) : tooltip);
|
||||
ui->check_selection(selected_preset_item);
|
||||
ui->Thaw();
|
||||
|
||||
// Update control min size after rescale (changed Display DPI under MSW)
|
||||
if (ui->GetMinWidth() != 20 * ui->em_unit())
|
||||
ui->SetMinSize(wxSize(20 * ui->em_unit(), ui->GetSize().GetHeight()));
|
||||
}
|
||||
|
||||
void PresetBundle::set_default_suppressed(bool default_suppressed)
|
||||
{
|
||||
prints.set_default_suppressed(default_suppressed);
|
@ -1,22 +1,16 @@
|
||||
#ifndef slic3r_PresetBundle_hpp_
|
||||
#define slic3r_PresetBundle_hpp_
|
||||
|
||||
#include "AppConfig.hpp"
|
||||
#include "Preset.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <unordered_map>
|
||||
#include <boost/filesystem/path.hpp>
|
||||
|
||||
class wxWindow;
|
||||
#include "slic3r/GUI/AppConfig.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
namespace GUI {
|
||||
class BitmapCache;
|
||||
};
|
||||
|
||||
// Bundle of Print + Filament + Printer presets.
|
||||
class PresetBundle
|
||||
{
|
||||
@ -45,6 +39,7 @@ public:
|
||||
PresetCollection& materials(PrinterTechnology pt) { return pt == ptFFF ? this->filaments : this->sla_materials; }
|
||||
const PresetCollection& materials(PrinterTechnology pt) const { return pt == ptFFF ? this->filaments : this->sla_materials; }
|
||||
PrinterPresetCollection printers;
|
||||
PhysicalPrinterCollection physical_printers;
|
||||
// Filament preset names for a multi-extruder or multi-material print.
|
||||
// extruders.size() should be the same as printers.get_edited_preset().config.nozzle_diameter.size()
|
||||
std::vector<std::string> filament_presets;
|
||||
@ -110,9 +105,6 @@ public:
|
||||
// Export a config bundle file containing all the presets and the names of the active presets.
|
||||
void export_configbundle(const std::string &path, bool export_system_settings = false);
|
||||
|
||||
// Update a filament selection combo box on the plater for an idx_extruder.
|
||||
void update_plater_filament_ui(unsigned int idx_extruder, GUI::PresetComboBox *ui);
|
||||
|
||||
// Enable / disable the "- default -" preset.
|
||||
void set_default_suppressed(bool default_suppressed);
|
||||
|
||||
@ -132,8 +124,6 @@ public:
|
||||
void update_compatible(PresetSelectCompatibleType select_other_print_if_incompatible, PresetSelectCompatibleType select_other_filament_if_incompatible);
|
||||
void update_compatible(PresetSelectCompatibleType select_other_if_incompatible) { this->update_compatible(select_other_if_incompatible, select_other_if_incompatible); }
|
||||
|
||||
void load_default_preset_bitmaps();
|
||||
|
||||
// Set the is_visible flag for printer vendors, printer models and printer variants
|
||||
// based on the user configuration.
|
||||
// If the "vendor" section is missing, enable all models and variants of the particular vendor.
|
||||
@ -163,21 +153,9 @@ private:
|
||||
// If it is not an external config, then the config will be stored into the user profile directory.
|
||||
void load_config_file_config(const std::string &name_or_path, bool is_external, DynamicPrintConfig &&config);
|
||||
void load_config_file_config_bundle(const std::string &path, const boost::property_tree::ptree &tree);
|
||||
void load_compatible_bitmaps();
|
||||
|
||||
DynamicPrintConfig full_fff_config() const;
|
||||
DynamicPrintConfig full_sla_config() const;
|
||||
|
||||
// Indicator, that the preset is compatible with the selected printer.
|
||||
wxBitmap *m_bitmapCompatible;
|
||||
// Indicator, that the preset is NOT compatible with the selected printer.
|
||||
wxBitmap *m_bitmapIncompatible;
|
||||
// Indicator, that the preset is system and not modified.
|
||||
wxBitmap *m_bitmapLock;
|
||||
// Indicator, that the preset is system and user modified.
|
||||
wxBitmap *m_bitmapLockOpen;
|
||||
// Caching color bitmaps for the filament combo box.
|
||||
GUI::BitmapCache *m_bitmapCache;
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
@ -130,6 +130,37 @@ void PrintConfigDef::init_common_params()
|
||||
def->min = 0;
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionFloat(0.2));
|
||||
|
||||
// Options used by physical printers
|
||||
|
||||
def = this->add("login", coString);
|
||||
def->label = L("Login");
|
||||
// def->tooltip = L("");
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionString(""));
|
||||
|
||||
def = this->add("password", coString);
|
||||
def->label = L("Password");
|
||||
// def->tooltip = L("");
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionString(""));
|
||||
|
||||
def = this->add("preset_name", coString);
|
||||
def->label = L("Printer preset name");
|
||||
def->tooltip = L("Related printer preset name");
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionString(""));
|
||||
|
||||
def = this->add("authorization_type", coEnum);
|
||||
def->label = L("Authorization Type");
|
||||
// def->tooltip = L("");
|
||||
def->enum_keys_map = &ConfigOptionEnum<AuthorizationType>::get_enum_values();
|
||||
def->enum_values.push_back("key");
|
||||
def->enum_values.push_back("user");
|
||||
def->enum_labels.push_back("KeyPassword");
|
||||
def->enum_labels.push_back("UserPassword");
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionEnum<AuthorizationType>(atKeyPassword));
|
||||
}
|
||||
|
||||
void PrintConfigDef::init_fff_params()
|
||||
@ -2715,7 +2746,7 @@ void PrintConfigDef::init_sla_params()
|
||||
def->set_default_value(new ConfigOptionBool(true));
|
||||
|
||||
def = this->add("support_head_front_diameter", coFloat);
|
||||
def->label = L("Support head front diameter");
|
||||
def->label = L("Pinhead front diameter");
|
||||
def->category = L("Supports");
|
||||
def->tooltip = L("Diameter of the pointing side of the head");
|
||||
def->sidetext = L("mm");
|
||||
@ -2724,7 +2755,7 @@ void PrintConfigDef::init_sla_params()
|
||||
def->set_default_value(new ConfigOptionFloat(0.4));
|
||||
|
||||
def = this->add("support_head_penetration", coFloat);
|
||||
def->label = L("Support head penetration");
|
||||
def->label = L("Head penetration");
|
||||
def->category = L("Supports");
|
||||
def->tooltip = L("How much the pinhead has to penetrate the model surface");
|
||||
def->sidetext = L("mm");
|
||||
@ -2733,7 +2764,7 @@ void PrintConfigDef::init_sla_params()
|
||||
def->set_default_value(new ConfigOptionFloat(0.2));
|
||||
|
||||
def = this->add("support_head_width", coFloat);
|
||||
def->label = L("Support head width");
|
||||
def->label = L("Pinhead width");
|
||||
def->category = L("Supports");
|
||||
def->tooltip = L("Width from the back sphere center to the front sphere center");
|
||||
def->sidetext = L("mm");
|
||||
@ -2743,7 +2774,7 @@ void PrintConfigDef::init_sla_params()
|
||||
def->set_default_value(new ConfigOptionFloat(1.0));
|
||||
|
||||
def = this->add("support_pillar_diameter", coFloat);
|
||||
def->label = L("Support pillar diameter");
|
||||
def->label = L("Pillar diameter");
|
||||
def->category = L("Supports");
|
||||
def->tooltip = L("Diameter in mm of the support pillars");
|
||||
def->sidetext = L("mm");
|
||||
@ -2751,6 +2782,17 @@ void PrintConfigDef::init_sla_params()
|
||||
def->max = 15;
|
||||
def->mode = comSimple;
|
||||
def->set_default_value(new ConfigOptionFloat(1.0));
|
||||
|
||||
def = this->add("support_small_pillar_diameter_percent", coPercent);
|
||||
def->label = L("Small pillar diameter percent");
|
||||
def->category = L("Supports");
|
||||
def->tooltip = L("The percentage of smaller pillars compared to the normal pillar diameter "
|
||||
"which are used in problematic areas where a normal pilla cannot fit.");
|
||||
def->sidetext = L("%");
|
||||
def->min = 1;
|
||||
def->max = 100;
|
||||
def->mode = comExpert;
|
||||
def->set_default_value(new ConfigOptionPercent(50));
|
||||
|
||||
def = this->add("support_max_bridges_on_pillar", coInt);
|
||||
def->label = L("Max bridges on a pillar");
|
||||
@ -2763,7 +2805,7 @@ void PrintConfigDef::init_sla_params()
|
||||
def->set_default_value(new ConfigOptionInt(3));
|
||||
|
||||
def = this->add("support_pillar_connection_mode", coEnum);
|
||||
def->label = L("Support pillar connection mode");
|
||||
def->label = L("Pillar connection mode");
|
||||
def->tooltip = L("Controls the bridge type between two neighboring pillars."
|
||||
" Can be zig-zag, cross (double zig-zag) or dynamic which"
|
||||
" will automatically switch between the first two depending"
|
||||
|
@ -33,6 +33,10 @@ enum PrintHostType {
|
||||
htOctoPrint, htDuet, htFlashAir, htAstroBox
|
||||
};
|
||||
|
||||
enum AuthorizationType {
|
||||
atKeyPassword, atUserPassword
|
||||
};
|
||||
|
||||
enum InfillPattern : int {
|
||||
ipRectilinear, ipMonotonous, ipGrid, ipTriangles, ipStars, ipCubic, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb,
|
||||
ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipCount,
|
||||
@ -109,6 +113,15 @@ template<> inline const t_config_enum_values& ConfigOptionEnum<PrintHostType>::g
|
||||
return keys_map;
|
||||
}
|
||||
|
||||
template<> inline const t_config_enum_values& ConfigOptionEnum<AuthorizationType>::get_enum_values() {
|
||||
static t_config_enum_values keys_map;
|
||||
if (keys_map.empty()) {
|
||||
keys_map["key"] = atKeyPassword;
|
||||
keys_map["user"] = atUserPassword;
|
||||
}
|
||||
return keys_map;
|
||||
}
|
||||
|
||||
template<> inline const t_config_enum_values& ConfigOptionEnum<InfillPattern>::get_enum_values() {
|
||||
static t_config_enum_values keys_map;
|
||||
if (keys_map.empty()) {
|
||||
@ -1018,6 +1031,10 @@ public:
|
||||
|
||||
// Radius in mm of the support pillars.
|
||||
ConfigOptionFloat support_pillar_diameter /*= 0.8*/;
|
||||
|
||||
// The percentage of smaller pillars compared to the normal pillar diameter
|
||||
// which are used in problematic areas where a normal pilla cannot fit.
|
||||
ConfigOptionPercent support_small_pillar_diameter_percent;
|
||||
|
||||
// How much bridge (supporting another pinhead) can be placed on a pillar.
|
||||
ConfigOptionInt support_max_bridges_on_pillar;
|
||||
@ -1142,6 +1159,7 @@ protected:
|
||||
OPT_PTR(support_head_penetration);
|
||||
OPT_PTR(support_head_width);
|
||||
OPT_PTR(support_pillar_diameter);
|
||||
OPT_PTR(support_small_pillar_diameter_percent);
|
||||
OPT_PTR(support_max_bridges_on_pillar);
|
||||
OPT_PTR(support_pillar_connection_mode);
|
||||
OPT_PTR(support_buildplate_only);
|
||||
|
@ -2673,14 +2673,14 @@ void PrintObject::project_and_append_custom_supports(
|
||||
FacetSupportType type, std::vector<ExPolygons>& expolys) const
|
||||
{
|
||||
for (const ModelVolume* mv : this->model_object()->volumes) {
|
||||
const std::vector<int> custom_facets = mv->m_supported_facets.get_facets(type);
|
||||
if (custom_facets.empty())
|
||||
const indexed_triangle_set custom_facets = mv->m_supported_facets.get_facets(*mv, type);
|
||||
if (custom_facets.indices.empty())
|
||||
continue;
|
||||
|
||||
const TriangleMesh& mesh = mv->mesh();
|
||||
const Transform3f& tr1 = mv->get_matrix().cast<float>();
|
||||
const Transform3f& tr2 = this->trafo().cast<float>();
|
||||
const Transform3f tr = tr2 * tr1;
|
||||
const float tr_det_sign = (tr.matrix().determinant() > 0. ? 1.f : -1.f);
|
||||
|
||||
|
||||
// The projection will be at most a pentagon. Let's minimize heap
|
||||
@ -2705,11 +2705,11 @@ void PrintObject::project_and_append_custom_supports(
|
||||
};
|
||||
|
||||
// Vector to collect resulting projections from each triangle.
|
||||
std::vector<TriangleProjections> projections_of_triangles(custom_facets.size());
|
||||
std::vector<TriangleProjections> projections_of_triangles(custom_facets.indices.size());
|
||||
|
||||
// Iterate over all triangles.
|
||||
tbb::parallel_for(
|
||||
tbb::blocked_range<size_t>(0, custom_facets.size()),
|
||||
tbb::blocked_range<size_t>(0, custom_facets.indices.size()),
|
||||
[&](const tbb::blocked_range<size_t>& range) {
|
||||
for (size_t idx = range.begin(); idx < range.end(); ++ idx) {
|
||||
|
||||
@ -2717,10 +2717,11 @@ void PrintObject::project_and_append_custom_supports(
|
||||
|
||||
// Transform the triangle into worlds coords.
|
||||
for (int i=0; i<3; ++i)
|
||||
facet[i] = tr * mesh.its.vertices[mesh.its.indices[custom_facets[idx]](i)];
|
||||
facet[i] = tr * custom_facets.vertices[custom_facets.indices[idx](i)];
|
||||
|
||||
// Ignore triangles with upward-pointing normal.
|
||||
if ((facet[1]-facet[0]).cross(facet[2]-facet[0]).z() > 0.)
|
||||
// Ignore triangles with upward-pointing normal. Don't forget about mirroring.
|
||||
float z_comp = (facet[1]-facet[0]).cross(facet[2]-facet[0]).z();
|
||||
if (tr_det_sign * z_comp > 0.)
|
||||
continue;
|
||||
|
||||
// Sort the three vertices according to z-coordinate.
|
||||
|
@ -1,7 +1,9 @@
|
||||
#ifndef SLA_BOOSTADAPTER_HPP
|
||||
#define SLA_BOOSTADAPTER_HPP
|
||||
|
||||
#include <libslic3r/SLA/Common.hpp>
|
||||
#include <libslic3r/Point.hpp>
|
||||
#include <libslic3r/BoundingBox.hpp>
|
||||
|
||||
#include <boost/geometry.hpp>
|
||||
|
||||
namespace boost {
|
||||
|
152
src/libslic3r/SLA/Clustering.cpp
Normal file
152
src/libslic3r/SLA/Clustering.cpp
Normal file
@ -0,0 +1,152 @@
|
||||
#include "Clustering.hpp"
|
||||
#include "boost/geometry/index/rtree.hpp"
|
||||
|
||||
#include <libslic3r/SLA/SpatIndex.hpp>
|
||||
#include <libslic3r/SLA/BoostAdapter.hpp>
|
||||
|
||||
namespace Slic3r { namespace sla {
|
||||
|
||||
namespace bgi = boost::geometry::index;
|
||||
using Index3D = bgi::rtree< PointIndexEl, bgi::rstar<16, 4> /* ? */ >;
|
||||
|
||||
namespace {
|
||||
|
||||
bool cmp_ptidx_elements(const PointIndexEl& e1, const PointIndexEl& e2)
|
||||
{
|
||||
return e1.second < e2.second;
|
||||
};
|
||||
|
||||
ClusteredPoints cluster(Index3D &sindex,
|
||||
unsigned max_points,
|
||||
std::function<std::vector<PointIndexEl>(
|
||||
const Index3D &, const PointIndexEl &)> qfn)
|
||||
{
|
||||
using Elems = std::vector<PointIndexEl>;
|
||||
|
||||
// Recursive function for visiting all the points in a given distance to
|
||||
// each other
|
||||
std::function<void(Elems&, Elems&)> group =
|
||||
[&sindex, &group, max_points, qfn](Elems& pts, Elems& cluster)
|
||||
{
|
||||
for(auto& p : pts) {
|
||||
std::vector<PointIndexEl> tmp = qfn(sindex, p);
|
||||
|
||||
std::sort(tmp.begin(), tmp.end(), cmp_ptidx_elements);
|
||||
|
||||
Elems newpts;
|
||||
std::set_difference(tmp.begin(), tmp.end(),
|
||||
cluster.begin(), cluster.end(),
|
||||
std::back_inserter(newpts), cmp_ptidx_elements);
|
||||
|
||||
int c = max_points && newpts.size() + cluster.size() > max_points?
|
||||
int(max_points - cluster.size()) : int(newpts.size());
|
||||
|
||||
cluster.insert(cluster.end(), newpts.begin(), newpts.begin() + c);
|
||||
std::sort(cluster.begin(), cluster.end(), cmp_ptidx_elements);
|
||||
|
||||
if(!newpts.empty() && (!max_points || cluster.size() < max_points))
|
||||
group(newpts, cluster);
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<Elems> clusters;
|
||||
for(auto it = sindex.begin(); it != sindex.end();) {
|
||||
Elems cluster = {};
|
||||
Elems pts = {*it};
|
||||
group(pts, cluster);
|
||||
|
||||
for(auto& c : cluster) sindex.remove(c);
|
||||
it = sindex.begin();
|
||||
|
||||
clusters.emplace_back(cluster);
|
||||
}
|
||||
|
||||
ClusteredPoints result;
|
||||
for(auto& cluster : clusters) {
|
||||
result.emplace_back();
|
||||
for(auto c : cluster) result.back().emplace_back(c.second);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<PointIndexEl> distance_queryfn(const Index3D& sindex,
|
||||
const PointIndexEl& p,
|
||||
double dist,
|
||||
unsigned max_points)
|
||||
{
|
||||
std::vector<PointIndexEl> tmp; tmp.reserve(max_points);
|
||||
sindex.query(
|
||||
bgi::nearest(p.first, max_points),
|
||||
std::back_inserter(tmp)
|
||||
);
|
||||
|
||||
for(auto it = tmp.begin(); it < tmp.end(); ++it)
|
||||
if((p.first - it->first).norm() > dist) it = tmp.erase(it);
|
||||
|
||||
return tmp;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Clustering a set of points by the given criteria
|
||||
ClusteredPoints cluster(
|
||||
const std::vector<unsigned>& indices,
|
||||
std::function<Vec3d(unsigned)> pointfn,
|
||||
double dist,
|
||||
unsigned max_points)
|
||||
{
|
||||
// A spatial index for querying the nearest points
|
||||
Index3D sindex;
|
||||
|
||||
// Build the index
|
||||
for(auto idx : indices) sindex.insert( std::make_pair(pointfn(idx), idx));
|
||||
|
||||
return cluster(sindex, max_points,
|
||||
[dist, max_points](const Index3D& sidx, const PointIndexEl& p)
|
||||
{
|
||||
return distance_queryfn(sidx, p, dist, max_points);
|
||||
});
|
||||
}
|
||||
|
||||
// Clustering a set of points by the given criteria
|
||||
ClusteredPoints cluster(
|
||||
const std::vector<unsigned>& indices,
|
||||
std::function<Vec3d(unsigned)> pointfn,
|
||||
std::function<bool(const PointIndexEl&, const PointIndexEl&)> predicate,
|
||||
unsigned max_points)
|
||||
{
|
||||
// A spatial index for querying the nearest points
|
||||
Index3D sindex;
|
||||
|
||||
// Build the index
|
||||
for(auto idx : indices) sindex.insert( std::make_pair(pointfn(idx), idx));
|
||||
|
||||
return cluster(sindex, max_points,
|
||||
[max_points, predicate](const Index3D& sidx, const PointIndexEl& p)
|
||||
{
|
||||
std::vector<PointIndexEl> tmp; tmp.reserve(max_points);
|
||||
sidx.query(bgi::satisfies([p, predicate](const PointIndexEl& e){
|
||||
return predicate(p, e);
|
||||
}), std::back_inserter(tmp));
|
||||
return tmp;
|
||||
});
|
||||
}
|
||||
|
||||
ClusteredPoints cluster(const Eigen::MatrixXd& pts, double dist, unsigned max_points)
|
||||
{
|
||||
// A spatial index for querying the nearest points
|
||||
Index3D sindex;
|
||||
|
||||
// Build the index
|
||||
for(Eigen::Index i = 0; i < pts.rows(); i++)
|
||||
sindex.insert(std::make_pair(Vec3d(pts.row(i)), unsigned(i)));
|
||||
|
||||
return cluster(sindex, max_points,
|
||||
[dist, max_points](const Index3D& sidx, const PointIndexEl& p)
|
||||
{
|
||||
return distance_queryfn(sidx, p, dist, max_points);
|
||||
});
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::sla
|
@ -2,7 +2,8 @@
|
||||
#define SLA_CLUSTERING_HPP
|
||||
|
||||
#include <vector>
|
||||
#include <libslic3r/SLA/Common.hpp>
|
||||
|
||||
#include <libslic3r/Point.hpp>
|
||||
#include <libslic3r/SLA/SpatIndex.hpp>
|
||||
|
||||
namespace Slic3r { namespace sla {
|
||||
@ -16,7 +17,7 @@ ClusteredPoints cluster(const std::vector<unsigned>& indices,
|
||||
double dist,
|
||||
unsigned max_points);
|
||||
|
||||
ClusteredPoints cluster(const PointSet& points,
|
||||
ClusteredPoints cluster(const Eigen::MatrixXd& points,
|
||||
double dist,
|
||||
unsigned max_points);
|
||||
|
||||
@ -26,5 +27,56 @@ ClusteredPoints cluster(
|
||||
std::function<bool(const PointIndexEl&, const PointIndexEl&)> predicate,
|
||||
unsigned max_points);
|
||||
|
||||
}}
|
||||
// This function returns the position of the centroid in the input 'clust'
|
||||
// vector of point indices.
|
||||
template<class DistFn, class PointFn>
|
||||
long cluster_centroid(const ClusterEl &clust, PointFn pointfn, DistFn df)
|
||||
{
|
||||
switch(clust.size()) {
|
||||
case 0: /* empty cluster */ return -1;
|
||||
case 1: /* only one element */ return 0;
|
||||
case 2: /* if two elements, there is no center */ return 0;
|
||||
default: ;
|
||||
}
|
||||
|
||||
// The function works by calculating for each point the average distance
|
||||
// from all the other points in the cluster. We create a selector bitmask of
|
||||
// the same size as the cluster. The bitmask will have two true bits and
|
||||
// false bits for the rest of items and we will loop through all the
|
||||
// permutations of the bitmask (combinations of two points). Get the
|
||||
// distance for the two points and add the distance to the averages.
|
||||
// The point with the smallest average than wins.
|
||||
|
||||
// The complexity should be O(n^2) but we will mostly apply this function
|
||||
// for small clusters only (cca 3 elements)
|
||||
|
||||
std::vector<bool> sel(clust.size(), false); // create full zero bitmask
|
||||
std::fill(sel.end() - 2, sel.end(), true); // insert the two ones
|
||||
std::vector<double> avgs(clust.size(), 0.0); // store the average distances
|
||||
|
||||
do {
|
||||
std::array<size_t, 2> idx;
|
||||
for(size_t i = 0, j = 0; i < clust.size(); i++)
|
||||
if(sel[i]) idx[j++] = i;
|
||||
|
||||
double d = df(pointfn(clust[idx[0]]),
|
||||
pointfn(clust[idx[1]]));
|
||||
|
||||
// add the distance to the sums for both associated points
|
||||
for(auto i : idx) avgs[i] += d;
|
||||
|
||||
// now continue with the next permutation of the bitmask with two 1s
|
||||
} while(std::next_permutation(sel.begin(), sel.end()));
|
||||
|
||||
// Divide by point size in the cluster to get the average (may be redundant)
|
||||
for(auto& a : avgs) a /= clust.size();
|
||||
|
||||
// get the lowest average distance and return the index
|
||||
auto minit = std::min_element(avgs.begin(), avgs.end());
|
||||
return long(minit - avgs.begin());
|
||||
}
|
||||
|
||||
|
||||
}} // namespace Slic3r::sla
|
||||
|
||||
#endif // CLUSTERING_HPP
|
||||
|
@ -1,27 +0,0 @@
|
||||
#ifndef SLA_COMMON_HPP
|
||||
#define SLA_COMMON_HPP
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <numeric>
|
||||
#include <functional>
|
||||
#include <Eigen/Geometry>
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// Typedefs from Point.hpp
|
||||
typedef Eigen::Matrix<float, 3, 1, Eigen::DontAlign> Vec3f;
|
||||
typedef Eigen::Matrix<double, 3, 1, Eigen::DontAlign> Vec3d;
|
||||
typedef Eigen::Matrix<int, 3, 1, Eigen::DontAlign> Vec3i;
|
||||
typedef Eigen::Matrix<int, 4, 1, Eigen::DontAlign> Vec4i;
|
||||
|
||||
namespace sla {
|
||||
|
||||
using PointSet = Eigen::MatrixXd;
|
||||
|
||||
} // namespace sla
|
||||
} // namespace Slic3r
|
||||
|
||||
|
||||
#endif // SLASUPPORTTREE_HPP
|
@ -1,5 +1,5 @@
|
||||
#include <libslic3r/SLA/Contour3D.hpp>
|
||||
#include <libslic3r/SLA/EigenMesh3D.hpp>
|
||||
#include <libslic3r/SLA/IndexedMesh.hpp>
|
||||
|
||||
#include <libslic3r/Format/objparser.hpp>
|
||||
|
||||
@ -27,7 +27,7 @@ Contour3D::Contour3D(TriangleMesh &&trmesh)
|
||||
faces3.swap(trmesh.its.indices);
|
||||
}
|
||||
|
||||
Contour3D::Contour3D(const EigenMesh3D &emesh) {
|
||||
Contour3D::Contour3D(const IndexedMesh &emesh) {
|
||||
points.reserve(emesh.vertices().size());
|
||||
faces3.reserve(emesh.indices().size());
|
||||
|
||||
|
@ -1,13 +1,16 @@
|
||||
#ifndef SLA_CONTOUR3D_HPP
|
||||
#define SLA_CONTOUR3D_HPP
|
||||
|
||||
#include <libslic3r/SLA/Common.hpp>
|
||||
|
||||
#include <libslic3r/TriangleMesh.hpp>
|
||||
|
||||
namespace Slic3r { namespace sla {
|
||||
namespace Slic3r {
|
||||
|
||||
class EigenMesh3D;
|
||||
// Used for quads (TODO: remove this, and convert quads to triangles in OpenVDBUtils)
|
||||
using Vec4i = Eigen::Matrix<int, 4, 1, Eigen::DontAlign>;
|
||||
|
||||
namespace sla {
|
||||
|
||||
class IndexedMesh;
|
||||
|
||||
/// Dumb vertex mesh consisting of triangles (or) quads. Capable of merging with
|
||||
/// other meshes of this type and converting to and from other mesh formats.
|
||||
@ -19,7 +22,7 @@ struct Contour3D {
|
||||
Contour3D() = default;
|
||||
Contour3D(const TriangleMesh &trmesh);
|
||||
Contour3D(TriangleMesh &&trmesh);
|
||||
Contour3D(const EigenMesh3D &emesh);
|
||||
Contour3D(const IndexedMesh &emesh);
|
||||
|
||||
Contour3D& merge(const Contour3D& ctr);
|
||||
Contour3D& merge(const Pointf3s& triangles);
|
||||
|
@ -3,11 +3,10 @@
|
||||
#include <libslic3r/OpenVDBUtils.hpp>
|
||||
#include <libslic3r/TriangleMesh.hpp>
|
||||
#include <libslic3r/SLA/Hollowing.hpp>
|
||||
#include <libslic3r/SLA/Contour3D.hpp>
|
||||
#include <libslic3r/SLA/EigenMesh3D.hpp>
|
||||
#include <libslic3r/SLA/SupportTreeBuilder.hpp>
|
||||
#include <libslic3r/SLA/IndexedMesh.hpp>
|
||||
#include <libslic3r/ClipperUtils.hpp>
|
||||
#include <libslic3r/SimplifyMesh.hpp>
|
||||
#include <libslic3r/SLA/SupportTreeMesher.hpp>
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
@ -160,7 +159,7 @@ bool DrainHole::get_intersections(const Vec3f& s, const Vec3f& dir,
|
||||
const Eigen::ParametrizedLine<float, 3> ray(s, dir.normalized());
|
||||
|
||||
for (size_t i=0; i<2; ++i)
|
||||
out[i] = std::make_pair(sla::EigenMesh3D::hit_result::infty(), Vec3d::Zero());
|
||||
out[i] = std::make_pair(sla::IndexedMesh::hit_result::infty(), Vec3d::Zero());
|
||||
|
||||
const float sqr_radius = pow(radius, 2.f);
|
||||
|
||||
|
@ -2,7 +2,6 @@
|
||||
#define SLA_HOLLOWING_HPP
|
||||
|
||||
#include <memory>
|
||||
#include <libslic3r/SLA/Common.hpp>
|
||||
#include <libslic3r/SLA/Contour3D.hpp>
|
||||
#include <libslic3r/SLA/JobController.hpp>
|
||||
|
||||
|
@ -1,187 +1,18 @@
|
||||
#include <cmath>
|
||||
#include <libslic3r/SLA/Common.hpp>
|
||||
#include <libslic3r/SLA/Concurrency.hpp>
|
||||
#include <libslic3r/SLA/SpatIndex.hpp>
|
||||
#include <libslic3r/SLA/EigenMesh3D.hpp>
|
||||
#include <libslic3r/SLA/Contour3D.hpp>
|
||||
#include <libslic3r/SLA/Clustering.hpp>
|
||||
#include "IndexedMesh.hpp"
|
||||
#include "Concurrency.hpp"
|
||||
|
||||
#include <libslic3r/AABBTreeIndirect.hpp>
|
||||
#include <libslic3r/TriangleMesh.hpp>
|
||||
|
||||
// for concave hull merging decisions
|
||||
#include <libslic3r/SLA/BoostAdapter.hpp>
|
||||
#include "boost/geometry/index/rtree.hpp"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4244)
|
||||
#pragma warning(disable: 4267)
|
||||
#endif
|
||||
|
||||
|
||||
#include <igl/remove_duplicate_vertices.h>
|
||||
#include <numeric>
|
||||
|
||||
#ifdef SLIC3R_HOLE_RAYCASTER
|
||||
#include <libslic3r/SLA/Hollowing.hpp>
|
||||
#include <libslic3r/SLA/Hollowing.hpp>
|
||||
#endif
|
||||
|
||||
namespace Slic3r { namespace sla {
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
namespace sla {
|
||||
|
||||
|
||||
/* **************************************************************************
|
||||
* PointIndex implementation
|
||||
* ************************************************************************** */
|
||||
|
||||
class PointIndex::Impl {
|
||||
public:
|
||||
using BoostIndex = boost::geometry::index::rtree< PointIndexEl,
|
||||
boost::geometry::index::rstar<16, 4> /* ? */ >;
|
||||
|
||||
BoostIndex m_store;
|
||||
};
|
||||
|
||||
PointIndex::PointIndex(): m_impl(new Impl()) {}
|
||||
PointIndex::~PointIndex() {}
|
||||
|
||||
PointIndex::PointIndex(const PointIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {}
|
||||
PointIndex::PointIndex(PointIndex&& cpy): m_impl(std::move(cpy.m_impl)) {}
|
||||
|
||||
PointIndex& PointIndex::operator=(const PointIndex &cpy)
|
||||
{
|
||||
m_impl.reset(new Impl(*cpy.m_impl));
|
||||
return *this;
|
||||
}
|
||||
|
||||
PointIndex& PointIndex::operator=(PointIndex &&cpy)
|
||||
{
|
||||
m_impl.swap(cpy.m_impl);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void PointIndex::insert(const PointIndexEl &el)
|
||||
{
|
||||
m_impl->m_store.insert(el);
|
||||
}
|
||||
|
||||
bool PointIndex::remove(const PointIndexEl& el)
|
||||
{
|
||||
return m_impl->m_store.remove(el) == 1;
|
||||
}
|
||||
|
||||
std::vector<PointIndexEl>
|
||||
PointIndex::query(std::function<bool(const PointIndexEl &)> fn) const
|
||||
{
|
||||
namespace bgi = boost::geometry::index;
|
||||
|
||||
std::vector<PointIndexEl> ret;
|
||||
m_impl->m_store.query(bgi::satisfies(fn), std::back_inserter(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<PointIndexEl> PointIndex::nearest(const Vec3d &el, unsigned k = 1) const
|
||||
{
|
||||
namespace bgi = boost::geometry::index;
|
||||
std::vector<PointIndexEl> ret; ret.reserve(k);
|
||||
m_impl->m_store.query(bgi::nearest(el, k), std::back_inserter(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t PointIndex::size() const
|
||||
{
|
||||
return m_impl->m_store.size();
|
||||
}
|
||||
|
||||
void PointIndex::foreach(std::function<void (const PointIndexEl &)> fn)
|
||||
{
|
||||
for(auto& el : m_impl->m_store) fn(el);
|
||||
}
|
||||
|
||||
void PointIndex::foreach(std::function<void (const PointIndexEl &)> fn) const
|
||||
{
|
||||
for(const auto &el : m_impl->m_store) fn(el);
|
||||
}
|
||||
|
||||
/* **************************************************************************
|
||||
* BoxIndex implementation
|
||||
* ************************************************************************** */
|
||||
|
||||
class BoxIndex::Impl {
|
||||
public:
|
||||
using BoostIndex = boost::geometry::index::
|
||||
rtree<BoxIndexEl, boost::geometry::index::rstar<16, 4> /* ? */>;
|
||||
|
||||
BoostIndex m_store;
|
||||
};
|
||||
|
||||
BoxIndex::BoxIndex(): m_impl(new Impl()) {}
|
||||
BoxIndex::~BoxIndex() {}
|
||||
|
||||
BoxIndex::BoxIndex(const BoxIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {}
|
||||
BoxIndex::BoxIndex(BoxIndex&& cpy): m_impl(std::move(cpy.m_impl)) {}
|
||||
|
||||
BoxIndex& BoxIndex::operator=(const BoxIndex &cpy)
|
||||
{
|
||||
m_impl.reset(new Impl(*cpy.m_impl));
|
||||
return *this;
|
||||
}
|
||||
|
||||
BoxIndex& BoxIndex::operator=(BoxIndex &&cpy)
|
||||
{
|
||||
m_impl.swap(cpy.m_impl);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void BoxIndex::insert(const BoxIndexEl &el)
|
||||
{
|
||||
m_impl->m_store.insert(el);
|
||||
}
|
||||
|
||||
bool BoxIndex::remove(const BoxIndexEl& el)
|
||||
{
|
||||
return m_impl->m_store.remove(el) == 1;
|
||||
}
|
||||
|
||||
std::vector<BoxIndexEl> BoxIndex::query(const BoundingBox &qrbb,
|
||||
BoxIndex::QueryType qt)
|
||||
{
|
||||
namespace bgi = boost::geometry::index;
|
||||
|
||||
std::vector<BoxIndexEl> ret; ret.reserve(m_impl->m_store.size());
|
||||
|
||||
switch (qt) {
|
||||
case qtIntersects:
|
||||
m_impl->m_store.query(bgi::intersects(qrbb), std::back_inserter(ret));
|
||||
break;
|
||||
case qtWithin:
|
||||
m_impl->m_store.query(bgi::within(qrbb), std::back_inserter(ret));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t BoxIndex::size() const
|
||||
{
|
||||
return m_impl->m_store.size();
|
||||
}
|
||||
|
||||
void BoxIndex::foreach(std::function<void (const BoxIndexEl &)> fn)
|
||||
{
|
||||
for(auto& el : m_impl->m_store) fn(el);
|
||||
}
|
||||
|
||||
|
||||
/* ****************************************************************************
|
||||
* EigenMesh3D implementation
|
||||
* ****************************************************************************/
|
||||
|
||||
|
||||
class EigenMesh3D::AABBImpl {
|
||||
class IndexedMesh::AABBImpl {
|
||||
private:
|
||||
AABBTreeIndirect::Tree3f m_tree;
|
||||
|
||||
@ -189,7 +20,7 @@ public:
|
||||
void init(const TriangleMesh& tm)
|
||||
{
|
||||
m_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(
|
||||
tm.its.vertices, tm.its.indices);
|
||||
tm.its.vertices, tm.its.indices);
|
||||
}
|
||||
|
||||
void intersect_ray(const TriangleMesh& tm,
|
||||
@ -215,9 +46,9 @@ public:
|
||||
size_t idx_unsigned = 0;
|
||||
Vec3d closest_vec3d(closest);
|
||||
double dist = AABBTreeIndirect::squared_distance_to_indexed_triangle_set(
|
||||
tm.its.vertices,
|
||||
tm.its.indices,
|
||||
m_tree, point, idx_unsigned, closest_vec3d);
|
||||
tm.its.vertices,
|
||||
tm.its.indices,
|
||||
m_tree, point, idx_unsigned, closest_vec3d);
|
||||
i = int(idx_unsigned);
|
||||
closest = closest_vec3d;
|
||||
return dist;
|
||||
@ -226,72 +57,71 @@ public:
|
||||
|
||||
static const constexpr double MESH_EPS = 1e-6;
|
||||
|
||||
EigenMesh3D::EigenMesh3D(const TriangleMesh& tmesh)
|
||||
IndexedMesh::IndexedMesh(const TriangleMesh& tmesh)
|
||||
: m_aabb(new AABBImpl()), m_tm(&tmesh)
|
||||
{
|
||||
auto&& bb = tmesh.bounding_box();
|
||||
m_ground_level += bb.min(Z);
|
||||
|
||||
|
||||
// Build the AABB accelaration tree
|
||||
m_aabb->init(tmesh);
|
||||
}
|
||||
|
||||
EigenMesh3D::~EigenMesh3D() {}
|
||||
IndexedMesh::~IndexedMesh() {}
|
||||
|
||||
EigenMesh3D::EigenMesh3D(const EigenMesh3D &other):
|
||||
IndexedMesh::IndexedMesh(const IndexedMesh &other):
|
||||
m_tm(other.m_tm), m_ground_level(other.m_ground_level),
|
||||
m_aabb( new AABBImpl(*other.m_aabb) ) {}
|
||||
|
||||
|
||||
EigenMesh3D &EigenMesh3D::operator=(const EigenMesh3D &other)
|
||||
IndexedMesh &IndexedMesh::operator=(const IndexedMesh &other)
|
||||
{
|
||||
m_tm = other.m_tm;
|
||||
m_ground_level = other.m_ground_level;
|
||||
m_aabb.reset(new AABBImpl(*other.m_aabb)); return *this;
|
||||
}
|
||||
|
||||
EigenMesh3D &EigenMesh3D::operator=(EigenMesh3D &&other) = default;
|
||||
IndexedMesh &IndexedMesh::operator=(IndexedMesh &&other) = default;
|
||||
|
||||
EigenMesh3D::EigenMesh3D(EigenMesh3D &&other) = default;
|
||||
IndexedMesh::IndexedMesh(IndexedMesh &&other) = default;
|
||||
|
||||
|
||||
|
||||
const std::vector<Vec3f>& EigenMesh3D::vertices() const
|
||||
const std::vector<Vec3f>& IndexedMesh::vertices() const
|
||||
{
|
||||
return m_tm->its.vertices;
|
||||
}
|
||||
|
||||
|
||||
|
||||
const std::vector<Vec3i>& EigenMesh3D::indices() const
|
||||
const std::vector<Vec3i>& IndexedMesh::indices() const
|
||||
{
|
||||
return m_tm->its.indices;
|
||||
}
|
||||
|
||||
|
||||
|
||||
const Vec3f& EigenMesh3D::vertices(size_t idx) const
|
||||
const Vec3f& IndexedMesh::vertices(size_t idx) const
|
||||
{
|
||||
return m_tm->its.vertices[idx];
|
||||
}
|
||||
|
||||
|
||||
|
||||
const Vec3i& EigenMesh3D::indices(size_t idx) const
|
||||
const Vec3i& IndexedMesh::indices(size_t idx) const
|
||||
{
|
||||
return m_tm->its.indices[idx];
|
||||
}
|
||||
|
||||
|
||||
|
||||
Vec3d EigenMesh3D::normal_by_face_id(int face_id) const {
|
||||
Vec3d IndexedMesh::normal_by_face_id(int face_id) const {
|
||||
return m_tm->stl.facet_start[face_id].normal.cast<double>();
|
||||
}
|
||||
|
||||
|
||||
|
||||
EigenMesh3D::hit_result
|
||||
EigenMesh3D::query_ray_hit(const Vec3d &s, const Vec3d &dir) const
|
||||
IndexedMesh::hit_result
|
||||
IndexedMesh::query_ray_hit(const Vec3d &s, const Vec3d &dir) const
|
||||
{
|
||||
assert(is_approx(dir.norm(), 1.));
|
||||
igl::Hit hit;
|
||||
@ -319,13 +149,13 @@ EigenMesh3D::query_ray_hit(const Vec3d &s, const Vec3d &dir) const
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<EigenMesh3D::hit_result>
|
||||
EigenMesh3D::query_ray_hits(const Vec3d &s, const Vec3d &dir) const
|
||||
std::vector<IndexedMesh::hit_result>
|
||||
IndexedMesh::query_ray_hits(const Vec3d &s, const Vec3d &dir) const
|
||||
{
|
||||
std::vector<EigenMesh3D::hit_result> outs;
|
||||
std::vector<IndexedMesh::hit_result> outs;
|
||||
std::vector<igl::Hit> hits;
|
||||
m_aabb->intersect_ray(*m_tm, s, dir, hits);
|
||||
|
||||
|
||||
// The sort is necessary, the hits are not always sorted.
|
||||
std::sort(hits.begin(), hits.end(),
|
||||
[](const igl::Hit& a, const igl::Hit& b) { return a.t < b.t; });
|
||||
@ -334,13 +164,13 @@ EigenMesh3D::query_ray_hits(const Vec3d &s, const Vec3d &dir) const
|
||||
// along an axis of a cube due to floating-point approximations in igl (?)
|
||||
hits.erase(std::unique(hits.begin(), hits.end(),
|
||||
[](const igl::Hit& a, const igl::Hit& b)
|
||||
{ return a.t == b.t; }),
|
||||
{ return a.t == b.t; }),
|
||||
hits.end());
|
||||
|
||||
// Convert the igl::Hit into hit_result
|
||||
outs.reserve(hits.size());
|
||||
for (const igl::Hit& hit : hits) {
|
||||
outs.emplace_back(EigenMesh3D::hit_result(*this));
|
||||
outs.emplace_back(IndexedMesh::hit_result(*this));
|
||||
outs.back().m_t = double(hit.t);
|
||||
outs.back().m_dir = dir;
|
||||
outs.back().m_source = s;
|
||||
@ -355,8 +185,8 @@ EigenMesh3D::query_ray_hits(const Vec3d &s, const Vec3d &dir) const
|
||||
|
||||
|
||||
#ifdef SLIC3R_HOLE_RAYCASTER
|
||||
EigenMesh3D::hit_result EigenMesh3D::filter_hits(
|
||||
const std::vector<EigenMesh3D::hit_result>& object_hits) const
|
||||
IndexedMesh::hit_result IndexedMesh::filter_hits(
|
||||
const std::vector<IndexedMesh::hit_result>& object_hits) const
|
||||
{
|
||||
assert(! m_holes.empty());
|
||||
hit_result out(*this);
|
||||
@ -377,7 +207,7 @@ EigenMesh3D::hit_result EigenMesh3D::filter_hits(
|
||||
};
|
||||
std::vector<HoleHit> hole_isects;
|
||||
hole_isects.reserve(m_holes.size());
|
||||
|
||||
|
||||
auto sf = s.cast<float>();
|
||||
auto dirf = dir.cast<float>();
|
||||
|
||||
@ -452,7 +282,7 @@ EigenMesh3D::hit_result EigenMesh3D::filter_hits(
|
||||
#endif
|
||||
|
||||
|
||||
double EigenMesh3D::squared_distance(const Vec3d &p, int& i, Vec3d& c) const {
|
||||
double IndexedMesh::squared_distance(const Vec3d &p, int& i, Vec3d& c) const {
|
||||
double sqdst = 0;
|
||||
Eigen::Matrix<double, 1, 3> pp = p;
|
||||
Eigen::Matrix<double, 1, 3> cc;
|
||||
@ -461,31 +291,19 @@ double EigenMesh3D::squared_distance(const Vec3d &p, int& i, Vec3d& c) const {
|
||||
return sqdst;
|
||||
}
|
||||
|
||||
/* ****************************************************************************
|
||||
* Misc functions
|
||||
* ****************************************************************************/
|
||||
|
||||
namespace {
|
||||
|
||||
bool point_on_edge(const Vec3d& p, const Vec3d& e1, const Vec3d& e2,
|
||||
double eps = 0.05)
|
||||
static bool point_on_edge(const Vec3d& p, const Vec3d& e1, const Vec3d& e2,
|
||||
double eps = 0.05)
|
||||
{
|
||||
using Line3D = Eigen::ParametrizedLine<double, 3>;
|
||||
|
||||
|
||||
auto line = Line3D::Through(e1, e2);
|
||||
double d = line.distance(p);
|
||||
return std::abs(d) < eps;
|
||||
}
|
||||
|
||||
template<class Vec> double distance(const Vec& pp1, const Vec& pp2) {
|
||||
auto p = pp2 - pp1;
|
||||
return std::sqrt(p.transpose() * p);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
PointSet normals(const PointSet& points,
|
||||
const EigenMesh3D& mesh,
|
||||
const IndexedMesh& mesh,
|
||||
double eps,
|
||||
std::function<void()> thr, // throw on cancel
|
||||
const std::vector<unsigned>& pt_indices)
|
||||
@ -531,11 +349,11 @@ PointSet normals(const PointSet& points,
|
||||
// ic will mark a single vertex.
|
||||
int ia = -1, ib = -1, ic = -1;
|
||||
|
||||
if (std::abs(distance(p, p1)) < eps) {
|
||||
if (std::abs((p - p1).norm()) < eps) {
|
||||
ic = trindex(0);
|
||||
} else if (std::abs(distance(p, p2)) < eps) {
|
||||
} else if (std::abs((p - p2).norm()) < eps) {
|
||||
ic = trindex(1);
|
||||
} else if (std::abs(distance(p, p3)) < eps) {
|
||||
} else if (std::abs((p - p3).norm()) < eps) {
|
||||
ic = trindex(2);
|
||||
} else if (point_on_edge(p, p1, p2, eps)) {
|
||||
ia = trindex(0);
|
||||
@ -612,148 +430,4 @@ PointSet normals(const PointSet& points,
|
||||
return ret;
|
||||
}
|
||||
|
||||
namespace bgi = boost::geometry::index;
|
||||
using Index3D = bgi::rtree< PointIndexEl, bgi::rstar<16, 4> /* ? */ >;
|
||||
|
||||
namespace {
|
||||
|
||||
bool cmp_ptidx_elements(const PointIndexEl& e1, const PointIndexEl& e2)
|
||||
{
|
||||
return e1.second < e2.second;
|
||||
};
|
||||
|
||||
ClusteredPoints cluster(Index3D &sindex,
|
||||
unsigned max_points,
|
||||
std::function<std::vector<PointIndexEl>(
|
||||
const Index3D &, const PointIndexEl &)> qfn)
|
||||
{
|
||||
using Elems = std::vector<PointIndexEl>;
|
||||
|
||||
// Recursive function for visiting all the points in a given distance to
|
||||
// each other
|
||||
std::function<void(Elems&, Elems&)> group =
|
||||
[&sindex, &group, max_points, qfn](Elems& pts, Elems& cluster)
|
||||
{
|
||||
for(auto& p : pts) {
|
||||
std::vector<PointIndexEl> tmp = qfn(sindex, p);
|
||||
|
||||
std::sort(tmp.begin(), tmp.end(), cmp_ptidx_elements);
|
||||
|
||||
Elems newpts;
|
||||
std::set_difference(tmp.begin(), tmp.end(),
|
||||
cluster.begin(), cluster.end(),
|
||||
std::back_inserter(newpts), cmp_ptidx_elements);
|
||||
|
||||
int c = max_points && newpts.size() + cluster.size() > max_points?
|
||||
int(max_points - cluster.size()) : int(newpts.size());
|
||||
|
||||
cluster.insert(cluster.end(), newpts.begin(), newpts.begin() + c);
|
||||
std::sort(cluster.begin(), cluster.end(), cmp_ptidx_elements);
|
||||
|
||||
if(!newpts.empty() && (!max_points || cluster.size() < max_points))
|
||||
group(newpts, cluster);
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<Elems> clusters;
|
||||
for(auto it = sindex.begin(); it != sindex.end();) {
|
||||
Elems cluster = {};
|
||||
Elems pts = {*it};
|
||||
group(pts, cluster);
|
||||
|
||||
for(auto& c : cluster) sindex.remove(c);
|
||||
it = sindex.begin();
|
||||
|
||||
clusters.emplace_back(cluster);
|
||||
}
|
||||
|
||||
ClusteredPoints result;
|
||||
for(auto& cluster : clusters) {
|
||||
result.emplace_back();
|
||||
for(auto c : cluster) result.back().emplace_back(c.second);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<PointIndexEl> distance_queryfn(const Index3D& sindex,
|
||||
const PointIndexEl& p,
|
||||
double dist,
|
||||
unsigned max_points)
|
||||
{
|
||||
std::vector<PointIndexEl> tmp; tmp.reserve(max_points);
|
||||
sindex.query(
|
||||
bgi::nearest(p.first, max_points),
|
||||
std::back_inserter(tmp)
|
||||
);
|
||||
|
||||
for(auto it = tmp.begin(); it < tmp.end(); ++it)
|
||||
if(distance(p.first, it->first) > dist) it = tmp.erase(it);
|
||||
|
||||
return tmp;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Clustering a set of points by the given criteria
|
||||
ClusteredPoints cluster(
|
||||
const std::vector<unsigned>& indices,
|
||||
std::function<Vec3d(unsigned)> pointfn,
|
||||
double dist,
|
||||
unsigned max_points)
|
||||
{
|
||||
// A spatial index for querying the nearest points
|
||||
Index3D sindex;
|
||||
|
||||
// Build the index
|
||||
for(auto idx : indices) sindex.insert( std::make_pair(pointfn(idx), idx));
|
||||
|
||||
return cluster(sindex, max_points,
|
||||
[dist, max_points](const Index3D& sidx, const PointIndexEl& p)
|
||||
{
|
||||
return distance_queryfn(sidx, p, dist, max_points);
|
||||
});
|
||||
}
|
||||
|
||||
// Clustering a set of points by the given criteria
|
||||
ClusteredPoints cluster(
|
||||
const std::vector<unsigned>& indices,
|
||||
std::function<Vec3d(unsigned)> pointfn,
|
||||
std::function<bool(const PointIndexEl&, const PointIndexEl&)> predicate,
|
||||
unsigned max_points)
|
||||
{
|
||||
// A spatial index for querying the nearest points
|
||||
Index3D sindex;
|
||||
|
||||
// Build the index
|
||||
for(auto idx : indices) sindex.insert( std::make_pair(pointfn(idx), idx));
|
||||
|
||||
return cluster(sindex, max_points,
|
||||
[max_points, predicate](const Index3D& sidx, const PointIndexEl& p)
|
||||
{
|
||||
std::vector<PointIndexEl> tmp; tmp.reserve(max_points);
|
||||
sidx.query(bgi::satisfies([p, predicate](const PointIndexEl& e){
|
||||
return predicate(p, e);
|
||||
}), std::back_inserter(tmp));
|
||||
return tmp;
|
||||
});
|
||||
}
|
||||
|
||||
ClusteredPoints cluster(const PointSet& pts, double dist, unsigned max_points)
|
||||
{
|
||||
// A spatial index for querying the nearest points
|
||||
Index3D sindex;
|
||||
|
||||
// Build the index
|
||||
for(Eigen::Index i = 0; i < pts.rows(); i++)
|
||||
sindex.insert(std::make_pair(Vec3d(pts.row(i)), unsigned(i)));
|
||||
|
||||
return cluster(sindex, max_points,
|
||||
[dist, max_points](const Index3D& sidx, const PointIndexEl& p)
|
||||
{
|
||||
return distance_queryfn(sidx, p, dist, max_points);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace sla
|
||||
} // namespace Slic3r
|
||||
}} // namespace Slic3r::sla
|
@ -1,8 +1,10 @@
|
||||
#ifndef SLA_EIGENMESH3D_H
|
||||
#define SLA_EIGENMESH3D_H
|
||||
#ifndef SLA_INDEXEDMESH_H
|
||||
#define SLA_INDEXEDMESH_H
|
||||
|
||||
#include <libslic3r/SLA/Common.hpp>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include <libslic3r/Point.hpp>
|
||||
|
||||
// There is an implementation of a hole-aware raycaster that was eventually
|
||||
// not used in production version. It is now hidden under following define
|
||||
@ -19,10 +21,12 @@ class TriangleMesh;
|
||||
|
||||
namespace sla {
|
||||
|
||||
using PointSet = Eigen::MatrixXd;
|
||||
|
||||
/// An index-triangle structure for libIGL functions. Also serves as an
|
||||
/// alternative (raw) input format for the SLASupportTree.
|
||||
// Implemented in libslic3r/SLA/Common.cpp
|
||||
class EigenMesh3D {
|
||||
class IndexedMesh {
|
||||
class AABBImpl;
|
||||
|
||||
const TriangleMesh* m_tm;
|
||||
@ -38,15 +42,15 @@ class EigenMesh3D {
|
||||
|
||||
public:
|
||||
|
||||
explicit EigenMesh3D(const TriangleMesh&);
|
||||
explicit IndexedMesh(const TriangleMesh&);
|
||||
|
||||
EigenMesh3D(const EigenMesh3D& other);
|
||||
EigenMesh3D& operator=(const EigenMesh3D&);
|
||||
IndexedMesh(const IndexedMesh& other);
|
||||
IndexedMesh& operator=(const IndexedMesh&);
|
||||
|
||||
EigenMesh3D(EigenMesh3D &&other);
|
||||
EigenMesh3D& operator=(EigenMesh3D &&other);
|
||||
IndexedMesh(IndexedMesh &&other);
|
||||
IndexedMesh& operator=(IndexedMesh &&other);
|
||||
|
||||
~EigenMesh3D();
|
||||
~IndexedMesh();
|
||||
|
||||
inline double ground_level() const { return m_ground_level + m_gnd_offset; }
|
||||
inline void ground_level_offset(double o) { m_gnd_offset = o; }
|
||||
@ -62,15 +66,15 @@ public:
|
||||
// m_t holds a distance from m_source to the intersection.
|
||||
double m_t = infty();
|
||||
int m_face_id = -1;
|
||||
const EigenMesh3D *m_mesh = nullptr;
|
||||
const IndexedMesh *m_mesh = nullptr;
|
||||
Vec3d m_dir;
|
||||
Vec3d m_source;
|
||||
Vec3d m_normal;
|
||||
friend class EigenMesh3D;
|
||||
friend class IndexedMesh;
|
||||
|
||||
// A valid object of this class can only be obtained from
|
||||
// EigenMesh3D::query_ray_hit method.
|
||||
explicit inline hit_result(const EigenMesh3D& em): m_mesh(&em) {}
|
||||
// IndexedMesh::query_ray_hit method.
|
||||
explicit inline hit_result(const IndexedMesh& em): m_mesh(&em) {}
|
||||
public:
|
||||
// This denotes no hit on the mesh.
|
||||
static inline constexpr double infty() { return std::numeric_limits<double>::infinity(); }
|
||||
@ -83,7 +87,7 @@ public:
|
||||
inline Vec3d position() const { return m_source + m_dir * m_t; }
|
||||
inline int face() const { return m_face_id; }
|
||||
inline bool is_valid() const { return m_mesh != nullptr; }
|
||||
inline bool is_hit() const { return !std::isinf(m_t); }
|
||||
inline bool is_hit() const { return m_face_id >= 0 && !std::isinf(m_t); }
|
||||
|
||||
inline const Vec3d& normal() const {
|
||||
assert(is_valid());
|
||||
@ -107,7 +111,7 @@ public:
|
||||
// This function is currently not used anywhere, it was written when the
|
||||
// holes were subtracted on slices, that is, before we started using CGAL
|
||||
// to actually cut the holes into the mesh.
|
||||
hit_result filter_hits(const std::vector<EigenMesh3D::hit_result>& obj_hits) const;
|
||||
hit_result filter_hits(const std::vector<IndexedMesh::hit_result>& obj_hits) const;
|
||||
#endif
|
||||
|
||||
// Casting a ray on the mesh, returns the distance where the hit occures.
|
||||
@ -125,16 +129,18 @@ public:
|
||||
}
|
||||
|
||||
Vec3d normal_by_face_id(int face_id) const;
|
||||
|
||||
const TriangleMesh * get_triangle_mesh() const { return m_tm; }
|
||||
};
|
||||
|
||||
// Calculate the normals for the selected points (from 'points' set) on the
|
||||
// mesh. This will call squared distance for each point.
|
||||
PointSet normals(const PointSet& points,
|
||||
const EigenMesh3D& convert_mesh,
|
||||
const IndexedMesh& convert_mesh,
|
||||
double eps = 0.05, // min distance from edges
|
||||
std::function<void()> throw_on_cancel = [](){},
|
||||
const std::vector<unsigned>& selected_points = {});
|
||||
|
||||
}} // namespace Slic3r::sla
|
||||
|
||||
#endif // EIGENMESH3D_H
|
||||
#endif // INDEXEDMESH_H
|
@ -2,6 +2,7 @@
|
||||
#define SLA_JOBCONTROLLER_HPP
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
namespace Slic3r { namespace sla {
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
#include <libslic3r/SLA/Pad.hpp>
|
||||
#include <libslic3r/SLA/Common.hpp>
|
||||
#include <libslic3r/SLA/SpatIndex.hpp>
|
||||
#include <libslic3r/SLA/BoostAdapter.hpp>
|
||||
#include <libslic3r/SLA/Contour3D.hpp>
|
||||
|
@ -4,7 +4,7 @@
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "SupportPoint.hpp"
|
||||
#include "Hollowing.hpp"
|
||||
#include "EigenMesh3D.hpp"
|
||||
#include "IndexedMesh.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
|
||||
#include <tbb/parallel_for.h>
|
||||
@ -15,7 +15,7 @@ template<class Pt> Vec3d pos(const Pt &p) { return p.pos.template cast<double>()
|
||||
template<class Pt> void pos(Pt &p, const Vec3d &pp) { p.pos = pp.cast<float>(); }
|
||||
|
||||
template<class PointType>
|
||||
void reproject_support_points(const EigenMesh3D &mesh, std::vector<PointType> &pts)
|
||||
void reproject_support_points(const IndexedMesh &mesh, std::vector<PointType> &pts)
|
||||
{
|
||||
tbb::parallel_for(size_t(0), pts.size(), [&mesh, &pts](size_t idx) {
|
||||
int junk;
|
||||
@ -40,7 +40,7 @@ inline void reproject_points_and_holes(ModelObject *object)
|
||||
|
||||
TriangleMesh rmsh = object->raw_mesh();
|
||||
rmsh.require_shared_vertices();
|
||||
EigenMesh3D emesh{rmsh};
|
||||
IndexedMesh emesh{rmsh};
|
||||
|
||||
if (has_sppoints)
|
||||
reproject_support_points(emesh, object->sla_support_points);
|
||||
|
@ -2,7 +2,6 @@
|
||||
#include <exception>
|
||||
|
||||
#include <libnest2d/optimizers/nlopt/genetic.hpp>
|
||||
#include <libslic3r/SLA/Common.hpp>
|
||||
#include <libslic3r/SLA/Rotfinder.hpp>
|
||||
#include <libslic3r/SLA/SupportTree.hpp>
|
||||
#include "Model.hpp"
|
||||
|
161
src/libslic3r/SLA/SpatIndex.cpp
Normal file
161
src/libslic3r/SLA/SpatIndex.cpp
Normal file
@ -0,0 +1,161 @@
|
||||
#include "SpatIndex.hpp"
|
||||
|
||||
// for concave hull merging decisions
|
||||
#include <libslic3r/SLA/BoostAdapter.hpp>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4244)
|
||||
#pragma warning(disable: 4267)
|
||||
#endif
|
||||
|
||||
#include "boost/geometry/index/rtree.hpp"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
namespace Slic3r { namespace sla {
|
||||
|
||||
/* **************************************************************************
|
||||
* PointIndex implementation
|
||||
* ************************************************************************** */
|
||||
|
||||
class PointIndex::Impl {
|
||||
public:
|
||||
using BoostIndex = boost::geometry::index::rtree< PointIndexEl,
|
||||
boost::geometry::index::rstar<16, 4> /* ? */ >;
|
||||
|
||||
BoostIndex m_store;
|
||||
};
|
||||
|
||||
PointIndex::PointIndex(): m_impl(new Impl()) {}
|
||||
PointIndex::~PointIndex() {}
|
||||
|
||||
PointIndex::PointIndex(const PointIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {}
|
||||
PointIndex::PointIndex(PointIndex&& cpy): m_impl(std::move(cpy.m_impl)) {}
|
||||
|
||||
PointIndex& PointIndex::operator=(const PointIndex &cpy)
|
||||
{
|
||||
m_impl.reset(new Impl(*cpy.m_impl));
|
||||
return *this;
|
||||
}
|
||||
|
||||
PointIndex& PointIndex::operator=(PointIndex &&cpy)
|
||||
{
|
||||
m_impl.swap(cpy.m_impl);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void PointIndex::insert(const PointIndexEl &el)
|
||||
{
|
||||
m_impl->m_store.insert(el);
|
||||
}
|
||||
|
||||
bool PointIndex::remove(const PointIndexEl& el)
|
||||
{
|
||||
return m_impl->m_store.remove(el) == 1;
|
||||
}
|
||||
|
||||
std::vector<PointIndexEl>
|
||||
PointIndex::query(std::function<bool(const PointIndexEl &)> fn) const
|
||||
{
|
||||
namespace bgi = boost::geometry::index;
|
||||
|
||||
std::vector<PointIndexEl> ret;
|
||||
m_impl->m_store.query(bgi::satisfies(fn), std::back_inserter(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<PointIndexEl> PointIndex::nearest(const Vec3d &el, unsigned k = 1) const
|
||||
{
|
||||
namespace bgi = boost::geometry::index;
|
||||
std::vector<PointIndexEl> ret; ret.reserve(k);
|
||||
m_impl->m_store.query(bgi::nearest(el, k), std::back_inserter(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t PointIndex::size() const
|
||||
{
|
||||
return m_impl->m_store.size();
|
||||
}
|
||||
|
||||
void PointIndex::foreach(std::function<void (const PointIndexEl &)> fn)
|
||||
{
|
||||
for(auto& el : m_impl->m_store) fn(el);
|
||||
}
|
||||
|
||||
void PointIndex::foreach(std::function<void (const PointIndexEl &)> fn) const
|
||||
{
|
||||
for(const auto &el : m_impl->m_store) fn(el);
|
||||
}
|
||||
|
||||
/* **************************************************************************
|
||||
* BoxIndex implementation
|
||||
* ************************************************************************** */
|
||||
|
||||
class BoxIndex::Impl {
|
||||
public:
|
||||
using BoostIndex = boost::geometry::index::
|
||||
rtree<BoxIndexEl, boost::geometry::index::rstar<16, 4> /* ? */>;
|
||||
|
||||
BoostIndex m_store;
|
||||
};
|
||||
|
||||
BoxIndex::BoxIndex(): m_impl(new Impl()) {}
|
||||
BoxIndex::~BoxIndex() {}
|
||||
|
||||
BoxIndex::BoxIndex(const BoxIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {}
|
||||
BoxIndex::BoxIndex(BoxIndex&& cpy): m_impl(std::move(cpy.m_impl)) {}
|
||||
|
||||
BoxIndex& BoxIndex::operator=(const BoxIndex &cpy)
|
||||
{
|
||||
m_impl.reset(new Impl(*cpy.m_impl));
|
||||
return *this;
|
||||
}
|
||||
|
||||
BoxIndex& BoxIndex::operator=(BoxIndex &&cpy)
|
||||
{
|
||||
m_impl.swap(cpy.m_impl);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void BoxIndex::insert(const BoxIndexEl &el)
|
||||
{
|
||||
m_impl->m_store.insert(el);
|
||||
}
|
||||
|
||||
bool BoxIndex::remove(const BoxIndexEl& el)
|
||||
{
|
||||
return m_impl->m_store.remove(el) == 1;
|
||||
}
|
||||
|
||||
std::vector<BoxIndexEl> BoxIndex::query(const BoundingBox &qrbb,
|
||||
BoxIndex::QueryType qt)
|
||||
{
|
||||
namespace bgi = boost::geometry::index;
|
||||
|
||||
std::vector<BoxIndexEl> ret; ret.reserve(m_impl->m_store.size());
|
||||
|
||||
switch (qt) {
|
||||
case qtIntersects:
|
||||
m_impl->m_store.query(bgi::intersects(qrbb), std::back_inserter(ret));
|
||||
break;
|
||||
case qtWithin:
|
||||
m_impl->m_store.query(bgi::within(qrbb), std::back_inserter(ret));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t BoxIndex::size() const
|
||||
{
|
||||
return m_impl->m_store.size();
|
||||
}
|
||||
|
||||
void BoxIndex::foreach(std::function<void (const BoxIndexEl &)> fn)
|
||||
{
|
||||
for(auto& el : m_impl->m_store) fn(el);
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::sla
|
@ -73,7 +73,7 @@ public:
|
||||
BoxIndex& operator=(BoxIndex&&);
|
||||
|
||||
void insert(const BoxIndexEl&);
|
||||
inline void insert(const BoundingBox& bb, unsigned idx)
|
||||
void insert(const BoundingBox& bb, unsigned idx)
|
||||
{
|
||||
insert(std::make_pair(bb, unsigned(idx)));
|
||||
}
|
||||
|
@ -2,7 +2,6 @@
|
||||
#define SLA_SUPPORTPOINT_HPP
|
||||
|
||||
#include <vector>
|
||||
#include <libslic3r/SLA/Common.hpp>
|
||||
#include <libslic3r/ExPolygon.hpp>
|
||||
|
||||
namespace Slic3r { namespace sla {
|
||||
@ -29,13 +28,13 @@ struct SupportPoint
|
||||
float pos_y,
|
||||
float pos_z,
|
||||
float head_radius,
|
||||
bool new_island)
|
||||
bool new_island = false)
|
||||
: pos(pos_x, pos_y, pos_z)
|
||||
, head_front_radius(head_radius)
|
||||
, is_new_island(new_island)
|
||||
{}
|
||||
|
||||
SupportPoint(Vec3f position, float head_radius, bool new_island)
|
||||
SupportPoint(Vec3f position, float head_radius, bool new_island = false)
|
||||
: pos(position)
|
||||
, head_front_radius(head_radius)
|
||||
, is_new_island(new_island)
|
||||
|
@ -50,7 +50,7 @@ float SupportPointGenerator::distance_limit(float angle) const
|
||||
}*/
|
||||
|
||||
SupportPointGenerator::SupportPointGenerator(
|
||||
const sla::EigenMesh3D &emesh,
|
||||
const sla::IndexedMesh &emesh,
|
||||
const std::vector<ExPolygons> &slices,
|
||||
const std::vector<float> & heights,
|
||||
const Config & config,
|
||||
@ -64,7 +64,7 @@ SupportPointGenerator::SupportPointGenerator(
|
||||
}
|
||||
|
||||
SupportPointGenerator::SupportPointGenerator(
|
||||
const EigenMesh3D &emesh,
|
||||
const IndexedMesh &emesh,
|
||||
const SupportPointGenerator::Config &config,
|
||||
std::function<void ()> throw_on_cancel,
|
||||
std::function<void (int)> statusfn)
|
||||
@ -95,8 +95,8 @@ void SupportPointGenerator::project_onto_mesh(std::vector<sla::SupportPoint>& po
|
||||
m_throw_on_cancel();
|
||||
Vec3f& p = points[point_id].pos;
|
||||
// Project the point upward and downward and choose the closer intersection with the mesh.
|
||||
sla::EigenMesh3D::hit_result hit_up = m_emesh.query_ray_hit(p.cast<double>(), Vec3d(0., 0., 1.));
|
||||
sla::EigenMesh3D::hit_result hit_down = m_emesh.query_ray_hit(p.cast<double>(), Vec3d(0., 0., -1.));
|
||||
sla::IndexedMesh::hit_result hit_up = m_emesh.query_ray_hit(p.cast<double>(), Vec3d(0., 0., 1.));
|
||||
sla::IndexedMesh::hit_result hit_down = m_emesh.query_ray_hit(p.cast<double>(), Vec3d(0., 0., -1.));
|
||||
|
||||
bool up = hit_up.is_hit();
|
||||
bool down = hit_down.is_hit();
|
||||
@ -104,7 +104,7 @@ void SupportPointGenerator::project_onto_mesh(std::vector<sla::SupportPoint>& po
|
||||
if (!up && !down)
|
||||
continue;
|
||||
|
||||
sla::EigenMesh3D::hit_result& hit = (!down || (hit_up.distance() < hit_down.distance())) ? hit_up : hit_down;
|
||||
sla::IndexedMesh::hit_result& hit = (!down || (hit_up.distance() < hit_down.distance())) ? hit_up : hit_down;
|
||||
p = p + (hit.distance() * hit.direction()).cast<float>();
|
||||
}
|
||||
});
|
||||
@ -523,15 +523,12 @@ void SupportPointGenerator::uniformly_cover(const ExPolygons& islands, Structure
|
||||
}
|
||||
}
|
||||
|
||||
void remove_bottom_points(std::vector<SupportPoint> &pts, double gnd_lvl, double tolerance)
|
||||
void remove_bottom_points(std::vector<SupportPoint> &pts, float lvl)
|
||||
{
|
||||
// get iterator to the reorganized vector end
|
||||
auto endit =
|
||||
std::remove_if(pts.begin(), pts.end(),
|
||||
[tolerance, gnd_lvl](const sla::SupportPoint &sp) {
|
||||
double diff = std::abs(gnd_lvl -
|
||||
double(sp.pos(Z)));
|
||||
return diff <= tolerance;
|
||||
auto endit = std::remove_if(pts.begin(), pts.end(), [lvl]
|
||||
(const sla::SupportPoint &sp) {
|
||||
return sp.pos.z() <= lvl;
|
||||
});
|
||||
|
||||
// erase all elements after the new end
|
||||
|
@ -3,9 +3,8 @@
|
||||
|
||||
#include <random>
|
||||
|
||||
#include <libslic3r/SLA/Common.hpp>
|
||||
#include <libslic3r/SLA/SupportPoint.hpp>
|
||||
#include <libslic3r/SLA/EigenMesh3D.hpp>
|
||||
#include <libslic3r/SLA/IndexedMesh.hpp>
|
||||
|
||||
#include <libslic3r/BoundingBox.hpp>
|
||||
#include <libslic3r/ClipperUtils.hpp>
|
||||
@ -28,10 +27,10 @@ public:
|
||||
inline float tear_pressure() const { return 1.f; } // pressure that the display exerts (the force unit per mm2)
|
||||
};
|
||||
|
||||
SupportPointGenerator(const EigenMesh3D& emesh, const std::vector<ExPolygons>& slices,
|
||||
SupportPointGenerator(const IndexedMesh& emesh, const std::vector<ExPolygons>& slices,
|
||||
const std::vector<float>& heights, const Config& config, std::function<void(void)> throw_on_cancel, std::function<void(int)> statusfn);
|
||||
|
||||
SupportPointGenerator(const EigenMesh3D& emesh, const Config& config, std::function<void(void)> throw_on_cancel, std::function<void(int)> statusfn);
|
||||
SupportPointGenerator(const IndexedMesh& emesh, const Config& config, std::function<void(void)> throw_on_cancel, std::function<void(int)> statusfn);
|
||||
|
||||
const std::vector<SupportPoint>& output() const { return m_output; }
|
||||
std::vector<SupportPoint>& output() { return m_output; }
|
||||
@ -207,14 +206,14 @@ private:
|
||||
static void output_structures(const std::vector<Structure> &structures);
|
||||
#endif // SLA_SUPPORTPOINTGEN_DEBUG
|
||||
|
||||
const EigenMesh3D& m_emesh;
|
||||
const IndexedMesh& m_emesh;
|
||||
std::function<void(void)> m_throw_on_cancel;
|
||||
std::function<void(int)> m_statusfn;
|
||||
|
||||
std::mt19937 m_rng;
|
||||
};
|
||||
|
||||
void remove_bottom_points(std::vector<SupportPoint> &pts, double gnd_lvl, double tolerance);
|
||||
void remove_bottom_points(std::vector<SupportPoint> &pts, float lvl);
|
||||
|
||||
}} // namespace Slic3r::sla
|
||||
|
||||
|
@ -5,9 +5,9 @@
|
||||
|
||||
#include <numeric>
|
||||
#include <libslic3r/SLA/SupportTree.hpp>
|
||||
#include <libslic3r/SLA/Common.hpp>
|
||||
#include <libslic3r/SLA/SpatIndex.hpp>
|
||||
#include <libslic3r/SLA/SupportTreeBuilder.hpp>
|
||||
#include <libslic3r/SLA/SupportTreeBuildsteps.hpp>
|
||||
|
||||
#include <libslic3r/MTUtils.hpp>
|
||||
#include <libslic3r/ClipperUtils.hpp>
|
||||
@ -28,20 +28,6 @@
|
||||
namespace Slic3r {
|
||||
namespace sla {
|
||||
|
||||
// Compile time configuration value definitions:
|
||||
|
||||
// The max Z angle for a normal at which it will get completely ignored.
|
||||
const double SupportConfig::normal_cutoff_angle = 150.0 * M_PI / 180.0;
|
||||
|
||||
// The shortest distance of any support structure from the model surface
|
||||
const double SupportConfig::safety_distance_mm = 0.5;
|
||||
|
||||
const double SupportConfig::max_solo_pillar_height_mm = 15.0;
|
||||
const double SupportConfig::max_dual_pillar_height_mm = 35.0;
|
||||
const double SupportConfig::optimizer_rel_score_diff = 1e-6;
|
||||
const unsigned SupportConfig::optimizer_max_iterations = 1000;
|
||||
const unsigned SupportConfig::pillar_cascade_neighbors = 3;
|
||||
|
||||
void SupportTree::retrieve_full_mesh(TriangleMesh &outmesh) const {
|
||||
outmesh.merge(retrieve_mesh(MeshType::Support));
|
||||
outmesh.merge(retrieve_mesh(MeshType::Pad));
|
||||
@ -103,9 +89,11 @@ SupportTree::UPtr SupportTree::create(const SupportableMesh &sm,
|
||||
builder->m_ctl = ctl;
|
||||
|
||||
if (sm.cfg.enabled) {
|
||||
builder->build(sm);
|
||||
// Execute takes care about the ground_level
|
||||
SupportTreeBuildsteps::execute(*builder, sm);
|
||||
builder->merge_and_cleanup(); // clean metadata, leave only the meshes.
|
||||
} else {
|
||||
// If a pad gets added later, it will be in the right Z level
|
||||
builder->ground_level = sm.emesh.ground_level();
|
||||
}
|
||||
|
||||
|
@ -5,9 +5,8 @@
|
||||
#include <memory>
|
||||
#include <Eigen/Geometry>
|
||||
|
||||
#include <libslic3r/SLA/Common.hpp>
|
||||
#include <libslic3r/SLA/Pad.hpp>
|
||||
#include <libslic3r/SLA/EigenMesh3D.hpp>
|
||||
#include <libslic3r/SLA/IndexedMesh.hpp>
|
||||
#include <libslic3r/SLA/SupportPoint.hpp>
|
||||
#include <libslic3r/SLA/JobController.hpp>
|
||||
|
||||
@ -32,7 +31,7 @@ enum class PillarConnectionMode
|
||||
dynamic
|
||||
};
|
||||
|
||||
struct SupportConfig
|
||||
struct SupportTreeConfig
|
||||
{
|
||||
bool enabled = true;
|
||||
|
||||
@ -45,6 +44,8 @@ struct SupportConfig
|
||||
// Radius of the back side of the 3d arrow.
|
||||
double head_back_radius_mm = 0.5;
|
||||
|
||||
double head_fallback_radius_mm = 0.25;
|
||||
|
||||
// Width in mm from the back sphere center to the front sphere center.
|
||||
double head_width_mm = 1.0;
|
||||
|
||||
@ -95,36 +96,43 @@ struct SupportConfig
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// The max Z angle for a normal at which it will get completely ignored.
|
||||
static const double normal_cutoff_angle;
|
||||
static const double constexpr normal_cutoff_angle = 150.0 * M_PI / 180.0;
|
||||
|
||||
// The shortest distance of any support structure from the model surface
|
||||
static const double safety_distance_mm;
|
||||
static const double constexpr safety_distance_mm = 0.5;
|
||||
|
||||
static const double max_solo_pillar_height_mm;
|
||||
static const double max_dual_pillar_height_mm;
|
||||
static const double optimizer_rel_score_diff;
|
||||
static const unsigned optimizer_max_iterations;
|
||||
static const unsigned pillar_cascade_neighbors;
|
||||
static const double constexpr max_solo_pillar_height_mm = 15.0;
|
||||
static const double constexpr max_dual_pillar_height_mm = 35.0;
|
||||
static const double constexpr optimizer_rel_score_diff = 1e-6;
|
||||
static const unsigned constexpr optimizer_max_iterations = 1000;
|
||||
static const unsigned constexpr pillar_cascade_neighbors = 3;
|
||||
|
||||
};
|
||||
|
||||
// TODO: Part of future refactor
|
||||
//class SupportConfig {
|
||||
// std::optional<SupportTreeConfig> tree_cfg {std::in_place_t{}}; // fill up
|
||||
// std::optional<PadConfig> pad_cfg;
|
||||
//};
|
||||
|
||||
enum class MeshType { Support, Pad };
|
||||
|
||||
struct SupportableMesh
|
||||
{
|
||||
EigenMesh3D emesh;
|
||||
IndexedMesh emesh;
|
||||
SupportPoints pts;
|
||||
SupportConfig cfg;
|
||||
SupportTreeConfig cfg;
|
||||
PadConfig pad_cfg;
|
||||
|
||||
explicit SupportableMesh(const TriangleMesh & trmsh,
|
||||
const SupportPoints &sp,
|
||||
const SupportConfig &c)
|
||||
const SupportTreeConfig &c)
|
||||
: emesh{trmsh}, pts{sp}, cfg{c}
|
||||
{}
|
||||
|
||||
explicit SupportableMesh(const EigenMesh3D &em,
|
||||
explicit SupportableMesh(const IndexedMesh &em,
|
||||
const SupportPoints &sp,
|
||||
const SupportConfig &c)
|
||||
const SupportTreeConfig &c)
|
||||
: emesh{em}, pts{sp}, cfg{c}
|
||||
{}
|
||||
};
|
||||
|
@ -1,336 +1,26 @@
|
||||
#define NOMINMAX
|
||||
|
||||
#include <libslic3r/SLA/SupportTreeBuilder.hpp>
|
||||
#include <libslic3r/SLA/SupportTreeBuildsteps.hpp>
|
||||
#include <libslic3r/SLA/SupportTreeMesher.hpp>
|
||||
#include <libslic3r/SLA/Contour3D.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace sla {
|
||||
|
||||
Contour3D sphere(double rho, Portion portion, double fa) {
|
||||
|
||||
Contour3D ret;
|
||||
|
||||
// prohibit close to zero radius
|
||||
if(rho <= 1e-6 && rho >= -1e-6) return ret;
|
||||
|
||||
auto& vertices = ret.points;
|
||||
auto& facets = ret.faces3;
|
||||
|
||||
// Algorithm:
|
||||
// Add points one-by-one to the sphere grid and form facets using relative
|
||||
// coordinates. Sphere is composed effectively of a mesh of stacked circles.
|
||||
|
||||
// adjust via rounding to get an even multiple for any provided angle.
|
||||
double angle = (2*PI / floor(2*PI / fa));
|
||||
|
||||
// Ring to be scaled to generate the steps of the sphere
|
||||
std::vector<double> ring;
|
||||
|
||||
for (double i = 0; i < 2*PI; i+=angle) ring.emplace_back(i);
|
||||
|
||||
const auto sbegin = size_t(2*std::get<0>(portion)/angle);
|
||||
const auto send = size_t(2*std::get<1>(portion)/angle);
|
||||
|
||||
const size_t steps = ring.size();
|
||||
const double increment = 1.0 / double(steps);
|
||||
|
||||
// special case: first ring connects to 0,0,0
|
||||
// insert and form facets.
|
||||
if(sbegin == 0)
|
||||
vertices.emplace_back(Vec3d(0.0, 0.0, -rho + increment*sbegin*2.0*rho));
|
||||
|
||||
auto id = coord_t(vertices.size());
|
||||
for (size_t i = 0; i < ring.size(); i++) {
|
||||
// Fixed scaling
|
||||
const double z = -rho + increment*rho*2.0 * (sbegin + 1.0);
|
||||
// radius of the circle for this step.
|
||||
const double r = std::sqrt(std::abs(rho*rho - z*z));
|
||||
Vec2d b = Eigen::Rotation2Dd(ring[i]) * Eigen::Vector2d(0, r);
|
||||
vertices.emplace_back(Vec3d(b(0), b(1), z));
|
||||
|
||||
if (sbegin == 0)
|
||||
(i == 0) ? facets.emplace_back(coord_t(ring.size()), 0, 1) :
|
||||
facets.emplace_back(id - 1, 0, id);
|
||||
++id;
|
||||
}
|
||||
|
||||
// General case: insert and form facets for each step,
|
||||
// joining it to the ring below it.
|
||||
for (size_t s = sbegin + 2; s < send - 1; s++) {
|
||||
const double z = -rho + increment*double(s*2.0*rho);
|
||||
const double r = std::sqrt(std::abs(rho*rho - z*z));
|
||||
|
||||
for (size_t i = 0; i < ring.size(); i++) {
|
||||
Vec2d b = Eigen::Rotation2Dd(ring[i]) * Eigen::Vector2d(0, r);
|
||||
vertices.emplace_back(Vec3d(b(0), b(1), z));
|
||||
auto id_ringsize = coord_t(id - int(ring.size()));
|
||||
if (i == 0) {
|
||||
// wrap around
|
||||
facets.emplace_back(id - 1, id, id + coord_t(ring.size() - 1) );
|
||||
facets.emplace_back(id - 1, id_ringsize, id);
|
||||
} else {
|
||||
facets.emplace_back(id_ringsize - 1, id_ringsize, id);
|
||||
facets.emplace_back(id - 1, id_ringsize - 1, id);
|
||||
}
|
||||
id++;
|
||||
}
|
||||
}
|
||||
|
||||
// special case: last ring connects to 0,0,rho*2.0
|
||||
// only form facets.
|
||||
if(send >= size_t(2*PI / angle)) {
|
||||
vertices.emplace_back(Vec3d(0.0, 0.0, -rho + increment*send*2.0*rho));
|
||||
for (size_t i = 0; i < ring.size(); i++) {
|
||||
auto id_ringsize = coord_t(id - int(ring.size()));
|
||||
if (i == 0) {
|
||||
// third vertex is on the other side of the ring.
|
||||
facets.emplace_back(id - 1, id_ringsize, id);
|
||||
} else {
|
||||
auto ci = coord_t(id_ringsize + coord_t(i));
|
||||
facets.emplace_back(ci - 1, ci, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
id++;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Contour3D cylinder(double r, double h, size_t ssteps, const Vec3d &sp)
|
||||
{
|
||||
Contour3D ret;
|
||||
|
||||
auto steps = int(ssteps);
|
||||
auto& points = ret.points;
|
||||
auto& indices = ret.faces3;
|
||||
points.reserve(2*ssteps);
|
||||
double a = 2*PI/steps;
|
||||
|
||||
Vec3d jp = sp;
|
||||
Vec3d endp = {sp(X), sp(Y), sp(Z) + h};
|
||||
|
||||
// Upper circle points
|
||||
for(int i = 0; i < steps; ++i) {
|
||||
double phi = i*a;
|
||||
double ex = endp(X) + r*std::cos(phi);
|
||||
double ey = endp(Y) + r*std::sin(phi);
|
||||
points.emplace_back(ex, ey, endp(Z));
|
||||
}
|
||||
|
||||
// Lower circle points
|
||||
for(int i = 0; i < steps; ++i) {
|
||||
double phi = i*a;
|
||||
double x = jp(X) + r*std::cos(phi);
|
||||
double y = jp(Y) + r*std::sin(phi);
|
||||
points.emplace_back(x, y, jp(Z));
|
||||
}
|
||||
|
||||
// Now create long triangles connecting upper and lower circles
|
||||
indices.reserve(2*ssteps);
|
||||
auto offs = steps;
|
||||
for(int i = 0; i < steps - 1; ++i) {
|
||||
indices.emplace_back(i, i + offs, offs + i + 1);
|
||||
indices.emplace_back(i, offs + i + 1, i + 1);
|
||||
}
|
||||
|
||||
// Last triangle connecting the first and last vertices
|
||||
auto last = steps - 1;
|
||||
indices.emplace_back(0, last, offs);
|
||||
indices.emplace_back(last, offs + last, offs);
|
||||
|
||||
// According to the slicing algorithms, we need to aid them with generating
|
||||
// a watertight body. So we create a triangle fan for the upper and lower
|
||||
// ending of the cylinder to close the geometry.
|
||||
points.emplace_back(jp); int ci = int(points.size() - 1);
|
||||
for(int i = 0; i < steps - 1; ++i)
|
||||
indices.emplace_back(i + offs + 1, i + offs, ci);
|
||||
|
||||
indices.emplace_back(offs, steps + offs - 1, ci);
|
||||
|
||||
points.emplace_back(endp); ci = int(points.size() - 1);
|
||||
for(int i = 0; i < steps - 1; ++i)
|
||||
indices.emplace_back(ci, i, i + 1);
|
||||
|
||||
indices.emplace_back(steps - 1, 0, ci);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Head::Head(double r_big_mm,
|
||||
double r_small_mm,
|
||||
double length_mm,
|
||||
double penetration,
|
||||
const Vec3d &direction,
|
||||
const Vec3d &offset,
|
||||
const size_t circlesteps)
|
||||
: steps(circlesteps)
|
||||
, dir(direction)
|
||||
, tr(offset)
|
||||
const Vec3d &offset)
|
||||
: dir(direction)
|
||||
, pos(offset)
|
||||
, r_back_mm(r_big_mm)
|
||||
, r_pin_mm(r_small_mm)
|
||||
, width_mm(length_mm)
|
||||
, penetration_mm(penetration)
|
||||
{
|
||||
assert(width_mm > 0.);
|
||||
assert(r_back_mm > 0.);
|
||||
assert(r_pin_mm > 0.);
|
||||
|
||||
// We create two spheres which will be connected with a robe that fits
|
||||
// both circles perfectly.
|
||||
|
||||
// Set up the model detail level
|
||||
const double detail = 2*PI/steps;
|
||||
|
||||
// We don't generate whole circles. Instead, we generate only the
|
||||
// portions which are visible (not covered by the robe) To know the
|
||||
// exact portion of the bottom and top circles we need to use some
|
||||
// rules of tangent circles from which we can derive (using simple
|
||||
// triangles the following relations:
|
||||
|
||||
// The height of the whole mesh
|
||||
const double h = r_big_mm + r_small_mm + width_mm;
|
||||
double phi = PI/2 - std::acos( (r_big_mm - r_small_mm) / h );
|
||||
|
||||
// To generate a whole circle we would pass a portion of (0, Pi)
|
||||
// To generate only a half horizontal circle we can pass (0, Pi/2)
|
||||
// The calculated phi is an offset to the half circles needed to smooth
|
||||
// the transition from the circle to the robe geometry
|
||||
|
||||
auto&& s1 = sphere(r_big_mm, make_portion(0, PI/2 + phi), detail);
|
||||
auto&& s2 = sphere(r_small_mm, make_portion(PI/2 + phi, PI), detail);
|
||||
|
||||
for(auto& p : s2.points) p.z() += h;
|
||||
|
||||
mesh.merge(s1);
|
||||
mesh.merge(s2);
|
||||
|
||||
for(size_t idx1 = s1.points.size() - steps, idx2 = s1.points.size();
|
||||
idx1 < s1.points.size() - 1;
|
||||
idx1++, idx2++)
|
||||
{
|
||||
coord_t i1s1 = coord_t(idx1), i1s2 = coord_t(idx2);
|
||||
coord_t i2s1 = i1s1 + 1, i2s2 = i1s2 + 1;
|
||||
|
||||
mesh.faces3.emplace_back(i1s1, i2s1, i2s2);
|
||||
mesh.faces3.emplace_back(i1s1, i2s2, i1s2);
|
||||
}
|
||||
|
||||
auto i1s1 = coord_t(s1.points.size()) - coord_t(steps);
|
||||
auto i2s1 = coord_t(s1.points.size()) - 1;
|
||||
auto i1s2 = coord_t(s1.points.size());
|
||||
auto i2s2 = coord_t(s1.points.size()) + coord_t(steps) - 1;
|
||||
|
||||
mesh.faces3.emplace_back(i2s2, i2s1, i1s1);
|
||||
mesh.faces3.emplace_back(i1s2, i2s2, i1s1);
|
||||
|
||||
// To simplify further processing, we translate the mesh so that the
|
||||
// last vertex of the pointing sphere (the pinpoint) will be at (0,0,0)
|
||||
for(auto& p : mesh.points) p.z() -= (h + r_small_mm - penetration_mm);
|
||||
}
|
||||
|
||||
Pillar::Pillar(const Vec3d &jp, const Vec3d &endp, double radius, size_t st):
|
||||
r(radius), steps(st), endpt(endp), starts_from_head(false)
|
||||
{
|
||||
assert(steps > 0);
|
||||
|
||||
height = jp(Z) - endp(Z);
|
||||
if(height > EPSILON) { // Endpoint is below the starting point
|
||||
|
||||
// We just create a bridge geometry with the pillar parameters and
|
||||
// move the data.
|
||||
Contour3D body = cylinder(radius, height, st, endp);
|
||||
mesh.points.swap(body.points);
|
||||
mesh.faces3.swap(body.faces3);
|
||||
}
|
||||
}
|
||||
|
||||
Pillar &Pillar::add_base(double baseheight, double radius)
|
||||
{
|
||||
if(baseheight <= 0) return *this;
|
||||
if(baseheight > height) baseheight = height;
|
||||
|
||||
assert(steps >= 0);
|
||||
auto last = int(steps - 1);
|
||||
|
||||
if(radius < r ) radius = r;
|
||||
|
||||
double a = 2*PI/steps;
|
||||
double z = endpt(Z) + baseheight;
|
||||
|
||||
for(size_t i = 0; i < steps; ++i) {
|
||||
double phi = i*a;
|
||||
double x = endpt(X) + r*std::cos(phi);
|
||||
double y = endpt(Y) + r*std::sin(phi);
|
||||
base.points.emplace_back(x, y, z);
|
||||
}
|
||||
|
||||
for(size_t i = 0; i < steps; ++i) {
|
||||
double phi = i*a;
|
||||
double x = endpt(X) + radius*std::cos(phi);
|
||||
double y = endpt(Y) + radius*std::sin(phi);
|
||||
base.points.emplace_back(x, y, z - baseheight);
|
||||
}
|
||||
|
||||
auto ep = endpt; ep(Z) += baseheight;
|
||||
base.points.emplace_back(endpt);
|
||||
base.points.emplace_back(ep);
|
||||
|
||||
auto& indices = base.faces3;
|
||||
auto hcenter = int(base.points.size() - 1);
|
||||
auto lcenter = int(base.points.size() - 2);
|
||||
auto offs = int(steps);
|
||||
for(int i = 0; i < last; ++i) {
|
||||
indices.emplace_back(i, i + offs, offs + i + 1);
|
||||
indices.emplace_back(i, offs + i + 1, i + 1);
|
||||
indices.emplace_back(i, i + 1, hcenter);
|
||||
indices.emplace_back(lcenter, offs + i + 1, offs + i);
|
||||
}
|
||||
|
||||
indices.emplace_back(0, last, offs);
|
||||
indices.emplace_back(last, offs + last, offs);
|
||||
indices.emplace_back(hcenter, last, 0);
|
||||
indices.emplace_back(offs, offs + last, lcenter);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Bridge::Bridge(const Vec3d &j1, const Vec3d &j2, double r_mm, size_t steps):
|
||||
r(r_mm), startp(j1), endp(j2)
|
||||
{
|
||||
using Quaternion = Eigen::Quaternion<double>;
|
||||
Vec3d dir = (j2 - j1).normalized();
|
||||
double d = distance(j2, j1);
|
||||
|
||||
mesh = cylinder(r, d, steps);
|
||||
|
||||
auto quater = Quaternion::FromTwoVectors(Vec3d{0,0,1}, dir);
|
||||
for(auto& p : mesh.points) p = quater * p + j1;
|
||||
}
|
||||
|
||||
CompactBridge::CompactBridge(const Vec3d &sp,
|
||||
const Vec3d &ep,
|
||||
const Vec3d &n,
|
||||
double r,
|
||||
bool endball,
|
||||
size_t steps)
|
||||
{
|
||||
Vec3d startp = sp + r * n;
|
||||
Vec3d dir = (ep - startp).normalized();
|
||||
Vec3d endp = ep - r * dir;
|
||||
|
||||
Bridge br(startp, endp, r, steps);
|
||||
mesh.merge(br.mesh);
|
||||
|
||||
// now add the pins
|
||||
double fa = 2*PI/steps;
|
||||
auto upperball = sphere(r, Portion{PI / 2 - fa, PI}, fa);
|
||||
for(auto& p : upperball.points) p += startp;
|
||||
|
||||
if(endball) {
|
||||
auto lowerball = sphere(r, Portion{0, PI/2 + 2*fa}, fa);
|
||||
for(auto& p : lowerball.points) p += endp;
|
||||
mesh.merge(lowerball);
|
||||
}
|
||||
|
||||
mesh.merge(upperball);
|
||||
}
|
||||
|
||||
Pad::Pad(const TriangleMesh &support_mesh,
|
||||
@ -368,7 +58,6 @@ SupportTreeBuilder::SupportTreeBuilder(SupportTreeBuilder &&o)
|
||||
, m_pillars{std::move(o.m_pillars)}
|
||||
, m_bridges{std::move(o.m_bridges)}
|
||||
, m_crossbridges{std::move(o.m_crossbridges)}
|
||||
, m_compact_bridges{std::move(o.m_compact_bridges)}
|
||||
, m_pad{std::move(o.m_pad)}
|
||||
, m_meshcache{std::move(o.m_meshcache)}
|
||||
, m_meshcache_valid{o.m_meshcache_valid}
|
||||
@ -382,7 +71,6 @@ SupportTreeBuilder::SupportTreeBuilder(const SupportTreeBuilder &o)
|
||||
, m_pillars{o.m_pillars}
|
||||
, m_bridges{o.m_bridges}
|
||||
, m_crossbridges{o.m_crossbridges}
|
||||
, m_compact_bridges{o.m_compact_bridges}
|
||||
, m_pad{o.m_pad}
|
||||
, m_meshcache{o.m_meshcache}
|
||||
, m_meshcache_valid{o.m_meshcache_valid}
|
||||
@ -397,7 +85,6 @@ SupportTreeBuilder &SupportTreeBuilder::operator=(SupportTreeBuilder &&o)
|
||||
m_pillars = std::move(o.m_pillars);
|
||||
m_bridges = std::move(o.m_bridges);
|
||||
m_crossbridges = std::move(o.m_crossbridges);
|
||||
m_compact_bridges = std::move(o.m_compact_bridges);
|
||||
m_pad = std::move(o.m_pad);
|
||||
m_meshcache = std::move(o.m_meshcache);
|
||||
m_meshcache_valid = o.m_meshcache_valid;
|
||||
@ -413,7 +100,6 @@ SupportTreeBuilder &SupportTreeBuilder::operator=(const SupportTreeBuilder &o)
|
||||
m_pillars = o.m_pillars;
|
||||
m_bridges = o.m_bridges;
|
||||
m_crossbridges = o.m_crossbridges;
|
||||
m_compact_bridges = o.m_compact_bridges;
|
||||
m_pad = o.m_pad;
|
||||
m_meshcache = o.m_meshcache;
|
||||
m_meshcache_valid = o.m_meshcache_valid;
|
||||
@ -422,7 +108,19 @@ SupportTreeBuilder &SupportTreeBuilder::operator=(const SupportTreeBuilder &o)
|
||||
return *this;
|
||||
}
|
||||
|
||||
const TriangleMesh &SupportTreeBuilder::merged_mesh() const
|
||||
void SupportTreeBuilder::add_pillar_base(long pid, double baseheight, double radius)
|
||||
{
|
||||
std::lock_guard<Mutex> lk(m_mutex);
|
||||
assert(pid >= 0 && size_t(pid) < m_pillars.size());
|
||||
Pillar& pll = m_pillars[size_t(pid)];
|
||||
m_pedestals.emplace_back(pll.endpt, std::min(baseheight, pll.height),
|
||||
std::max(radius, pll.r), pll.r);
|
||||
|
||||
m_pedestals.back().id = m_pedestals.size() - 1;
|
||||
m_meshcache_valid = false;
|
||||
}
|
||||
|
||||
const TriangleMesh &SupportTreeBuilder::merged_mesh(size_t steps) const
|
||||
{
|
||||
if (m_meshcache_valid) return m_meshcache;
|
||||
|
||||
@ -430,35 +128,44 @@ const TriangleMesh &SupportTreeBuilder::merged_mesh() const
|
||||
|
||||
for (auto &head : m_heads) {
|
||||
if (ctl().stopcondition()) break;
|
||||
if (head.is_valid()) merged.merge(head.mesh);
|
||||
if (head.is_valid()) merged.merge(get_mesh(head, steps));
|
||||
}
|
||||
|
||||
for (auto &stick : m_pillars) {
|
||||
for (auto &pill : m_pillars) {
|
||||
if (ctl().stopcondition()) break;
|
||||
merged.merge(stick.mesh);
|
||||
merged.merge(stick.base);
|
||||
merged.merge(get_mesh(pill, steps));
|
||||
}
|
||||
|
||||
for (auto &pedest : m_pedestals) {
|
||||
if (ctl().stopcondition()) break;
|
||||
merged.merge(get_mesh(pedest, steps));
|
||||
}
|
||||
|
||||
for (auto &j : m_junctions) {
|
||||
if (ctl().stopcondition()) break;
|
||||
merged.merge(j.mesh);
|
||||
merged.merge(get_mesh(j, steps));
|
||||
}
|
||||
|
||||
for (auto &cb : m_compact_bridges) {
|
||||
if (ctl().stopcondition()) break;
|
||||
merged.merge(cb.mesh);
|
||||
}
|
||||
|
||||
|
||||
for (auto &bs : m_bridges) {
|
||||
if (ctl().stopcondition()) break;
|
||||
merged.merge(bs.mesh);
|
||||
merged.merge(get_mesh(bs, steps));
|
||||
}
|
||||
|
||||
for (auto &bs : m_crossbridges) {
|
||||
if (ctl().stopcondition()) break;
|
||||
merged.merge(bs.mesh);
|
||||
merged.merge(get_mesh(bs, steps));
|
||||
}
|
||||
|
||||
|
||||
for (auto &bs : m_diffbridges) {
|
||||
if (ctl().stopcondition()) break;
|
||||
merged.merge(get_mesh(bs, steps));
|
||||
}
|
||||
|
||||
for (auto &anch : m_anchors) {
|
||||
if (ctl().stopcondition()) break;
|
||||
merged.merge(get_mesh(anch, steps));
|
||||
}
|
||||
|
||||
if (ctl().stopcondition()) {
|
||||
// In case of failure we have to return an empty mesh
|
||||
m_meshcache = TriangleMesh();
|
||||
@ -499,7 +206,6 @@ const TriangleMesh &SupportTreeBuilder::merge_and_cleanup()
|
||||
m_pillars = {};
|
||||
m_junctions = {};
|
||||
m_bridges = {};
|
||||
m_compact_bridges = {};
|
||||
|
||||
return ret;
|
||||
}
|
||||
@ -514,11 +220,4 @@ const TriangleMesh &SupportTreeBuilder::retrieve_mesh(MeshType meshtype) const
|
||||
return m_meshcache;
|
||||
}
|
||||
|
||||
bool SupportTreeBuilder::build(const SupportableMesh &sm)
|
||||
{
|
||||
ground_level = sm.emesh.ground_level() - sm.cfg.object_elevation_mm;
|
||||
return SupportTreeBuildsteps::execute(*this, sm);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}} // namespace Slic3r::sla
|
||||
|
@ -2,7 +2,6 @@
|
||||
#define SLA_SUPPORTTREEBUILDER_HPP
|
||||
|
||||
#include <libslic3r/SLA/Concurrency.hpp>
|
||||
#include <libslic3r/SLA/Common.hpp>
|
||||
#include <libslic3r/SLA/SupportTree.hpp>
|
||||
#include <libslic3r/SLA/Contour3D.hpp>
|
||||
#include <libslic3r/SLA/Pad.hpp>
|
||||
@ -50,13 +49,6 @@ namespace sla {
|
||||
* nearby pillar.
|
||||
*/
|
||||
|
||||
using Coordf = double;
|
||||
using Portion = std::tuple<double, double>;
|
||||
|
||||
inline Portion make_portion(double a, double b) {
|
||||
return std::make_tuple(a, b);
|
||||
}
|
||||
|
||||
template<class Vec> double distance(const Vec& p) {
|
||||
return std::sqrt(p.transpose() * p);
|
||||
}
|
||||
@ -66,33 +58,25 @@ template<class Vec> double distance(const Vec& pp1, const Vec& pp2) {
|
||||
return distance(p);
|
||||
}
|
||||
|
||||
Contour3D sphere(double rho, Portion portion = make_portion(0.0, 2.0*PI),
|
||||
double fa=(2*PI/360));
|
||||
const Vec3d DOWN = {0.0, 0.0, -1.0};
|
||||
|
||||
// Down facing cylinder in Z direction with arguments:
|
||||
// r: radius
|
||||
// h: Height
|
||||
// ssteps: how many edges will create the base circle
|
||||
// sp: starting point
|
||||
Contour3D cylinder(double r, double h, size_t ssteps = 45, const Vec3d &sp = {0,0,0});
|
||||
struct SupportTreeNode
|
||||
{
|
||||
static const constexpr long ID_UNSET = -1;
|
||||
|
||||
const constexpr long ID_UNSET = -1;
|
||||
long id = ID_UNSET; // For identification withing a tree.
|
||||
};
|
||||
|
||||
struct Head {
|
||||
Contour3D mesh;
|
||||
|
||||
size_t steps = 45;
|
||||
Vec3d dir = {0, 0, -1};
|
||||
Vec3d tr = {0, 0, 0};
|
||||
// A pinhead originating from a support point
|
||||
struct Head: public SupportTreeNode {
|
||||
Vec3d dir = DOWN;
|
||||
Vec3d pos = {0, 0, 0};
|
||||
|
||||
double r_back_mm = 1;
|
||||
double r_pin_mm = 0.5;
|
||||
double width_mm = 2;
|
||||
double penetration_mm = 0.5;
|
||||
|
||||
// For identification purposes. This will be used as the index into the
|
||||
// container holding the head structures. See SLASupportTree::Impl
|
||||
long id = ID_UNSET;
|
||||
|
||||
|
||||
// If there is a pillar connecting to this head, then the id will be set.
|
||||
long pillar_id = ID_UNSET;
|
||||
@ -106,31 +90,23 @@ struct Head {
|
||||
double r_small_mm,
|
||||
double length_mm,
|
||||
double penetration,
|
||||
const Vec3d &direction = {0, 0, -1}, // direction (normal to the dull end)
|
||||
const Vec3d &offset = {0, 0, 0}, // displacement
|
||||
const size_t circlesteps = 45);
|
||||
|
||||
void transform()
|
||||
const Vec3d &direction = DOWN, // direction (normal to the dull end)
|
||||
const Vec3d &offset = {0, 0, 0} // displacement
|
||||
);
|
||||
|
||||
inline double real_width() const
|
||||
{
|
||||
using Quaternion = Eigen::Quaternion<double>;
|
||||
|
||||
// We rotate the head to the specified direction The head's pointing
|
||||
// side is facing upwards so this means that it would hold a support
|
||||
// point with a normal pointing straight down. This is the reason of
|
||||
// the -1 z coordinate
|
||||
auto quatern = Quaternion::FromTwoVectors(Vec3d{0, 0, -1}, dir);
|
||||
|
||||
for(auto& p : mesh.points) p = quatern * p + tr;
|
||||
return 2 * r_pin_mm + width_mm + 2 * r_back_mm ;
|
||||
}
|
||||
|
||||
|
||||
inline double fullwidth() const
|
||||
{
|
||||
return 2 * r_pin_mm + width_mm + 2*r_back_mm - penetration_mm;
|
||||
return real_width() - penetration_mm;
|
||||
}
|
||||
|
||||
inline Vec3d junction_point() const
|
||||
{
|
||||
return tr + ( 2 * r_pin_mm + width_mm + r_back_mm - penetration_mm)*dir;
|
||||
return pos + (fullwidth() - r_back_mm) * dir;
|
||||
}
|
||||
|
||||
inline double request_pillar_radius(double radius) const
|
||||
@ -140,31 +116,17 @@ struct Head {
|
||||
}
|
||||
};
|
||||
|
||||
struct Junction {
|
||||
Contour3D mesh;
|
||||
// A junction connecting bridges and pillars
|
||||
struct Junction: public SupportTreeNode {
|
||||
double r = 1;
|
||||
size_t steps = 45;
|
||||
Vec3d pos;
|
||||
|
||||
long id = ID_UNSET;
|
||||
|
||||
Junction(const Vec3d& tr, double r_mm, size_t stepnum = 45):
|
||||
r(r_mm), steps(stepnum), pos(tr)
|
||||
{
|
||||
mesh = sphere(r_mm, make_portion(0, PI), 2*PI/steps);
|
||||
for(auto& p : mesh.points) p += tr;
|
||||
}
|
||||
|
||||
Junction(const Vec3d &tr, double r_mm) : r(r_mm), pos(tr) {}
|
||||
};
|
||||
|
||||
struct Pillar {
|
||||
Contour3D mesh;
|
||||
Contour3D base;
|
||||
double r = 1;
|
||||
size_t steps = 0;
|
||||
struct Pillar: public SupportTreeNode {
|
||||
double height, r;
|
||||
Vec3d endpt;
|
||||
double height = 0;
|
||||
|
||||
long id = ID_UNSET;
|
||||
|
||||
// If the pillar connects to a head, this is the id of that head
|
||||
bool starts_from_head = true; // Could start from a junction as well
|
||||
@ -175,54 +137,52 @@ struct Pillar {
|
||||
|
||||
// How many pillars are cascaded with this one
|
||||
unsigned links = 0;
|
||||
|
||||
Pillar(const Vec3d& jp, const Vec3d& endp,
|
||||
double radius = 1, size_t st = 45);
|
||||
|
||||
Pillar(const Junction &junc, const Vec3d &endp)
|
||||
: Pillar(junc.pos, endp, junc.r, junc.steps)
|
||||
{}
|
||||
|
||||
Pillar(const Head &head, const Vec3d &endp, double radius = 1)
|
||||
: Pillar(head.junction_point(), endp,
|
||||
head.request_pillar_radius(radius), head.steps)
|
||||
{}
|
||||
|
||||
inline Vec3d startpoint() const
|
||||
|
||||
Pillar(const Vec3d &endp, double h, double radius = 1.):
|
||||
height{h}, r(radius), endpt(endp), starts_from_head(false) {}
|
||||
|
||||
Vec3d startpoint() const
|
||||
{
|
||||
return {endpt(X), endpt(Y), endpt(Z) + height};
|
||||
return {endpt.x(), endpt.y(), endpt.z() + height};
|
||||
}
|
||||
|
||||
inline const Vec3d& endpoint() const { return endpt; }
|
||||
|
||||
Pillar& add_base(double baseheight = 3, double radius = 2);
|
||||
const Vec3d& endpoint() const { return endpt; }
|
||||
};
|
||||
|
||||
// A base for pillars or bridges that end on the ground
|
||||
struct Pedestal: public SupportTreeNode {
|
||||
Vec3d pos;
|
||||
double height, r_bottom, r_top;
|
||||
|
||||
Pedestal(const Vec3d &p, double h, double rbottom, double rtop)
|
||||
: pos{p}, height{h}, r_bottom{rbottom}, r_top{rtop}
|
||||
{}
|
||||
};
|
||||
|
||||
// This is the thing that anchors a pillar or bridge to the model body.
|
||||
// It is actually a reverse pinhead.
|
||||
struct Anchor: public Head { using Head::Head; };
|
||||
|
||||
// A Bridge between two pillars (with junction endpoints)
|
||||
struct Bridge {
|
||||
Contour3D mesh;
|
||||
struct Bridge: public SupportTreeNode {
|
||||
double r = 0.8;
|
||||
long id = ID_UNSET;
|
||||
Vec3d startp = Vec3d::Zero(), endp = Vec3d::Zero();
|
||||
|
||||
Bridge(const Vec3d &j1,
|
||||
const Vec3d &j2,
|
||||
double r_mm = 0.8,
|
||||
size_t steps = 45);
|
||||
double r_mm = 0.8): r{r_mm}, startp{j1}, endp{j2}
|
||||
{}
|
||||
|
||||
double get_length() const { return (endp - startp).norm(); }
|
||||
Vec3d get_dir() const { return (endp - startp).normalized(); }
|
||||
};
|
||||
|
||||
// A bridge that spans from model surface to model surface with small connecting
|
||||
// edges on the endpoints. Used for headless support points.
|
||||
struct CompactBridge {
|
||||
Contour3D mesh;
|
||||
long id = ID_UNSET;
|
||||
|
||||
CompactBridge(const Vec3d& sp,
|
||||
const Vec3d& ep,
|
||||
const Vec3d& n,
|
||||
double r,
|
||||
bool endball = true,
|
||||
size_t steps = 45);
|
||||
struct DiffBridge: public Bridge {
|
||||
double end_r;
|
||||
|
||||
DiffBridge(const Vec3d &p_s, const Vec3d &p_e, double r_s, double r_e)
|
||||
: Bridge{p_s, p_e, r_s}, end_r{r_e}
|
||||
{}
|
||||
};
|
||||
|
||||
// A wrapper struct around the pad
|
||||
@ -258,13 +218,16 @@ struct Pad {
|
||||
// merged mesh. It can be retrieved using a dedicated method (pad())
|
||||
class SupportTreeBuilder: public SupportTree {
|
||||
// For heads it is beneficial to use the same IDs as for the support points.
|
||||
std::vector<Head> m_heads;
|
||||
std::vector<size_t> m_head_indices;
|
||||
std::vector<Pillar> m_pillars;
|
||||
std::vector<Junction> m_junctions;
|
||||
std::vector<Bridge> m_bridges;
|
||||
std::vector<Bridge> m_crossbridges;
|
||||
std::vector<CompactBridge> m_compact_bridges;
|
||||
std::vector<Head> m_heads;
|
||||
std::vector<size_t> m_head_indices;
|
||||
std::vector<Pillar> m_pillars;
|
||||
std::vector<Junction> m_junctions;
|
||||
std::vector<Bridge> m_bridges;
|
||||
std::vector<Bridge> m_crossbridges;
|
||||
std::vector<DiffBridge> m_diffbridges;
|
||||
std::vector<Pedestal> m_pedestals;
|
||||
std::vector<Anchor> m_anchors;
|
||||
|
||||
Pad m_pad;
|
||||
|
||||
using Mutex = ccr::SpinningMutex;
|
||||
@ -274,8 +237,8 @@ class SupportTreeBuilder: public SupportTree {
|
||||
mutable bool m_meshcache_valid = false;
|
||||
mutable double m_model_height = 0; // the full height of the model
|
||||
|
||||
template<class...Args>
|
||||
const Bridge& _add_bridge(std::vector<Bridge> &br, Args&&... args)
|
||||
template<class BridgeT, class...Args>
|
||||
const BridgeT& _add_bridge(std::vector<BridgeT> &br, Args&&... args)
|
||||
{
|
||||
std::lock_guard<Mutex> lk(m_mutex);
|
||||
br.emplace_back(std::forward<Args>(args)...);
|
||||
@ -306,7 +269,7 @@ public:
|
||||
return m_heads.back();
|
||||
}
|
||||
|
||||
template<class...Args> long add_pillar(long headid, Args&&... args)
|
||||
template<class...Args> long add_pillar(long headid, double length)
|
||||
{
|
||||
std::lock_guard<Mutex> lk(m_mutex);
|
||||
if (m_pillars.capacity() < m_heads.size())
|
||||
@ -315,7 +278,9 @@ public:
|
||||
assert(headid >= 0 && size_t(headid) < m_head_indices.size());
|
||||
Head &head = m_heads[m_head_indices[size_t(headid)]];
|
||||
|
||||
m_pillars.emplace_back(head, std::forward<Args>(args)...);
|
||||
Vec3d hjp = head.junction_point() - Vec3d{0, 0, length};
|
||||
m_pillars.emplace_back(hjp, length, head.r_back_mm);
|
||||
|
||||
Pillar& pillar = m_pillars.back();
|
||||
pillar.id = long(m_pillars.size() - 1);
|
||||
head.pillar_id = pillar.id;
|
||||
@ -326,11 +291,15 @@ public:
|
||||
return pillar.id;
|
||||
}
|
||||
|
||||
void add_pillar_base(long pid, double baseheight = 3, double radius = 2)
|
||||
void add_pillar_base(long pid, double baseheight = 3, double radius = 2);
|
||||
|
||||
template<class...Args> const Anchor& add_anchor(Args&&...args)
|
||||
{
|
||||
std::lock_guard<Mutex> lk(m_mutex);
|
||||
assert(pid >= 0 && size_t(pid) < m_pillars.size());
|
||||
m_pillars[size_t(pid)].add_base(baseheight, radius);
|
||||
m_anchors.emplace_back(std::forward<Args>(args)...);
|
||||
m_anchors.back().id = long(m_junctions.size() - 1);
|
||||
m_meshcache_valid = false;
|
||||
return m_anchors.back();
|
||||
}
|
||||
|
||||
void increment_bridges(const Pillar& pillar)
|
||||
@ -371,17 +340,6 @@ public:
|
||||
return pillar.id;
|
||||
}
|
||||
|
||||
const Pillar& head_pillar(unsigned headid) const
|
||||
{
|
||||
std::lock_guard<Mutex> lk(m_mutex);
|
||||
assert(headid < m_head_indices.size());
|
||||
|
||||
const Head& h = m_heads[m_head_indices[headid]];
|
||||
assert(h.pillar_id >= 0 && h.pillar_id < long(m_pillars.size()));
|
||||
|
||||
return m_pillars[size_t(h.pillar_id)];
|
||||
}
|
||||
|
||||
template<class...Args> const Junction& add_junction(Args&&... args)
|
||||
{
|
||||
std::lock_guard<Mutex> lk(m_mutex);
|
||||
@ -391,18 +349,18 @@ public:
|
||||
return m_junctions.back();
|
||||
}
|
||||
|
||||
const Bridge& add_bridge(const Vec3d &s, const Vec3d &e, double r, size_t n = 45)
|
||||
const Bridge& add_bridge(const Vec3d &s, const Vec3d &e, double r)
|
||||
{
|
||||
return _add_bridge(m_bridges, s, e, r, n);
|
||||
return _add_bridge(m_bridges, s, e, r);
|
||||
}
|
||||
|
||||
const Bridge& add_bridge(long headid, const Vec3d &endp, size_t s = 45)
|
||||
const Bridge& add_bridge(long headid, const Vec3d &endp)
|
||||
{
|
||||
std::lock_guard<Mutex> lk(m_mutex);
|
||||
assert(headid >= 0 && size_t(headid) < m_head_indices.size());
|
||||
|
||||
Head &h = m_heads[m_head_indices[size_t(headid)]];
|
||||
m_bridges.emplace_back(h.junction_point(), endp, h.r_back_mm, s);
|
||||
m_bridges.emplace_back(h.junction_point(), endp, h.r_back_mm);
|
||||
m_bridges.back().id = long(m_bridges.size() - 1);
|
||||
|
||||
h.bridge_id = m_bridges.back().id;
|
||||
@ -414,14 +372,10 @@ public:
|
||||
{
|
||||
return _add_bridge(m_crossbridges, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template<class...Args> const CompactBridge& add_compact_bridge(Args&&...args)
|
||||
|
||||
template<class...Args> const DiffBridge& add_diffbridge(Args&&... args)
|
||||
{
|
||||
std::lock_guard<Mutex> lk(m_mutex);
|
||||
m_compact_bridges.emplace_back(std::forward<Args>(args)...);
|
||||
m_compact_bridges.back().id = long(m_compact_bridges.size() - 1);
|
||||
m_meshcache_valid = false;
|
||||
return m_compact_bridges.back();
|
||||
return _add_bridge(m_diffbridges, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
Head &head(unsigned id)
|
||||
@ -439,7 +393,7 @@ public:
|
||||
}
|
||||
|
||||
inline const std::vector<Pillar> &pillars() const { return m_pillars; }
|
||||
inline const std::vector<Head> &heads() const { return m_heads; }
|
||||
inline const std::vector<Head> &heads() const { return m_heads; }
|
||||
inline const std::vector<Bridge> &bridges() const { return m_bridges; }
|
||||
inline const std::vector<Bridge> &crossbridges() const { return m_crossbridges; }
|
||||
|
||||
@ -464,7 +418,7 @@ public:
|
||||
const Pad& pad() const { return m_pad; }
|
||||
|
||||
// WITHOUT THE PAD!!!
|
||||
const TriangleMesh &merged_mesh() const;
|
||||
const TriangleMesh &merged_mesh(size_t steps = 45) const;
|
||||
|
||||
// WITH THE PAD
|
||||
double full_height() const;
|
||||
@ -488,8 +442,6 @@ public:
|
||||
|
||||
virtual const TriangleMesh &retrieve_mesh(
|
||||
MeshType meshtype = MeshType::Support) const override;
|
||||
|
||||
bool build(const SupportableMesh &supportable_mesh);
|
||||
};
|
||||
|
||||
}} // namespace Slic3r::sla
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -5,6 +5,7 @@
|
||||
|
||||
#include <libslic3r/SLA/SupportTreeBuilder.hpp>
|
||||
#include <libslic3r/SLA/Clustering.hpp>
|
||||
#include <libslic3r/SLA/SpatIndex.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace sla {
|
||||
@ -16,9 +17,7 @@ enum { // For indexing Eigen vectors as v(X), v(Y), v(Z) instead of numbers
|
||||
X, Y, Z
|
||||
};
|
||||
|
||||
inline Vec2d to_vec2(const Vec3d& v3) {
|
||||
return {v3(X), v3(Y)};
|
||||
}
|
||||
inline Vec2d to_vec2(const Vec3d &v3) { return {v3(X), v3(Y)}; }
|
||||
|
||||
inline std::pair<double, double> dir_to_spheric(const Vec3d &n, double norm = 1.)
|
||||
{
|
||||
@ -46,55 +45,71 @@ inline Vec3d spheric_to_dir(const std::pair<double, double> &v)
|
||||
return spheric_to_dir(v.first, v.second);
|
||||
}
|
||||
|
||||
// This function returns the position of the centroid in the input 'clust'
|
||||
// vector of point indices.
|
||||
template<class DistFn>
|
||||
long cluster_centroid(const ClusterEl& clust,
|
||||
const std::function<Vec3d(size_t)> &pointfn,
|
||||
DistFn df)
|
||||
inline Vec3d spheric_to_dir(const std::array<double, 2> &v)
|
||||
{
|
||||
switch(clust.size()) {
|
||||
case 0: /* empty cluster */ return ID_UNSET;
|
||||
case 1: /* only one element */ return 0;
|
||||
case 2: /* if two elements, there is no center */ return 0;
|
||||
default: ;
|
||||
return spheric_to_dir(v[0], v[1]);
|
||||
}
|
||||
|
||||
// Give points on a 3D ring with given center, radius and orientation
|
||||
// method based on:
|
||||
// https://math.stackexchange.com/questions/73237/parametric-equation-of-a-circle-in-3d-space
|
||||
template<size_t N>
|
||||
class PointRing {
|
||||
std::array<double, N> m_phis;
|
||||
|
||||
// Two vectors that will be perpendicular to each other and to the
|
||||
// axis. Values for a(X) and a(Y) are now arbitrary, a(Z) is just a
|
||||
// placeholder.
|
||||
// a and b vectors are perpendicular to the ring direction and to each other.
|
||||
// Together they define the plane where we have to iterate with the
|
||||
// given angles in the 'm_phis' vector
|
||||
Vec3d a = {0, 1, 0}, b;
|
||||
double m_radius = 0.;
|
||||
|
||||
static inline bool constexpr is_one(double val)
|
||||
{
|
||||
return std::abs(std::abs(val) - 1) < 1e-20;
|
||||
}
|
||||
|
||||
// The function works by calculating for each point the average distance
|
||||
// from all the other points in the cluster. We create a selector bitmask of
|
||||
// the same size as the cluster. The bitmask will have two true bits and
|
||||
// false bits for the rest of items and we will loop through all the
|
||||
// permutations of the bitmask (combinations of two points). Get the
|
||||
// distance for the two points and add the distance to the averages.
|
||||
// The point with the smallest average than wins.
|
||||
public:
|
||||
|
||||
// The complexity should be O(n^2) but we will mostly apply this function
|
||||
// for small clusters only (cca 3 elements)
|
||||
PointRing(const Vec3d &n)
|
||||
{
|
||||
m_phis = linspace_array<N>(0., 2 * PI);
|
||||
|
||||
std::vector<bool> sel(clust.size(), false); // create full zero bitmask
|
||||
std::fill(sel.end() - 2, sel.end(), true); // insert the two ones
|
||||
std::vector<double> avgs(clust.size(), 0.0); // store the average distances
|
||||
// We have to address the case when the direction vector v (same as
|
||||
// dir) is coincident with one of the world axes. In this case two of
|
||||
// its components will be completely zero and one is 1.0. Our method
|
||||
// becomes dangerous here due to division with zero. Instead, vector
|
||||
// 'a' can be an element-wise rotated version of 'v'
|
||||
if(is_one(n(X)) || is_one(n(Y)) || is_one(n(Z))) {
|
||||
a = {n(Z), n(X), n(Y)};
|
||||
b = {n(Y), n(Z), n(X)};
|
||||
}
|
||||
else {
|
||||
a(Z) = -(n(Y)*a(Y)) / n(Z); a.normalize();
|
||||
b = a.cross(n);
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
std::array<size_t, 2> idx;
|
||||
for(size_t i = 0, j = 0; i < clust.size(); i++) if(sel[i]) idx[j++] = i;
|
||||
Vec3d get(size_t idx, const Vec3d src, double r) const
|
||||
{
|
||||
double phi = m_phis[idx];
|
||||
double sinphi = std::sin(phi);
|
||||
double cosphi = std::cos(phi);
|
||||
|
||||
double d = df(pointfn(clust[idx[0]]),
|
||||
pointfn(clust[idx[1]]));
|
||||
double rpscos = r * cosphi;
|
||||
double rpssin = r * sinphi;
|
||||
|
||||
// add the distance to the sums for both associated points
|
||||
for(auto i : idx) avgs[i] += d;
|
||||
// Point on the sphere
|
||||
return {src(X) + rpscos * a(X) + rpssin * b(X),
|
||||
src(Y) + rpscos * a(Y) + rpssin * b(Y),
|
||||
src(Z) + rpscos * a(Z) + rpssin * b(Z)};
|
||||
}
|
||||
};
|
||||
|
||||
// now continue with the next permutation of the bitmask with two 1s
|
||||
} while(std::next_permutation(sel.begin(), sel.end()));
|
||||
|
||||
// Divide by point size in the cluster to get the average (may be redundant)
|
||||
for(auto& a : avgs) a /= clust.size();
|
||||
|
||||
// get the lowest average distance and return the index
|
||||
auto minit = std::min_element(avgs.begin(), avgs.end());
|
||||
return long(minit - avgs.begin());
|
||||
}
|
||||
//IndexedMesh::hit_result query_hit(const SupportableMesh &msh, const Bridge &br, double safety_d = std::nan(""));
|
||||
//IndexedMesh::hit_result query_hit(const SupportableMesh &msh, const Head &br, double safety_d = std::nan(""));
|
||||
|
||||
inline Vec3d dirv(const Vec3d& startp, const Vec3d& endp) {
|
||||
return (endp - startp).normalized();
|
||||
@ -170,8 +185,8 @@ IntegerOnly<DoubleI> pairhash(I a, I b)
|
||||
}
|
||||
|
||||
class SupportTreeBuildsteps {
|
||||
const SupportConfig& m_cfg;
|
||||
const EigenMesh3D& m_mesh;
|
||||
const SupportTreeConfig& m_cfg;
|
||||
const IndexedMesh& m_mesh;
|
||||
const std::vector<SupportPoint>& m_support_pts;
|
||||
|
||||
using PtIndices = std::vector<unsigned>;
|
||||
@ -180,7 +195,7 @@ class SupportTreeBuildsteps {
|
||||
PtIndices m_iheads_onmodel;
|
||||
PtIndices m_iheadless; // headless support points
|
||||
|
||||
std::map<unsigned, EigenMesh3D::hit_result> m_head_to_ground_scans;
|
||||
std::map<unsigned, IndexedMesh::hit_result> m_head_to_ground_scans;
|
||||
|
||||
// normals for support points from model faces.
|
||||
PointSet m_support_nmls;
|
||||
@ -206,7 +221,7 @@ class SupportTreeBuildsteps {
|
||||
// When bridging heads to pillars... TODO: find a cleaner solution
|
||||
ccr::BlockingMutex m_bridge_mutex;
|
||||
|
||||
inline EigenMesh3D::hit_result ray_mesh_intersect(const Vec3d& s,
|
||||
inline IndexedMesh::hit_result ray_mesh_intersect(const Vec3d& s,
|
||||
const Vec3d& dir)
|
||||
{
|
||||
return m_mesh.query_ray_hit(s, dir);
|
||||
@ -223,16 +238,24 @@ class SupportTreeBuildsteps {
|
||||
// point was inside the model, an "invalid" hit_result will be returned
|
||||
// with a zero distance value instead of a NAN. This way the result can
|
||||
// be used safely for comparison with other distances.
|
||||
EigenMesh3D::hit_result pinhead_mesh_intersect(
|
||||
IndexedMesh::hit_result pinhead_mesh_intersect(
|
||||
const Vec3d& s,
|
||||
const Vec3d& dir,
|
||||
double r_pin,
|
||||
double r_back,
|
||||
double width);
|
||||
|
||||
template<class...Args>
|
||||
inline double pinhead_mesh_distance(Args&&...args) {
|
||||
return pinhead_mesh_intersect(std::forward<Args>(args)...).distance();
|
||||
double width,
|
||||
double safety_d);
|
||||
|
||||
IndexedMesh::hit_result pinhead_mesh_intersect(
|
||||
const Vec3d& s,
|
||||
const Vec3d& dir,
|
||||
double r_pin,
|
||||
double r_back,
|
||||
double width)
|
||||
{
|
||||
return pinhead_mesh_intersect(s, dir, r_pin, r_back, width,
|
||||
r_back * m_cfg.safety_distance_mm /
|
||||
m_cfg.head_back_radius_mm);
|
||||
}
|
||||
|
||||
// Checking bridge (pillar and stick as well) intersection with the model.
|
||||
@ -243,11 +266,21 @@ class SupportTreeBuildsteps {
|
||||
// point was inside the model, an "invalid" hit_result will be returned
|
||||
// with a zero distance value instead of a NAN. This way the result can
|
||||
// be used safely for comparison with other distances.
|
||||
EigenMesh3D::hit_result bridge_mesh_intersect(
|
||||
IndexedMesh::hit_result bridge_mesh_intersect(
|
||||
const Vec3d& s,
|
||||
const Vec3d& dir,
|
||||
double r,
|
||||
bool ins_check = false);
|
||||
double safety_d);
|
||||
|
||||
IndexedMesh::hit_result bridge_mesh_intersect(
|
||||
const Vec3d& s,
|
||||
const Vec3d& dir,
|
||||
double r)
|
||||
{
|
||||
return bridge_mesh_intersect(s, dir, r,
|
||||
r * m_cfg.safety_distance_mm /
|
||||
m_cfg.head_back_radius_mm);
|
||||
}
|
||||
|
||||
template<class...Args>
|
||||
inline double bridge_mesh_distance(Args&&...args) {
|
||||
@ -268,20 +301,29 @@ class SupportTreeBuildsteps {
|
||||
inline bool connect_to_ground(Head& head);
|
||||
|
||||
bool connect_to_model_body(Head &head);
|
||||
|
||||
bool search_pillar_and_connect(const Head& head);
|
||||
|
||||
bool search_pillar_and_connect(const Head& source);
|
||||
|
||||
// This is a proxy function for pillar creation which will mind the gap
|
||||
// between the pad and the model bottom in zero elevation mode.
|
||||
// jp is the starting junction point which needs to be routed down.
|
||||
// sourcedir is the allowed direction of an optional bridge between the
|
||||
// jp junction and the final pillar.
|
||||
void create_ground_pillar(const Vec3d &jp,
|
||||
bool create_ground_pillar(const Vec3d &jp,
|
||||
const Vec3d &sourcedir,
|
||||
double radius,
|
||||
long head_id = ID_UNSET);
|
||||
|
||||
|
||||
long head_id = SupportTreeNode::ID_UNSET);
|
||||
|
||||
void add_pillar_base(long pid)
|
||||
{
|
||||
m_builder.add_pillar_base(pid, m_cfg.base_height_mm, m_cfg.base_radius_mm);
|
||||
}
|
||||
|
||||
std::optional<DiffBridge> search_widening_path(const Vec3d &jp,
|
||||
const Vec3d &dir,
|
||||
double radius,
|
||||
double new_radius);
|
||||
|
||||
public:
|
||||
SupportTreeBuildsteps(SupportTreeBuilder & builder, const SupportableMesh &sm);
|
||||
|
||||
@ -324,11 +366,6 @@ public:
|
||||
|
||||
void interconnect_pillars();
|
||||
|
||||
// Step: process the support points where there is not enough space for a
|
||||
// full pinhead. In this case we will use a rounded sphere as a touching
|
||||
// point and use a thinner bridge (let's call it a stick).
|
||||
void routing_headless ();
|
||||
|
||||
inline void merge_result() { m_builder.merged_mesh(); }
|
||||
|
||||
static bool execute(SupportTreeBuilder & builder, const SupportableMesh &sm);
|
||||
|
266
src/libslic3r/SLA/SupportTreeMesher.cpp
Normal file
266
src/libslic3r/SLA/SupportTreeMesher.cpp
Normal file
@ -0,0 +1,266 @@
|
||||
#include "SupportTreeMesher.hpp"
|
||||
|
||||
namespace Slic3r { namespace sla {
|
||||
|
||||
Contour3D sphere(double rho, Portion portion, double fa) {
|
||||
|
||||
Contour3D ret;
|
||||
|
||||
// prohibit close to zero radius
|
||||
if(rho <= 1e-6 && rho >= -1e-6) return ret;
|
||||
|
||||
auto& vertices = ret.points;
|
||||
auto& facets = ret.faces3;
|
||||
|
||||
// Algorithm:
|
||||
// Add points one-by-one to the sphere grid and form facets using relative
|
||||
// coordinates. Sphere is composed effectively of a mesh of stacked circles.
|
||||
|
||||
// adjust via rounding to get an even multiple for any provided angle.
|
||||
double angle = (2*PI / floor(2*PI / fa));
|
||||
|
||||
// Ring to be scaled to generate the steps of the sphere
|
||||
std::vector<double> ring;
|
||||
|
||||
for (double i = 0; i < 2*PI; i+=angle) ring.emplace_back(i);
|
||||
|
||||
const auto sbegin = size_t(2*std::get<0>(portion)/angle);
|
||||
const auto send = size_t(2*std::get<1>(portion)/angle);
|
||||
|
||||
const size_t steps = ring.size();
|
||||
const double increment = 1.0 / double(steps);
|
||||
|
||||
// special case: first ring connects to 0,0,0
|
||||
// insert and form facets.
|
||||
if(sbegin == 0)
|
||||
vertices.emplace_back(Vec3d(0.0, 0.0, -rho + increment*sbegin*2.0*rho));
|
||||
|
||||
auto id = coord_t(vertices.size());
|
||||
for (size_t i = 0; i < ring.size(); i++) {
|
||||
// Fixed scaling
|
||||
const double z = -rho + increment*rho*2.0 * (sbegin + 1.0);
|
||||
// radius of the circle for this step.
|
||||
const double r = std::sqrt(std::abs(rho*rho - z*z));
|
||||
Vec2d b = Eigen::Rotation2Dd(ring[i]) * Eigen::Vector2d(0, r);
|
||||
vertices.emplace_back(Vec3d(b(0), b(1), z));
|
||||
|
||||
if (sbegin == 0)
|
||||
(i == 0) ? facets.emplace_back(coord_t(ring.size()), 0, 1) :
|
||||
facets.emplace_back(id - 1, 0, id);
|
||||
++id;
|
||||
}
|
||||
|
||||
// General case: insert and form facets for each step,
|
||||
// joining it to the ring below it.
|
||||
for (size_t s = sbegin + 2; s < send - 1; s++) {
|
||||
const double z = -rho + increment*double(s*2.0*rho);
|
||||
const double r = std::sqrt(std::abs(rho*rho - z*z));
|
||||
|
||||
for (size_t i = 0; i < ring.size(); i++) {
|
||||
Vec2d b = Eigen::Rotation2Dd(ring[i]) * Eigen::Vector2d(0, r);
|
||||
vertices.emplace_back(Vec3d(b(0), b(1), z));
|
||||
auto id_ringsize = coord_t(id - int(ring.size()));
|
||||
if (i == 0) {
|
||||
// wrap around
|
||||
facets.emplace_back(id - 1, id, id + coord_t(ring.size() - 1) );
|
||||
facets.emplace_back(id - 1, id_ringsize, id);
|
||||
} else {
|
||||
facets.emplace_back(id_ringsize - 1, id_ringsize, id);
|
||||
facets.emplace_back(id - 1, id_ringsize - 1, id);
|
||||
}
|
||||
id++;
|
||||
}
|
||||
}
|
||||
|
||||
// special case: last ring connects to 0,0,rho*2.0
|
||||
// only form facets.
|
||||
if(send >= size_t(2*PI / angle)) {
|
||||
vertices.emplace_back(Vec3d(0.0, 0.0, -rho + increment*send*2.0*rho));
|
||||
for (size_t i = 0; i < ring.size(); i++) {
|
||||
auto id_ringsize = coord_t(id - int(ring.size()));
|
||||
if (i == 0) {
|
||||
// third vertex is on the other side of the ring.
|
||||
facets.emplace_back(id - 1, id_ringsize, id);
|
||||
} else {
|
||||
auto ci = coord_t(id_ringsize + coord_t(i));
|
||||
facets.emplace_back(ci - 1, ci, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
id++;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Contour3D cylinder(double r, double h, size_t ssteps, const Vec3d &sp)
|
||||
{
|
||||
assert(ssteps > 0);
|
||||
|
||||
Contour3D ret;
|
||||
|
||||
auto steps = int(ssteps);
|
||||
auto& points = ret.points;
|
||||
auto& indices = ret.faces3;
|
||||
points.reserve(2*ssteps);
|
||||
double a = 2*PI/steps;
|
||||
|
||||
Vec3d jp = sp;
|
||||
Vec3d endp = {sp(X), sp(Y), sp(Z) + h};
|
||||
|
||||
// Upper circle points
|
||||
for(int i = 0; i < steps; ++i) {
|
||||
double phi = i*a;
|
||||
double ex = endp(X) + r*std::cos(phi);
|
||||
double ey = endp(Y) + r*std::sin(phi);
|
||||
points.emplace_back(ex, ey, endp(Z));
|
||||
}
|
||||
|
||||
// Lower circle points
|
||||
for(int i = 0; i < steps; ++i) {
|
||||
double phi = i*a;
|
||||
double x = jp(X) + r*std::cos(phi);
|
||||
double y = jp(Y) + r*std::sin(phi);
|
||||
points.emplace_back(x, y, jp(Z));
|
||||
}
|
||||
|
||||
// Now create long triangles connecting upper and lower circles
|
||||
indices.reserve(2*ssteps);
|
||||
auto offs = steps;
|
||||
for(int i = 0; i < steps - 1; ++i) {
|
||||
indices.emplace_back(i, i + offs, offs + i + 1);
|
||||
indices.emplace_back(i, offs + i + 1, i + 1);
|
||||
}
|
||||
|
||||
// Last triangle connecting the first and last vertices
|
||||
auto last = steps - 1;
|
||||
indices.emplace_back(0, last, offs);
|
||||
indices.emplace_back(last, offs + last, offs);
|
||||
|
||||
// According to the slicing algorithms, we need to aid them with generating
|
||||
// a watertight body. So we create a triangle fan for the upper and lower
|
||||
// ending of the cylinder to close the geometry.
|
||||
points.emplace_back(jp); int ci = int(points.size() - 1);
|
||||
for(int i = 0; i < steps - 1; ++i)
|
||||
indices.emplace_back(i + offs + 1, i + offs, ci);
|
||||
|
||||
indices.emplace_back(offs, steps + offs - 1, ci);
|
||||
|
||||
points.emplace_back(endp); ci = int(points.size() - 1);
|
||||
for(int i = 0; i < steps - 1; ++i)
|
||||
indices.emplace_back(ci, i, i + 1);
|
||||
|
||||
indices.emplace_back(steps - 1, 0, ci);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Contour3D pinhead(double r_pin, double r_back, double length, size_t steps)
|
||||
{
|
||||
assert(steps > 0);
|
||||
assert(length >= 0.);
|
||||
assert(r_back > 0.);
|
||||
assert(r_pin > 0.);
|
||||
|
||||
Contour3D mesh;
|
||||
|
||||
// We create two spheres which will be connected with a robe that fits
|
||||
// both circles perfectly.
|
||||
|
||||
// Set up the model detail level
|
||||
const double detail = 2 * PI / steps;
|
||||
|
||||
// We don't generate whole circles. Instead, we generate only the
|
||||
// portions which are visible (not covered by the robe) To know the
|
||||
// exact portion of the bottom and top circles we need to use some
|
||||
// rules of tangent circles from which we can derive (using simple
|
||||
// triangles the following relations:
|
||||
|
||||
// The height of the whole mesh
|
||||
const double h = r_back + r_pin + length;
|
||||
double phi = PI / 2. - std::acos((r_back - r_pin) / h);
|
||||
|
||||
// To generate a whole circle we would pass a portion of (0, Pi)
|
||||
// To generate only a half horizontal circle we can pass (0, Pi/2)
|
||||
// The calculated phi is an offset to the half circles needed to smooth
|
||||
// the transition from the circle to the robe geometry
|
||||
|
||||
auto &&s1 = sphere(r_back, make_portion(0, PI / 2 + phi), detail);
|
||||
auto &&s2 = sphere(r_pin, make_portion(PI / 2 + phi, PI), detail);
|
||||
|
||||
for (auto &p : s2.points) p.z() += h;
|
||||
|
||||
mesh.merge(s1);
|
||||
mesh.merge(s2);
|
||||
|
||||
for (size_t idx1 = s1.points.size() - steps, idx2 = s1.points.size();
|
||||
idx1 < s1.points.size() - 1; idx1++, idx2++) {
|
||||
coord_t i1s1 = coord_t(idx1), i1s2 = coord_t(idx2);
|
||||
coord_t i2s1 = i1s1 + 1, i2s2 = i1s2 + 1;
|
||||
|
||||
mesh.faces3.emplace_back(i1s1, i2s1, i2s2);
|
||||
mesh.faces3.emplace_back(i1s1, i2s2, i1s2);
|
||||
}
|
||||
|
||||
auto i1s1 = coord_t(s1.points.size()) - coord_t(steps);
|
||||
auto i2s1 = coord_t(s1.points.size()) - 1;
|
||||
auto i1s2 = coord_t(s1.points.size());
|
||||
auto i2s2 = coord_t(s1.points.size()) + coord_t(steps) - 1;
|
||||
|
||||
mesh.faces3.emplace_back(i2s2, i2s1, i1s1);
|
||||
mesh.faces3.emplace_back(i1s2, i2s2, i1s1);
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
Contour3D halfcone(double baseheight,
|
||||
double r_bottom,
|
||||
double r_top,
|
||||
const Vec3d &pos,
|
||||
size_t steps)
|
||||
{
|
||||
assert(steps > 0);
|
||||
|
||||
if (baseheight <= 0 || steps <= 0) return {};
|
||||
|
||||
Contour3D base;
|
||||
|
||||
double a = 2 * PI / steps;
|
||||
auto last = int(steps - 1);
|
||||
Vec3d ep{pos.x(), pos.y(), pos.z() + baseheight};
|
||||
for (size_t i = 0; i < steps; ++i) {
|
||||
double phi = i * a;
|
||||
double x = pos.x() + r_top * std::cos(phi);
|
||||
double y = pos.y() + r_top * std::sin(phi);
|
||||
base.points.emplace_back(x, y, ep.z());
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < steps; ++i) {
|
||||
double phi = i * a;
|
||||
double x = pos.x() + r_bottom * std::cos(phi);
|
||||
double y = pos.y() + r_bottom * std::sin(phi);
|
||||
base.points.emplace_back(x, y, pos.z());
|
||||
}
|
||||
|
||||
base.points.emplace_back(pos);
|
||||
base.points.emplace_back(ep);
|
||||
|
||||
auto &indices = base.faces3;
|
||||
auto hcenter = int(base.points.size() - 1);
|
||||
auto lcenter = int(base.points.size() - 2);
|
||||
auto offs = int(steps);
|
||||
for (int i = 0; i < last; ++i) {
|
||||
indices.emplace_back(i, i + offs, offs + i + 1);
|
||||
indices.emplace_back(i, offs + i + 1, i + 1);
|
||||
indices.emplace_back(i, i + 1, hcenter);
|
||||
indices.emplace_back(lcenter, offs + i + 1, offs + i);
|
||||
}
|
||||
|
||||
indices.emplace_back(0, last, offs);
|
||||
indices.emplace_back(last, offs + last, offs);
|
||||
indices.emplace_back(hcenter, last, 0);
|
||||
indices.emplace_back(offs, offs + last, lcenter);
|
||||
|
||||
return base;
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::sla
|
117
src/libslic3r/SLA/SupportTreeMesher.hpp
Normal file
117
src/libslic3r/SLA/SupportTreeMesher.hpp
Normal file
@ -0,0 +1,117 @@
|
||||
#ifndef SUPPORTTREEMESHER_HPP
|
||||
#define SUPPORTTREEMESHER_HPP
|
||||
|
||||
#include "libslic3r/Point.hpp"
|
||||
|
||||
#include "libslic3r/SLA/SupportTreeBuilder.hpp"
|
||||
#include "libslic3r/SLA/Contour3D.hpp"
|
||||
|
||||
namespace Slic3r { namespace sla {
|
||||
|
||||
using Portion = std::tuple<double, double>;
|
||||
|
||||
inline Portion make_portion(double a, double b)
|
||||
{
|
||||
return std::make_tuple(a, b);
|
||||
}
|
||||
|
||||
Contour3D sphere(double rho,
|
||||
Portion portion = make_portion(0., 2. * PI),
|
||||
double fa = (2. * PI / 360.));
|
||||
|
||||
// Down facing cylinder in Z direction with arguments:
|
||||
// r: radius
|
||||
// h: Height
|
||||
// ssteps: how many edges will create the base circle
|
||||
// sp: starting point
|
||||
Contour3D cylinder(double r,
|
||||
double h,
|
||||
size_t steps = 45,
|
||||
const Vec3d &sp = Vec3d::Zero());
|
||||
|
||||
Contour3D pinhead(double r_pin, double r_back, double length, size_t steps = 45);
|
||||
|
||||
Contour3D halfcone(double baseheight,
|
||||
double r_bottom,
|
||||
double r_top,
|
||||
const Vec3d &pt = Vec3d::Zero(),
|
||||
size_t steps = 45);
|
||||
|
||||
inline Contour3D get_mesh(const Head &h, size_t steps)
|
||||
{
|
||||
Contour3D mesh = pinhead(h.r_pin_mm, h.r_back_mm, h.width_mm, steps);
|
||||
|
||||
for(auto& p : mesh.points) p.z() -= (h.fullwidth() - h.r_back_mm);
|
||||
|
||||
using Quaternion = Eigen::Quaternion<double>;
|
||||
|
||||
// We rotate the head to the specified direction. The head's pointing
|
||||
// side is facing upwards so this means that it would hold a support
|
||||
// point with a normal pointing straight down. This is the reason of
|
||||
// the -1 z coordinate
|
||||
auto quatern = Quaternion::FromTwoVectors(Vec3d{0, 0, -1}, h.dir);
|
||||
|
||||
for(auto& p : mesh.points) p = quatern * p + h.pos;
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
inline Contour3D get_mesh(const Pillar &p, size_t steps)
|
||||
{
|
||||
if(p.height > EPSILON) { // Endpoint is below the starting point
|
||||
// We just create a bridge geometry with the pillar parameters and
|
||||
// move the data.
|
||||
return cylinder(p.r, p.height, steps, p.endpoint());
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
inline Contour3D get_mesh(const Pedestal &p, size_t steps)
|
||||
{
|
||||
return halfcone(p.height, p.r_bottom, p.r_top, p.pos, steps);
|
||||
}
|
||||
|
||||
inline Contour3D get_mesh(const Junction &j, size_t steps)
|
||||
{
|
||||
Contour3D mesh = sphere(j.r, make_portion(0, PI), 2 *PI / steps);
|
||||
for(auto& p : mesh.points) p += j.pos;
|
||||
return mesh;
|
||||
}
|
||||
|
||||
inline Contour3D get_mesh(const Bridge &br, size_t steps)
|
||||
{
|
||||
using Quaternion = Eigen::Quaternion<double>;
|
||||
Vec3d v = (br.endp - br.startp);
|
||||
Vec3d dir = v.normalized();
|
||||
double d = v.norm();
|
||||
|
||||
Contour3D mesh = cylinder(br.r, d, steps);
|
||||
|
||||
auto quater = Quaternion::FromTwoVectors(Vec3d{0,0,1}, dir);
|
||||
for(auto& p : mesh.points) p = quater * p + br.startp;
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
inline Contour3D get_mesh(const DiffBridge &br, size_t steps)
|
||||
{
|
||||
double h = br.get_length();
|
||||
Contour3D mesh = halfcone(h, br.r, br.end_r, Vec3d::Zero(), steps);
|
||||
|
||||
using Quaternion = Eigen::Quaternion<double>;
|
||||
|
||||
// We rotate the head to the specified direction. The head's pointing
|
||||
// side is facing upwards so this means that it would hold a support
|
||||
// point with a normal pointing straight down. This is the reason of
|
||||
// the -1 z coordinate
|
||||
auto quatern = Quaternion::FromTwoVectors(Vec3d{0, 0, 1}, br.get_dir());
|
||||
|
||||
for(auto& p : mesh.points) p = quatern * p + br.startp;
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::sla
|
||||
|
||||
#endif // SUPPORTTREEMESHER_HPP
|
@ -35,13 +35,16 @@ bool is_zero_elevation(const SLAPrintObjectConfig &c)
|
||||
}
|
||||
|
||||
// Compile the argument for support creation from the static print config.
|
||||
sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c)
|
||||
sla::SupportTreeConfig make_support_cfg(const SLAPrintObjectConfig& c)
|
||||
{
|
||||
sla::SupportConfig scfg;
|
||||
sla::SupportTreeConfig scfg;
|
||||
|
||||
scfg.enabled = c.supports_enable.getBool();
|
||||
scfg.head_front_radius_mm = 0.5*c.support_head_front_diameter.getFloat();
|
||||
scfg.head_back_radius_mm = 0.5*c.support_pillar_diameter.getFloat();
|
||||
double pillar_r = 0.5 * c.support_pillar_diameter.getFloat();
|
||||
scfg.head_back_radius_mm = pillar_r;
|
||||
scfg.head_fallback_radius_mm =
|
||||
0.01 * c.support_small_pillar_diameter_percent.getFloat() * pillar_r;
|
||||
scfg.head_penetration_mm = c.support_head_penetration.getFloat();
|
||||
scfg.head_width_mm = c.support_head_width.getFloat();
|
||||
scfg.object_elevation_mm = is_zero_elevation(c) ?
|
||||
@ -616,7 +619,7 @@ std::string SLAPrint::validate() const
|
||||
return L("Cannot proceed without support points! "
|
||||
"Add support points or disable support generation.");
|
||||
|
||||
sla::SupportConfig cfg = make_support_cfg(po->config());
|
||||
sla::SupportTreeConfig cfg = make_support_cfg(po->config());
|
||||
|
||||
double elv = cfg.object_elevation_mm;
|
||||
|
||||
@ -925,6 +928,7 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vector<t_conf
|
||||
|| opt_key == "support_head_penetration"
|
||||
|| opt_key == "support_head_width"
|
||||
|| opt_key == "support_pillar_diameter"
|
||||
|| opt_key == "support_small_pillar_diameter_percent"
|
||||
|| opt_key == "support_max_bridges_on_pillar"
|
||||
|| opt_key == "support_pillar_connection_mode"
|
||||
|| opt_key == "support_buildplate_only"
|
||||
|
@ -544,7 +544,7 @@ private:
|
||||
|
||||
bool is_zero_elevation(const SLAPrintObjectConfig &c);
|
||||
|
||||
sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c);
|
||||
sla::SupportTreeConfig make_support_cfg(const SLAPrintObjectConfig& c);
|
||||
|
||||
sla::PadConfig::EmbedObject builtin_pad_cfg(const SLAPrintObjectConfig& c);
|
||||
|
||||
|
@ -360,18 +360,6 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po)
|
||||
// removed them on purpose. No calculation will be done.
|
||||
po.m_supportdata->pts = po.transformed_support_points();
|
||||
}
|
||||
|
||||
// If the zero elevation mode is engaged, we have to filter out all the
|
||||
// points that are on the bottom of the object
|
||||
if (is_zero_elevation(po.config())) {
|
||||
double tolerance = po.config().pad_enable.getBool() ?
|
||||
po.m_config.pad_wall_thickness.getFloat() :
|
||||
po.m_config.support_base_height.getFloat();
|
||||
|
||||
remove_bottom_points(po.m_supportdata->pts,
|
||||
po.m_supportdata->emesh.ground_level(),
|
||||
tolerance);
|
||||
}
|
||||
}
|
||||
|
||||
void SLAPrint::Steps::support_tree(SLAPrintObject &po)
|
||||
@ -382,6 +370,13 @@ void SLAPrint::Steps::support_tree(SLAPrintObject &po)
|
||||
|
||||
if (pcfg.embed_object)
|
||||
po.m_supportdata->emesh.ground_level_offset(pcfg.wall_thickness_mm);
|
||||
|
||||
// If the zero elevation mode is engaged, we have to filter out all the
|
||||
// points that are on the bottom of the object
|
||||
if (is_zero_elevation(po.config())) {
|
||||
remove_bottom_points(po.m_supportdata->pts,
|
||||
float(po.m_supportdata->emesh.ground_level() + EPSILON));
|
||||
}
|
||||
|
||||
po.m_supportdata->cfg = make_support_cfg(po.m_config);
|
||||
// po.m_supportdata->emesh.load_holes(po.transformed_drainhole_points());
|
||||
|
@ -54,8 +54,5 @@
|
||||
// Enable built-in DPI changed event handler of wxWidgets 3.1.3
|
||||
#define ENABLE_WX_3_1_3_DPI_CHANGED_EVENT (1 && ENABLE_2_3_0_ALPHA1)
|
||||
|
||||
// Enable changing application layout without the need to restart
|
||||
#define ENABLE_LAYOUT_NO_RESTART (1 && ENABLE_2_3_0_ALPHA1)
|
||||
|
||||
|
||||
#endif // _prusaslicer_technologies_h_
|
||||
|
689
src/libslic3r/TriangleSelector.cpp
Normal file
689
src/libslic3r/TriangleSelector.cpp
Normal file
@ -0,0 +1,689 @@
|
||||
#include "TriangleSelector.hpp"
|
||||
#include "Model.hpp"
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
||||
|
||||
// sides_to_split==-1 : just restore previous split
|
||||
void TriangleSelector::Triangle::set_division(int sides_to_split, int special_side_idx)
|
||||
{
|
||||
assert(sides_to_split >=-1 && sides_to_split <= 3);
|
||||
assert(special_side_idx >=-1 && special_side_idx < 3);
|
||||
|
||||
// If splitting one or two sides, second argument must be provided.
|
||||
assert(sides_to_split != 1 || special_side_idx != -1);
|
||||
assert(sides_to_split != 2 || special_side_idx != -1);
|
||||
|
||||
if (sides_to_split != -1) {
|
||||
this->number_of_splits = sides_to_split;
|
||||
if (sides_to_split != 0) {
|
||||
assert(old_number_of_splits == 0);
|
||||
this->special_side_idx = special_side_idx;
|
||||
this->old_number_of_splits = sides_to_split;
|
||||
}
|
||||
}
|
||||
else {
|
||||
assert(old_number_of_splits != 0);
|
||||
this->number_of_splits = old_number_of_splits;
|
||||
// indices of children should still be there.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void TriangleSelector::select_patch(const Vec3f& hit, int facet_start,
|
||||
const Vec3f& source, const Vec3f& dir,
|
||||
float radius, FacetSupportType new_state)
|
||||
{
|
||||
assert(facet_start < m_orig_size_indices);
|
||||
assert(is_approx(dir.norm(), 1.f));
|
||||
|
||||
// Save current cursor center, squared radius and camera direction,
|
||||
// so we don't have to pass it around.
|
||||
m_cursor = {hit, source, dir, radius*radius};
|
||||
|
||||
// In case user changed cursor size since last time, update triangle edge limit.
|
||||
if (m_old_cursor_radius != radius) {
|
||||
set_edge_limit(radius / 5.f);
|
||||
m_old_cursor_radius = radius;
|
||||
}
|
||||
|
||||
// Now start with the facet the pointer points to and check all adjacent facets.
|
||||
std::vector<int> facets_to_check{facet_start};
|
||||
std::vector<bool> visited(m_orig_size_indices, false); // keep track of facets we already processed
|
||||
int facet_idx = 0; // index into facets_to_check
|
||||
while (facet_idx < int(facets_to_check.size())) {
|
||||
int facet = facets_to_check[facet_idx];
|
||||
if (! visited[facet]) {
|
||||
if (select_triangle(facet, new_state)) {
|
||||
// add neighboring facets to list to be proccessed later
|
||||
for (int n=0; n<3; ++n) {
|
||||
if (faces_camera(m_mesh->stl.neighbors_start[facet].neighbor[n]))
|
||||
facets_to_check.push_back(m_mesh->stl.neighbors_start[facet].neighbor[n]);
|
||||
}
|
||||
}
|
||||
}
|
||||
visited[facet] = true;
|
||||
++facet_idx;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Selects either the whole triangle (discarding any children it had), or divides
|
||||
// the triangle recursively, selecting just subtriangles truly inside the circle.
|
||||
// This is done by an actual recursive call. Returns false if the triangle is
|
||||
// outside the cursor.
|
||||
bool TriangleSelector::select_triangle(int facet_idx, FacetSupportType type, bool recursive_call)
|
||||
{
|
||||
assert(facet_idx < int(m_triangles.size()));
|
||||
|
||||
Triangle* tr = &m_triangles[facet_idx];
|
||||
if (! tr->valid)
|
||||
return false;
|
||||
|
||||
int num_of_inside_vertices = vertices_inside(facet_idx);
|
||||
|
||||
if (num_of_inside_vertices == 0
|
||||
&& ! is_pointer_in_triangle(facet_idx)
|
||||
&& ! is_edge_inside_cursor(facet_idx))
|
||||
return false;
|
||||
|
||||
if (num_of_inside_vertices == 3) {
|
||||
// dump any subdivision and select whole triangle
|
||||
undivide_triangle(facet_idx);
|
||||
tr->set_state(type);
|
||||
} else {
|
||||
// the triangle is partially inside, let's recursively divide it
|
||||
// (if not already) and try selecting its children.
|
||||
|
||||
if (! tr->is_split() && tr->get_state() == type) {
|
||||
// This is leaf triangle that is already of correct type as a whole.
|
||||
// No need to split, all children would end up selected anyway.
|
||||
return true;
|
||||
}
|
||||
|
||||
split_triangle(facet_idx);
|
||||
tr = &m_triangles[facet_idx]; // might have been invalidated
|
||||
|
||||
|
||||
int num_of_children = tr->number_of_split_sides() + 1;
|
||||
if (num_of_children != 1) {
|
||||
for (int i=0; i<num_of_children; ++i) {
|
||||
assert(i < int(tr->children.size()));
|
||||
assert(tr->children[i] < int(m_triangles.size()));
|
||||
|
||||
select_triangle(tr->children[i], type, true);
|
||||
tr = &m_triangles[facet_idx]; // might have been invalidated
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (! recursive_call) {
|
||||
// In case that all children are leafs and have the same state now,
|
||||
// they may be removed and substituted by the parent triangle.
|
||||
remove_useless_children(facet_idx);
|
||||
|
||||
// Make sure that we did not lose track of invalid triangles.
|
||||
assert(m_invalid_triangles == std::count_if(m_triangles.begin(), m_triangles.end(),
|
||||
[](const Triangle& tr) { return ! tr.valid; }));
|
||||
|
||||
// Do garbage collection maybe?
|
||||
if (2*m_invalid_triangles > int(m_triangles.size()))
|
||||
garbage_collect();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void TriangleSelector::set_facet(int facet_idx, FacetSupportType state)
|
||||
{
|
||||
assert(facet_idx < m_orig_size_indices);
|
||||
undivide_triangle(facet_idx);
|
||||
assert(! m_triangles[facet_idx].is_split());
|
||||
m_triangles[facet_idx].set_state(state);
|
||||
}
|
||||
|
||||
void TriangleSelector::split_triangle(int facet_idx)
|
||||
{
|
||||
if (m_triangles[facet_idx].is_split()) {
|
||||
// The triangle is divided already.
|
||||
return;
|
||||
}
|
||||
|
||||
Triangle* tr = &m_triangles[facet_idx];
|
||||
|
||||
FacetSupportType old_type = tr->get_state();
|
||||
|
||||
if (tr->was_split_before() != 0) {
|
||||
// This triangle is not split at the moment, but was at one point
|
||||
// in history. We can just restore it and resurrect its children.
|
||||
tr->set_division(-1);
|
||||
for (int i=0; i<=tr->number_of_split_sides(); ++i) {
|
||||
m_triangles[tr->children[i]].set_state(old_type);
|
||||
m_triangles[tr->children[i]].valid = true;
|
||||
--m_invalid_triangles;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// If we got here, we are about to actually split the triangle.
|
||||
const double limit_squared = m_edge_limit_sqr;
|
||||
|
||||
std::array<int, 3>& facet = tr->verts_idxs;
|
||||
const stl_vertex* pts[3] = { &m_vertices[facet[0]].v, &m_vertices[facet[1]].v, &m_vertices[facet[2]].v};
|
||||
double sides[3] = { (*pts[2]-*pts[1]).squaredNorm(),
|
||||
(*pts[0]-*pts[2]).squaredNorm(),
|
||||
(*pts[1]-*pts[0]).squaredNorm() };
|
||||
|
||||
std::vector<int> sides_to_split;
|
||||
int side_to_keep = -1;
|
||||
for (int pt_idx = 0; pt_idx<3; ++pt_idx) {
|
||||
if (sides[pt_idx] > limit_squared)
|
||||
sides_to_split.push_back(pt_idx);
|
||||
else
|
||||
side_to_keep = pt_idx;
|
||||
}
|
||||
if (sides_to_split.empty()) {
|
||||
// This shall be unselected.
|
||||
tr->set_division(0);
|
||||
return;
|
||||
}
|
||||
|
||||
// Save how the triangle will be split. Second argument makes sense only for one
|
||||
// or two split sides, otherwise the value is ignored.
|
||||
tr->set_division(sides_to_split.size(),
|
||||
sides_to_split.size() == 2 ? side_to_keep : sides_to_split[0]);
|
||||
|
||||
perform_split(facet_idx, old_type);
|
||||
}
|
||||
|
||||
|
||||
// Calculate distance of a point from a line.
|
||||
bool TriangleSelector::is_point_inside_cursor(const Vec3f& point) const
|
||||
{
|
||||
Vec3f diff = m_cursor.center - point;
|
||||
return (diff - diff.dot(m_cursor.dir) * m_cursor.dir).squaredNorm() < m_cursor.radius_sqr;
|
||||
}
|
||||
|
||||
|
||||
// Is pointer in a triangle?
|
||||
bool TriangleSelector::is_pointer_in_triangle(int facet_idx) const
|
||||
{
|
||||
auto signed_volume_sign = [](const Vec3f& a, const Vec3f& b,
|
||||
const Vec3f& c, const Vec3f& d) -> bool {
|
||||
return ((b-a).cross(c-a)).dot(d-a) > 0.;
|
||||
};
|
||||
|
||||
const Vec3f& p1 = m_vertices[m_triangles[facet_idx].verts_idxs[0]].v;
|
||||
const Vec3f& p2 = m_vertices[m_triangles[facet_idx].verts_idxs[1]].v;
|
||||
const Vec3f& p3 = m_vertices[m_triangles[facet_idx].verts_idxs[2]].v;
|
||||
const Vec3f& q1 = m_cursor.center + m_cursor.dir;
|
||||
const Vec3f q2 = m_cursor.center - m_cursor.dir;
|
||||
|
||||
if (signed_volume_sign(q1,p1,p2,p3) != signed_volume_sign(q2,p1,p2,p3)) {
|
||||
bool pos = signed_volume_sign(q1,q2,p1,p2);
|
||||
if (signed_volume_sign(q1,q2,p2,p3) == pos && signed_volume_sign(q1,q2,p3,p1) == pos)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Determine whether this facet is potentially visible (still can be obscured).
|
||||
bool TriangleSelector::faces_camera(int facet) const
|
||||
{
|
||||
assert(facet < m_orig_size_indices);
|
||||
// The normal is cached in mesh->stl, use it.
|
||||
return (m_mesh->stl.facet_start[facet].normal.dot(m_cursor.dir) < 0.);
|
||||
}
|
||||
|
||||
|
||||
// How many vertices of a triangle are inside the circle?
|
||||
int TriangleSelector::vertices_inside(int facet_idx) const
|
||||
{
|
||||
int inside = 0;
|
||||
for (size_t i=0; i<3; ++i) {
|
||||
if (is_point_inside_cursor(m_vertices[m_triangles[facet_idx].verts_idxs[i]].v))
|
||||
++inside;
|
||||
}
|
||||
return inside;
|
||||
}
|
||||
|
||||
|
||||
// Is edge inside cursor?
|
||||
bool TriangleSelector::is_edge_inside_cursor(int facet_idx) const
|
||||
{
|
||||
Vec3f pts[3];
|
||||
for (int i=0; i<3; ++i)
|
||||
pts[i] = m_vertices[m_triangles[facet_idx].verts_idxs[i]].v;
|
||||
|
||||
const Vec3f& p = m_cursor.center;
|
||||
|
||||
for (int side = 0; side < 3; ++side) {
|
||||
const Vec3f& a = pts[side];
|
||||
const Vec3f& b = pts[side<2 ? side+1 : 0];
|
||||
Vec3f s = (b-a).normalized();
|
||||
float t = (p-a).dot(s);
|
||||
Vec3f vector = a+t*s - p;
|
||||
|
||||
// vector is 3D vector from center to the intersection. What we want to
|
||||
// measure is length of its projection onto plane perpendicular to dir.
|
||||
float dist_sqr = vector.squaredNorm() - std::pow(vector.dot(m_cursor.dir), 2.f);
|
||||
if (dist_sqr < m_cursor.radius_sqr && t>=0.f && t<=(b-a).norm())
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Recursively remove all subtriangles.
|
||||
void TriangleSelector::undivide_triangle(int facet_idx)
|
||||
{
|
||||
assert(facet_idx < int(m_triangles.size()));
|
||||
Triangle& tr = m_triangles[facet_idx];
|
||||
|
||||
if (tr.is_split()) {
|
||||
for (int i=0; i<=tr.number_of_split_sides(); ++i) {
|
||||
undivide_triangle(tr.children[i]);
|
||||
m_triangles[tr.children[i]].valid = false;
|
||||
++m_invalid_triangles;
|
||||
}
|
||||
tr.set_division(0); // not split
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void TriangleSelector::remove_useless_children(int facet_idx)
|
||||
{
|
||||
// Check that all children are leafs of the same type. If not, try to
|
||||
// make them (recursive call). Remove them if sucessful.
|
||||
|
||||
assert(facet_idx < int(m_triangles.size()) && m_triangles[facet_idx].valid);
|
||||
Triangle& tr = m_triangles[facet_idx];
|
||||
|
||||
if (! tr.is_split()) {
|
||||
// This is a leaf, there nothing to do. This can happen during the
|
||||
// first (non-recursive call). Shouldn't otherwise.
|
||||
return;
|
||||
}
|
||||
|
||||
// Call this for all non-leaf children.
|
||||
for (int child_idx=0; child_idx<=tr.number_of_split_sides(); ++child_idx) {
|
||||
assert(child_idx < int(m_triangles.size()) && m_triangles[child_idx].valid);
|
||||
if (m_triangles[tr.children[child_idx]].is_split())
|
||||
remove_useless_children(tr.children[child_idx]);
|
||||
}
|
||||
|
||||
|
||||
// Return if a child is not leaf or two children differ in type.
|
||||
FacetSupportType first_child_type = FacetSupportType::NONE;
|
||||
for (int child_idx=0; child_idx<=tr.number_of_split_sides(); ++child_idx) {
|
||||
if (m_triangles[tr.children[child_idx]].is_split())
|
||||
return;
|
||||
if (child_idx == 0)
|
||||
first_child_type = m_triangles[tr.children[0]].get_state();
|
||||
else if (m_triangles[tr.children[child_idx]].get_state() != first_child_type)
|
||||
return;
|
||||
}
|
||||
|
||||
// If we got here, the children can be removed.
|
||||
undivide_triangle(facet_idx);
|
||||
tr.set_state(first_child_type);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void TriangleSelector::garbage_collect()
|
||||
{
|
||||
// First make a map from old to new triangle indices.
|
||||
int new_idx = m_orig_size_indices;
|
||||
std::vector<int> new_triangle_indices(m_triangles.size(), -1);
|
||||
for (int i = m_orig_size_indices; i<int(m_triangles.size()); ++i) {
|
||||
if (m_triangles[i].valid) {
|
||||
new_triangle_indices[i] = new_idx;
|
||||
++new_idx;
|
||||
} else {
|
||||
// Decrement reference counter for the vertices.
|
||||
for (int j=0; j<3; ++j)
|
||||
--m_vertices[m_triangles[i].verts_idxs[j]].ref_cnt;
|
||||
}
|
||||
}
|
||||
|
||||
// Now we know which vertices are not referenced anymore. Make a map
|
||||
// from old idxs to new ones, like we did for triangles.
|
||||
new_idx = m_orig_size_vertices;
|
||||
std::vector<int> new_vertices_indices(m_vertices.size(), -1);
|
||||
for (int i=m_orig_size_vertices; i<int(m_vertices.size()); ++i) {
|
||||
assert(m_vertices[i].ref_cnt >= 0);
|
||||
if (m_vertices[i].ref_cnt != 0) {
|
||||
new_vertices_indices[i] = new_idx;
|
||||
++new_idx;
|
||||
}
|
||||
}
|
||||
|
||||
// We can remove all invalid triangles and vertices that are no longer referenced.
|
||||
m_triangles.erase(std::remove_if(m_triangles.begin()+m_orig_size_indices, m_triangles.end(),
|
||||
[](const Triangle& tr) { return ! tr.valid; }),
|
||||
m_triangles.end());
|
||||
m_vertices.erase(std::remove_if(m_vertices.begin()+m_orig_size_vertices, m_vertices.end(),
|
||||
[](const Vertex& vert) { return vert.ref_cnt == 0; }),
|
||||
m_vertices.end());
|
||||
|
||||
// Now go through all remaining triangles and update changed indices.
|
||||
for (Triangle& tr : m_triangles) {
|
||||
assert(tr.valid);
|
||||
|
||||
if (tr.is_split()) {
|
||||
// There are children. Update their indices.
|
||||
for (int j=0; j<=tr.number_of_split_sides(); ++j) {
|
||||
assert(new_triangle_indices[tr.children[j]] != -1);
|
||||
tr.children[j] = new_triangle_indices[tr.children[j]];
|
||||
}
|
||||
}
|
||||
|
||||
// Update indices into m_vertices. The original vertices are never
|
||||
// touched and need not be reindexed.
|
||||
for (int& idx : tr.verts_idxs) {
|
||||
if (idx >= m_orig_size_vertices) {
|
||||
assert(new_vertices_indices[idx] != -1);
|
||||
idx = new_vertices_indices[idx];
|
||||
}
|
||||
}
|
||||
|
||||
// If this triangle was split before, forget it.
|
||||
// Children referenced in the cache are dead by now.
|
||||
tr.forget_history();
|
||||
}
|
||||
|
||||
m_invalid_triangles = 0;
|
||||
}
|
||||
|
||||
TriangleSelector::TriangleSelector(const TriangleMesh& mesh)
|
||||
: m_mesh{&mesh}
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
|
||||
void TriangleSelector::reset()
|
||||
{
|
||||
if (! m_orig_size_indices != 0) // unless this is run from constructor
|
||||
garbage_collect();
|
||||
m_vertices.clear();
|
||||
m_triangles.clear();
|
||||
for (const stl_vertex& vert : m_mesh->its.vertices)
|
||||
m_vertices.emplace_back(vert);
|
||||
for (const stl_triangle_vertex_indices& ind : m_mesh->its.indices)
|
||||
push_triangle(ind[0], ind[1], ind[2]);
|
||||
m_orig_size_vertices = m_vertices.size();
|
||||
m_orig_size_indices = m_triangles.size();
|
||||
m_invalid_triangles = 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void TriangleSelector::set_edge_limit(float edge_limit)
|
||||
{
|
||||
float new_limit_sqr = std::pow(edge_limit, 2.f);
|
||||
|
||||
if (new_limit_sqr != m_edge_limit_sqr) {
|
||||
m_edge_limit_sqr = new_limit_sqr;
|
||||
|
||||
// The way how triangles split may be different now, forget
|
||||
// all cached splits.
|
||||
garbage_collect();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void TriangleSelector::push_triangle(int a, int b, int c)
|
||||
{
|
||||
for (int i : {a, b, c}) {
|
||||
assert(i >= 0 && i < int(m_vertices.size()));
|
||||
++m_vertices[i].ref_cnt;
|
||||
}
|
||||
m_triangles.emplace_back(a, b, c);
|
||||
}
|
||||
|
||||
|
||||
void TriangleSelector::perform_split(int facet_idx, FacetSupportType old_state)
|
||||
{
|
||||
Triangle* tr = &m_triangles[facet_idx];
|
||||
|
||||
assert(tr->is_split());
|
||||
|
||||
// Read info about how to split this triangle.
|
||||
int sides_to_split = tr->number_of_split_sides();
|
||||
|
||||
// indices of triangle vertices
|
||||
std::vector<int> verts_idxs;
|
||||
int idx = tr->special_side();
|
||||
for (int j=0; j<3; ++j) {
|
||||
verts_idxs.push_back(tr->verts_idxs[idx++]);
|
||||
if (idx == 3)
|
||||
idx = 0;
|
||||
}
|
||||
|
||||
if (sides_to_split == 1) {
|
||||
m_vertices.emplace_back((m_vertices[verts_idxs[1]].v + m_vertices[verts_idxs[2]].v)/2.);
|
||||
verts_idxs.insert(verts_idxs.begin()+2, m_vertices.size() - 1);
|
||||
|
||||
push_triangle(verts_idxs[0], verts_idxs[1], verts_idxs[2]);
|
||||
push_triangle(verts_idxs[2], verts_idxs[3], verts_idxs[0]);
|
||||
}
|
||||
|
||||
if (sides_to_split == 2) {
|
||||
m_vertices.emplace_back((m_vertices[verts_idxs[0]].v + m_vertices[verts_idxs[1]].v)/2.);
|
||||
verts_idxs.insert(verts_idxs.begin()+1, m_vertices.size() - 1);
|
||||
|
||||
m_vertices.emplace_back((m_vertices[verts_idxs[0]].v + m_vertices[verts_idxs[3]].v)/2.);
|
||||
verts_idxs.insert(verts_idxs.begin()+4, m_vertices.size() - 1);
|
||||
|
||||
push_triangle(verts_idxs[0], verts_idxs[1], verts_idxs[4]);
|
||||
push_triangle(verts_idxs[1], verts_idxs[2], verts_idxs[4]);
|
||||
push_triangle(verts_idxs[2], verts_idxs[3], verts_idxs[4]);
|
||||
}
|
||||
|
||||
if (sides_to_split == 3) {
|
||||
m_vertices.emplace_back((m_vertices[verts_idxs[0]].v + m_vertices[verts_idxs[1]].v)/2.);
|
||||
verts_idxs.insert(verts_idxs.begin()+1, m_vertices.size() - 1);
|
||||
m_vertices.emplace_back((m_vertices[verts_idxs[2]].v + m_vertices[verts_idxs[3]].v)/2.);
|
||||
verts_idxs.insert(verts_idxs.begin()+3, m_vertices.size() - 1);
|
||||
m_vertices.emplace_back((m_vertices[verts_idxs[4]].v + m_vertices[verts_idxs[0]].v)/2.);
|
||||
verts_idxs.insert(verts_idxs.begin()+5, m_vertices.size() - 1);
|
||||
|
||||
push_triangle(verts_idxs[0], verts_idxs[1], verts_idxs[5]);
|
||||
push_triangle(verts_idxs[1], verts_idxs[2], verts_idxs[3]);
|
||||
push_triangle(verts_idxs[3], verts_idxs[4], verts_idxs[5]);
|
||||
push_triangle(verts_idxs[1], verts_idxs[3], verts_idxs[5]);
|
||||
}
|
||||
|
||||
tr = &m_triangles[facet_idx]; // may have been invalidated
|
||||
|
||||
// And save the children. All children should start in the same state as the triangle we just split.
|
||||
assert(sides_to_split <= 3);
|
||||
for (int i=0; i<=sides_to_split; ++i) {
|
||||
tr->children[i] = m_triangles.size()-1-i;
|
||||
m_triangles[tr->children[i]].set_state(old_state);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
indexed_triangle_set TriangleSelector::get_facets(FacetSupportType state) const
|
||||
{
|
||||
indexed_triangle_set out;
|
||||
for (const Triangle& tr : m_triangles) {
|
||||
if (tr.valid && ! tr.is_split() && tr.get_state() == state) {
|
||||
stl_triangle_vertex_indices indices;
|
||||
for (int i=0; i<3; ++i) {
|
||||
out.vertices.emplace_back(m_vertices[tr.verts_idxs[i]].v);
|
||||
indices[i] = out.vertices.size() - 1;
|
||||
}
|
||||
out.indices.emplace_back(indices);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
|
||||
std::map<int, std::vector<bool>> TriangleSelector::serialize() const
|
||||
{
|
||||
// Each original triangle of the mesh is assigned a number encoding its state
|
||||
// or how it is split. Each triangle is encoded by 4 bits (xxyy):
|
||||
// leaf triangle: xx = FacetSupportType, yy = 0
|
||||
// non-leaf: xx = special side, yy = number of split sides
|
||||
// These are bitwise appended and formed into one 64-bit integer.
|
||||
|
||||
// The function returns a map from original triangle indices to
|
||||
// stream of bits encoding state and offsprings.
|
||||
|
||||
std::map<int, std::vector<bool>> out;
|
||||
for (int i=0; i<m_orig_size_indices; ++i) {
|
||||
const Triangle& tr = m_triangles[i];
|
||||
|
||||
if (! tr.is_split() && tr.get_state() == FacetSupportType::NONE)
|
||||
continue; // no need to save anything, unsplit and unselected is default
|
||||
|
||||
std::vector<bool> data; // complete encoding of this mesh triangle
|
||||
int stored_triangles = 0; // how many have been already encoded
|
||||
|
||||
std::function<void(int)> serialize_recursive;
|
||||
serialize_recursive = [this, &serialize_recursive, &stored_triangles, &data](int facet_idx) {
|
||||
const Triangle& tr = m_triangles[facet_idx];
|
||||
|
||||
// Always save number of split sides. It is zero for unsplit triangles.
|
||||
int split_sides = tr.number_of_split_sides();
|
||||
assert(split_sides >= 0 && split_sides <= 3);
|
||||
|
||||
//data |= (split_sides << (stored_triangles * 4));
|
||||
data.push_back(split_sides & 0b01);
|
||||
data.push_back(split_sides & 0b10);
|
||||
|
||||
if (tr.is_split()) {
|
||||
// If this triangle is split, save which side is split (in case
|
||||
// of one split) or kept (in case of two splits). The value will
|
||||
// be ignored for 3-side split.
|
||||
assert(split_sides > 0);
|
||||
assert(tr.special_side() >= 0 && tr.special_side() <= 3);
|
||||
data.push_back(tr.special_side() & 0b01);
|
||||
data.push_back(tr.special_side() & 0b10);
|
||||
++stored_triangles;
|
||||
// Now save all children.
|
||||
for (int child_idx=0; child_idx<=split_sides; ++child_idx)
|
||||
serialize_recursive(tr.children[child_idx]);
|
||||
} else {
|
||||
// In case this is leaf, we better save information about its state.
|
||||
assert(int(tr.get_state()) <= 3);
|
||||
data.push_back(int(tr.get_state()) & 0b01);
|
||||
data.push_back(int(tr.get_state()) & 0b10);
|
||||
++stored_triangles;
|
||||
}
|
||||
};
|
||||
|
||||
serialize_recursive(i);
|
||||
out[i] = data;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
void TriangleSelector::deserialize(const std::map<int, std::vector<bool>> data)
|
||||
{
|
||||
reset(); // dump any current state
|
||||
for (const auto& [triangle_id, code] : data) {
|
||||
assert(triangle_id < int(m_triangles.size()));
|
||||
assert(! code.empty());
|
||||
int processed_triangles = 0;
|
||||
struct ProcessingInfo {
|
||||
int facet_id = 0;
|
||||
int processed_children = 0;
|
||||
int total_children = 0;
|
||||
};
|
||||
|
||||
// Vector to store all parents that have offsprings.
|
||||
std::vector<ProcessingInfo> parents;
|
||||
|
||||
while (true) {
|
||||
// Read next triangle info.
|
||||
int next_code = 0;
|
||||
for (int i=3; i>=0; --i) {
|
||||
next_code = next_code << 1;
|
||||
next_code |= int(code[4 * processed_triangles + i]);
|
||||
}
|
||||
++processed_triangles;
|
||||
|
||||
int num_of_split_sides = (next_code & 0b11);
|
||||
int num_of_children = num_of_split_sides != 0 ? num_of_split_sides + 1 : 0;
|
||||
bool is_split = num_of_children != 0;
|
||||
FacetSupportType state = FacetSupportType(next_code >> 2);
|
||||
int special_side = (next_code >> 2);
|
||||
|
||||
// Take care of the first iteration separately, so handling of the others is simpler.
|
||||
if (parents.empty()) {
|
||||
if (! is_split) {
|
||||
// root is not split. just set the state and that's it.
|
||||
m_triangles[triangle_id].set_state(state);
|
||||
break;
|
||||
} else {
|
||||
// root is split, add it into list of parents and split it.
|
||||
// then go to the next.
|
||||
parents.push_back({triangle_id, 0, num_of_children});
|
||||
m_triangles[triangle_id].set_division(num_of_children-1, special_side);
|
||||
perform_split(triangle_id, FacetSupportType::NONE);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// This is not the first iteration. This triangle is a child of last seen parent.
|
||||
assert(! parents.empty());
|
||||
assert(parents.back().processed_children < parents.back().total_children);
|
||||
|
||||
if (is_split) {
|
||||
// split the triangle and save it as parent of the next ones.
|
||||
const ProcessingInfo& last = parents.back();
|
||||
int this_idx = m_triangles[last.facet_id].children[last.processed_children];
|
||||
m_triangles[this_idx].set_division(num_of_children-1, special_side);
|
||||
perform_split(this_idx, FacetSupportType::NONE);
|
||||
parents.push_back({this_idx, 0, num_of_children});
|
||||
} else {
|
||||
// this triangle belongs to last split one
|
||||
m_triangles[m_triangles[parents.back().facet_id].children[parents.back().processed_children]].set_state(state);
|
||||
++parents.back().processed_children;
|
||||
}
|
||||
|
||||
|
||||
// If all children of the past parent triangle are claimed, move to grandparent.
|
||||
while (parents.back().processed_children == parents.back().total_children) {
|
||||
parents.pop_back();
|
||||
|
||||
if (parents.empty())
|
||||
break;
|
||||
|
||||
// And increment the grandparent children counter, because
|
||||
// we have just finished that branch and got back here.
|
||||
++parents.back().processed_children;
|
||||
}
|
||||
|
||||
// In case we popped back the root, we should be done.
|
||||
if (parents.empty())
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
} // namespace Slic3r
|
155
src/libslic3r/TriangleSelector.hpp
Normal file
155
src/libslic3r/TriangleSelector.hpp
Normal file
@ -0,0 +1,155 @@
|
||||
#ifndef libslic3r_TriangleSelector_hpp_
|
||||
#define libslic3r_TriangleSelector_hpp_
|
||||
|
||||
// #define PRUSASLICER_TRIANGLE_SELECTOR_DEBUG
|
||||
|
||||
|
||||
#include "Point.hpp"
|
||||
#include "TriangleMesh.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
enum class FacetSupportType : int8_t;
|
||||
|
||||
|
||||
|
||||
// Following class holds information about selected triangles. It also has power
|
||||
// to recursively subdivide the triangles and make the selection finer.
|
||||
class TriangleSelector {
|
||||
public:
|
||||
void set_edge_limit(float edge_limit);
|
||||
|
||||
// Create new object on a TriangleMesh. The referenced mesh must
|
||||
// stay valid, a ptr to it is saved and used.
|
||||
explicit TriangleSelector(const TriangleMesh& mesh);
|
||||
|
||||
// Select all triangles fully inside the circle, subdivide where needed.
|
||||
void select_patch(const Vec3f& hit, // point where to start
|
||||
int facet_start, // facet that point belongs to
|
||||
const Vec3f& source, // camera position (mesh coords)
|
||||
const Vec3f& dir, // direction of the ray (mesh coords)
|
||||
float radius, // radius of the cursor
|
||||
FacetSupportType new_state); // enforcer or blocker?
|
||||
|
||||
// Get facets currently in the given state.
|
||||
indexed_triangle_set get_facets(FacetSupportType state) const;
|
||||
|
||||
// Set facet of the mesh to a given state. Only works for original triangles.
|
||||
void set_facet(int facet_idx, FacetSupportType state);
|
||||
|
||||
// Clear everything and make the tree empty.
|
||||
void reset();
|
||||
|
||||
// Remove all unnecessary data.
|
||||
void garbage_collect();
|
||||
|
||||
// Store the division trees in compact form (a long stream of
|
||||
// bits for each triangle of the original mesh).
|
||||
std::map<int, std::vector<bool>> serialize() const;
|
||||
|
||||
// Load serialized data. Assumes that correct mesh is loaded.
|
||||
void deserialize(const std::map<int, std::vector<bool>> data);
|
||||
|
||||
|
||||
protected:
|
||||
// Triangle and info about how it's split.
|
||||
class Triangle {
|
||||
public:
|
||||
// Use TriangleSelector::push_triangle to create a new triangle.
|
||||
// It increments/decrements reference counter on vertices.
|
||||
Triangle(int a, int b, int c)
|
||||
: verts_idxs{a, b, c},
|
||||
state{FacetSupportType(0)},
|
||||
number_of_splits{0},
|
||||
special_side_idx{0},
|
||||
old_number_of_splits{0}
|
||||
{}
|
||||
// Indices into m_vertices.
|
||||
std::array<int, 3> verts_idxs;
|
||||
|
||||
// Is this triangle valid or marked to be removed?
|
||||
bool valid{true};
|
||||
|
||||
// Children triangles.
|
||||
std::array<int, 4> children;
|
||||
|
||||
// Set the division type.
|
||||
void set_division(int sides_to_split, int special_side_idx = -1);
|
||||
|
||||
// Get/set current state.
|
||||
void set_state(FacetSupportType type) { assert(! is_split()); state = type; }
|
||||
FacetSupportType get_state() const { assert(! is_split()); return state; }
|
||||
|
||||
// Get info on how it's split.
|
||||
bool is_split() const { return number_of_split_sides() != 0; }
|
||||
int number_of_split_sides() const { return number_of_splits; }
|
||||
int special_side() const { assert(is_split()); return special_side_idx; }
|
||||
bool was_split_before() const { return old_number_of_splits != 0; }
|
||||
void forget_history() { old_number_of_splits = 0; }
|
||||
|
||||
private:
|
||||
int number_of_splits;
|
||||
int special_side_idx;
|
||||
FacetSupportType state;
|
||||
|
||||
// How many children were spawned during last split?
|
||||
// Is not reset on remerging the triangle.
|
||||
int old_number_of_splits;
|
||||
};
|
||||
|
||||
struct Vertex {
|
||||
explicit Vertex(const stl_vertex& vert)
|
||||
: v{vert},
|
||||
ref_cnt{0}
|
||||
{}
|
||||
stl_vertex v;
|
||||
int ref_cnt;
|
||||
};
|
||||
|
||||
// Lists of vertices and triangles, both original and new
|
||||
std::vector<Vertex> m_vertices;
|
||||
std::vector<Triangle> m_triangles;
|
||||
const TriangleMesh* m_mesh;
|
||||
|
||||
// Number of invalid triangles (to trigger garbage collection).
|
||||
int m_invalid_triangles;
|
||||
|
||||
// Limiting length of triangle side (squared).
|
||||
float m_edge_limit_sqr = 1.f;
|
||||
|
||||
// Number of original vertices and triangles.
|
||||
int m_orig_size_vertices = 0;
|
||||
int m_orig_size_indices = 0;
|
||||
|
||||
// Cache for cursor position, radius and direction.
|
||||
struct Cursor {
|
||||
Vec3f center;
|
||||
Vec3f source;
|
||||
Vec3f dir;
|
||||
float radius_sqr;
|
||||
};
|
||||
|
||||
Cursor m_cursor;
|
||||
float m_old_cursor_radius;
|
||||
|
||||
// Private functions:
|
||||
bool select_triangle(int facet_idx, FacetSupportType type,
|
||||
bool recursive_call = false);
|
||||
bool is_point_inside_cursor(const Vec3f& point) const;
|
||||
int vertices_inside(int facet_idx) const;
|
||||
bool faces_camera(int facet) const;
|
||||
void undivide_triangle(int facet_idx);
|
||||
void split_triangle(int facet_idx);
|
||||
void remove_useless_children(int facet_idx); // No hidden meaning. Triangles are meant.
|
||||
bool is_pointer_in_triangle(int facet_idx) const;
|
||||
bool is_edge_inside_cursor(int facet_idx) const;
|
||||
void push_triangle(int a, int b, int c);
|
||||
void perform_split(int facet_idx, FacetSupportType old_state);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // libslic3r_TriangleSelector_hpp_
|
@ -5,6 +5,7 @@
|
||||
|
||||
// this needs to be included early for MSVC (listing it in Build.PL is not enough)
|
||||
#include <memory>
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
#include <ostream>
|
||||
#include <iostream>
|
||||
|
@ -61,10 +61,6 @@ set(SLIC3R_GUI_SOURCES
|
||||
GUI/GLToolbar.cpp
|
||||
GUI/Preferences.cpp
|
||||
GUI/Preferences.hpp
|
||||
GUI/Preset.cpp
|
||||
GUI/Preset.hpp
|
||||
GUI/PresetBundle.cpp
|
||||
GUI/PresetBundle.hpp
|
||||
GUI/PresetHints.cpp
|
||||
GUI/PresetHints.hpp
|
||||
GUI/GUI.cpp
|
||||
@ -81,6 +77,10 @@ set(SLIC3R_GUI_SOURCES
|
||||
GUI/MainFrame.hpp
|
||||
GUI/Plater.cpp
|
||||
GUI/Plater.hpp
|
||||
GUI/PresetComboBoxes.hpp
|
||||
GUI/PresetComboBoxes.cpp
|
||||
GUI/PhysicalPrinterDialog.hpp
|
||||
GUI/PhysicalPrinterDialog.cpp
|
||||
GUI/GUI_ObjectList.cpp
|
||||
GUI/GUI_ObjectList.hpp
|
||||
GUI/GUI_ObjectManipulation.cpp
|
||||
@ -163,6 +163,8 @@ set(SLIC3R_GUI_SOURCES
|
||||
GUI/InstanceCheck.hpp
|
||||
GUI/Search.cpp
|
||||
GUI/Search.hpp
|
||||
GUI/NotificationManager.cpp
|
||||
GUI/NotificationManager.hpp
|
||||
Utils/Http.cpp
|
||||
Utils/Http.hpp
|
||||
Utils/FixModelByWin10.cpp
|
||||
|
@ -1,6 +1,5 @@
|
||||
#include "Snapshot.hpp"
|
||||
#include "../GUI/AppConfig.hpp"
|
||||
#include "../GUI/PresetBundle.hpp"
|
||||
|
||||
#include <time.h>
|
||||
|
||||
@ -11,7 +10,7 @@
|
||||
#include <boost/property_tree/ptree_fwd.hpp>
|
||||
#include <boost/filesystem/operations.hpp>
|
||||
|
||||
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "libslic3r/Time.hpp"
|
||||
#include "libslic3r/Config.hpp"
|
||||
|
@ -7,7 +7,7 @@
|
||||
#include "libslic3r/BoundingBox.hpp"
|
||||
|
||||
#include "GUI_App.hpp"
|
||||
#include "PresetBundle.hpp"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "GLCanvas3D.hpp"
|
||||
|
||||
#include <GL/glew.h>
|
||||
|
@ -89,11 +89,13 @@ void BackgroundSlicingProcess::process_fff()
|
||||
{
|
||||
assert(m_print == m_fff_print);
|
||||
m_print->process();
|
||||
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_slicing_completed_id));
|
||||
wxCommandEvent evt(m_event_slicing_completed_id);
|
||||
evt.SetInt((int)(m_fff_print->step_state_with_timestamp(PrintStep::psBrim).timestamp));
|
||||
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, evt.Clone());
|
||||
m_fff_print->export_gcode(m_temp_output_path, m_gcode_preview_data, m_thumbnail_cb);
|
||||
|
||||
if (this->set_step_started(bspsGCodeFinalize)) {
|
||||
if (! m_export_path.empty()) {
|
||||
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id));
|
||||
//FIXME localize the messages
|
||||
// Perform the final post-processing of the export path by applying the print statistics over the file name.
|
||||
std::string export_path = m_fff_print->print_statistics().finalize_output_path(m_export_path);
|
||||
@ -124,6 +126,7 @@ void BackgroundSlicingProcess::process_fff()
|
||||
run_post_process_scripts(export_path, m_fff_print->config());
|
||||
m_print->set_status(100, (boost::format(_utf8(L("G-code file exported to %1%"))) % export_path).str());
|
||||
} else if (! m_upload_job.empty()) {
|
||||
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id));
|
||||
prepare_upload();
|
||||
} else {
|
||||
m_print->set_status(100, _utf8(L("Slicing complete")));
|
||||
@ -149,6 +152,8 @@ void BackgroundSlicingProcess::process_sla()
|
||||
m_print->process();
|
||||
if (this->set_step_started(bspsGCodeFinalize)) {
|
||||
if (! m_export_path.empty()) {
|
||||
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id));
|
||||
|
||||
const std::string export_path = m_sla_print->print_statistics().finalize_output_path(m_export_path);
|
||||
|
||||
Zipper zipper(export_path);
|
||||
@ -170,6 +175,7 @@ void BackgroundSlicingProcess::process_sla()
|
||||
|
||||
m_print->set_status(100, (boost::format(_utf8(L("Masked SLA file exported to %1%"))) % export_path).str());
|
||||
} else if (! m_upload_job.empty()) {
|
||||
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id));
|
||||
prepare_upload();
|
||||
} else {
|
||||
m_print->set_status(100, _utf8(L("Slicing complete")));
|
||||
|
@ -60,6 +60,10 @@ public:
|
||||
// The following wxCommandEvent will be sent to the UI thread / Plater window, when the G-code export is finished.
|
||||
// The wxCommandEvent is sent to the UI thread asynchronously without waiting for the event to be processed.
|
||||
void set_finished_event(int event_id) { m_event_finished_id = event_id; }
|
||||
// The following wxCommandEvent will be sent to the UI thread / Plater window, when the G-code is being exported to
|
||||
// specified path or uploaded.
|
||||
// The wxCommandEvent is sent to the UI thread asynchronously without waiting for the event to be processed.
|
||||
void set_export_began_event(int event_id) { m_event_export_began_id = event_id; }
|
||||
|
||||
// Activate either m_fff_print or m_sla_print.
|
||||
// Return true if changed.
|
||||
@ -190,6 +194,9 @@ private:
|
||||
int m_event_slicing_completed_id = 0;
|
||||
// wxWidgets command ID to be sent to the plater to inform that the task finished.
|
||||
int m_event_finished_id = 0;
|
||||
// wxWidgets command ID to be sent to the plater to inform that the G-code is being exported.
|
||||
int m_event_export_began_id = 0;
|
||||
|
||||
};
|
||||
|
||||
}; // namespace Slic3r
|
||||
|
@ -2,7 +2,7 @@
|
||||
#include "ConfigManipulation.hpp"
|
||||
#include "I18N.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "PresetBundle.hpp"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
|
||||
#include <wx/msgdlg.h>
|
||||
|
||||
@ -353,6 +353,7 @@ void ConfigManipulation::toggle_print_sla_options(DynamicPrintConfig* config)
|
||||
toggle_field("support_head_penetration", supports_en);
|
||||
toggle_field("support_head_width", supports_en);
|
||||
toggle_field("support_pillar_diameter", supports_en);
|
||||
toggle_field("support_small_pillar_diameter_percent", supports_en);
|
||||
toggle_field("support_max_bridges_on_pillar", supports_en);
|
||||
toggle_field("support_pillar_connection_mode", supports_en);
|
||||
toggle_field("support_buildplate_only", supports_en);
|
||||
|
@ -20,9 +20,9 @@
|
||||
#include <wx/radiobut.h>
|
||||
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "slic3r/Utils/PresetUpdater.hpp"
|
||||
#include "AppConfig.hpp"
|
||||
#include "PresetBundle.hpp"
|
||||
#include "BedShapeDialog.hpp"
|
||||
#include "GUI.hpp"
|
||||
#include "wxExtensions.hpp"
|
||||
|
@ -295,6 +295,7 @@ void Field::msw_rescale(bool rescale_sidetext)
|
||||
{
|
||||
m_Undo_to_sys_btn->msw_rescale();
|
||||
m_Undo_btn->msw_rescale();
|
||||
m_blinking_bmp->msw_rescale();
|
||||
|
||||
// update em_unit value
|
||||
m_em_unit = em_unit(m_parent);
|
||||
@ -1079,6 +1080,8 @@ boost::any& Choice::get_value()
|
||||
m_value = static_cast<SLADisplayOrientation>(ret_enum);
|
||||
else if (m_opt_id.compare("support_pillar_connection_mode") == 0)
|
||||
m_value = static_cast<SLAPillarConnectionMode>(ret_enum);
|
||||
else if (m_opt_id == "authorization_type")
|
||||
m_value = static_cast<AuthorizationType>(ret_enum);
|
||||
}
|
||||
else if (m_opt.gui_type == "f_enum_open") {
|
||||
const int ret_enum = field->GetSelection();
|
||||
|
@ -151,6 +151,8 @@ public:
|
||||
virtual wxSizer* getSizer() { return nullptr; }
|
||||
virtual wxWindow* getWindow() { return nullptr; }
|
||||
|
||||
wxStaticText* getLabel() { return m_Label; }
|
||||
|
||||
bool is_matched(const std::string& string, const std::string& pattern);
|
||||
void get_value_by_opt_type(wxString& str, const bool check_value = true);
|
||||
|
||||
|
@ -13,11 +13,11 @@
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include "libslic3r/Technologies.hpp"
|
||||
#include "libslic3r/Tesselate.hpp"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "slic3r/GUI/3DScene.hpp"
|
||||
#include "slic3r/GUI/BackgroundSlicingProcess.hpp"
|
||||
#include "slic3r/GUI/GLShader.hpp"
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
#include "slic3r/GUI/PresetBundle.hpp"
|
||||
#include "slic3r/GUI/Tab.hpp"
|
||||
#include "slic3r/GUI/GUI_Preview.hpp"
|
||||
#include "slic3r/GUI/OpenGLManager.hpp"
|
||||
@ -31,6 +31,7 @@
|
||||
#include "GUI_ObjectManipulation.hpp"
|
||||
#include "Mouse3DController.hpp"
|
||||
#include "I18N.hpp"
|
||||
#include "NotificationManager.hpp"
|
||||
|
||||
#if ENABLE_RETINA_GL
|
||||
#include "slic3r/Utils/RetinaHelper.hpp"
|
||||
@ -225,56 +226,44 @@ void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) const
|
||||
static const ImVec4 ORANGE(1.0f, 0.49f, 0.22f, 1.0f);
|
||||
|
||||
const Size& cnv_size = canvas.get_canvas_size();
|
||||
float canvas_w = (float)cnv_size.get_width();
|
||||
float canvas_h = (float)cnv_size.get_height();
|
||||
|
||||
ImGuiWrapper& imgui = *wxGetApp().imgui();
|
||||
imgui.set_next_window_pos(canvas_w - imgui.get_style_scaling() * THICKNESS_BAR_WIDTH, canvas_h, ImGuiCond_Always, 1.0f, 1.0f);
|
||||
imgui.set_next_window_pos(static_cast<float>(cnv_size.get_width()) - imgui.get_style_scaling() * THICKNESS_BAR_WIDTH,
|
||||
static_cast<float>(cnv_size.get_height()), ImGuiCond_Always, 1.0f, 1.0f);
|
||||
|
||||
imgui.begin(_(L("Variable layer height")), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse);
|
||||
imgui.begin(_L("Variable layer height"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse);
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ORANGE);
|
||||
imgui.text(_(L("Left mouse button:")));
|
||||
ImGui::PopStyleColor();
|
||||
imgui.text_colored(ORANGE, _L("Left mouse button:"));
|
||||
ImGui::SameLine();
|
||||
imgui.text(_(L("Add detail")));
|
||||
imgui.text(_L("Add detail"));
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ORANGE);
|
||||
imgui.text(_(L("Right mouse button:")));
|
||||
ImGui::PopStyleColor();
|
||||
imgui.text_colored(ORANGE, _L("Right mouse button:"));
|
||||
ImGui::SameLine();
|
||||
imgui.text(_(L("Remove detail")));
|
||||
imgui.text(_L("Remove detail"));
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ORANGE);
|
||||
imgui.text(_(L("Shift + Left mouse button:")));
|
||||
ImGui::PopStyleColor();
|
||||
imgui.text_colored(ORANGE, _L("Shift + Left mouse button:"));
|
||||
ImGui::SameLine();
|
||||
imgui.text(_(L("Reset to base")));
|
||||
imgui.text(_L("Reset to base"));
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ORANGE);
|
||||
imgui.text(_(L("Shift + Right mouse button:")));
|
||||
ImGui::PopStyleColor();
|
||||
imgui.text_colored(ORANGE, _L("Shift + Right mouse button:"));
|
||||
ImGui::SameLine();
|
||||
imgui.text(_(L("Smoothing")));
|
||||
imgui.text(_L("Smoothing"));
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ORANGE);
|
||||
imgui.text(_(L("Mouse wheel:")));
|
||||
ImGui::PopStyleColor();
|
||||
imgui.text_colored(ORANGE, _L("Mouse wheel:"));
|
||||
ImGui::SameLine();
|
||||
imgui.text(_(L("Increase/decrease edit area")));
|
||||
imgui.text(_L("Increase/decrease edit area"));
|
||||
|
||||
ImGui::Separator();
|
||||
if (imgui.button(_(L("Adaptive"))))
|
||||
if (imgui.button(_L("Adaptive")))
|
||||
wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), Event<float>(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, m_adaptive_quality));
|
||||
|
||||
ImGui::SameLine();
|
||||
float text_align = ImGui::GetCursorPosX();
|
||||
ImGui::AlignTextToFramePadding();
|
||||
imgui.text(_(L("Quality / Speed")));
|
||||
if (ImGui::IsItemHovered())
|
||||
{
|
||||
imgui.text(_L("Quality / Speed"));
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::TextUnformatted(_(L("Higher print quality versus higher print speed.")).ToUTF8());
|
||||
ImGui::TextUnformatted(_L("Higher print quality versus higher print speed.").ToUTF8());
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
|
||||
@ -285,13 +274,13 @@ void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) const
|
||||
ImGui::SliderFloat("", &m_adaptive_quality, 0.0f, 1.f, "%.2f");
|
||||
|
||||
ImGui::Separator();
|
||||
if (imgui.button(_(L("Smooth"))))
|
||||
if (imgui.button(_L("Smooth")))
|
||||
wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), HeightProfileSmoothEvent(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, m_smooth_params));
|
||||
|
||||
ImGui::SameLine();
|
||||
ImGui::SetCursorPosX(text_align);
|
||||
ImGui::AlignTextToFramePadding();
|
||||
imgui.text(_(L("Radius")));
|
||||
imgui.text(_L("Radius"));
|
||||
ImGui::SameLine();
|
||||
ImGui::SetCursorPosX(widget_align);
|
||||
ImGui::PushItemWidth(imgui.get_style_scaling() * 120.0f);
|
||||
@ -301,7 +290,7 @@ void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) const
|
||||
|
||||
ImGui::SetCursorPosX(text_align);
|
||||
ImGui::AlignTextToFramePadding();
|
||||
imgui.text(_(L("Keep min")));
|
||||
imgui.text(_L("Keep min"));
|
||||
ImGui::SameLine();
|
||||
if (ImGui::GetCursorPosX() < widget_align) // because of line lenght after localization
|
||||
ImGui::SetCursorPosX(widget_align);
|
||||
@ -310,7 +299,7 @@ void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) const
|
||||
imgui.checkbox("##2", m_smooth_params.keep_min);
|
||||
|
||||
ImGui::Separator();
|
||||
if (imgui.button(_(L("Reset"))))
|
||||
if (imgui.button(_L("Reset")))
|
||||
wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), SimpleEvent(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE));
|
||||
|
||||
imgui.end();
|
||||
@ -663,19 +652,45 @@ void GLCanvas3D::WarningTexture::activate(WarningTexture::Warning warning, bool
|
||||
|
||||
m_warnings.emplace_back(warning);
|
||||
std::sort(m_warnings.begin(), m_warnings.end());
|
||||
|
||||
std::string text;
|
||||
switch (warning) {
|
||||
case ObjectOutside: text = L("An object outside the print area was detected."); break;
|
||||
case ToolpathOutside: text = L("A toolpath outside the print area was detected."); break;
|
||||
case SlaSupportsOutside: text = L("SLA supports outside the print area were detected."); break;
|
||||
case SomethingNotShown: text = L("Some objects are not visible."); break;
|
||||
case ObjectClashed: wxGetApp().plater()->get_notification_manager()->push_plater_error_notification(L("An object outside the print area was detected.\n"
|
||||
"Resolve the current problem to continue slicing."),
|
||||
*(wxGetApp().plater()->get_current_canvas3D()));
|
||||
break;
|
||||
}
|
||||
if (!text.empty())
|
||||
wxGetApp().plater()->get_notification_manager()->push_plater_warning_notification(text, *(wxGetApp().plater()->get_current_canvas3D()));
|
||||
}
|
||||
else {
|
||||
if (it == m_warnings.end()) // deactivating something that is not active is an easy task
|
||||
return;
|
||||
|
||||
m_warnings.erase(it);
|
||||
if (m_warnings.empty()) { // nothing remains to be shown
|
||||
|
||||
std::string text;
|
||||
switch (warning) {
|
||||
case ObjectOutside: text = L("An object outside the print area was detected."); break;
|
||||
case ToolpathOutside: text = L("A toolpath outside the print area was detected."); break;
|
||||
case SlaSupportsOutside: text = L("SLA supports outside the print area were detected."); break;
|
||||
case SomethingNotShown: text = L("Some objects are not visibl.e"); break;
|
||||
case ObjectClashed: wxGetApp().plater()->get_notification_manager()->close_plater_error_notification(); break;
|
||||
}
|
||||
if (!text.empty())
|
||||
wxGetApp().plater()->get_notification_manager()->close_plater_warning_notification(text);
|
||||
|
||||
/*if (m_warnings.empty()) { // nothing remains to be shown
|
||||
reset();
|
||||
m_msg_text = "";// save information for rescaling
|
||||
return;
|
||||
}
|
||||
}*/
|
||||
}
|
||||
|
||||
/*
|
||||
// Look at the end of our vector and generate proper texture.
|
||||
std::string text;
|
||||
bool red_colored = false;
|
||||
@ -697,6 +712,7 @@ void GLCanvas3D::WarningTexture::activate(WarningTexture::Warning warning, bool
|
||||
// save information for rescaling
|
||||
m_msg_text = text;
|
||||
m_is_colored_red = red_colored;
|
||||
*/
|
||||
}
|
||||
|
||||
|
||||
@ -1430,8 +1446,7 @@ void GLCanvas3D::Tooltip::render(const Vec2d& mouse_position, GLCanvas3D& canvas
|
||||
#if ENABLE_SLOPE_RENDERING
|
||||
void GLCanvas3D::Slope::render() const
|
||||
{
|
||||
if (m_dialog_shown)
|
||||
{
|
||||
if (m_dialog_shown) {
|
||||
const std::array<float, 2>& z_range = m_volumes.get_slope_z_range();
|
||||
std::array<float, 2> angle_range = { Geometry::rad2deg(::acos(z_range[0])) - 90.0f, Geometry::rad2deg(::acos(z_range[1])) - 90.0f };
|
||||
bool modified = false;
|
||||
@ -1439,9 +1454,9 @@ void GLCanvas3D::Slope::render() const
|
||||
ImGuiWrapper& imgui = *wxGetApp().imgui();
|
||||
const Size& cnv_size = m_canvas.get_canvas_size();
|
||||
imgui.set_next_window_pos((float)cnv_size.get_width(), (float)cnv_size.get_height(), ImGuiCond_Always, 1.0f, 1.0f);
|
||||
imgui.begin(_(L("Slope visualization")), nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
|
||||
imgui.begin(_L("Slope visualization"), nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
|
||||
|
||||
imgui.text(_(L("Facets' slope range (degrees)")) + ":");
|
||||
imgui.text(_L("Facets' slope range (degrees)") + ":");
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.75f, 0.0f, 0.0f, 0.5f));
|
||||
ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(1.0f, 0.0f, 0.0f, 0.5f));
|
||||
@ -1453,8 +1468,7 @@ void GLCanvas3D::Slope::render() const
|
||||
float slope_bound = 90.f - angle_range[1];
|
||||
bool mod = ImGui::SliderFloat("##red", &slope_bound, 0.0f, 90.0f, "%.1f");
|
||||
angle_range[1] = 90.f - slope_bound;
|
||||
if (mod)
|
||||
{
|
||||
if (mod) {
|
||||
modified = true;
|
||||
if (angle_range[0] > angle_range[1])
|
||||
angle_range[0] = angle_range[1];
|
||||
@ -1462,15 +1476,14 @@ void GLCanvas3D::Slope::render() const
|
||||
|
||||
ImGui::PopStyleColor(4);
|
||||
ImGui::PushStyleColor(ImGuiCol_FrameBg, ImVec4(0.75f, 0.75f, 0.0f, 0.5f));
|
||||
ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(1.0f, 1.0f, 0.0f, 0.5f));
|
||||
ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(0.85f, 0.85f, 0.0f, 0.5f));
|
||||
ImGui::PushStyleColor(ImGuiCol_SliderGrab, ImVec4(0.25f, 0.25f, 0.0f, 1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(1.0f, 1.0f, 0.0f, 0.5f));
|
||||
ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(0.85f, 0.85f, 0.0f, 0.5f));
|
||||
ImGui::PushStyleColor(ImGuiCol_SliderGrab, ImVec4(0.25f, 0.25f, 0.0f, 1.0f));
|
||||
|
||||
slope_bound = 90.f - angle_range[0];
|
||||
mod = ImGui::SliderFloat("##yellow", &slope_bound, 0.0f, 90.0f, "%.1f");
|
||||
angle_range[0] = 90.f - slope_bound;
|
||||
if (mod)
|
||||
{
|
||||
if (mod) {
|
||||
modified = true;
|
||||
if (angle_range[1] < angle_range[0])
|
||||
angle_range[1] = angle_range[0];
|
||||
@ -2089,6 +2102,8 @@ void GLCanvas3D::render()
|
||||
|
||||
std::string tooltip;
|
||||
|
||||
|
||||
|
||||
// Negative coordinate means out of the window, likely because the window was deactivated.
|
||||
// In that case the tooltip should be hidden.
|
||||
if (m_mouse.position.x() >= 0. && m_mouse.position.y() >= 0.)
|
||||
@ -2118,6 +2133,8 @@ void GLCanvas3D::render()
|
||||
m_tooltip.render(m_mouse.position, *this);
|
||||
|
||||
wxGetApp().plater()->get_mouse3d_controller().render_settings_dialog(*this);
|
||||
|
||||
wxGetApp().plater()->get_notification_manager()->render_notifications(*this);
|
||||
|
||||
wxGetApp().imgui()->render();
|
||||
|
||||
@ -3300,6 +3317,11 @@ void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt)
|
||||
if (evt.MiddleIsDown())
|
||||
return;
|
||||
|
||||
if (wxGetApp().imgui()->update_mouse_data(evt)) {
|
||||
m_dirty = true;
|
||||
return;
|
||||
}
|
||||
|
||||
#if ENABLE_RETINA_GL
|
||||
const float scale = m_retina_helper->get_scale_factor();
|
||||
evt.SetX(evt.GetX() * scale);
|
||||
@ -3428,6 +3450,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||
#ifdef SLIC3R_DEBUG_MOUSE_EVENTS
|
||||
printf((format_mouse_event_debug_message(evt) + " - Consumed by ImGUI\n").c_str());
|
||||
#endif /* SLIC3R_DEBUG_MOUSE_EVENTS */
|
||||
m_dirty = true;
|
||||
// do not return if dragging or tooltip not empty to allow for tooltip update
|
||||
if (!m_mouse.dragging && m_tooltip.is_empty())
|
||||
return;
|
||||
@ -3821,7 +3844,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||
m_gizmos.reset_all_states();
|
||||
|
||||
// Only refresh if picking is enabled, in that case the objects may get highlighted if the mouse cursor hovers over.
|
||||
if (m_picking_enabled)
|
||||
//if (m_picking_enabled)
|
||||
m_dirty = true;
|
||||
}
|
||||
else
|
||||
|
@ -194,6 +194,8 @@ void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt
|
||||
config.set_key_value(opt_key, new ConfigOptionEnum<SLADisplayOrientation>(boost::any_cast<SLADisplayOrientation>(value)));
|
||||
else if(opt_key.compare("support_pillar_connection_mode") == 0)
|
||||
config.set_key_value(opt_key, new ConfigOptionEnum<SLAPillarConnectionMode>(boost::any_cast<SLAPillarConnectionMode>(value)));
|
||||
else if(opt_key == "authorization_type")
|
||||
config.set_key_value(opt_key, new ConfigOptionEnum<AuthorizationType>(boost::any_cast<AuthorizationType>(value)));
|
||||
}
|
||||
break;
|
||||
case coPoints:{
|
||||
|
@ -28,14 +28,17 @@
|
||||
#include <wx/log.h>
|
||||
#include <wx/intl.h>
|
||||
|
||||
#include <wx/dialog.h>
|
||||
#include <wx/textctrl.h>
|
||||
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/I18N.hpp"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
|
||||
#include "GUI.hpp"
|
||||
#include "GUI_Utils.hpp"
|
||||
#include "AppConfig.hpp"
|
||||
#include "PresetBundle.hpp"
|
||||
#include "3DScene.hpp"
|
||||
#include "MainFrame.hpp"
|
||||
#include "Plater.hpp"
|
||||
@ -54,6 +57,7 @@
|
||||
#include "Mouse3DController.hpp"
|
||||
#include "RemovableDriveManager.hpp"
|
||||
#include "InstanceCheck.hpp"
|
||||
#include "NotificationManager.hpp"
|
||||
|
||||
#ifdef __WXMSW__
|
||||
#include <dbt.h>
|
||||
@ -384,7 +388,7 @@ bool GUI_App::on_init_inner()
|
||||
// supplied as argument to --datadir; in that case we should still run the wizard
|
||||
preset_bundle->setup_directories();
|
||||
|
||||
#ifdef __WXMSW__
|
||||
#ifdef __WXMSW__
|
||||
associate_3mf_files();
|
||||
#endif // __WXMSW__
|
||||
|
||||
@ -392,6 +396,11 @@ bool GUI_App::on_init_inner()
|
||||
Bind(EVT_SLIC3R_VERSION_ONLINE, [this](const wxCommandEvent &evt) {
|
||||
app_config->set("version_online", into_u8(evt.GetString()));
|
||||
app_config->save();
|
||||
if(this->plater_ != nullptr) {
|
||||
if (*Semver::parse(SLIC3R_VERSION) < * Semver::parse(into_u8(evt.GetString()))) {
|
||||
this->plater_->get_notification_manager()->push_notification(NotificationType::NewAppAviable, *(this->plater_->get_current_canvas3D()));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// initialize label colors and fonts
|
||||
@ -629,6 +638,27 @@ void GUI_App::set_auto_toolbar_icon_scale(float scale) const
|
||||
app_config->set("auto_toolbar_size", val);
|
||||
}
|
||||
|
||||
// check user printer_presets for the containing information about "Print Host upload"
|
||||
void GUI_App::check_printer_presets()
|
||||
{
|
||||
std::vector<std::string> preset_names = PhysicalPrinter::presets_with_print_host_information(preset_bundle->printers);
|
||||
if (preset_names.empty())
|
||||
return;
|
||||
|
||||
wxString msg_text = _L("You have next presets with saved options for \"Print Host upload\"") + ":";
|
||||
for (const std::string& preset_name : preset_names)
|
||||
msg_text += "\n \"" + from_u8(preset_name) + "\",";
|
||||
msg_text.RemoveLast();
|
||||
msg_text += "\n\n" + _L("But from this version of PrusaSlicer we don't show/use this information in Printer Settings.\n"
|
||||
"Now, this information will be exposed in physical printers settings.") + "\n\n" +
|
||||
_L("By default new Printer devices will be named as \"Printer N\" during its creation.\n"
|
||||
"Note: This name can be changed later from the physical printers settings");
|
||||
|
||||
wxMessageDialog(nullptr, msg_text, _L("Information"), wxOK | wxICON_INFORMATION).ShowModal();
|
||||
|
||||
preset_bundle->physical_printers.load_printers_from_presets(preset_bundle->printers);
|
||||
}
|
||||
|
||||
void GUI_App::recreate_GUI(const wxString& msg_name)
|
||||
{
|
||||
mainframe->shutdown();
|
||||
@ -937,7 +967,7 @@ bool GUI_App::load_language(wxString language, bool initial)
|
||||
m_imgui->set_language(into_u8(language_info->CanonicalName));
|
||||
//FIXME This is a temporary workaround, the correct solution is to switch to "C" locale during file import / export only.
|
||||
wxSetlocale(LC_NUMERIC, "C");
|
||||
Preset::update_suffix_modified();
|
||||
Preset::update_suffix_modified((" (" + _L("modified") + ")").ToUTF8().data());
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1061,34 +1091,21 @@ void GUI_App::add_config_menu(wxMenuBar *menu)
|
||||
break;
|
||||
case ConfigMenuPreferences:
|
||||
{
|
||||
#if ENABLE_LAYOUT_NO_RESTART
|
||||
bool app_layout_changed = false;
|
||||
#else
|
||||
bool recreate_app = false;
|
||||
#endif // ENABLE_LAYOUT_NO_RESTART
|
||||
{
|
||||
// the dialog needs to be destroyed before the call to recreate_GUI()
|
||||
// or sometimes the application crashes into wxDialogBase() destructor
|
||||
// so we put it into an inner scope
|
||||
PreferencesDialog dlg(mainframe);
|
||||
dlg.ShowModal();
|
||||
#if ENABLE_LAYOUT_NO_RESTART
|
||||
app_layout_changed = dlg.settings_layout_changed();
|
||||
#else
|
||||
recreate_app = dlg.settings_layout_changed();
|
||||
#endif // ENABLE_LAYOUT_NO_RESTART
|
||||
}
|
||||
#if ENABLE_LAYOUT_NO_RESTART
|
||||
if (app_layout_changed) {
|
||||
mainframe->GetSizer()->Hide((size_t)0);
|
||||
mainframe->update_layout();
|
||||
mainframe->select_tab(0);
|
||||
mainframe->GetSizer()->Show((size_t)0);
|
||||
}
|
||||
#else
|
||||
if (recreate_app)
|
||||
recreate_GUI(_L("Changing of the settings layout") + dots);
|
||||
#endif // ENABLE_LAYOUT_NO_RESTART
|
||||
break;
|
||||
}
|
||||
case ConfigMenuLanguage:
|
||||
@ -1171,6 +1188,10 @@ bool GUI_App::checked_tab(Tab* tab)
|
||||
// Update UI / Tabs to reflect changes in the currently loaded presets
|
||||
void GUI_App::load_current_presets()
|
||||
{
|
||||
// check printer_presets for the containing information about "Print Host upload"
|
||||
// and create physical printer from it, if any exists
|
||||
check_printer_presets();
|
||||
|
||||
PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology();
|
||||
this->plater()->set_printer_technology(printer_technology);
|
||||
for (Tab *tab : tabs_list)
|
||||
@ -1452,7 +1473,7 @@ void GUI_App::check_updates(const bool verbose)
|
||||
|
||||
PresetUpdater::UpdateResult updater_result;
|
||||
try {
|
||||
updater_result = preset_updater->config_update(app_config->orig_version());
|
||||
updater_result = preset_updater->config_update(app_config->orig_version(), verbose);
|
||||
if (updater_result == PresetUpdater::R_INCOMPAT_EXIT) {
|
||||
mainframe->Close();
|
||||
}
|
||||
|
@ -3,10 +3,10 @@
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include "Preset.hpp"
|
||||
#include "ImGuiWrapper.hpp"
|
||||
#include "ConfigWizard.hpp"
|
||||
#include "OpenGLManager.hpp"
|
||||
#include "libslic3r/Preset.hpp"
|
||||
|
||||
#include <wx/app.h>
|
||||
#include <wx/colour.h>
|
||||
@ -150,6 +150,7 @@ public:
|
||||
wxSize get_min_size() const;
|
||||
float toolbar_icon_scale(const bool is_limited = false) const;
|
||||
void set_auto_toolbar_icon_scale(float scale) const;
|
||||
void check_printer_presets();
|
||||
|
||||
void recreate_GUI(const wxString& message);
|
||||
void system_info();
|
||||
@ -194,12 +195,15 @@ public:
|
||||
Plater* plater();
|
||||
Model& model();
|
||||
|
||||
|
||||
AppConfig* app_config{ nullptr };
|
||||
PresetBundle* preset_bundle{ nullptr };
|
||||
PresetUpdater* preset_updater{ nullptr };
|
||||
MainFrame* mainframe{ nullptr };
|
||||
Plater* plater_{ nullptr };
|
||||
|
||||
PresetUpdater* get_preset_updater() { return preset_updater; }
|
||||
|
||||
wxNotebook* tab_panel() const ;
|
||||
int extruders_cnt() const;
|
||||
int extruders_edited_cnt() const;
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
#include "OptionsGroup.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "PresetBundle.hpp"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "GLCanvas3D.hpp"
|
||||
#include "Plater.hpp"
|
||||
|
@ -1,4 +1,5 @@
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "GUI_ObjectList.hpp"
|
||||
#include "GUI_ObjectManipulation.hpp"
|
||||
#include "GUI_ObjectLayers.hpp"
|
||||
@ -7,7 +8,6 @@
|
||||
#include "Plater.hpp"
|
||||
|
||||
#include "OptionsGroup.hpp"
|
||||
#include "PresetBundle.hpp"
|
||||
#include "Tab.hpp"
|
||||
#include "wxExtensions.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
@ -88,13 +88,10 @@ ObjectList::ObjectList(wxWindow* parent) :
|
||||
{
|
||||
// Fill CATEGORY_ICON
|
||||
{
|
||||
// Note: `this` isn't passed to create_scaled_bitmap() here because of bugs in the widget,
|
||||
// see note in PresetBundle::load_compatible_bitmaps()
|
||||
|
||||
// ptFFF
|
||||
CATEGORY_ICON[L("Layers and Perimeters")] = create_scaled_bitmap("layers");
|
||||
CATEGORY_ICON[L("Infill")] = create_scaled_bitmap("infill");
|
||||
CATEGORY_ICON[L("Ironing")] = create_scaled_bitmap("infill"); // FIXME when the ironing icon is available
|
||||
CATEGORY_ICON[L("Ironing")] = create_scaled_bitmap("ironing");
|
||||
CATEGORY_ICON[L("Support material")] = create_scaled_bitmap("support");
|
||||
CATEGORY_ICON[L("Speed")] = create_scaled_bitmap("time");
|
||||
CATEGORY_ICON[L("Extruders")] = create_scaled_bitmap("funnel");
|
||||
@ -645,7 +642,7 @@ void ObjectList::msw_rescale_icons()
|
||||
// ptFFF
|
||||
CATEGORY_ICON[L("Layers and Perimeters")] = create_scaled_bitmap("layers");
|
||||
CATEGORY_ICON[L("Infill")] = create_scaled_bitmap("infill");
|
||||
CATEGORY_ICON[L("Ironing")] = create_scaled_bitmap("infill"); // FIXME when the ironing icon is available
|
||||
CATEGORY_ICON[L("Ironing")] = create_scaled_bitmap("ironing");
|
||||
CATEGORY_ICON[L("Support material")] = create_scaled_bitmap("support");
|
||||
CATEGORY_ICON[L("Speed")] = create_scaled_bitmap("time");
|
||||
CATEGORY_ICON[L("Extruders")] = create_scaled_bitmap("funnel");
|
||||
|
@ -6,7 +6,7 @@
|
||||
#include "OptionsGroup.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "wxExtensions.hpp"
|
||||
#include "PresetBundle.hpp"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/Geometry.hpp"
|
||||
#include "Selection.hpp"
|
||||
|
@ -4,8 +4,8 @@
|
||||
#include "OptionsGroup.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "wxExtensions.hpp"
|
||||
#include "PresetBundle.hpp"
|
||||
#include "Plater.hpp"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
@ -8,7 +8,7 @@
|
||||
#include "BackgroundSlicingProcess.hpp"
|
||||
#include "OpenGLManager.hpp"
|
||||
#include "GLCanvas3D.hpp"
|
||||
#include "PresetBundle.hpp"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "DoubleSlider.hpp"
|
||||
#include "Plater.hpp"
|
||||
|
||||
|
@ -110,13 +110,8 @@ public:
|
||||
if (!m_can_rescale)
|
||||
return;
|
||||
|
||||
#if ENABLE_LAYOUT_NO_RESTART
|
||||
if (m_force_rescale || is_new_scale_factor())
|
||||
rescale(wxRect());
|
||||
#else
|
||||
if (is_new_scale_factor())
|
||||
rescale(wxRect());
|
||||
#endif // ENABLE_LAYOUT_NO_RESTART
|
||||
});
|
||||
#else
|
||||
this->Bind(EVT_DPI_CHANGED_SLICER, [this](const DpiChangedEvent& evt) {
|
||||
@ -127,13 +122,8 @@ public:
|
||||
if (!m_can_rescale)
|
||||
return;
|
||||
|
||||
#if ENABLE_LAYOUT_NO_RESTART
|
||||
if (m_force_rescale || is_new_scale_factor())
|
||||
rescale(evt.rect);
|
||||
#else
|
||||
if (is_new_scale_factor())
|
||||
rescale(evt.rect);
|
||||
#endif // ENABLE_LAYOUT_NO_RESTART
|
||||
});
|
||||
#endif // wxVERSION_EQUAL_OR_GREATER_THAN
|
||||
|
||||
@ -175,9 +165,7 @@ public:
|
||||
int em_unit() const { return m_em_unit; }
|
||||
// int font_size() const { return m_font_size; }
|
||||
const wxFont& normal_font() const { return m_normal_font; }
|
||||
#if ENABLE_LAYOUT_NO_RESTART
|
||||
void enable_force_rescale() { m_force_rescale = true; }
|
||||
#endif // ENABLE_LAYOUT_NO_RESTART
|
||||
|
||||
protected:
|
||||
virtual void on_dpi_changed(const wxRect &suggested_rect) = 0;
|
||||
@ -191,9 +179,7 @@ private:
|
||||
wxFont m_normal_font;
|
||||
float m_prev_scale_factor;
|
||||
bool m_can_rescale{ true };
|
||||
#if ENABLE_LAYOUT_NO_RESTART
|
||||
bool m_force_rescale{ false };
|
||||
#endif // ENABLE_LAYOUT_NO_RESTART
|
||||
|
||||
int m_new_font_point_size;
|
||||
|
||||
@ -233,17 +219,17 @@ private:
|
||||
{
|
||||
this->Freeze();
|
||||
|
||||
#if ENABLE_LAYOUT_NO_RESTART && wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3)
|
||||
#if wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3)
|
||||
if (m_force_rescale) {
|
||||
#endif // ENABLE_LAYOUT_NO_RESTART
|
||||
#endif // wxVERSION_EQUAL_OR_GREATER_THAN
|
||||
// rescale fonts of all controls
|
||||
scale_controls_fonts(this, m_new_font_point_size);
|
||||
// rescale current window font
|
||||
scale_win_font(this, m_new_font_point_size);
|
||||
#if ENABLE_LAYOUT_NO_RESTART && wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3)
|
||||
#if wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3)
|
||||
m_force_rescale = false;
|
||||
}
|
||||
#endif // ENABLE_LAYOUT_NO_RESTART
|
||||
#endif // wxVERSION_EQUAL_OR_GREATER_THAN
|
||||
|
||||
// set normal application font as a current window font
|
||||
m_normal_font = this->GetFont();
|
||||
|
@ -6,9 +6,9 @@
|
||||
#include <GL/glew.h>
|
||||
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/PresetBundle.hpp"
|
||||
#include "slic3r/GUI/Camera.hpp"
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
|
||||
|
||||
@ -16,7 +16,6 @@
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
static constexpr size_t MaxVertexBuffers = 50;
|
||||
|
||||
GLGizmoFdmSupports::GLGizmoFdmSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
|
||||
: GLGizmoBase(parent, icon_filename, sprite_id)
|
||||
@ -49,7 +48,7 @@ bool GLGizmoFdmSupports::on_init()
|
||||
m_desc["block"] = _L("Block supports");
|
||||
m_desc["remove_caption"] = _L("Shift + Left mouse button") + ": ";
|
||||
m_desc["remove"] = _L("Remove selection");
|
||||
m_desc["remove_all"] = _L("Remove all");
|
||||
m_desc["remove_all"] = _L("Remove all selection");
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -96,6 +95,7 @@ void GLGizmoFdmSupports::on_render() const
|
||||
glsafe(::glEnable(GL_DEPTH_TEST));
|
||||
|
||||
render_triangles(selection);
|
||||
|
||||
m_c->object_clipper()->render_cut();
|
||||
render_cursor_circle();
|
||||
|
||||
@ -145,14 +145,9 @@ void GLGizmoFdmSupports::render_triangles(const Selection& selection) const
|
||||
glsafe(::glPushMatrix());
|
||||
glsafe(::glMultMatrixd(trafo_matrix.data()));
|
||||
|
||||
// Now render both enforcers and blockers.
|
||||
for (int i=0; i<2; ++i) {
|
||||
glsafe(::glColor4f(i ? 1.f : 0.2f, 0.2f, i ? 0.2f : 1.0f, 0.5f));
|
||||
for (const GLIndexedVertexArray& iva : m_ivas[mesh_id][i]) {
|
||||
if (iva.has_VBOs())
|
||||
iva.render();
|
||||
}
|
||||
}
|
||||
if (! m_setting_angle)
|
||||
m_triangle_selectors[mesh_id]->render(m_imgui);
|
||||
|
||||
glsafe(::glPopMatrix());
|
||||
if (is_left_handed)
|
||||
glsafe(::glFrontFace(GL_CCW));
|
||||
@ -209,15 +204,18 @@ void GLGizmoFdmSupports::render_cursor_circle() const
|
||||
|
||||
void GLGizmoFdmSupports::update_model_object() const
|
||||
{
|
||||
bool updated = false;
|
||||
ModelObject* mo = m_c->selection_info()->model_object();
|
||||
int idx = -1;
|
||||
for (ModelVolume* mv : mo->volumes) {
|
||||
++idx;
|
||||
if (! mv->is_model_part())
|
||||
continue;
|
||||
for (int i=0; i<int(m_selected_facets[idx].size()); ++i)
|
||||
mv->m_supported_facets.set_facet(i, m_selected_facets[idx][i]);
|
||||
++idx;
|
||||
updated |= mv->m_supported_facets.set(*m_triangle_selectors[idx].get());
|
||||
}
|
||||
|
||||
if (updated)
|
||||
m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
|
||||
}
|
||||
|
||||
|
||||
@ -226,19 +224,7 @@ void GLGizmoFdmSupports::update_from_model_object()
|
||||
wxBusyCursor wait;
|
||||
|
||||
const ModelObject* mo = m_c->selection_info()->model_object();
|
||||
size_t num_of_volumes = 0;
|
||||
for (const ModelVolume* mv : mo->volumes)
|
||||
if (mv->is_model_part())
|
||||
++num_of_volumes;
|
||||
m_selected_facets.resize(num_of_volumes);
|
||||
|
||||
m_ivas.clear();
|
||||
m_ivas.resize(num_of_volumes);
|
||||
for (size_t i=0; i<num_of_volumes; ++i) {
|
||||
m_ivas[i][0].reserve(MaxVertexBuffers);
|
||||
m_ivas[i][1].reserve(MaxVertexBuffers);
|
||||
}
|
||||
|
||||
m_triangle_selectors.clear();
|
||||
|
||||
int volume_id = -1;
|
||||
for (const ModelVolume* mv : mo->volumes) {
|
||||
@ -250,16 +236,8 @@ void GLGizmoFdmSupports::update_from_model_object()
|
||||
// This mesh does not account for the possible Z up SLA offset.
|
||||
const TriangleMesh* mesh = &mv->mesh();
|
||||
|
||||
m_selected_facets[volume_id].assign(mesh->its.indices.size(), FacetSupportType::NONE);
|
||||
|
||||
// Load current state from ModelVolume.
|
||||
for (FacetSupportType type : {FacetSupportType::ENFORCER, FacetSupportType::BLOCKER}) {
|
||||
const std::vector<int>& list = mv->m_supported_facets.get_facets(type);
|
||||
for (int i : list)
|
||||
m_selected_facets[volume_id][i] = type;
|
||||
}
|
||||
update_vertex_buffers(mesh, volume_id, FacetSupportType::ENFORCER);
|
||||
update_vertex_buffers(mesh, volume_id, FacetSupportType::BLOCKER);
|
||||
m_triangle_selectors.emplace_back(std::make_unique<TriangleSelectorGUI>(*mesh));
|
||||
m_triangle_selectors.back()->deserialize(mv->m_supported_facets.get_data());
|
||||
}
|
||||
}
|
||||
|
||||
@ -315,6 +293,9 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
||||
|| action == SLAGizmoEventType::RightDown
|
||||
|| (action == SLAGizmoEventType::Dragging && m_button_down != Button::None)) {
|
||||
|
||||
if (m_triangle_selectors.empty())
|
||||
return false;
|
||||
|
||||
FacetSupportType new_state = FacetSupportType::NONE;
|
||||
if (! shift_down) {
|
||||
if (action == SLAGizmoEventType::Dragging)
|
||||
@ -403,103 +384,35 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
||||
|| dragging_while_painting;
|
||||
}
|
||||
|
||||
// Now propagate the hits
|
||||
// Find respective mesh id.
|
||||
// FIXME We need a separate TriangleSelector for each volume mesh.
|
||||
mesh_id = -1;
|
||||
const TriangleMesh* mesh = nullptr;
|
||||
//const TriangleMesh* mesh = nullptr;
|
||||
for (const ModelVolume* mv : mo->volumes) {
|
||||
if (! mv->is_model_part())
|
||||
continue;
|
||||
++mesh_id;
|
||||
if (mesh_id == closest_hit_mesh_id) {
|
||||
mesh = &mv->mesh();
|
||||
//mesh = &mv->mesh();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool update_both = false;
|
||||
|
||||
const Transform3d& trafo_matrix = trafo_matrices[mesh_id];
|
||||
|
||||
// Calculate how far can a point be from the line (in mesh coords).
|
||||
// FIXME: The scaling of the mesh can be non-uniform.
|
||||
const Vec3d sf = Geometry::Transformation(trafo_matrix).get_scaling_factor();
|
||||
const float avg_scaling = (sf(0) + sf(1) + sf(2))/3.;
|
||||
const float limit = pow(m_cursor_radius/avg_scaling , 2.f);
|
||||
|
||||
const std::pair<Vec3f, size_t>& hit_and_facet = { closest_hit, closest_facet };
|
||||
const float limit = m_cursor_radius/avg_scaling;
|
||||
|
||||
// Calculate direction from camera to the hit (in mesh coords):
|
||||
Vec3f dir = ((trafo_matrix.inverse() * camera.get_position()).cast<float>() - hit_and_facet.first).normalized();
|
||||
Vec3f camera_pos = (trafo_matrix.inverse() * camera.get_position()).cast<float>();
|
||||
Vec3f dir = (closest_hit - camera_pos).normalized();
|
||||
|
||||
// A lambda to calculate distance from the centerline:
|
||||
auto squared_distance_from_line = [&hit_and_facet, &dir](const Vec3f& point) -> float {
|
||||
Vec3f diff = hit_and_facet.first - point;
|
||||
return (diff - diff.dot(dir) * dir).squaredNorm();
|
||||
};
|
||||
|
||||
// A lambda to determine whether this facet is potentionally visible (still can be obscured)
|
||||
auto faces_camera = [&dir, &mesh](const size_t& facet) -> bool {
|
||||
return (mesh->stl.facet_start[facet].normal.dot(dir) > 0.);
|
||||
};
|
||||
// Now start with the facet the pointer points to and check all adjacent facets.
|
||||
std::vector<size_t> facets_to_select{hit_and_facet.second};
|
||||
std::vector<bool> visited(m_selected_facets[mesh_id].size(), false); // keep track of facets we already processed
|
||||
size_t facet_idx = 0; // index into facets_to_select
|
||||
while (facet_idx < facets_to_select.size()) {
|
||||
size_t facet = facets_to_select[facet_idx];
|
||||
if (! visited[facet]) {
|
||||
// check all three vertices and in case they're close enough,
|
||||
// add neighboring facets to be proccessed later
|
||||
for (size_t i=0; i<3; ++i) {
|
||||
float dist = squared_distance_from_line(
|
||||
mesh->its.vertices[mesh->its.indices[facet](i)]);
|
||||
if (dist < limit) {
|
||||
for (int n=0; n<3; ++n) {
|
||||
if (faces_camera(mesh->stl.neighbors_start[facet].neighbor[n]))
|
||||
facets_to_select.push_back(mesh->stl.neighbors_start[facet].neighbor[n]);
|
||||
}
|
||||
}
|
||||
}
|
||||
visited[facet] = true;
|
||||
}
|
||||
++facet_idx;
|
||||
}
|
||||
|
||||
std::vector<size_t> new_facets;
|
||||
new_facets.reserve(facets_to_select.size());
|
||||
|
||||
// Now just select all facets that passed and remember which
|
||||
// ones have really changed state.
|
||||
for (size_t next_facet : facets_to_select) {
|
||||
FacetSupportType& facet = m_selected_facets[mesh_id][next_facet];
|
||||
|
||||
if (facet != new_state) {
|
||||
if (facet != FacetSupportType::NONE) {
|
||||
// this triangle is currently in the other VBA.
|
||||
// Both VBAs need to be refreshed.
|
||||
update_both = true;
|
||||
}
|
||||
facet = new_state;
|
||||
new_facets.push_back(next_facet);
|
||||
}
|
||||
}
|
||||
|
||||
if (! new_facets.empty()) {
|
||||
if (new_state != FacetSupportType::NONE) {
|
||||
// append triangles into the respective VBA
|
||||
update_vertex_buffers(mesh, mesh_id, new_state, &new_facets);
|
||||
if (update_both) {
|
||||
auto other = new_state == FacetSupportType::ENFORCER
|
||||
? FacetSupportType::BLOCKER
|
||||
: FacetSupportType::ENFORCER;
|
||||
update_vertex_buffers(mesh, mesh_id, other); // regenerate the other VBA
|
||||
}
|
||||
}
|
||||
else {
|
||||
update_vertex_buffers(mesh, mesh_id, FacetSupportType::ENFORCER);
|
||||
update_vertex_buffers(mesh, mesh_id, FacetSupportType::BLOCKER);
|
||||
}
|
||||
}
|
||||
assert(mesh_id < int(m_triangle_selectors.size()));
|
||||
m_triangle_selectors[mesh_id]->select_patch(closest_hit, closest_facet, camera_pos,
|
||||
dir, limit, new_state);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -524,58 +437,8 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
||||
}
|
||||
|
||||
|
||||
void GLGizmoFdmSupports::update_vertex_buffers(const TriangleMesh* mesh,
|
||||
int mesh_id,
|
||||
FacetSupportType type,
|
||||
const std::vector<size_t>* new_facets)
|
||||
{
|
||||
std::vector<GLIndexedVertexArray>& ivas = m_ivas[mesh_id][type == FacetSupportType::ENFORCER ? 0 : 1];
|
||||
|
||||
// lambda to push facet into vertex buffer
|
||||
auto push_facet = [this, &mesh, &mesh_id](size_t idx, GLIndexedVertexArray& iva) {
|
||||
for (int i=0; i<3; ++i)
|
||||
iva.push_geometry(
|
||||
mesh->its.vertices[mesh->its.indices[idx](i)].cast<double>(),
|
||||
m_c->raycaster()->raycasters()[mesh_id]->get_triangle_normal(idx).cast<double>()
|
||||
);
|
||||
size_t num = iva.triangle_indices_size;
|
||||
iva.push_triangle(num, num+1, num+2);
|
||||
};
|
||||
|
||||
|
||||
if (ivas.size() == MaxVertexBuffers || ! new_facets) {
|
||||
// If there are too many or they should be regenerated, make one large
|
||||
// GLVertexBufferArray.
|
||||
ivas.clear(); // destructors release geometry
|
||||
ivas.push_back(GLIndexedVertexArray());
|
||||
|
||||
bool pushed = false;
|
||||
for (size_t facet_idx=0; facet_idx<m_selected_facets[mesh_id].size(); ++facet_idx) {
|
||||
if (m_selected_facets[mesh_id][facet_idx] == type) {
|
||||
push_facet(facet_idx, ivas.back());
|
||||
pushed = true;
|
||||
}
|
||||
}
|
||||
if (pushed)
|
||||
ivas.back().finalize_geometry(true);
|
||||
else
|
||||
ivas.pop_back();
|
||||
} else {
|
||||
// we are only appending - let's make new vertex array and let the old ones live
|
||||
ivas.push_back(GLIndexedVertexArray());
|
||||
for (size_t facet_idx : *new_facets)
|
||||
push_facet(facet_idx, ivas.back());
|
||||
|
||||
if (! new_facets->empty())
|
||||
ivas.back().finalize_geometry(true);
|
||||
else
|
||||
ivas.pop_back();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool overwrite, bool block)
|
||||
void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool block)
|
||||
{
|
||||
float threshold = (M_PI/180.)*threshold_deg;
|
||||
const Selection& selection = m_parent.get_selection();
|
||||
@ -599,13 +462,12 @@ void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool overwr
|
||||
int idx = -1;
|
||||
for (const stl_facet& facet : mv->mesh().stl.facet_start) {
|
||||
++idx;
|
||||
if (facet.normal.dot(down) > dot_limit && (overwrite || m_selected_facets[mesh_id][idx] == FacetSupportType::NONE))
|
||||
m_selected_facets[mesh_id][idx] = block
|
||||
? FacetSupportType::BLOCKER
|
||||
: FacetSupportType::ENFORCER;
|
||||
if (facet.normal.dot(down) > dot_limit)
|
||||
m_triangle_selectors[mesh_id]->set_facet(idx,
|
||||
block
|
||||
? FacetSupportType::BLOCKER
|
||||
: FacetSupportType::ENFORCER);
|
||||
}
|
||||
update_vertex_buffers(&mv->mesh(), mesh_id, FacetSupportType::ENFORCER);
|
||||
update_vertex_buffers(&mv->mesh(), mesh_id, FacetSupportType::BLOCKER);
|
||||
}
|
||||
|
||||
activate_internal_undo_redo_stack(true);
|
||||
@ -651,9 +513,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
|
||||
|
||||
auto draw_text_with_caption = [this, &caption_max](const wxString& caption, const wxString& text) {
|
||||
static const ImVec4 ORANGE(1.0f, 0.49f, 0.22f, 1.0f);
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ORANGE);
|
||||
m_imgui->text(caption);
|
||||
ImGui::PopStyleColor();
|
||||
m_imgui->text_colored(ORANGE, caption);
|
||||
ImGui::SameLine(caption_max);
|
||||
m_imgui->text(text);
|
||||
};
|
||||
@ -670,18 +530,17 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
|
||||
ImGui::SameLine();
|
||||
|
||||
if (m_imgui->button(m_desc.at("remove_all"))) {
|
||||
Plater::TakeSnapshot(wxGetApp().plater(), wxString(_L("Reset selection")));
|
||||
ModelObject* mo = m_c->selection_info()->model_object();
|
||||
int idx = -1;
|
||||
for (ModelVolume* mv : mo->volumes) {
|
||||
++idx;
|
||||
if (mv->is_model_part()) {
|
||||
m_selected_facets[idx].assign(m_selected_facets[idx].size(), FacetSupportType::NONE);
|
||||
mv->m_supported_facets.clear();
|
||||
update_vertex_buffers(&mv->mesh(), idx, FacetSupportType::ENFORCER);
|
||||
update_vertex_buffers(&mv->mesh(), idx, FacetSupportType::BLOCKER);
|
||||
m_parent.set_as_dirty();
|
||||
++idx;
|
||||
m_triangle_selectors[idx]->reset();
|
||||
}
|
||||
}
|
||||
update_model_object();
|
||||
m_parent.set_as_dirty();
|
||||
}
|
||||
|
||||
const float max_tooltip_width = ImGui::GetFontSize() * 20.0f;
|
||||
@ -737,12 +596,11 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
|
||||
ImGui::SameLine();
|
||||
if (m_imgui->slider_float("", &m_angle_threshold_deg, 0.f, 90.f, "%.f"))
|
||||
m_parent.set_slope_range({90.f - m_angle_threshold_deg, 90.f - m_angle_threshold_deg});
|
||||
m_imgui->checkbox(wxString("Overwrite already selected facets"), m_overwrite_selected);
|
||||
if (m_imgui->button("Enforce"))
|
||||
select_facets_by_angle(m_angle_threshold_deg, m_overwrite_selected, false);
|
||||
select_facets_by_angle(m_angle_threshold_deg, false);
|
||||
ImGui::SameLine();
|
||||
if (m_imgui->button("Block"))
|
||||
select_facets_by_angle(m_angle_threshold_deg, m_overwrite_selected, true);
|
||||
select_facets_by_angle(m_angle_threshold_deg, true);
|
||||
ImGui::SameLine();
|
||||
if (m_imgui->button("Cancel"))
|
||||
m_setting_angle = false;
|
||||
@ -788,9 +646,7 @@ CommonGizmosDataID GLGizmoFdmSupports::on_get_requirements() const
|
||||
int(CommonGizmosDataID::SelectionInfo)
|
||||
| int(CommonGizmosDataID::InstancesHider)
|
||||
| int(CommonGizmosDataID::Raycaster)
|
||||
| int(CommonGizmosDataID::HollowedMesh)
|
||||
| int(CommonGizmosDataID::ObjectClipper)
|
||||
| int(CommonGizmosDataID::SupportsClipper));
|
||||
| int(CommonGizmosDataID::ObjectClipper));
|
||||
}
|
||||
|
||||
|
||||
@ -814,8 +670,8 @@ void GLGizmoFdmSupports::on_set_state()
|
||||
}
|
||||
activate_internal_undo_redo_stack(false);
|
||||
m_old_mo_id = -1;
|
||||
m_ivas.clear();
|
||||
m_selected_facets.clear();
|
||||
//m_iva.release_geometry();
|
||||
m_triangle_selectors.clear();
|
||||
}
|
||||
m_old_state = m_state;
|
||||
}
|
||||
@ -853,6 +709,151 @@ void GLGizmoFdmSupports::on_save(cereal::BinaryOutputArchive&) const
|
||||
}
|
||||
|
||||
|
||||
void TriangleSelectorGUI::render(ImGuiWrapper* imgui)
|
||||
{
|
||||
int enf_cnt = 0;
|
||||
int blc_cnt = 0;
|
||||
|
||||
m_iva_enforcers.release_geometry();
|
||||
m_iva_blockers.release_geometry();
|
||||
|
||||
for (const Triangle& tr : m_triangles) {
|
||||
if (! tr.valid || tr.is_split() || tr.get_state() == FacetSupportType::NONE)
|
||||
continue;
|
||||
|
||||
GLIndexedVertexArray& va = tr.get_state() == FacetSupportType::ENFORCER
|
||||
? m_iva_enforcers
|
||||
: m_iva_blockers;
|
||||
int& cnt = tr.get_state() == FacetSupportType::ENFORCER
|
||||
? enf_cnt
|
||||
: blc_cnt;
|
||||
|
||||
for (int i=0; i<3; ++i)
|
||||
va.push_geometry(double(m_vertices[tr.verts_idxs[i]].v[0]),
|
||||
double(m_vertices[tr.verts_idxs[i]].v[1]),
|
||||
double(m_vertices[tr.verts_idxs[i]].v[2]),
|
||||
0., 0., 1.);
|
||||
va.push_triangle(cnt,
|
||||
cnt+1,
|
||||
cnt+2);
|
||||
cnt += 3;
|
||||
}
|
||||
|
||||
m_iva_enforcers.finalize_geometry(true);
|
||||
m_iva_blockers.finalize_geometry(true);
|
||||
|
||||
if (m_iva_enforcers.has_VBOs()) {
|
||||
::glColor4f(0.f, 0.f, 1.f, 0.2f);
|
||||
m_iva_enforcers.render();
|
||||
}
|
||||
|
||||
|
||||
if (m_iva_blockers.has_VBOs()) {
|
||||
::glColor4f(1.f, 0.f, 0.f, 0.2f);
|
||||
m_iva_blockers.render();
|
||||
}
|
||||
|
||||
|
||||
#ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG
|
||||
if (imgui)
|
||||
render_debug(imgui);
|
||||
else
|
||||
assert(false); // If you want debug output, pass ptr to ImGuiWrapper.
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
#ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG
|
||||
void TriangleSelectorGUI::render_debug(ImGuiWrapper* imgui)
|
||||
{
|
||||
imgui->begin(std::string("TriangleSelector dialog (DEV ONLY)"),
|
||||
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse);
|
||||
static float edge_limit = 1.f;
|
||||
imgui->text("Edge limit (mm): ");
|
||||
imgui->slider_float("", &edge_limit, 0.1f, 8.f);
|
||||
set_edge_limit(edge_limit);
|
||||
imgui->checkbox("Show split triangles: ", m_show_triangles);
|
||||
imgui->checkbox("Show invalid triangles: ", m_show_invalid);
|
||||
|
||||
int valid_triangles = m_triangles.size() - m_invalid_triangles;
|
||||
imgui->text("Valid triangles: " + std::to_string(valid_triangles) +
|
||||
"/" + std::to_string(m_triangles.size()));
|
||||
imgui->text("Vertices: " + std::to_string(m_vertices.size()));
|
||||
if (imgui->button("Force garbage collection"))
|
||||
garbage_collect();
|
||||
|
||||
if (imgui->button("Serialize - deserialize")) {
|
||||
auto map = serialize();
|
||||
deserialize(map);
|
||||
}
|
||||
|
||||
imgui->end();
|
||||
|
||||
if (! m_show_triangles)
|
||||
return;
|
||||
|
||||
enum vtype {
|
||||
ORIGINAL = 0,
|
||||
SPLIT,
|
||||
INVALID
|
||||
};
|
||||
|
||||
for (auto& va : m_varrays)
|
||||
va.release_geometry();
|
||||
|
||||
std::array<int, 3> cnts;
|
||||
|
||||
::glScalef(1.01f, 1.01f, 1.01f);
|
||||
|
||||
for (int tr_id=0; tr_id<int(m_triangles.size()); ++tr_id) {
|
||||
const Triangle& tr = m_triangles[tr_id];
|
||||
GLIndexedVertexArray* va = nullptr;
|
||||
int* cnt = nullptr;
|
||||
if (tr_id < m_orig_size_indices) {
|
||||
va = &m_varrays[ORIGINAL];
|
||||
cnt = &cnts[ORIGINAL];
|
||||
}
|
||||
else if (tr.valid) {
|
||||
va = &m_varrays[SPLIT];
|
||||
cnt = &cnts[SPLIT];
|
||||
}
|
||||
else {
|
||||
if (! m_show_invalid)
|
||||
continue;
|
||||
va = &m_varrays[INVALID];
|
||||
cnt = &cnts[INVALID];
|
||||
}
|
||||
|
||||
for (int i=0; i<3; ++i)
|
||||
va->push_geometry(double(m_vertices[tr.verts_idxs[i]].v[0]),
|
||||
double(m_vertices[tr.verts_idxs[i]].v[1]),
|
||||
double(m_vertices[tr.verts_idxs[i]].v[2]),
|
||||
0., 0., 1.);
|
||||
va->push_triangle(*cnt,
|
||||
*cnt+1,
|
||||
*cnt+2);
|
||||
*cnt += 3;
|
||||
}
|
||||
|
||||
::glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
|
||||
for (vtype i : {ORIGINAL, SPLIT, INVALID}) {
|
||||
GLIndexedVertexArray& va = m_varrays[i];
|
||||
va.finalize_geometry(true);
|
||||
if (va.has_VBOs()) {
|
||||
switch (i) {
|
||||
case ORIGINAL : ::glColor3f(0.f, 0.f, 1.f); break;
|
||||
case SPLIT : ::glColor3f(1.f, 0.f, 0.f); break;
|
||||
case INVALID : ::glColor3f(1.f, 1.f, 0.f); break;
|
||||
}
|
||||
va.render();
|
||||
}
|
||||
}
|
||||
::glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
@ -6,10 +6,13 @@
|
||||
#include "slic3r/GUI/3DScene.hpp"
|
||||
|
||||
#include "libslic3r/ObjectID.hpp"
|
||||
#include "libslic3r/TriangleSelector.hpp"
|
||||
|
||||
#include <cereal/types/vector.hpp>
|
||||
|
||||
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
enum class FacetSupportType : int8_t;
|
||||
@ -19,6 +22,31 @@ namespace GUI {
|
||||
enum class SLAGizmoEventType : unsigned char;
|
||||
class ClippingPlane;
|
||||
|
||||
|
||||
|
||||
class TriangleSelectorGUI : public TriangleSelector {
|
||||
public:
|
||||
explicit TriangleSelectorGUI(const TriangleMesh& mesh)
|
||||
: TriangleSelector(mesh) {}
|
||||
|
||||
// Render current selection. Transformation matrices are supposed
|
||||
// to be already set.
|
||||
void render(ImGuiWrapper* imgui = nullptr);
|
||||
|
||||
#ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG
|
||||
void render_debug(ImGuiWrapper* imgui);
|
||||
bool m_show_triangles{false};
|
||||
bool m_show_invalid{false};
|
||||
#endif
|
||||
|
||||
private:
|
||||
GLIndexedVertexArray m_iva_enforcers;
|
||||
GLIndexedVertexArray m_iva_blockers;
|
||||
std::array<GLIndexedVertexArray, 3> m_varrays;
|
||||
};
|
||||
|
||||
|
||||
|
||||
class GLGizmoFdmSupports : public GLGizmoBase
|
||||
{
|
||||
private:
|
||||
@ -28,24 +56,12 @@ private:
|
||||
GLUquadricObj* m_quadric;
|
||||
|
||||
float m_cursor_radius = 2.f;
|
||||
static constexpr float CursorRadiusMin = 0.f;
|
||||
static constexpr float CursorRadiusMin = 0.4f; // cannot be zero
|
||||
static constexpr float CursorRadiusMax = 8.f;
|
||||
static constexpr float CursorRadiusStep = 0.2f;
|
||||
|
||||
// For each model-part volume, store a list of statuses of
|
||||
// individual facets (one of the enum values above).
|
||||
std::vector<std::vector<FacetSupportType>> m_selected_facets;
|
||||
|
||||
// Vertex buffer arrays for each model-part volume. There is a vector of
|
||||
// arrays so that adding triangles can be done without regenerating all
|
||||
// other triangles. Enforcers and blockers are of course separate.
|
||||
std::vector<std::array<std::vector<GLIndexedVertexArray>, 2>> m_ivas;
|
||||
|
||||
void update_vertex_buffers(const TriangleMesh* mesh,
|
||||
int mesh_id,
|
||||
FacetSupportType type, // enforcers / blockers
|
||||
const std::vector<size_t>* new_facets = nullptr); // nullptr -> regenerate all
|
||||
|
||||
// For each model-part volume, store status and division of the triangles.
|
||||
std::vector<std::unique_ptr<TriangleSelectorGUI>> m_triangle_selectors;
|
||||
|
||||
public:
|
||||
GLGizmoFdmSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
|
||||
@ -66,8 +82,7 @@ private:
|
||||
void update_from_model_object();
|
||||
void activate_internal_undo_redo_stack(bool activate);
|
||||
|
||||
void select_facets_by_angle(float threshold, bool overwrite, bool block);
|
||||
bool m_overwrite_selected = false;
|
||||
void select_facets_by_angle(float threshold, bool block);
|
||||
float m_angle_threshold_deg = 45.f;
|
||||
|
||||
bool is_mesh_point_clipped(const Vec3d& point) const;
|
||||
|
@ -9,7 +9,7 @@
|
||||
#include "slic3r/GUI/GUI_ObjectSettings.hpp"
|
||||
#include "slic3r/GUI/GUI_ObjectList.hpp"
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
#include "slic3r/GUI/PresetBundle.hpp"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
|
||||
#include "libslic3r/Model.hpp"
|
||||
|
||||
|
@ -16,7 +16,7 @@
|
||||
#include "slic3r/GUI/GUI_ObjectSettings.hpp"
|
||||
#include "slic3r/GUI/GUI_ObjectList.hpp"
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
#include "slic3r/GUI/PresetBundle.hpp"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "libslic3r/SLAPrint.hpp"
|
||||
|
||||
|
||||
|
@ -8,7 +8,7 @@
|
||||
#include "slic3r/GUI/Camera.hpp"
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
|
||||
#include "slic3r/GUI/PresetBundle.hpp"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
|
||||
#include <GL/glew.h>
|
||||
|
||||
|
@ -5,7 +5,6 @@
|
||||
#include "slic3r/GUI/Camera.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/GUI_ObjectManipulation.hpp"
|
||||
#include "slic3r/GUI/PresetBundle.hpp"
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
#include "slic3r/Utils/UndoRedo.hpp"
|
||||
|
||||
@ -19,6 +18,7 @@
|
||||
#include "slic3r/GUI/Gizmos/GLGizmoHollow.hpp"
|
||||
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
|
||||
#include <wx/glcanvas.h>
|
||||
|
||||
|
@ -12,7 +12,7 @@
|
||||
|
||||
#ifndef L
|
||||
// !!! If you needed to translate some wxString,
|
||||
// !!! please use _(L(string))
|
||||
// !!! please use _L(string)
|
||||
// !!! _() - is a standard wxWidgets macro to translate
|
||||
// !!! L() is used only for marking localizable string
|
||||
// !!! It will be used in "xgettext" to create a Locating Message Catalog.
|
||||
|
@ -37,11 +37,17 @@ namespace GUI {
|
||||
|
||||
|
||||
static const std::map<const char, std::string> font_icons = {
|
||||
{ImGui::PrintIconMarker , "cog" },
|
||||
{ImGui::PrinterIconMarker , "printer" },
|
||||
{ImGui::PrinterSlaIconMarker, "sla_printer"},
|
||||
{ImGui::FilamentIconMarker , "spool" },
|
||||
{ImGui::MaterialIconMarker , "resin" }
|
||||
{ImGui::PrintIconMarker , "cog" },
|
||||
{ImGui::PrinterIconMarker , "printer" },
|
||||
{ImGui::PrinterSlaIconMarker, "sla_printer" },
|
||||
{ImGui::FilamentIconMarker , "spool" },
|
||||
{ImGui::MaterialIconMarker , "resin" },
|
||||
{ImGui::CloseIconMarker , "cross" },
|
||||
{ImGui::CloseIconHoverMarker, "cross_focus_large" },
|
||||
{ImGui::TimerDotMarker , "timer_dot" },
|
||||
{ImGui::TimerDotEmptyMarker , "timer_dot_empty" },
|
||||
{ImGui::WarningMarker , "flag_green" },
|
||||
{ImGui::ErrorMarker , "flag_red" }
|
||||
};
|
||||
|
||||
ImGuiWrapper::ImGuiWrapper()
|
||||
@ -176,6 +182,9 @@ bool ImGuiWrapper::update_mouse_data(wxMouseEvent& evt)
|
||||
io.MouseDown[0] = evt.LeftIsDown();
|
||||
io.MouseDown[1] = evt.RightIsDown();
|
||||
io.MouseDown[2] = evt.MiddleIsDown();
|
||||
float wheel_delta = static_cast<float>(evt.GetWheelDelta());
|
||||
if (wheel_delta != 0.0f)
|
||||
io.MouseWheel = static_cast<float>(evt.GetWheelRotation()) / wheel_delta;
|
||||
|
||||
unsigned buttons = (evt.LeftIsDown() ? 1 : 0) | (evt.RightIsDown() ? 2 : 0) | (evt.MiddleIsDown() ? 4 : 0);
|
||||
m_mouse_buttons = buttons;
|
||||
@ -262,6 +271,11 @@ void ImGuiWrapper::set_next_window_bg_alpha(float alpha)
|
||||
ImGui::SetNextWindowBgAlpha(alpha);
|
||||
}
|
||||
|
||||
void ImGuiWrapper::set_next_window_size(float x, float y, ImGuiCond cond)
|
||||
{
|
||||
ImGui::SetNextWindowSize(ImVec2(x, y), cond);
|
||||
}
|
||||
|
||||
bool ImGuiWrapper::begin(const std::string &name, int flags)
|
||||
{
|
||||
return ImGui::Begin(name.c_str(), nullptr, (ImGuiWindowFlags)flags);
|
||||
@ -293,12 +307,23 @@ bool ImGuiWrapper::button(const wxString &label)
|
||||
return ImGui::Button(label_utf8.c_str());
|
||||
}
|
||||
|
||||
bool ImGuiWrapper::button(const wxString& label, float width, float height)
|
||||
{
|
||||
auto label_utf8 = into_u8(label);
|
||||
return ImGui::Button(label_utf8.c_str(), ImVec2(width, height));
|
||||
}
|
||||
|
||||
bool ImGuiWrapper::radio_button(const wxString &label, bool active)
|
||||
{
|
||||
auto label_utf8 = into_u8(label);
|
||||
return ImGui::RadioButton(label_utf8.c_str(), active);
|
||||
}
|
||||
|
||||
bool ImGuiWrapper::image_button()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ImGuiWrapper::input_double(const std::string &label, const double &value, const std::string &format)
|
||||
{
|
||||
return ImGui::InputDouble(label.c_str(), const_cast<double*>(&value), 0.0f, 0.0f, format.c_str());
|
||||
@ -351,6 +376,22 @@ void ImGuiWrapper::text(const wxString &label)
|
||||
this->text(label_utf8.c_str());
|
||||
}
|
||||
|
||||
void ImGuiWrapper::text_colored(const ImVec4& color, const char* label)
|
||||
{
|
||||
ImGui::TextColored(color, label);
|
||||
}
|
||||
|
||||
void ImGuiWrapper::text_colored(const ImVec4& color, const std::string& label)
|
||||
{
|
||||
this->text_colored(color, label.c_str());
|
||||
}
|
||||
|
||||
void ImGuiWrapper::text_colored(const ImVec4& color, const wxString& label)
|
||||
{
|
||||
auto label_utf8 = into_u8(label);
|
||||
this->text_colored(color, label_utf8.c_str());
|
||||
}
|
||||
|
||||
bool ImGuiWrapper::slider_float(const char* label, float* v, float v_min, float v_max, const char* format/* = "%.3f"*/, float power/* = 1.0f*/)
|
||||
{
|
||||
return ImGui::SliderFloat(label, v, v_min, v_max, format, power);
|
||||
|
@ -57,6 +57,7 @@ public:
|
||||
|
||||
void set_next_window_pos(float x, float y, int flag, float pivot_x = 0.0f, float pivot_y = 0.0f);
|
||||
void set_next_window_bg_alpha(float alpha);
|
||||
void set_next_window_size(float x, float y, ImGuiCond cond);
|
||||
|
||||
bool begin(const std::string &name, int flags = 0);
|
||||
bool begin(const wxString &name, int flags = 0);
|
||||
@ -65,7 +66,9 @@ public:
|
||||
void end();
|
||||
|
||||
bool button(const wxString &label);
|
||||
bool button(const wxString& label, float width, float height);
|
||||
bool radio_button(const wxString &label, bool active);
|
||||
bool image_button();
|
||||
bool input_double(const std::string &label, const double &value, const std::string &format = "%.3f");
|
||||
bool input_double(const wxString &label, const double &value, const std::string &format = "%.3f");
|
||||
bool input_vec3(const std::string &label, const Vec3d &value, float width, const std::string &format = "%.3f");
|
||||
@ -73,6 +76,9 @@ public:
|
||||
void text(const char *label);
|
||||
void text(const std::string &label);
|
||||
void text(const wxString &label);
|
||||
void text_colored(const ImVec4& color, const char* label);
|
||||
void text_colored(const ImVec4& color, const std::string& label);
|
||||
void text_colored(const ImVec4& color, const wxString& label);
|
||||
bool slider_float(const char* label, float* v, float v_min, float v_max, const char* format = "%.3f", float power = 1.0f);
|
||||
bool slider_float(const std::string& label, float* v, float v_min, float v_max, const char* format = "%.3f", float power = 1.0f);
|
||||
bool slider_float(const wxString& label, float* v, float v_min, float v_max, const char* format = "%.3f", float power = 1.0f);
|
||||
|
@ -4,11 +4,11 @@
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/AppConfig.hpp"
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
#include "slic3r/GUI/PresetBundle.hpp"
|
||||
#include "slic3r/GUI/GUI_ObjectList.hpp"
|
||||
#include "slic3r/Utils/SLAImport.hpp"
|
||||
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
|
||||
#include <wx/dialog.h>
|
||||
#include <wx/stattext.h>
|
||||
|
@ -15,9 +15,9 @@
|
||||
#include "libslic3r/Print.hpp"
|
||||
#include "libslic3r/Polygon.hpp"
|
||||
#include "libslic3r/SLAPrint.hpp"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
|
||||
#include "Tab.hpp"
|
||||
#include "PresetBundle.hpp"
|
||||
#include "ProgressStatusBar.hpp"
|
||||
#include "3DScene.hpp"
|
||||
#include "AppConfig.hpp"
|
||||
@ -42,7 +42,6 @@
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
#if ENABLE_LAYOUT_NO_RESTART
|
||||
enum class ERescaleTarget
|
||||
{
|
||||
Mainframe,
|
||||
@ -71,15 +70,12 @@ static void rescale_dialog_after_dpi_change(MainFrame& mainframe, SettingsDialog
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // ENABLE_LAYOUT_NO_RESTART
|
||||
|
||||
MainFrame::MainFrame() :
|
||||
DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_STYLE, "mainframe"),
|
||||
m_printhost_queue_dlg(new PrintHostQueueDialog(this))
|
||||
, m_recent_projects(9)
|
||||
#if ENABLE_LAYOUT_NO_RESTART
|
||||
, m_settings_dialog(this)
|
||||
#endif // ENABLE_LAYOUT_NO_RESTART
|
||||
{
|
||||
// Fonts were created by the DPIFrame constructor for the monitor, on which the window opened.
|
||||
wxGetApp().update_fonts(this);
|
||||
@ -106,12 +102,7 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S
|
||||
m_statusbar->embed(this);
|
||||
m_statusbar->set_status_text(_(L("Version")) + " " +
|
||||
SLIC3R_VERSION +
|
||||
_(L(" - Remember to check for updates at http://github.com/prusa3d/PrusaSlicer/releases")));
|
||||
|
||||
/* Load default preset bitmaps before a tabpanel initialization,
|
||||
* but after filling of an em_unit value
|
||||
*/
|
||||
wxGetApp().preset_bundle->load_default_preset_bitmaps();
|
||||
_(L(" - Remember to check for updates at https://github.com/prusa3d/PrusaSlicer/releases")));
|
||||
|
||||
// initialize tabpanel and menubar
|
||||
init_tabpanel();
|
||||
@ -124,43 +115,15 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S
|
||||
|
||||
m_loaded = true;
|
||||
|
||||
#if !ENABLE_LAYOUT_NO_RESTART
|
||||
#ifdef __APPLE__
|
||||
// Using SetMinSize() on Mac messes up the window position in some cases
|
||||
// cf. https://groups.google.com/forum/#!topic/wx-users/yUKPBBfXWO0
|
||||
// So, if we haven't possibility to set MinSize() for the MainFrame,
|
||||
// set the MinSize() as a half of regular for the m_plater and m_tabpanel, when settings layout is in slNew mode
|
||||
// Otherwise, MainFrame will be maximized by height
|
||||
if (slNew) {
|
||||
wxSize size = wxGetApp().get_min_size();
|
||||
size.SetHeight(int(0.5*size.GetHeight()));
|
||||
m_plater->SetMinSize(size);
|
||||
m_tabpanel->SetMinSize(size);
|
||||
}
|
||||
#endif
|
||||
#endif // !ENABLE_LAYOUT_NO_RESTART
|
||||
|
||||
// initialize layout
|
||||
m_main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
wxSizer* sizer = new wxBoxSizer(wxVERTICAL);
|
||||
sizer->Add(m_main_sizer, 1, wxEXPAND);
|
||||
#if ENABLE_LAYOUT_NO_RESTART
|
||||
SetSizer(sizer);
|
||||
// initialize layout from config
|
||||
update_layout();
|
||||
sizer->SetSizeHints(this);
|
||||
Fit();
|
||||
#else
|
||||
if (m_plater && m_layout != slOld)
|
||||
sizer->Add(m_plater, 1, wxEXPAND);
|
||||
|
||||
if (m_tabpanel && m_layout != slDlg)
|
||||
sizer->Add(m_tabpanel, 1, wxEXPAND);
|
||||
|
||||
sizer->SetSizeHints(this);
|
||||
SetSizer(sizer);
|
||||
Fit();
|
||||
#endif // !ENABLE_LAYOUT_NO_RESTART
|
||||
|
||||
const wxSize min_size = wxGetApp().get_min_size(); //wxSize(76*wxGetApp().em_unit(), 49*wxGetApp().em_unit());
|
||||
#ifdef __APPLE__
|
||||
@ -252,12 +215,7 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S
|
||||
});
|
||||
|
||||
wxGetApp().persist_window_geometry(this, true);
|
||||
#if ENABLE_LAYOUT_NO_RESTART
|
||||
wxGetApp().persist_window_geometry(&m_settings_dialog, true);
|
||||
#else
|
||||
if (m_settings_dialog != nullptr)
|
||||
wxGetApp().persist_window_geometry(m_settings_dialog, true);
|
||||
#endif // ENABLE_LAYOUT_NO_RESTART
|
||||
|
||||
update_ui_from_settings(); // FIXME (?)
|
||||
|
||||
@ -265,7 +223,6 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S
|
||||
m_plater->show_action_buttons(true);
|
||||
}
|
||||
|
||||
#if ENABLE_LAYOUT_NO_RESTART
|
||||
void MainFrame::update_layout()
|
||||
{
|
||||
auto restore_to_creation = [this]() {
|
||||
@ -380,7 +337,6 @@ void MainFrame::update_layout()
|
||||
Layout();
|
||||
Thaw();
|
||||
}
|
||||
#endif // ENABLE_LAYOUT_NO_RESTART
|
||||
|
||||
// Called when closing the application and when switching the application language.
|
||||
void MainFrame::shutdown()
|
||||
@ -414,20 +370,9 @@ void MainFrame::shutdown()
|
||||
// In addition, there were some crashes due to the Paint events sent to already destructed windows.
|
||||
this->Show(false);
|
||||
|
||||
#if ENABLE_LAYOUT_NO_RESTART
|
||||
if (m_settings_dialog.IsShown())
|
||||
// call Close() to trigger call to lambda defined into GUI_App::persist_window_geometry()
|
||||
m_settings_dialog.Close();
|
||||
#else
|
||||
if (m_settings_dialog != nullptr)
|
||||
{
|
||||
if (m_settings_dialog->IsShown())
|
||||
// call Close() to trigger call to lambda defined into GUI_App::persist_window_geometry()
|
||||
m_settings_dialog->Close();
|
||||
|
||||
m_settings_dialog->Destroy();
|
||||
}
|
||||
#endif // ENABLE_LAYOUT_NO_RESTART
|
||||
|
||||
// Stop the background thread (Windows and Linux).
|
||||
// Disconnect from a 3DConnextion driver (OSX).
|
||||
@ -486,7 +431,6 @@ void MainFrame::update_title()
|
||||
|
||||
void MainFrame::init_tabpanel()
|
||||
{
|
||||
#if ENABLE_LAYOUT_NO_RESTART
|
||||
// wxNB_NOPAGETHEME: Disable Windows Vista theme for the Notebook background. The theme performance is terrible on Windows 10
|
||||
// with multiple high resolution displays connected.
|
||||
m_tabpanel = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME);
|
||||
@ -495,27 +439,6 @@ void MainFrame::init_tabpanel()
|
||||
#endif
|
||||
m_tabpanel->Hide();
|
||||
m_settings_dialog.set_tabpanel(m_tabpanel);
|
||||
#else
|
||||
m_layout = wxGetApp().app_config->get("old_settings_layout_mode") == "1" ? slOld :
|
||||
wxGetApp().app_config->get("new_settings_layout_mode") == "1" ? slNew :
|
||||
wxGetApp().app_config->get("dlg_settings_layout_mode") == "1" ? slDlg : slOld;
|
||||
|
||||
// From the very beginning the Print settings should be selected
|
||||
m_last_selected_tab = m_layout == slDlg ? 0 : 1;
|
||||
|
||||
if (m_layout == slDlg) {
|
||||
m_settings_dialog = new SettingsDialog(this);
|
||||
m_tabpanel = m_settings_dialog->get_tabpanel();
|
||||
}
|
||||
else {
|
||||
// wxNB_NOPAGETHEME: Disable Windows Vista theme for the Notebook background. The theme performance is terrible on Windows 10
|
||||
// with multiple high resolution displays connected.
|
||||
m_tabpanel = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME);
|
||||
#ifndef __WXOSX__ // Don't call SetFont under OSX to avoid name cutting in ObjectList
|
||||
m_tabpanel->SetFont(Slic3r::GUI::wxGetApp().normal_font());
|
||||
#endif
|
||||
}
|
||||
#endif // ENABLE_LAYOUT_NO_RESTART
|
||||
|
||||
m_tabpanel->Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, [this](wxEvent&) {
|
||||
wxWindow* panel = m_tabpanel->GetCurrentPage();
|
||||
@ -536,20 +459,9 @@ void MainFrame::init_tabpanel()
|
||||
select_tab(0); // select Plater
|
||||
});
|
||||
|
||||
#if ENABLE_LAYOUT_NO_RESTART
|
||||
m_plater = new Plater(this, this);
|
||||
m_plater->Hide();
|
||||
#else
|
||||
if (m_layout == slOld) {
|
||||
m_plater = new Plater(m_tabpanel, this);
|
||||
m_tabpanel->AddPage(m_plater, _L("Plater"));
|
||||
}
|
||||
else {
|
||||
m_plater = new Plater(this, this);
|
||||
if (m_layout == slNew)
|
||||
m_tabpanel->AddPage(new wxPanel(m_tabpanel), _L("Plater")); // empty panel just for Plater tab
|
||||
}
|
||||
#endif // ENABLE_LAYOUT_NO_RESTART
|
||||
|
||||
wxGetApp().plater_ = m_plater;
|
||||
|
||||
wxGetApp().obj_list()->create_popup_menus();
|
||||
@ -691,7 +603,6 @@ bool MainFrame::can_slice() const
|
||||
|
||||
bool MainFrame::can_change_view() const
|
||||
{
|
||||
#if ENABLE_LAYOUT_NO_RESTART
|
||||
switch (m_layout)
|
||||
{
|
||||
default: { return false; }
|
||||
@ -702,15 +613,6 @@ bool MainFrame::can_change_view() const
|
||||
return page_id != wxNOT_FOUND && dynamic_cast<const Slic3r::GUI::Plater*>(m_tabpanel->GetPage((size_t)page_id)) != nullptr;
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (m_layout == slNew)
|
||||
return m_plater->IsShown();
|
||||
if (m_layout == slDlg)
|
||||
return true;
|
||||
// slOld layout mode
|
||||
int page_id = m_tabpanel->GetSelection();
|
||||
return page_id != wxNOT_FOUND && dynamic_cast<const Slic3r::GUI::Plater*>(m_tabpanel->GetPage((size_t)page_id)) != nullptr;
|
||||
#endif // ENABLE_LAYOUT_NO_RESTART
|
||||
}
|
||||
|
||||
bool MainFrame::can_select() const
|
||||
@ -747,20 +649,11 @@ void MainFrame::on_dpi_changed(const wxRect& suggested_rect)
|
||||
#endif // ENABLE_WX_3_1_3_DPI_CHANGED_EVENT
|
||||
this->SetFont(this->normal_font());
|
||||
|
||||
/* Load default preset bitmaps before a tabpanel initialization,
|
||||
* but after filling of an em_unit value
|
||||
*/
|
||||
wxGetApp().preset_bundle->load_default_preset_bitmaps();
|
||||
|
||||
// update Plater
|
||||
wxGetApp().plater()->msw_rescale();
|
||||
|
||||
// update Tabs
|
||||
#if ENABLE_LAYOUT_NO_RESTART
|
||||
if (m_layout != ESettingsLayout::Dlg) // Do not update tabs if the Settings are in the separated dialog
|
||||
#else
|
||||
if (m_layout != slDlg) // Update tabs later, from the SettingsDialog, when the Settings are in the separated dialog
|
||||
#endif // ENABLE_LAYOUT_NO_RESTART
|
||||
for (auto tab : wxGetApp().tabs_list)
|
||||
tab->msw_rescale();
|
||||
|
||||
@ -789,10 +682,8 @@ void MainFrame::on_dpi_changed(const wxRect& suggested_rect)
|
||||
|
||||
this->Maximize(is_maximized);
|
||||
|
||||
#if ENABLE_LAYOUT_NO_RESTART
|
||||
if (m_layout == ESettingsLayout::Dlg)
|
||||
rescale_dialog_after_dpi_change(*this, m_settings_dialog, ERescaleTarget::SettingsDialog);
|
||||
#endif // ENABLE_LAYOUT_NO_RESTART
|
||||
}
|
||||
|
||||
void MainFrame::on_sys_color_changed()
|
||||
@ -802,8 +693,6 @@ void MainFrame::on_sys_color_changed()
|
||||
// update label colors in respect to the system mode
|
||||
wxGetApp().init_label_colours();
|
||||
|
||||
wxGetApp().preset_bundle->load_default_preset_bitmaps();
|
||||
|
||||
// update Plater
|
||||
wxGetApp().plater()->sys_color_changed();
|
||||
|
||||
@ -909,7 +798,7 @@ void MainFrame::init_menubar()
|
||||
|
||||
wxMenu* export_menu = new wxMenu();
|
||||
wxMenuItem* item_export_gcode = append_menu_item(export_menu, wxID_ANY, _(L("Export &G-code")) + dots +"\tCtrl+G", _(L("Export current plate as G-code")),
|
||||
[this](wxCommandEvent&) { if (m_plater) m_plater->export_gcode(); }, "export_gcode", nullptr,
|
||||
[this](wxCommandEvent&) { if (m_plater) m_plater->export_gcode(false); }, "export_gcode", nullptr,
|
||||
[this](){return can_export_gcode(); }, this);
|
||||
m_changeable_menu_items.push_back(item_export_gcode);
|
||||
wxMenuItem* item_send_gcode = append_menu_item(export_menu, wxID_ANY, _(L("S&end G-code")) + dots +"\tCtrl+Shift+G", _(L("Send to print current plate as G-code")),
|
||||
@ -1141,7 +1030,7 @@ void MainFrame::init_menubar()
|
||||
append_menu_item(helpMenu, wxID_ANY, _(L("Prusa 3D &Drivers")), _(L("Open the Prusa3D drivers download page in your browser")),
|
||||
[this](wxCommandEvent&) { wxGetApp().open_web_page_localized("https://www.prusa3d.com/downloads"); });
|
||||
append_menu_item(helpMenu, wxID_ANY, _(L("Software &Releases")), _(L("Open the software releases page in your browser")),
|
||||
[this](wxCommandEvent&) { wxLaunchDefaultBrowser("http://github.com/prusa3d/PrusaSlicer/releases"); });
|
||||
[this](wxCommandEvent&) { wxLaunchDefaultBrowser("https://github.com/prusa3d/PrusaSlicer/releases"); });
|
||||
//# my $versioncheck = $self->_append_menu_item($helpMenu, "Check for &Updates...", "Check for new Slic3r versions", sub{
|
||||
//# wxTheApp->check_version(1);
|
||||
//# });
|
||||
@ -1158,7 +1047,7 @@ void MainFrame::init_menubar()
|
||||
append_menu_item(helpMenu, wxID_ANY, _(L("Show &Configuration Folder")), _(L("Show user configuration folder (datadir)")),
|
||||
[this](wxCommandEvent&) { Slic3r::GUI::desktop_open_datadir_folder(); });
|
||||
append_menu_item(helpMenu, wxID_ANY, _(L("Report an I&ssue")), wxString::Format(_(L("Report an issue on %s")), SLIC3R_APP_NAME),
|
||||
[this](wxCommandEvent&) { wxLaunchDefaultBrowser("http://github.com/prusa3d/slic3r/issues/new"); });
|
||||
[this](wxCommandEvent&) { wxLaunchDefaultBrowser("https://github.com/prusa3d/slic3r/issues/new"); });
|
||||
append_menu_item(helpMenu, wxID_ANY, wxString::Format(_(L("&About %s")), SLIC3R_APP_NAME), _(L("Show about dialog")),
|
||||
[this](wxCommandEvent&) { Slic3r::GUI::about(); });
|
||||
helpMenu->AppendSeparator();
|
||||
@ -1528,25 +1417,15 @@ void MainFrame::load_config(const DynamicPrintConfig& config)
|
||||
void MainFrame::select_tab(size_t tab/* = size_t(-1)*/)
|
||||
{
|
||||
bool tabpanel_was_hidden = false;
|
||||
#if ENABLE_LAYOUT_NO_RESTART
|
||||
if (m_layout == ESettingsLayout::Dlg) {
|
||||
#else
|
||||
if (m_layout == slDlg) {
|
||||
#endif // ENABLE_LAYOUT_NO_RESTART
|
||||
if (tab==0) {
|
||||
#if ENABLE_LAYOUT_NO_RESTART
|
||||
if (m_settings_dialog.IsShown())
|
||||
this->SetFocus();
|
||||
#else
|
||||
if (m_settings_dialog->IsShown())
|
||||
this->SetFocus();
|
||||
#endif // ENABLE_LAYOUT_NO_RESTART
|
||||
// plater should be focused for correct navigation inside search window
|
||||
if (m_plater->canvas3D()->is_search_pressed())
|
||||
m_plater->SetFocus();
|
||||
return;
|
||||
}
|
||||
#if ENABLE_LAYOUT_NO_RESTART
|
||||
// Show/Activate Settings Dialog
|
||||
#ifdef __WXOSX__ // Don't call SetFont under OSX to avoid name cutting in ObjectList
|
||||
if (m_settings_dialog.IsShown())
|
||||
@ -1563,28 +1442,11 @@ void MainFrame::select_tab(size_t tab/* = size_t(-1)*/)
|
||||
m_settings_dialog.Show();
|
||||
}
|
||||
#endif
|
||||
#else
|
||||
// Show/Activate Settings Dialog
|
||||
if (m_settings_dialog->IsShown())
|
||||
#ifdef __WXOSX__ // Don't call SetFont under OSX to avoid name cutting in ObjectList
|
||||
m_settings_dialog->Hide();
|
||||
#else
|
||||
m_settings_dialog->SetFocus();
|
||||
else
|
||||
#endif
|
||||
m_settings_dialog->Show();
|
||||
#endif // ENABLE_LAYOUT_NO_RESTART
|
||||
}
|
||||
#if ENABLE_LAYOUT_NO_RESTART
|
||||
else if (m_layout == ESettingsLayout::New) {
|
||||
m_main_sizer->Show(m_plater, tab == 0);
|
||||
tabpanel_was_hidden = !m_main_sizer->IsShown(m_tabpanel);
|
||||
m_main_sizer->Show(m_tabpanel, tab != 0);
|
||||
#else
|
||||
else if (m_layout == slNew) {
|
||||
m_plater->Show(tab == 0);
|
||||
m_tabpanel->Show(tab != 0);
|
||||
#endif // ENABLE_LAYOUT_NO_RESTART
|
||||
|
||||
// plater should be focused for correct navigation inside search window
|
||||
if (tab == 0 && m_plater->canvas3D()->is_search_pressed())
|
||||
@ -1601,11 +1463,7 @@ void MainFrame::select_tab(size_t tab/* = size_t(-1)*/)
|
||||
tab->update_changed_tree_ui();
|
||||
|
||||
// when tab == -1, it means we should show the last selected tab
|
||||
#if ENABLE_LAYOUT_NO_RESTART
|
||||
m_tabpanel->SetSelection(tab == (size_t)(-1) ? m_last_selected_tab : (m_layout == ESettingsLayout::Dlg && tab != 0) ? tab - 1 : tab);
|
||||
#else
|
||||
m_tabpanel->SetSelection(tab == (size_t)(-1) ? m_last_selected_tab : (m_layout == slDlg && tab != 0) ? tab-1 : tab);
|
||||
#endif // ENABLE_LAYOUT_NO_RESTART
|
||||
}
|
||||
|
||||
// Set a camera direction, zoom to all objects.
|
||||
@ -1734,34 +1592,6 @@ SettingsDialog::SettingsDialog(MainFrame* mainframe)
|
||||
SetIcon(wxIcon(var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG));
|
||||
#endif // _WIN32
|
||||
|
||||
#if !ENABLE_LAYOUT_NO_RESTART
|
||||
// wxNB_NOPAGETHEME: Disable Windows Vista theme for the Notebook background. The theme performance is terrible on Windows 10
|
||||
// with multiple high resolution displays connected.
|
||||
m_tabpanel = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxGetApp().get_min_size(), wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME);
|
||||
#ifndef __WXOSX__ // Don't call SetFont under OSX to avoid name cutting in ObjectList
|
||||
m_tabpanel->SetFont(Slic3r::GUI::wxGetApp().normal_font());
|
||||
#endif
|
||||
|
||||
m_tabpanel->Bind(wxEVT_KEY_UP, [this](wxKeyEvent& evt) {
|
||||
if ((evt.GetModifiers() & wxMOD_CONTROL) != 0) {
|
||||
switch (evt.GetKeyCode()) {
|
||||
case '1': { m_main_frame->select_tab(0); break; }
|
||||
case '2': { m_main_frame->select_tab(1); break; }
|
||||
case '3': { m_main_frame->select_tab(2); break; }
|
||||
case '4': { m_main_frame->select_tab(3); break; }
|
||||
#ifdef __APPLE__
|
||||
case 'f':
|
||||
#else /* __APPLE__ */
|
||||
case WXK_CONTROL_F:
|
||||
#endif /* __APPLE__ */
|
||||
case 'F': { m_main_frame->plater()->search(false); break; }
|
||||
default:break;
|
||||
}
|
||||
}
|
||||
});
|
||||
#endif // !ENABLE_LAYOUT_NO_RESTART
|
||||
|
||||
#if ENABLE_LAYOUT_NO_RESTART
|
||||
this->Bind(wxEVT_SHOW, [this](wxShowEvent& evt) {
|
||||
|
||||
auto key_up_handker = [this](wxKeyEvent& evt) {
|
||||
@ -1791,13 +1621,9 @@ SettingsDialog::SettingsDialog(MainFrame* mainframe)
|
||||
m_tabpanel->Unbind(wxEVT_KEY_UP, key_up_handker);
|
||||
}
|
||||
});
|
||||
#endif // ENABLE_LAYOUT_NO_RESTART
|
||||
|
||||
// initialize layout
|
||||
auto sizer = new wxBoxSizer(wxVERTICAL);
|
||||
#if !ENABLE_LAYOUT_NO_RESTART
|
||||
sizer->Add(m_tabpanel, 1, wxEXPAND);
|
||||
#endif // !ENABLE_LAYOUT_NO_RESTART
|
||||
sizer->SetSizeHints(this);
|
||||
SetSizer(sizer);
|
||||
Fit();
|
||||
|
@ -55,11 +55,7 @@ class SettingsDialog : public DPIDialog
|
||||
public:
|
||||
SettingsDialog(MainFrame* mainframe);
|
||||
~SettingsDialog() {}
|
||||
#if ENABLE_LAYOUT_NO_RESTART
|
||||
void set_tabpanel(wxNotebook* tabpanel) { m_tabpanel = tabpanel; }
|
||||
#else
|
||||
wxNotebook* get_tabpanel() { return m_tabpanel; }
|
||||
#endif // ENABLE_LAYOUT_NO_RESTART
|
||||
|
||||
protected:
|
||||
void on_dpi_changed(const wxRect& suggested_rect) override;
|
||||
@ -119,7 +115,6 @@ class MainFrame : public DPIFrame
|
||||
|
||||
wxFileHistory m_recent_projects;
|
||||
|
||||
#if ENABLE_LAYOUT_NO_RESTART
|
||||
enum class ESettingsLayout
|
||||
{
|
||||
Unknown,
|
||||
@ -129,13 +124,6 @@ class MainFrame : public DPIFrame
|
||||
};
|
||||
|
||||
ESettingsLayout m_layout{ ESettingsLayout::Unknown };
|
||||
#else
|
||||
enum SettingsLayout {
|
||||
slOld = 0,
|
||||
slNew,
|
||||
slDlg,
|
||||
} m_layout;
|
||||
#endif // ENABLE_LAYOUT_NO_RESTART
|
||||
|
||||
protected:
|
||||
virtual void on_dpi_changed(const wxRect &suggested_rect);
|
||||
@ -145,9 +133,7 @@ public:
|
||||
MainFrame();
|
||||
~MainFrame() = default;
|
||||
|
||||
#if ENABLE_LAYOUT_NO_RESTART
|
||||
void update_layout();
|
||||
#endif // ENABLE_LAYOUT_NO_RESTART
|
||||
|
||||
// Called when closing the application and when switching the application language.
|
||||
void shutdown();
|
||||
@ -190,12 +176,8 @@ public:
|
||||
|
||||
Plater* m_plater { nullptr };
|
||||
wxNotebook* m_tabpanel { nullptr };
|
||||
#if ENABLE_LAYOUT_NO_RESTART
|
||||
SettingsDialog m_settings_dialog;
|
||||
wxWindow* m_plater_page{ nullptr };
|
||||
#else
|
||||
SettingsDialog* m_settings_dialog { nullptr };
|
||||
#endif // ENABLE_LAYOUT_NO_RESTART
|
||||
wxProgressDialog* m_progress_dialog { nullptr };
|
||||
std::shared_ptr<ProgressStatusBar> m_statusbar;
|
||||
|
||||
|
@ -134,7 +134,7 @@ bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d&
|
||||
Vec3d direction;
|
||||
line_from_mouse_pos(mouse_pos, trafo, camera, point, direction);
|
||||
|
||||
std::vector<sla::EigenMesh3D::hit_result> hits = m_emesh.query_ray_hits(point, direction);
|
||||
std::vector<sla::IndexedMesh::hit_result> hits = m_emesh.query_ray_hits(point, direction);
|
||||
|
||||
if (hits.empty())
|
||||
return false; // no intersection found
|
||||
@ -184,7 +184,7 @@ std::vector<unsigned> MeshRaycaster::get_unobscured_idxs(const Geometry::Transfo
|
||||
|
||||
bool is_obscured = false;
|
||||
// Cast a ray in the direction of the camera and look for intersection with the mesh:
|
||||
std::vector<sla::EigenMesh3D::hit_result> hits;
|
||||
std::vector<sla::IndexedMesh::hit_result> hits;
|
||||
// Offset the start of the ray by EPSILON to account for numerical inaccuracies.
|
||||
hits = m_emesh.query_ray_hits((inverse_trafo * pt + direction_to_camera_mesh * EPSILON).cast<double>(),
|
||||
direction_to_camera.cast<double>());
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r/Geometry.hpp"
|
||||
#include "libslic3r/SLA/EigenMesh3D.hpp"
|
||||
#include "libslic3r/SLA/IndexedMesh.hpp"
|
||||
#include "admesh/stl.h"
|
||||
|
||||
#include "slic3r/GUI/3DScene.hpp"
|
||||
@ -147,7 +147,7 @@ public:
|
||||
Vec3f get_triangle_normal(size_t facet_idx) const;
|
||||
|
||||
private:
|
||||
sla::EigenMesh3D m_emesh;
|
||||
sla::IndexedMesh m_emesh;
|
||||
std::vector<stl_normal> m_normals;
|
||||
};
|
||||
|
||||
|
@ -1,12 +1,13 @@
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "Mouse3DController.hpp"
|
||||
|
||||
#include "Camera.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "PresetBundle.hpp"
|
||||
#include "AppConfig.hpp"
|
||||
#include "GLCanvas3D.hpp"
|
||||
#include "Plater.hpp"
|
||||
#include "NotificationManager.hpp"
|
||||
|
||||
#include <wx/glcanvas.h>
|
||||
|
||||
@ -239,8 +240,7 @@ void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const
|
||||
|
||||
// when the user clicks on [X] or [Close] button we need to trigger
|
||||
// an extra frame to let the dialog disappear
|
||||
if (m_settings_dialog_closed_by_user)
|
||||
{
|
||||
if (m_settings_dialog_closed_by_user) {
|
||||
m_show_settings_dialog = false;
|
||||
m_settings_dialog_closed_by_user = false;
|
||||
canvas.request_extra_frame();
|
||||
@ -261,13 +261,10 @@ void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const
|
||||
|
||||
static ImVec2 last_win_size(0.0f, 0.0f);
|
||||
bool shown = true;
|
||||
if (imgui.begin(_(L("3Dconnexion settings")), &shown, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse))
|
||||
{
|
||||
if (shown)
|
||||
{
|
||||
if (imgui.begin(_L("3Dconnexion settings"), &shown, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse)) {
|
||||
if (shown) {
|
||||
ImVec2 win_size = ImGui::GetWindowSize();
|
||||
if ((last_win_size.x != win_size.x) || (last_win_size.y != win_size.y))
|
||||
{
|
||||
if (last_win_size.x != win_size.x || last_win_size.y != win_size.y) {
|
||||
// when the user clicks on [X] button, the next time the dialog is shown
|
||||
// has a dummy size, so we trigger an extra frame to let it have the correct size
|
||||
last_win_size = win_size;
|
||||
@ -275,59 +272,51 @@ void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const
|
||||
}
|
||||
|
||||
const ImVec4& color = ImGui::GetStyleColorVec4(ImGuiCol_Separator);
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, color);
|
||||
imgui.text(_(L("Device:")));
|
||||
ImGui::PopStyleColor();
|
||||
imgui.text_colored(color, _L("Device:"));
|
||||
ImGui::SameLine();
|
||||
imgui.text(m_device_str);
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, color);
|
||||
imgui.text(_(L("Speed:")));
|
||||
ImGui::PopStyleColor();
|
||||
imgui.text_colored(color, _L("Speed:"));
|
||||
|
||||
float translation_scale = (float)params_copy.translation.scale / Params::DefaultTranslationScale;
|
||||
if (imgui.slider_float(_(L("Translation")) + "##1", &translation_scale, 0.1f, 10.0f, "%.1f")) {
|
||||
if (imgui.slider_float(_L("Translation") + "##1", &translation_scale, 0.1f, 10.0f, "%.1f")) {
|
||||
params_copy.translation.scale = Params::DefaultTranslationScale * (double)translation_scale;
|
||||
params_changed = true;
|
||||
}
|
||||
|
||||
float rotation_scale = params_copy.rotation.scale / Params::DefaultRotationScale;
|
||||
if (imgui.slider_float(_(L("Rotation")) + "##1", &rotation_scale, 0.1f, 10.0f, "%.1f")) {
|
||||
if (imgui.slider_float(_L("Rotation") + "##1", &rotation_scale, 0.1f, 10.0f, "%.1f")) {
|
||||
params_copy.rotation.scale = Params::DefaultRotationScale * rotation_scale;
|
||||
params_changed = true;
|
||||
}
|
||||
|
||||
float zoom_scale = params_copy.zoom.scale / Params::DefaultZoomScale;
|
||||
if (imgui.slider_float(_(L("Zoom")), &zoom_scale, 0.1f, 10.0f, "%.1f")) {
|
||||
if (imgui.slider_float(_L("Zoom"), &zoom_scale, 0.1f, 10.0f, "%.1f")) {
|
||||
params_copy.zoom.scale = Params::DefaultZoomScale * zoom_scale;
|
||||
params_changed = true;
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, color);
|
||||
imgui.text(_(L("Deadzone:")));
|
||||
ImGui::PopStyleColor();
|
||||
imgui.text_colored(color, _L("Deadzone:"));
|
||||
|
||||
float translation_deadzone = (float)params_copy.translation.deadzone;
|
||||
if (imgui.slider_float(_(L("Translation")) + "/" + _(L("Zoom")), &translation_deadzone, 0.0f, (float)Params::MaxTranslationDeadzone, "%.2f")) {
|
||||
if (imgui.slider_float(_L("Translation") + "/" + _L("Zoom"), &translation_deadzone, 0.0f, (float)Params::MaxTranslationDeadzone, "%.2f")) {
|
||||
params_copy.translation.deadzone = (double)translation_deadzone;
|
||||
params_changed = true;
|
||||
}
|
||||
|
||||
float rotation_deadzone = params_copy.rotation.deadzone;
|
||||
if (imgui.slider_float(_(L("Rotation")) + "##2", &rotation_deadzone, 0.0f, Params::MaxRotationDeadzone, "%.2f")) {
|
||||
if (imgui.slider_float(_L("Rotation") + "##2", &rotation_deadzone, 0.0f, Params::MaxRotationDeadzone, "%.2f")) {
|
||||
params_copy.rotation.deadzone = rotation_deadzone;
|
||||
params_changed = true;
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, color);
|
||||
imgui.text(_(L("Options:")));
|
||||
ImGui::PopStyleColor();
|
||||
imgui.text_colored(color, _L("Options:"));
|
||||
|
||||
bool swap_yz = params_copy.swap_yz;
|
||||
if (imgui.checkbox("Swap Y/Z axes", swap_yz)) {
|
||||
if (imgui.checkbox(_L("Swap Y/Z axes"), swap_yz)) {
|
||||
params_copy.swap_yz = swap_yz;
|
||||
params_changed = true;
|
||||
}
|
||||
@ -335,25 +324,20 @@ void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const
|
||||
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
ImGui::Separator();
|
||||
ImGui::Separator();
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, color);
|
||||
imgui.text("DEBUG:");
|
||||
imgui.text("Vectors:");
|
||||
ImGui::PopStyleColor();
|
||||
imgui.text_colored(color, "DEBUG:");
|
||||
imgui.text_colored(color, "Vectors:");
|
||||
Vec3f translation = m_state.get_first_vector_of_type(State::QueueItem::TranslationType).cast<float>();
|
||||
Vec3f rotation = m_state.get_first_vector_of_type(State::QueueItem::RotationType).cast<float>();
|
||||
ImGui::InputFloat3("Translation##3", translation.data(), "%.3f", ImGuiInputTextFlags_ReadOnly);
|
||||
ImGui::InputFloat3("Rotation##3", rotation.data(), "%.3f", ImGuiInputTextFlags_ReadOnly);
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, color);
|
||||
imgui.text("Queue size:");
|
||||
ImGui::PopStyleColor();
|
||||
imgui.text_colored(color, "Queue size:");
|
||||
|
||||
int input_queue_size_current[2] = { int(m_state.input_queue_size_current()), int(m_state.input_queue_max_size_achieved) };
|
||||
ImGui::InputInt2("Current##4", input_queue_size_current, ImGuiInputTextFlags_ReadOnly);
|
||||
|
||||
int input_queue_size_param = int(params_copy.input_queue_max_size);
|
||||
if (ImGui::InputInt("Max size", &input_queue_size_param, 1, 1, ImGuiInputTextFlags_ReadOnly))
|
||||
{
|
||||
if (ImGui::InputInt("Max size", &input_queue_size_param, 1, 1, ImGuiInputTextFlags_ReadOnly)) {
|
||||
if (input_queue_size_param > 0) {
|
||||
params_copy.input_queue_max_size = input_queue_size_param;
|
||||
params_changed = true;
|
||||
@ -361,23 +345,19 @@ void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, color);
|
||||
imgui.text("Camera:");
|
||||
ImGui::PopStyleColor();
|
||||
imgui.text_colored(color, "Camera:");
|
||||
Vec3f target = wxGetApp().plater()->get_camera().get_target().cast<float>();
|
||||
ImGui::InputFloat3("Target", target.data(), "%.3f", ImGuiInputTextFlags_ReadOnly);
|
||||
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
|
||||
ImGui::Separator();
|
||||
if (imgui.button(_(L("Close"))))
|
||||
{
|
||||
if (imgui.button(_L("Close"))) {
|
||||
// the user clicked on the [Close] button
|
||||
m_settings_dialog_closed_by_user = true;
|
||||
canvas.set_as_dirty();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
else {
|
||||
// the user clicked on the [X] button
|
||||
m_settings_dialog_closed_by_user = true;
|
||||
canvas.set_as_dirty();
|
||||
@ -424,6 +404,8 @@ void Mouse3DController::disconnected()
|
||||
m_params_by_device[m_device_str] = m_params_ui;
|
||||
m_device_str.clear();
|
||||
m_connected = false;
|
||||
wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::Mouse3dDisconnected, *(wxGetApp().plater()->get_current_canvas3D()));
|
||||
|
||||
wxGetApp().plater()->CallAfter([]() {
|
||||
Plater *plater = wxGetApp().plater();
|
||||
if (plater != nullptr) {
|
||||
|
918
src/slic3r/GUI/NotificationManager.cpp
Normal file
918
src/slic3r/GUI/NotificationManager.cpp
Normal file
@ -0,0 +1,918 @@
|
||||
#include "NotificationManager.hpp"
|
||||
|
||||
#include "GUI_App.hpp"
|
||||
#include "Plater.hpp"
|
||||
#include "GLCanvas3D.hpp"
|
||||
#include "ImGuiWrapper.hpp"
|
||||
|
||||
#include "wxExtensions.hpp"
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <wx/glcanvas.h>
|
||||
#include <iostream>
|
||||
|
||||
|
||||
|
||||
|
||||
#define NOTIFICATION_MAX_MOVE 3.0f
|
||||
|
||||
#define GAP_WIDTH 10.0f
|
||||
#define SPACE_RIGHT_PANEL 10.0f
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
wxDEFINE_EVENT(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED, EjectDriveNotificationClickedEvent);
|
||||
wxDEFINE_EVENT(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED, ExportGcodeNotificationClickedEvent);
|
||||
wxDEFINE_EVENT(EVT_PRESET_UPDATE_AVIABLE_CLICKED, PresetUpdateAviableClickedEvent);
|
||||
|
||||
namespace Notifications_Internal{
|
||||
void push_style_color(ImGuiCol idx, const ImVec4& col, bool fading_out, float current_fade_opacity)
|
||||
{
|
||||
if (fading_out)
|
||||
ImGui::PushStyleColor(idx, ImVec4(col.x, col.y, col.z, col.w * current_fade_opacity));
|
||||
else
|
||||
ImGui::PushStyleColor(idx, col);
|
||||
}
|
||||
}
|
||||
//ScalableBitmap bmp_icon;
|
||||
//------PopNotification--------
|
||||
NotificationManager::PopNotification::PopNotification(const NotificationData &n, const int id, wxEvtHandler* evt_handler) :
|
||||
m_data (n)
|
||||
, m_id (id)
|
||||
, m_remaining_time (n.duration)
|
||||
, m_last_remaining_time (n.duration)
|
||||
, m_counting_down (n.duration != 0)
|
||||
, m_text1 (n.text1)
|
||||
, m_hypertext (n.hypertext)
|
||||
, m_text2 (n.text2)
|
||||
, m_evt_handler (evt_handler)
|
||||
{
|
||||
init();
|
||||
}
|
||||
NotificationManager::PopNotification::~PopNotification()
|
||||
{
|
||||
}
|
||||
NotificationManager::PopNotification::RenderResult NotificationManager::PopNotification::render(GLCanvas3D& canvas, const float& initial_y)
|
||||
{
|
||||
if (m_finished)
|
||||
return RenderResult::Finished;
|
||||
if (m_close_pending) {
|
||||
// request of extra frame will be done in caller function by ret val ClosePending
|
||||
m_finished = true;
|
||||
return RenderResult::ClosePending;
|
||||
}
|
||||
if (m_hidden) {
|
||||
m_top_y = initial_y - GAP_WIDTH;
|
||||
return RenderResult::Static;
|
||||
}
|
||||
RenderResult ret_val = m_counting_down ? RenderResult::Countdown : RenderResult::Static;
|
||||
Size cnv_size = canvas.get_canvas_size();
|
||||
ImGuiWrapper& imgui = *wxGetApp().imgui();
|
||||
bool shown = true;
|
||||
std::string name;
|
||||
ImVec2 mouse_pos = ImGui::GetMousePos();
|
||||
|
||||
if (m_line_height != ImGui::CalcTextSize("A").y)
|
||||
init();
|
||||
|
||||
set_next_window_size(imgui);
|
||||
|
||||
//top y of window
|
||||
m_top_y = initial_y + m_window_height;
|
||||
//top right position
|
||||
ImVec2 win_pos(1.0f * (float)cnv_size.get_width() - SPACE_RIGHT_PANEL, 1.0f * (float)cnv_size.get_height() - m_top_y);
|
||||
imgui.set_next_window_pos(win_pos.x, win_pos.y, ImGuiCond_Always, 1.0f, 0.0f);
|
||||
imgui.set_next_window_size(m_window_width, m_window_height, ImGuiCond_Always);
|
||||
|
||||
//find if hovered
|
||||
if (mouse_pos.x < win_pos.x && mouse_pos.x > win_pos.x - m_window_width && mouse_pos.y > win_pos.y&& mouse_pos.y < win_pos.y + m_window_height)
|
||||
{
|
||||
ImGui::SetNextWindowFocus();
|
||||
ret_val = RenderResult::Hovered;
|
||||
//reset fading
|
||||
m_fading_out = false;
|
||||
m_current_fade_opacity = 1.f;
|
||||
m_remaining_time = m_data.duration;
|
||||
m_countdown_frame = 0;
|
||||
}
|
||||
|
||||
if (m_counting_down && m_remaining_time < 0)
|
||||
m_close_pending = true;
|
||||
|
||||
if (m_close_pending) {
|
||||
// request of extra frame will be done in caller function by ret val ClosePending
|
||||
m_finished = true;
|
||||
return RenderResult::ClosePending;
|
||||
}
|
||||
|
||||
// color change based on fading out
|
||||
bool fading_pop = false;
|
||||
if (m_fading_out) {
|
||||
if (!m_paused)
|
||||
m_current_fade_opacity -= 1.f / ((m_fading_time + 1.f) * 60.f);
|
||||
Notifications_Internal::push_style_color(ImGuiCol_WindowBg, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), m_fading_out, m_current_fade_opacity);
|
||||
Notifications_Internal::push_style_color(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_Text), m_fading_out, m_current_fade_opacity);
|
||||
fading_pop = true;
|
||||
}
|
||||
// background color
|
||||
if (m_is_gray) {
|
||||
ImVec4 backcolor(0.7f, 0.7f, 0.7f, 0.5f);
|
||||
Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity);
|
||||
} else if (m_data.level == NotificationLevel::ErrorNotification) {
|
||||
ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg);
|
||||
backcolor.x += 0.3f;
|
||||
Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity);
|
||||
} else if (m_data.level == NotificationLevel::WarningNotification) {
|
||||
ImVec4 backcolor = ImGui::GetStyleColorVec4(ImGuiCol_WindowBg);
|
||||
backcolor.x += 0.3f;
|
||||
backcolor.y += 0.15f;
|
||||
Notifications_Internal::push_style_color(ImGuiCol_WindowBg, backcolor, m_fading_out, m_current_fade_opacity);
|
||||
}
|
||||
|
||||
//name of window - probably indentifies window and is shown so last_end add whitespaces according to id
|
||||
for (size_t i = 0; i < m_id; i++)
|
||||
name += " ";
|
||||
if (imgui.begin(name, &shown, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar )) {
|
||||
if (shown) {
|
||||
|
||||
ImVec2 win_size = ImGui::GetWindowSize();
|
||||
|
||||
|
||||
//FIXME: dont forget to us this for texts
|
||||
//GUI::format(_utf8(L()));
|
||||
|
||||
/*
|
||||
//countdown numbers
|
||||
ImGui::SetCursorPosX(15);
|
||||
ImGui::SetCursorPosY(15);
|
||||
imgui.text(std::to_string(m_remaining_time).c_str());
|
||||
*/
|
||||
if(m_counting_down)
|
||||
render_countdown(imgui, win_size.x, win_size.y, win_pos.x, win_pos.y);
|
||||
render_left_sign(imgui);
|
||||
render_text(imgui, win_size.x, win_size.y, win_pos.x, win_pos.y);
|
||||
render_close_button(imgui, win_size.x, win_size.y, win_pos.x, win_pos.y);
|
||||
if (m_multiline && m_lines_count > 3)
|
||||
render_minimize_button(imgui, win_pos.x, win_pos.y);
|
||||
} else {
|
||||
// the user clicked on the [X] button ( ImGuiWindowFlags_NoTitleBar means theres no [X] button)
|
||||
m_close_pending = true;
|
||||
canvas.set_as_dirty();
|
||||
}
|
||||
}
|
||||
imgui.end();
|
||||
|
||||
if (fading_pop) {
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
if (m_is_gray)
|
||||
ImGui::PopStyleColor();
|
||||
else if (m_data.level == NotificationLevel::ErrorNotification)
|
||||
ImGui::PopStyleColor();
|
||||
else if (m_data.level == NotificationLevel::WarningNotification)
|
||||
ImGui::PopStyleColor();
|
||||
return ret_val;
|
||||
}
|
||||
void NotificationManager::PopNotification::init()
|
||||
{
|
||||
std::string text = m_text1 + " " + m_hypertext;
|
||||
int last_end = 0;
|
||||
m_lines_count = 0;
|
||||
|
||||
//determine line width
|
||||
m_line_height = ImGui::CalcTextSize("A").y;
|
||||
|
||||
m_left_indentation = m_line_height;
|
||||
if (m_data.level == NotificationLevel::ErrorNotification || m_data.level == NotificationLevel::WarningNotification) {
|
||||
std::string text;
|
||||
text = (m_data.level == NotificationLevel::ErrorNotification ? ImGui::ErrorMarker : ImGui::WarningMarker);
|
||||
float picture_width = ImGui::CalcTextSize(text.c_str()).x;
|
||||
m_left_indentation = picture_width + m_line_height / 2;
|
||||
}
|
||||
m_window_width_offset = m_left_indentation + m_line_height * 2;
|
||||
m_window_width = m_line_height * 25;
|
||||
|
||||
// count lines
|
||||
m_endlines.clear();
|
||||
while (last_end < text.length() - 1)
|
||||
{
|
||||
int next_hard_end = text.find_first_of('\n', last_end);
|
||||
if (next_hard_end > 0 && ImGui::CalcTextSize(text.substr(last_end, next_hard_end - last_end).c_str()).x < m_window_width - m_window_width_offset) {
|
||||
//next line is ended by '/n'
|
||||
m_endlines.push_back(next_hard_end);
|
||||
last_end = next_hard_end + 1;
|
||||
}
|
||||
else {
|
||||
// find next suitable endline
|
||||
if (ImGui::CalcTextSize(text.substr(last_end).c_str()).x >= m_window_width - 3.5f * m_line_height) {// m_window_width_offset) {
|
||||
// more than one line till end
|
||||
int next_space = text.find_first_of(' ', last_end);
|
||||
if (next_space > 0) {
|
||||
int next_space_candidate = text.find_first_of(' ', next_space + 1);
|
||||
while (next_space_candidate > 0 && ImGui::CalcTextSize(text.substr(last_end, next_space_candidate - last_end).c_str()).x < m_window_width - m_window_width_offset) {
|
||||
next_space = next_space_candidate;
|
||||
next_space_candidate = text.find_first_of(' ', next_space + 1);
|
||||
}
|
||||
m_endlines.push_back(next_space);
|
||||
last_end = next_space + 1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
m_endlines.push_back(text.length());
|
||||
last_end = text.length();
|
||||
}
|
||||
|
||||
}
|
||||
m_lines_count++;
|
||||
}
|
||||
}
|
||||
void NotificationManager::PopNotification::set_next_window_size(ImGuiWrapper& imgui)
|
||||
{
|
||||
if (m_multiline) {
|
||||
m_window_height = m_lines_count * m_line_height;
|
||||
}else
|
||||
{
|
||||
m_window_height = 2 * m_line_height;
|
||||
}
|
||||
m_window_height += 1 * m_line_height; // top and bottom
|
||||
}
|
||||
|
||||
void NotificationManager::PopNotification::render_text(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);
|
||||
float x_offset = m_left_indentation;
|
||||
std::string fulltext = m_text1 + m_hypertext; //+ m_text2;
|
||||
ImVec2 text_size = ImGui::CalcTextSize(fulltext.c_str());
|
||||
// text posistions are calculated by lines count
|
||||
// large texts has "more" button or are displayed whole
|
||||
// smaller texts are divided as one liners and two liners
|
||||
if (m_lines_count > 2) {
|
||||
if (m_multiline) {
|
||||
|
||||
int last_end = 0;
|
||||
float starting_y = m_line_height/2;//10;
|
||||
float shift_y = m_line_height;// -m_line_height / 20;
|
||||
for (size_t i = 0; i < m_lines_count; i++) {
|
||||
std::string line = m_text1.substr(last_end , m_endlines[i] - last_end);
|
||||
last_end = m_endlines[i] + 1;
|
||||
ImGui::SetCursorPosX(x_offset);
|
||||
ImGui::SetCursorPosY(starting_y + i * shift_y);
|
||||
imgui.text(line.c_str());
|
||||
}
|
||||
//hyperlink text
|
||||
if (!m_hypertext.empty())
|
||||
{
|
||||
render_hypertext(imgui, x_offset + ImGui::CalcTextSize(m_text1.substr(m_endlines[m_lines_count - 2] + 1, m_endlines[m_lines_count - 1] - m_endlines[m_lines_count - 2] - 1).c_str()).x, starting_y + (m_lines_count - 1) * shift_y, m_hypertext);
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
// line1
|
||||
ImGui::SetCursorPosX(x_offset);
|
||||
ImGui::SetCursorPosY(win_size.y / 2 - win_size.y / 6 - m_line_height / 2);
|
||||
imgui.text(m_text1.substr(0, m_endlines[0]).c_str());
|
||||
// line2
|
||||
std::string line = m_text1.substr(m_endlines[0] + 1, m_endlines[1] - m_endlines[0] - 1);
|
||||
if (ImGui::CalcTextSize(line.c_str()).x > m_window_width - m_window_width_offset - ImGui::CalcTextSize((".." + _u8L("More")).c_str()).x)
|
||||
{
|
||||
line = line.substr(0, line.length() - 6);
|
||||
line += "..";
|
||||
}else
|
||||
line += " ";
|
||||
ImGui::SetCursorPosX(x_offset);
|
||||
ImGui::SetCursorPosY(win_size.y / 2 + win_size.y / 6 - m_line_height / 2);
|
||||
imgui.text(line.c_str());
|
||||
// "More" hypertext
|
||||
render_hypertext(imgui, x_offset + ImGui::CalcTextSize(line.c_str()).x, win_size.y / 2 + win_size.y / 6 - m_line_height / 2, _u8L("More"), true);
|
||||
}
|
||||
} else {
|
||||
//text 1
|
||||
float cursor_y = win_size.y / 2 - text_size.y / 2;
|
||||
float cursor_x = x_offset;
|
||||
if(m_lines_count > 1) {
|
||||
// line1
|
||||
ImGui::SetCursorPosX(x_offset);
|
||||
ImGui::SetCursorPosY(win_size.y / 2 - win_size.y / 6 - m_line_height / 2);
|
||||
imgui.text(m_text1.substr(0, m_endlines[0]).c_str());
|
||||
// line2
|
||||
std::string line = m_text1.substr(m_endlines[0] + 1);
|
||||
cursor_y = win_size.y / 2 + win_size.y / 6 - m_line_height / 2;
|
||||
ImGui::SetCursorPosX(x_offset);
|
||||
ImGui::SetCursorPosY(cursor_y);
|
||||
imgui.text(line.c_str());
|
||||
cursor_x = x_offset + ImGui::CalcTextSize(line.c_str()).x;
|
||||
} else {
|
||||
ImGui::SetCursorPosX(x_offset);
|
||||
ImGui::SetCursorPosY(cursor_y);
|
||||
imgui.text(m_text1.c_str());
|
||||
cursor_x = x_offset + ImGui::CalcTextSize(m_text1.c_str()).x;
|
||||
}
|
||||
//hyperlink text
|
||||
if (!m_hypertext.empty())
|
||||
{
|
||||
render_hypertext(imgui, cursor_x + 4, cursor_y, m_hypertext);
|
||||
}
|
||||
|
||||
//notification text 2
|
||||
//text 2 is suposed to be after the hyperlink - currently it is not used
|
||||
/*
|
||||
if (!m_text2.empty())
|
||||
{
|
||||
ImVec2 part_size = ImGui::CalcTextSize(m_hypertext.c_str());
|
||||
ImGui::SetCursorPosX(win_size.x / 2 + text_size.x / 2 - part_size.x + 8 - x_offset);
|
||||
ImGui::SetCursorPosY(cursor_y);
|
||||
imgui.text(m_text2.c_str());
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
void NotificationManager::PopNotification::render_hypertext(ImGuiWrapper& imgui, const float text_x, const float text_y, const std::string text, bool more)
|
||||
{
|
||||
//invisible button
|
||||
ImVec2 part_size = ImGui::CalcTextSize(text.c_str());
|
||||
ImGui::SetCursorPosX(text_x -4);
|
||||
ImGui::SetCursorPosY(text_y -5);
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f));
|
||||
if (imgui.button(" ", part_size.x + 6, part_size.y + 10))
|
||||
{
|
||||
if (more)
|
||||
{
|
||||
m_multiline = true;
|
||||
set_next_window_size(imgui);
|
||||
}
|
||||
else {
|
||||
on_text_click();
|
||||
m_close_pending = true;
|
||||
}
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
//hover color
|
||||
ImVec4 orange_color = ImGui::GetStyleColorVec4(ImGuiCol_Button);
|
||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_RectOnly))
|
||||
orange_color.y += 0.2f;
|
||||
|
||||
//text
|
||||
Notifications_Internal::push_style_color(ImGuiCol_Text, orange_color, m_fading_out, m_current_fade_opacity);
|
||||
ImGui::SetCursorPosX(text_x);
|
||||
ImGui::SetCursorPosY(text_y);
|
||||
imgui.text(text.c_str());
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
//underline
|
||||
ImVec2 lineEnd = ImGui::GetItemRectMax();
|
||||
lineEnd.y -= 2;
|
||||
ImVec2 lineStart = lineEnd;
|
||||
lineStart.x = ImGui::GetItemRectMin().x;
|
||||
ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, IM_COL32((int)(orange_color.x * 255), (int)(orange_color.y * 255), (int)(orange_color.z * 255), (int)(orange_color.w * 255.f * (m_fading_out ? m_current_fade_opacity : 1.f))));
|
||||
|
||||
}
|
||||
|
||||
void NotificationManager::PopNotification::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)
|
||||
{
|
||||
ImVec2 win_size(win_size_x, win_size_y);
|
||||
ImVec2 win_pos(win_pos_x, win_pos_y);
|
||||
ImVec4 orange_color = ImGui::GetStyleColorVec4(ImGuiCol_Button);
|
||||
orange_color.w = 0.8f;
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
|
||||
Notifications_Internal::push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_fading_out, m_current_fade_opacity);
|
||||
Notifications_Internal::push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_fading_out, m_current_fade_opacity);
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f));
|
||||
|
||||
|
||||
//button - if part if treggered
|
||||
std::string button_text;
|
||||
button_text = ImGui::CloseIconMarker;
|
||||
|
||||
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_multiline? 2 * m_line_height : 0)),
|
||||
true))
|
||||
{
|
||||
button_text = ImGui::CloseIconHoverMarker;
|
||||
}
|
||||
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.25f);
|
||||
ImGui::SetCursorPosY(win_size.y / 2 - button_size.y/2);
|
||||
if (imgui.button(button_text.c_str(), button_size.x, button_size.y))
|
||||
{
|
||||
m_close_pending = true;
|
||||
}
|
||||
|
||||
//invisible large button
|
||||
ImGui::SetCursorPosX(win_size.x - win_size.x / 10.f);
|
||||
ImGui::SetCursorPosY(0);
|
||||
if (imgui.button(" ", win_size.x / 10.f, win_size.y - (m_multiline ? 2 * m_line_height : 0)))
|
||||
{
|
||||
m_close_pending = true;
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
void NotificationManager::PopNotification::render_countdown(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);
|
||||
|
||||
//countdown dots
|
||||
std::string dot_text;
|
||||
dot_text = m_remaining_time <= (float)m_data.duration / 4 * 3 ? ImGui::TimerDotEmptyMarker : ImGui::TimerDotMarker;
|
||||
ImGui::SetCursorPosX(win_size.x - m_line_height);
|
||||
//ImGui::SetCursorPosY(win_size.y / 2 - 24);
|
||||
ImGui::SetCursorPosY(0);
|
||||
imgui.text(dot_text.c_str());
|
||||
|
||||
dot_text = m_remaining_time < m_data.duration / 2 ? ImGui::TimerDotEmptyMarker : ImGui::TimerDotMarker;
|
||||
ImGui::SetCursorPosX(win_size.x - m_line_height);
|
||||
//ImGui::SetCursorPosY(win_size.y / 2 - 9);
|
||||
ImGui::SetCursorPosY(win_size.y / 2 - m_line_height / 2);
|
||||
imgui.text(dot_text.c_str());
|
||||
|
||||
dot_text = m_remaining_time <= m_data.duration / 4 ? ImGui::TimerDotEmptyMarker : ImGui::TimerDotMarker;
|
||||
ImGui::SetCursorPosX(win_size.x - m_line_height);
|
||||
//ImGui::SetCursorPosY(win_size.y / 2 + 6);
|
||||
ImGui::SetCursorPosY(win_size.y - m_line_height);
|
||||
imgui.text(dot_text.c_str());
|
||||
*/
|
||||
if (!m_fading_out && m_remaining_time <= m_data.duration / 4) {
|
||||
m_fading_out = true;
|
||||
m_fading_time = m_remaining_time;
|
||||
}
|
||||
|
||||
if (m_last_remaining_time != m_remaining_time) {
|
||||
m_last_remaining_time = m_remaining_time;
|
||||
m_countdown_frame = 0;
|
||||
}
|
||||
/*
|
||||
//countdown line
|
||||
ImVec4 orange_color = ImGui::GetStyleColorVec4(ImGuiCol_Button);
|
||||
float invisible_length = ((float)(m_data.duration - m_remaining_time) / (float)m_data.duration * win_size_x);
|
||||
invisible_length -= win_size_x / ((float)m_data.duration * 60.f) * (60 - m_countdown_frame);
|
||||
ImVec2 lineEnd = ImVec2(win_pos_x - invisible_length, win_pos_y + win_size_y - 5);
|
||||
ImVec2 lineStart = ImVec2(win_pos_x - win_size_x, win_pos_y + win_size_y - 5);
|
||||
ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, IM_COL32((int)(orange_color.x * 255), (int)(orange_color.y * 255), (int)(orange_color.z * 255), (int)(orange_color.picture_width * 255.f * (m_fading_out ? m_current_fade_opacity : 1.f))), 2.f);
|
||||
if (!m_paused)
|
||||
m_countdown_frame++;
|
||||
*/
|
||||
}
|
||||
void NotificationManager::PopNotification::render_left_sign(ImGuiWrapper& imgui)
|
||||
{
|
||||
if (m_data.level == NotificationLevel::ErrorNotification || m_data.level == NotificationLevel::WarningNotification) {
|
||||
std::string text;
|
||||
text = (m_data.level == NotificationLevel::ErrorNotification ? ImGui::ErrorMarker : ImGui::WarningMarker);
|
||||
ImGui::SetCursorPosX(m_line_height / 3);
|
||||
ImGui::SetCursorPosY(m_window_height / 2 - m_line_height / 2);
|
||||
imgui.text(text.c_str());
|
||||
}
|
||||
}
|
||||
void NotificationManager::PopNotification::render_minimize_button(ImGuiWrapper& imgui, const float win_pos_x, const float win_pos_y)
|
||||
{
|
||||
ImVec4 orange_color = ImGui::GetStyleColorVec4(ImGuiCol_Button);
|
||||
orange_color.w = 0.8f;
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
|
||||
Notifications_Internal::push_style_color(ImGuiCol_ButtonActive, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), m_fading_out, m_current_fade_opacity);
|
||||
Notifications_Internal::push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_fading_out, m_current_fade_opacity);
|
||||
Notifications_Internal::push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_fading_out, m_current_fade_opacity);
|
||||
|
||||
|
||||
//button - if part if treggered
|
||||
std::string button_text;
|
||||
button_text = ImGui::CloseIconMarker;
|
||||
if (ImGui::IsMouseHoveringRect(ImVec2(win_pos_x - m_window_width / 10.f, win_pos_y + m_window_height - 2 * m_line_height + 1),
|
||||
ImVec2(win_pos_x, win_pos_y + m_window_height),
|
||||
true))
|
||||
{
|
||||
button_text = ImGui::CloseIconHoverMarker;
|
||||
}
|
||||
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(m_window_width - m_line_height * 2.25f);
|
||||
ImGui::SetCursorPosY(m_window_height - button_size.y - 5);
|
||||
if (imgui.button(button_text.c_str(), button_size.x, button_size.y))
|
||||
{
|
||||
m_multiline = false;
|
||||
}
|
||||
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
void NotificationManager::PopNotification::on_text_click()
|
||||
{
|
||||
switch (m_data.type) {
|
||||
case NotificationType::ExportToRemovableFinished :
|
||||
assert(m_evt_handler != nullptr);
|
||||
if (m_evt_handler != nullptr)
|
||||
wxPostEvent(m_evt_handler, EjectDriveNotificationClickedEvent(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED));
|
||||
break;
|
||||
case NotificationType::SlicingComplete :
|
||||
//wxGetApp().plater()->export_gcode(false);
|
||||
assert(m_evt_handler != nullptr);
|
||||
if (m_evt_handler != nullptr)
|
||||
wxPostEvent(m_evt_handler, ExportGcodeNotificationClickedEvent(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED));
|
||||
break;
|
||||
case NotificationType::PresetUpdateAviable :
|
||||
//wxGetApp().plater()->export_gcode(false);
|
||||
assert(m_evt_handler != nullptr);
|
||||
if (m_evt_handler != nullptr)
|
||||
wxPostEvent(m_evt_handler, PresetUpdateAviableClickedEvent(EVT_PRESET_UPDATE_AVIABLE_CLICKED));
|
||||
break;
|
||||
case NotificationType::NewAppAviable:
|
||||
wxLaunchDefaultBrowser("https://github.com/prusa3d/PrusaSlicer/releases");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
void NotificationManager::PopNotification::update(const NotificationData& n)
|
||||
{
|
||||
m_text1 = n.text1;
|
||||
m_hypertext = n.hypertext;
|
||||
m_text2 = n.text2;
|
||||
init();
|
||||
}
|
||||
bool NotificationManager::PopNotification::compare_text(const std::string& text)
|
||||
{
|
||||
std::string t1(m_text1);
|
||||
std::string t2(text);
|
||||
t1.erase(std::remove_if(t1.begin(), t1.end(), ::isspace), t1.end());
|
||||
t2.erase(std::remove_if(t2.begin(), t2.end(), ::isspace), t2.end());
|
||||
if (t1.compare(t2) == 0)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
NotificationManager::SlicingCompleteLargeNotification::SlicingCompleteLargeNotification(const NotificationData& n, const int id, wxEvtHandler* evt_handler, bool large) :
|
||||
NotificationManager::PopNotification(n, id, evt_handler)
|
||||
{
|
||||
set_large(large);
|
||||
}
|
||||
void NotificationManager::SlicingCompleteLargeNotification::render_text(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
|
||||
{
|
||||
if (!m_is_large)
|
||||
PopNotification::render_text(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y);
|
||||
else {
|
||||
ImVec2 win_size(win_size_x, win_size_y);
|
||||
ImVec2 win_pos(win_pos_x, win_pos_y);
|
||||
|
||||
ImVec2 text1_size = ImGui::CalcTextSize(m_text1.c_str());
|
||||
float x_offset = m_left_indentation;
|
||||
std::string fulltext = m_text1 + m_hypertext + m_text2;
|
||||
ImVec2 text_size = ImGui::CalcTextSize(fulltext.c_str());
|
||||
float cursor_y = win_size.y / 2 - text_size.y / 2;
|
||||
if (m_has_print_info) {
|
||||
x_offset = 20;
|
||||
cursor_y = win_size.y / 2 + win_size.y / 6 - text_size.y / 2;
|
||||
ImGui::SetCursorPosX(x_offset);
|
||||
ImGui::SetCursorPosY(cursor_y);
|
||||
imgui.text(m_print_info.c_str());
|
||||
cursor_y = win_size.y / 2 - win_size.y / 6 - text_size.y / 2;
|
||||
}
|
||||
ImGui::SetCursorPosX(x_offset);
|
||||
ImGui::SetCursorPosY(cursor_y);
|
||||
imgui.text(m_text1.c_str());
|
||||
|
||||
render_hypertext(imgui, x_offset + text1_size.x + 4, cursor_y, m_hypertext);
|
||||
|
||||
}
|
||||
}
|
||||
void NotificationManager::SlicingCompleteLargeNotification::set_print_info(std::string info)
|
||||
{
|
||||
m_print_info = info;
|
||||
m_has_print_info = true;
|
||||
if(m_is_large)
|
||||
m_lines_count = 2;
|
||||
}
|
||||
void NotificationManager::SlicingCompleteLargeNotification::set_large(bool l)
|
||||
{
|
||||
m_is_large = l;
|
||||
m_counting_down = !l;
|
||||
m_hypertext = l ? _u8L("Export G-Code.") : std::string();
|
||||
m_hidden = !l;
|
||||
}
|
||||
//------NotificationManager--------
|
||||
NotificationManager::NotificationManager(wxEvtHandler* evt_handler) :
|
||||
m_evt_handler(evt_handler)
|
||||
{
|
||||
}
|
||||
NotificationManager::~NotificationManager()
|
||||
{
|
||||
for (PopNotification* notification : m_pop_notifications)
|
||||
{
|
||||
delete notification;
|
||||
}
|
||||
}
|
||||
void NotificationManager::push_notification(const NotificationType type, GLCanvas3D& canvas, int timestamp)
|
||||
{
|
||||
auto it = std::find_if(basic_notifications.begin(), basic_notifications.end(),
|
||||
boost::bind(&NotificationData::type, _1) == type);
|
||||
if (it != basic_notifications.end())
|
||||
push_notification_data( *it, canvas, timestamp);
|
||||
}
|
||||
void NotificationManager::push_notification(const std::string& text, GLCanvas3D& canvas, int timestamp)
|
||||
{
|
||||
push_notification_data({ NotificationType::CustomNotification, NotificationLevel::RegularNotification, 10, text }, canvas, timestamp );
|
||||
}
|
||||
void NotificationManager::push_notification(const std::string& text, NotificationManager::NotificationLevel level, GLCanvas3D& canvas, int timestamp)
|
||||
{
|
||||
switch (level)
|
||||
{
|
||||
case Slic3r::GUI::NotificationManager::NotificationLevel::RegularNotification:
|
||||
push_notification_data({ NotificationType::CustomNotification, level, 10, text }, canvas, timestamp);
|
||||
break;
|
||||
case Slic3r::GUI::NotificationManager::NotificationLevel::ErrorNotification:
|
||||
push_notification_data({ NotificationType::CustomNotification, level, 0, text }, canvas, timestamp);
|
||||
|
||||
break;
|
||||
case Slic3r::GUI::NotificationManager::NotificationLevel::ImportantNotification:
|
||||
push_notification_data({ NotificationType::CustomNotification, level, 0, text }, canvas, timestamp);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
void NotificationManager::push_slicing_error_notification(const std::string& text, GLCanvas3D& canvas)
|
||||
{
|
||||
set_all_slicing_errors_gray(false);
|
||||
push_notification_data({ NotificationType::SlicingError, NotificationLevel::ErrorNotification, 0, _u8L("ERROR:") + "\n" + text }, canvas, 0);
|
||||
close_notification_of_type(NotificationType::SlicingComplete);
|
||||
}
|
||||
void NotificationManager::push_slicing_warning_notification(const std::string& text, bool gray, GLCanvas3D& canvas, size_t oid, int warning_step)
|
||||
{
|
||||
NotificationData data { NotificationType::SlicingWarning, NotificationLevel::WarningNotification, 0, _u8L("WARNING:") + "\n" + text };
|
||||
|
||||
NotificationManager::SlicingWarningNotification* notification = new NotificationManager::SlicingWarningNotification(data, m_next_id++, m_evt_handler);
|
||||
notification->set_object_id(oid);
|
||||
notification->set_warning_step(warning_step);
|
||||
if
|
||||
(push_notification_data(notification, canvas, 0)) {
|
||||
notification->set_gray(gray);
|
||||
}
|
||||
else {
|
||||
delete notification;
|
||||
}
|
||||
|
||||
}
|
||||
void NotificationManager::push_plater_error_notification(const std::string& text, GLCanvas3D& canvas)
|
||||
{
|
||||
push_notification_data({ NotificationType::PlaterError, NotificationLevel::ErrorNotification, 0, _u8L("ERROR:") + "\n" + text }, canvas, 0);
|
||||
}
|
||||
void NotificationManager::push_plater_warning_notification(const std::string& text, GLCanvas3D& canvas)
|
||||
{
|
||||
push_notification_data({ NotificationType::PlaterWarning, NotificationLevel::WarningNotification, 0, _u8L("WARNING:") + "\n" + text }, canvas, 0);
|
||||
}
|
||||
void NotificationManager::close_plater_error_notification()
|
||||
{
|
||||
for (PopNotification* notification : m_pop_notifications) {
|
||||
if (notification->get_type() == NotificationType::PlaterError) {
|
||||
notification->close();
|
||||
}
|
||||
}
|
||||
}
|
||||
void NotificationManager::close_plater_warning_notification(const std::string& text)
|
||||
{
|
||||
for (PopNotification* notification : m_pop_notifications) {
|
||||
if (notification->get_type() == NotificationType::PlaterWarning && notification->compare_text(_u8L("WARNING:") + "\n" + text)) {
|
||||
notification->close();
|
||||
}
|
||||
}
|
||||
}
|
||||
void NotificationManager::set_all_slicing_errors_gray(bool g)
|
||||
{
|
||||
for (PopNotification* notification : m_pop_notifications) {
|
||||
if (notification->get_type() == NotificationType::SlicingError) {
|
||||
notification->set_gray(g);
|
||||
}
|
||||
}
|
||||
}
|
||||
void NotificationManager::set_all_slicing_warnings_gray(bool g)
|
||||
{
|
||||
for (PopNotification* notification : m_pop_notifications) {
|
||||
if (notification->get_type() == NotificationType::SlicingWarning) {
|
||||
notification->set_gray(g);
|
||||
}
|
||||
}
|
||||
}
|
||||
void NotificationManager::set_slicing_warning_gray(const std::string& text, bool g)
|
||||
{
|
||||
for (PopNotification* notification : m_pop_notifications) {
|
||||
if (notification->get_type() == NotificationType::SlicingWarning && notification->compare_text(text)) {
|
||||
notification->set_gray(g);
|
||||
}
|
||||
}
|
||||
}
|
||||
void NotificationManager::close_slicing_errors_and_warnings()
|
||||
{
|
||||
for (PopNotification* notification : m_pop_notifications) {
|
||||
if (notification->get_type() == NotificationType::SlicingError || notification->get_type() == NotificationType::SlicingWarning) {
|
||||
notification->close();
|
||||
}
|
||||
}
|
||||
}
|
||||
void NotificationManager::push_slicing_complete_notification(GLCanvas3D& canvas, int timestamp, bool large)
|
||||
{
|
||||
std::string hypertext;
|
||||
int time = 10;
|
||||
if(large)
|
||||
{
|
||||
hypertext = _u8L("Export G-Code.");
|
||||
time = 0;
|
||||
}
|
||||
NotificationData data{ NotificationType::SlicingComplete, NotificationLevel::RegularNotification, time, _u8L("Slicing finished."), hypertext };
|
||||
|
||||
NotificationManager::SlicingCompleteLargeNotification* notification = new NotificationManager::SlicingCompleteLargeNotification(data, m_next_id++, m_evt_handler, large);
|
||||
if (push_notification_data(notification, canvas, timestamp)) {
|
||||
} else {
|
||||
delete notification;
|
||||
}
|
||||
}
|
||||
void NotificationManager::set_slicing_complete_print_time(std::string info)
|
||||
{
|
||||
for (PopNotification* notification : m_pop_notifications) {
|
||||
if (notification->get_type() == NotificationType::SlicingComplete) {
|
||||
dynamic_cast<SlicingCompleteLargeNotification*>(notification)->set_print_info(info);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
void NotificationManager::set_slicing_complete_large(bool large)
|
||||
{
|
||||
for (PopNotification* notification : m_pop_notifications) {
|
||||
if (notification->get_type() == NotificationType::SlicingComplete) {
|
||||
dynamic_cast<SlicingCompleteLargeNotification*>(notification)->set_large(large);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
void NotificationManager::close_notification_of_type(const NotificationType type)
|
||||
{
|
||||
for (PopNotification* notification : m_pop_notifications) {
|
||||
if (notification->get_type() == type) {
|
||||
notification->close();
|
||||
}
|
||||
}
|
||||
}
|
||||
void NotificationManager::compare_warning_oids(const std::vector<size_t>& living_oids)
|
||||
{
|
||||
for (PopNotification* notification : m_pop_notifications) {
|
||||
if (notification->get_type() == NotificationType::SlicingWarning) {
|
||||
auto w = dynamic_cast<SlicingWarningNotification*>(notification);
|
||||
bool found = false;
|
||||
for (size_t oid : living_oids) {
|
||||
if (w->get_object_id() == oid) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
notification->close();
|
||||
}
|
||||
}
|
||||
}
|
||||
bool NotificationManager::push_notification_data(const NotificationData ¬ification_data, GLCanvas3D& canvas, int timestamp)
|
||||
{
|
||||
PopNotification* n = new PopNotification(notification_data, m_next_id++, m_evt_handler);
|
||||
bool r = push_notification_data(n, canvas, timestamp);
|
||||
if (!r)
|
||||
delete n;
|
||||
return r;
|
||||
}
|
||||
bool NotificationManager::push_notification_data(NotificationManager::PopNotification* notification, GLCanvas3D& canvas, int timestamp)
|
||||
{
|
||||
// if timestamped notif, push only new one
|
||||
if (timestamp != 0) {
|
||||
if (m_used_timestamps.find(timestamp) == m_used_timestamps.end()) {
|
||||
m_used_timestamps.insert(timestamp);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!this->find_older(notification)) {
|
||||
m_pop_notifications.emplace_back(notification);
|
||||
canvas.request_extra_frame();
|
||||
return true;
|
||||
} else {
|
||||
m_pop_notifications.back()->update(notification->get_data());
|
||||
canvas.request_extra_frame();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void NotificationManager::render_notifications(GLCanvas3D& canvas)
|
||||
{
|
||||
float last_x = 0.0f;
|
||||
float current_height = 0.0f;
|
||||
bool request_next_frame = false;
|
||||
bool render_main = false;
|
||||
bool hovered = false;
|
||||
sort_notifications();
|
||||
// iterate thru notifications and render them / erease them
|
||||
for (auto it = m_pop_notifications.begin(); it != m_pop_notifications.end();) {
|
||||
if ((*it)->get_finished()) {
|
||||
delete (*it);
|
||||
it = m_pop_notifications.erase(it);
|
||||
} else {
|
||||
(*it)->set_paused(m_hovered);
|
||||
PopNotification::RenderResult res = (*it)->render(canvas, last_x);
|
||||
if (res != PopNotification::RenderResult::Finished) {
|
||||
last_x = (*it)->get_top() + GAP_WIDTH;
|
||||
current_height = std::max(current_height, (*it)->get_current_top());
|
||||
render_main = true;
|
||||
}
|
||||
if (res == PopNotification::RenderResult::Countdown || res == PopNotification::RenderResult::ClosePending || res == PopNotification::RenderResult::Finished)
|
||||
request_next_frame = true;
|
||||
if (res == PopNotification::RenderResult::Hovered)
|
||||
hovered = true;
|
||||
++it;
|
||||
}
|
||||
}
|
||||
m_hovered = hovered;
|
||||
|
||||
//actualizate timers and request frame if needed
|
||||
wxWindow* p = dynamic_cast<wxWindow*> (wxGetApp().plater());
|
||||
while (p->GetParent())
|
||||
p = p->GetParent();
|
||||
wxTopLevelWindow* top_level_wnd = dynamic_cast<wxTopLevelWindow*>(p);
|
||||
if (!top_level_wnd->IsActive())
|
||||
return;
|
||||
|
||||
if (!m_hovered && m_last_time < wxGetLocalTime())
|
||||
{
|
||||
if (wxGetLocalTime() - m_last_time == 1)
|
||||
{
|
||||
for(auto notification : m_pop_notifications)
|
||||
{
|
||||
notification->substract_remaining_time();
|
||||
}
|
||||
}
|
||||
m_last_time = wxGetLocalTime();
|
||||
}
|
||||
|
||||
if (request_next_frame)
|
||||
canvas.request_extra_frame();
|
||||
}
|
||||
|
||||
|
||||
void NotificationManager::sort_notifications()
|
||||
{
|
||||
std::sort(m_pop_notifications.begin(), m_pop_notifications.end(), [](PopNotification* n1, PopNotification* n2) {
|
||||
int n1l = (int)n1->get_data().level;
|
||||
int n2l = (int)n2->get_data().level;
|
||||
if (n1l == n2l && n1->get_is_gray() && !n2->get_is_gray())
|
||||
return true;
|
||||
return (n1l < n2l);
|
||||
});
|
||||
}
|
||||
|
||||
bool NotificationManager::find_older(NotificationManager::PopNotification* notification)
|
||||
{
|
||||
NotificationType type = notification->get_type();
|
||||
std::string text = notification->get_data().text1;
|
||||
for (auto it = m_pop_notifications.begin(); it != m_pop_notifications.end(); ++it) {
|
||||
if((*it)->get_type() == type && !(*it)->get_finished()) {
|
||||
if (type == NotificationType::CustomNotification || type == NotificationType::PlaterWarning) {
|
||||
if (!(*it)->compare_text(text))
|
||||
continue;
|
||||
}else if (type == NotificationType::SlicingWarning) {
|
||||
auto w1 = dynamic_cast<SlicingWarningNotification*>(notification);
|
||||
auto w2 = dynamic_cast<SlicingWarningNotification*>(*it);
|
||||
if (w1 != nullptr && w2 != nullptr) {
|
||||
if (!(*it)->compare_text(text) || w1->get_object_id() != w2->get_object_id()) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (it != m_pop_notifications.end() - 1)
|
||||
std::rotate(it, it + 1, m_pop_notifications.end());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void NotificationManager::dpi_changed()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}//namespace GUI
|
||||
}//namespace Slic3r
|
268
src/slic3r/GUI/NotificationManager.hpp
Normal file
268
src/slic3r/GUI/NotificationManager.hpp
Normal file
@ -0,0 +1,268 @@
|
||||
#ifndef slic3r_GUI_NotificationManager_hpp_
|
||||
#define slic3r_GUI_NotificationManager_hpp_
|
||||
|
||||
#include "Event.hpp"
|
||||
#include "I18N.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
using EjectDriveNotificationClickedEvent = SimpleEvent;
|
||||
wxDECLARE_EVENT(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED, EjectDriveNotificationClickedEvent);
|
||||
using ExportGcodeNotificationClickedEvent = SimpleEvent;
|
||||
wxDECLARE_EVENT(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED, ExportGcodeNotificationClickedEvent);
|
||||
using PresetUpdateAviableClickedEvent = SimpleEvent;
|
||||
wxDECLARE_EVENT(EVT_PRESET_UPDATE_AVIABLE_CLICKED, PresetUpdateAviableClickedEvent);
|
||||
|
||||
class GLCanvas3D;
|
||||
class ImGuiWrapper;
|
||||
|
||||
enum class NotificationType
|
||||
{
|
||||
CustomNotification,
|
||||
SlicingComplete,
|
||||
SlicingNotPossible,
|
||||
ExportToRemovableFinished,
|
||||
Mouse3dDisconnected,
|
||||
Mouse3dConnected,
|
||||
NewPresetsAviable,
|
||||
NewAppAviable,
|
||||
PresetUpdateAviable,
|
||||
LoadingFailed,
|
||||
ValidateError, // currently not used - instead Slicing error is used for both slicing and validate errors
|
||||
SlicingError,
|
||||
SlicingWarning,
|
||||
PlaterError,
|
||||
PlaterWarning,
|
||||
ApplyError
|
||||
|
||||
};
|
||||
class NotificationManager
|
||||
{
|
||||
public:
|
||||
enum class NotificationLevel : int
|
||||
{
|
||||
ErrorNotification = 4,
|
||||
WarningNotification = 3,
|
||||
ImportantNotification = 2,
|
||||
RegularNotification = 1,
|
||||
};
|
||||
// duration 0 means not disapearing
|
||||
struct NotificationData {
|
||||
NotificationType type;
|
||||
NotificationLevel level;
|
||||
const int duration;
|
||||
const std::string text1;
|
||||
const std::string hypertext = std::string();
|
||||
const std::string text2 = std::string();
|
||||
};
|
||||
|
||||
//Pop notification - shows only once to user.
|
||||
class PopNotification
|
||||
{
|
||||
public:
|
||||
enum class RenderResult
|
||||
{
|
||||
Finished,
|
||||
ClosePending,
|
||||
Static,
|
||||
Countdown,
|
||||
Hovered
|
||||
};
|
||||
PopNotification(const NotificationData &n, const int id, wxEvtHandler* evt_handler);
|
||||
virtual ~PopNotification();
|
||||
RenderResult render(GLCanvas3D& canvas, const float& initial_y);
|
||||
// close will dissapear notification on next render
|
||||
void close() { m_close_pending = true; }
|
||||
// data from newer notification of same type
|
||||
void update(const NotificationData& n);
|
||||
bool get_finished() const { return m_finished; }
|
||||
// returns top after movement
|
||||
float get_top() const { return m_top_y; }
|
||||
//returns top in actual frame
|
||||
float get_current_top() const { return m_top_y; }
|
||||
const NotificationType get_type() const { return m_data.type; }
|
||||
const NotificationData get_data() const { return m_data; }
|
||||
const bool get_is_gray() const { return m_is_gray; }
|
||||
// Call equals one second down
|
||||
void substract_remaining_time() { m_remaining_time--; }
|
||||
void set_gray(bool g) { m_is_gray = g; }
|
||||
void set_paused(bool p) { m_paused = p; }
|
||||
bool compare_text(const std::string& text);
|
||||
protected:
|
||||
// Call after every size change
|
||||
void init();
|
||||
// Calculetes correct size but not se it in imgui!
|
||||
virtual void set_next_window_size(ImGuiWrapper& imgui);
|
||||
virtual void render_text(ImGuiWrapper& imgui,
|
||||
const float win_size_x, const float win_size_y,
|
||||
const float win_pos_x , const float win_pos_y);
|
||||
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);
|
||||
void render_countdown(ImGuiWrapper& imgui,
|
||||
const float win_size_x, const float win_size_y,
|
||||
const float win_pos_x , const float win_pos_y);
|
||||
void render_hypertext(ImGuiWrapper& imgui,
|
||||
const float text_x, const float text_y,
|
||||
const std::string text,
|
||||
bool more = false);
|
||||
void render_left_sign(ImGuiWrapper& imgui);
|
||||
void render_minimize_button(ImGuiWrapper& imgui,
|
||||
const float win_pos_x, const float win_pos_y);
|
||||
void on_text_click();
|
||||
|
||||
const NotificationData m_data;
|
||||
|
||||
int m_id;
|
||||
// Main text
|
||||
std::string m_text1;
|
||||
// Clickable text
|
||||
std::string m_hypertext;
|
||||
// Aditional text after hypertext - currently not used
|
||||
std::string m_text2;
|
||||
// Countdown variables
|
||||
long m_remaining_time;
|
||||
bool m_counting_down;
|
||||
long m_last_remaining_time;
|
||||
bool m_paused{ false };
|
||||
int m_countdown_frame{ 0 };
|
||||
bool m_fading_out{ false };
|
||||
// total time left when fading beggins
|
||||
float m_fading_time{ 0.0f };
|
||||
float m_current_fade_opacity{ 1.f };
|
||||
// If hidden the notif is alive but not visible to user
|
||||
bool m_hidden { false };
|
||||
// m_finished = true - does not render, marked to delete
|
||||
bool m_finished { false };
|
||||
// Will go to m_finished next render
|
||||
bool m_close_pending { false };
|
||||
// variables to count positions correctly
|
||||
float m_window_width_offset;
|
||||
float m_left_indentation;
|
||||
// Total size of notification window - varies based on monitor
|
||||
float m_window_height { 56.0f };
|
||||
float m_window_width { 450.0f };
|
||||
//Distance from bottom of notifications to top of this notification
|
||||
float m_top_y { 0.0f };
|
||||
|
||||
// Height of text
|
||||
// Used as basic scaling unit!
|
||||
float m_line_height;
|
||||
std::vector<int> m_endlines;
|
||||
// Gray are f.e. eorrors when its uknown if they are still valid
|
||||
bool m_is_gray { false };
|
||||
//if multiline = true, notification is showing all lines(>2)
|
||||
bool m_multiline { false };
|
||||
int m_lines_count{ 1 };
|
||||
wxEvtHandler* m_evt_handler;
|
||||
};
|
||||
|
||||
class SlicingCompleteLargeNotification : public PopNotification
|
||||
{
|
||||
public:
|
||||
SlicingCompleteLargeNotification(const NotificationData& n, const int id, wxEvtHandler* evt_handler, bool largeds);
|
||||
void set_large(bool l);
|
||||
bool get_large() { return m_is_large; }
|
||||
|
||||
void set_print_info(std::string info);
|
||||
protected:
|
||||
virtual void render_text(ImGuiWrapper& imgui,
|
||||
const float win_size_x, const float win_size_y,
|
||||
const float win_pos_x, const float win_pos_y)
|
||||
override;
|
||||
|
||||
bool m_is_large;
|
||||
bool m_has_print_info { false };
|
||||
std::string m_print_info { std::string() };
|
||||
};
|
||||
|
||||
class SlicingWarningNotification : public PopNotification
|
||||
{
|
||||
public:
|
||||
SlicingWarningNotification(const NotificationData& n, const int id, wxEvtHandler* evt_handler) : PopNotification(n, id, evt_handler) {}
|
||||
void set_object_id(size_t id) { object_id = id; }
|
||||
const size_t get_object_id() { return object_id; }
|
||||
void set_warning_step(int ws) { warning_step = ws; }
|
||||
const int get_warning_step() { return warning_step; }
|
||||
protected:
|
||||
size_t object_id;
|
||||
int warning_step;
|
||||
};
|
||||
|
||||
NotificationManager(wxEvtHandler* evt_handler);
|
||||
~NotificationManager();
|
||||
|
||||
|
||||
// only type means one of basic_notification (see below)
|
||||
void push_notification(const NotificationType type, GLCanvas3D& canvas, int timestamp = 0);
|
||||
// only text means Undefined type
|
||||
void push_notification(const std::string& text, GLCanvas3D& canvas, int timestamp = 0);
|
||||
void push_notification(const std::string& text, NotificationLevel level, GLCanvas3D& canvas, int timestamp = 0);
|
||||
// creates Slicing Error notification with custom text
|
||||
void push_slicing_error_notification(const std::string& text, GLCanvas3D& canvas);
|
||||
// creates Slicing Warning notification with custom text
|
||||
void push_slicing_warning_notification(const std::string& text, bool gray, GLCanvas3D& canvas, size_t oid, int warning_step);
|
||||
// marks slicing errors as gray
|
||||
void set_all_slicing_errors_gray(bool g);
|
||||
// marks slicing warings as gray
|
||||
void set_all_slicing_warnings_gray(bool g);
|
||||
void set_slicing_warning_gray(const std::string& text, bool g);
|
||||
// imidietly stops showing slicing errors
|
||||
void close_slicing_errors_and_warnings();
|
||||
void compare_warning_oids(const std::vector<size_t>& living_oids);
|
||||
void push_plater_error_notification(const std::string& text, GLCanvas3D& canvas);
|
||||
void push_plater_warning_notification(const std::string& text, GLCanvas3D& canvas);
|
||||
void close_plater_error_notification();
|
||||
void close_plater_warning_notification(const std::string& text);
|
||||
// creates special notification slicing complete
|
||||
// if large = true prints printing time and export button
|
||||
void push_slicing_complete_notification(GLCanvas3D& canvas, int timestamp, bool large);
|
||||
void set_slicing_complete_print_time(std::string info);
|
||||
void set_slicing_complete_large(bool large);
|
||||
// renders notifications in queue and deletes expired ones
|
||||
void render_notifications(GLCanvas3D& canvas);
|
||||
// finds and closes all notifications of given type
|
||||
void close_notification_of_type(const NotificationType type);
|
||||
void dpi_changed();
|
||||
private:
|
||||
//pushes notification into the queue of notifications that are rendered
|
||||
//can be used to create custom notification
|
||||
bool push_notification_data(const NotificationData& notification_data, GLCanvas3D& canvas, int timestamp);
|
||||
bool push_notification_data(NotificationManager::PopNotification* notification, GLCanvas3D& canvas, int timestamp);
|
||||
//finds older notification of same type and moves it to the end of queue. returns true if found
|
||||
bool find_older(NotificationManager::PopNotification* notification);
|
||||
void sort_notifications();
|
||||
|
||||
wxEvtHandler* m_evt_handler;
|
||||
std::deque<PopNotification*> m_pop_notifications;
|
||||
int m_next_id { 1 };
|
||||
long m_last_time { 0 };
|
||||
bool m_hovered { false };
|
||||
//timestamps used for slining finished - notification could be gone so it needs to be stored here
|
||||
std::unordered_set<int> m_used_timestamps;
|
||||
|
||||
//prepared (basic) notifications
|
||||
const std::vector<NotificationData> basic_notifications = {
|
||||
{NotificationType::SlicingNotPossible, NotificationLevel::RegularNotification, 10, _u8L("Slicing is not possible.")},
|
||||
{NotificationType::ExportToRemovableFinished, NotificationLevel::ImportantNotification, 0, _u8L("Exporting finished."), _u8L("Eject drive.") },
|
||||
{NotificationType::Mouse3dDisconnected, NotificationLevel::RegularNotification, 10, _u8L("3D Mouse disconnected.") },
|
||||
{NotificationType::Mouse3dConnected, NotificationLevel::RegularNotification, 5, _u8L("3D Mouse connected.") },
|
||||
{NotificationType::NewPresetsAviable, NotificationLevel::ImportantNotification, 20, _u8L("New Presets are available."), _u8L("See here.") },
|
||||
{NotificationType::PresetUpdateAviable, NotificationLevel::ImportantNotification, 20, _u8L("Configuration update is available."), _u8L("See more.")},
|
||||
{NotificationType::NewAppAviable, NotificationLevel::ImportantNotification, 20, _u8L("New version is available."), _u8L("See Releases page.")},
|
||||
//{NotificationType::NewAppAviable, NotificationLevel::ImportantNotification, 20, _u8L("New vesion of PrusaSlicer is available.", _u8L("Download page.") },
|
||||
//{NotificationType::LoadingFailed, NotificationLevel::RegularNotification, 20, _u8L("Loading of model has Failed") },
|
||||
//{NotificationType::DeviceEjected, NotificationLevel::RegularNotification, 10, _u8L("Removable device has been safely ejected")} // if we want changeble text (like here name of device), we need to do it as CustomNotification
|
||||
};
|
||||
};
|
||||
|
||||
}//namespace GUI
|
||||
}//namespace Slic3r
|
||||
|
||||
#endif //slic3r_GUI_NotificationManager_hpp_
|
@ -729,31 +729,34 @@ boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config
|
||||
opt_key == "fill_pattern" ) {
|
||||
ret = static_cast<int>(config.option<ConfigOptionEnum<InfillPattern>>(opt_key)->value);
|
||||
}
|
||||
else if (opt_key.compare("ironing_type") == 0 ) {
|
||||
else if (opt_key == "ironing_type") {
|
||||
ret = static_cast<int>(config.option<ConfigOptionEnum<IroningType>>(opt_key)->value);
|
||||
}
|
||||
else if (opt_key.compare("gcode_flavor") == 0 ) {
|
||||
else if (opt_key == "gcode_flavor") {
|
||||
ret = static_cast<int>(config.option<ConfigOptionEnum<GCodeFlavor>>(opt_key)->value);
|
||||
}
|
||||
else if (opt_key.compare("support_material_pattern") == 0) {
|
||||
else if (opt_key == "support_material_pattern") {
|
||||
ret = static_cast<int>(config.option<ConfigOptionEnum<SupportMaterialPattern>>(opt_key)->value);
|
||||
}
|
||||
else if (opt_key.compare("seam_position") == 0) {
|
||||
else if (opt_key == "seam_position") {
|
||||
ret = static_cast<int>(config.option<ConfigOptionEnum<SeamPosition>>(opt_key)->value);
|
||||
}
|
||||
else if (opt_key.compare("host_type") == 0) {
|
||||
else if (opt_key == "host_type") {
|
||||
ret = static_cast<int>(config.option<ConfigOptionEnum<PrintHostType>>(opt_key)->value);
|
||||
}
|
||||
else if (opt_key.compare("display_orientation") == 0) {
|
||||
else if (opt_key == "display_orientation") {
|
||||
ret = static_cast<int>(config.option<ConfigOptionEnum<SLADisplayOrientation>>(opt_key)->value);
|
||||
}
|
||||
else if (opt_key.compare("support_pillar_connection_mode") == 0) {
|
||||
else if (opt_key == "support_pillar_connection_mode") {
|
||||
ret = static_cast<int>(config.option<ConfigOptionEnum<SLAPillarConnectionMode>>(opt_key)->value);
|
||||
}
|
||||
else if (opt_key == "authorization_type") {
|
||||
ret = static_cast<int>(config.option<ConfigOptionEnum<AuthorizationType>>(opt_key)->value);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case coPoints:
|
||||
if (opt_key.compare("bed_shape") == 0)
|
||||
if (opt_key == "bed_shape")
|
||||
ret = config.option<ConfigOptionPoints>(opt_key)->values;
|
||||
else
|
||||
ret = config.option<ConfigOptionPoints>(opt_key)->get_at(idx);
|
||||
|
@ -149,6 +149,13 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
void show_field(const t_config_option_key& opt_key, bool show = true) {
|
||||
Field* field = get_field(opt_key);
|
||||
field->getWindow()->Show(show);
|
||||
field->getLabel()->Show(show);
|
||||
}
|
||||
void hide_field(const t_config_option_key& opt_key) { show_field(opt_key, false); }
|
||||
|
||||
void set_name(const wxString& new_name) {
|
||||
stb->SetLabel(new_name);
|
||||
}
|
||||
|
564
src/slic3r/GUI/PhysicalPrinterDialog.cpp
Normal file
564
src/slic3r/GUI/PhysicalPrinterDialog.cpp
Normal file
@ -0,0 +1,564 @@
|
||||
#include "PhysicalPrinterDialog.hpp"
|
||||
#include "PresetComboBoxes.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/textctrl.h>
|
||||
#include <wx/button.h>
|
||||
#include <wx/statbox.h>
|
||||
#include <wx/wupdlock.h>
|
||||
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
|
||||
#include "GUI.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "MainFrame.hpp"
|
||||
#include "format.hpp"
|
||||
#include "Tab.hpp"
|
||||
#include "wxExtensions.hpp"
|
||||
#include "PrintHostDialogs.hpp"
|
||||
#include "../Utils/ASCIIFolding.hpp"
|
||||
#include "../Utils/PrintHost.hpp"
|
||||
#include "../Utils/FixModelByWin10.hpp"
|
||||
#include "../Utils/UndoRedo.hpp"
|
||||
#include "RemovableDriveManager.hpp"
|
||||
#include "BitmapCache.hpp"
|
||||
#include "BonjourDialog.hpp"
|
||||
|
||||
using Slic3r::GUI::format_wxstr;
|
||||
|
||||
//static const std::pair<unsigned int, unsigned int> THUMBNAIL_SIZE_3MF = { 256, 256 };
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
#define BORDER_W 10
|
||||
|
||||
//------------------------------------------
|
||||
// PresetForPrinter
|
||||
//------------------------------------------
|
||||
|
||||
PresetForPrinter::PresetForPrinter(PhysicalPrinterDialog* parent, const std::string& preset_name) :
|
||||
m_parent(parent)
|
||||
{
|
||||
m_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
m_delete_preset_btn = new ScalableButton(parent, wxID_ANY, "cross", "", wxDefaultSize, wxDefaultPosition, /*wxBU_LEFT | */wxBU_EXACTFIT);
|
||||
m_delete_preset_btn->SetFont(wxGetApp().normal_font());
|
||||
m_delete_preset_btn->SetToolTip(_L("Delete this preset from this printer device"));
|
||||
m_delete_preset_btn->Bind(wxEVT_BUTTON, &PresetForPrinter::DeletePreset, this);
|
||||
|
||||
m_presets_list = new PresetComboBox(parent, Preset::TYPE_PRINTER);
|
||||
m_presets_list->set_printer_technology(parent->get_printer_technology());
|
||||
|
||||
m_presets_list->set_selection_changed_function([this](int selection) {
|
||||
std::string selected_string = Preset::remove_suffix_modified(m_presets_list->GetString(selection).ToUTF8().data());
|
||||
Preset* preset = wxGetApp().preset_bundle->printers.find_preset(selected_string);
|
||||
assert(preset);
|
||||
Preset& edited_preset = wxGetApp().preset_bundle->printers.get_edited_preset();
|
||||
if (preset->name == edited_preset.name)
|
||||
preset = &edited_preset;
|
||||
|
||||
// if created physical printer doesn't have any settings, use the settings from the selected preset
|
||||
if (m_parent->get_printer()->has_empty_config()) {
|
||||
// update Print Host upload from the selected preset
|
||||
m_parent->get_printer()->update_from_preset(*preset);
|
||||
// update values in parent (PhysicalPrinterDialog)
|
||||
m_parent->update();
|
||||
}
|
||||
|
||||
// update PrinterTechnology if it was changed
|
||||
if (m_presets_list->set_printer_technology(preset->printer_technology()))
|
||||
m_parent->set_printer_technology(preset->printer_technology());
|
||||
|
||||
update_full_printer_name();
|
||||
});
|
||||
m_presets_list->update(preset_name);
|
||||
|
||||
m_info_line = new wxStaticText(parent, wxID_ANY, _L("This printer will be shown in the presets list as") + ":");
|
||||
|
||||
m_full_printer_name = new wxStaticText(parent, wxID_ANY, "");
|
||||
m_full_printer_name->SetFont(wxGetApp().bold_font());
|
||||
|
||||
wxBoxSizer* preset_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
preset_sizer->Add(m_presets_list , 1, wxEXPAND);
|
||||
preset_sizer->Add(m_delete_preset_btn , 0, wxEXPAND | wxLEFT, BORDER_W);
|
||||
|
||||
wxBoxSizer* name_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
name_sizer->Add(m_info_line, 0, wxEXPAND);
|
||||
name_sizer->Add(m_full_printer_name, 0, wxEXPAND | wxLEFT, BORDER_W);
|
||||
|
||||
m_sizer->Add(preset_sizer , 0, wxEXPAND);
|
||||
m_sizer->Add(name_sizer, 0, wxEXPAND);
|
||||
}
|
||||
|
||||
PresetForPrinter::~PresetForPrinter()
|
||||
{
|
||||
m_presets_list->Destroy();
|
||||
m_delete_preset_btn->Destroy();
|
||||
m_info_line->Destroy();
|
||||
m_full_printer_name->Destroy();
|
||||
}
|
||||
|
||||
void PresetForPrinter::DeletePreset(wxEvent& event)
|
||||
{
|
||||
m_parent->DeletePreset(this);
|
||||
}
|
||||
|
||||
void PresetForPrinter::update_full_printer_name()
|
||||
{
|
||||
wxString printer_name = m_parent->get_printer_name();
|
||||
wxString preset_name = m_presets_list->GetString(m_presets_list->GetSelection());
|
||||
|
||||
m_full_printer_name->SetLabelText(printer_name + " * " + preset_name);
|
||||
}
|
||||
|
||||
std::string PresetForPrinter::get_preset_name()
|
||||
{
|
||||
return into_u8(m_presets_list->GetString(m_presets_list->GetSelection()));
|
||||
}
|
||||
|
||||
void PresetForPrinter::SuppressDelete()
|
||||
{
|
||||
m_delete_preset_btn->Enable(false);
|
||||
|
||||
// this case means that now we have only one related preset for the printer
|
||||
// So, allow any selection
|
||||
m_presets_list->set_printer_technology(ptAny);
|
||||
m_presets_list->update();
|
||||
}
|
||||
|
||||
void PresetForPrinter::AllowDelete()
|
||||
{
|
||||
if (!m_delete_preset_btn->IsEnabled())
|
||||
m_delete_preset_btn->Enable();
|
||||
|
||||
m_presets_list->set_printer_technology(m_parent->get_printer_technology());
|
||||
m_presets_list->update();
|
||||
}
|
||||
|
||||
void PresetForPrinter::msw_rescale()
|
||||
{
|
||||
m_presets_list->msw_rescale();
|
||||
m_delete_preset_btn->msw_rescale();
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------
|
||||
// PhysicalPrinterDialog
|
||||
//------------------------------------------
|
||||
|
||||
PhysicalPrinterDialog::PhysicalPrinterDialog(wxString printer_name)
|
||||
: DPIDialog(NULL, wxID_ANY, _L("Physical Printer"), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), -1), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
|
||||
{
|
||||
SetFont(wxGetApp().normal_font());
|
||||
SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
|
||||
|
||||
m_default_name = _L("Type here the name of your printer device");
|
||||
bool new_printer = true;
|
||||
|
||||
if (printer_name.IsEmpty())
|
||||
printer_name = m_default_name;
|
||||
else {
|
||||
std::string full_name = into_u8(printer_name);
|
||||
printer_name = from_u8(PhysicalPrinter::get_short_name(full_name));
|
||||
new_printer = false;
|
||||
}
|
||||
|
||||
wxStaticText* label_top = new wxStaticText(this, wxID_ANY, _L("Descriptive name for the printer device") + ":");
|
||||
|
||||
m_add_preset_btn = new ScalableButton(this, wxID_ANY, "add_copies", "", wxDefaultSize, wxDefaultPosition, /*wxBU_LEFT | */wxBU_EXACTFIT);
|
||||
m_add_preset_btn->SetFont(wxGetApp().normal_font());
|
||||
m_add_preset_btn->SetToolTip(_L("Add preset for this printer device"));
|
||||
m_add_preset_btn->Bind(wxEVT_BUTTON, &PhysicalPrinterDialog::AddPreset, this);
|
||||
|
||||
m_printer_name = new wxTextCtrl(this, wxID_ANY, printer_name, wxDefaultPosition, wxDefaultSize);
|
||||
m_printer_name->Bind(wxEVT_TEXT, [this](wxEvent&) { this->update_full_printer_names(); });
|
||||
|
||||
PhysicalPrinterCollection& printers = wxGetApp().preset_bundle->physical_printers;
|
||||
PhysicalPrinter* printer = printers.find_printer(into_u8(printer_name));
|
||||
if (!printer) {
|
||||
const Preset& preset = wxGetApp().preset_bundle->printers.get_edited_preset();
|
||||
printer = new PhysicalPrinter(into_u8(printer_name), preset);
|
||||
// if printer_name is empty it means that new printer is created, so enable all items in the preset list
|
||||
m_presets.emplace_back(new PresetForPrinter(this, preset.name));
|
||||
}
|
||||
else
|
||||
{
|
||||
const std::set<std::string>& preset_names = printer->get_preset_names();
|
||||
for (const std::string& preset_name : preset_names)
|
||||
m_presets.emplace_back(new PresetForPrinter(this, preset_name));
|
||||
}
|
||||
assert(printer);
|
||||
m_printer = *printer;
|
||||
|
||||
if (m_presets.size() == 1)
|
||||
m_presets.front()->SuppressDelete();
|
||||
|
||||
update_full_printer_names();
|
||||
|
||||
m_config = &m_printer.config;
|
||||
|
||||
m_optgroup = new ConfigOptionsGroup(this, _L("Print Host upload"), m_config);
|
||||
build_printhost_settings(m_optgroup);
|
||||
|
||||
wxStdDialogButtonSizer* btns = this->CreateStdDialogButtonSizer(wxOK | wxCANCEL);
|
||||
wxButton* btnOK = static_cast<wxButton*>(this->FindWindowById(wxID_OK, this));
|
||||
btnOK->Bind(wxEVT_BUTTON, &PhysicalPrinterDialog::OnOK, this);
|
||||
|
||||
wxBoxSizer* nameSizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
nameSizer->Add(m_printer_name, 1, wxEXPAND);
|
||||
nameSizer->Add(m_add_preset_btn, 0, wxEXPAND | wxLEFT, BORDER_W);
|
||||
|
||||
m_presets_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
for (PresetForPrinter* preset : m_presets)
|
||||
m_presets_sizer->Add(preset->sizer(), 1, wxEXPAND | wxTOP, BORDER_W);
|
||||
|
||||
wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
topSizer->Add(label_top , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, BORDER_W);
|
||||
topSizer->Add(nameSizer , 0, wxEXPAND | wxLEFT | wxRIGHT, BORDER_W);
|
||||
topSizer->Add(m_presets_sizer , 0, wxEXPAND | wxLEFT | wxRIGHT, BORDER_W);
|
||||
topSizer->Add(m_optgroup->sizer , 1, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, BORDER_W);
|
||||
topSizer->Add(btns , 0, wxEXPAND | wxALL, BORDER_W);
|
||||
|
||||
SetSizer(topSizer);
|
||||
topSizer->SetSizeHints(this);
|
||||
|
||||
if (new_printer) {
|
||||
m_printer_name->SetFocus();
|
||||
m_printer_name->SelectAll();
|
||||
}
|
||||
}
|
||||
|
||||
PhysicalPrinterDialog::~PhysicalPrinterDialog()
|
||||
{
|
||||
for (PresetForPrinter* preset : m_presets) {
|
||||
delete preset;
|
||||
preset = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicalPrinterDialog::build_printhost_settings(ConfigOptionsGroup* m_optgroup)
|
||||
{
|
||||
m_optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) {
|
||||
if (opt_key == "authorization_type")
|
||||
this->update();
|
||||
};
|
||||
|
||||
m_optgroup->append_single_option_line("host_type");
|
||||
|
||||
auto create_sizer_with_btn = [this](wxWindow* parent, ScalableButton** btn, const std::string& icon_name, const wxString& label) {
|
||||
*btn = new ScalableButton(parent, wxID_ANY, icon_name, label, wxDefaultSize, wxDefaultPosition, wxBU_LEFT | wxBU_EXACTFIT);
|
||||
(*btn)->SetFont(wxGetApp().normal_font());
|
||||
|
||||
auto sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
sizer->Add(*btn);
|
||||
return sizer;
|
||||
};
|
||||
|
||||
auto printhost_browse = [=](wxWindow* parent)
|
||||
{
|
||||
auto sizer = create_sizer_with_btn(parent, &m_printhost_browse_btn, "browse", _L("Browse") + " " + dots);
|
||||
m_printhost_browse_btn->Bind(wxEVT_BUTTON, [=](wxCommandEvent& e) {
|
||||
BonjourDialog dialog(this, Preset::printer_technology(m_printer.config));
|
||||
if (dialog.show_and_lookup()) {
|
||||
m_optgroup->set_value("print_host", std::move(dialog.get_selected()), true);
|
||||
m_optgroup->get_field("print_host")->field_changed();
|
||||
}
|
||||
});
|
||||
|
||||
return sizer;
|
||||
};
|
||||
|
||||
auto print_host_test = [=](wxWindow* parent) {
|
||||
auto sizer = create_sizer_with_btn(parent, &m_printhost_test_btn, "test", _L("Test"));
|
||||
|
||||
m_printhost_test_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& e) {
|
||||
std::unique_ptr<PrintHost> host(PrintHost::get_print_host(m_config));
|
||||
if (!host) {
|
||||
const wxString text = _L("Could not get a valid Printer Host reference");
|
||||
show_error(this, text);
|
||||
return;
|
||||
}
|
||||
wxString msg;
|
||||
if (host->test(msg)) {
|
||||
show_info(this, host->get_test_ok_msg(), _L("Success!"));
|
||||
}
|
||||
else {
|
||||
show_error(this, host->get_test_failed_msg(msg));
|
||||
}
|
||||
});
|
||||
|
||||
return sizer;
|
||||
};
|
||||
|
||||
// Set a wider width for a better alignment
|
||||
Option option = m_optgroup->get_option("print_host");
|
||||
option.opt.width = Field::def_width_wider();
|
||||
Line host_line = m_optgroup->create_single_option_line(option);
|
||||
host_line.append_widget(printhost_browse);
|
||||
host_line.append_widget(print_host_test);
|
||||
m_optgroup->append_line(host_line);
|
||||
|
||||
m_optgroup->append_single_option_line("authorization_type");
|
||||
|
||||
option = m_optgroup->get_option("printhost_apikey");
|
||||
option.opt.width = Field::def_width_wider();
|
||||
m_optgroup->append_single_option_line(option);
|
||||
|
||||
const auto ca_file_hint = _u8L("HTTPS CA file is optional. It is only needed if you use HTTPS with a self-signed certificate.");
|
||||
|
||||
if (Http::ca_file_supported()) {
|
||||
option = m_optgroup->get_option("printhost_cafile");
|
||||
option.opt.width = Field::def_width_wider();
|
||||
Line cafile_line = m_optgroup->create_single_option_line(option);
|
||||
|
||||
auto printhost_cafile_browse = [=](wxWindow* parent) {
|
||||
auto sizer = create_sizer_with_btn(parent, &m_printhost_cafile_browse_btn, "browse", _L("Browse") + " " + dots);
|
||||
m_printhost_cafile_browse_btn->Bind(wxEVT_BUTTON, [this, m_optgroup](wxCommandEvent e) {
|
||||
static const auto filemasks = _L("Certificate files (*.crt, *.pem)|*.crt;*.pem|All files|*.*");
|
||||
wxFileDialog openFileDialog(this, _L("Open CA certificate file"), "", "", filemasks, wxFD_OPEN | wxFD_FILE_MUST_EXIST);
|
||||
if (openFileDialog.ShowModal() != wxID_CANCEL) {
|
||||
m_optgroup->set_value("printhost_cafile", std::move(openFileDialog.GetPath()), true);
|
||||
m_optgroup->get_field("printhost_cafile")->field_changed();
|
||||
}
|
||||
});
|
||||
|
||||
return sizer;
|
||||
};
|
||||
|
||||
cafile_line.append_widget(printhost_cafile_browse);
|
||||
m_optgroup->append_line(cafile_line);
|
||||
|
||||
Line cafile_hint{ "", "" };
|
||||
cafile_hint.full_width = 1;
|
||||
cafile_hint.widget = [this, ca_file_hint](wxWindow* parent) {
|
||||
auto txt = new wxStaticText(parent, wxID_ANY, ca_file_hint);
|
||||
auto sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
sizer->Add(txt);
|
||||
return sizer;
|
||||
};
|
||||
m_optgroup->append_line(cafile_hint);
|
||||
}
|
||||
else {
|
||||
Line line{ "", "" };
|
||||
line.full_width = 1;
|
||||
|
||||
line.widget = [ca_file_hint](wxWindow* parent) {
|
||||
std::string info = _u8L("HTTPS CA File") + ":\n\t" +
|
||||
(boost::format(_u8L("On this system, %s uses HTTPS certificates from the system Certificate Store or Keychain.")) % SLIC3R_APP_NAME).str() +
|
||||
"\n\t" + _u8L("To use a custom CA file, please import your CA file into Certificate Store / Keychain.");
|
||||
|
||||
//auto txt = new wxStaticText(parent, wxID_ANY, from_u8((boost::format("%1%\n\n\t%2%") % info % ca_file_hint).str()));
|
||||
auto txt = new wxStaticText(parent, wxID_ANY, from_u8((boost::format("%1%\n\t%2%") % info % ca_file_hint).str()));
|
||||
txt->SetFont(wxGetApp().normal_font());
|
||||
auto sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
sizer->Add(txt, 1, wxEXPAND);
|
||||
return sizer;
|
||||
};
|
||||
|
||||
m_optgroup->append_line(line);
|
||||
}
|
||||
|
||||
for (const std::string& opt_key : std::vector<std::string>{ "login", "password" }) {
|
||||
option = m_optgroup->get_option(opt_key);
|
||||
option.opt.width = Field::def_width_wider();
|
||||
m_optgroup->append_single_option_line(option);
|
||||
}
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
void PhysicalPrinterDialog::update()
|
||||
{
|
||||
m_optgroup->reload_config();
|
||||
|
||||
const PrinterTechnology tech = Preset::printer_technology(*m_config);
|
||||
// Only offer the host type selection for FFF, for SLA it's always the SL1 printer (at the moment)
|
||||
if (tech == ptFFF) {
|
||||
m_optgroup->show_field("host_type");
|
||||
m_optgroup->hide_field("authorization_type");
|
||||
for (const std::string& opt_key : std::vector<std::string>{ "login", "password" })
|
||||
m_optgroup->hide_field(opt_key);
|
||||
}
|
||||
else {
|
||||
m_optgroup->set_value("host_type", int(PrintHostType::htOctoPrint), false);
|
||||
m_optgroup->hide_field("host_type");
|
||||
|
||||
m_optgroup->show_field("authorization_type");
|
||||
|
||||
AuthorizationType auth_type = m_config->option<ConfigOptionEnum<AuthorizationType>>("authorization_type")->value;
|
||||
m_optgroup->show_field("printhost_apikey", auth_type == AuthorizationType::atKeyPassword);
|
||||
|
||||
for (const std::string& opt_key : std::vector<std::string>{ "login", "password" })
|
||||
m_optgroup->show_field(opt_key, auth_type == AuthorizationType::atUserPassword);
|
||||
}
|
||||
|
||||
this->Layout();
|
||||
}
|
||||
|
||||
|
||||
wxString PhysicalPrinterDialog::get_printer_name()
|
||||
{
|
||||
return m_printer_name->GetValue();
|
||||
}
|
||||
|
||||
void PhysicalPrinterDialog::update_full_printer_names()
|
||||
{
|
||||
for (PresetForPrinter* preset : m_presets)
|
||||
preset->update_full_printer_name();
|
||||
|
||||
this->Layout();
|
||||
}
|
||||
|
||||
void PhysicalPrinterDialog::set_printer_technology(PrinterTechnology pt)
|
||||
{
|
||||
m_config->set_key_value("printer_technology", new ConfigOptionEnum<PrinterTechnology>(pt));
|
||||
update();
|
||||
}
|
||||
|
||||
PrinterTechnology PhysicalPrinterDialog::get_printer_technology()
|
||||
{
|
||||
return m_printer.printer_technology();
|
||||
}
|
||||
|
||||
void PhysicalPrinterDialog::on_dpi_changed(const wxRect& suggested_rect)
|
||||
{
|
||||
const int& em = em_unit();
|
||||
|
||||
m_printhost_browse_btn->msw_rescale();
|
||||
m_printhost_test_btn->msw_rescale();
|
||||
if (m_printhost_cafile_browse_btn)
|
||||
m_printhost_cafile_browse_btn->msw_rescale();
|
||||
|
||||
m_optgroup->msw_rescale();
|
||||
|
||||
msw_buttons_rescale(this, em, { wxID_OK, wxID_CANCEL });
|
||||
|
||||
for (PresetForPrinter* preset : m_presets)
|
||||
preset->msw_rescale();
|
||||
|
||||
const wxSize& size = wxSize(45 * em, 35 * em);
|
||||
SetMinSize(size);
|
||||
|
||||
Fit();
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void PhysicalPrinterDialog::OnOK(wxEvent& event)
|
||||
{
|
||||
wxString printer_name = m_printer_name->GetValue();
|
||||
if (printer_name.IsEmpty()) {
|
||||
warning_catcher(this, _L("The supplied name is empty. It can't be saved."));
|
||||
return;
|
||||
}
|
||||
if (printer_name == m_default_name) {
|
||||
warning_catcher(this, _L("You should to change a name of your printer device. It can't be saved."));
|
||||
return;
|
||||
}
|
||||
|
||||
PhysicalPrinterCollection& printers = wxGetApp().preset_bundle->physical_printers;
|
||||
const PhysicalPrinter* existing = printers.find_printer(into_u8(printer_name));
|
||||
if (existing && into_u8(printer_name) != printers.get_selected_printer_name())
|
||||
{
|
||||
wxString msg_text = from_u8((boost::format(_u8L("Printer with name \"%1%\" already exists.")) % printer_name).str());
|
||||
msg_text += "\n" + _L("Replace?");
|
||||
wxMessageDialog dialog(nullptr, msg_text, _L("Warning"), wxICON_WARNING | wxYES | wxNO);
|
||||
|
||||
if (dialog.ShowModal() == wxID_NO)
|
||||
return;
|
||||
}
|
||||
|
||||
std::set<std::string> repeat_presets;
|
||||
m_printer.reset_presets();
|
||||
for (PresetForPrinter* preset : m_presets) {
|
||||
if (!m_printer.add_preset(preset->get_preset_name()))
|
||||
repeat_presets.emplace(preset->get_preset_name());
|
||||
}
|
||||
|
||||
if (!repeat_presets.empty())
|
||||
{
|
||||
wxString repeatable_presets = "\n";
|
||||
for (const std::string& preset_name : repeat_presets)
|
||||
repeatable_presets += " " + from_u8(preset_name) + "\n";
|
||||
repeatable_presets += "\n";
|
||||
|
||||
wxString msg_text = from_u8((boost::format(_u8L("Next printer preset(s) is(are) duplicated:%1%"
|
||||
"Should I add it(they) just once for the printer \"%2%\" and close the Editing Dialog?")) % repeatable_presets % printer_name).str());
|
||||
wxMessageDialog dialog(nullptr, msg_text, _L("Warning"), wxICON_WARNING | wxYES | wxNO);
|
||||
if (dialog.ShowModal() == wxID_NO)
|
||||
return;
|
||||
}
|
||||
|
||||
std::string renamed_from;
|
||||
// temporary save previous printer name if it was edited
|
||||
if (m_printer.name != into_u8(m_default_name) &&
|
||||
m_printer.name != into_u8(printer_name))
|
||||
renamed_from = m_printer.name;
|
||||
|
||||
//update printer name, if it was changed
|
||||
m_printer.set_name(into_u8(printer_name));
|
||||
|
||||
// save new physical printer
|
||||
printers.save_printer(m_printer, renamed_from);
|
||||
|
||||
if (m_printer.preset_names.find(printers.get_selected_printer_preset_name()) == m_printer.preset_names.end()) {
|
||||
// select first preset for this printer
|
||||
printers.select_printer(m_printer);
|
||||
// refresh preset list on Printer Settings Tab
|
||||
wxGetApp().get_tab(Preset::TYPE_PRINTER)->select_preset(printers.get_selected_printer_preset_name());
|
||||
}
|
||||
else
|
||||
wxGetApp().get_tab(Preset::TYPE_PRINTER)->update_preset_choice();
|
||||
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
void PhysicalPrinterDialog::AddPreset(wxEvent& event)
|
||||
{
|
||||
m_presets.emplace_back(new PresetForPrinter(this));
|
||||
// enable DELETE button for the first preset, if was disabled
|
||||
m_presets.front()->AllowDelete();
|
||||
|
||||
m_presets_sizer->Add(m_presets.back()->sizer(), 1, wxEXPAND | wxTOP, BORDER_W);
|
||||
update_full_printer_names();
|
||||
|
||||
this->Fit();
|
||||
}
|
||||
|
||||
void PhysicalPrinterDialog::DeletePreset(PresetForPrinter* preset_for_printer)
|
||||
{
|
||||
if (m_presets.size() == 1) {
|
||||
wxString msg_text = _L("It's not possible to delete last related preset for the printer.");
|
||||
wxMessageDialog dialog(nullptr, msg_text, _L("Infornation"), wxICON_INFORMATION | wxOK);
|
||||
dialog.ShowModal();
|
||||
return;
|
||||
}
|
||||
|
||||
assert(preset_for_printer);
|
||||
auto it = std::find(m_presets.begin(), m_presets.end(), preset_for_printer);
|
||||
if (it == m_presets.end())
|
||||
return;
|
||||
|
||||
const int remove_id = it - m_presets.begin();
|
||||
m_presets_sizer->Remove(remove_id);
|
||||
delete preset_for_printer;
|
||||
m_presets.erase(it);
|
||||
|
||||
if (m_presets.size() == 1)
|
||||
m_presets.front()->SuppressDelete();
|
||||
|
||||
this->Layout();
|
||||
this->Fit();
|
||||
}
|
||||
|
||||
|
||||
}} // namespace Slic3r::GUI
|
105
src/slic3r/GUI/PhysicalPrinterDialog.hpp
Normal file
105
src/slic3r/GUI/PhysicalPrinterDialog.hpp
Normal file
@ -0,0 +1,105 @@
|
||||
#ifndef slic3r_PhysicalPrinterDialog_hpp_
|
||||
#define slic3r_PhysicalPrinterDialog_hpp_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <wx/gdicmn.h>
|
||||
|
||||
#include "libslic3r/Preset.hpp"
|
||||
#include "GUI_Utils.hpp"
|
||||
|
||||
class wxString;
|
||||
class wxTextCtrl;
|
||||
class wxStaticText;
|
||||
class ScalableButton;
|
||||
class wxBoxSizer;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
namespace GUI {
|
||||
|
||||
class PresetComboBox;
|
||||
|
||||
//------------------------------------------
|
||||
// PresetForPrinter
|
||||
//------------------------------------------
|
||||
//static std::string g_info_string = " (modified)";
|
||||
class PhysicalPrinterDialog;
|
||||
class PresetForPrinter
|
||||
{
|
||||
PhysicalPrinterDialog* m_parent { nullptr };
|
||||
|
||||
PresetComboBox* m_presets_list { nullptr };
|
||||
ScalableButton* m_delete_preset_btn { nullptr };
|
||||
wxStaticText* m_info_line { nullptr };
|
||||
wxStaticText* m_full_printer_name { nullptr };
|
||||
|
||||
wxBoxSizer* m_sizer { nullptr };
|
||||
|
||||
void DeletePreset(wxEvent& event);
|
||||
|
||||
public:
|
||||
PresetForPrinter(PhysicalPrinterDialog* parent, const std::string& preset_name = "");
|
||||
~PresetForPrinter();
|
||||
|
||||
wxBoxSizer* sizer() { return m_sizer; }
|
||||
void update_full_printer_name();
|
||||
std::string get_preset_name();
|
||||
void SuppressDelete();
|
||||
void AllowDelete();
|
||||
|
||||
void msw_rescale();
|
||||
void on_sys_color_changed() {};
|
||||
};
|
||||
|
||||
|
||||
//------------------------------------------
|
||||
// PhysicalPrinterDialog
|
||||
//------------------------------------------
|
||||
|
||||
class ConfigOptionsGroup;
|
||||
class PhysicalPrinterDialog : public DPIDialog
|
||||
{
|
||||
PhysicalPrinter m_printer;
|
||||
wxString m_default_name;
|
||||
DynamicPrintConfig* m_config { nullptr };
|
||||
|
||||
wxTextCtrl* m_printer_name { nullptr };
|
||||
std::vector<PresetForPrinter*> m_presets;
|
||||
|
||||
ConfigOptionsGroup* m_optgroup { nullptr };
|
||||
|
||||
ScalableButton* m_add_preset_btn {nullptr};
|
||||
ScalableButton* m_printhost_browse_btn {nullptr};
|
||||
ScalableButton* m_printhost_test_btn {nullptr};
|
||||
ScalableButton* m_printhost_cafile_browse_btn {nullptr};
|
||||
|
||||
wxBoxSizer* m_presets_sizer {nullptr};
|
||||
|
||||
void build_printhost_settings(ConfigOptionsGroup* optgroup);
|
||||
void OnOK(wxEvent& event);
|
||||
void AddPreset(wxEvent& event);
|
||||
|
||||
public:
|
||||
PhysicalPrinterDialog(wxString printer_name);
|
||||
~PhysicalPrinterDialog();
|
||||
|
||||
void update();
|
||||
wxString get_printer_name();
|
||||
void update_full_printer_names();
|
||||
PhysicalPrinter* get_printer() {return &m_printer; }
|
||||
void set_printer_technology(PrinterTechnology pt);
|
||||
PrinterTechnology get_printer_technology();
|
||||
|
||||
void DeletePreset(PresetForPrinter* preset_for_printer);
|
||||
|
||||
protected:
|
||||
void on_dpi_changed(const wxRect& suggested_rect) override;
|
||||
void on_sys_color_changed() override {};
|
||||
};
|
||||
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif
|
@ -24,7 +24,6 @@
|
||||
#include <wx/dnd.h>
|
||||
#include <wx/progdlg.h>
|
||||
#include <wx/wupdlock.h>
|
||||
#include <wx/colordlg.h>
|
||||
#include <wx/numdlg.h>
|
||||
#include <wx/debug.h>
|
||||
#include <wx/busyinfo.h>
|
||||
@ -44,6 +43,7 @@
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "libslic3r/SLAPrint.hpp"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
|
||||
#include "GUI.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
@ -66,7 +66,6 @@
|
||||
#include "Jobs/ArrangeJob.hpp"
|
||||
#include "Jobs/RotoptimizeJob.hpp"
|
||||
#include "Jobs/SLAImportJob.hpp"
|
||||
#include "PresetBundle.hpp"
|
||||
#include "BackgroundSlicingProcess.hpp"
|
||||
#include "ProgressStatusBar.hpp"
|
||||
#include "PrintHostDialogs.hpp"
|
||||
@ -75,8 +74,11 @@
|
||||
#include "../Utils/PrintHost.hpp"
|
||||
#include "../Utils/FixModelByWin10.hpp"
|
||||
#include "../Utils/UndoRedo.hpp"
|
||||
#include "../Utils/PresetUpdater.hpp"
|
||||
#include "RemovableDriveManager.hpp"
|
||||
#include "InstanceCheck.hpp"
|
||||
#include "NotificationManager.hpp"
|
||||
#include "PresetComboBoxes.hpp"
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include "Gizmos/GLGizmosManager.hpp"
|
||||
@ -102,6 +104,7 @@ wxDEFINE_EVENT(EVT_SCHEDULE_BACKGROUND_PROCESS, SimpleEvent);
|
||||
wxDEFINE_EVENT(EVT_SLICING_UPDATE, SlicingStatusEvent);
|
||||
wxDEFINE_EVENT(EVT_SLICING_COMPLETED, wxCommandEvent);
|
||||
wxDEFINE_EVENT(EVT_PROCESS_COMPLETED, wxCommandEvent);
|
||||
wxDEFINE_EVENT(EVT_EXPORT_BEGAN, wxCommandEvent);
|
||||
|
||||
// Sidebar widgets
|
||||
|
||||
@ -253,153 +256,6 @@ void SlicedInfo::SetTextAndShow(SlicedInfoIdx idx, const wxString& text, const w
|
||||
info_vec[idx].second->Show(show);
|
||||
}
|
||||
|
||||
PresetComboBox::PresetComboBox(wxWindow *parent, Preset::Type preset_type) :
|
||||
PresetBitmapComboBox(parent, wxSize(15 * wxGetApp().em_unit(), -1)),
|
||||
preset_type(preset_type),
|
||||
last_selected(wxNOT_FOUND),
|
||||
m_em_unit(wxGetApp().em_unit())
|
||||
{
|
||||
SetFont(wxGetApp().normal_font());
|
||||
#ifdef _WIN32
|
||||
// Workaround for ignoring CBN_EDITCHANGE events, which are processed after the content of the combo box changes, so that
|
||||
// the index of the item inside CBN_EDITCHANGE may no more be valid.
|
||||
EnableTextChangedEvents(false);
|
||||
#endif /* _WIN32 */
|
||||
Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &evt) {
|
||||
auto selected_item = evt.GetSelection();
|
||||
|
||||
auto marker = reinterpret_cast<Marker>(this->GetClientData(selected_item));
|
||||
if (marker >= LABEL_ITEM_MARKER && marker < LABEL_ITEM_MAX) {
|
||||
this->SetSelection(this->last_selected);
|
||||
evt.StopPropagation();
|
||||
if (marker >= LABEL_ITEM_WIZARD_PRINTERS) {
|
||||
ConfigWizard::StartPage sp = ConfigWizard::SP_WELCOME;
|
||||
switch (marker) {
|
||||
case LABEL_ITEM_WIZARD_PRINTERS: sp = ConfigWizard::SP_PRINTERS; break;
|
||||
case LABEL_ITEM_WIZARD_FILAMENTS: sp = ConfigWizard::SP_FILAMENTS; break;
|
||||
case LABEL_ITEM_WIZARD_MATERIALS: sp = ConfigWizard::SP_MATERIALS; break;
|
||||
}
|
||||
wxTheApp->CallAfter([sp]() { wxGetApp().run_wizard(ConfigWizard::RR_USER, sp); });
|
||||
}
|
||||
} else if ( this->last_selected != selected_item ||
|
||||
wxGetApp().get_tab(this->preset_type)->get_presets()->current_is_dirty() ) {
|
||||
this->last_selected = selected_item;
|
||||
evt.SetInt(this->preset_type);
|
||||
evt.Skip();
|
||||
} else {
|
||||
evt.StopPropagation();
|
||||
}
|
||||
});
|
||||
|
||||
if (preset_type == Slic3r::Preset::TYPE_FILAMENT)
|
||||
{
|
||||
Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent &event) {
|
||||
PresetBundle* preset_bundle = wxGetApp().preset_bundle;
|
||||
const Preset* selected_preset = preset_bundle->filaments.find_preset(preset_bundle->filament_presets[extruder_idx]);
|
||||
// Wide icons are shown if the currently selected preset is not compatible with the current printer,
|
||||
// and red flag is drown in front of the selected preset.
|
||||
bool wide_icons = selected_preset != nullptr && !selected_preset->is_compatible;
|
||||
float scale = m_em_unit*0.1f;
|
||||
|
||||
int shifl_Left = wide_icons ? int(scale * 16 + 0.5) : 0;
|
||||
#if defined(wxBITMAPCOMBOBOX_OWNERDRAWN_BASED)
|
||||
shifl_Left += int(scale * 4 + 0.5f); // IMAGE_SPACING_RIGHT = 4 for wxBitmapComboBox -> Space left of image
|
||||
#endif
|
||||
int icon_right_pos = shifl_Left + int(scale * (24+4) + 0.5);
|
||||
int mouse_pos = event.GetLogicalPosition(wxClientDC(this)).x;
|
||||
if (mouse_pos < shifl_Left || mouse_pos > icon_right_pos ) {
|
||||
// Let the combo box process the mouse click.
|
||||
event.Skip();
|
||||
return;
|
||||
}
|
||||
|
||||
// Swallow the mouse click and open the color picker.
|
||||
|
||||
// get current color
|
||||
DynamicPrintConfig* cfg = wxGetApp().get_tab(Preset::TYPE_PRINTER)->get_config();
|
||||
auto colors = static_cast<ConfigOptionStrings*>(cfg->option("extruder_colour")->clone());
|
||||
wxColour clr(colors->values[extruder_idx]);
|
||||
if (!clr.IsOk())
|
||||
clr = wxColour(0,0,0); // Don't set alfa to transparence
|
||||
|
||||
auto data = new wxColourData();
|
||||
data->SetChooseFull(1);
|
||||
data->SetColour(clr);
|
||||
|
||||
wxColourDialog dialog(this, data);
|
||||
dialog.CenterOnParent();
|
||||
if (dialog.ShowModal() == wxID_OK)
|
||||
{
|
||||
colors->values[extruder_idx] = dialog.GetColourData().GetColour().GetAsString(wxC2S_HTML_SYNTAX).ToStdString();
|
||||
|
||||
DynamicPrintConfig cfg_new = *cfg;
|
||||
cfg_new.set_key_value("extruder_colour", colors);
|
||||
|
||||
wxGetApp().get_tab(Preset::TYPE_PRINTER)->load_config(cfg_new);
|
||||
preset_bundle->update_plater_filament_ui(extruder_idx, this);
|
||||
wxGetApp().plater()->on_config_change(cfg_new);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
edit_btn = new ScalableButton(parent, wxID_ANY, "cog");
|
||||
edit_btn->SetToolTip(_L("Click to edit preset"));
|
||||
|
||||
edit_btn->Bind(wxEVT_BUTTON, ([preset_type, this](wxCommandEvent)
|
||||
{
|
||||
Tab* tab = wxGetApp().get_tab(preset_type);
|
||||
if (!tab)
|
||||
return;
|
||||
|
||||
int page_id = wxGetApp().tab_panel()->FindPage(tab);
|
||||
if (page_id == wxNOT_FOUND)
|
||||
return;
|
||||
|
||||
wxGetApp().tab_panel()->SetSelection(page_id);
|
||||
|
||||
// Switch to Settings NotePad
|
||||
wxGetApp().mainframe->select_tab();
|
||||
|
||||
/* In a case of a multi-material printing, for editing another Filament Preset
|
||||
* it's needed to select this preset for the "Filament settings" Tab
|
||||
*/
|
||||
if (preset_type == Preset::TYPE_FILAMENT && wxGetApp().extruders_edited_cnt() > 1)
|
||||
{
|
||||
const std::string& selected_preset = GetString(GetSelection()).ToUTF8().data();
|
||||
|
||||
// Call select_preset() only if there is new preset and not just modified
|
||||
if ( !boost::algorithm::ends_with(selected_preset, Preset::suffix_modified()) )
|
||||
{
|
||||
const std::string& preset_name = wxGetApp().preset_bundle->filaments.get_preset_name_by_alias(selected_preset);
|
||||
tab->select_preset(/*selected_preset*/preset_name);
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
PresetComboBox::~PresetComboBox()
|
||||
{
|
||||
if (edit_btn)
|
||||
edit_btn->Destroy();
|
||||
}
|
||||
|
||||
|
||||
void PresetComboBox::set_label_marker(int item, LabelItemType label_item_type)
|
||||
{
|
||||
this->SetClientData(item, (void*)label_item_type);
|
||||
}
|
||||
|
||||
void PresetComboBox::check_selection(int selection)
|
||||
{
|
||||
this->last_selected = selection;
|
||||
}
|
||||
|
||||
void PresetComboBox::msw_rescale()
|
||||
{
|
||||
m_em_unit = wxGetApp().em_unit();
|
||||
edit_btn->msw_rescale();
|
||||
}
|
||||
|
||||
// Frequently changed parameters
|
||||
|
||||
class FreqChangedParams : public OG_Settings
|
||||
@ -697,12 +553,12 @@ struct Sidebar::priv
|
||||
|
||||
ModeSizer *mode_sizer;
|
||||
wxFlexGridSizer *sizer_presets;
|
||||
PresetComboBox *combo_print;
|
||||
std::vector<PresetComboBox*> combos_filament;
|
||||
PlaterPresetComboBox *combo_print;
|
||||
std::vector<PlaterPresetComboBox*> combos_filament;
|
||||
wxBoxSizer *sizer_filaments;
|
||||
PresetComboBox *combo_sla_print;
|
||||
PresetComboBox *combo_sla_material;
|
||||
PresetComboBox *combo_printer;
|
||||
PlaterPresetComboBox *combo_sla_print;
|
||||
PlaterPresetComboBox *combo_sla_material;
|
||||
PlaterPresetComboBox *combo_printer;
|
||||
|
||||
wxBoxSizer *sizer_params;
|
||||
FreqChangedParams *frequently_changed_parameters{ nullptr };
|
||||
@ -716,7 +572,7 @@ struct Sidebar::priv
|
||||
wxButton *btn_export_gcode;
|
||||
wxButton *btn_reslice;
|
||||
ScalableButton *btn_send_gcode;
|
||||
ScalableButton *btn_remove_device;
|
||||
ScalableButton *btn_eject_device;
|
||||
ScalableButton* btn_export_gcode_removable; //exports to removable drives (appears only if removable drive is connected)
|
||||
|
||||
bool is_collapsed {false};
|
||||
@ -801,10 +657,10 @@ Sidebar::Sidebar(Plater *parent)
|
||||
|
||||
p->sizer_filaments = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
auto init_combo = [this](PresetComboBox **combo, wxString label, Preset::Type preset_type, bool filament) {
|
||||
auto init_combo = [this](PlaterPresetComboBox **combo, wxString label, Preset::Type preset_type, bool filament) {
|
||||
auto *text = new wxStaticText(p->presets_panel, wxID_ANY, label + " :");
|
||||
text->SetFont(wxGetApp().small_font());
|
||||
*combo = new PresetComboBox(p->presets_panel, preset_type);
|
||||
*combo = new PlaterPresetComboBox(p->presets_panel, preset_type);
|
||||
|
||||
auto combo_and_btn_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
combo_and_btn_sizer->Add(*combo, 1, wxEXPAND);
|
||||
@ -889,12 +745,12 @@ Sidebar::Sidebar(Plater *parent)
|
||||
};
|
||||
|
||||
init_scalable_btn(&p->btn_send_gcode , "export_gcode", _L("Send to printer") + "\tCtrl+Shift+G");
|
||||
init_scalable_btn(&p->btn_remove_device, "eject_sd" , _L("Remove device") + "\tCtrl+T");
|
||||
init_scalable_btn(&p->btn_eject_device, "eject_sd" , _L("Remove device") + "\tCtrl+T");
|
||||
init_scalable_btn(&p->btn_export_gcode_removable, "export_to_sd", _L("Export to SD card / Flash drive") + "\tCtrl+U");
|
||||
|
||||
// regular buttons "Slice now" and "Export G-code"
|
||||
|
||||
const int scaled_height = p->btn_remove_device->GetBitmapHeight() + 4;
|
||||
const int scaled_height = p->btn_eject_device->GetBitmapHeight() + 4;
|
||||
auto init_btn = [this](wxButton **btn, wxString label, const int button_height) {
|
||||
*btn = new wxButton(this, wxID_ANY, label, wxDefaultPosition,
|
||||
wxSize(-1, button_height), wxBU_EXACTFIT);
|
||||
@ -912,7 +768,7 @@ Sidebar::Sidebar(Plater *parent)
|
||||
complect_btns_sizer->Add(p->btn_export_gcode, 1, wxEXPAND);
|
||||
complect_btns_sizer->Add(p->btn_send_gcode);
|
||||
complect_btns_sizer->Add(p->btn_export_gcode_removable);
|
||||
complect_btns_sizer->Add(p->btn_remove_device);
|
||||
complect_btns_sizer->Add(p->btn_eject_device);
|
||||
|
||||
|
||||
btns_sizer->Add(p->btn_reslice, 0, wxEXPAND | wxTOP, margin_5);
|
||||
@ -929,20 +785,20 @@ Sidebar::Sidebar(Plater *parent)
|
||||
{
|
||||
const bool export_gcode_after_slicing = wxGetKeyState(WXK_SHIFT);
|
||||
if (export_gcode_after_slicing)
|
||||
p->plater->export_gcode();
|
||||
p->plater->export_gcode(true);
|
||||
else
|
||||
p->plater->reslice();
|
||||
p->plater->select_view_3D("Preview");
|
||||
});
|
||||
p->btn_send_gcode->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->send_gcode(); });
|
||||
p->btn_remove_device->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->eject_drive(); });
|
||||
p->btn_eject_device->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->eject_drive(); });
|
||||
p->btn_export_gcode_removable->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { p->plater->export_gcode(true); });
|
||||
}
|
||||
|
||||
Sidebar::~Sidebar() {}
|
||||
|
||||
void Sidebar::init_filament_combo(PresetComboBox **combo, const int extr_idx) {
|
||||
*combo = new PresetComboBox(p->presets_panel, Slic3r::Preset::TYPE_FILAMENT);
|
||||
void Sidebar::init_filament_combo(PlaterPresetComboBox **combo, const int extr_idx) {
|
||||
*combo = new PlaterPresetComboBox(p->presets_panel, Slic3r::Preset::TYPE_FILAMENT);
|
||||
// # copy icons from first choice
|
||||
// $choice->SetItemBitmap($_, $choices->[0]->GetItemBitmap($_)) for 0..$#presets;
|
||||
|
||||
@ -977,18 +833,18 @@ void Sidebar::update_all_preset_comboboxes()
|
||||
|
||||
// Update the print choosers to only contain the compatible presets, update the dirty flags.
|
||||
if (print_tech == ptFFF)
|
||||
preset_bundle.prints.update_plater_ui(p->combo_print);
|
||||
p->combo_print->update();
|
||||
else {
|
||||
preset_bundle.sla_prints.update_plater_ui(p->combo_sla_print);
|
||||
preset_bundle.sla_materials.update_plater_ui(p->combo_sla_material);
|
||||
p->combo_sla_print->update();
|
||||
p->combo_sla_material->update();
|
||||
}
|
||||
// Update the printer choosers, update the dirty flags.
|
||||
preset_bundle.printers.update_plater_ui(p->combo_printer);
|
||||
p->combo_printer->update();
|
||||
// Update the filament choosers to only contain the compatible presets, update the color preview,
|
||||
// update the dirty flags.
|
||||
if (print_tech == ptFFF) {
|
||||
for (size_t i = 0; i < p->combos_filament.size(); ++i)
|
||||
preset_bundle.update_plater_filament_ui(i, p->combos_filament[i]);
|
||||
for (PlaterPresetComboBox* cb : p->combos_filament)
|
||||
cb->update();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1010,23 +866,22 @@ void Sidebar::update_presets(Preset::Type preset_type)
|
||||
preset_bundle.set_filament_preset(0, name);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < filament_cnt; i++) {
|
||||
preset_bundle.update_plater_filament_ui(i, p->combos_filament[i]);
|
||||
}
|
||||
for (size_t i = 0; i < filament_cnt; i++)
|
||||
p->combos_filament[i]->update();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case Preset::TYPE_PRINT:
|
||||
preset_bundle.prints.update_plater_ui(p->combo_print);
|
||||
p->combo_print->update();
|
||||
break;
|
||||
|
||||
case Preset::TYPE_SLA_PRINT:
|
||||
preset_bundle.sla_prints.update_plater_ui(p->combo_sla_print);
|
||||
p->combo_sla_print->update();
|
||||
break;
|
||||
|
||||
case Preset::TYPE_SLA_MATERIAL:
|
||||
preset_bundle.sla_materials.update_plater_ui(p->combo_sla_material);
|
||||
p->combo_sla_material->update();
|
||||
break;
|
||||
|
||||
case Preset::TYPE_PRINTER:
|
||||
@ -1062,18 +917,14 @@ void Sidebar::msw_rescale()
|
||||
|
||||
p->mode_sizer->msw_rescale();
|
||||
|
||||
// Rescale preset comboboxes in respect to the current em_unit ...
|
||||
for (PresetComboBox* combo : std::vector<PresetComboBox*> { p->combo_print,
|
||||
for (PlaterPresetComboBox* combo : std::vector<PlaterPresetComboBox*> { p->combo_print,
|
||||
p->combo_sla_print,
|
||||
p->combo_sla_material,
|
||||
p->combo_printer } )
|
||||
combo->msw_rescale();
|
||||
for (PresetComboBox* combo : p->combos_filament)
|
||||
for (PlaterPresetComboBox* combo : p->combos_filament)
|
||||
combo->msw_rescale();
|
||||
|
||||
// ... then refill them and set min size to correct layout of the sidebar
|
||||
update_all_preset_comboboxes();
|
||||
|
||||
p->frequently_changed_parameters->msw_rescale();
|
||||
p->object_list->msw_rescale();
|
||||
p->object_manipulation->msw_rescale();
|
||||
@ -1083,9 +934,9 @@ void Sidebar::msw_rescale()
|
||||
p->object_info->msw_rescale();
|
||||
|
||||
p->btn_send_gcode->msw_rescale();
|
||||
p->btn_remove_device->msw_rescale();
|
||||
p->btn_eject_device->msw_rescale();
|
||||
p->btn_export_gcode_removable->msw_rescale();
|
||||
const int scaled_height = p->btn_remove_device->GetBitmap().GetHeight() + 4;
|
||||
const int scaled_height = p->btn_eject_device->GetBitmap().GetHeight() + 4;
|
||||
p->btn_export_gcode->SetMinSize(wxSize(-1, scaled_height));
|
||||
p->btn_reslice ->SetMinSize(wxSize(-1, scaled_height));
|
||||
|
||||
@ -1094,27 +945,21 @@ void Sidebar::msw_rescale()
|
||||
|
||||
void Sidebar::sys_color_changed()
|
||||
{
|
||||
// Update preset comboboxes in respect to the system color ...
|
||||
// combo->msw_rescale() updates icon on button, so use it
|
||||
for (PresetComboBox* combo : std::vector<PresetComboBox*>{ p->combo_print,
|
||||
for (PlaterPresetComboBox* combo : std::vector<PlaterPresetComboBox*>{ p->combo_print,
|
||||
p->combo_sla_print,
|
||||
p->combo_sla_material,
|
||||
p->combo_printer })
|
||||
combo->msw_rescale();
|
||||
for (PresetComboBox* combo : p->combos_filament)
|
||||
for (PlaterPresetComboBox* combo : p->combos_filament)
|
||||
combo->msw_rescale();
|
||||
|
||||
// ... then refill them and set min size to correct layout of the sidebar
|
||||
update_all_preset_comboboxes();
|
||||
|
||||
p->object_list->sys_color_changed();
|
||||
p->object_manipulation->sys_color_changed();
|
||||
// p->object_settings->msw_rescale();
|
||||
p->object_layers->sys_color_changed();
|
||||
|
||||
// btn...->msw_rescale() updates icon on button, so use it
|
||||
p->btn_send_gcode->msw_rescale();
|
||||
p->btn_remove_device->msw_rescale();
|
||||
p->btn_eject_device->msw_rescale();
|
||||
p->btn_export_gcode_removable->msw_rescale();
|
||||
|
||||
p->scrolled->Layout();
|
||||
@ -1350,6 +1195,12 @@ void Sidebar::update_sliced_info_sizer()
|
||||
new_label += format_wxstr("\n - %1%", _L("normal mode"));
|
||||
info_text += format_wxstr("\n%1%", ps.estimated_normal_print_time);
|
||||
fill_labels(ps.estimated_normal_custom_gcode_print_times, new_label, info_text);
|
||||
|
||||
// uncomment next line to not disappear slicing finished notif when colapsing sidebar before time estimate
|
||||
//if (p->plater->is_sidebar_collapsed())
|
||||
p->plater->get_notification_manager()->set_slicing_complete_large(p->plater->is_sidebar_collapsed());
|
||||
p->plater->get_notification_manager()->set_slicing_complete_print_time("Estimated printing time: " + ps.estimated_normal_print_time);
|
||||
|
||||
}
|
||||
if (ps.estimated_silent_print_time != "N/A") {
|
||||
new_label += format_wxstr("\n - %1%", _L("stealth mode"));
|
||||
@ -1385,15 +1236,16 @@ void Sidebar::enable_buttons(bool enable)
|
||||
p->btn_reslice->Enable(enable);
|
||||
p->btn_export_gcode->Enable(enable);
|
||||
p->btn_send_gcode->Enable(enable);
|
||||
p->btn_remove_device->Enable(enable);
|
||||
p->btn_eject_device->Enable(enable);
|
||||
p->btn_export_gcode_removable->Enable(enable);
|
||||
}
|
||||
|
||||
bool Sidebar::show_reslice(bool show) const { return p->btn_reslice->Show(show); }
|
||||
bool Sidebar::show_export(bool show) const { return p->btn_export_gcode->Show(show); }
|
||||
bool Sidebar::show_send(bool show) const { return p->btn_send_gcode->Show(show); }
|
||||
bool Sidebar::show_disconnect(bool show) const { return p->btn_remove_device->Show(show); }
|
||||
bool Sidebar::show_export_removable(bool show)const { return p->btn_export_gcode_removable->Show(show); }
|
||||
bool Sidebar::show_reslice(bool show) const { return p->btn_reslice->Show(show); }
|
||||
bool Sidebar::show_export(bool show) const { return p->btn_export_gcode->Show(show); }
|
||||
bool Sidebar::show_send(bool show) const { return p->btn_send_gcode->Show(show); }
|
||||
bool Sidebar::show_export_removable(bool show) const { return p->btn_export_gcode_removable->Show(show); }
|
||||
bool Sidebar::show_eject(bool show) const { return p->btn_eject_device->Show(show); }
|
||||
bool Sidebar::get_eject_shown() const { return p->btn_eject_device->IsShown(); }
|
||||
|
||||
bool Sidebar::is_multifilament()
|
||||
{
|
||||
@ -1458,7 +1310,7 @@ void Sidebar::update_ui_from_settings()
|
||||
update_sliced_info_sizer();
|
||||
}
|
||||
|
||||
std::vector<PresetComboBox*>& Sidebar::combos_filament()
|
||||
std::vector<PlaterPresetComboBox*>& Sidebar::combos_filament()
|
||||
{
|
||||
return p->combos_filament;
|
||||
}
|
||||
@ -1591,6 +1443,7 @@ struct Plater::priv
|
||||
GLToolbar view_toolbar;
|
||||
GLToolbar collapse_toolbar;
|
||||
Preview *preview;
|
||||
NotificationManager* notification_manager;
|
||||
|
||||
BackgroundSlicingProcess background_process;
|
||||
bool suppressed_backround_processing_update { false };
|
||||
@ -1775,7 +1628,17 @@ struct Plater::priv
|
||||
void on_slicing_update(SlicingStatusEvent&);
|
||||
void on_slicing_completed(wxCommandEvent&);
|
||||
void on_process_completed(wxCommandEvent&);
|
||||
void on_export_began(wxCommandEvent&);
|
||||
void on_layer_editing_toggled(bool enable);
|
||||
void on_slicing_began();
|
||||
|
||||
void clear_warnings();
|
||||
void add_warning(const Slic3r::PrintStateBase::Warning &warning, size_t oid);
|
||||
void actualizate_warnings(const Model& model, size_t print_oid);
|
||||
// Displays dialog window with list of warnings.
|
||||
// Returns true if user clicks OK.
|
||||
// Returns true if current_warnings vector is empty without showning the dialog
|
||||
bool warnings_dialog();
|
||||
|
||||
void on_action_add(SimpleEvent&);
|
||||
void on_action_split_objects(SimpleEvent&);
|
||||
@ -1826,7 +1689,7 @@ struct Plater::priv
|
||||
// Flag indicating that the G-code export targets a removable device, therefore the show_action_buttons() needs to be called at any case when the background processing finishes.
|
||||
bool writing_to_removable_device = { false };
|
||||
bool inside_snapshot_capture() { return m_prevent_snapshots != 0; }
|
||||
|
||||
bool process_completed_with_error { false };
|
||||
private:
|
||||
bool init_object_menu();
|
||||
bool init_common_menu(wxMenu* menu, const bool is_part = false);
|
||||
@ -1854,6 +1717,11 @@ private:
|
||||
* */
|
||||
std::string m_last_fff_printer_profile_name;
|
||||
std::string m_last_sla_printer_profile_name;
|
||||
|
||||
// vector of all warnings generated by last slicing
|
||||
std::vector<std::pair<Slic3r::PrintStateBase::Warning, size_t>> current_warnings;
|
||||
bool show_warning_dialog { false };
|
||||
|
||||
};
|
||||
|
||||
const std::regex Plater::priv::pattern_bundle(".*[.](amf|amf[.]xml|zip[.]amf|3mf|prusa)", std::regex::icase);
|
||||
@ -1899,6 +1767,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
|
||||
});
|
||||
background_process.set_slicing_completed_event(EVT_SLICING_COMPLETED);
|
||||
background_process.set_finished_event(EVT_PROCESS_COMPLETED);
|
||||
background_process.set_export_began_event(EVT_EXPORT_BEGAN);
|
||||
// Default printer technology for default config.
|
||||
background_process.select_technology(this->printer_technology);
|
||||
// Register progress callback from the Print class to the Plater.
|
||||
@ -2010,8 +1879,9 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
|
||||
preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, [this](wxKeyEvent& evt) { preview->move_double_slider(evt); });
|
||||
preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_EDIT_COLOR_CHANGE, [this](wxKeyEvent& evt) { preview->edit_double_slider(evt); });
|
||||
|
||||
q->Bind(EVT_SLICING_COMPLETED, &priv::on_slicing_completed, this);
|
||||
q->Bind(EVT_SLICING_COMPLETED, &priv::on_slicing_completed, this);
|
||||
q->Bind(EVT_PROCESS_COMPLETED, &priv::on_process_completed, this);
|
||||
q->Bind(EVT_EXPORT_BEGAN, &priv::on_export_began, this);
|
||||
q->Bind(EVT_GLVIEWTOOLBAR_3D, [q](SimpleEvent&) { q->select_view_3D("3D"); });
|
||||
q->Bind(EVT_GLVIEWTOOLBAR_PREVIEW, [q](SimpleEvent&) { q->select_view_3D("Preview"); });
|
||||
|
||||
@ -2038,16 +1908,27 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
|
||||
});
|
||||
#endif /* _WIN32 */
|
||||
|
||||
this->q->Bind(EVT_REMOVABLE_DRIVE_EJECTED, [this](RemovableDriveEjectEvent &evt) {
|
||||
notification_manager = new NotificationManager(this->q);
|
||||
this->q->Bind(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED, [this](EjectDriveNotificationClickedEvent&) { this->q->eject_drive(); });
|
||||
this->q->Bind(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED, [this](ExportGcodeNotificationClickedEvent&) { this->q->export_gcode(true); });
|
||||
this->q->Bind(EVT_PRESET_UPDATE_AVIABLE_CLICKED, [this](PresetUpdateAviableClickedEvent&) { wxGetApp().get_preset_updater()->on_update_notification_confirm(); });
|
||||
|
||||
this->q->Bind(EVT_REMOVABLE_DRIVE_EJECTED, [this, q](RemovableDriveEjectEvent &evt) {
|
||||
if (evt.data.second) {
|
||||
this->show_action_buttons(this->ready_to_slice);
|
||||
Slic3r::GUI::show_info(this->q, format_wxstr(_L("Unmounting successful. The device %s(%s) can now be safely removed from the computer."),
|
||||
evt.data.first.name, evt.data.first.path));
|
||||
} else
|
||||
Slic3r::GUI::show_info(this->q, format_wxstr(_L("Ejecting of device %s(%s) has failed."),
|
||||
evt.data.first.name, evt.data.first.path));
|
||||
notification_manager->push_notification(format(_L("Unmounting successful. The device %s(%s) can now be safely removed from the computer."),evt.data.first.name, evt.data.first.path),
|
||||
NotificationManager::NotificationLevel::RegularNotification, *q->get_current_canvas3D());
|
||||
} else {
|
||||
notification_manager->push_notification(format(_L("Ejecting of device %s(%s) has failed."), evt.data.first.name, evt.data.first.path),
|
||||
NotificationManager::NotificationLevel::ErrorNotification, *q->get_current_canvas3D());
|
||||
}
|
||||
});
|
||||
this->q->Bind(EVT_REMOVABLE_DRIVES_CHANGED, [this, q](RemovableDrivesChangedEvent &) {
|
||||
this->show_action_buttons(this->ready_to_slice);
|
||||
if (!this->sidebar->get_eject_shown()) {
|
||||
notification_manager->close_notification_of_type(NotificationType::ExportToRemovableFinished);
|
||||
}
|
||||
});
|
||||
this->q->Bind(EVT_REMOVABLE_DRIVES_CHANGED, [this](RemovableDrivesChangedEvent &) { this->show_action_buttons(this->ready_to_slice); });
|
||||
// Start the background thread and register this window as a target for update events.
|
||||
wxGetApp().removable_drive_manager()->init(this->q);
|
||||
#ifdef _WIN32
|
||||
@ -2269,6 +2150,8 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
||||
if (!config.empty()) {
|
||||
Preset::normalize(config);
|
||||
wxGetApp().preset_bundle->load_config_model(filename.string(), std::move(config));
|
||||
if (printer_technology == ptFFF)
|
||||
CustomGCode::update_custom_gcode_per_print_z_from_config(model.custom_gcode_per_print_z, &wxGetApp().preset_bundle->project_config);
|
||||
wxGetApp().load_current_presets();
|
||||
is_project_file = true;
|
||||
}
|
||||
@ -2675,6 +2558,8 @@ void Plater::priv::reset()
|
||||
{
|
||||
Plater::TakeSnapshot snapshot(q, _L("Reset Project"));
|
||||
|
||||
clear_warnings();
|
||||
|
||||
set_project_filename(wxEmptyString);
|
||||
|
||||
// Prevent toolpaths preview from rendering while we modify the Print object
|
||||
@ -2844,22 +2729,13 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool
|
||||
// The state of the Print changed, and it is non-zero. Let's validate it and give the user feedback on errors.
|
||||
std::string err = this->background_process.validate();
|
||||
if (err.empty()) {
|
||||
notification_manager->set_all_slicing_errors_gray(true);
|
||||
if (invalidated != Print::APPLY_STATUS_UNCHANGED && this->background_processing_enabled())
|
||||
return_state |= UPDATE_BACKGROUND_PROCESS_RESTART;
|
||||
} else {
|
||||
// The print is not valid.
|
||||
// Only show the error message immediately, if the top level parent of this window is active.
|
||||
auto p = dynamic_cast<wxWindow*>(this->q);
|
||||
while (p->GetParent())
|
||||
p = p->GetParent();
|
||||
auto *top_level_wnd = dynamic_cast<wxTopLevelWindow*>(p);
|
||||
if (! postpone_error_messages && top_level_wnd && top_level_wnd->IsActive()) {
|
||||
// The error returned from the Print needs to be translated into the local language.
|
||||
GUI::show_error(this->q, err);
|
||||
} else {
|
||||
// Show the error message once the main window gets activated.
|
||||
this->delayed_error_message = err;
|
||||
}
|
||||
// The print is not valid.
|
||||
// Show error as notification.
|
||||
notification_manager->push_slicing_error_notification(err, *q->get_current_canvas3D());
|
||||
return_state |= UPDATE_BACKGROUND_PROCESS_INVALID;
|
||||
}
|
||||
} else if (! this->delayed_error_message.empty()) {
|
||||
@ -2867,6 +2743,14 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool
|
||||
return_state |= UPDATE_BACKGROUND_PROCESS_INVALID;
|
||||
}
|
||||
|
||||
//actualizate warnings
|
||||
if (invalidated != Print::APPLY_STATUS_UNCHANGED) {
|
||||
actualizate_warnings(this->q->model(), this->background_process.current_print()->id().id);
|
||||
notification_manager->set_all_slicing_warnings_gray(true);
|
||||
show_warning_dialog = false;
|
||||
process_completed_with_error = false;
|
||||
}
|
||||
|
||||
if (invalidated != Print::APPLY_STATUS_UNCHANGED && was_running && ! this->background_process.running() &&
|
||||
(return_state & UPDATE_BACKGROUND_PROCESS_RESTART) == 0) {
|
||||
// The background processing was killed and it will not be restarted.
|
||||
@ -2929,6 +2813,8 @@ bool Plater::priv::restart_background_process(unsigned int state)
|
||||
this->statusbar()->set_status_text(_L("Cancelling"));
|
||||
this->background_process.stop();
|
||||
});
|
||||
if (!show_warning_dialog)
|
||||
on_slicing_began();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -2955,6 +2841,7 @@ void Plater::priv::export_gcode(fs::path output_path, bool output_path_on_remova
|
||||
if ((state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) != 0)
|
||||
return;
|
||||
|
||||
show_warning_dialog = true;
|
||||
if (! output_path.empty()) {
|
||||
background_process.schedule_export(output_path.string(), output_path_on_removable_media);
|
||||
} else {
|
||||
@ -3336,7 +3223,7 @@ void Plater::priv::set_current_panel(wxPanel* panel)
|
||||
void Plater::priv::on_select_preset(wxCommandEvent &evt)
|
||||
{
|
||||
auto preset_type = static_cast<Preset::Type>(evt.GetInt());
|
||||
auto *combo = static_cast<PresetComboBox*>(evt.GetEventObject());
|
||||
auto *combo = static_cast<PlaterPresetComboBox*>(evt.GetEventObject());
|
||||
|
||||
// see https://github.com/prusa3d/PrusaSlicer/issues/3889
|
||||
// Under OSX: in case of use of a same names written in different case (like "ENDER" and "Ender"),
|
||||
@ -3355,19 +3242,27 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt)
|
||||
//! instead of
|
||||
//! combo->GetStringSelection().ToUTF8().data());
|
||||
|
||||
const std::string preset_name = wxGetApp().preset_bundle->get_preset_name_by_alias(preset_type,
|
||||
std::string preset_name = wxGetApp().preset_bundle->get_preset_name_by_alias(preset_type,
|
||||
Preset::remove_suffix_modified(combo->GetString(selection).ToUTF8().data()));
|
||||
|
||||
if (preset_type == Preset::TYPE_FILAMENT) {
|
||||
wxGetApp().preset_bundle->set_filament_preset(idx, preset_name);
|
||||
}
|
||||
|
||||
bool select_preset = !combo->selection_is_changed_according_to_physical_printers();
|
||||
// TODO: ?
|
||||
if (preset_type == Preset::TYPE_FILAMENT && sidebar->is_multifilament()) {
|
||||
// Only update the plater UI for the 2nd and other filaments.
|
||||
wxGetApp().preset_bundle->update_plater_filament_ui(idx, combo);
|
||||
combo->update();
|
||||
}
|
||||
else {
|
||||
else if (select_preset) {
|
||||
if (preset_type == Preset::TYPE_PRINTER) {
|
||||
PhysicalPrinterCollection& physical_printers = wxGetApp().preset_bundle->physical_printers;
|
||||
if(combo->is_selected_physical_printer())
|
||||
preset_name = physical_printers.get_selected_printer_preset_name();
|
||||
else
|
||||
physical_printers.unselect_printer();
|
||||
}
|
||||
wxWindowUpdateLocker noUpdates(sidebar->presets_panel());
|
||||
wxGetApp().get_tab(preset_type)->select_preset(preset_name);
|
||||
}
|
||||
@ -3433,11 +3328,20 @@ void Plater::priv::on_slicing_update(SlicingStatusEvent &evt)
|
||||
state = print_object->step_state_with_warnings(static_cast<SLAPrintObjectStep>(warning_step));
|
||||
}
|
||||
// Now process state.warnings.
|
||||
for (auto const& warning : state.warnings) {
|
||||
if (warning.current) {
|
||||
notification_manager->push_slicing_warning_notification(warning.message, false, *q->get_current_canvas3D(), object_id.id, warning_step);
|
||||
add_warning(warning, object_id.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Plater::priv::on_slicing_completed(wxCommandEvent &)
|
||||
void Plater::priv::on_slicing_completed(wxCommandEvent & evt)
|
||||
{
|
||||
//notification_manager->push_notification(NotificationType::SlicingComplete, *q->get_current_canvas3D(), evt.GetInt());
|
||||
notification_manager->push_slicing_complete_notification(*q->get_current_canvas3D(), evt.GetInt(), is_sidebar_collapsed());
|
||||
|
||||
switch (this->printer_technology) {
|
||||
case ptFFF:
|
||||
this->update_fff_scene();
|
||||
@ -3450,8 +3354,63 @@ void Plater::priv::on_slicing_completed(wxCommandEvent &)
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
void Plater::priv::on_export_began(wxCommandEvent& evt)
|
||||
{
|
||||
if (show_warning_dialog)
|
||||
warnings_dialog();
|
||||
}
|
||||
void Plater::priv::on_slicing_began()
|
||||
{
|
||||
clear_warnings();
|
||||
notification_manager->close_notification_of_type(NotificationType::SlicingComplete);
|
||||
}
|
||||
void Plater::priv::add_warning(const Slic3r::PrintStateBase::Warning& warning, size_t oid)
|
||||
{
|
||||
for (auto const& it : current_warnings) {
|
||||
if (warning.message_id == it.first.message_id) {
|
||||
if (warning.message_id != 0 || (warning.message_id == 0 && warning.message == it.first.message))
|
||||
return;
|
||||
}
|
||||
}
|
||||
current_warnings.emplace_back(std::pair<Slic3r::PrintStateBase::Warning, size_t>(warning, oid));
|
||||
}
|
||||
void Plater::priv::actualizate_warnings(const Model& model, size_t print_oid)
|
||||
{
|
||||
std::vector<size_t> living_oids;
|
||||
living_oids.push_back(model.id().id);
|
||||
living_oids.push_back(print_oid);
|
||||
for (auto it = model.objects.begin(); it != model.objects.end(); ++it) {
|
||||
living_oids.push_back((*it)->id().id);
|
||||
}
|
||||
notification_manager->compare_warning_oids(living_oids);
|
||||
}
|
||||
void Plater::priv::clear_warnings()
|
||||
{
|
||||
notification_manager->close_slicing_errors_and_warnings();
|
||||
this->current_warnings.clear();
|
||||
}
|
||||
bool Plater::priv::warnings_dialog()
|
||||
{
|
||||
if (current_warnings.empty())
|
||||
return true;
|
||||
std::string text = _u8L("There are active warnings concerning sliced models:\n");
|
||||
bool empt = true;
|
||||
for (auto const& it : current_warnings) {
|
||||
int next_n = it.first.message.find_first_of('\n', 0);
|
||||
text += "\n";
|
||||
if (next_n != std::string::npos)
|
||||
text += it.first.message.substr(0, next_n);
|
||||
else
|
||||
text += it.first.message;
|
||||
}
|
||||
//text += "\n\nDo you still wish to export?";
|
||||
wxMessageDialog msg_wingow(this->q, text, wxString(SLIC3R_APP_NAME " ") + _L("generated warnings"), wxOK);
|
||||
const auto res = msg_wingow.ShowModal();
|
||||
return res == wxID_OK;
|
||||
|
||||
}
|
||||
void Plater::priv::on_process_completed(wxCommandEvent &evt)
|
||||
{
|
||||
// Stop the background task, wait until the thread goes into the "Idle" state.
|
||||
@ -3470,14 +3429,13 @@ void Plater::priv::on_process_completed(wxCommandEvent &evt)
|
||||
if (error) {
|
||||
wxString message = evt.GetString();
|
||||
if (message.IsEmpty())
|
||||
message = _L("Export failed");
|
||||
if (q->m_tracking_popup_menu)
|
||||
// We don't want to pop-up a message box when tracking a pop-up menu.
|
||||
// We postpone the error message instead.
|
||||
q->m_tracking_popup_menu_error_message = message;
|
||||
else
|
||||
show_error(q, message);
|
||||
message = _L("Export failed.");
|
||||
notification_manager->push_slicing_error_notification(boost::nowide::narrow(message), *q->get_current_canvas3D());
|
||||
this->statusbar()->set_status_text(message);
|
||||
const wxString invalid_str = _L("Invalid data");
|
||||
for (auto btn : { ActionButtonType::abReslice, ActionButtonType::abSendGCode, ActionButtonType::abExport })
|
||||
sidebar->set_btn_label(btn, invalid_str);
|
||||
process_completed_with_error = true;
|
||||
}
|
||||
if (canceled)
|
||||
this->statusbar()->set_status_text(_L("Cancelled"));
|
||||
@ -3503,15 +3461,21 @@ void Plater::priv::on_process_completed(wxCommandEvent &evt)
|
||||
default: break;
|
||||
}
|
||||
|
||||
|
||||
if (canceled) {
|
||||
if (wxGetApp().get_mode() == comSimple)
|
||||
sidebar->set_btn_label(ActionButtonType::abReslice, "Slice now");
|
||||
show_action_buttons(true);
|
||||
}
|
||||
else if (this->writing_to_removable_device || wxGetApp().get_mode() == comSimple)
|
||||
else if (wxGetApp().get_mode() == comSimple)
|
||||
{
|
||||
show_action_buttons(false);
|
||||
this->writing_to_removable_device = false;
|
||||
}
|
||||
else if (this->writing_to_removable_device)
|
||||
{
|
||||
show_action_buttons(false);
|
||||
notification_manager->push_notification(NotificationType::ExportToRemovableFinished, *q->get_current_canvas3D());
|
||||
}
|
||||
this->writing_to_removable_device = false;
|
||||
}
|
||||
|
||||
void Plater::priv::on_layer_editing_toggled(bool enable)
|
||||
@ -4142,7 +4106,12 @@ void Plater::priv::show_action_buttons(const bool ready_to_slice) const
|
||||
this->ready_to_slice = ready_to_slice;
|
||||
|
||||
wxWindowUpdateLocker noUpdater(sidebar);
|
||||
const auto prin_host_opt = config->option<ConfigOptionString>("print_host");
|
||||
|
||||
DynamicPrintConfig* selected_printer_config = wxGetApp().preset_bundle->physical_printers.get_selected_printer_config();
|
||||
if (!selected_printer_config)
|
||||
selected_printer_config = config;
|
||||
|
||||
const auto prin_host_opt = selected_printer_config->option<ConfigOptionString>("print_host");
|
||||
const bool send_gcode_shown = prin_host_opt != nullptr && !prin_host_opt->value.empty();
|
||||
|
||||
// when a background processing is ON, export_btn and/or send_btn are showing
|
||||
@ -4153,7 +4122,7 @@ void Plater::priv::show_action_buttons(const bool ready_to_slice) const
|
||||
sidebar->show_export(true) |
|
||||
sidebar->show_send(send_gcode_shown) |
|
||||
sidebar->show_export_removable(removable_media_status.has_removable_drives) |
|
||||
sidebar->show_disconnect(removable_media_status.has_eject))
|
||||
sidebar->show_eject(removable_media_status.has_eject))
|
||||
sidebar->Layout();
|
||||
}
|
||||
else
|
||||
@ -4165,7 +4134,7 @@ void Plater::priv::show_action_buttons(const bool ready_to_slice) const
|
||||
sidebar->show_export(!ready_to_slice) |
|
||||
sidebar->show_send(send_gcode_shown && !ready_to_slice) |
|
||||
sidebar->show_export_removable(!ready_to_slice && removable_media_status.has_removable_drives) |
|
||||
sidebar->show_disconnect(!ready_to_slice && removable_media_status.has_eject))
|
||||
sidebar->show_eject(!ready_to_slice && removable_media_status.has_eject))
|
||||
sidebar->Layout();
|
||||
}
|
||||
}
|
||||
@ -4728,6 +4697,9 @@ void Plater::export_gcode(bool prefer_removable)
|
||||
if (p->model.objects.empty())
|
||||
return;
|
||||
|
||||
if (p->process_completed_with_error)//here
|
||||
return;
|
||||
|
||||
// If possible, remove accents from accented latin characters.
|
||||
// This function is useful for generating file names to be processed by legacy firmwares.
|
||||
fs::path default_output_file;
|
||||
@ -4987,7 +4959,6 @@ void Plater::export_toolpaths_to_obj() const
|
||||
p->preview->get_canvas3d()->export_toolpaths_to_obj(into_u8(path).c_str());
|
||||
}
|
||||
|
||||
|
||||
void Plater::reslice()
|
||||
{
|
||||
// Stop arrange and (or) optimize rotation tasks.
|
||||
@ -5069,7 +5040,9 @@ void Plater::send_gcode()
|
||||
{
|
||||
if (p->model.objects.empty()) { return; }
|
||||
|
||||
PrintHostJob upload_job(p->config);
|
||||
// if physical_printer is selected, send gcode for this printer
|
||||
DynamicPrintConfig* physical_printer_config = wxGetApp().preset_bundle->physical_printers.get_selected_printer_config();
|
||||
PrintHostJob upload_job(physical_printer_config ? physical_printer_config : p->config);
|
||||
if (upload_job.empty()) { return; }
|
||||
|
||||
// Obtain default output path
|
||||
@ -5180,12 +5153,12 @@ void Plater::on_extruders_change(size_t num_extruders)
|
||||
size_t i = choices.size();
|
||||
while ( i < num_extruders )
|
||||
{
|
||||
PresetComboBox* choice/*{ nullptr }*/;
|
||||
PlaterPresetComboBox* choice/*{ nullptr }*/;
|
||||
sidebar().init_filament_combo(&choice, i);
|
||||
choices.push_back(choice);
|
||||
|
||||
// initialize selection
|
||||
wxGetApp().preset_bundle->update_plater_filament_ui(i, choice);
|
||||
choice->update();
|
||||
++i;
|
||||
}
|
||||
|
||||
@ -5673,6 +5646,16 @@ Mouse3DController& Plater::get_mouse3d_controller()
|
||||
return p->mouse3d_controller;
|
||||
}
|
||||
|
||||
const NotificationManager* Plater::get_notification_manager() const
|
||||
{
|
||||
return p->notification_manager;
|
||||
}
|
||||
|
||||
NotificationManager* Plater::get_notification_manager()
|
||||
{
|
||||
return p->notification_manager;
|
||||
}
|
||||
|
||||
bool Plater::can_delete() const { return p->can_delete(); }
|
||||
bool Plater::can_delete_all() const { return p->can_delete_all(); }
|
||||
bool Plater::can_increase_instances() const { return p->can_increase_instances(); }
|
||||
|
@ -6,14 +6,12 @@
|
||||
#include <boost/filesystem/path.hpp>
|
||||
|
||||
#include <wx/panel.h>
|
||||
#include <wx/bmpcbox.h>
|
||||
|
||||
#include "Preset.hpp"
|
||||
#include "Selection.hpp"
|
||||
|
||||
#include "libslic3r/Preset.hpp"
|
||||
#include "libslic3r/BoundingBox.hpp"
|
||||
#include "Jobs/Job.hpp"
|
||||
#include "wxExtensions.hpp"
|
||||
#include "Search.hpp"
|
||||
|
||||
class wxButton;
|
||||
@ -47,49 +45,17 @@ class ObjectLayers;
|
||||
class ObjectList;
|
||||
class GLCanvas3D;
|
||||
class Mouse3DController;
|
||||
class NotificationManager;
|
||||
struct Camera;
|
||||
class Bed3D;
|
||||
class GLToolbar;
|
||||
class PlaterPresetComboBox;
|
||||
|
||||
using t_optgroups = std::vector <std::shared_ptr<ConfigOptionsGroup>>;
|
||||
|
||||
class Plater;
|
||||
enum class ActionButtonType : int;
|
||||
|
||||
class PresetComboBox : public PresetBitmapComboBox
|
||||
{
|
||||
public:
|
||||
PresetComboBox(wxWindow *parent, Preset::Type preset_type);
|
||||
~PresetComboBox();
|
||||
|
||||
ScalableButton* edit_btn { nullptr };
|
||||
|
||||
enum LabelItemType {
|
||||
LABEL_ITEM_MARKER = 0xffffff01,
|
||||
LABEL_ITEM_WIZARD_PRINTERS,
|
||||
LABEL_ITEM_WIZARD_FILAMENTS,
|
||||
LABEL_ITEM_WIZARD_MATERIALS,
|
||||
|
||||
LABEL_ITEM_MAX,
|
||||
};
|
||||
|
||||
void set_label_marker(int item, LabelItemType label_item_type = LABEL_ITEM_MARKER);
|
||||
void set_extruder_idx(const int extr_idx) { extruder_idx = extr_idx; }
|
||||
int get_extruder_idx() const { return extruder_idx; }
|
||||
int em_unit() const { return m_em_unit; }
|
||||
void check_selection(int selection);
|
||||
|
||||
void msw_rescale();
|
||||
|
||||
private:
|
||||
typedef std::size_t Marker;
|
||||
|
||||
Preset::Type preset_type;
|
||||
int last_selected;
|
||||
int extruder_idx = -1;
|
||||
int m_em_unit;
|
||||
};
|
||||
|
||||
class Sidebar : public wxPanel
|
||||
{
|
||||
ConfigOptionMode m_mode;
|
||||
@ -101,7 +67,7 @@ public:
|
||||
Sidebar &operator=(const Sidebar &) = delete;
|
||||
~Sidebar();
|
||||
|
||||
void init_filament_combo(PresetComboBox **combo, const int extr_idx);
|
||||
void init_filament_combo(PlaterPresetComboBox **combo, const int extr_idx);
|
||||
void remove_unused_filament_combos(const size_t current_extruder_count);
|
||||
void update_all_preset_comboboxes();
|
||||
void update_presets(Slic3r::Preset::Type preset_type);
|
||||
@ -130,8 +96,9 @@ public:
|
||||
bool show_reslice(bool show) const;
|
||||
bool show_export(bool show) const;
|
||||
bool show_send(bool show) const;
|
||||
bool show_disconnect(bool show)const;
|
||||
bool show_eject(bool show)const;
|
||||
bool show_export_removable(bool show) const;
|
||||
bool get_eject_shown() const;
|
||||
bool is_multifilament();
|
||||
void update_mode();
|
||||
bool is_collapsed();
|
||||
@ -139,7 +106,7 @@ public:
|
||||
void update_searcher();
|
||||
void update_ui_from_settings();
|
||||
|
||||
std::vector<PresetComboBox*>& combos_filament();
|
||||
std::vector<PlaterPresetComboBox*>& combos_filament();
|
||||
Search::OptionsSearcher& get_searcher();
|
||||
std::string& get_search_line();
|
||||
|
||||
@ -220,7 +187,7 @@ public:
|
||||
|
||||
void cut(size_t obj_idx, size_t instance_idx, coordf_t z, bool keep_upper = true, bool keep_lower = true, bool rotate_lower = false);
|
||||
|
||||
void export_gcode(bool prefer_removable = true);
|
||||
void export_gcode(bool prefer_removable);
|
||||
void export_stl(bool extended = false, bool selection_only = false);
|
||||
void export_amf();
|
||||
void export_3mf(const boost::filesystem::path& output_path = boost::filesystem::path());
|
||||
@ -338,6 +305,9 @@ public:
|
||||
Mouse3DController& get_mouse3d_controller();
|
||||
|
||||
void set_bed_shape() const;
|
||||
|
||||
const NotificationManager* get_notification_manager() const;
|
||||
NotificationManager* get_notification_manager();
|
||||
|
||||
// ROII wrapper for suppressing the Undo / Redo snapshot to be taken.
|
||||
class SuppressSnapshots
|
||||
|
@ -234,30 +234,6 @@ void PreferencesDialog::accept()
|
||||
}
|
||||
}
|
||||
|
||||
#if !ENABLE_LAYOUT_NO_RESTART
|
||||
if (m_settings_layout_changed) {
|
||||
// the dialog needs to be destroyed before the call to recreate_gui()
|
||||
// or sometimes the application crashes into wxDialogBase() destructor
|
||||
// so we put it into an inner scope
|
||||
wxMessageDialog dialog(nullptr,
|
||||
_L("Switching the settings layout mode will trigger application restart.\n"
|
||||
"You will lose content of the plater.") + "\n\n" +
|
||||
_L("Do you want to proceed?"),
|
||||
wxString(SLIC3R_APP_NAME) + " - " + _L("Switching the settings layout mode"),
|
||||
wxICON_QUESTION | wxOK | wxCANCEL);
|
||||
|
||||
if (dialog.ShowModal() == wxID_CANCEL)
|
||||
{
|
||||
int selection = app_config->get("old_settings_layout_mode") == "1" ? 0 :
|
||||
app_config->get("new_settings_layout_mode") == "1" ? 1 :
|
||||
app_config->get("dlg_settings_layout_mode") == "1" ? 2 : 0;
|
||||
|
||||
m_layout_mode_box->SetSelection(selection);
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif // !ENABLE_LAYOUT_NO_RESTART
|
||||
|
||||
for (std::map<std::string, std::string>::iterator it = m_values.begin(); it != m_values.end(); ++it)
|
||||
app_config->set(it->first, it->second);
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user