图标升级
6
.gitignore
vendored
@@ -1,4 +1,6 @@
|
||||
config.json
|
||||
# refer/*
|
||||
refer/*
|
||||
*/__pycache__/*
|
||||
.venv/*
|
||||
.venv/*
|
||||
venv/*
|
||||
__pycache__/*
|
||||
674
LICENSE
Normal file
@@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is 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. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
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.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
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 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. Use with the GNU Affero General Public License.
|
||||
|
||||
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 Affero 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 special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU 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 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 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 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 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 General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
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 GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
51
README.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Printer Screen Menu
|
||||
|
||||
本项目为一个基于 PyQt6 的触摸屏控制与显示界面,作为 AIO_3D_Print_Web_Platform 的附属项目,用于在树莓派或类似设备上显示打印机状态、控制打印流程、查看 GCode 预览以及管理网络与设置。
|
||||
|
||||
主要功能
|
||||
- 状态面板:显示打印作业、进度、温度等信息
|
||||
- 控制面板:发送常用 GCode、暂停/停止/回原点等操作
|
||||
- 设置面板:WiFi 管理、系统设置与悬浮虚拟键盘
|
||||
- GCode 3D 预览:基于 OpenGL 的轻量渲染(适配 eglfs)
|
||||
- 自动风扇状态读取与显示
|
||||
|
||||
关联项目
|
||||
- 本仓库为 AIO_3D_Print_Web_Platform 的附属项目:
|
||||
https://gitea.lhye.work/lhye200/AIO_3D_Print_Web_Platform
|
||||
|
||||
可选相关项目
|
||||
- 该UI会显示该项目管理的风扇信息:
|
||||
https://gitea.lhye.work/lhye200/Raspi_Auto_Fan
|
||||
|
||||
依赖说明
|
||||
- PyQt6:优先通过系统包管理(apt)安装(脚本会尝试检测并安装可用的 python3-pyqt6 包);若系统不可用,则回退到在虚拟环境中通过 pip 安装。
|
||||
- 其它 Python 包(通过 pip 安装,并在 `requirements.txt` 列出):`requests`, `numpy`, `PyOpenGL`, `qrcode`, `Pillow`。
|
||||
|
||||
快速安装
|
||||
1. 赋予安装脚本可执行权限并运行(脚本会为你创建 `venv` 并安装缺失依赖):
|
||||
|
||||
```bash
|
||||
chmod +x install.sh
|
||||
./install.sh
|
||||
```
|
||||
|
||||
2. 启动程序:
|
||||
|
||||
```bash
|
||||
source venv/bin/activate
|
||||
python3 main.py
|
||||
```
|
||||
|
||||
说明与开发者备注
|
||||
- 本项目假设在 Debian/Ubuntu 类系统(支持 apt)上运行;在其他发行版上请根据包管理器调整安装方式。
|
||||
- 安装脚本会优先尝试通过 apt 安装系统级 PyQt6 包(若可用),并在虚拟环境中安装剩余的 pip 依赖。
|
||||
|
||||
致谢与声明
|
||||
- 本仓库为 AIO_3D_Print_Web_Platform 的本地触屏界面辅助项目(非原始主仓库),相关功能与 API 由主项目提供。
|
||||
- 本文档与安装脚本在编写过程中得到 AI 辅助生成与整理。
|
||||
|
||||
License
|
||||
- 详见项目根目录的 LICENSE 文件。
|
||||
|
||||
## 第三方许可说明
|
||||
- 本项目中使用了 [Bootstrap Icons](https://icons.getbootstrap.com/) 作为部分界面图标。Bootstrap Icons 基于 **MIT License** 开源。
|
||||
94
install.sh
@@ -1,10 +1,92 @@
|
||||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# apt install -y python3-pyqt6.qtwebengine
|
||||
# apt install -y python3-pyqt6.qtquick3d
|
||||
# apt install -y python3-pyqt6.qtquick
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
|
||||
VENVDIR="$SCRIPT_DIR/venv"
|
||||
|
||||
echo "== Printer_Screen_Menu 安装脚本 =="
|
||||
|
||||
if ! command -v apt >/dev/null 2>&1; then
|
||||
echo "错误:本脚本需要 apt(Debian/Ubuntu)。请在支持 apt 的系统上运行。"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SUDO=""
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
SUDO="sudo"
|
||||
fi
|
||||
|
||||
echo "更新 apt 索引并安装基础包(python3-venv, python3-pip)..."
|
||||
$SUDO apt-get update -y
|
||||
$SUDO apt-get install -y python3-venv python3-pip
|
||||
|
||||
echo "检测可用的 PyQt6 apt 包并尝试通过 apt 安装(优先使用系统包)..."
|
||||
pyqt_candidates=(
|
||||
python3-pyqt6
|
||||
python3-pyqt6.qt6-opengl
|
||||
python3-pyqt6.qtsvg
|
||||
python3-pyqt6.qt6widgets
|
||||
python3-pyqt6.qtopengl
|
||||
python3-pyqt6.qt6opengl
|
||||
)
|
||||
found_pyqt=()
|
||||
for p in "${pyqt_candidates[@]}"; do
|
||||
if apt-cache show "$p" >/dev/null 2>&1; then
|
||||
found_pyqt+=("$p")
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "${#found_pyqt[@]}" -gt 0 ]; then
|
||||
echo "通过 apt 安装: ${found_pyqt[*]}"
|
||||
$SUDO apt-get install -y "${found_pyqt[@]}"
|
||||
else
|
||||
echo "未检测到合适的 PyQt6 apt 包,将在虚拟环境内使用 pip 安装 PyQt6 作为回退。"
|
||||
install_pyqt_via_pip=1
|
||||
fi
|
||||
|
||||
if apt-cache show python3-opengl >/dev/null 2>&1; then
|
||||
echo "检测到系统级 python3-opengl,尝试通过 apt 安装(可选加速)。"
|
||||
$SUDO apt-get install -y python3-opengl
|
||||
else
|
||||
echo "未检测到 python3-opengl,PyOpenGL 将在虚拟环境中通过 pip 安装。"
|
||||
fi
|
||||
|
||||
echo "创建虚拟环境(如果不存在): $VENVDIR"
|
||||
if [ ! -d "$VENVDIR" ]; then
|
||||
python3 -m venv "$VENVDIR"
|
||||
else
|
||||
echo "虚拟环境已存在,跳过创建。"
|
||||
fi
|
||||
|
||||
echo "激活虚拟环境并升级 pip..."
|
||||
# shellcheck disable=SC1090
|
||||
source "$VENVDIR/bin/activate"
|
||||
python -m pip install --upgrade pip setuptools wheel
|
||||
|
||||
if [ "${install_pyqt_via_pip:-0}" = "1" ]; then
|
||||
echo "在 venv 中通过 pip 安装 PyQt6(回退)..."
|
||||
pip install PyQt6
|
||||
fi
|
||||
|
||||
REQ_FILE="$SCRIPT_DIR/requirements.txt"
|
||||
if [ -f "$REQ_FILE" ]; then
|
||||
echo "通过 requirements.txt 安装 pip 依赖..."
|
||||
pip install -r "$REQ_FILE"
|
||||
else
|
||||
echo "找不到 requirements.txt,安装常用依赖..."
|
||||
pip install requests numpy PyOpenGL qrcode Pillow
|
||||
fi
|
||||
|
||||
echo "\n依赖安装完成。要使用本项目:"
|
||||
echo "1) 激活虚拟环境:source venv/bin/activate"
|
||||
echo "2) 启动程序:python3 main.py"
|
||||
echo "接下来进行 systemd 服务配置... (将会安装于 /opt/Printer_Screen_Menu)"
|
||||
|
||||
|
||||
echo "Stopping existing service..."
|
||||
systemctl disable printer-screen.service
|
||||
systemctl stop printer-screen.service
|
||||
|
||||
echo "Installing Printer Screen Menu..."
|
||||
echo "Cleaning up old installation..."
|
||||
rm -rf /etc/systemd/system/printer-screen.service
|
||||
rm -rf /opt/Printer_Screen_Menu
|
||||
@@ -16,7 +98,7 @@ cp -r ./. /opt/Printer_Screen_Menu/
|
||||
echo "Setting up systemd service..."
|
||||
chmod +x /opt/Printer_Screen_Menu/run.sh
|
||||
|
||||
systemctl disable printer-screen.service
|
||||
|
||||
cp printer-screen.service /etc/systemd/system/
|
||||
systemctl daemon-reload
|
||||
systemctl enable printer-screen.service
|
||||
|
||||
320
main.py
@@ -1,13 +1,15 @@
|
||||
import sys
|
||||
import base64
|
||||
import os
|
||||
import json
|
||||
import time
|
||||
import re
|
||||
|
||||
from datetime import datetime
|
||||
from PyQt6.QtWidgets import (QApplication, QWidget, QVBoxLayout, QHBoxLayout,
|
||||
QPushButton, QLabel, QStackedWidget)
|
||||
QPushButton, QLabel, QStackedWidget, QProgressBar)
|
||||
from PyQt6.QtCore import Qt, QSize, QTimer
|
||||
from PyQt6.QtGui import QIcon, QFont
|
||||
|
||||
from PyQt6.QtGui import QIcon, QFont, QPixmap
|
||||
from pages.status_page import StatusPage
|
||||
from pages.control_page import ControlPage
|
||||
from pages.setting_page import SettingPage
|
||||
@@ -15,29 +17,243 @@ from utils.aio_print_api import AIOPrrintSystemAPI
|
||||
from utils.auto_fan_status import AutoFanStatus
|
||||
from utils.config_parse import ConfigParse
|
||||
from utils.wifi_manager import WifiManager
|
||||
from utils.gcode_viewer import GCodeViewerWidget
|
||||
from utils.get_bootstrap_icon import get_colored_svg_uri
|
||||
|
||||
|
||||
|
||||
class SplashWidget(QWidget):
|
||||
"""开屏启动动画,显示 Logo 与初始化进度"""
|
||||
|
||||
def __init__(self, fix_width=None, fix_height=None, parent=None):
|
||||
self.gcode_viewer = parent.gcode_viewer
|
||||
super().__init__(parent)
|
||||
self.setStyleSheet("background-color: #222222;")
|
||||
self.fix_width = fix_width
|
||||
self.fix_height = fix_height
|
||||
if self.fix_width is not None and self.fix_height is not None:
|
||||
self.setFixedSize(self.fix_width,self.fix_height)
|
||||
|
||||
# 主布局
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
layout.setContentsMargins(40,40,40,40)
|
||||
layout.setSpacing(20)
|
||||
|
||||
# ---- Logo ----
|
||||
self.logo_label = QLabel()
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
logo_path = os.path.join(script_dir, "assets", "img", "logo.jpg")
|
||||
if os.path.isfile(logo_path):
|
||||
pixmap = QPixmap(logo_path)
|
||||
scaled = pixmap.scaled(400, 400,
|
||||
Qt.AspectRatioMode.KeepAspectRatio,
|
||||
Qt.TransformationMode.SmoothTransformation)
|
||||
self.logo_label.setPixmap(scaled)
|
||||
else:
|
||||
self.logo_label.setText("🖨️")
|
||||
self.logo_label.setStyleSheet("font-size: 120px; color: #4CAF50;")
|
||||
self.logo_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
layout.addWidget(self.logo_label)
|
||||
|
||||
# ---- 标题 ----
|
||||
self.title_label = QLabel("Printer Screen Menu")
|
||||
self.title_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
self.title_label.setStyleSheet(
|
||||
"color: #e0e0e0; font-size: 36px; font-weight: 700;"
|
||||
"padding: 10px 0 5px 0;"
|
||||
)
|
||||
layout.addWidget(self.title_label)
|
||||
|
||||
# ---- 副标题 / 版本 ----
|
||||
self.subtitle_label = QLabel("正在启动 ...")
|
||||
self.subtitle_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
self.subtitle_label.setStyleSheet(
|
||||
"color: #888888; font-size: 18px; font-weight: 400;"
|
||||
)
|
||||
layout.addWidget(self.subtitle_label)
|
||||
|
||||
# ---- 进度条 ----
|
||||
self.progress_bar = QProgressBar()
|
||||
self.progress_bar.setRange(0, 0) # 不确定模式(脉冲动画)
|
||||
self.progress_bar.setFixedHeight(6)
|
||||
self.progress_bar.setFixedWidth(500)
|
||||
self.progress_bar.setTextVisible(False)
|
||||
self.progress_bar.setStyleSheet("""
|
||||
QProgressBar {
|
||||
background-color: #333355;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
}
|
||||
QProgressBar::chunk {
|
||||
background-color: #4CAF50;
|
||||
border-radius: 3px;
|
||||
}
|
||||
""")
|
||||
bar_container = QHBoxLayout()
|
||||
bar_container.addStretch()
|
||||
bar_container.addWidget(self.progress_bar)
|
||||
bar_container.addStretch()
|
||||
layout.addLayout(bar_container)
|
||||
|
||||
# ---- 进度文字 ----
|
||||
self.progress_label = QLabel("正在加载配置...")
|
||||
self.progress_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
self.progress_label.setStyleSheet(
|
||||
"color: #a0d8a0; font-size: 22px; font-weight: 500; padding: 10px;"
|
||||
)
|
||||
layout.addWidget(self.progress_label)
|
||||
layout.addWidget(self.gcode_viewer)
|
||||
|
||||
def update_progress(self, text: str):
|
||||
"""更新进度文字并立即刷新界面"""
|
||||
self.progress_label.setText(text)
|
||||
if text.find("界面") != -1:
|
||||
self.subtitle_label.setText("请稍候 (界面加载时间较长) ...")
|
||||
else:
|
||||
self.subtitle_label.setText("请稍候 ...")
|
||||
QApplication.processEvents()
|
||||
|
||||
class TopStacked(QWidget):
|
||||
def __init__(self, fix_width=None, fix_height=None):
|
||||
super().__init__()
|
||||
self.root_layout = QVBoxLayout(self)
|
||||
self.root_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.root_layout.setSpacing(0)
|
||||
self.fix_width = fix_width
|
||||
self.fix_height = fix_height
|
||||
if self.fix_width is not None and self.fix_height is not None:
|
||||
self.setFixedSize(self.fix_width,self.fix_height)
|
||||
|
||||
self.root_stack = QStackedWidget()
|
||||
self.root_stack.setStyleSheet("background-color: #222222;") # 深灰色主显示区
|
||||
|
||||
# !Important: 先初始化带OpenGL的东西防止 Could not queue DRM page flip on screen HDMI1 (Device or resource busy)
|
||||
self.gcode_viewer = GCodeViewerWidget()
|
||||
self.gcode_viewer.setUpdatesEnabled(False)
|
||||
self.gcode_viewer.hide()
|
||||
|
||||
# 1. 模拟的启动画面
|
||||
self.splash_widget = SplashWidget(fix_width=self.fix_width, fix_height=self.fix_height, parent=self)
|
||||
self.root_stack.addWidget(self.splash_widget)
|
||||
|
||||
# 2. 真实主页面容器
|
||||
self.main_content = MainWindow(fix_width=self.fix_width, fix_height=self.fix_height, parent=self)
|
||||
self.root_stack.addWidget(self.main_content)
|
||||
|
||||
self.root_layout.addWidget(self.root_stack)
|
||||
# self.root_layout.addWidget(self.root_container)
|
||||
|
||||
# 初始显示启动界面
|
||||
# self.root_stack.setCurrentWidget(self.splash_widget)
|
||||
self.root_stack.setCurrentIndex(0)
|
||||
|
||||
|
||||
QTimer.singleShot(100, self.main_content.start_init)
|
||||
# self.main_content.start_init()
|
||||
# self.setLayout(self.root_layout)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class MainWindow(QWidget):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
# 初始化 API 客户端
|
||||
def __init__(self, fix_width=None, fix_height=None, parent=None):
|
||||
super().__init__(parent)
|
||||
self.fix_width = fix_width
|
||||
self.fix_height = fix_height
|
||||
if self.fix_width is not None and self.fix_height is not None:
|
||||
self.setFixedSize(self.fix_width,self.fix_height)
|
||||
|
||||
self.splash_widget = parent.splash_widget
|
||||
self.root_stack = parent.root_stack
|
||||
self.gcode_viewer = parent.gcode_viewer
|
||||
|
||||
self._last_network_check = 0.0
|
||||
self._is_network_connected = False
|
||||
self._clock_has_synced = False
|
||||
|
||||
def start_init(self):
|
||||
"""真正的初始化逻辑,使用定时器链式调用,避免卡死主线程"""
|
||||
self.splash_widget.update_progress("正在加载配置...")
|
||||
QTimer.singleShot(100, self._init_step1)
|
||||
|
||||
def _init_step1(self):
|
||||
self.config_parser = ConfigParse()
|
||||
self.config_parser.config_changed.connect(self._on_config_changed)
|
||||
self.splash_widget.update_progress("正在初始化 API 客户端...")
|
||||
QTimer.singleShot(600, self._init_step2)
|
||||
|
||||
def _init_step2(self):
|
||||
self.api_client = AIOPrrintSystemAPI(
|
||||
api_url=self.config_parser.api_url,
|
||||
api_key=self.config_parser.api_key
|
||||
)
|
||||
self.splash_widget.update_progress("正在检测散热风扇...")
|
||||
QTimer.singleShot(600, self._init_step3)
|
||||
|
||||
def _init_step3(self):
|
||||
self.auto_fan_status = AutoFanStatus()
|
||||
self.wifi_manager = WifiManager()
|
||||
self._last_network_check = 0.0
|
||||
self._is_network_connected = False
|
||||
self._clock_has_synced = False # 是否曾成功获取到时间(断网后继续显示)
|
||||
self.init_ui()
|
||||
self.splash_widget.update_progress("正在检查网络连接...")
|
||||
QTimer.singleShot(600, self._init_step4)
|
||||
|
||||
def _init_step4(self):
|
||||
self.wifi_manager = WifiManager()
|
||||
self.splash_widget.update_progress("正在初始化主框架...")
|
||||
QTimer.singleShot(100, self._init_step5)
|
||||
|
||||
def _init_step5(self):
|
||||
# 整体布局附加到 main_content 占位符
|
||||
self.main_layout = QVBoxLayout(self)
|
||||
self.main_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.main_layout.setSpacing(0)
|
||||
|
||||
# 顶部主显示区 (使用 QStackedWidget 切换不同页面)
|
||||
self.stacked_widget = QStackedWidget()
|
||||
self.stacked_widget.setStyleSheet("background-color: #555555;") # 深灰色主显示区
|
||||
|
||||
self.splash_widget.update_progress("正在加载状态界面...")
|
||||
QTimer.singleShot(100, self._init_step5_status)
|
||||
|
||||
def _init_step5_status(self):
|
||||
self.page_status = StatusPage(self.api_client, GcodeViewer=self.gcode_viewer)
|
||||
self.stacked_widget.addWidget(self.page_status)
|
||||
|
||||
self.splash_widget.update_progress("正在加载控制界面...")
|
||||
QTimer.singleShot(100, self._init_step5_control)
|
||||
|
||||
def _init_step5_control(self):
|
||||
self.page_control = ControlPage(self.api_client)
|
||||
self.stacked_widget.addWidget(self.page_control)
|
||||
|
||||
self.splash_widget.update_progress("正在加载设置界面...")
|
||||
QTimer.singleShot(100, self._init_step5_settings)
|
||||
|
||||
def _init_step5_settings(self):
|
||||
self.page_settings = SettingPage(self.api_client)
|
||||
self.stacked_widget.addWidget(self.page_settings)
|
||||
|
||||
self.splash_widget.update_progress("正在组合界面...")
|
||||
QTimer.singleShot(100, self._init_step5_finish)
|
||||
|
||||
def _init_step5_finish(self):
|
||||
self.build_ui_bars()
|
||||
|
||||
# 定时刷新风扇状态显示
|
||||
self._fan_timer = QTimer(self)
|
||||
self._fan_timer.timeout.connect(self._update_top_bar)
|
||||
self._fan_timer.start(1000)
|
||||
self._update_top_bar() # 立即刷新横条数据
|
||||
|
||||
self.splash_widget.update_progress("启动完成!")
|
||||
QTimer.singleShot(600, self._init_step6)
|
||||
|
||||
def _init_step6(self):
|
||||
# 切换到真实主界面
|
||||
self.root_stack.setCurrentIndex(1)
|
||||
self.gcode_viewer.setUpdatesEnabled(True)
|
||||
self.gcode_viewer.show()
|
||||
|
||||
|
||||
def _check_network(self):
|
||||
"""检查网络连接状态(每30秒检测一次,避免频繁调用)"""
|
||||
@@ -63,28 +279,33 @@ class MainWindow(QWidget):
|
||||
"""将信号强度转换为条形图标字符串
|
||||
支持 dBm(负值,如 -45)和百分比(0-100,nmcli 格式)
|
||||
"""
|
||||
icon_name = "reception-0.svg"
|
||||
if signal_val is None:
|
||||
return "⬜⬜⬜"
|
||||
if signal_val < 0:
|
||||
icon_name = "reception-0.svg"
|
||||
elif signal_val < 0:
|
||||
# dBm 格式
|
||||
if signal_val >= -50:
|
||||
return "■■■"
|
||||
icon_name = "reception-4.svg"
|
||||
elif signal_val >= -60:
|
||||
return "■■□"
|
||||
icon_name = "reception-2.svg"
|
||||
elif signal_val >= -70:
|
||||
return "■□□"
|
||||
icon_name = "reception-1.svg"
|
||||
else:
|
||||
return "□□□"
|
||||
icon_name = "reception-0.svg"
|
||||
else:
|
||||
# 百分比格式 (0-100)
|
||||
if signal_val >= 75:
|
||||
return "■■■"
|
||||
icon_name = "reception-4.svg"
|
||||
elif signal_val >= 50:
|
||||
return "■■□"
|
||||
icon_name = "reception-2.svg"
|
||||
elif signal_val >= 25:
|
||||
return "■□□"
|
||||
icon_name = "reception-1.svg"
|
||||
else:
|
||||
return "□□□"
|
||||
icon_name = "reception-0.svg"
|
||||
|
||||
# 为了能够在深色主题正确显示颜色,通过图片标签引入
|
||||
colored_uri = get_colored_svg_uri(icon_name, "#a0d8a0", 40, 20, "0 -4 18 18")
|
||||
return f"<img src='{colored_uri}'>"
|
||||
|
||||
def _update_top_bar(self):
|
||||
"""更新风扇/网络状态横条显示"""
|
||||
@@ -107,10 +328,17 @@ class MainWindow(QWidget):
|
||||
f"background-color: #2a2a2a; color: {color}; "
|
||||
f"font-size: 18px; font-weight: 600; padding: 4px 16px;"
|
||||
)
|
||||
load_color = "#a0d8a0" if s.cpu_load < 1.0 else ("#e8a060" if s.cpu_load < 2.0 else "#e86c60")
|
||||
load_color = "#a0d8a0" if s.cpu_load < 1.0 else ("#e8a060" if s.cpu_load < 3.0 else "#e86c60")
|
||||
cpu_load_str = f"<span style='color:{load_color}'>{s.cpu_load:.2f}</span><span>/4.0</span>"
|
||||
|
||||
icon_temp = get_colored_svg_uri("thermometer-half.svg", color, 20, 20, "-4 -2 18 18")
|
||||
icon_fan = get_colored_svg_uri("fan.svg", color, 20, 20, "-2 -2 18 18")
|
||||
icon_cpu = get_colored_svg_uri("cpu.svg", load_color, 20, 20, "-2 -2 18 18")
|
||||
|
||||
self._fan_label.setText(
|
||||
f"🌡 {temp} {state} 𖣘 {speed} {rpm} 🖥 {cpu_load_str}"
|
||||
f"<img src='{icon_temp}'> {temp} {state} "
|
||||
f"<img src='{icon_fan}'> {speed} {rpm} "
|
||||
f"<img src='{icon_cpu}'> {cpu_load_str}"
|
||||
)
|
||||
self._fan_label.setTextFormat(Qt.TextFormat.RichText)
|
||||
|
||||
@@ -125,6 +353,7 @@ class MainWindow(QWidget):
|
||||
signal_dbm = None
|
||||
bars = self._signal_to_bars(signal_dbm)
|
||||
self._wifi_label.setText(f"Signal: {bars}")
|
||||
self._wifi_label.setTextFormat(Qt.TextFormat.RichText)
|
||||
self._wifi_label.setStyleSheet("color: #a0d8a0; font-size: 18px; font-weight: 600;")
|
||||
else:
|
||||
self._wifi_label.setText("No Signal")
|
||||
@@ -144,25 +373,7 @@ class MainWindow(QWidget):
|
||||
else:
|
||||
self._clock_label.hide()
|
||||
|
||||
def init_ui(self):
|
||||
# 整体布局
|
||||
main_layout = QVBoxLayout(self)
|
||||
main_layout.setContentsMargins(0, 0, 0, 0)
|
||||
main_layout.setSpacing(0)
|
||||
|
||||
# 顶部主显示区 (使用 QStackedWidget 切换不同页面)
|
||||
self.stacked_widget = QStackedWidget()
|
||||
self.stacked_widget.setStyleSheet("background-color: #555555;") # 深灰色主显示区
|
||||
|
||||
# 添加测试页面
|
||||
self.page_status = StatusPage(self.api_client)
|
||||
self.page_control = ControlPage(self.api_client)
|
||||
self.page_settings = SettingPage(self.api_client)
|
||||
|
||||
self.stacked_widget.addWidget(self.page_status)
|
||||
self.stacked_widget.addWidget(self.page_control)
|
||||
self.stacked_widget.addWidget(self.page_settings)
|
||||
|
||||
def build_ui_bars(self):
|
||||
# 风扇状态横条
|
||||
self._top_bar = QWidget()
|
||||
self._top_bar.setFixedHeight(36)
|
||||
@@ -178,8 +389,10 @@ class MainWindow(QWidget):
|
||||
top_layout.addStretch()
|
||||
|
||||
# WiFi 状态指示
|
||||
self._wifi_label = QLabel("📶 --")
|
||||
init_wifi_uri = get_colored_svg_uri('reception-0.svg', '#a0d8a0', 40, 20, "0 -4 18 18")
|
||||
self._wifi_label = QLabel(f"<img src='{init_wifi_uri}'> --")
|
||||
self._wifi_label.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
|
||||
self._wifi_label.setTextFormat(Qt.TextFormat.RichText)
|
||||
self._wifi_label.setStyleSheet("color: #a0d8a0; font-size: 18px; font-weight: 600;")
|
||||
top_layout.addWidget(self._wifi_label)
|
||||
|
||||
@@ -211,11 +424,10 @@ class MainWindow(QWidget):
|
||||
bottom_layout.addWidget(self.btn_settings)
|
||||
|
||||
# 将主显示区、风扇横条、底部按钮加入整体布局
|
||||
main_layout.addWidget(self._top_bar)
|
||||
main_layout.addWidget(self.stacked_widget)
|
||||
main_layout.addWidget(bottom_widget)
|
||||
self.main_layout.addWidget(self._top_bar)
|
||||
self.main_layout.addWidget(self.stacked_widget)
|
||||
self.main_layout.addWidget(bottom_widget)
|
||||
|
||||
self.setLayout(main_layout)
|
||||
self.setStyleSheet("background-color: #666666;") # 整体灰色背景
|
||||
|
||||
def create_nav_button(self, text, callback):
|
||||
@@ -255,9 +467,19 @@ def main():
|
||||
# 隐藏鼠标光标,针对触摸屏优化
|
||||
app.setOverrideCursor(Qt.CursorShape.BlankCursor)
|
||||
|
||||
window = MainWindow()
|
||||
# # 唯一且纯粹的顶级窗口
|
||||
# window = MainWindow()
|
||||
# window.showFullScreen()
|
||||
|
||||
# 全屏无边框显示
|
||||
# # 待窗口在底层完全拿到 Plane 并开始渲染后,触发后续耗时初始化
|
||||
# QTimer.singleShot(100, window.start_init)
|
||||
|
||||
QApplication.setHighDpiScaleFactorRoundingPolicy(Qt.HighDpiScaleFactorRoundingPolicy.PassThrough)
|
||||
screen = QApplication.primaryScreen()
|
||||
fix_width = screen.size().width()
|
||||
fix_height = screen.size().height()
|
||||
|
||||
window = TopStacked(fix_width,fix_height)
|
||||
window.showFullScreen()
|
||||
|
||||
sys.exit(app.exec())
|
||||
|
||||
@@ -9,7 +9,7 @@ from utils.floating_keyboard import FloatingKeyboard
|
||||
|
||||
MOVE_STEP = 10 # 每次点击移动 mm (保留备用)
|
||||
|
||||
MOVE_DETECT_MS = 100
|
||||
MOVE_DETECT_MS = 300
|
||||
|
||||
|
||||
class JoystickWidget(QWidget):
|
||||
@@ -32,7 +32,7 @@ class JoystickWidget(QWidget):
|
||||
|
||||
# 定时发送 GCode
|
||||
self._move_timer = QTimer(self)
|
||||
self._move_timer.setInterval(120) # ~8次/秒
|
||||
self._move_timer.setInterval(MOVE_DETECT_MS)
|
||||
self._move_timer.timeout.connect(self._emit_move)
|
||||
|
||||
# 回调(上层设置)
|
||||
@@ -45,7 +45,9 @@ class JoystickWidget(QWidget):
|
||||
return
|
||||
ratio = min(dist / (self._radius_outer - self._radius_inner), 1.0)
|
||||
# 速度映射:30% ~ 100% * max_speed
|
||||
speed = int((0.3 + ratio * 0.7) * self._max_speed)
|
||||
# speed = int((0.3 + ratio * 0.7) * self._max_speed)
|
||||
# ratio = min(dist / max_dist, 1.0)
|
||||
speed = ratio * self._max_speed / (1000/MOVE_DETECT_MS)
|
||||
if dist > 0:
|
||||
nx = self._dx / dist
|
||||
ny = self._dy / dist
|
||||
@@ -350,12 +352,13 @@ class ControlPage(QWidget):
|
||||
def _on_joystick_move(self, nx, ny, speed):
|
||||
if not self._is_control_enabled():
|
||||
return
|
||||
step = 5.0
|
||||
step = speed * MOVE_DETECT_MS / 1000
|
||||
dx = nx * step
|
||||
dy = ny * step
|
||||
tx = max(self.x_min, min(self.x_max, self.pos_x + dx))
|
||||
ty = max(self.y_min, min(self.y_max, self.pos_y + dy))
|
||||
gcode = f"G1 X{tx:.1f} Y{ty:.1f} F{speed}"
|
||||
print(f"Moving: X{tx:.1f} Y{ty:.1f} F{speed}")
|
||||
self.api_client.send_gcode(gcode)
|
||||
self.pos_x, self.pos_y = tx, ty
|
||||
self._sync_inputs()
|
||||
@@ -363,11 +366,11 @@ class ControlPage(QWidget):
|
||||
def _on_fader_move(self, direction, speed):
|
||||
if not self._is_control_enabled():
|
||||
return
|
||||
print(f"d:{direction} s:{speed}")
|
||||
step = speed * MOVE_DETECT_MS / 1000
|
||||
tz = self.pos_z + direction * step
|
||||
tz = max(self.z_min, min(self.z_max, tz))
|
||||
gcode = f"G1 Z{tz:.3f} F{speed}"
|
||||
print(f"Moving: Z{tz:.3f} F{speed}")
|
||||
self.api_client.send_gcode(gcode)
|
||||
self.pos_z = tz
|
||||
self._sync_inputs()
|
||||
|
||||
@@ -18,14 +18,16 @@ from PyQt6.QtWidgets import (
|
||||
QCheckBox,
|
||||
)
|
||||
from PyQt6.QtCore import Qt, QEvent, QObject, QThread, QTimer, QSize, pyqtSignal
|
||||
from PyQt6.QtGui import QMouseEvent, QPixmap, QImage, QFont
|
||||
from PyQt6.QtGui import QMouseEvent, QPixmap, QImage, QFont, QIcon
|
||||
import codecs
|
||||
import io
|
||||
import os
|
||||
import subprocess
|
||||
import qrcode
|
||||
import re
|
||||
from utils.wifi_manager import WifiManager
|
||||
from utils.floating_keyboard import FloatingKeyboard
|
||||
from utils.get_bootstrap_icon import get_colored_icon, get_colored_pixmap
|
||||
|
||||
|
||||
class DragScrollArea(QScrollArea):
|
||||
@@ -318,7 +320,15 @@ class SettingPage(QWidget):
|
||||
def _styled_message(icon, parent, title, text, buttons=QMessageBox.StandardButton.Ok):
|
||||
"""显示与整体风格一致的暗色主题消息框"""
|
||||
msg = QMessageBox(parent)
|
||||
msg.setIcon(icon)
|
||||
if isinstance(icon, str):
|
||||
if icon == "info":
|
||||
msg.setIconPixmap(get_colored_pixmap("info-circle.svg", "#f8f8f8", 40, 40))
|
||||
elif icon == "warning":
|
||||
msg.setIconPixmap(get_colored_pixmap("exclamation-circle.svg", "#f8f804", 40, 40))
|
||||
elif icon == "error":
|
||||
msg.setIconPixmap(get_colored_pixmap("x-circle.svg", "#f80404", 40, 40))
|
||||
else:
|
||||
msg.setIcon(icon)
|
||||
msg.setWindowTitle(title)
|
||||
msg.setText(text)
|
||||
msg.setStandardButtons(buttons)
|
||||
@@ -332,8 +342,9 @@ class SettingPage(QWidget):
|
||||
}
|
||||
QMessageBox QLabel {
|
||||
color: #f2f2f2;
|
||||
font-size: 20px;
|
||||
font-size: 30px;
|
||||
padding: 10px;
|
||||
background-color: #3f3f3f;
|
||||
}
|
||||
QMessageBox QPushButton {
|
||||
min-width: 130px;
|
||||
@@ -819,12 +830,12 @@ class SettingPage(QWidget):
|
||||
if not ssid:
|
||||
self.hotspot_toggle.setChecked(False)
|
||||
self.hotspot_toggle.blockSignals(False)
|
||||
self._styled_message(QMessageBox.Icon.Warning, self, "提示", "请输入热点名称")
|
||||
self._styled_message("waring", self, "提示", "请输入热点名称")
|
||||
return
|
||||
if len(password) < 8:
|
||||
self.hotspot_toggle.setChecked(False)
|
||||
self.hotspot_toggle.blockSignals(False)
|
||||
self._styled_message(QMessageBox.Icon.Warning, self, "提示", "密码至少需要8位")
|
||||
self._styled_message("waring", self, "提示", "密码至少需要8位")
|
||||
return
|
||||
|
||||
# 按钮UI反馈 → 显示"开启中……"
|
||||
@@ -871,7 +882,7 @@ class SettingPage(QWidget):
|
||||
self.hotspot_password.setEnabled(False)
|
||||
self._generate_qr_code(ssid, self.hotspot_password.text().strip())
|
||||
else:
|
||||
self._styled_message(QMessageBox.Icon.Critical, self, "错误", "开启热点失败: wpa_cli 返回失败")
|
||||
self._styled_message("error", self, "错误", "开启热点失败: wpa_cli 返回失败")
|
||||
self.hotspot_toggle.blockSignals(True)
|
||||
self.hotspot_toggle.setChecked(False)
|
||||
self.hotspot_toggle.blockSignals(False)
|
||||
@@ -879,7 +890,7 @@ class SettingPage(QWidget):
|
||||
self.hotspot_toggle.setEnabled(True)
|
||||
|
||||
def _on_hotspot_open_error(self, err_msg):
|
||||
self._styled_message(QMessageBox.Icon.Critical, self, "错误", f"开启热点失败: {err_msg}")
|
||||
self._styled_message("error", self, "错误", f"开启热点失败: {err_msg}")
|
||||
self.hotspot_toggle.blockSignals(True)
|
||||
self.hotspot_toggle.setChecked(False)
|
||||
self.hotspot_toggle.blockSignals(False)
|
||||
@@ -904,7 +915,7 @@ class SettingPage(QWidget):
|
||||
self.qr_label.clear()
|
||||
self.qr_hint.setText("开启热点后自动生成二维码")
|
||||
self.hotspot_toggle.setEnabled(True)
|
||||
self._styled_message(QMessageBox.Icon.Warning, self, "提示", f"关闭热点时出现异常: {err_msg}")
|
||||
self._styled_message("waring", self, "提示", f"关闭热点时出现异常: {err_msg}")
|
||||
|
||||
def _generate_qr_code(self, ssid, password):
|
||||
"""生成 WiFi 二维码并显示"""
|
||||
@@ -944,7 +955,9 @@ class SettingPage(QWidget):
|
||||
power_layout.addStretch()
|
||||
|
||||
# 重启按钮
|
||||
reboot_btn = QPushButton("↻ 重启系统")
|
||||
reboot_btn = QPushButton(" 重启系统")
|
||||
reboot_btn.setIcon(get_colored_icon("arrow-clockwise.svg", "#ffffff"))
|
||||
reboot_btn.setIconSize(QSize(26, 26))
|
||||
reboot_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
min-height: 70px;
|
||||
@@ -955,6 +968,7 @@ class SettingPage(QWidget):
|
||||
border: 2px solid #4a9fc8;
|
||||
border-radius: 14px;
|
||||
padding: 10px 24px;
|
||||
text-align: middle;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #3a85b3;
|
||||
@@ -971,7 +985,9 @@ class SettingPage(QWidget):
|
||||
power_layout.addSpacing(20)
|
||||
|
||||
# 关机按钮
|
||||
shutdown_btn = QPushButton("⏻ 关机")
|
||||
shutdown_btn = QPushButton(" 关机")
|
||||
shutdown_btn.setIcon(get_colored_icon("power.svg", "#ffffff"))
|
||||
shutdown_btn.setIconSize(QSize(26, 26))
|
||||
shutdown_btn.setStyleSheet("""
|
||||
QPushButton {
|
||||
min-height: 70px;
|
||||
@@ -982,6 +998,7 @@ class SettingPage(QWidget):
|
||||
border: 2px solid #e74c3c;
|
||||
border-radius: 14px;
|
||||
padding: 10px 24px;
|
||||
text-align: middle;
|
||||
}
|
||||
QPushButton:hover {
|
||||
background-color: #e74c3c;
|
||||
@@ -1007,7 +1024,7 @@ class SettingPage(QWidget):
|
||||
)
|
||||
if reply == QMessageBox.StandardButton.Yes:
|
||||
self._styled_message(
|
||||
QMessageBox.Icon.Information, self, "重启",
|
||||
"info", self, "重启",
|
||||
"系统正在重启..."
|
||||
)
|
||||
QTimer.singleShot(500, lambda: os.system("sudo reboot"))
|
||||
@@ -1021,7 +1038,7 @@ class SettingPage(QWidget):
|
||||
)
|
||||
if reply == QMessageBox.StandardButton.Yes:
|
||||
self._styled_message(
|
||||
QMessageBox.Icon.Information, self, "关机",
|
||||
"info", self, "关机",
|
||||
"系统正在关机..."
|
||||
)
|
||||
QTimer.singleShot(500, lambda: os.system("sudo poweroff"))
|
||||
@@ -1070,7 +1087,7 @@ class SettingPage(QWidget):
|
||||
try:
|
||||
saved_networks = self.wifi_manager.list_saved_networks()
|
||||
except Exception as e:
|
||||
self._styled_message(QMessageBox.Icon.Critical, self, "错误", f"无法加载保存的WiFi: {str(e)}")
|
||||
self._styled_message("error", self, "错误", f"无法加载保存的WiFi: {str(e)}")
|
||||
return
|
||||
|
||||
# 比较与上次缓存的网络列表是否有变化
|
||||
@@ -1122,7 +1139,7 @@ class SettingPage(QWidget):
|
||||
|
||||
processed = self._deduplicate_networks(networks)
|
||||
if not processed:
|
||||
self._styled_message(QMessageBox.Icon.Information, self, "提示", "未扫描到可用网络")
|
||||
self._styled_message("info", self, "提示", "未扫描到可用网络")
|
||||
return
|
||||
|
||||
for network in processed:
|
||||
@@ -1179,7 +1196,7 @@ class SettingPage(QWidget):
|
||||
"""扫描出错后的UI恢复(主线程中执行)"""
|
||||
self.scan_button.setEnabled(True)
|
||||
self.scan_button.setText("扫描网络")
|
||||
self._styled_message(QMessageBox.Icon.Critical, self, "错误", f"扫描网络失败: {error_msg}")
|
||||
self._styled_message("error", self, "错误", f"扫描网络失败: {error_msg}")
|
||||
|
||||
def _on_ssid_text_changed(self, text):
|
||||
"""SSID输入框文本变化时,如果清空则重置为开放网络"""
|
||||
@@ -1314,13 +1331,13 @@ class SettingPage(QWidget):
|
||||
"""连接已保存列表中选中的网络(后台线程)"""
|
||||
item = self.saved_wifi_list.currentItem()
|
||||
if item is None:
|
||||
self._styled_message(QMessageBox.Icon.Warning, self, "提示", "请先选择一个已保存网络")
|
||||
self._styled_message("waring", self, "提示", "请先选择一个已保存网络")
|
||||
return
|
||||
network = item.data(Qt.ItemDataRole.UserRole) or {}
|
||||
network_id = network.get("network_id")
|
||||
ssid = network.get("ssid", "")
|
||||
if network_id is None:
|
||||
self._styled_message(QMessageBox.Icon.Warning, self, "提示", "选中网络无效")
|
||||
self._styled_message("waring", self, "提示", "选中网络无效")
|
||||
return
|
||||
|
||||
# 按钮UI反馈
|
||||
@@ -1344,27 +1361,27 @@ class SettingPage(QWidget):
|
||||
self.connect_saved_button.setEnabled(True)
|
||||
self.connect_saved_button.setText("连接到此网络")
|
||||
if ok:
|
||||
self._styled_message(QMessageBox.Icon.Information, self, "成功", f"已连接: {ssid}")
|
||||
self._styled_message("info", self, "成功", f"已连接: {ssid}")
|
||||
else:
|
||||
self._styled_message(QMessageBox.Icon.Critical, self, "错误", "连接失败")
|
||||
self._styled_message("error", self, "错误", "连接失败")
|
||||
self.refresh_saved_wifi()
|
||||
self.refresh_current_status()
|
||||
|
||||
def _on_saved_connect_error(self, err_msg):
|
||||
self.connect_saved_button.setEnabled(True)
|
||||
self.connect_saved_button.setText("连接到此网络")
|
||||
self._styled_message(QMessageBox.Icon.Critical, self, "错误", f"连接失败: {err_msg}")
|
||||
self._styled_message("error", self, "错误", f"连接失败: {err_msg}")
|
||||
|
||||
def remove_selected_saved_wifi(self):
|
||||
item = self.saved_wifi_list.currentItem()
|
||||
if item is None:
|
||||
self._styled_message(QMessageBox.Icon.Warning, self, "提示", "请先选择一个已保存网络")
|
||||
self._styled_message("waring", self, "提示", "请先选择一个已保存网络")
|
||||
return
|
||||
network = item.data(Qt.ItemDataRole.UserRole) or {}
|
||||
network_id = network.get("network_id")
|
||||
ssid = network.get("ssid", "")
|
||||
if network_id is None:
|
||||
self._styled_message(QMessageBox.Icon.Warning, self, "提示", "选中网络无效,无法删除")
|
||||
self._styled_message("waring", self, "提示", "选中网络无效,无法删除")
|
||||
return
|
||||
|
||||
# 按钮UI反馈
|
||||
@@ -1388,14 +1405,14 @@ class SettingPage(QWidget):
|
||||
self.remove_saved_button.setEnabled(True)
|
||||
self.remove_saved_button.setText("删除选中")
|
||||
if ok:
|
||||
self._styled_message(QMessageBox.Icon.Information, self, "成功", f"已删除网络: {ssid}")
|
||||
self._styled_message("info", self, "成功", f"已删除网络: {ssid}")
|
||||
self.refresh_saved_wifi()
|
||||
self.refresh_current_status()
|
||||
|
||||
def _on_remove_error(self, err_msg):
|
||||
self.remove_saved_button.setEnabled(True)
|
||||
self.remove_saved_button.setText("删除选中")
|
||||
self._styled_message(QMessageBox.Icon.Critical, self, "错误", f"删除失败: {err_msg}")
|
||||
self._styled_message("error", self, "错误", f"删除失败: {err_msg}")
|
||||
|
||||
def connect_to_wifi(self):
|
||||
ssid = self.ssid_input.text().strip()
|
||||
@@ -1404,15 +1421,15 @@ class SettingPage(QWidget):
|
||||
auth_mode = self.auth_combo.currentData()
|
||||
|
||||
if not ssid:
|
||||
self._styled_message(QMessageBox.Icon.Warning, self, "警告", "WiFi名称不能为空!")
|
||||
self._styled_message("waring", self, "警告", "WiFi名称不能为空!")
|
||||
return
|
||||
|
||||
if auth_mode == "psk" and not password:
|
||||
self._styled_message(QMessageBox.Icon.Warning, self, "警告", "WPA/WPA2 认证需要密码")
|
||||
self._styled_message("waring", self, "警告", "WPA/WPA2 认证需要密码")
|
||||
return
|
||||
|
||||
if auth_mode == "eap" and (not identity or not password):
|
||||
self._styled_message(QMessageBox.Icon.Warning, self, "警告", "WPA-EAP 认证需要身份和密码")
|
||||
self._styled_message("waring", self, "警告", "WPA-EAP 认证需要身份和密码")
|
||||
return
|
||||
|
||||
# 按钮UI反馈
|
||||
@@ -1436,16 +1453,16 @@ class SettingPage(QWidget):
|
||||
self.connect_button.setEnabled(True)
|
||||
self.connect_button.setText("连接")
|
||||
if ok:
|
||||
self._styled_message(QMessageBox.Icon.Information, self, "成功", f"已连接: {ssid}")
|
||||
self._styled_message("info", self, "成功", f"已连接: {ssid}")
|
||||
else:
|
||||
self._styled_message(QMessageBox.Icon.Critical, self, "错误", "连接失败")
|
||||
self._styled_message("error", self, "错误", "连接失败")
|
||||
self.refresh_saved_wifi()
|
||||
self.refresh_current_status()
|
||||
|
||||
def _on_connect_error(self, err_msg):
|
||||
self.connect_button.setEnabled(True)
|
||||
self.connect_button.setText("连接")
|
||||
self._styled_message(QMessageBox.Icon.Critical, self, "错误", f"连接WiFi失败: {err_msg}")
|
||||
self._styled_message("error", self, "错误", f"连接WiFi失败: {err_msg}")
|
||||
|
||||
def display_setting(self, index):
|
||||
if index < 0:
|
||||
|
||||
@@ -119,10 +119,11 @@ class TempGauge(QWidget):
|
||||
p.drawText(44, 16, w - 44, 24, Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter,
|
||||
f"{self._actual:.0f}°")
|
||||
if self._target > 0:
|
||||
tgt_y = bar_y + bar_h - int((bar_h - 4) * min(self._target / 300, 1))
|
||||
font2 = QFont("sans-serif", 10)
|
||||
p.setFont(font2)
|
||||
p.setPen(QPen(QColor("#888888")))
|
||||
p.drawText(44, 34, w - 44, 20, Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter,
|
||||
p.drawText(44, tgt_y - 10, w - 44, 20, Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter,
|
||||
f"→ {self._target:.0f}°")
|
||||
|
||||
font3 = QFont("sans-serif", 10, QFont.Weight.Bold)
|
||||
@@ -134,9 +135,10 @@ class TempGauge(QWidget):
|
||||
|
||||
# ── 状态页面 ────────────────────────────────────────────
|
||||
class StatusPage(QWidget):
|
||||
def __init__(self, api_client, parent=None):
|
||||
def __init__(self, api_client, GcodeViewer=None, parent=None):
|
||||
super().__init__(parent)
|
||||
self.api_client = api_client
|
||||
self.gcode_viewer = GcodeViewer
|
||||
|
||||
self.file_name = "None"
|
||||
self.progress = 0.0
|
||||
@@ -283,15 +285,24 @@ class StatusPage(QWidget):
|
||||
# ── 右侧预留区域 ─────────────────────────────
|
||||
right_frame = QFrame()
|
||||
right_frame.setStyleSheet("background-color: #3a3a3a; border-radius: 10px;")
|
||||
right_layout = QVBoxLayout(right_frame)
|
||||
right_layout.setContentsMargins(6, 6, 6, 6)
|
||||
self.right_layout = QVBoxLayout(right_frame)
|
||||
self.right_layout.setContentsMargins(6, 6, 6, 6)
|
||||
|
||||
self.gcode_viewer = GCodeViewerWidget()
|
||||
right_layout.addWidget(self.gcode_viewer)
|
||||
# self.gcode_viewer = GCodeViewerWidget()
|
||||
if self.gcode_viewer is not None:
|
||||
self.right_layout.addWidget(self.gcode_viewer)
|
||||
# self.gcode_viewer.setUpdatesEnabled(False)
|
||||
# self.gcode_viewer.hide()
|
||||
|
||||
main_layout.addWidget(left_frame, 2)
|
||||
main_layout.addWidget(right_frame, 3)
|
||||
|
||||
# QTimer.singleShot(5000, self.init_gcode_viewer)
|
||||
|
||||
# def init_gcode_viewer(self):
|
||||
# self.gcode_viewer = GCodeViewerWidget()
|
||||
# self.right_layout.addWidget(self.gcode_viewer)
|
||||
|
||||
def update_status(self):
|
||||
self.fresh_status_valve()
|
||||
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
import re
|
||||
|
||||
|
||||
def load_gcode_vertices(path):
|
||||
vertices = []
|
||||
|
||||
x = 0
|
||||
y = 0
|
||||
z = 0
|
||||
|
||||
with open(path, "r", encoding="utf-8", errors="ignore") as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
|
||||
if not line:
|
||||
continue
|
||||
|
||||
if line.startswith("G0") or line.startswith("G1"):
|
||||
old_x = x
|
||||
old_y = y
|
||||
old_z = z
|
||||
|
||||
mx = re.search(r"X([-0-9.]+)", line)
|
||||
my = re.search(r"Y([-0-9.]+)", line)
|
||||
mz = re.search(r"Z([-0-9.]+)", line)
|
||||
|
||||
if mx:
|
||||
x = float(mx.group(1))
|
||||
|
||||
if my:
|
||||
y = float(my.group(1))
|
||||
|
||||
if mz:
|
||||
z = float(mz.group(1))
|
||||
|
||||
vertices.append({
|
||||
"x1": old_x,
|
||||
"y1": old_y,
|
||||
"z1": old_z,
|
||||
"x2": x,
|
||||
"y2": y,
|
||||
"z2": z,
|
||||
})
|
||||
|
||||
return vertices
|
||||
@@ -1,77 +0,0 @@
|
||||
import functools
|
||||
from flask import Blueprint, request, jsonify
|
||||
from app.models import ApiKey
|
||||
from app.utils.octoprint_client import OctoPrintClient
|
||||
from app.models import SystemConfig
|
||||
|
||||
api_bp = Blueprint('api_handle', __name__, url_prefix='/api/v1')
|
||||
|
||||
def get_octo_client():
|
||||
url = SystemConfig.query.filter_by(key='octoprint_url').first()
|
||||
apikey = SystemConfig.query.filter_by(key='octoprint_apikey').first()
|
||||
if url and url.value and apikey and apikey.value:
|
||||
return OctoPrintClient(url.value, apikey.value)
|
||||
return None
|
||||
|
||||
def require_api_key(f):
|
||||
@functools.wraps(f)
|
||||
def decorated(*args, **kwargs):
|
||||
api_key_header = request.headers.get('X-Api-Key')
|
||||
if not api_key_header:
|
||||
return jsonify({'error': 'Missing API Key in headers (X-Api-Key)'}), 401
|
||||
|
||||
key_record = ApiKey.query.filter_by(key=api_key_header).first()
|
||||
if not key_record:
|
||||
return jsonify({'error': 'Invalid API Key'}), 401
|
||||
|
||||
return f(*args, **kwargs)
|
||||
return decorated
|
||||
|
||||
@api_bp.route('/status', methods=['GET'])
|
||||
@require_api_key
|
||||
def get_status():
|
||||
client = get_octo_client()
|
||||
if not client:
|
||||
return jsonify({'error': 'Printer not configured'}), 503
|
||||
try:
|
||||
status_data = client.get_printer_status()
|
||||
job_data = client.get_job_info()
|
||||
return jsonify({'status': status_data, 'job': job_data})
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@api_bp.route('/octoprint_client', methods=['POST'])
|
||||
@require_api_key
|
||||
def invoke_octoprint_client():
|
||||
"""
|
||||
Expects JSON payload like:
|
||||
{
|
||||
"method": "pause_print",
|
||||
"kwargs": {"action": "pause"}
|
||||
}
|
||||
"""
|
||||
client = get_octo_client()
|
||||
if not client:
|
||||
return jsonify({'error': 'Printer not configured'}), 503
|
||||
|
||||
data = request.get_json()
|
||||
if not data or 'method' not in data:
|
||||
return jsonify({'error': 'Missing method in JSON payload'}), 400
|
||||
|
||||
method_name = data['method']
|
||||
kwargs = data.get('kwargs', {})
|
||||
args = data.get('args', [])
|
||||
|
||||
if not hasattr(client, method_name):
|
||||
return jsonify({'error': f'Method {method_name} not found on OctoPrintClient'}), 400
|
||||
|
||||
func = getattr(client, method_name)
|
||||
if not callable(func) or method_name.startswith('_'):
|
||||
return jsonify({'error': f'Method {method_name} is not allowed'}), 403
|
||||
|
||||
try:
|
||||
result = func(*args, **kwargs)
|
||||
return jsonify({'success': True, 'result': result})
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
@@ -1,549 +0,0 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-2 border-bottom">
|
||||
<div>
|
||||
<h1 class="h2 mb-1"><i class="bi bi-eye text-primary me-2"></i>{{ _('GCode Preview') }}: {{ file.original_filename }}</h1>
|
||||
<div class="text-muted small">
|
||||
<span class="me-3"><i class="bi bi-clock-history me-1"></i>{{ _('Estimated Time:') }} <span class="fw-bold">{{ time_info }}</span></span>
|
||||
<span class="me-3"><i class="bi bi-layers me-1"></i>{{ _('First Layer Time:') }} <span class="fw-bold">{{ layer1_time }}</span></span>
|
||||
<span><i class="bi bi-rulers me-1"></i>{{ _('Filament Used [mm]:') }} <span class="fw-bold">{{ filament_used }}</span></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2 mt-md-0">
|
||||
<a href="{{ url_for('printer.prepare') }}#file-{{ file.id }}" class="btn btn-warning btn-sm rounded shadow-sm fw-bold"><i class="bi bi-printer"></i> {{ _('Go to Print') }}</a>
|
||||
<a href="{{ url_for('main.download_gcode', file_id=file.id) }}" class="btn btn-primary btn-sm rounded shadow-sm ms-2"><i class="bi bi-download"></i> {{ _('Download GCode') }}</a>
|
||||
<a href="{{ url_for('main.files') }}" class="btn btn-outline-secondary btn-sm rounded ms-2 shadow-sm"><i class="bi bi-arrow-left"></i> {{ _('Back') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="loading-overlay" class="text-center py-5 my-5">
|
||||
<div class="spinner-border text-primary shadow-sm" role="status" style="width: 3rem; height: 3rem;"></div>
|
||||
<h4 class="mt-4 text-secondary">{{ _('Loading and Parsing GCode Data...') }}</h4>
|
||||
</div>
|
||||
|
||||
<div class="row d-none" id="preview-container" style="height: 75vh;">
|
||||
<!-- 3D Canvas Area -->
|
||||
<div class="col-md-11 position-relative h-100 p-0 border rounded border-secondary shadow-sm" style="background: #111;">
|
||||
<div id="canvas-container" class="w-100 h-100 d-block overflow-hidden"></div>
|
||||
|
||||
<!-- Legend Overlay -->
|
||||
<div id="legend-overlay" class="position-absolute top-0 start-0 m-3 p-2 rounded shadow bg-dark bg-opacity-75 border border-secondary" style="color: #eee; font-size: 0.85rem; pointer-events: auto; z-index: 10;">
|
||||
</div>
|
||||
|
||||
<!-- Bottom Slider (Intra-Layer Progress) -->
|
||||
<div class="position-absolute bottom-0 start-0 w-100 p-3" style="background: linear-gradient(180deg, transparent 0%, rgba(0,0,0,0.8) 100%); z-index: 10;">
|
||||
<div class="d-flex align-items-center gap-3">
|
||||
<span class="text-white fw-medium text-nowrap user-select-none"><i class="bi bi-play-circle me-1"></i>{{ _('Layer Progress:') }}</span>
|
||||
<input type="range" class="form-range flex-grow-1" id="progress-slider" min="0" max="100" value="100" step="0.1">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Sidebar (Layer Slider) -->
|
||||
<div class="col-md-1 h-100 d-flex flex-column align-items-center justify-content-center bg-white border rounded shadow-sm position-relative">
|
||||
<label class="form-label mb-3 fw-bold text-center text-primary mt-3">{{ _('Layer') }}<br>
|
||||
<span id="layer-display" class="badge bg-primary fs-6 mt-1 shadow-sm px-3 rounded-pill">0</span>
|
||||
</label>
|
||||
<div class="flex-grow-1 w-100 d-flex justify-content-center pb-4 py-2">
|
||||
<input type="range" class="form-range h-100" id="layer-slider" min="0" max="0" value="0" style="writing-mode: bt-lr; -webkit-appearance: slider-vertical; cursor: ns-resize;">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="{{ url_for('static', filename='js/three.min.js') }}"></script>
|
||||
<script src="{{ url_for('static', filename='js/OrbitControls.js') }}"></script>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', async function() {
|
||||
|
||||
// Inject printer machine dimensions via Jinja
|
||||
const bedWidth = {{ machine_width | default(220) }};
|
||||
const bedDepth = {{ machine_depth | default(220) }};
|
||||
const bedHeight = {{ machine_height | default(250) }};
|
||||
const offsetX = {{ offset_x | default(0.0) }};
|
||||
const offsetY = {{ offset_y | default(0.0) }};
|
||||
|
||||
// Type indices for shader visibility filtering
|
||||
let COLORS = {};
|
||||
let TYPE_INDEX = {};
|
||||
let gcodeMat = null;
|
||||
|
||||
const SLICER_CONFIGS = {
|
||||
'Cura': [
|
||||
{ id: 'TRAVEL', label: '{{ _("Travel (Move)") }}', color: 0x405060, defaultShow: false },
|
||||
{ id: 'WALL-OUTER', label: '{{ _("Outer Wall") }}', color: 0xeb8b38, defaultShow: true },
|
||||
{ id: 'WALL-INNER', label: '{{ _("Inner Wall") }}', color: 0x4080cf, defaultShow: true },
|
||||
{ id: 'FILL', label: '{{ _("Infill") }}', color: 0xccc04b, defaultShow: true },
|
||||
{ id: 'SKIN', label: '{{ _("Skin/TopBottom") }}', color: 0x9e60b3, defaultShow: true },
|
||||
{ id: 'SUPPORT', label: '{{ _("Support") }}', color: 0x57b357, defaultShow: true },
|
||||
{ id: 'SKIRT', label: '{{ _("Skirt") }}', color: 0x00ffff, defaultShow: true },
|
||||
{ id: 'SUPPORT-INTERFACE', label: '{{ _("Support Interface") }}', color: 0x2b6b2b, defaultShow: true },
|
||||
{ id: 'DEFAULT', label: '{{ _("Others") }}', color: 0xaaaaaa, defaultShow: true }
|
||||
],
|
||||
'Prusa': [
|
||||
{ id: 'TRAVEL', label: '{{ _("Travel (Move)") }}', color: 0x405060, defaultShow: false },
|
||||
{ id: 'Custom', label: '{{ _("Custom") }}', color: 0xd0e0ff, defaultShow: true },
|
||||
{ id: 'Skirt/Brim', label: '{{ _("Skirt/Brim") }}', color: 0x00FFFF, defaultShow: true },
|
||||
{ id: 'Support material', label: '{{ _("Support material") }}', color: 0x90EE90, defaultShow: true },
|
||||
{ id: 'Perimeter', label: '{{ _("Perimeter") }}', color: 0xFFFFE0, defaultShow: true },
|
||||
{ id: 'External perimeter', label: '{{ _("External perimeter") }}', color: 0xFFA500, defaultShow: true },
|
||||
{ id: 'Solid infill', label: '{{ _("Solid infill") }}', color: 0x800080, defaultShow: true },
|
||||
{ id: 'Overhang perimeter', label: '{{ _("Overhang perimeter") }}', color: 0x00008B, defaultShow: true },
|
||||
{ id: 'Internal infill', label: '{{ _("Internal infill") }}', color: 0x8B0000, defaultShow: true },
|
||||
{ id: 'Bridge infill', label: '{{ _("Bridge infill") }}', color: 0x0000FF, defaultShow: true },
|
||||
{ id: 'Top solid infill', label: '{{ _("Top solid infill") }}', color: 0xFF0000, defaultShow: true },
|
||||
{ id: 'Support material interface', label: '{{ _("Support Interface") }}', color: 0x2b6b2b, defaultShow: true },
|
||||
{ id: 'DEFAULT', label: '{{ _("Others") }}', color: 0xaaaaaa, defaultShow: true }
|
||||
]
|
||||
};
|
||||
|
||||
let layers = [];
|
||||
let scene, camera, renderer, controls;
|
||||
let group = new THREE.Group();
|
||||
|
||||
const layerSlider = document.getElementById('layer-slider');
|
||||
const layerDisplay = document.getElementById('layer-display');
|
||||
const progressSlider = document.getElementById('progress-slider');
|
||||
|
||||
function setupSlicerConfig(text) {
|
||||
let slicerType = 'Cura'; // default
|
||||
if (text.substring(0, 500).includes('generated by PrusaSlicer')) {
|
||||
slicerType = 'Prusa';
|
||||
}
|
||||
|
||||
const config = SLICER_CONFIGS[slicerType];
|
||||
|
||||
// 1. Build uniforms & shader strings dynamically
|
||||
let uniformsObj = {};
|
||||
let fragmentUniformsDecl = '';
|
||||
let fragmentUniformsLogic = '';
|
||||
|
||||
let overlayHTML = '';
|
||||
|
||||
config.forEach((c, idx) => {
|
||||
COLORS[c.id] = new THREE.Color(c.color);
|
||||
TYPE_INDEX[c.id] = idx;
|
||||
|
||||
const uniformName = 'uShow' + idx;
|
||||
uniformsObj[uniformName] = { value: c.defaultShow ? 1.0 : 0.0 };
|
||||
|
||||
fragmentUniformsDecl += `uniform float ${uniformName};\n`;
|
||||
|
||||
if (idx === 0) {
|
||||
fragmentUniformsLogic += `if (t == 0) show = ${uniformName};\n`;
|
||||
} else {
|
||||
fragmentUniformsLogic += ` else if (t == ${idx}) show = ${uniformName};\n`;
|
||||
}
|
||||
|
||||
// Build Legend UI
|
||||
const hexColor = '#' + c.color.toString(16).padStart(6, '0');
|
||||
const opacityStyle = c.defaultShow ? '1.0' : '0.4';
|
||||
overlayHTML += `
|
||||
<div class="mb-1 legend-item user-select-none" data-id="${c.id}" data-uniform="${uniformName}" style="cursor: pointer; transition: opacity 0.2s; opacity: ${opacityStyle};"><span class="d-inline-block rounded-circle me-2 border border-dark" style="width: 12px; height: 12px; background: ${hexColor};"></span>${c.label}</div>`;
|
||||
});
|
||||
|
||||
// Add fallback condition
|
||||
fragmentUniformsLogic += ` else show = 1.0;\n`;
|
||||
|
||||
document.getElementById('legend-overlay').innerHTML = overlayHTML;
|
||||
|
||||
gcodeMat = new THREE.ShaderMaterial({
|
||||
uniforms: uniformsObj,
|
||||
vertexShader: `
|
||||
attribute float pType;
|
||||
varying vec3 vColor;
|
||||
varying float vType;
|
||||
void main() {
|
||||
vColor = color;
|
||||
vType = pType;
|
||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
||||
}
|
||||
`,
|
||||
fragmentShader: `
|
||||
varying vec3 vColor;
|
||||
varying float vType;
|
||||
${fragmentUniformsDecl}
|
||||
void main() {
|
||||
float show = 1.0;
|
||||
int t = int(vType + 0.5);
|
||||
${fragmentUniformsLogic}
|
||||
if (show < 0.5) discard;
|
||||
gl_FragColor = vec4(vColor, 1.0);
|
||||
}
|
||||
`,
|
||||
vertexColors: true,
|
||||
side: THREE.DoubleSide,
|
||||
linewidth: 1
|
||||
});
|
||||
|
||||
// Legend binding
|
||||
document.querySelectorAll('.legend-item').forEach(el => {
|
||||
el.addEventListener('click', function() {
|
||||
const uniformName = this.dataset.uniform;
|
||||
if (uniformName && gcodeMat.uniforms[uniformName]) {
|
||||
const currentVal = gcodeMat.uniforms[uniformName].value;
|
||||
const newVal = currentVal > 0.5 ? 0.0 : 1.0;
|
||||
gcodeMat.uniforms[uniformName].value = newVal;
|
||||
this.style.opacity = newVal > 0.5 ? "1.0" : "0.4";
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function init3D() {
|
||||
const container = document.getElementById('canvas-container');
|
||||
scene = new THREE.Scene();
|
||||
scene.background = new THREE.Color(0x1a1a1a);
|
||||
|
||||
scene.add(group);
|
||||
|
||||
camera = new THREE.PerspectiveCamera(45, container.clientWidth / container.clientHeight, 1, 5000);
|
||||
camera.up.set(0, 0, 1);
|
||||
|
||||
renderer = new THREE.WebGLRenderer({ antialias: true });
|
||||
renderer.setSize(container.clientWidth, container.clientHeight);
|
||||
renderer.setPixelRatio(window.devicePixelRatio);
|
||||
container.appendChild(renderer.domElement);
|
||||
|
||||
controls = new THREE.OrbitControls(camera, renderer.domElement);
|
||||
controls.enableDamping = false;
|
||||
controls.mouseButtons.MIDDLE = THREE.MOUSE.PAN;
|
||||
|
||||
window.addEventListener('resize', onWindowResize);
|
||||
}
|
||||
|
||||
function onWindowResize() {
|
||||
const container = document.getElementById('canvas-container');
|
||||
camera.aspect = container.clientWidth / container.clientHeight;
|
||||
camera.updateProjectionMatrix();
|
||||
renderer.setSize(container.clientWidth, container.clientHeight);
|
||||
}
|
||||
|
||||
function animate() {
|
||||
requestAnimationFrame(animate);
|
||||
controls.update();
|
||||
renderer.render(scene, camera);
|
||||
}
|
||||
|
||||
async function loadGCode() {
|
||||
try {
|
||||
const url = '{{ url_for("main.download_gcode", file_id=file.id) }}';
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) throw new Error("GCode Request Failed");
|
||||
|
||||
const gcodeText = await response.text();
|
||||
|
||||
document.getElementById('loading-overlay').classList.add('d-none');
|
||||
document.getElementById('preview-container').classList.remove('d-none');
|
||||
|
||||
setupSlicerConfig(gcodeText);
|
||||
init3D();
|
||||
parseGCode(gcodeText);
|
||||
|
||||
// Add grid matching printer size
|
||||
setupMachineEnvironment();
|
||||
|
||||
animate();
|
||||
|
||||
// Init controls
|
||||
layerSlider.max = Math.max(0, layers.length - 1);
|
||||
layerSlider.value = Math.max(0, layers.length - 1);
|
||||
updateUI();
|
||||
|
||||
layerSlider.addEventListener('input', updateUI);
|
||||
progressSlider.addEventListener('input', updateUI);
|
||||
|
||||
} catch(e) {
|
||||
console.error("Error Loading GCode", e);
|
||||
document.getElementById('loading-overlay').innerHTML = `
|
||||
<div class="text-danger my-5 py-5">
|
||||
<i class="bi bi-exclamation-triangle display-1"></i>
|
||||
<h3 class="mt-3">{{ _('Failed to load GCode preview.') }}</h3>
|
||||
<p class="text-muted">${e.toString()}</p>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
function parseGCode(text) {
|
||||
const lines = text.split('\n');
|
||||
|
||||
let current = { x: 0, y: 0, z: 0, e: 0 };
|
||||
let relativeE = false; // Track M83 (relative) vs M82 (absolute)
|
||||
|
||||
// Dynamically compute width and layer height based on gcode info if possible
|
||||
let extWidth = 0.4;
|
||||
let layerHeight = 0.2;
|
||||
let pWidth = extWidth;
|
||||
|
||||
let currentTypeStr = 'DEFAULT';
|
||||
|
||||
let currentExtrudePoints = [];
|
||||
let currentExtrudeColors = [];
|
||||
let currentExtrudeTypes = [];
|
||||
|
||||
let currentTravelPoints = [];
|
||||
let currentTravelColors = [];
|
||||
let currentTravelTypes = [];
|
||||
|
||||
function flushLayer() {
|
||||
if (currentExtrudePoints.length === 0 && currentTravelPoints.length === 0) return;
|
||||
|
||||
let layerGroup = new THREE.Group();
|
||||
|
||||
if (currentExtrudePoints.length > 0) {
|
||||
const geo = new THREE.BufferGeometry();
|
||||
geo.setAttribute('position', new THREE.Float32BufferAttribute(currentExtrudePoints, 3));
|
||||
geo.setAttribute('color', new THREE.Float32BufferAttribute(currentExtrudeColors, 3));
|
||||
geo.setAttribute('pType', new THREE.Float32BufferAttribute(currentExtrudeTypes, 1));
|
||||
const mesh = new THREE.Mesh(geo, gcodeMat);
|
||||
mesh.userData.isExtrude = true;
|
||||
layerGroup.add(mesh);
|
||||
currentExtrudePoints = []; currentExtrudeColors = []; currentExtrudeTypes = [];
|
||||
}
|
||||
|
||||
if (currentTravelPoints.length > 0) {
|
||||
const geo = new THREE.BufferGeometry();
|
||||
geo.setAttribute('position', new THREE.Float32BufferAttribute(currentTravelPoints, 3));
|
||||
geo.setAttribute('color', new THREE.Float32BufferAttribute(currentTravelColors, 3));
|
||||
geo.setAttribute('pType', new THREE.Float32BufferAttribute(currentTravelTypes, 1));
|
||||
const lineSeg = new THREE.LineSegments(geo, gcodeMat);
|
||||
lineSeg.userData.isTravel = true;
|
||||
layerGroup.add(lineSeg);
|
||||
currentTravelPoints = []; currentTravelColors = []; currentTravelTypes = [];
|
||||
}
|
||||
|
||||
layers.push(layerGroup);
|
||||
group.add(layerGroup);
|
||||
}
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
let chunk = lines[i].trim();
|
||||
if (!chunk) continue;
|
||||
let upperChunk = chunk.toUpperCase();
|
||||
|
||||
if (upperChunk.startsWith('M82')) relativeE = false;
|
||||
else if (upperChunk.startsWith('M83')) relativeE = true;
|
||||
|
||||
if (upperChunk.startsWith(';LAYER:') || upperChunk.startsWith(';LAYER_CHANGE')) {
|
||||
flushLayer();
|
||||
} else if (upperChunk.startsWith(';LAYER_HEIGHT:')) {
|
||||
let lh = parseFloat(chunk.substring(14));
|
||||
if (!isNaN(lh) && lh > 0) layerHeight = lh;
|
||||
} else if (upperChunk.startsWith(';HEIGHT:')) {
|
||||
let lh = parseFloat(chunk.substring(8));
|
||||
if (!isNaN(lh) && lh > 0) layerHeight = lh;
|
||||
} else if (upperChunk.startsWith(';WIDTH:')) {
|
||||
let w = parseFloat(chunk.substring(7));
|
||||
if (!isNaN(w) && w > 0) pWidth = w;
|
||||
} else if (upperChunk.startsWith(';TYPE:')) {
|
||||
currentTypeStr = chunk.substring(6).trim();
|
||||
} else if (chunk.startsWith(';') && COLORS[chunk.substring(1).trim()] !== undefined) {
|
||||
currentTypeStr = chunk.substring(1).trim();
|
||||
} else if (upperChunk.startsWith(';') && chunk.includes(' perimeter')) {
|
||||
currentTypeStr = chunk.substring(1).trim();
|
||||
} else if (upperChunk.startsWith(';') && chunk.includes(' infill')) {
|
||||
// Heuristics for Prusa/Slic3r specific comments like `; Internal infill`
|
||||
currentTypeStr = chunk.substring(1).trim();
|
||||
} else if (upperChunk.startsWith(';') && chunk.includes(' material')) {
|
||||
// Support material
|
||||
currentTypeStr = chunk.substring(1).trim();
|
||||
} else if (upperChunk.startsWith(';') && chunk.includes('Skirt/Brim')) {
|
||||
// Skirt/Brim
|
||||
currentTypeStr = 'Skirt/Brim';
|
||||
} else if (upperChunk.startsWith('G0') || upperChunk.startsWith('G1')) {
|
||||
let next = { x: current.x, y: current.y, z: current.z, e: current.e };
|
||||
let parts = upperChunk.split(/\s+/);
|
||||
let hasMove = false;
|
||||
let hasE = false;
|
||||
let eVal = 0;
|
||||
|
||||
for (let p of parts) {
|
||||
if (p.startsWith('X')) { let v = parseFloat(p.substring(1)); if(!isNaN(v)) { next.x = v; hasMove = true; } }
|
||||
if (p.startsWith('Y')) { let v = parseFloat(p.substring(1)); if(!isNaN(v)) { next.y = v; hasMove = true; } }
|
||||
if (p.startsWith('Z')) { let v = parseFloat(p.substring(1)); if(!isNaN(v)) { next.z = v; hasMove = true; } }
|
||||
if (p.startsWith('E')) { let v = parseFloat(p.substring(1)); if(!isNaN(v)) { eVal = v; hasE = true; } }
|
||||
}
|
||||
|
||||
if (hasMove && !isNaN(next.x) && !isNaN(next.y) && !isNaN(next.z)) {
|
||||
let isExtrude = false;
|
||||
if (hasE) {
|
||||
if (relativeE) {
|
||||
next.e = current.e + eVal;
|
||||
isExtrude = eVal > 0;
|
||||
} else {
|
||||
next.e = eVal;
|
||||
isExtrude = next.e > current.e;
|
||||
}
|
||||
}
|
||||
// Cura uses G0 for travel generally
|
||||
if (upperChunk.startsWith('G0') && !upperChunk.includes('E')) isExtrude = false;
|
||||
|
||||
let activeType = isExtrude ? currentTypeStr : 'TRAVEL';
|
||||
let resolvedType = activeType;
|
||||
|
||||
if (isExtrude && COLORS[activeType] === undefined) {
|
||||
resolvedType = 'DEFAULT';
|
||||
}
|
||||
|
||||
let col = COLORS[resolvedType] || COLORS['DEFAULT'];
|
||||
let tIdx = TYPE_INDEX[resolvedType] !== undefined ? TYPE_INDEX[resolvedType] : TYPE_INDEX['DEFAULT'];
|
||||
|
||||
if (isExtrude) {
|
||||
let dx = next.x - current.x;
|
||||
let dy = next.y - current.y;
|
||||
let dist = Math.sqrt(dx*dx + dy*dy);
|
||||
if (dist > 0.0001) {
|
||||
let hw = pWidth / 2.0;
|
||||
let hh = layerHeight / 2.0;
|
||||
|
||||
let nx = -(dy / dist) * hw;
|
||||
let ny = (dx / dist) * hw;
|
||||
|
||||
let p1x = current.x + nx, p1y = current.y + ny; // current-left
|
||||
let p2x = current.x - nx, p2y = current.y - ny; // current-right
|
||||
let p3x = next.x + nx, p3y = next.y + ny; // next-left
|
||||
let p4x = next.x - nx, p4y = next.y - ny; // next-right
|
||||
|
||||
// Top face
|
||||
currentExtrudePoints.push(
|
||||
p1x, p1y, current.z + hh,
|
||||
p3x, p3y, next.z + hh,
|
||||
p2x, p2y, current.z + hh,
|
||||
p3x, p3y, next.z + hh,
|
||||
p4x, p4y, next.z + hh,
|
||||
p2x, p2y, current.z + hh
|
||||
);
|
||||
for(let k=0; k<6; k++) { currentExtrudeColors.push(col.r, col.g, col.b); currentExtrudeTypes.push(tIdx); }
|
||||
|
||||
// Bottom face
|
||||
currentExtrudePoints.push(
|
||||
p1x, p1y, current.z - hh,
|
||||
p2x, p2y, current.z - hh,
|
||||
p3x, p3y, next.z - hh,
|
||||
p2x, p2y, current.z - hh,
|
||||
p4x, p4y, next.z - hh,
|
||||
p3x, p3y, next.z - hh
|
||||
);
|
||||
for(let k=0; k<6; k++) { currentExtrudeColors.push(col.r*0.4, col.g*0.4, col.b*0.4); currentExtrudeTypes.push(tIdx); }
|
||||
|
||||
// Left face
|
||||
currentExtrudePoints.push(
|
||||
p1x, p1y, current.z - hh,
|
||||
p3x, p3y, next.z - hh,
|
||||
p1x, p1y, current.z + hh,
|
||||
p3x, p3y, next.z - hh,
|
||||
p3x, p3y, next.z + hh,
|
||||
p1x, p1y, current.z + hh
|
||||
);
|
||||
// Fake lighting based on normal side
|
||||
for(let k=0; k<6; k++) { currentExtrudeColors.push(col.r*0.6, col.g*0.6, col.b*0.6); currentExtrudeTypes.push(tIdx); }
|
||||
|
||||
// Right face
|
||||
currentExtrudePoints.push(
|
||||
p2x, p2y, current.z - hh,
|
||||
p2x, p2y, current.z + hh,
|
||||
p4x, p4y, next.z - hh,
|
||||
p2x, p2y, current.z + hh,
|
||||
p4x, p4y, next.z + hh,
|
||||
p4x, p4y, next.z - hh
|
||||
);
|
||||
for(let k=0; k<6; k++) { currentExtrudeColors.push(col.r*0.8, col.g*0.8, col.b*0.8); currentExtrudeTypes.push(tIdx); }
|
||||
}
|
||||
} else {
|
||||
// Travel lines get slight vertical offset for visibility
|
||||
let zOff = 0.05;
|
||||
currentTravelPoints.push(current.x, current.y, current.z + zOff);
|
||||
currentTravelPoints.push(next.x, next.y, next.z + zOff);
|
||||
currentTravelColors.push(col.r, col.g, col.b, col.r, col.g, col.b);
|
||||
currentTravelTypes.push(tIdx, tIdx);
|
||||
}
|
||||
|
||||
// Update E based on parsed G-code execution type
|
||||
if (hasE) {
|
||||
if (relativeE) current.e += eVal;
|
||||
else current.e = eVal;
|
||||
}
|
||||
|
||||
current.x = next.x; current.y = next.y; current.z = next.z;
|
||||
}
|
||||
} else if (upperChunk.startsWith('G92')) {
|
||||
let parts = chunk.split(/\s+/);
|
||||
for (let p of parts) {
|
||||
if (p.startsWith('E')) { let v = parseFloat(p.substring(1)); if(!isNaN(v)) current.e = v; }
|
||||
if (p.startsWith('X')) { let v = parseFloat(p.substring(1)); if(!isNaN(v)) current.x = v; }
|
||||
if (p.startsWith('Y')) { let v = parseFloat(p.substring(1)); if(!isNaN(v)) current.y = v; }
|
||||
if (p.startsWith('Z')) { let v = parseFloat(p.substring(1)); if(!isNaN(v)) current.z = v; }
|
||||
}
|
||||
}
|
||||
}
|
||||
flushLayer();
|
||||
}
|
||||
|
||||
function setupMachineEnvironment() {
|
||||
if (layers.length === 0) return;
|
||||
|
||||
let bbox = new THREE.Box3();
|
||||
for (let layerGrp of layers) {
|
||||
layerGrp.children.forEach(child => {
|
||||
child.geometry.computeBoundingBox();
|
||||
bbox.union(child.geometry.boundingBox);
|
||||
});
|
||||
}
|
||||
|
||||
// The GCode coordinates for the actual print bed are from (0,0) to (W,H).
|
||||
// The GCode trajectory is ALREADY offset by plater.html during slicing.
|
||||
// We just need to place the grid exactly in the center of the bed: (W/2, H/2).
|
||||
let gridOffsetX = (bedWidth / 2);
|
||||
let gridOffsetY = (bedDepth / 2);
|
||||
|
||||
// Add Grid
|
||||
const gridDivisions = Math.ceil(Math.max(bedWidth, bedDepth) / 10);
|
||||
const gridHelper = new THREE.GridHelper(Math.max(bedWidth, bedDepth), gridDivisions, 0x444444, 0x242424);
|
||||
gridHelper.rotation.x = Math.PI / 2;
|
||||
gridHelper.position.set(gridOffsetX, gridOffsetY, 0);
|
||||
scene.add(gridHelper);
|
||||
|
||||
// Add Printer Volume Outline
|
||||
const boxGeo = new THREE.BoxGeometry(bedWidth, bedDepth, bedHeight);
|
||||
const edges = new THREE.EdgesGeometry(boxGeo);
|
||||
const boxOutline = new THREE.LineSegments(edges, new THREE.LineBasicMaterial({ color: 0x444444 }));
|
||||
boxOutline.position.set(gridOffsetX, gridOffsetY, bedHeight/2);
|
||||
scene.add(boxOutline);
|
||||
|
||||
// Align Camera to target the center of the bed grid
|
||||
controls.target.set(gridOffsetX, gridOffsetY, 0);
|
||||
camera.position.set(gridOffsetX, gridOffsetY - (bedDepth * 1.5), bedHeight * 0.8);
|
||||
}
|
||||
|
||||
function updateUI() {
|
||||
if (layers.length === 0) return;
|
||||
let activeIdx = parseInt(layerSlider.value);
|
||||
let intraProg = parseFloat(progressSlider.value);
|
||||
|
||||
layerDisplay.innerText = activeIdx + " / " + (layers.length - 1);
|
||||
|
||||
for (let i = 0; i < layers.length; i++) {
|
||||
let layerGrp = layers[i];
|
||||
if (i < activeIdx) {
|
||||
layerGrp.visible = true;
|
||||
layerGrp.children.forEach(child => child.geometry.setDrawRange(0, Infinity));
|
||||
} else if (i === activeIdx) {
|
||||
layerGrp.visible = true;
|
||||
layerGrp.children.forEach(child => {
|
||||
let totalVertices = child.geometry.attributes.position.count;
|
||||
let elementsPerUnit = child.userData.isTravel ? 2 : 24;
|
||||
let totalUnits = totalVertices / elementsPerUnit;
|
||||
let drawCount = Math.floor(totalUnits * (intraProg / 100)) * elementsPerUnit;
|
||||
child.geometry.setDrawRange(0, drawCount);
|
||||
});
|
||||
} else {
|
||||
layerGrp.visible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loadGCode();
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -1,34 +0,0 @@
|
||||
|
||||
import QtQuick
|
||||
import QtQuick3D
|
||||
|
||||
Item {
|
||||
id: root
|
||||
property real progress: 100
|
||||
property real maxLayer: 10
|
||||
|
||||
View3D {
|
||||
anchors.fill: parent
|
||||
environment: SceneEnvironment {
|
||||
clearColor: "#1a1a1a"
|
||||
backgroundMode: SceneEnvironment.Color
|
||||
}
|
||||
PerspectiveCamera {
|
||||
id: camera
|
||||
z: 300
|
||||
y: -200
|
||||
eulerRotation.x: 45
|
||||
}
|
||||
DirectionalLight { eulerRotation.x: -45 }
|
||||
|
||||
// Render layers using repeated line models or a custom geometry
|
||||
// Since QtQuick3D dynamic geometry from Python is complex, we render primitives for demo
|
||||
Node { id: gcodeNode }
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "QtQuick3D GCode Preview\nProgress: " + progress.toFixed(1) + "%"
|
||||
color: "white"
|
||||
font.pixelSize: 20
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
import QtQuick
|
||||
import QtQuick.Window
|
||||
import QtQuick3D
|
||||
|
||||
Window {
|
||||
visible: true
|
||||
visibility: Window.FullScreen
|
||||
width: 1280
|
||||
height: 720
|
||||
|
||||
color: "black"
|
||||
|
||||
View3D {
|
||||
anchors.fill: parent
|
||||
|
||||
environment: SceneEnvironment {
|
||||
clearColor: "#202020"
|
||||
backgroundMode: SceneEnvironment.Color
|
||||
}
|
||||
|
||||
PerspectiveCamera {
|
||||
id: camera
|
||||
position: Qt.vector3d(0, 100, 300)
|
||||
eulerRotation.x: -20
|
||||
}
|
||||
|
||||
DirectionalLight {
|
||||
eulerRotation.x: -45
|
||||
brightness: 2
|
||||
}
|
||||
|
||||
Node {
|
||||
id: rootNode
|
||||
|
||||
Repeater3D {
|
||||
model: gcodeData
|
||||
|
||||
delegate: Model {
|
||||
property real dx: modelData.x2 - modelData.x1
|
||||
property real dy: modelData.y2 - modelData.y1
|
||||
property real dz: modelData.z2 - modelData.z1
|
||||
|
||||
property real length: Math.sqrt(dx*dx + dy*dy + dz*dz)
|
||||
|
||||
source: "#Cylinder"
|
||||
|
||||
position: Qt.vector3d(
|
||||
(modelData.x1 + modelData.x2)/2,
|
||||
(modelData.z1 + modelData.z2)/2,
|
||||
(modelData.y1 + modelData.y2)/2
|
||||
)
|
||||
|
||||
scale: Qt.vector3d(0.2, length / 100.0, 0.2)
|
||||
|
||||
materials: [
|
||||
DefaultMaterial {
|
||||
diffuseColor: "#00ff88"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
|
||||
property real lastX
|
||||
property real lastY
|
||||
|
||||
onPressed: {
|
||||
lastX = mouse.x
|
||||
lastY = mouse.y
|
||||
}
|
||||
|
||||
onPositionChanged: {
|
||||
let dx = mouse.x - lastX
|
||||
let dy = mouse.y - lastY
|
||||
|
||||
rootNode.eulerRotation.y += dx * 0.3
|
||||
rootNode.eulerRotation.x += dy * 0.3
|
||||
|
||||
lastX = mouse.x
|
||||
lastY = mouse.y
|
||||
}
|
||||
|
||||
onWheel: {
|
||||
camera.position.z += wheel.angleDelta.y * -0.1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
37
refer/qml.py
@@ -1,37 +0,0 @@
|
||||
import sys
|
||||
from PyQt6.QtGui import QGuiApplication
|
||||
from PyQt6.QtQml import QQmlApplicationEngine
|
||||
from PyQt6.QtCore import QObject, pyqtProperty, pyqtSignal
|
||||
|
||||
from GcodeParser import load_gcode_vertices
|
||||
|
||||
|
||||
class GcodeData(QObject):
|
||||
verticesChanged = pyqtSignal()
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
# self._vertices = load_gcode_vertices("/home/lhye200/.octoprint/uploads/20260414135441_42bff5215c6148b8b5f4d8c4f15d5ddc.gcode")
|
||||
self._vertices = load_gcode_vertices("test.gcode")
|
||||
|
||||
def getVertices(self):
|
||||
return self._vertices
|
||||
|
||||
vertices = pyqtProperty('QVariantList', getVertices, notify=verticesChanged)
|
||||
|
||||
|
||||
app = QGuiApplication(sys.argv)
|
||||
|
||||
engine = QQmlApplicationEngine()
|
||||
|
||||
model = GcodeData()
|
||||
|
||||
engine.rootContext().setContextProperty("gcodeData", model.vertices)
|
||||
|
||||
engine.load("gcode_view2.qml")
|
||||
|
||||
if not engine.rootObjects():
|
||||
sys.exit(-1)
|
||||
|
||||
sys.exit(app.exec())
|
||||
242948
refer/smp4bwithfan.gcode
268
refer/test.gcode
@@ -1,268 +0,0 @@
|
||||
G92 E0 ;Reset Extruder
|
||||
G1 Z20.0 F3000 ;Move Z Axis up
|
||||
M104 S235 ;Set final nozzle temp
|
||||
G1 X-13 Y20 Z3 F5000.0 ;Move to out position
|
||||
M190 S70 ;Wait for bed temp to stabilize
|
||||
M109 S235 ;Wait for nozzle temp to stabilize
|
||||
G1 X-2.1 Y20 Z0.28 F5000.0 ;Move to start position
|
||||
G1 X-2.1 Y145.0 Z0.28 F1500.0 E15 ;Draw the first line
|
||||
G1 X-2.4 Y145.0 Z0.28 F5000.0 ;Move to side a little
|
||||
G1 X-2.4 Y20 Z0.28 F1500.0 E30 ;Draw the second line
|
||||
G92 E0 ;Reset Extruder
|
||||
G1 E-1.0000 F1800 ;Retract a bit
|
||||
G1 Z2.0 F3000 ;Move Z Axis up
|
||||
G1 E0.0000 F1800
|
||||
G21 ; set units to millimeters
|
||||
G90 ; use absolute coordinates
|
||||
M83 ; use relative distances for extrusion
|
||||
M107
|
||||
;LAYER_CHANGE
|
||||
;Z:0.2
|
||||
;HEIGHT:0.2
|
||||
G1 E-1.2 F1800
|
||||
G1 Z.6 F9000
|
||||
G1 X52.817 Y67.413
|
||||
G1 Z.2
|
||||
G1 E1.2 F1800
|
||||
M204 P500
|
||||
;TYPE:Skirt/Brim
|
||||
;WIDTH:0.5
|
||||
G1 X53.827 Y66.755 E.04581
|
||||
G1 X54.927 Y66.264 E.04578
|
||||
G1 X56.091 Y65.95 E.04582
|
||||
G1 X57.5 Y65.814 E.0538
|
||||
G1 X162.711 Y65.823 E3.99868
|
||||
G1 X163.87 Y65.945 E.04429
|
||||
G1 X165.073 Y66.264 E.0473
|
||||
G1 X165.798 Y66.564 E.02982
|
||||
G1 X166.842 Y67.165 E.04578
|
||||
G1 X167.78 Y67.922 E.04581
|
||||
G1 X168.587 Y68.817 E.0458
|
||||
G1 X169.436 Y70.202 E.06174
|
||||
G1 X169.867 Y71.328 E.04582
|
||||
G1 X170.05 Y72.091 E.02982
|
||||
G1 X170.177 Y73.289 E.04579
|
||||
G1 X170.186 Y146.5 E2.78248
|
||||
G1 X170.05 Y147.909 E.0538
|
||||
G1 X169.736 Y149.073 E.04582
|
||||
G1 X169.436 Y149.798 E.02982
|
||||
G1 X168.587 Y151.183 E.06174
|
||||
G1 X168.078 Y151.78 E.02982
|
||||
G1 X167.183 Y152.587 E.0458
|
||||
G1 X165.798 Y153.436 E.06174
|
||||
G1 X164.672 Y153.867 E.04582
|
||||
G1 X163.796 Y154.074 E.03421
|
||||
G1 X129.811 Y160.705 E1.316
|
||||
G1 X128.826 Y160.825 E.03771
|
||||
G1 X127.704 Y160.792 E.04266
|
||||
G1 X56.467 Y154.112 E2.71933
|
||||
G1 X54.927 Y153.736 E.06025
|
||||
G1 X53.827 Y153.245 E.04578
|
||||
G1 X53.158 Y152.835 E.02982
|
||||
G1 X52.22 Y152.078 E.04581
|
||||
G1 X51.413 Y151.183 E.0458
|
||||
G1 X50.755 Y150.173 E.04581
|
||||
G1 X50.264 Y149.073 E.04578
|
||||
G1 X49.95 Y147.909 E.04582
|
||||
G1 X49.814 Y146.5 E.0538
|
||||
G1 X49.814 Y73.5 E2.77446
|
||||
G1 X49.95 Y72.091 E.0538
|
||||
G1 X50.133 Y71.328 E.02982
|
||||
G1 X50.564 Y70.202 E.04582
|
||||
G1 X51.413 Y68.817 E.06174
|
||||
G1 X51.922 Y68.22 E.02982
|
||||
G1 X52.772 Y67.453 E.04351
|
||||
M204 P2500
|
||||
M204 T2000
|
||||
G1 X53.077 Y67.793 F9000
|
||||
M204 P500
|
||||
G1 F1800
|
||||
G1 X53.114 Y67.76 E.00188
|
||||
G1 X54.066 Y67.145 E.04308
|
||||
G1 X55.102 Y66.686 E.04307
|
||||
G1 X56.198 Y66.395 E.0431
|
||||
G1 X57.5 Y66.271 E.04971
|
||||
G1 X162.675 Y66.278 E3.99732
|
||||
G1 X163.765 Y66.39 E.04165
|
||||
G1 X164.554 Y66.575 E.0308
|
||||
G1 X165.623 Y66.986 E.04353
|
||||
G1 X166.603 Y67.555 E.04307
|
||||
G1 X167.73 Y68.517 E.05632
|
||||
G1 X168.445 Y69.397 E.04309
|
||||
G1 X169.014 Y70.377 E.04307
|
||||
G1 X169.314 Y71.102 E.02982
|
||||
G1 X169.605 Y72.198 E.0431
|
||||
G1 X169.722 Y73.325 E.04306
|
||||
G1 X169.729 Y146.5 E2.78111
|
||||
G1 X169.605 Y147.802 E.04971
|
||||
G1 X169.422 Y148.565 E.02982
|
||||
G1 X169.014 Y149.623 E.0431
|
||||
G1 X168.24 Y150.886 E.0563
|
||||
G1 X167.73 Y151.483 E.02984
|
||||
G1 X166.886 Y152.24 E.04309
|
||||
G1 X165.623 Y153.014 E.0563
|
||||
G1 X164.565 Y153.422 E.0431
|
||||
G1 X163.709 Y153.626 E.03344
|
||||
G1 X129.723 Y160.256 E1.31603
|
||||
G1 X128.837 Y160.368 E.03394
|
||||
G1 X127.746 Y160.337 E.04148
|
||||
G1 X56.51 Y153.657 E2.7193
|
||||
G1 X55.102 Y153.314 E.05508
|
||||
G1 X54.066 Y152.855 E.04307
|
||||
G1 X53.114 Y152.24 E.04308
|
||||
G1 X52.517 Y151.73 E.02984
|
||||
G1 X51.76 Y150.886 E.04309
|
||||
G1 X51.145 Y149.934 E.04308
|
||||
G1 X50.686 Y148.898 E.04307
|
||||
G1 X50.395 Y147.802 E.0431
|
||||
G1 X50.271 Y146.5 E.04971
|
||||
G1 X50.271 Y73.5 E2.77446
|
||||
G1 X50.395 Y72.198 E.04971
|
||||
G1 X50.578 Y71.435 E.02982
|
||||
G1 X50.986 Y70.377 E.0431
|
||||
G1 X51.76 Y69.114 E.0563
|
||||
G1 X52.27 Y68.517 E.02984
|
||||
G1 X53.033 Y67.833 E.03895
|
||||
M204 P2500
|
||||
G1 E-1.2
|
||||
G1 Z.6 F9000
|
||||
; printing object temp_edit_f69b6cf4417a43fa81e39d69b3952b58.stl id:0 copy 0
|
||||
G1 X131.959 Y152.611
|
||||
G1 Z.2
|
||||
G1 E1.2 F1800
|
||||
M204 P500
|
||||
;TYPE:Support material
|
||||
G1 X132.246 Y153.441 E.03338
|
||||
G1 X132.344 Y154.317 E.0335
|
||||
G1 X132.246 Y155.193 E.0335
|
||||
G1 X131.959 Y156.022 E.03334
|
||||
G1 X131.547 Y156.692 E.02989
|
||||
G1 X131.146 Y157.136 E.02274
|
||||
G1 X130.723 Y157.499 E.02118
|
||||
G1 X129.894 Y157.918 E.0353
|
||||
G1 X128.891 Y158.141 E.03905
|
||||
G1 X127.954 Y158.118 E.03562
|
||||
G1 X127.013 Y157.85 E.03719
|
||||
G1 X126.539 Y157.579 E.02075
|
||||
G1 X126.056 Y157.138 E.02486
|
||||
G1 X125.499 Y156.436 E.03406
|
||||
G1 X125.153 Y155.827 E.02662
|
||||
G1 X124.929 Y155.168 E.02645
|
||||
G1 X124.834 Y154.278 E.03402
|
||||
G1 X124.963 Y153.42 E.03298
|
||||
G1 X125.287 Y152.609 E.03319
|
||||
G1 X125.63 Y152.1 E.02333
|
||||
G1 X131.643 Y152.1 E.22853
|
||||
G1 X131.923 Y152.553 E.02024
|
||||
M204 P2500
|
||||
G1 X131.838 Y155.742 F9000
|
||||
M204 P500
|
||||
G1 F1800
|
||||
G1 X131.838 Y153.52 E.08445
|
||||
G1 X131.585 Y152.789 E.0294
|
||||
G1 X131.414 Y152.511 E.0124
|
||||
G1 X131.33 Y152.511 E.00319
|
||||
G1 X131.33 Y156.259 E.14245
|
||||
G1 X130.822 Y156.872 E.03026
|
||||
G1 X130.822 Y152.511 E.16575
|
||||
G1 X130.315 Y152.511 E.01927
|
||||
G1 X130.315 Y157.244 E.17988
|
||||
G1 X129.807 Y157.501 E.02164
|
||||
G1 X129.807 Y152.511 E.18965
|
||||
G1 X129.299 Y152.511 E.01931
|
||||
G1 X129.299 Y157.644 E.19509
|
||||
G1 X128.791 Y157.727 E.01956
|
||||
G1 X128.791 Y152.511 E.19824
|
||||
G1 X128.283 Y152.511 E.01931
|
||||
G1 X128.283 Y157.714 E.19775
|
||||
G1 X127.775 Y157.639 E.01952
|
||||
G1 X127.775 Y152.511 E.1949
|
||||
G1 X127.267 Y152.511 E.01931
|
||||
G1 X127.267 Y157.494 E.18939
|
||||
G1 X127.173 Y157.468 E.00371
|
||||
G1 X126.76 Y157.224 E.01823
|
||||
G1 X126.76 Y152.511 E.17912
|
||||
G1 X126.252 Y152.511 E.01931
|
||||
G1 X126.252 Y156.695 E.15902
|
||||
G1 X125.744 Y156.034 E.03168
|
||||
G1 X125.744 Y152.306 E.14169
|
||||
M204 P2500
|
||||
G1 E-1.2
|
||||
G1 Z.6 F9000
|
||||
G1 X140.305 Y87.116
|
||||
G1 Z.2
|
||||
G1 E1.2 F1800
|
||||
M204 P500
|
||||
;TYPE:Perimeter
|
||||
;WIDTH:0.499999
|
||||
G1 X140.124 Y87.275 E.00916
|
||||
G1 X139.922 Y87.388 E.0088
|
||||
G1 X139.444 Y87.556 E.01926
|
||||
G1 X138.76 Y87.587 E.02602
|
||||
G1 X138.531 Y87.548 E.00883
|
||||
G1 X138.055 Y87.379 E.0192
|
||||
G1 X137.502 Y86.973 E.02607
|
||||
G1 X137.349 Y86.799 E.00881
|
||||
G1 X137.085 Y86.371 E.01911
|
||||
G1 X137.01 Y86.175 E.00798
|
||||
G1 X136.904 Y85.68 E.01924
|
||||
G1 X136.899 Y85.473 E.00787
|
||||
G1 X136.967 Y84.955 E.01986
|
||||
G1 X137.244 Y84.346 E.02543
|
||||
G1 X137.382 Y84.159 E.00883
|
||||
G1 X137.771 Y83.79 E.02038
|
||||
G1 X138.359 Y83.499 E.02493
|
||||
G1 X138.584 Y83.441 E.00883
|
||||
G1 X139.121 Y83.397 E.02048
|
||||
G1 X139.587 Y83.478 E.01798
|
||||
G1 X140.079 Y83.697 E.02047
|
||||
G1 X140.271 Y83.827 E.00881
|
||||
G1 X140.66 Y84.204 E.02059
|
||||
G1 X140.972 Y84.775 E.02473
|
||||
G1 X141.04 Y84.996 E.00879
|
||||
G1 X141.105 Y85.513 E.0198
|
||||
G1 X140.99 Y86.175 E.02554
|
||||
G1 X140.903 Y86.39 E.00881
|
||||
G1 X140.632 Y86.828 E.01958
|
||||
G1 X140.35 Y87.076 E.01427
|
||||
M204 P2500
|
||||
G1 X140.021 Y86.764 F9000
|
||||
M204 P500
|
||||
;TYPE:External perimeter
|
||||
G1 F1800
|
||||
G1 X139.837 Y86.919 E.00914
|
||||
G1 X139.316 Y87.117 E.02118
|
||||
G1 X138.76 Y87.13 E.02114
|
||||
G1 X138.23 Y86.957 E.02119
|
||||
G1 X137.789 Y86.617 E.02116
|
||||
G1 X137.486 Y86.15 E.02116
|
||||
G1 X137.356 Y85.608 E.02118
|
||||
G1 X137.414 Y85.055 E.02113
|
||||
G1 X137.653 Y84.551 E.0212
|
||||
G1 X138.046 Y84.157 E.02115
|
||||
G1 X138.548 Y83.916 E.02116
|
||||
G1 X139.101 Y83.855 E.02114
|
||||
G1 X139.452 Y83.916 E.01354
|
||||
G1 X139.908 Y84.123 E.01903
|
||||
G1 X140.301 Y84.489 E.02041
|
||||
G1 X140.564 Y84.981 E.0212
|
||||
G1 X140.647 Y85.531 E.02114
|
||||
G1 X140.543 Y86.078 E.02116
|
||||
G1 X140.262 Y86.559 E.02117
|
||||
G1 X140.066 Y86.725 E.00976
|
||||
M204 P2500
|
||||
G1 E-1.2
|
||||
G1 Z.6 F9000
|
||||
G1 X162.311 Y71.773
|
||||
G1 Z.2
|
||||
G1 E1.2 F1800
|
||||
M204 P500
|
||||
G1 X162.659 Y71.282 E.02287
|
||||
G1 X162.97 Y71.031 E.01519
|
||||
G1 X163.488 Y70.797 E.0216
|
||||
G1 X164.024 Y70.726 E.02055
|
||||
G1 X164.494 Y70.79 E.01803
|
||||
G1 X164.817 Y70.912 E.01312
|
||||
G1 X165.285 Y71.228 E.02146
|
||||
G1 X165.677 Y71.736 E.02439
|
||||
G1 X165.877 Y72.346 E.0244
|
||||
@@ -1,53 +0,0 @@
|
||||
import sys
|
||||
from PyQt6.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QCheckBox, QSlider
|
||||
from PyQt6.QtCore import Qt
|
||||
from gcode_viewer import GCodeViewerWidget
|
||||
|
||||
class MainWindow(QMainWindow):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.setWindowTitle("G-code 预览")
|
||||
self.viewer = GCodeViewerWidget()
|
||||
|
||||
# 加载文件(请替换为你的 G-code 路径)
|
||||
self.viewer.load_gcode("/home/lhye200/.octoprint/uploads/20260508141659_085359c9908947bebcaa0fe7490641e8.gcode")
|
||||
|
||||
# 进度滑块(0~100%)
|
||||
self.slider = QSlider(Qt.Orientation.Horizontal)
|
||||
self.slider.setRange(0, 100)
|
||||
self.slider.setValue(100)
|
||||
self.slider.valueChanged.connect(
|
||||
lambda v: self.viewer.update_processes(v / 100.0))
|
||||
|
||||
# 类型开关
|
||||
self.chk_support = QCheckBox("显示支撑")
|
||||
self.chk_support.setChecked(True)
|
||||
self.chk_support.toggled.connect(
|
||||
lambda v: self.viewer.update_switch('SUPPORT', v))
|
||||
|
||||
self.chk_infill = QCheckBox("显示填充")
|
||||
self.chk_infill.setChecked(True)
|
||||
self.chk_infill.toggled.connect(
|
||||
lambda v: self.viewer.update_switch('FILL', v))
|
||||
|
||||
self.chk_perimeter = QCheckBox("显示外壳")
|
||||
self.chk_perimeter.setChecked(True)
|
||||
self.chk_perimeter.toggled.connect(
|
||||
lambda v: self.viewer.update_switch('WALL-OUTER', v))
|
||||
self.chk_perimeter.toggled.connect(
|
||||
lambda v: self.viewer.update_switch('WALL-INNER', v))
|
||||
|
||||
central = QWidget()
|
||||
layout = QVBoxLayout(central)
|
||||
layout.addWidget(self.viewer, 1)
|
||||
layout.addWidget(self.slider)
|
||||
layout.addWidget(self.chk_support)
|
||||
layout.addWidget(self.chk_infill)
|
||||
layout.addWidget(self.chk_perimeter)
|
||||
self.setCentralWidget(central)
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = QApplication(sys.argv)
|
||||
win = MainWindow()
|
||||
win.show()
|
||||
sys.exit(app.exec())
|
||||
@@ -1,14 +0,0 @@
|
||||
import OpenGL.GL as gl
|
||||
from PyQt6.QtWidgets import QApplication
|
||||
from PyQt6.QtOpenGLWidgets import QOpenGLWidget
|
||||
|
||||
class View(QOpenGLWidget):
|
||||
def initializeGL(self):
|
||||
v = gl.glGetFloatv(gl.GL_ALIASED_LINE_WIDTH_RANGE)
|
||||
print("Line width range:", v)
|
||||
QApplication.instance().quit()
|
||||
|
||||
app = QApplication([])
|
||||
v = View()
|
||||
v.show()
|
||||
app.exec()
|
||||
@@ -1,13 +0,0 @@
|
||||
import sys
|
||||
from PyQt6.QtWidgets import QApplication
|
||||
from PyQt6.QtOpenGLWidgets import QOpenGLWidget
|
||||
class W(QOpenGLWidget):
|
||||
def initializeGL(self):
|
||||
import OpenGL.GL as gl
|
||||
bounds = gl.glGetFloatv(gl.GL_ALIASED_LINE_WIDTH_RANGE)
|
||||
print("Line width range:", bounds)
|
||||
sys.exit(0)
|
||||
app = QApplication([])
|
||||
w = W()
|
||||
w.show()
|
||||
app.exec()
|
||||
@@ -1,15 +0,0 @@
|
||||
from PyQt6.QtGui import QMatrix4x4
|
||||
import numpy as np
|
||||
|
||||
mat = QMatrix4x4()
|
||||
mat.translate(0.0, 0.0, -250.0)
|
||||
mat.rotate(30.0, 1.0, 0.0, 0.0)
|
||||
mat.rotate(45.0, 0.0, 0.0, 1.0)
|
||||
|
||||
# If QMatrix4x4 passes list to uniformMatrix4fv, does it need column-major or row-major?
|
||||
# When I used numpy: ``mvp.flatten().tolist()`` -> it gave row-major. And it WORKED.
|
||||
# Let's check QMatrix4x4 data.
|
||||
data = mat.data()
|
||||
print("QMatrix4x4 data (length %d):" % len(data))
|
||||
print(data)
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
import numpy as np
|
||||
import math
|
||||
|
||||
rx = math.radians(-60.0)
|
||||
rz = math.radians(45.0)
|
||||
cosX, sinX = math.cos(rx), math.sin(rx)
|
||||
cosZ, sinZ = math.cos(rz), math.sin(rz)
|
||||
# Original code rotation matrix
|
||||
rot = np.array([
|
||||
[cosZ, -sinZ*cosX, sinZ*sinX, 0],
|
||||
[sinZ, cosZ*cosX, -cosZ*sinX, 0],
|
||||
[0, sinX, cosX, 0],
|
||||
[0, 0, 0, 1]
|
||||
])
|
||||
print("Original rot:\\n", rot)
|
||||
|
||||
# Correct orbit Rx @ Rz
|
||||
rot_correct = np.array([
|
||||
[cosZ, -sinZ, 0, 0],
|
||||
[cosX*sinZ, cosX*cosZ, -sinX, 0],
|
||||
[sinX*sinZ, sinX*cosZ, cosX, 0],
|
||||
[0, 0, 0, 1]
|
||||
])
|
||||
print("Rx @ Rz:\\n", rot_correct)
|
||||
|
||||
@@ -1 +1,5 @@
|
||||
PyQt6
|
||||
requests
|
||||
numpy
|
||||
PyOpenGL
|
||||
qrcode
|
||||
Pillow
|
||||
|
||||
8
run.sh
@@ -2,10 +2,15 @@
|
||||
|
||||
# echo "Running Printer Screen Menu..."
|
||||
# 激活虚拟环境
|
||||
source "$(dirname "$0")/.venv/bin/activate"
|
||||
source "$(dirname "$0")/venv/bin/activate"
|
||||
# echo "Virtual environment activated."
|
||||
# 告诉Qt使用 eglfs 插件(无桌面环境直接利用 KMS/DRM/EGL 渲染)
|
||||
export QT_QPA_PLATFORM=eglfs
|
||||
# export QT_QPA_EGLFS_SWAPINTERVAL=0
|
||||
# export QT_QPA_EGLFS_FORCEVSYNC=0
|
||||
# export QT_AUTO_SCREEN_SCALE_FACTOR=0
|
||||
# export QT_SCALE_FACTOR=1
|
||||
# export QT_ENABLE_HIGHDPI_SCALING=0
|
||||
# export QT_QUICK_BACKEND=software
|
||||
# 如果你的屏幕触摸不生效或者显示比例不对,可能还需要下面这些环境变量:
|
||||
# 触摸屏设备路径,可以通过 ls /dev/input/ 查看
|
||||
@@ -15,6 +20,7 @@ export QT_QPA_PLATFORM=eglfs
|
||||
|
||||
# 若启动报错,可以打开以下变量排查 eglfs 相关日志
|
||||
# export QT_DEBUG_PLUGINS=1
|
||||
# export QT_QPA_EGLFS_DEBUG=1
|
||||
# echo "QT_QPA_PLATFORM: $QT_QPA_PLATFORM"
|
||||
# echo "directory: $(dirname "$0")"
|
||||
|
||||
|
||||
22
test_svg.py
Normal file
@@ -0,0 +1,22 @@
|
||||
from PyQt6.QtWidgets import QApplication, QLabel
|
||||
from PyQt6.QtGui import QPixmap
|
||||
import sys
|
||||
import base64
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
def get_colored_svg_uri(path: str, color: str) -> str:
|
||||
with open(path, 'r', encoding='utf-8') as f:
|
||||
svg_content = f.read()
|
||||
svg_content = svg_content.replace('currentColor', color)
|
||||
b64 = base64.b64encode(svg_content.encode('utf-8')).decode('utf-8')
|
||||
return f"data:image/svg+xml;base64,{b64}"
|
||||
|
||||
path = "third_party/Bootstrap/bootstrap-icons-1.13.1/reception-4.svg"
|
||||
uri = get_colored_svg_uri(path, "#a0d8a0")
|
||||
|
||||
label = QLabel()
|
||||
label.setText(f"<img src='{uri}'> Hello")
|
||||
label.show()
|
||||
|
||||
sys.exit(app.exec())
|
||||
21
third_party/Bootstrap/LICENSE.txt
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2019-2024 The Bootstrap Authors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
4
third_party/Bootstrap/bootstrap-icons-1.13.1/0-circle-fill.svg
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-0-circle-fill" viewBox="0 0 16 16">
|
||||
<path d="M8 4.951c-1.008 0-1.629 1.09-1.629 2.895v.31c0 1.81.627 2.895 1.629 2.895s1.623-1.09 1.623-2.895v-.31c0-1.8-.621-2.895-1.623-2.895"/>
|
||||
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0m-8.012 4.158c1.858 0 2.96-1.582 2.96-3.99V7.84c0-2.426-1.079-3.996-2.936-3.996-1.864 0-2.965 1.588-2.965 3.996v.328c0 2.42 1.09 3.99 2.941 3.99"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 476 B |
4
third_party/Bootstrap/bootstrap-icons-1.13.1/0-circle.svg
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-0-circle" viewBox="0 0 16 16">
|
||||
<path d="M7.988 12.158c-1.851 0-2.941-1.57-2.941-3.99V7.84c0-2.408 1.101-3.996 2.965-3.996 1.857 0 2.935 1.57 2.935 3.996v.328c0 2.408-1.101 3.99-2.959 3.99M8 4.951c-1.008 0-1.629 1.09-1.629 2.895v.31c0 1.81.627 2.895 1.629 2.895s1.623-1.09 1.623-2.895v-.31c0-1.8-.621-2.895-1.623-2.895"/>
|
||||
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0M1 8a7 7 0 1 0 14 0A7 7 0 0 0 1 8"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 507 B |
4
third_party/Bootstrap/bootstrap-icons-1.13.1/0-square-fill.svg
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-0-square-fill" viewBox="0 0 16 16">
|
||||
<path d="M8 4.951c-1.008 0-1.629 1.09-1.629 2.895v.31c0 1.81.627 2.895 1.629 2.895s1.623-1.09 1.623-2.895v-.31c0-1.8-.621-2.895-1.623-2.895"/>
|
||||
<path d="M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2zm5.988 12.158c-1.851 0-2.941-1.57-2.941-3.99V7.84c0-2.408 1.101-3.996 2.965-3.996 1.857 0 2.935 1.57 2.935 3.996v.328c0 2.408-1.101 3.99-2.959 3.99"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 514 B |
4
third_party/Bootstrap/bootstrap-icons-1.13.1/0-square.svg
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-0-square" viewBox="0 0 16 16">
|
||||
<path d="M7.988 12.158c-1.851 0-2.941-1.57-2.941-3.99V7.84c0-2.408 1.101-3.996 2.965-3.996 1.857 0 2.935 1.57 2.935 3.996v.328c0 2.408-1.101 3.99-2.959 3.99M8 4.951c-1.008 0-1.629 1.09-1.629 2.895v.31c0 1.81.627 2.895 1.629 2.895s1.623-1.09 1.623-2.895v-.31c0-1.8-.621-2.895-1.623-2.895"/>
|
||||
<path d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2zm15 0a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 579 B |
3
third_party/Bootstrap/bootstrap-icons-1.13.1/1-circle-fill.svg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-1-circle-fill" viewBox="0 0 16 16">
|
||||
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0M9.283 4.002H7.971L6.072 5.385v1.271l1.834-1.318h.065V12h1.312z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 250 B |
3
third_party/Bootstrap/bootstrap-icons-1.13.1/1-circle.svg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-1-circle" viewBox="0 0 16 16">
|
||||
<path d="M1 8a7 7 0 1 0 14 0A7 7 0 0 0 1 8m15 0A8 8 0 1 1 0 8a8 8 0 0 1 16 0M9.283 4.002V12H7.971V5.338h-.065L6.072 6.656V5.385l1.899-1.383z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 279 B |
3
third_party/Bootstrap/bootstrap-icons-1.13.1/1-square-fill.svg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-1-square-fill" viewBox="0 0 16 16">
|
||||
<path d="M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2zm7.283 4.002V12H7.971V5.338h-.065L6.072 6.656V5.385l1.899-1.383z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 286 B |
4
third_party/Bootstrap/bootstrap-icons-1.13.1/1-square.svg
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-1-square" viewBox="0 0 16 16">
|
||||
<path d="M9.283 4.002V12H7.971V5.338h-.065L6.072 6.656V5.385l1.899-1.383z"/>
|
||||
<path d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2zm15 0a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 366 B |
3
third_party/Bootstrap/bootstrap-icons-1.13.1/123.svg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-123" viewBox="0 0 16 16">
|
||||
<path d="M2.873 11.297V4.142H1.699L0 5.379v1.137l1.64-1.18h.06v5.961zm3.213-5.09v-.063c0-.618.44-1.169 1.196-1.169.676 0 1.174.44 1.174 1.106 0 .624-.42 1.101-.807 1.526L4.99 10.553v.744h4.78v-.99H6.643v-.069L8.41 8.252c.65-.724 1.237-1.332 1.237-2.27C9.646 4.849 8.723 4 7.308 4c-1.573 0-2.36 1.064-2.36 2.15v.057zm6.559 1.883h.786c.823 0 1.374.481 1.379 1.179.01.707-.55 1.216-1.421 1.21-.77-.005-1.326-.419-1.379-.953h-1.095c.042 1.053.938 1.918 2.464 1.918 1.478 0 2.642-.839 2.62-2.144-.02-1.143-.922-1.651-1.551-1.714v-.063c.535-.09 1.347-.66 1.326-1.678-.026-1.053-.933-1.855-2.359-1.845-1.5.005-2.317.88-2.348 1.898h1.116c.032-.498.498-.944 1.206-.944.703 0 1.206.435 1.206 1.07.005.64-.504 1.106-1.2 1.106h-.75z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 854 B |
3
third_party/Bootstrap/bootstrap-icons-1.13.1/2-circle-fill.svg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-2-circle-fill" viewBox="0 0 16 16">
|
||||
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0M6.646 6.24c0-.691.493-1.306 1.336-1.306.756 0 1.313.492 1.313 1.236 0 .697-.469 1.23-.902 1.705l-2.971 3.293V12h5.344v-1.107H7.268v-.077l1.974-2.22.096-.107c.688-.763 1.287-1.428 1.287-2.43 0-1.266-1.031-2.215-2.613-2.215-1.758 0-2.637 1.19-2.637 2.402v.065h1.271v-.07Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 457 B |
3
third_party/Bootstrap/bootstrap-icons-1.13.1/2-circle.svg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-2-circle" viewBox="0 0 16 16">
|
||||
<path d="M1 8a7 7 0 1 0 14 0A7 7 0 0 0 1 8m15 0A8 8 0 1 1 0 8a8 8 0 0 1 16 0M6.646 6.24v.07H5.375v-.064c0-1.213.879-2.402 2.637-2.402 1.582 0 2.613.949 2.613 2.215 0 1.002-.6 1.667-1.287 2.43l-.096.107-1.974 2.22v.077h3.498V12H5.422v-.832l2.97-3.293c.434-.475.903-1.008.903-1.705 0-.744-.557-1.236-1.313-1.236-.843 0-1.336.615-1.336 1.306"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 477 B |
3
third_party/Bootstrap/bootstrap-icons-1.13.1/2-square-fill.svg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-2-square-fill" viewBox="0 0 16 16">
|
||||
<path d="M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2zm4.646 6.24v.07H5.375v-.064c0-1.213.879-2.402 2.637-2.402 1.582 0 2.613.949 2.613 2.215 0 1.002-.6 1.667-1.287 2.43l-.096.107-1.974 2.22v.077h3.498V12H5.422v-.832l2.97-3.293c.434-.475.903-1.008.903-1.705 0-.744-.557-1.236-1.313-1.236-.843 0-1.336.615-1.336 1.306"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 484 B |
4
third_party/Bootstrap/bootstrap-icons-1.13.1/2-square.svg
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-2-square" viewBox="0 0 16 16">
|
||||
<path d="M6.646 6.24v.07H5.375v-.064c0-1.213.879-2.402 2.637-2.402 1.582 0 2.613.949 2.613 2.215 0 1.002-.6 1.667-1.287 2.43l-.096.107-1.974 2.22v.077h3.498V12H5.422v-.832l2.97-3.293c.434-.475.903-1.008.903-1.705 0-.744-.557-1.236-1.313-1.236-.843 0-1.336.615-1.336 1.306"/>
|
||||
<path d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2zm15 0a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 564 B |
3
third_party/Bootstrap/bootstrap-icons-1.13.1/3-circle-fill.svg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-3-circle-fill" viewBox="0 0 16 16">
|
||||
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0m-8.082.414c.92 0 1.535.54 1.541 1.318.012.791-.615 1.36-1.588 1.354-.861-.006-1.482-.469-1.54-1.066H5.104c.047 1.177 1.05 2.144 2.754 2.144 1.653 0 2.954-.937 2.93-2.396-.023-1.278-1.031-1.846-1.734-1.916v-.07c.597-.1 1.505-.739 1.482-1.876-.03-1.177-1.043-2.074-2.637-2.062-1.675.006-2.59.984-2.625 2.12h1.248c.036-.556.557-1.054 1.348-1.054.785 0 1.348.486 1.348 1.195.006.715-.563 1.237-1.342 1.237h-.838v1.072h.879Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 607 B |
4
third_party/Bootstrap/bootstrap-icons-1.13.1/3-circle.svg
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-3-circle" viewBox="0 0 16 16">
|
||||
<path d="M7.918 8.414h-.879V7.342h.838c.78 0 1.348-.522 1.342-1.237 0-.709-.563-1.195-1.348-1.195-.79 0-1.312.498-1.348 1.055H5.275c.036-1.137.95-2.115 2.625-2.121 1.594-.012 2.608.885 2.637 2.062.023 1.137-.885 1.776-1.482 1.875v.07c.703.07 1.71.64 1.734 1.917.024 1.459-1.277 2.396-2.93 2.396-1.705 0-2.707-.967-2.754-2.144H6.33c.059.597.68 1.06 1.541 1.066.973.006 1.6-.563 1.588-1.354-.006-.779-.621-1.318-1.541-1.318"/>
|
||||
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0M1 8a7 7 0 1 0 14 0A7 7 0 0 0 1 8"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 642 B |
3
third_party/Bootstrap/bootstrap-icons-1.13.1/3-square-fill.svg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-3-square-fill" viewBox="0 0 16 16">
|
||||
<path d="M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2zm5.918 8.414h-.879V7.342h.838c.78 0 1.348-.522 1.342-1.237 0-.709-.563-1.195-1.348-1.195-.79 0-1.312.498-1.348 1.055H5.275c.036-1.137.95-2.115 2.625-2.121 1.594-.012 2.608.885 2.637 2.062.023 1.137-.885 1.776-1.482 1.875v.07c.703.07 1.71.64 1.734 1.917.024 1.459-1.277 2.396-2.93 2.396-1.705 0-2.707-.967-2.754-2.144H6.33c.059.597.68 1.06 1.541 1.066.973.006 1.6-.563 1.588-1.354-.006-.779-.621-1.318-1.541-1.318"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 634 B |
4
third_party/Bootstrap/bootstrap-icons-1.13.1/3-square.svg
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-3-square" viewBox="0 0 16 16">
|
||||
<path d="M7.918 8.414h-.879V7.342h.838c.78 0 1.348-.522 1.342-1.237 0-.709-.563-1.195-1.348-1.195-.79 0-1.312.498-1.348 1.055H5.275c.036-1.137.95-2.115 2.625-2.121 1.594-.012 2.608.885 2.637 2.062.023 1.137-.885 1.776-1.482 1.875v.07c.703.07 1.71.64 1.734 1.917.024 1.459-1.277 2.396-2.93 2.396-1.705 0-2.707-.967-2.754-2.144H6.33c.059.597.68 1.06 1.541 1.066.973.006 1.6-.563 1.588-1.354-.006-.779-.621-1.318-1.541-1.318"/>
|
||||
<path d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2zm15 0a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 714 B |
3
third_party/Bootstrap/bootstrap-icons-1.13.1/4-circle-fill.svg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-4-circle-fill" viewBox="0 0 16 16">
|
||||
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0M7.519 5.057c-.886 1.418-1.772 2.838-2.542 4.265v1.12H8.85V12h1.26v-1.559h1.007V9.334H10.11V4.002H8.176zM6.225 9.281v.053H8.85V5.063h-.065c-.867 1.33-1.787 2.806-2.56 4.218"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 359 B |
4
third_party/Bootstrap/bootstrap-icons-1.13.1/4-circle.svg
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-4-circle" viewBox="0 0 16 16">
|
||||
<path d="M7.519 5.057q.33-.527.657-1.055h1.933v5.332h1.008v1.107H10.11V12H8.85v-1.559H4.978V9.322c.77-1.427 1.656-2.847 2.542-4.265ZM6.225 9.281v.053H8.85V5.063h-.065c-.867 1.33-1.787 2.806-2.56 4.218"/>
|
||||
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0M1 8a7 7 0 1 0 14 0A7 7 0 0 0 1 8"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 421 B |
4
third_party/Bootstrap/bootstrap-icons-1.13.1/4-square-fill.svg
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-4-square-fill" viewBox="0 0 16 16">
|
||||
<path d="M6.225 9.281v.053H8.85V5.063h-.065c-.867 1.33-1.787 2.806-2.56 4.218"/>
|
||||
<path d="M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2zm5.519 5.057q.33-.527.657-1.055h1.933v5.332h1.008v1.107H10.11V12H8.85v-1.559H4.978V9.322c.77-1.427 1.656-2.847 2.542-4.265Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 428 B |
4
third_party/Bootstrap/bootstrap-icons-1.13.1/4-square.svg
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-4-square" viewBox="0 0 16 16">
|
||||
<path d="M7.519 5.057q.33-.527.657-1.055h1.933v5.332h1.008v1.107H10.11V12H8.85v-1.559H4.978V9.322c.77-1.427 1.656-2.847 2.542-4.265ZM6.225 9.281v.053H8.85V5.063h-.065c-.867 1.33-1.787 2.806-2.56 4.218"/>
|
||||
<path d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2zm15 0a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 493 B |
3
third_party/Bootstrap/bootstrap-icons-1.13.1/5-circle-fill.svg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-5-circle-fill" viewBox="0 0 16 16">
|
||||
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0m-8.006 4.158c1.74 0 2.924-1.119 2.924-2.806 0-1.641-1.178-2.584-2.56-2.584-.897 0-1.442.421-1.612.68h-.064l.193-2.344h3.621V4.002H5.791L5.445 8.63h1.149c.193-.358.668-.809 1.435-.809.85 0 1.582.604 1.582 1.57 0 1.085-.779 1.682-1.57 1.682-.697 0-1.389-.31-1.53-1.031H5.276c.065 1.213 1.149 2.115 2.72 2.115Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 495 B |
3
third_party/Bootstrap/bootstrap-icons-1.13.1/5-circle.svg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-5-circle" viewBox="0 0 16 16">
|
||||
<path d="M1 8a7 7 0 1 1 14 0A7 7 0 0 1 1 8m15 0A8 8 0 1 0 0 8a8 8 0 0 0 16 0m-8.006 4.158c-1.57 0-2.654-.902-2.719-2.115h1.237c.14.72.832 1.031 1.529 1.031.791 0 1.57-.597 1.57-1.681 0-.967-.732-1.57-1.582-1.57-.767 0-1.242.45-1.435.808H5.445L5.791 4h4.705v1.103H6.875l-.193 2.343h.064c.17-.258.715-.68 1.611-.68 1.383 0 2.561.944 2.561 2.585 0 1.687-1.184 2.806-2.924 2.806Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 514 B |
3
third_party/Bootstrap/bootstrap-icons-1.13.1/5-square-fill.svg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-5-square-fill" viewBox="0 0 16 16">
|
||||
<path d="M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2zm5.994 12.158c-1.57 0-2.654-.902-2.719-2.115h1.237c.14.72.832 1.031 1.529 1.031.791 0 1.57-.597 1.57-1.681 0-.967-.732-1.57-1.582-1.57-.767 0-1.242.45-1.435.808H5.445L5.791 4h4.705v1.103H6.875l-.193 2.343h.064c.17-.258.715-.68 1.611-.68 1.383 0 2.561.944 2.561 2.585 0 1.687-1.184 2.806-2.924 2.806Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 521 B |
4
third_party/Bootstrap/bootstrap-icons-1.13.1/5-square.svg
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-5-square" viewBox="0 0 16 16">
|
||||
<path d="M7.994 12.158c-1.57 0-2.654-.902-2.719-2.115h1.237c.14.72.832 1.031 1.529 1.031.791 0 1.57-.597 1.57-1.681 0-.967-.732-1.57-1.582-1.57-.767 0-1.242.45-1.435.808H5.445L5.791 4h4.705v1.103H6.875l-.193 2.343h.064c.17-.258.715-.68 1.611-.68 1.383 0 2.561.944 2.561 2.585 0 1.687-1.184 2.806-2.924 2.806Z"/>
|
||||
<path d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2zm15 0a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 601 B |
3
third_party/Bootstrap/bootstrap-icons-1.13.1/6-circle-fill.svg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-6-circle-fill" viewBox="0 0 16 16">
|
||||
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0M8.21 3.855c-1.868 0-3.116 1.395-3.116 4.407 0 1.183.228 2.039.597 2.642.569.926 1.477 1.254 2.409 1.254 1.629 0 2.847-1.013 2.847-2.783 0-1.676-1.254-2.555-2.508-2.555-1.125 0-1.752.61-1.98 1.155h-.082c-.012-1.946.727-3.036 1.805-3.036.802 0 1.213.457 1.312.815h1.29c-.06-.908-.962-1.899-2.573-1.899Zm-.099 4.008c-.92 0-1.564.65-1.564 1.576 0 1.032.703 1.635 1.558 1.635.868 0 1.553-.533 1.553-1.629 0-1.06-.744-1.582-1.547-1.582"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 617 B |
3
third_party/Bootstrap/bootstrap-icons-1.13.1/6-circle.svg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-6-circle" viewBox="0 0 16 16">
|
||||
<path d="M1 8a7 7 0 1 0 14 0A7 7 0 0 0 1 8m15 0A8 8 0 1 1 0 8a8 8 0 0 1 16 0M8.21 3.855c1.612 0 2.515.99 2.573 1.899H9.494c-.1-.358-.51-.815-1.312-.815-1.078 0-1.817 1.09-1.805 3.036h.082c.229-.545.855-1.155 1.98-1.155 1.254 0 2.508.88 2.508 2.555 0 1.77-1.218 2.783-2.847 2.783-.932 0-1.84-.328-2.409-1.254-.369-.603-.597-1.459-.597-2.642 0-3.012 1.248-4.407 3.117-4.407Zm-.099 4.008c-.92 0-1.564.65-1.564 1.576 0 1.032.703 1.635 1.558 1.635.868 0 1.553-.533 1.553-1.629 0-1.06-.744-1.582-1.547-1.582"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 640 B |
4
third_party/Bootstrap/bootstrap-icons-1.13.1/6-square-fill.svg
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-6-square-fill" viewBox="0 0 16 16">
|
||||
<path d="M8.111 7.863c-.92 0-1.564.65-1.564 1.576 0 1.032.703 1.635 1.558 1.635.868 0 1.553-.533 1.553-1.629 0-1.06-.744-1.582-1.547-1.582"/>
|
||||
<path d="M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2zm6.21 3.855c1.612 0 2.515.99 2.573 1.899H9.494c-.1-.358-.51-.815-1.312-.815-1.078 0-1.817 1.09-1.805 3.036h.082c.229-.545.855-1.155 1.98-1.155 1.254 0 2.508.88 2.508 2.555 0 1.77-1.218 2.783-2.847 2.783-.932 0-1.84-.328-2.409-1.254-.369-.603-.597-1.459-.597-2.642 0-3.012 1.248-4.407 3.117-4.407Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 662 B |
4
third_party/Bootstrap/bootstrap-icons-1.13.1/6-square.svg
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-6-square" viewBox="0 0 16 16">
|
||||
<path d="M8.21 3.855c1.612 0 2.515.99 2.573 1.899H9.494c-.1-.358-.51-.815-1.312-.815-1.078 0-1.817 1.09-1.805 3.036h.082c.229-.545.855-1.155 1.98-1.155 1.254 0 2.508.88 2.508 2.555 0 1.77-1.218 2.783-2.847 2.783-.932 0-1.84-.328-2.409-1.254-.369-.603-.597-1.459-.597-2.642 0-3.012 1.248-4.407 3.117-4.407Zm-.099 4.008c-.92 0-1.564.65-1.564 1.576 0 1.032.703 1.635 1.558 1.635.868 0 1.553-.533 1.553-1.629 0-1.06-.744-1.582-1.547-1.582"/>
|
||||
<path d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2zm15 0a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 727 B |
3
third_party/Bootstrap/bootstrap-icons-1.13.1/7-circle-fill.svg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-7-circle-fill" viewBox="0 0 16 16">
|
||||
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0M5.37 5.11h3.972v.07L6.025 12H7.42l3.258-6.85V4.002H5.369v1.107Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 251 B |
3
third_party/Bootstrap/bootstrap-icons-1.13.1/7-circle.svg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-7-circle" viewBox="0 0 16 16">
|
||||
<path d="M1 8a7 7 0 1 0 14 0A7 7 0 0 0 1 8m15 0A8 8 0 1 1 0 8a8 8 0 0 1 16 0M5.37 5.11V4.001h5.308V5.15L7.42 12H6.025l3.317-6.82v-.07H5.369Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 279 B |
3
third_party/Bootstrap/bootstrap-icons-1.13.1/7-square-fill.svg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-7-square-fill" viewBox="0 0 16 16">
|
||||
<path d="M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2zm3.37 5.11V4.001h5.308V5.15L7.42 12H6.025l3.317-6.82v-.07H5.369Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 286 B |
4
third_party/Bootstrap/bootstrap-icons-1.13.1/7-square.svg
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-7-square" viewBox="0 0 16 16">
|
||||
<path d="M5.37 5.11V4.001h5.308V5.15L7.42 12H6.025l3.317-6.82v-.07H5.369Z"/>
|
||||
<path d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2zm15 0a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 366 B |
3
third_party/Bootstrap/bootstrap-icons-1.13.1/8-circle-fill.svg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-8-circle-fill" viewBox="0 0 16 16">
|
||||
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0m-5.03 1.803c0-1.248-.943-1.84-1.646-1.992v-.065c.598-.187 1.336-.72 1.336-1.781 0-1.225-1.084-2.121-2.654-2.121s-2.66.896-2.66 2.12c0 1.044.709 1.589 1.33 1.782v.065c-.697.152-1.647.732-1.647 2.003 0 1.39 1.19 2.344 2.953 2.344 1.77 0 2.989-.96 2.989-2.355Zm-4.347-3.71c0 .739.586 1.255 1.383 1.255s1.377-.516 1.377-1.254c0-.733-.58-1.23-1.377-1.23s-1.383.497-1.383 1.23Zm-.281 3.645c0 .838.72 1.412 1.664 1.412.943 0 1.658-.574 1.658-1.412 0-.843-.715-1.424-1.658-1.424-.944 0-1.664.58-1.664 1.424"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 686 B |
3
third_party/Bootstrap/bootstrap-icons-1.13.1/8-circle.svg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-8-circle" viewBox="0 0 16 16">
|
||||
<path d="M1 8a7 7 0 1 0 14 0A7 7 0 0 0 1 8m15 0A8 8 0 1 1 0 8a8 8 0 0 1 16 0m-5.03 1.803c0 1.394-1.218 2.355-2.988 2.355-1.763 0-2.953-.955-2.953-2.344 0-1.271.95-1.851 1.647-2.003v-.065c-.621-.193-1.33-.738-1.33-1.781 0-1.225 1.09-2.121 2.66-2.121s2.654.896 2.654 2.12c0 1.061-.738 1.595-1.336 1.782v.065c.703.152 1.647.744 1.647 1.992Zm-4.347-3.71c0 .739.586 1.255 1.383 1.255s1.377-.516 1.377-1.254c0-.733-.58-1.23-1.377-1.23s-1.383.497-1.383 1.23Zm-.281 3.645c0 .838.72 1.412 1.664 1.412.943 0 1.658-.574 1.658-1.412 0-.843-.715-1.424-1.658-1.424-.944 0-1.664.58-1.664 1.424"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 717 B |
4
third_party/Bootstrap/bootstrap-icons-1.13.1/8-square-fill.svg
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-8-square-fill" viewBox="0 0 16 16">
|
||||
<path d="M6.623 6.094c0 .738.586 1.254 1.383 1.254s1.377-.516 1.377-1.254c0-.733-.58-1.23-1.377-1.23s-1.383.497-1.383 1.23m-.281 3.644c0 .838.72 1.412 1.664 1.412.943 0 1.658-.574 1.658-1.412 0-.843-.715-1.424-1.658-1.424-.944 0-1.664.58-1.664 1.424"/>
|
||||
<path d="M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2zm8.97 9.803c0 1.394-1.218 2.355-2.988 2.355-1.763 0-2.953-.955-2.953-2.344 0-1.271.95-1.851 1.647-2.003v-.065c-.621-.193-1.33-.738-1.33-1.781 0-1.225 1.09-2.121 2.66-2.121s2.654.896 2.654 2.12c0 1.061-.738 1.595-1.336 1.782v.065c.703.152 1.647.744 1.647 1.992Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 737 B |
4
third_party/Bootstrap/bootstrap-icons-1.13.1/8-square.svg
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-8-square" viewBox="0 0 16 16">
|
||||
<path d="M10.97 9.803c0 1.394-1.218 2.355-2.988 2.355-1.763 0-2.953-.955-2.953-2.344 0-1.271.95-1.851 1.647-2.003v-.065c-.621-.193-1.33-.738-1.33-1.781 0-1.225 1.09-2.121 2.66-2.121s2.654.896 2.654 2.12c0 1.061-.738 1.595-1.336 1.782v.065c.703.152 1.647.744 1.647 1.992Zm-4.347-3.71c0 .739.586 1.255 1.383 1.255s1.377-.516 1.377-1.254c0-.733-.58-1.23-1.377-1.23s-1.383.497-1.383 1.23Zm-.281 3.645c0 .838.72 1.412 1.664 1.412.943 0 1.658-.574 1.658-1.412 0-.843-.715-1.424-1.658-1.424-.944 0-1.664.58-1.664 1.424"/>
|
||||
<path d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2zm15 0a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 804 B |
3
third_party/Bootstrap/bootstrap-icons-1.13.1/9-circle-fill.svg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-9-circle-fill" viewBox="0 0 16 16">
|
||||
<path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0m-8.223 4.146c2.104 0 3.123-1.464 3.123-4.3 0-3.147-1.459-4.014-2.97-4.014-1.63 0-2.871 1.02-2.871 2.73 0 1.706 1.171 2.667 2.566 2.667 1.06 0 1.7-.557 1.934-1.184h.076c.047 1.67-.475 3.023-1.834 3.023-.71 0-1.149-.363-1.248-.72H5.258c.094.908.926 1.798 2.52 1.798Zm.118-3.972c.808 0 1.535-.528 1.535-1.594s-.668-1.676-1.56-1.676c-.838 0-1.517.616-1.517 1.659 0 1.072.708 1.61 1.54 1.61Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 574 B |
3
third_party/Bootstrap/bootstrap-icons-1.13.1/9-circle.svg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-9-circle" viewBox="0 0 16 16">
|
||||
<path d="M1 8a7 7 0 1 0 14 0A7 7 0 0 0 1 8m15 0A8 8 0 1 1 0 8a8 8 0 0 1 16 0m-8.223 4.146c-1.593 0-2.425-.89-2.52-1.798h1.296c.1.357.539.72 1.248.72 1.36 0 1.88-1.353 1.834-3.023h-.076c-.235.627-.873 1.184-1.934 1.184-1.395 0-2.566-.961-2.566-2.666 0-1.711 1.242-2.731 2.87-2.731 1.512 0 2.971.867 2.971 4.014 0 2.836-1.02 4.3-3.123 4.3m.118-3.972c.808 0 1.535-.528 1.535-1.594s-.668-1.676-1.56-1.676c-.838 0-1.517.616-1.517 1.659 0 1.072.708 1.61 1.54 1.61Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 597 B |
4
third_party/Bootstrap/bootstrap-icons-1.13.1/9-square-fill.svg
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-9-square-fill" viewBox="0 0 16 16">
|
||||
<path d="M7.895 8.174c.808 0 1.535-.528 1.535-1.594s-.668-1.676-1.56-1.676c-.838 0-1.517.616-1.517 1.659 0 1.072.708 1.61 1.54 1.61Z"/>
|
||||
<path d="M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2zm5.777 12.146c-1.593 0-2.425-.89-2.52-1.798h1.296c.1.357.539.72 1.248.72 1.36 0 1.88-1.353 1.834-3.023h-.076c-.235.627-.873 1.184-1.934 1.184-1.395 0-2.566-.961-2.566-2.666 0-1.711 1.242-2.731 2.87-2.731 1.512 0 2.971.867 2.971 4.014 0 2.836-1.02 4.3-3.123 4.3"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 620 B |
4
third_party/Bootstrap/bootstrap-icons-1.13.1/9-square.svg
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-9-square" viewBox="0 0 16 16">
|
||||
<path d="M7.777 12.146c-1.593 0-2.425-.89-2.52-1.798h1.296c.1.357.539.72 1.248.72 1.36 0 1.88-1.353 1.834-3.023h-.076c-.235.627-.873 1.184-1.934 1.184-1.395 0-2.566-.961-2.566-2.666 0-1.711 1.242-2.731 2.87-2.731 1.512 0 2.971.867 2.971 4.014 0 2.836-1.02 4.3-3.123 4.3m.118-3.972c.808 0 1.535-.528 1.535-1.594s-.668-1.676-1.56-1.676c-.838 0-1.517.616-1.517 1.659 0 1.072.708 1.61 1.54 1.61Z"/>
|
||||
<path d="M0 2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2zm15 0a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 684 B |
3
third_party/Bootstrap/bootstrap-icons-1.13.1/activity.svg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-activity" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M6 2a.5.5 0 0 1 .47.33L10 12.036l1.53-4.208A.5.5 0 0 1 12 7.5h3.5a.5.5 0 0 1 0 1h-3.15l-1.88 5.17a.5.5 0 0 1-.94 0L6 3.964 4.47 8.171A.5.5 0 0 1 4 8.5H.5a.5.5 0 0 1 0-1h3.15l1.88-5.17A.5.5 0 0 1 6 2"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 366 B |
3
third_party/Bootstrap/bootstrap-icons-1.13.1/airplane-engines-fill.svg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-airplane-engines-fill" viewBox="0 0 16 16">
|
||||
<path d="M8 0c-.787 0-1.292.592-1.572 1.151A4.35 4.35 0 0 0 6 3v3.691l-2 1V7.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.191l-1.17.585A1.5 1.5 0 0 0 0 10.618V12a.5.5 0 0 0 .582.493l1.631-.272.313.937a.5.5 0 0 0 .948 0l.405-1.214 2.21-.369.375 2.253-1.318 1.318A.5.5 0 0 0 5.5 16h5a.5.5 0 0 0 .354-.854l-1.318-1.318.375-2.253 2.21.369.405 1.214a.5.5 0 0 0 .948 0l.313-.937 1.63.272A.5.5 0 0 0 16 12v-1.382a1.5 1.5 0 0 0-.83-1.342L14 8.691V7.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v.191l-2-1V3c0-.568-.14-1.271-.428-1.849C9.292.591 8.787 0 8 0"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 687 B |
3
third_party/Bootstrap/bootstrap-icons-1.13.1/airplane-engines.svg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-airplane-engines" viewBox="0 0 16 16">
|
||||
<path d="M8 0c-.787 0-1.292.592-1.572 1.151A4.35 4.35 0 0 0 6 3v3.691l-2 1V7.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.191l-1.17.585A1.5 1.5 0 0 0 0 10.618V12a.5.5 0 0 0 .582.493l1.631-.272.313.937a.5.5 0 0 0 .948 0l.405-1.214 2.21-.369.375 2.253-1.318 1.318A.5.5 0 0 0 5.5 16h5a.5.5 0 0 0 .354-.854l-1.318-1.318.375-2.253 2.21.369.405 1.214a.5.5 0 0 0 .948 0l.313-.937 1.63.272A.5.5 0 0 0 16 12v-1.382a1.5 1.5 0 0 0-.83-1.342L14 8.691V7.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v.191l-2-1V3c0-.568-.14-1.271-.428-1.849C9.292.591 8.787 0 8 0M7 3c0-.432.11-.979.322-1.401C7.542 1.159 7.787 1 8 1s.458.158.678.599C8.889 2.02 9 2.569 9 3v4a.5.5 0 0 0 .276.447l5.448 2.724a.5.5 0 0 1 .276.447v.792l-5.418-.903a.5.5 0 0 0-.575.41l-.5 3a.5.5 0 0 0 .14.437l.646.646H6.707l.647-.646a.5.5 0 0 0 .14-.436l-.5-3a.5.5 0 0 0-.576-.411L1 11.41v-.792a.5.5 0 0 1 .276-.447l5.448-2.724A.5.5 0 0 0 7 7z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
3
third_party/Bootstrap/bootstrap-icons-1.13.1/airplane-fill.svg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-airplane-fill" viewBox="0 0 16 16">
|
||||
<path d="M6.428 1.151C6.708.591 7.213 0 8 0s1.292.592 1.572 1.151C9.861 1.73 10 2.431 10 3v3.691l5.17 2.585a1.5 1.5 0 0 1 .83 1.342V12a.5.5 0 0 1-.582.493l-5.507-.918-.375 2.253 1.318 1.318A.5.5 0 0 1 10.5 16h-5a.5.5 0 0 1-.354-.854l1.319-1.318-.376-2.253-5.507.918A.5.5 0 0 1 0 12v-1.382a1.5 1.5 0 0 1 .83-1.342L6 6.691V3c0-.568.14-1.271.428-1.849"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 492 B |
3
third_party/Bootstrap/bootstrap-icons-1.13.1/airplane.svg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-airplane" viewBox="0 0 16 16">
|
||||
<path d="M6.428 1.151C6.708.591 7.213 0 8 0s1.292.592 1.572 1.151C9.861 1.73 10 2.431 10 3v3.691l5.17 2.585a1.5 1.5 0 0 1 .83 1.342V12a.5.5 0 0 1-.582.493l-5.507-.918-.375 2.253 1.318 1.318A.5.5 0 0 1 10.5 16h-5a.5.5 0 0 1-.354-.854l1.319-1.318-.376-2.253-5.507.918A.5.5 0 0 1 0 12v-1.382a1.5 1.5 0 0 1 .83-1.342L6 6.691V3c0-.568.14-1.271.428-1.849m.894.448C7.111 2.02 7 2.569 7 3v4a.5.5 0 0 1-.276.447l-5.448 2.724a.5.5 0 0 0-.276.447v.792l5.418-.903a.5.5 0 0 1 .575.41l.5 3a.5.5 0 0 1-.14.437L6.708 15h2.586l-.647-.646a.5.5 0 0 1-.14-.436l.5-3a.5.5 0 0 1 .576-.411L15 11.41v-.792a.5.5 0 0 0-.276-.447L9.276 7.447A.5.5 0 0 1 9 7V3c0-.432-.11-.979-.322-1.401C8.458 1.159 8.213 1 8 1s-.458.158-.678.599"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 840 B |
3
third_party/Bootstrap/bootstrap-icons-1.13.1/alarm-fill.svg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-alarm-fill" viewBox="0 0 16 16">
|
||||
<path d="M6 .5a.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 0 1H9v1.07a7.001 7.001 0 0 1 3.274 12.474l.601.602a.5.5 0 0 1-.707.708l-.746-.746A6.97 6.97 0 0 1 8 16a6.97 6.97 0 0 1-3.422-.892l-.746.746a.5.5 0 0 1-.707-.708l.602-.602A7.001 7.001 0 0 1 7 2.07V1h-.5A.5.5 0 0 1 6 .5m2.5 5a.5.5 0 0 0-1 0v3.362l-1.429 2.38a.5.5 0 1 0 .858.515l1.5-2.5A.5.5 0 0 0 8.5 9zM.86 5.387A2.5 2.5 0 1 1 4.387 1.86 8.04 8.04 0 0 0 .86 5.387M11.613 1.86a2.5 2.5 0 1 1 3.527 3.527 8.04 8.04 0 0 0-3.527-3.527"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 615 B |
4
third_party/Bootstrap/bootstrap-icons-1.13.1/alarm.svg
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-alarm" viewBox="0 0 16 16">
|
||||
<path d="M8.5 5.5a.5.5 0 0 0-1 0v3.362l-1.429 2.38a.5.5 0 1 0 .858.515l1.5-2.5A.5.5 0 0 0 8.5 9z"/>
|
||||
<path d="M6.5 0a.5.5 0 0 0 0 1H7v1.07a7.001 7.001 0 0 0-3.273 12.474l-.602.602a.5.5 0 0 0 .707.708l.746-.746A6.97 6.97 0 0 0 8 16a6.97 6.97 0 0 0 3.422-.892l.746.746a.5.5 0 0 0 .707-.708l-.601-.602A7.001 7.001 0 0 0 9 2.07V1h.5a.5.5 0 0 0 0-1zm1.038 3.018a6 6 0 0 1 .924 0 6 6 0 1 1-.924 0M0 3.5c0 .753.333 1.429.86 1.887A8.04 8.04 0 0 1 4.387 1.86 2.5 2.5 0 0 0 0 3.5M13.5 1c-.753 0-1.429.333-1.887.86a8.04 8.04 0 0 1 3.527 3.527A2.5 2.5 0 0 0 13.5 1"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 689 B |
3
third_party/Bootstrap/bootstrap-icons-1.13.1/alexa.svg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-alexa" viewBox="0 0 16 16">
|
||||
<path d="M7.996 0A8 8 0 0 0 0 8a8 8 0 0 0 6.93 7.93v-1.613a1.06 1.06 0 0 0-.717-1.008A5.6 5.6 0 0 1 2.4 7.865 5.58 5.58 0 0 1 8.054 2.4a5.6 5.6 0 0 1 5.535 5.81l-.002.046-.012.192-.005.061a5 5 0 0 1-.033.284l-.01.068c-.685 4.516-6.564 7.054-6.596 7.068A7.998 7.998 0 0 0 15.992 8 8 8 0 0 0 7.996.001Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 436 B |
4
third_party/Bootstrap/bootstrap-icons-1.13.1/align-bottom.svg
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-align-bottom" viewBox="0 0 16 16">
|
||||
<rect width="4" height="12" x="6" y="1" rx="1"/>
|
||||
<path d="M1.5 14a.5.5 0 0 0 0 1zm13 1a.5.5 0 0 0 0-1zm-13 0h13v-1h-13z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 264 B |
3
third_party/Bootstrap/bootstrap-icons-1.13.1/align-center.svg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-align-center" viewBox="0 0 16 16">
|
||||
<path d="M8 1a.5.5 0 0 1 .5.5V6h-1V1.5A.5.5 0 0 1 8 1m0 14a.5.5 0 0 1-.5-.5V10h1v4.5a.5.5 0 0 1-.5.5M2 7a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 311 B |
4
third_party/Bootstrap/bootstrap-icons-1.13.1/align-end.svg
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-align-end" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M14.5 1a.5.5 0 0 0-.5.5v13a.5.5 0 0 0 1 0v-13a.5.5 0 0 0-.5-.5"/>
|
||||
<path d="M13 7a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1v2a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 315 B |
3
third_party/Bootstrap/bootstrap-icons-1.13.1/align-middle.svg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-align-middle" viewBox="0 0 16 16">
|
||||
<path d="M6 13a1 1 0 0 0 1 1h2a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H7a1 1 0 0 0-1 1zM1 8a.5.5 0 0 0 .5.5H6v-1H1.5A.5.5 0 0 0 1 8m14 0a.5.5 0 0 1-.5.5H10v-1h4.5a.5.5 0 0 1 .5.5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 311 B |
4
third_party/Bootstrap/bootstrap-icons-1.13.1/align-start.svg
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-align-start" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M1.5 1a.5.5 0 0 1 .5.5v13a.5.5 0 0 1-1 0v-13a.5.5 0 0 1 .5-.5"/>
|
||||
<path d="M3 7a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 315 B |
4
third_party/Bootstrap/bootstrap-icons-1.13.1/align-top.svg
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-align-top" viewBox="0 0 16 16">
|
||||
<rect width="4" height="12" rx="1" transform="matrix(1 0 0 -1 6 15)"/>
|
||||
<path d="M1.5 2a.5.5 0 0 1 0-1zm13-1a.5.5 0 0 1 0 1zm-13 0h13v1h-13z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 281 B |
4
third_party/Bootstrap/bootstrap-icons-1.13.1/alipay.svg
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-alipay" viewBox="0 0 16 16">
|
||||
<path d="M2.541 0H13.5a2.55 2.55 0 0 1 2.54 2.563v8.297c-.006 0-.531-.046-2.978-.813-.412-.14-.916-.327-1.479-.536q-.456-.17-.957-.353a13 13 0 0 0 1.325-3.373H8.822V4.649h3.831v-.634h-3.83V2.121H7.26c-.274 0-.274.273-.274.273v1.621H3.11v.634h3.875v1.136h-3.2v.634H9.99c-.227.789-.532 1.53-.894 2.202-2.013-.67-4.161-1.212-5.51-.878-.864.214-1.42.597-1.746.998-1.499 1.84-.424 4.633 2.741 4.633 1.872 0 3.675-1.053 5.072-2.787 2.08 1.008 6.37 2.738 6.387 2.745v.105A2.55 2.55 0 0 1 13.5 16H2.541A2.55 2.55 0 0 1 0 13.437V2.563A2.55 2.55 0 0 1 2.541 0"/>
|
||||
<path d="M2.309 9.27c-1.22 1.073-.49 3.034 1.978 3.034 1.434 0 2.868-.925 3.994-2.406-1.602-.789-2.959-1.353-4.425-1.207-.397.04-1.14.217-1.547.58Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 839 B |
3
third_party/Bootstrap/bootstrap-icons-1.13.1/alphabet-uppercase.svg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-alphabet-uppercase" viewBox="0 0 16 16">
|
||||
<path d="M1.226 10.88H0l2.056-6.26h1.42l2.047 6.26h-1.29l-.48-1.61H1.707l-.48 1.61ZM2.76 5.818h-.054l-.75 2.532H3.51zm3.217 5.062V4.62h2.56c1.09 0 1.808.582 1.808 1.54 0 .762-.444 1.22-1.05 1.372v.055c.736.074 1.365.587 1.365 1.528 0 1.119-.89 1.766-2.133 1.766zM7.18 5.55v1.675h.8c.812 0 1.171-.308 1.171-.853 0-.51-.328-.822-.898-.822zm0 2.537V9.95h.903c.951 0 1.342-.312 1.342-.909 0-.591-.382-.954-1.095-.954zm5.089-.711v.775c0 1.156.49 1.803 1.347 1.803.705 0 1.163-.454 1.212-1.096H16v.12C15.942 10.173 14.95 11 13.607 11c-1.648 0-2.573-1.073-2.573-2.849v-.78c0-1.775.934-2.871 2.573-2.871 1.347 0 2.34.849 2.393 2.087v.115h-1.172c-.05-.665-.516-1.156-1.212-1.156-.849 0-1.347.67-1.347 1.83"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 845 B |
3
third_party/Bootstrap/bootstrap-icons-1.13.1/alphabet.svg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-alphabet" viewBox="0 0 16 16">
|
||||
<path d="M2.204 11.078c.767 0 1.201-.356 1.406-.737h.059V11h1.216V7.519c0-1.314-.947-1.783-2.11-1.783C1.355 5.736.75 6.42.69 7.27h1.216c.064-.323.313-.552.84-.552s.864.249.864.771v.464H2.346C1.145 7.953.5 8.568.5 9.496c0 .977.693 1.582 1.704 1.582m.42-.947c-.44 0-.845-.235-.845-.718 0-.395.269-.684.84-.684h.991v.538c0 .503-.444.864-.986.864m5.593.937c1.216 0 1.948-.869 1.948-2.31v-.702c0-1.44-.727-2.305-1.929-2.305-.742 0-1.328.347-1.499.889h-.063V3.983h-1.29V11h1.27v-.791h.064c.21.532.776.86 1.499.86Zm-.43-1.025c-.66 0-1.113-.518-1.113-1.28V8.12c0-.825.42-1.343 1.098-1.343.684 0 1.075.518 1.075 1.416v.45c0 .888-.386 1.401-1.06 1.401Zm2.834-1.328c0 1.47.87 2.378 2.305 2.378 1.416 0 2.139-.777 2.158-1.763h-1.186c-.06.425-.313.732-.933.732-.66 0-1.05-.512-1.05-1.352v-.625c0-.81.371-1.328 1.045-1.328.635 0 .879.425.918.776h1.187c-.02-.986-.787-1.806-2.14-1.806-1.41 0-2.304.918-2.304 2.338z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
3
third_party/Bootstrap/bootstrap-icons-1.13.1/alt.svg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-alt" viewBox="0 0 16 16">
|
||||
<path d="M1 13.5a.5.5 0 0 0 .5.5h3.797a.5.5 0 0 0 .439-.26L11 3h3.5a.5.5 0 0 0 0-1h-3.797a.5.5 0 0 0-.439.26L5 13H1.5a.5.5 0 0 0-.5.5m10 0a.5.5 0 0 0 .5.5h3a.5.5 0 0 0 0-1h-3a.5.5 0 0 0-.5.5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 324 B |
4
third_party/Bootstrap/bootstrap-icons-1.13.1/amazon.svg
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-amazon" viewBox="0 0 16 16">
|
||||
<path d="M10.813 11.968c.157.083.36.074.5-.05l.005.005a90 90 0 0 1 1.623-1.405c.173-.143.143-.372.006-.563l-.125-.17c-.345-.465-.673-.906-.673-1.791v-3.3l.001-.335c.008-1.265.014-2.421-.933-3.305C10.404.274 9.06 0 8.03 0 6.017 0 3.77.75 3.296 3.24c-.047.264.143.404.316.443l2.054.22c.19-.009.33-.196.366-.387.176-.857.896-1.271 1.703-1.271.435 0 .929.16 1.188.55.264.39.26.91.257 1.376v.432q-.3.033-.621.065c-1.113.114-2.397.246-3.36.67C3.873 5.91 2.94 7.08 2.94 8.798c0 2.2 1.387 3.298 3.168 3.298 1.506 0 2.328-.354 3.489-1.54l.167.246c.274.405.456.675 1.047 1.166ZM6.03 8.431C6.03 6.627 7.647 6.3 9.177 6.3v.57c.001.776.002 1.434-.396 2.133-.336.595-.87.961-1.465.961-.812 0-1.286-.619-1.286-1.533M.435 12.174c2.629 1.603 6.698 4.084 13.183.997.28-.116.475.078.199.431C13.538 13.96 11.312 16 7.57 16 3.832 16 .968 13.446.094 12.386c-.24-.275.036-.4.199-.299z"/>
|
||||
<path d="M13.828 11.943c.567-.07 1.468-.027 1.645.204.135.176-.004.966-.233 1.533-.23.563-.572.961-.762 1.115s-.333.094-.23-.137c.105-.23.684-1.663.455-1.963-.213-.278-1.177-.177-1.625-.13l-.09.009q-.142.013-.233.024c-.193.021-.245.027-.274-.032-.074-.209.779-.556 1.347-.623"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
3
third_party/Bootstrap/bootstrap-icons-1.13.1/amd.svg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-amd" viewBox="0 0 16 16">
|
||||
<path d="m.334 0 4.358 4.359h7.15v7.15l4.358 4.358V0zM.2 9.72l4.487-4.488v6.281h6.28L6.48 16H.2z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 230 B |
3
third_party/Bootstrap/bootstrap-icons-1.13.1/android.svg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-android" viewBox="0 0 16 16">
|
||||
<path d="M2.76 3.061a.5.5 0 0 1 .679.2l1.283 2.352A8.9 8.9 0 0 1 8 5a8.9 8.9 0 0 1 3.278.613l1.283-2.352a.5.5 0 1 1 .878.478l-1.252 2.295C14.475 7.266 16 9.477 16 12H0c0-2.523 1.525-4.734 3.813-5.966L2.56 3.74a.5.5 0 0 1 .2-.678ZM5 10a1 1 0 1 0 0-2 1 1 0 0 0 0 2m6 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 432 B |
3
third_party/Bootstrap/bootstrap-icons-1.13.1/android2.svg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-android2" viewBox="0 0 16 16">
|
||||
<path d="m10.213 1.471.691-1.26q.069-.124-.048-.192-.128-.057-.195.058l-.7 1.27A4.8 4.8 0 0 0 8.005.941q-1.032 0-1.956.404l-.7-1.27Q5.281-.037 5.154.02q-.117.069-.049.193l.691 1.259a4.25 4.25 0 0 0-1.673 1.476A3.7 3.7 0 0 0 3.5 5.02h9q0-1.125-.623-2.072a4.27 4.27 0 0 0-1.664-1.476ZM6.22 3.303a.37.37 0 0 1-.267.11.35.35 0 0 1-.263-.11.37.37 0 0 1-.107-.264.37.37 0 0 1 .107-.265.35.35 0 0 1 .263-.11q.155 0 .267.11a.36.36 0 0 1 .112.265.36.36 0 0 1-.112.264m4.101 0a.35.35 0 0 1-.262.11.37.37 0 0 1-.268-.11.36.36 0 0 1-.112-.264q0-.154.112-.265a.37.37 0 0 1 .268-.11q.155 0 .262.11a.37.37 0 0 1 .107.265q0 .153-.107.264M3.5 11.77q0 .441.311.75.311.306.76.307h.758l.01 2.182q0 .414.292.703a.96.96 0 0 0 .7.288.97.97 0 0 0 .71-.288.95.95 0 0 0 .292-.703v-2.182h1.343v2.182q0 .414.292.703a.97.97 0 0 0 .71.288.97.97 0 0 0 .71-.288.95.95 0 0 0 .292-.703v-2.182h.76q.436 0 .749-.308.31-.307.311-.75V5.365h-9zm10.495-6.587a.98.98 0 0 0-.702.278.9.9 0 0 0-.293.685v4.063q0 .406.293.69a.97.97 0 0 0 .702.284q.42 0 .712-.284a.92.92 0 0 0 .293-.69V6.146a.9.9 0 0 0-.293-.685 1 1 0 0 0-.712-.278m-12.702.283a1 1 0 0 1 .712-.283q.41 0 .702.283a.9.9 0 0 1 .293.68v4.063a.93.93 0 0 1-.288.69.97.97 0 0 1-.707.284 1 1 0 0 1-.712-.284.92.92 0 0 1-.293-.69V6.146q0-.396.293-.68"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
3
third_party/Bootstrap/bootstrap-icons-1.13.1/anthropic.svg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-anthropic" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M9.218 2h2.402L16 12.987h-2.402zM4.379 2h2.512l4.38 10.987H8.82l-.895-2.308h-4.58l-.896 2.307H0L4.38 2.001zm2.755 6.64L5.635 4.777 4.137 8.64z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 311 B |
4
third_party/Bootstrap/bootstrap-icons-1.13.1/app-indicator.svg
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-app-indicator" viewBox="0 0 16 16">
|
||||
<path d="M5.5 2A3.5 3.5 0 0 0 2 5.5v5A3.5 3.5 0 0 0 5.5 14h5a3.5 3.5 0 0 0 3.5-3.5V8a.5.5 0 0 1 1 0v2.5a4.5 4.5 0 0 1-4.5 4.5h-5A4.5 4.5 0 0 1 1 10.5v-5A4.5 4.5 0 0 1 5.5 1H8a.5.5 0 0 1 0 1z"/>
|
||||
<path d="M16 3a3 3 0 1 1-6 0 3 3 0 0 1 6 0"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 382 B |
3
third_party/Bootstrap/bootstrap-icons-1.13.1/app.svg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-app" viewBox="0 0 16 16">
|
||||
<path d="M11 2a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3H5a3 3 0 0 1-3-3V5a3 3 0 0 1 3-3zM5 1a4 4 0 0 0-4 4v6a4 4 0 0 0 4 4h6a4 4 0 0 0 4-4V5a4 4 0 0 0-4-4z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 278 B |
3
third_party/Bootstrap/bootstrap-icons-1.13.1/apple-music.svg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-apple-music" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="m10.995 0 .573.001q.241 0 .483.007c.35.01.705.03 1.051.093.352.063.68.166.999.329a3.36 3.36 0 0 1 1.47 1.468c.162.32.265.648.328 1 .063.347.084.7.093 1.051q.007.241.007.483l.001.573v5.99l-.001.573q0 .241-.008.483c-.01.35-.03.704-.092 1.05a3.5 3.5 0 0 1-.33 1 3.36 3.36 0 0 1-1.468 1.468 3.5 3.5 0 0 1-1 .33 7 7 0 0 1-1.05.092q-.241.007-.483.008l-.573.001h-5.99l-.573-.001q-.241 0-.483-.008a7 7 0 0 1-1.052-.092 3.6 3.6 0 0 1-.998-.33 3.36 3.36 0 0 1-1.47-1.468 3.6 3.6 0 0 1-.328-1 7 7 0 0 1-.093-1.05Q.002 11.81 0 11.568V5.005l.001-.573q0-.241.007-.483c.01-.35.03-.704.093-1.05a3.6 3.6 0 0 1 .329-1A3.36 3.36 0 0 1 1.9.431 3.5 3.5 0 0 1 2.896.1 7 7 0 0 1 3.95.008Q4.19.002 4.432 0h.573zm-.107 2.518-4.756.959H6.13a.66.66 0 0 0-.296.133.5.5 0 0 0-.16.31c-.004.027-.01.08-.01.16v5.952c0 .14-.012.275-.106.39-.095.115-.21.15-.347.177l-.31.063c-.393.08-.65.133-.881.223a1.4 1.4 0 0 0-.519.333 1.25 1.25 0 0 0-.332.995c.031.297.166.582.395.792.156.142.35.25.578.296.236.047.49.031.858-.043.196-.04.38-.102.555-.205a1.4 1.4 0 0 0 .438-.405 1.5 1.5 0 0 0 .233-.55c.042-.202.052-.386.052-.588V6.347c0-.276.08-.35.302-.404.024-.005 3.954-.797 4.138-.833.257-.049.378.025.378.294v3.524c0 .14-.001.28-.096.396-.094.115-.211.15-.348.178l-.31.062c-.393.08-.649.133-.88.223a1.4 1.4 0 0 0-.52.334 1.26 1.26 0 0 0-.34.994c.03.297.174.582.404.792a1.2 1.2 0 0 0 .577.294c.237.048.49.03.858-.044.197-.04.381-.098.556-.202a1.4 1.4 0 0 0 .438-.405q.173-.252.233-.549a2.7 2.7 0 0 0 .044-.589V2.865c0-.273-.143-.443-.4-.42-.04.003-.383.064-.424.073"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
4
third_party/Bootstrap/bootstrap-icons-1.13.1/apple.svg
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-apple" viewBox="0 0 16 16">
|
||||
<path d="M11.182.008C11.148-.03 9.923.023 8.857 1.18c-1.066 1.156-.902 2.482-.878 2.516s1.52.087 2.475-1.258.762-2.391.728-2.43m3.314 11.733c-.048-.096-2.325-1.234-2.113-3.422s1.675-2.789 1.698-2.854-.597-.79-1.254-1.157a3.7 3.7 0 0 0-1.563-.434c-.108-.003-.483-.095-1.254.116-.508.139-1.653.589-1.968.607-.316.018-1.256-.522-2.267-.665-.647-.125-1.333.131-1.824.328-.49.196-1.422.754-2.074 2.237-.652 1.482-.311 3.83-.067 4.56s.625 1.924 1.273 2.796c.576.984 1.34 1.667 1.659 1.899s1.219.386 1.843.067c.502-.308 1.408-.485 1.766-.472.357.013 1.061.154 1.782.539.571.197 1.111.115 1.652-.105.541-.221 1.324-1.059 2.238-2.758q.52-1.185.473-1.282"/>
|
||||
<path d="M11.182.008C11.148-.03 9.923.023 8.857 1.18c-1.066 1.156-.902 2.482-.878 2.516s1.52.087 2.475-1.258.762-2.391.728-2.43m3.314 11.733c-.048-.096-2.325-1.234-2.113-3.422s1.675-2.789 1.698-2.854-.597-.79-1.254-1.157a3.7 3.7 0 0 0-1.563-.434c-.108-.003-.483-.095-1.254.116-.508.139-1.653.589-1.968.607-.316.018-1.256-.522-2.267-.665-.647-.125-1.333.131-1.824.328-.49.196-1.422.754-2.074 2.237-.652 1.482-.311 3.83-.067 4.56s.625 1.924 1.273 2.796c.576.984 1.34 1.667 1.659 1.899s1.219.386 1.843.067c.502-.308 1.408-.485 1.766-.472.357.013 1.061.154 1.782.539.571.197 1.111.115 1.652-.105.541-.221 1.324-1.059 2.238-2.758q.52-1.185.473-1.282"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
3
third_party/Bootstrap/bootstrap-icons-1.13.1/archive-fill.svg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-archive-fill" viewBox="0 0 16 16">
|
||||
<path d="M12.643 15C13.979 15 15 13.845 15 12.5V5H1v7.5C1 13.845 2.021 15 3.357 15zM5.5 7h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1 0-1M.8 1a.8.8 0 0 0-.8.8V3a.8.8 0 0 0 .8.8h14.4A.8.8 0 0 0 16 3V1.8a.8.8 0 0 0-.8-.8z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 349 B |
3
third_party/Bootstrap/bootstrap-icons-1.13.1/archive.svg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-archive" viewBox="0 0 16 16">
|
||||
<path d="M0 2a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v2a1 1 0 0 1-1 1v7.5a2.5 2.5 0 0 1-2.5 2.5h-9A2.5 2.5 0 0 1 1 12.5V5a1 1 0 0 1-1-1zm2 3v7.5A1.5 1.5 0 0 0 3.5 14h9a1.5 1.5 0 0 0 1.5-1.5V5zm13-3H1v2h14zM5 7.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 0 1h-5a.5.5 0 0 1-.5-.5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 394 B |
3
third_party/Bootstrap/bootstrap-icons-1.13.1/arrow-90deg-down.svg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-90deg-down" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M4.854 14.854a.5.5 0 0 1-.708 0l-4-4a.5.5 0 0 1 .708-.708L4 13.293V3.5A2.5 2.5 0 0 1 6.5 1h8a.5.5 0 0 1 0 1h-8A1.5 1.5 0 0 0 5 3.5v9.793l3.146-3.147a.5.5 0 0 1 .708.708z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 345 B |
3
third_party/Bootstrap/bootstrap-icons-1.13.1/arrow-90deg-left.svg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-90deg-left" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M1.146 4.854a.5.5 0 0 1 0-.708l4-4a.5.5 0 1 1 .708.708L2.707 4H12.5A2.5 2.5 0 0 1 15 6.5v8a.5.5 0 0 1-1 0v-8A1.5 1.5 0 0 0 12.5 5H2.707l3.147 3.146a.5.5 0 1 1-.708.708z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 344 B |
3
third_party/Bootstrap/bootstrap-icons-1.13.1/arrow-90deg-right.svg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-90deg-right" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M14.854 4.854a.5.5 0 0 0 0-.708l-4-4a.5.5 0 0 0-.708.708L13.293 4H3.5A2.5 2.5 0 0 0 1 6.5v8a.5.5 0 0 0 1 0v-8A1.5 1.5 0 0 1 3.5 5h9.793l-3.147 3.146a.5.5 0 0 0 .708.708z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 346 B |
3
third_party/Bootstrap/bootstrap-icons-1.13.1/arrow-90deg-up.svg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-90deg-up" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M4.854 1.146a.5.5 0 0 0-.708 0l-4 4a.5.5 0 1 0 .708.708L4 2.707V12.5A2.5 2.5 0 0 0 6.5 15h8a.5.5 0 0 0 0-1h-8A1.5 1.5 0 0 1 5 12.5V2.707l3.146 3.147a.5.5 0 1 0 .708-.708z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 344 B |
3
third_party/Bootstrap/bootstrap-icons-1.13.1/arrow-bar-down.svg
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-bar-down" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M1 3.5a.5.5 0 0 1 .5-.5h13a.5.5 0 0 1 0 1h-13a.5.5 0 0 1-.5-.5M8 6a.5.5 0 0 1 .5.5v5.793l2.146-2.147a.5.5 0 0 1 .708.708l-3 3a.5.5 0 0 1-.708 0l-3-3a.5.5 0 0 1 .708-.708L7.5 12.293V6.5A.5.5 0 0 1 8 6"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 373 B |