diff --git a/plugins.json b/plugins.json
index 55ada5e6..2f26c96d 100644
--- a/plugins.json
+++ b/plugins.json
@@ -955,5 +955,23 @@
"dependencies": ["cem_template_loader"],
"creation_date": "2024-06-05",
"has_changelog": true
+ },
+ "pbr_preview": {
+ "title": "PBR Tools",
+ "author": "Jason J. Gardner",
+ "await_loading": true,
+ "creation_date": "2024-06-10",
+ "icon": "icon.png",
+ "description": "Create and view PBR materials in Blockbench. Export textures for Java and RenderDragon shaders.",
+ "has_changelog": true,
+ "website": "https://github.com/jasonjgardner/blockbench-plugins/",
+ "variant": "both",
+ "version": "1.1.0",
+ "tags": [
+ "Minecraft: Java Edition",
+ "Minecraft: Bedrock Edition",
+ "PBR"
+ ],
+ "min_version": "4.10.4"
}
}
diff --git a/plugins/pbr_preview/LICENSE.MD b/plugins/pbr_preview/LICENSE.MD
new file mode 100644
index 00000000..f288702d
--- /dev/null
+++ b/plugins/pbr_preview/LICENSE.MD
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ 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.
+
+
+ Copyright (C)
+
+ 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 .
+
+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:
+
+ Copyright (C)
+ 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
+.
+
+ 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
+.
diff --git a/plugins/pbr_preview/about.md b/plugins/pbr_preview/about.md
new file mode 100644
index 00000000..31b01c5b
--- /dev/null
+++ b/plugins/pbr_preview/about.md
@@ -0,0 +1,206 @@
+**Create and view PBR textures in Blockbench. Exports [USDZ](https://openusd.org/release/index.html) and in formats for labPBR
+(Java) or RenderDragon (Bedrock) shaders**
+
+> **_PBR:_ Physically based rendering**\
+> Read the
+> _[Intoduction to Physically Based Rendering](https://learn.microsoft.com/en-us/minecraft/creator/documents/rtxpbrintro?view=minecraft-bedrock-stable "View Minecraft Creator documentation article.")_
+> article in the Minecraft Creator documentation if you are not already familiar
+> with creating PBR content for Bedrock Edition. Visit the
+> [shaderLABS wiki](https://shaderlabs.org/wiki/Main_Page) for more information
+> on the labPBR material standard.
+
+## Blockbench PBR Plugin Features
+
+### PBR Material Preview
+
+- Preview PBR textures in Edit, Paint, and Animate mode.
+- Infers texture channels based on naming conventions when no channel has been
+ explicitly defined. Intended for previewing imported existing Bedrock and Java
+ PBR textures.
+- Uses Blockbench's preview scenes as model environment map.
+- PBR materials are updated with every edit to provide a live painting preview
+ of the material in Blockbench.
+
+### MER Maps
+
+> _MER:_ Metallic, emissive, and roughness maps assigned to red, green, and blue
+> channels respectively. It is the format used in Bedrock texture sets.
+
+#### Decode MER
+
+- MERs can be inferred and decoded automatically when PBR mode is enabled.
+- Decoding can be _slow_ on large textures (1024x+).
+- Assign an albedo map prior to decoding a MER to extract the emissive color.
+- Channels extracted from a MER can be saved as either separate textures or into
+ separate layers in a single texture.
+
+#### Export MER
+
+- Compiles metal, emissive and roughness channels into MER texture.
+- (Emissive colors will be lost upon export. Use grayscale values in emissive
+ channels for accurate brightness levels.)
+
+### Generate Normal Map
+
+- Calculate normal map based on the assigned or inferred height map for the
+ currently selected material/texture.
+
+### Generate Ambient Occlusion Map
+
+- Generate an AO map from the material's normal map. (AO available in some
+ labPBR shaders.)
+
+### Texture Baking
+
+- Bake a normal and emissive maps onto the base color channel.
+- Output multiple light positions.
+- Choose ambient light settings before baking.
+
+### Export Texture Set
+
+- Create a `.texture_set.json` file for the project textures.
+- Dialog allows defining values which can not be inferred from project.
+- Exports MER and normal maps in the process.
+
+### labPBR Specular/Normal Maps
+
+- Export `_s` and `_n` files for PBR textures created in Blockbench.
+- Decode existing, imported labPBR textures into PBR material channels.
+
+### Material Brush
+
+- Paint across multiple PBR channels simultaneously for a consistent material
+ output.
+- Save material brush presets to a collection.
+- Automatic PBR preview updates after every brush stroke.
+
+### Export USDZ Scenes
+
+- Adds new codec for exporting in USDZ format.
+
+### `.bbmat` Codec
+
+Reuse and share PBR materials in Blockbench with this custom file format.
+
+## Usage
+
+### PBR Controls
+
+This plugin adds the _PBR Controls_ panel, which controls enabling and disabling
+PBR materials in Blockbench previews.
+
+#### Toggle PBR
+
+- Use the _PBR Preview_ toggle to toggle or refresh the PBR preview. Also found
+ in the _View_ menu.
+
+>
+> Usage Tip
+> Toggling the PBR preview off and on may solve any texture discrepancies. Allowing the scene to render again will ensure all textures are up-to-date in the preview.
+>
+
+#### Toggle Corrected Lighting
+
+- Use the _Correct Lights_ toggle to enable or disable physically-corrected
+ lighting in the preview scene. This may improve the appearance of reflective
+ and emissive materials in the preview scene, but will dim the albedo/base
+ color texture.
+
+#### Tone Mapping
+
+- The _Tone Mapping_ select will apply various tone mapping techniques to the
+ preview scene. Use the _Linear_ option to match the tone map used by most
+ Bedrock shaders.
+
+#### Exposure
+
+- The scene exposure can be adjusted once a tone map technique is selected.
+ Values range from -2.0 to 2.0.
+
+### Channel Management
+
+Control which textures or texture layers are used for PBR channels using the
+following methods:
+
+#### Create Material Texture
+
+- Use the _Create Material Texture_ action to create a blank texture with PBR
+ material layers initialized.\
+ If an existing texture is selected, it will be used as the albedo channel in
+ the PBR material. If the project has other textures, include those textures in
+ the selection to have them automatically assigned to the new material's PBR
+ channels.
+
+Follow these naming conventions to ensure the textures are assigned to the
+appropriate channels when creating a new material:
+
+#### Channel Naming Convention
+
+The plugin will assume that textures and layers which end in an underscore and a
+channel name are intended to be used as that channel. For example,
+`texture_roughness` will be used as the roughness map unless the channel has
+been manually assigned a texture.
+
+##### Supported Channels
+
+| Channel | Description | Colorspace |
+| ----------- | --------------------- | ----------------------------------------------------------------- |
+| `ao` | Ambient Occlusion | **BW** |
+| `albedo` | Albedo / Base Color | **RGB** |
+| `normal` | DirectX Normal Map | **RGB** |
+| `metalness` | Metallic map | **BW** |
+| `roughness` | Roughness map | **BW** |
+| `emissive` | Emissive map | Displayed in **RGB**; Exported as **BW** in MER |
+| `sss` | Subsurface Scattering | **BW**; Not supported by shader but exported in MER alpha channel |
+
+###### Supported Minecraft Shader Channels
+
+RenderDragon and labPBR textures are automatically decoded and displayed when
+the channels can be inferred based on the existence of MER, specular, or normal
+maps. Some features are not supported by the Blockbench preview's shader, such
+as AO and POM, but can still be decoded/encoded during texture import/export
+
+#### Explicit Channel Assignment
+
+Create a Texture Layer in Blockbench to enable channel assignment in PBR
+previews. Each channel can be selected from a menu and applied to the current
+material. The menu is visible in the _PBR Controls_ panel when in Paint mode.
+The menu options are available in the _Image_ menu, as well as in the context
+menu of a texture or layer, and in the Layers panel.
+
+#### Removing Channel Assignment
+
+Channels can be disabled by hiding or deleting the layer, or un-assigning the
+channel in the menu. Texture layers cannot be assigned to more than one channel.
+Assigning a layer to a new channel will clear the layer's current channel.
+
+#### Finding Assigned Channels
+
+Select a texture with material layers assigned to reveal the _PBR Channels_
+panel. This panel displays the channels currently assigned in the selected
+texture. Click a channel in the menu to have it selected in the Layers panel.
+
+### Material Brush Tool
+
+Use the _Material Brush Tool_ to paint across multiple PBR channels. The
+controls found in the _Material Brush Panel_.
+
+- Set the metallic, roughness and height values on a scale of 0% - 100%
+- Use the color picker in the _Material Brush Panel_ to set the emissive color.
+- The current color selected in the main color picker is used as the albedo
+ color.
+- Only visible layers with channels assigned will be updated by the brush.
+- Blockbench's paint brush size and smoothness settings control the material
+ brush's settings as well.
+
+#### Material Brush Presets
+
+Open the _Material Brush Presets_ dialog to define and save new brush settings,
+or to select and apply existing presets. A material preview is generated upon
+saving a preset. Select a preset to populate its values in the Material Brush
+input controls.
+
+### `.bbmat` Files
+- Import Blockbench PBR materials by dropping a `.bbmat` file into a Blockbench project.
+ - The import option is also available in the _File_ menu.
+- Export existing materials from texture content menu to save its assigned channels together in one file.
\ No newline at end of file
diff --git a/plugins/pbr_preview/changelog.json b/plugins/pbr_preview/changelog.json
new file mode 100644
index 00000000..d8f93e67
--- /dev/null
+++ b/plugins/pbr_preview/changelog.json
@@ -0,0 +1,51 @@
+{
+ "1.0.0": {
+ "title": "1.0.0",
+ "date": "2024-05-25",
+ "author": "Jason J. Gardner",
+ "categories": [
+ {
+ "title": "PBR plugin",
+ "list": [
+ "Initial release 🚀"
+ ]
+ }
+ ]
+ },
+ "1.1.0": {
+ "title": "1.1.0",
+ "date": "2024-06-10",
+ "author": "Jason J. Gardner",
+ "categories": [
+ {
+ "title": "Java Support",
+ "list": [
+ "Added labPBR export option",
+ "Added labPBR texture decoding"
+ ]
+ },
+ {
+ "title": "Material Brush",
+ "list": [
+ "Added material brush tool",
+ "Added material brush preset dialog"
+ ]
+ }
+ ]
+ },
+ "1.2.0": {
+ "title": "1.2.0",
+ "date": "2024-06-20",
+ "author": "Jason J. Gardner",
+ "categories": [
+ {
+ "title": "USDZ Export",
+ "list": [
+ "Added USDZ export option",
+ "Added normal map orientation options",
+ "Created `.bbmat` codec"
+ ]
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/plugins/pbr_preview/icon.png b/plugins/pbr_preview/icon.png
new file mode 100644
index 00000000..273eda51
Binary files /dev/null and b/plugins/pbr_preview/icon.png differ
diff --git a/plugins/pbr_preview/members.yml b/plugins/pbr_preview/members.yml
new file mode 100644
index 00000000..438a1942
--- /dev/null
+++ b/plugins/pbr_preview/members.yml
@@ -0,0 +1,4 @@
+maintainers:
+ - jasonjgardner
+developers:
+ - jasonjgardner
diff --git a/plugins/pbr_preview/pbr_preview.js b/plugins/pbr_preview/pbr_preview.js
new file mode 100644
index 00000000..96e1f533
--- /dev/null
+++ b/plugins/pbr_preview/pbr_preview.js
@@ -0,0 +1,291 @@
+"use strict";(()=>{var _=THREE,ie=Vue,le=window.JSZip;var F="1.2.0",K="1.0.0",N="_NONE_",h={albedo:{id:"albedo",label:"Albedo",description:"The color of the material",map:"map",icon:"tonality",default:new _.Color(16777215),regex:new RegExp("(s|_)*(basecolor|color|albedo)","i")},metalness:{id:"metalness",label:"Metalness",description:"The material's metalness map",map:"metalnessMap",icon:"brightness_6",default:new _.Color(0),regex:new RegExp("[ _]*metal(lic|ness)?","i")},emissive:{id:"emissive",label:"Emissive",description:"The material's emissive map",map:"emissiveMap",icon:"wb_twilight",default:new _.Color(0),regex:new RegExp("[ _]*(emissive|emission)","i")},roughness:{id:"roughness",label:"Roughness",description:"The material's roughness map",map:"roughnessMap",icon:"grain",default:new _.Color(16777215),regex:new RegExp("[ _]*rough(ness)?","i")},height:{id:"height",label:"Height",description:"The material's height map",map:"bumpMap",icon:"landscape",default:new _.Color(16777215),regex:new RegExp("[ _]*(height|bump)","i")},normal:{id:"normal",label:"Normal",description:"The material's normal map",map:"normalMap",icon:"looks",default:new _.Color("rgb(128, 128, 255)"),regex:new RegExp("[ _]*normal","i")},ao:{id:"ao",label:"Ambient Occlusion",description:"The material's ambient occlusion map",map:"aoMap",icon:"motion_mode",default:new _.Color(16777215),regex:new RegExp("[ _]*(ao|ambientocclusion|ambient occlusion)","i")}},o={},y=[],C=[];var ce=[...Object.keys(h).map(e=>h[e].id),N];y.push(()=>{o.channelProp=new Property(TextureLayer,"enum","channel",{default:N,values:ce,label:"PBR Channel",exposed:!1}),o.textureChannelProp=new Property(Texture,"enum","channel",{default:N,values:ce,label:"PBR Channel",exposed:!1}),o.materialTextureProp=new Property(Texture,"boolean","material",{default:!1,label:"Material Texture"}),o.pbrMaterialsProp=new Property(ModelProject,"object","pbr_materials",{default:{},exposed:!1,label:"PBR Materials"}),o.projectMaterialsProp=new Property(ModelProject,"object","bb_materials",{default:{},exposed:!1,label:"Project Materials"}),o.projectPbrModeProp=new Property(ModelProject,"boolean","pbr_active",{default:!1,exposed:!1,values:[],label:"PBR Mode"})});function E(){return Texture.selected?Texture.selected:TextureLayer.selected?TextureLayer.selected.texture:Project?Project.selected_texture?Project.selected_texture:Project.textures.find(e=>e.selected)??null:Texture.all.find(e=>e.selected)??Texture.getDefault()}function R(){return TextureLayer.selected?TextureLayer.selected:Texture.selected?.selected_layer?Texture.selected.selected_layer:Project.selected_texture!==null&&Project.selected_texture?.layers_enabled===!0?Project.selected_texture.layers.find(e=>e.selected)??Project.selected_texture.layers[0]:E()?.getActiveLayer()??null}function G(){return Project?Project.model_identifier.length>0?Project.model_identifier:Project.getDisplayName():pathToName(E()?.name??"texture")}function ue(e,a){let t;return function(...r){let n=()=>{t=void 0,e.apply(this,r)};clearTimeout(t),t=setTimeout(n,a)}}function z(e){let a=MediaPreview.renderer??new _.WebGLRenderer({alpha:!0,antialias:!0}),t=new _.Scene,r=new _.PerspectiveCamera(75,96/96,.1,1e3),n=new _.AmbientLight(16777215,.75);t.add(n);let i=new _.PointLight(16777215,1,100);i.position.set(5,5,5),t.add(i);let s=new _.SphereGeometry(1,32,32),l=e instanceof _.MeshStandardMaterial&&e.isMeshStandardMaterial?e:new _.MeshStandardMaterial({color:e.albedo,metalness:e.metalness??0,roughness:e.roughness??1,emissive:e.emissive,bumpScale:e.height??0,envMap:PreviewScene.active?.cubemap??null,envMapIntensity:.5}),u=new _.Mesh(s,l);t.add(u),r.position.x=0,r.position.y=0,r.position.z=2,a.setSize(96,96),a.render(t,r);let m=a.domElement.toDataURL();return l.dispose(),MediaPreview.renderer||(a.clear(),a.dispose()),m}function Y(e,a){let t=a??document.createElement("canvas"),r=t.getContext("2d");if(!r)return t.remove(),null;let n=Math.max(Project?Project.texture_width:16,16),i=Math.max(Project?Project.texture_height:16,16);t.width=n,t.height=i,r.fillStyle=`rgb(${e.r*255}, ${e.g*255}, ${e.b*255})`,r.fillRect(0,0,n,i);let s=t.toDataURL();return a||t.remove(),s}var Ve=(e=!0)=>{let a=Project?Project.textures??Texture.all:Texture.all;return e?a.filter(t=>t.layers_enabled&&t.layers.length>0).flatMap(t=>t.layers):a},D=class e{constructor(a,t){this._scope=a??Ve(),this._materialUuid=t}merToCanvas(){let a=this.getTexture(h.emissive),t=this.getTexture(h.roughness),r=this.getTexture(h.metalness);if(!a&&!t&&!r){let{metalness:n,emissive:i,roughness:s}=this.decodeMer();n&&(r=e.makePixelatedCanvas(n)),i&&(a=e.makePixelatedCanvas(i)),s&&(t=e.makePixelatedCanvas(s))}return{emissiveMap:a,roughnessMap:t,metalnessMap:r}}getMaterial(a={}){let{emissiveMap:t,roughnessMap:r,metalnessMap:n}=Format.id.startsWith("bedrock")?this.merToCanvas():{emissiveMap:this.getTexture(h.emissive),roughnessMap:this.getTexture(h.roughness),metalnessMap:this.getTexture(h.metalness)},i=this.getTexture(h.normal);return new _.MeshStandardMaterial({map:this.getTexture(h.albedo)??e.makePixelatedCanvas(TextureLayer.selected?.canvas??Texture.all.find(s=>s.selected)?.canvas??Texture.getDefault().canvas),aoMap:this.getTexture(h.ao),bumpMap:this.getTexture(h.height),normalMap:i,metalnessMap:n,metalness:n?1:0,roughnessMap:r,roughness:1,emissiveMap:t,emissiveIntensity:t?1:0,emissive:t?16777215:0,envMap:PreviewScene.active?.cubemap??null,envMapIntensity:.95,alphaTest:.1,transparent:!0,...a})}renderMaterialPreview(){return z(this.getMaterial())}saveTexture(a,t){Project&&(Project.pbr_materials||(Project.pbr_materials={}),Project.pbr_materials[this._materialUuid]||(Project.pbr_materials[this._materialUuid]={}),Project.pbr_materials[this._materialUuid][a.id]=t.uuid,t.extend({channel:a.id}))}findTexture(a,t=!0){if(!Project)return null;let r=this._scope.find(u=>u.channel&&(u.channel===a||u.channel===a.id));if(r)return r;let[n,i]=typeof a=="string"?[a,new RegExp(`_*${a}(.[^.]+)?$`,"i")]:[a.id,a.regex??new RegExp(`_*${a.id}(.[^.]+)?$`,"i")];Project.pbr_materials=Project.pbr_materials??{};let s=Project.pbr_materials[this._materialUuid];if(t&&!s?.length&&n!==N)return this._scope.find(u=>i.test(u.name))??null;let l=s?.[n];return l?this._scope.find(u=>u.uuid===l)??null:null}static makePixelatedCanvas(a){let t=new _.CanvasTexture(a,void 0,void 0,void 0,_.NearestFilter,_.NearestFilter);return t.needsUpdate=!0,t}getTexture(a){let t=this.findTexture(a);return t?e.makePixelatedCanvas(t.canvas):null}static extractChannel(a,t){let r=a.canvas,{width:n,height:i}=r,s=r.getContext("2d");if(!s||!n||!i)return null;let l=document.createElement("canvas");l.width=n,l.height=i;let u=l.getContext("2d");if(!u)return null;let m={r:0,g:1,b:2,a:3}[t],{data:d}=s.getImageData(0,0,n,i),c=new Uint8ClampedArray(n*i*4);for(let g=0;ga){x[f]=v.data[f],x[f+1]=v.data[f+1],x[f+2]=v.data[f+2],x[f+3]=255;continue}x[f]=0,x[f+1]=0,x[f+2]=0,x[f+3]=255}return m.putImageData(new ImageData(x,l.width,l.height),0,0),{metalness:r,emissive:l,emissiveLevel:n,roughness:i,sss:s}}createMer(a=!1){let t=this.findTexture(h.metalness,a),r=this.findTexture(h.emissive,a),n=this.findTexture(h.roughness,a),i=this.findTexture("sss",!1),s=Math.max(t?.img.width??0,r?.img.width??0,n?.img.width??0,Project?Project.texture_width:0,16),l=Math.max(t?.img.height??0,r?.img.height??0,n?.img.height??0,Project?Project.texture_height:0,16),u=document.createElement("canvas");u.width=s,u.height=l;let m=u.getContext("2d");if(!m)return null;let d=t?.img?e.extractChannel(t,"r"):null,c=r?.img?e.extractChannel(r,"g"):null,v=n?.img?e.extractChannel(n,"b"):null,g=i&&i?.img?e.extractChannel(i,"a"):null,x=d?.getContext("2d")?.getImageData(0,0,s,l)??new ImageData(s,l),f=c?.getContext("2d")?.getImageData(0,0,s,l)??new ImageData(s,l),p=v?.getContext("2d")?.getImageData(0,0,s,l)??new ImageData(s,l),b=g?.getContext("2d")?.getImageData(0,0,s,l)??new ImageData(new Uint8ClampedArray(s*l*4).fill(255),s,l),M=new Uint8ClampedArray(s*l*4);for(let w=0;w{n&&new Texture({name:`${a.name}_${r}`,saved:!1,particle:!1,keep_size:!1}).fromDataURL(n.toDataURL()).add()}),t}createTexturesFromNormal(a){let t=this.decodeLabPbrNormal(a);return Object.entries(t).forEach(([r,n])=>{n&&new Texture({name:`${a.name}_${r}`,saved:!1,particle:!1,keep_size:!1}).fromDataURL(n.toDataURL()).add()}),t}};var V=class{constructor({lightHeight:a=.66,ambientLight:t=[.1,.1,.1],minLightIntensity:r=0,lightDiffuse:n=[1,1,1]}={}){this.lightHeight=a,this.ambientLight=t,this.minLightIntensity=r,this.lightDiffuse=n}bake(a,t,r){let n=t instanceof HTMLCanvasElement?t:this.createCanvas(t.width,t.height),i=r instanceof HTMLCanvasElement?r:this.createCanvas(r.width,r.height),s=n.getContext("2d"),l=i.getContext("2d");s.drawImage(t,0,0),l.drawImage(r,0,0);let u=s.getImageData(0,0,t.width,t.height),m=l.getImageData(0,0,r.width,r.height),d=[],c=[];for(let g=0;g{let x=this.createCanvas(t.width,t.height),f=x.getContext("2d"),p=f.getImageData(0,0,x.width,x.height),b=[Math.cos(g),Math.sin(g),this.lightHeight];for(let M=0;M{if(!Project)return;let r=Project.selected_texture??Texture.getDefault(),n=new D(r.layers_enabled?r.layers:Project.textures,r.uuid),i=n.findTexture(h.albedo);if(!i){Blockbench.showStatusMessage("Can not bake without a base color assigned.",3e3);return}let s=n.findTexture(h.normal);if(!s){Blockbench.showStatusMessage("Can not bake without a normal map assigned.",3e3);return}let u=new V(e).bake(a,i.canvas,s.canvas),m=new Texture({name:`${i.name}_baked`,saved:!1,particle:!1,keep_size:!1,layers_enabled:!0}).fromDataURL(u[0].toDataURL()),d=t?c=>{let v=n.findTexture(h.emissive);if(!v)return c;let g=v.canvas;if(!g.getContext("2d"))return c;let f=Math.max(c.width,g.width,Project?Project.texture_width:16),p=Math.max(c.height,g.height,Project?Project.texture_height:16),b=document.createElement("canvas");b.width=f,b.height=p;let M=b.getContext("2d");return M?(M.drawImage(c,0,0),M.globalCompositeOperation="screen",M.drawImage(g,0,0),b):c}:c=>c;u.forEach((c,v)=>{new TextureLayer({name:`baked_${v+1}`,data_url:d(c).toDataURL()},m).addForEditing()}),m.add().select(),Blockbench.showQuickMessage("Textures baked \u{1F950}",2e3)};y.push(()=>{o.bakeTexturesDialog=new Dialog("bake_textures",{id:"bake_textures",title:"Bake Textures",buttons:["Bake","Cancel"],form:{ambientLight:{type:"color",label:"Ambient Light",value:"#1f1f1f"},lightDiffuse:{type:"color",label:"Light Diffuse",value:"#ffffff"},lightHeight:{type:"range",label:"Light Height",min:0,max:1,step:.01,value:.66},minLightIntensity:{type:"range",label:"Minimum Light Intensity",min:0,max:1,step:.01,value:0},directions:{type:"number",label:"Directions",value:8,min:1,max:360,step:1},blendEmissive:{type:"checkbox",label:"Blend Emissive",value:!1}},onConfirm(e){let a=new _.Color(e.ambientLight.toString()),t=new _.Color(e.lightDiffuse.toString());me({ambientLight:[a.r,a.g,a.b],lightDiffuse:[t.r,t.g,t.b],lightHeight:Number(e.lightHeight),minLightIntensity:Number(e.minLightIntensity)},e.directions??8,e.blendEmissive??!1)}}),o.bakeTexturesAction=new Action("bake_textures",{icon:"cake",name:"Bake Textures",description:"Bakes textures for the selected PBR material",click(){o.bakeTexturesDialog?.show()}}),MenuBar.addAction(o.bakeTexturesAction,"tools")});C.push(()=>{MenuBar.removeAction("tools.bake_textures")});function de(e,a){let t=e.getTexture();if(!t||!Project)return null;let r=Project.materials[t.uuid];r.isShaderMaterial&&!Project.bb_materials[t.uuid]?Project.bb_materials[t.uuid]=r:r.isMeshStandardMaterial&&r.dispose();let n=new D(t.layers_enabled?t.layers.filter(i=>i.visible)??null:Project.textures,t.uuid).getMaterial({side:Canvas.getRenderSide(t),...a});return Project.materials[t.uuid]=_.ShaderMaterial.prototype.copy.call(n,r),t}function Qe(e){return Object.values(e).filter(a=>a?(Canvas.updateAllFaces(a),!0):!1).length>0}function Je(e,a){let t={};return e.forAllFaces(r=>{let n=de(r,a);n&&(t[n.uuid]=n)}),t}function Xe(e,a){let t={};return Object.keys(e.faces).forEach(r=>{let n=e.faces[r],i=de(n,a);i&&(t[i.uuid]=i)}),t}var B=(e={})=>{if(!Project)return;let t=Project.elements.map(r=>r instanceof Mesh&&Je(r,e)||r instanceof Cube&&Xe(r,e)).reduce((r,n)=>({...r,...n}),{});Project.pbr_active=Texture.all.length>0&&t&&Qe(t)},A=(e=100)=>ue(B,e);var te={},ee=()=>Condition({modes:["edit","paint"],selected:{texture:!0},project:!0,method(){let e=E();return e&&!e.material?!0:e?.material===!0&&R()!==null&&Modes.paint}}),he=()=>Condition({modes:["paint","edit"],selected:{texture:!0},method(){let e=E();if(e?.material&&Modes.edit)return!1;let a=R()??e;return a?.channel&&a.channel!==N}});y.push(()=>{Object.entries(h).forEach(([e,a])=>{te[e]=new Action(`assign_channel_${e}`,{icon:a.icon??"tv_options_edit_channels",name:`Assign to ${a.label.toLocaleLowerCase()} channel`,description:`Assign the selected layer to the ${a.label} channel`,category:"textures",condition:ee,click(){let t=TextureLayer.selected??(Project?Project.selected_texture:null);if(!t)return;Undo.initEdit({layers:[t]}),t.extend({channel:a.id});let r=t instanceof TextureLayer?t.texture:t;r.updateChangesAfterEdit(),Project.pbr_materials[r.uuid]||(Project.pbr_materials[r.uuid]={}),Object.entries(Project.pbr_materials[r.uuid]).forEach(([n,i])=>{i===t.uuid&&(delete Project.pbr_materials[r.uuid][n],t.channel=N)}),r.uuid===t.uuid&&(Project.pbr_materials[r.uuid]={}),Project.pbr_materials[r.uuid][e]=t.uuid,Undo.finishEdit("Change channel assignment"),Blockbench.showQuickMessage(`Assigned "${t.name}" to ${a.label} channel`,2e3),B()}})})});C.push(()=>{Object.values(te).forEach(e=>{e.delete()})});y.push(()=>{o.unassignChannel=new Action("unassign_channel",{icon:"cancel",name:"Unassign Channel",description:"Unassign the selected layer from the channel",category:"textures",condition:he,click(){let e=TextureLayer.selected??(Project?Project.selected_texture:null);if(!e)return;Undo.initEdit({layers:[e]});let a=e instanceof TextureLayer?e.texture:e,t=e.channel;Project.pbr_materials[a.uuid]={},e.channel=N,a.updateChangesAfterEdit(),Undo.finishEdit("Unassign channel"),Blockbench.showQuickMessage(`Unassigned "${e.name}" from ${t} channel`,2e3),A()}}),o.channelMenu=new Menu("channel_menu",[...Object.keys(h).map(e=>`assign_channel_${e}`),"unassign_channel"],{onOpen(){A()}}),o.openChannelMenu=new Action("pbr_channel_menu",{name:"Assign to PBR Channel",icon:"texture",condition:()=>ee()||he(),click(e){o.channelMenu?.open(e)},children:[...Object.values(te),o.unassignChannel]}),o.showChannelMenu=new Action("show_channel_menu",{icon:"texture",name:"Assign to PBR Channel",description:"Assign the selected layer to a channel",category:"textures",condition:ee,click(e){o.channelMenu?.open(e)}}),o.openChannelMenu&&(MenuBar.addAction(o.openChannelMenu,"image.0"),Texture.prototype.menu.addAction(o.openChannelMenu,"0"),TextureLayer.prototype.menu.addAction(o.openChannelMenu,"0")),Toolbars.layers.add(o.showChannelMenu,1)});C.push(()=>{MenuBar.removeAction("image.pbr_channel_menu"),Texture.prototype.menu.removeAction("pbr_channel_menu"),TextureLayer.prototype.menu.removeAction("pbr_channel_menu"),Toolbars.layers.remove(o.showChannelMenu)});function $(e,a="DirectX",t=!1){let r=e.canvas.getContext("2d");if(!r)return null;let n=Math.max(e.img.width??e.canvas.width,Project?Project.texture_width:0,16),i=Math.max(e.img.height??e.canvas.height,Project?Project.texture_height:0,16),{data:s}=r.getImageData(0,0,n,i),l=document.createElement("canvas"),u=l.getContext("2d");if(!u)return null;let m=(p,b)=>{let M=(p+b*n)*4;return s[M]/255};l.width=n,l.height=i,u.drawImage(e.img,0,0,n,i);let d=u.getImageData(0,0,n,i),c=d.data,v=p=>{let b=Math.sqrt(p[0]*p[0]+p[1]*p[1]+p[2]*p[2]);return[p[0]/b,p[1]/b,p[2]/b]};for(let p=0;p{let f=(g+x*t)*4;return n[f]/255};i.width=t,i.height=r,s.drawImage(e.img,0,0,t,r);let u=s.getImageData(0,0,t,r),m=u.data;for(let g=0;g{let t=R()??E()??Texture.getDefault();if(!t)return;let r=$(t,a,!1);if(!r){Blockbench.showQuickMessage("Failed to generate normal map",2e3);return}r.select(e),new D(t instanceof Texture&&t.layers_enabled?t.layers:null,t.uuid).saveTexture(h.normal,r),Blockbench.showQuickMessage("Normal map generated",2e3)};y.push(()=>{o.generateDirectXNormal=new Action("generate_dx_normal",{icon:h.normal.icon??"altitude",name:"Generate DirectX Normal Map",description:"Generates a DirectX normal map from the height map",condition:()=>(R()??E())!==null,click:e=>ge(e)}),o.generateOpenGlNormal=new Action("generate_opengl_normal",{icon:h.normal.icon??"altitude",name:"Generate OpenGL Normal Map",description:"Generates an OpenGL normal map from the height map",condition:()=>(R()??E())!==null,click:e=>ge(e,"OpenGL")}),o.generateAo=new Action("generate_ao",{icon:h.ao.icon??"motion_mode",name:"Generate Ambient Occlusion Map",description:"Generates an ambient occlusion map from the height map",condition:{selected:{texture:!0},project:!0},click(){let e=R()??E()??Texture.getDefault();if(!e)return;let a=new D(e instanceof Texture&&e.layers_enabled?e.layers:null,e.uuid),t=a.findTexture(h.normal)??$(e);if(!t){Blockbench.showQuickMessage("Unable to generate ambient occlusion map without a normal map",2e3);return}let r=pe(t);if(r){a.saveTexture(h.ao,r),r.select(),Blockbench.showQuickMessage("Ambient occlusion map generated",2e3);return}Blockbench.showQuickMessage("Failed to generate ambient occlusion map",2e3)}}),o.generateNormal=new Action("generate_normal",{children:[o.generateDirectXNormal,o.generateOpenGlNormal],name:"Generate Normal Map",description:"Generates a normal map from the height map",condition:()=>(R()??E())!==null,click(){},icon:h.normal.icon??"altitude"}),MenuBar.addAction(o.generateNormal,"tools"),MenuBar.addAction(o.generateAo,"tools")});C.push(()=>{MenuBar.removeAction("tools.generate_normal"),MenuBar.removeAction("tools.generate_ao")});var Ze=(e,a="texture")=>{e.toBlob(async t=>{t&&Blockbench.export({content:await t.arrayBuffer(),type:"PNG",name:`${a}_n`,extensions:["png"],resource_id:"normal_map",savetype:"image"})})},We=(e,a="texture")=>{e.toBlob(async t=>{t&&Blockbench.export({content:await t.arrayBuffer(),type:"PNG",name:`${a}_s`,extensions:["png"],resource_id:"specular_map",savetype:"image"})})};y.push(()=>{o.generateLabPbr=new Action("generate_lab_pbr",{icon:"experiment",name:"Generate labPBR textures",description:"Generate a specular and normal map in labPBR format for Java shaders",condition:{formats:["java_block"],project:!0},async click(){let e=E();if(!e)return;let t=new D(e.layers_enabled?e.layers:[e],e.uuid).createLabPbrOutput();if(t===null)return;let r=e.name??(Project?Project.getDisplayName():"texture");await Promise.all([Ze(t.normalMap,pathToName(r)),We(t.specular,pathToName(r))]),Blockbench.showQuickMessage("Exported labPBR textures")}}),o.decodeLabPbr=new Action("decode_lab_pbr",{icon:"frame_source",name:"Decode labPBR textures",description:"Decodes the selected texture into a specular or normal map in labPBR format",condition:{formats:["java"],project:!0,selected:{texture:!0}},click(){let e=TextureLayer.selected?.texture??Texture.all.find(t=>t.selected)??Texture.getDefault(),a=new D(e.layers_enabled?e.layers:[e],e.uuid);if(pathToName(e.name).endsWith("_n")){a.createTexturesFromNormal(e);return}if(pathToName(e.name).endsWith("_s")){a.createTexturesFromSpecular(e);return}Blockbench.showQuickMessage("Failed to decode labPBR texture")}}),MenuBar.addAction(o.generateLabPbr,"file.export"),MenuBar.addAction(o.decodeLabPbr,"tools")});C.push(()=>{MenuBar.removeAction("file.export.generate_lab_pbr")});y.push(()=>{o.createMaterialTexture=new Action("create_material_texture",{icon:"deployed_code",name:"Create Material Texture",description:"Creates a new texture for a PBR material",condition:{modes:["edit","paint"],project:!0},click(){if(!Project)return;let e={...h},a=new Texture({name:"New Material",saved:!1,particle:!1});a.extend({material:!0});let t=Texture.all.filter(s=>(s.selected||s.multi_selected)&&!s.material)??Texture.all,r=E(),n=r?new D(t,r.uuid):null;try{let s=n?.findTexture(h.albedo,!0)?.canvas.toDataURL()??r?.canvas.toDataURL()??Y(new _.Color(8421504),a.canvas);if(!s)return;a.fromDataURL(s);let l=new TextureLayer({name:e.albedo.label,visible:!0,data_url:s,keep_size:!0},a);l.extend({channel:e.albedo.id}),l.addForEditing(),l.texture.updateChangesAfterEdit(),n?.saveTexture(e.albedo,l),delete e.albedo}catch(s){console.warn("Failed to create base color texture",s),Blockbench.showStatusMessage("Failed to create base color texture in new material",3e3)}let i=Object.keys(e).reverse().map(s=>{let l=h[s],u=n?.findTexture(l,!0),m=u?u.canvas.toDataURL():Y(l.default??new _.Color(0));if(!m)return;let d=new TextureLayer({name:l.label,visible:!0,data_url:m,keep_size:!0},a);return d.extend({channel:l.id}),n?.saveTexture(l,d),d}).filter(Boolean);Undo.initEdit({textures:Texture.all,layers:i}),a.add().select(),a.activateLayers(),i.map(s=>{s.addForEditing(),a.width=Math.max(a.width,s.img.width),a.height=Math.max(a.height,s.img.height)}),a.updateChangesAfterEdit(),Undo.finishEdit("Create Material Texture")}}),MenuBar.addAction(o.createMaterialTexture,"tools"),Toolbars.texturelist.add(o.createMaterialTexture,3)});C.push(()=>{MenuBar.removeAction("tools.create_material_texture"),Toolbars.texturelist.remove("create_material_texture")});var Q=(e,a)=>{let t=E()??Texture.getDefault(),r=new D(t.layers_enabled?t.layers:Project?Project.textures:null,t.uuid).createMer(!0);if(!r)throw new Error("Failed to generate MER map from selected texture.");r.toBlob(async n=>{if(!n)throw new Error("Failed to save MER map.");let[i,s]=Project?[e?`${e}_mer`:`${t.name??Project.getDisplayName()}_mer`,Project.export_path]:["mer"];Blockbench.export({content:await n.arrayBuffer(),type:"PNG",name:i,extensions:["png"],resource_id:"mer",savetype:"image",startpath:s},a)})};y.push(()=>{o.generateMer=new Action("create_mer",{icon:"lightbulb_circle",name:"Export MER",description:"Exports a texture map from the metalness, emissive, and roughness channels. (For use in Bedrock resource packs.)",condition:{formats:["bedrock","bedrock_block"],project:!0},click(){try{Q()}catch(e){console.error("Failed to export MER map:",e),Blockbench.showStatusMessage("Failed to export MER map",3e3)}}}),o.decodeMer=new Action("decode_mer",{name:"Decode MER",icon:"arrow_split",condition:{formats:["bedrock","bedrock_block"],project:!0,selected:{texture:!0}},children:[{icon:"move_item",name:"Decode MER to Textures",description:"Decodes a MER texture map into metalness, emissive, and roughness channels into separate textures",click(){let e=E()??Texture.getDefault(),a=new D([e],e.uuid),t=a.decodeMer(),r=[h.metalness,h.emissive,h.roughness];Undo.initEdit({textures:[e]}),r.forEach(n=>{let i=n.id,s=t[i];if(!s){Blockbench.showStatusMessage(`Failed to decode ${n.label} channel`,3e3);return}let l=new Texture({name:`${e?.name}_${i}`,keep_size:!1}).fromDataURL(s.toDataURL());l.add(!0),a.saveTexture(n,l)}),Undo.finishEdit("Decode MER to textures")}},{icon:"move_group",name:"Decode MER to Layers",description:"Decodes a MER texture map into metalness, emissive, and roughness channels into material layers",condition:()=>E()?.layers_enabled===!0,click(){let e=E()??Texture.getDefault(),a=new D(e.layers_enabled?e.layers:[e],e.uuid),t=a.decodeMer(),r=[h.metalness,h.emissive,h.roughness];Undo.initEdit({textures:[e]}),r.forEach(n=>{let i=n.id,s=t[i];if(!s){Blockbench.showStatusMessage(`Failed to decode ${n.label} channel`,3e3);return}let l=new TextureLayer({name:`${e?.name}_${i}`,data_url:s.toDataURL()},e);a.saveTexture(n,l),l.addForEditing()}),Undo.finishEdit("Decode MER to layers")}}],click(){}}),MenuBar.addAction(o.decodeMer,"tools"),MenuBar.addAction(o.generateMer,"file.export")});C.push(()=>{MenuBar.removeAction("file.export.create_mer"),MenuBar.removeAction("tools.decode_mer")});var qe=()=>{Project&&Project.textures.forEach(e=>{let a=new D(null,e.uuid),t=a.findTexture(h.normal,!1),r=a.findTexture(h.height,!1),n=a.findTexture(h.albedo,!1),i=a.findTexture(h.metalness,!1)?.name,s=a.findTexture(h.emissive,!1)?.name,l=a.findTexture(h.roughness,!1)?.name,u={};return n||(u.baseColor={type:"color",label:"Base Color",value:"#ff00ff"}),!i&&!s&&!l&&(u.metalness={label:"Metalness",type:"range",min:0,max:255,step:1,value:0},u.emissive={label:"Emissive",type:"range",min:0,max:255,step:1,value:0},u.roughness={label:"Roughness",type:"range",min:0,max:255,step:1,value:0}),t&&(u.depthMap={type:"checkbox",label:"Normal Map",value:"normal"}),r&&(u.depthMap={type:"checkbox",label:"Height Map",value:"heightmap"}),t&&r&&(u.depthMap={type:"radio",label:"Depth Map",options:{normal:"Normal Map",heightmap:"Height"},value:"normal"}),o.textureSetDialog=new Dialog("texture_set",{id:"texture_set",title:"Create Texture Set JSON",buttons:["Create","Cancel"],form:u,cancelIndex:1,onConfirm(m){let d=G(),c=i||s||l,v={format_version:"1.16.100","minecraft:texture_set":{color:(n?d:m.baseColor?.toHexString())??d,metalness_emissive_roughness:[m.metalness??0,m.emissive??0,m.roughness??255]}};m.depthMap==="normal"&&t||!r&&t?v["minecraft:texture_set"].normal=`${d}_normal`:(!t||m.depthMap==="heightmap")&&r&&(v["minecraft:texture_set"].heightmap=`${d}_heightmap`);let g=p=>{if(!m.depthMap)return p();let b=m.depthMap==="normal"||m.depthMap&&!r,M=b?t:r;if(!M)return p();Blockbench.export({content:M.canvas.toDataURL()??"",type:"PNG",name:`${d}_${b?"normal":"heightmap"}`,extensions:["png"],resource_id:m.depthMap,startpath:Project.export_path,savetype:"image"},w=>{v["minecraft:texture_set"][b?"normal":"heightmap"]=pathToName(w,!1),p()})},x=p=>{if(!n)return p();Blockbench.export({content:n.canvas.toDataURL(),extensions:["png"],type:"PNG",name:d,startpath:Project.export_path,savetype:"image"},b=>{v["minecraft:texture_set"].color=pathToName(b,!1),p()})},f=()=>g(()=>{x(()=>{Blockbench.export({content:JSON.stringify(v,null,2),type:"JSON",name:`${d}.texture_set`,extensions:["json"],resource_id:"texture_set",startpath:Project.export_path,savetype:"text"},()=>{Blockbench.showQuickMessage("Texture set created",2e3),o.textureSetDialog?.hide()})})});if(c){try{Q(d,p=>{v["minecraft:texture_set"].metalness_emissive_roughness=pathToName(p,!1),f()})}catch(p){console.warn("Failed to export MER map:",p),Blockbench.showStatusMessage("Failed to export MER map",3e3)}return}f()}}),o.textureSetDialog.show()})};y.push(()=>{o.createTextureSet=new Action("create_texture_set",{name:"Create Texture Set",icon:"layers",description:"Creates a texture set JSON file. Generates a MER when metalness, emissive, or roughness channels are set.",click(){qe()},condition:{formats:["bedrock","bedrock_block"],project:!0}}),MenuBar.addAction(o.createTextureSet,"file.export")});C.push(()=>{MenuBar.removeAction("file.export.create_texture_set")});y.push(()=>{o.toggleCorrectLights=new Toggle("correct_lights",{category:"preview",name:"Correct Lights",description:"Corrects the lighting in the preview",icon:"fluorescent",default:!1,onChange(e){Preview.all.forEach(a=>{a.renderer.physicallyCorrectLights=e}),Preview.selected.renderer.physicallyCorrectLights=e,Blockbench.showQuickMessage(`Physically corrected lighting is now ${e?"enabled":"disabled"}`,2e3),e&&o.togglePbr?.set(!0),B()},click(){}}),MenuBar.addAction(o.toggleCorrectLights,"preview")});C.push(()=>{MenuBar.removeAction("preview.correct_lights")});var J=()=>{!Project||!Project.bb_materials||(Project.elements.forEach(e=>{e instanceof Cube&&Object.keys(e.faces).forEach(a=>{let r=e.faces[a].getTexture();if(!r)return;let n=Project.bb_materials[r.uuid];n&&(Project.materials[r.uuid]=n)})}),Project.pbr_active=!1,Canvas.updateAll())};var fe=["undo","redo","add_texture","finish_edit","finished_edit","load_project","select_preview_scene","change_texture_path","select_project","load_undo_save","add_cube"],xe=()=>Project&&Project.pbr_active&&B(),Ke=()=>{Blockbench.on(fe.join(" "),xe)},be=()=>{fe.forEach(e=>{Blockbench.removeListener(e,xe)})};y.push(()=>{o.togglePbr=new Toggle("toggle_pbr",{name:"PBR Preview",description:"Toggle PBR Preview",icon:"panorama_photosphere",category:"view",default:!1,click(){},onChange(e){if(e){B(),Ke(),Blockbench.showQuickMessage("PBR Preview is now enabled");return}J(),be(),Blockbench.showQuickMessage("PBR Preview is now disabled")}}),MenuBar.addAction(o.togglePbr,"view")});C.push(()=>{be(),MenuBar.removeAction("view.toggle_pbr")});var ve=e=>{let a=Math.max(-2,Math.min(2,e));Preview.all.forEach(t=>{t.renderer.toneMappingExposure=a}),Preview.selected.renderer.toneMappingExposure=a};y.push(()=>{o.exposureSlider=new NumSlider("display_settings_exposure",{category:"preview",name:"Exposure",description:"Adjusts the exposure of the scene",type:"number",value:1,icon:"exposure",settings:{min:-2,max:2,step:.01,default:1},onBefore(){Number(o.tonemappingSelect?.get())===_.NoToneMapping&&o.tonemappingSelect.change(_.LinearToneMapping.toString()),o.togglePbr?.set(!0)},onChange(e){ve(Number(e))},onAfter(){A()}}),o.resetExposureButton=new Action("display_settings_reset_exposure",{category:"preview",name:"Reset Exposure",description:"Resets the exposure of the scene",icon:"exposure_plus_1",condition:()=>o.exposureSlider!==void 0&&Number(o.exposureSlider?.get())!==1,click(){ve(1),o.exposureSlider?.setValue(1,!0),A()}}),o.tonemappingSelect=new BarSelect("display_settings_tone_mapping",{category:"preview",name:"Tone Mapping",description:"Changes the tone mapping of the preview",type:"select",default_value:_.NoToneMapping,value:Preview.selected.renderer.toneMapping??_.NoToneMapping,icon:"monochrome_photos",options:{[_.NoToneMapping]:"No Tone Mapping",[_.LinearToneMapping]:"Linear",[_.ReinhardToneMapping]:"Reinhard",[_.CineonToneMapping]:"Cineon",[_.ACESFilmicToneMapping]:"ACES"},onChange({value:e}){Preview.selected.renderer.toneMapping=Number(e);let a=1;Preview.selected.renderer.toneMapping===_.NoToneMapping?o.exposureSlider?.setValue(a,!0):a=Number(o.exposureSlider?.get()??1),Preview.all.forEach(t=>{t.renderer.toneMapping=Number(e),t.renderer.toneMappingExposure=a}),Preview.selected.renderer.toneMappingExposure=a,Blockbench.showQuickMessage(`Tone mapping set to ${this.getNameFor(e)}`,2e3),o.togglePbr&&!o.togglePbr.value&&o.togglePbr.set(!0),B()}})});var ae=class{async parse(a){let t=new le,r="model.usda";t.file(r,"");let n=we(),i={},s={};a.traverseVisible(m=>{if(!m.isMesh)return;let d=m;if(!d.material.isMeshStandardMaterial){console.warn("THREE.USDZExporter: Unsupported material type (USDZ only supports MeshStandardMaterial)",m);return}let c=d.geometry,v=d.material,g="geometries/Geometry_"+c.id+".usd";if(!t.file(g)){let x=rt(c);t.file(g,et(x))}v.uuid in i||(i[v.uuid]=v),n+=tt(d,c,v)}),n+=lt(i,s),t.file(r,n),n=null;for(let m in s){let d=s[m],c=m.split("_")[1],v=d.format===_.RGBAFormat,g=Ye(d.image,c),f=await(await new Promise(p=>g.toBlob(b=>b&&p(b),v?"image/png":"image/jpeg",1))).arrayBuffer();t.file(`textures/Texture_${m}.${v?"png":"jpg"}`,f)}let l=0;t.forEach(async m=>{let d=34+m.length;l+=d;let c=l&63,v=await t.file(m).async("uint8array");if(c!==4){let g=64-c,x=new Uint8Array(g),f=new Uint8Array(v.length+g);f.set(v,0),f.set(x,v.length),t.file(m,f)}l+=v.length});let u=await t.generateAsync({type:"blob",compression:"STORE"});return new Uint8Array(await u.arrayBuffer())}};function Ye(e,a){if(typeof HTMLImageElement<"u"&&e instanceof HTMLImageElement||typeof HTMLCanvasElement<"u"&&e instanceof HTMLCanvasElement||typeof OffscreenCanvas<"u"&&e instanceof OffscreenCanvas||typeof ImageBitmap<"u"&&e instanceof ImageBitmap){let t=1024/Math.max(e.width,e.height),r=document.createElement("canvas");r.width=e.width*Math.min(1,t),r.height=e.height*Math.min(1,t);let n=r.getContext("2d");if(n.imageSmoothingEnabled=!1,n.drawImage(e,0,0,r.width,r.height),a!==void 0){let i=parseInt(a,16),s=(i>>16&255)/255,l=(i>>8&255)/255,u=(i&255)/255,m=n.getImageData(0,0,r.width,r.height),d=m.data;for(let c=0;c
+)
+{
+ matrix4d xformOp:transform = ${n}
+ uniform token[] xformOpOrder = ["xformOp:transform"]
+
+ rel material:binding =
+}
+
+`}function at(e){let a=e.elements;return`( ${X(a,0)}, ${X(a,4)}, ${X(a,8)}, ${X(a,12)} )`}function X(e,a){return`(${e[a+0]}, ${e[a+1]}, ${e[a+2]}, ${e[a+3]})`}function rt(e){return`
+def "Geometry"
+{
+ ${nt(e)}
+}
+`}function nt(e){let a="Geometry",t=e.attributes,r=t.position.count;return`
+ def Mesh "${a}"
+ {
+ int[] faceVertexCounts = [${st(e)}]
+ int[] faceVertexIndices = [${ot(e)}]
+ normal3f[] normals = [${_e(t.normal,r)}] (
+ interpolation = "vertex"
+ )
+ point3f[] points = [${_e(t.position,r)}]
+ float2[] primvars:st = [${it(t.uv,r)}] (
+ interpolation = "vertex"
+ )
+ uniform token subdivisionScheme = "none"
+ }
+`}function st(e){let a=e.index!==null?e.index.count:e.attributes.position.count;return Array(a/3).fill(3).join(", ")}function ot(e){let a=e.index,t=[];if(a!==null)for(let r=0;r
+ float2 inputs:scale = ${ye(l.repeat)}
+ float2 inputs:translation = ${ye(l.offset)}
+ float2 outputs:result
+ }
+
+ def Shader "Texture_${l.id}_${u}"
+ {
+ uniform token info:id = "UsdUVTexture"
+ asset inputs:file = @textures/Texture_${d}.${c?"png":"jpg"}@
+ float2 inputs:st.connect =
+ token inputs:wrapS = "repeat"
+ token inputs:wrapT = "repeat"
+ float outputs:r
+ float outputs:g
+ float outputs:b
+ float3 outputs:rgb
+ }`}let s=e;if(s.map!==null?(r.push(`${t}color3f inputs:diffuseColor.connect = `),n.push(i(s.map,"diffuse",s.color))):r.push(`${t}color3f inputs:diffuseColor = ${Me(s.color)}`),s.emissiveMap!==null?(r.push(`${t}color3f inputs:emissiveColor.connect = `),n.push(i(s.emissiveMap,"emissive"))):s.emissive.getHex()>0&&r.push(`${t}color3f inputs:emissiveColor = ${Me(s.emissive)}`),s.normalMap!==null&&(r.push(`${t}normal3f inputs:normal.connect = `),n.push(i(s.normalMap,"normal"))),s.aoMap!==null&&(r.push(`${t}float inputs:occlusion.connect = `),n.push(i(s.aoMap,"occlusion"))),s.roughnessMap!==null&&s.roughness===1?(r.push(`${t}float inputs:roughness.connect = `),n.push(i(s.roughnessMap,"roughness"))):r.push(`${t}float inputs:roughness = ${s.roughness}`),s.metalnessMap!==null&&s.metalness===1?(r.push(`${t}float inputs:metallic.connect = `),n.push(i(s.metalnessMap,"metallic"))):r.push(`${t}float inputs:metallic = ${s.metalness}`),s.alphaMap!==null?(r.push(`${t}float inputs:opacity.connect = `),r.push(`${t}float inputs:opacityThreshold = 0.0001`),n.push(i(s.alphaMap,"opacity"))):r.push(`${t}float inputs:opacity = ${s.opacity}`),s.isMeshPhysicalMaterial){let l=s;r.push(`${t}float inputs:clearcoat = ${l.clearcoat}`),r.push(`${t}float inputs:clearcoatRoughness = ${l.clearcoatRoughness}`),r.push(`${t}float inputs:ior = ${l.ior}`)}return`
+ def Material "Material_${s.id}"
+ {
+ def Shader "PreviewSurface"
+ {
+ uniform token info:id = "UsdPreviewSurface"
+${r.join(`
+`)}
+ int inputs:useSpecularWorkflow = 0
+ token outputs:surface
+ }
+
+ token outputs:surface.connect =
+ token inputs:frame:stPrimvarName = "st"
+
+ def Shader "uvReader_st"
+ {
+ uniform token info:id = "UsdPrimvarReader_float2"
+ token inputs:varname.connect =
+ float2 inputs:fallback = (0.0, 0.0)
+ float2 outputs:result
+ }
+
+${n.join(`
+`)}
+
+ }
+`}function Me(e){return`(${e.r}, ${e.g}, ${e.b})`}function ye(e){return`(${e.x}, ${e.y})`}var Te=ae;y.push(()=>{let e=new Codec("usdz",{extension:"usdz",name:"USDZ",remember:!0,export_options:{normal_type:{type:"select",label:"Normal Map Type",default:"opengl",options:{opengl:"OpenGL",directx:"DirectX"}}},fileName(){return G()+".usdz"},async compile(a={}){if(!Project)throw new Error("No project loaded");let t=Object.assign(this.getExportOptions(),a);Project.textures.forEach(s=>{if(!s.material)return;let l=new D(s.layers,s.uuid),u=l.findTexture("normal",!0);if(!u)return;let m=$(u,t.normal_type);m&&l.saveTexture(h.normal,m)}),B();let r=new Te,n=new _.Scene;n.name="blockbench_export",n.add(Project.model_3d);let i=await r.parse(n);return this.dispatchEvent("compile",{model:i,options:t}),Canvas.scene.add(Project.model_3d),i},async export(a={}){let t=await this.compile(a);Blockbench.export({content:t,name:this.fileName(),startpath:this.startPath(),resource_id:"usdz",type:this.name,extensions:["usdz"],savetype:"buffer"},r=>this.afterDownload(r))}});o.exportUsdz=new Action("export_usdz",{category:"file",name:"Export USDZ",description:"Exports the current model as a USDZ file",icon:"stacks",async click(){if(!e)return;let a=await e.promptExportOptions();await e.export(a)}}),o.usdz=e,MenuBar.addAction(o.exportUsdz,"file.export")});C.push(()=>{MenuBar.removeAction("file.export_usdz")});y.push(()=>{o.bbmat=new Codec("material",{name:"Blockbench Material",extension:"bbmat",remember:!1,load_filter:{extensions:["bbmat"],type:"json"},compile(){if(!Texture.selected?.material||!Texture.selected?.layers_enabled)return;let e=Texture.selected.layers.map(a=>{let t=a.channel,r=a.canvas.toDataURL();return[t,r]});return JSON.stringify({version:K,channels:Object.fromEntries(e)})},parse(e,a){return e=typeof e=="string"?JSON.parse(e):e,e.version!==K?(Blockbench.showMessageBox({title:"Invalid Blockbench Material Version",message:`The material file "${a}" is not compatible with version ${F} of the PBR plugin.`,buttons:["OK"],confirm:0,width:400,cancel:0,checkboxes:{}},()=>null),{}):e.channels},load(e,a,t){if(!Project)return;let r=this.parse(e,a.path),n=Object.keys(r);if(!n.length)throw new Error("No valid channels found in the material");let i=r[h.albedo.id]??r[n[0]];n.includes("preview")&&(i=r.preview);let s=new Texture({name:pathToName(a.name),saved:!0,particle:!1,source:i,layers_enabled:!0});s.extend({material:!0});let l=n.map(u=>{if(!(u in h))return null;let m=r[u],d=new TextureLayer({name:u,data_url:m,visible:!0},s);return d.extend({channel:u}),d}).filter(Boolean);if(!l.length)throw new Error("No valid channel layers found in the material");i||s.fromDataURL(l[0].canvas.toDataURL()),s.add().select(),l.forEach(u=>{u.addForEditing(),s.width=Math.max(s.width,u.img.width),s.height=Math.max(s.height,u.img.height)}),s.updateChangesAfterEdit()},export(){Blockbench.export({resource_id:"material",type:this.name,extensions:[this.extension],name:`${Texture.selected?.name??this.fileName??"material"}.bbmat`,startpath:this.startPath(),content:this.compile()},e=>this.afterDownload(e))}}),o.bbMatExport=new Action("export_bbmat",{icon:"stacks",name:"Save as .bbmat",category:"file",condition:{project:!0,selected:{texture:!0},method(){return Texture.selected?.material}},click(){o.bbmat?.export?.()}}),o.bbMatImport=new Action("import_bbmat",{icon:"stacks",name:"Import .bbmat",category:"file",condition:{project:!0},click(){Blockbench.import({extensions:["bbmat"],type:"json",title:"Import .bbmat",multiple:!0},e=>{o.bbmat?.load&&e.forEach(a=>{o.bbmat.load(a.content,a,!0)})})}}),o.bbmat.export_action=o.bbMatExport,Texture.prototype.menu.addAction(o.bbMatExport),MenuBar.addAction(o.bbMatImport,"file.import")});C.push(()=>{Texture.prototype.menu.removeAction("export_bbmat"),MenuBar.removeAction("file.import.import_bbmat")});var Z=class e{constructor({colors:a}){this._colors={...Object.fromEntries(Object.keys(h).map(t=>[t,h[t].default??new _.Color(4294967040)])),...a}}get colors(){return this._colors}set colors(a){this._colors={...this._colors,...a}}toString(){let a=Object.entries(this._colors).map(([t,r])=>[t,r.getHexString()]);return JSON.stringify(a)}getChannel(a){return this._colors[a]}static makeLinearColor(a){let t=Math.min(1,Math.max(0,a));return new _.Color(t,t,t).convertSRGBToLinear()}static fromSettings(){let a="#000000",t=Number(o.brushMetalnessSlider?.get()),r=Number(o.brushRoughnessSlider?.get()??1),n=(o.brushEmissiveColor?.get()??a).toString(),i=Number(o.brushHeightSlider?.get()),s=ColorPanel.get(),l={[h.albedo.id]:new _.Color(s),[h.metalness.id]:e.makeLinearColor(t),[h.roughness.id]:e.makeLinearColor(r),[h.emissive.id]:new _.Color(n??a),[h.height.id]:e.makeLinearColor(i)};return new e({colors:l})}};var Ce="materialBrushPresets",re=()=>JSON.parse(localStorage.getItem(Ce)||"{}"),ut=(e,a)=>{let t=re(),r=a??guid(),n=o.userMaterialBrushPresets?.getFormResult()??{},i={};return n.albedo&&(i.albedo=n.albedo.toString()),n.metalness&&(i.metalness=Number(n.metalness)),n.roughness&&(i.roughness=Number(n.roughness)),n.emissive&&(i.emissive=n.emissive.toString()),n.height&&(i.height=Number(n.height)),t[r]=[i,e??"New Preset",z(i)],localStorage.setItem(Ce,JSON.stringify(t)),r},Ee=({metalness:e,roughness:a,emissive:t,height:r,albedo:n})=>{e!==void 0&&o.brushMetalnessSlider?.setValue(e||0,!0),a!==void 0&&o.brushRoughnessSlider?.setValue(a??1,!0),t!==void 0&&o.brushEmissiveColor?.set(t??"#000000"),r!==void 0&&o.brushHeightSlider?.setValue(Math.max(0,Math.min(1,r??.5)),!0),n!==void 0&&ColorPanel.set(n)},W=({id:e})=>Condition({project:!0,tools:["material_brush"],method(){let a=E();return(a?.layers_enabled&&a.layers.find(({channel:t})=>t&&t===e)!==void 0)===!0}}),mt=ie.extend({name:"UserPresetsDialog",data(){return{userPresets:{},channels:h}},methods:{applyPreset(e){try{let[a,t]=this.userPresets[e],{metalness:r,roughness:n,emissive:i,height:s,albedo:l}=a;Ee({metalness:Number(r),roughness:Number(n),emissive:i.toString(),height:Number(s),albedo:l.toString()}),o.userMaterialBrushPresets?.hide(),Blockbench.showQuickMessage(`Preset "${t}" applied`,2e3)}catch{Blockbench.showQuickMessage("Failed to apply preset",2e3)}},deletePreset(e){Blockbench.showMessageBox({title:"Delete Preset",message:"Are you sure you want to delete this preset?",confirm:1,cancel:0,buttons:["Cancel","Delete"],checkboxes:{},width:400},a=>{if(a){let t=re(),r=this.userPresets[e][1]??e;delete t[e],localStorage.setItem("materialBrushPresets",JSON.stringify(t)),this.userPresets=t,Blockbench.showQuickMessage(`Preset "${r}" deleted`,2e3)}})},editPreset(e){o.userMaterialBrushPresets?.setFormValues({name:this.userPresets[e][1]??e,...this.userPresets[e][0]})},getSummary(e){return Object.entries(e).filter(([a])=>a in this.channels).map(([a,t])=>a==="albedo"||a==="emissive"?`${this.channels[a]?.label??a}: ${t}`:`${this.channels[a]?.label??a}: ${Number(t).toFixed(1)}`).join(`
+`)}},computed:{presets(){return Object.entries(this.userPresets)}},mounted(){this.userPresets=re()},template:`
+