2026-03-11_4

This commit is contained in:
2026-03-17 15:34:28 -06:00
parent 9706bc055f
commit eef5547a2c
474 changed files with 113268 additions and 27500 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+674
View File
@@ -0,0 +1,674 @@
After-Creation-Menu-Template 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>.
@@ -0,0 +1,62 @@
'''
Copyright (C) 2020 Manuel Rais
manu@g-lul.com
Created by Manuel Rais and Christophe Seux
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 <http://www.gnu.org/licenses/>.
'''
if "bpy" in locals():
import importlib
importlib.reload(operators)
importlib.reload(props)
importlib.reload(panels)
importlib.reload(prefs)
importlib.reload(menus)
else:
import bpy
from . import operators
from . import props
from . import panels
from . import prefs
from . import menus
def get_user_preferences(context):
if hasattr(context, "user_preferences"):
return context.user_preferences
return context.preferences
def register():
operators.register()
props.register()
menus.register()
prefs.register()
# Apply preferences of the panel location.
context = bpy.context
pref = get_user_preferences(context).addons[__package__].preferences
prefs.BoneWidget_preferences.panel_category_update_fn(pref, context)
panels.register()
def unregister():
operators.unregister()
props.unregister()
menus.unregister()
prefs.unregister()
panels.unregister()
@@ -0,0 +1,34 @@
schema_version = "1.0.0"
id = "bone_widget"
version = "2.3.3"
name = "Bone Widget"
tagline = "Easily Create Bone Widgets"
maintainer = "Wayne Dixon <waylowinternet@gmail.com>"
type = "add-on"
website = "https://github.com/waylow/boneWidget"
tags = ["Rigging"]
blender_version_min = "4.2.0"
license = [
"SPDX:GPL-3.0-or-later",
]
[permissions]
files = "Import/export json and image files from/to disk"
[build]
paths_exclude_pattern = [
"/.git/",
"__pycache__/",
"images/",
".*",
"*.blend",
"*.blend[0123456789]",
"*.md",
"*.zip",
]
@@ -0,0 +1,168 @@
from .props import ImportColorSet
class BoneWidgetImportData:
"""
Tracks import status for BoneWidgets, including successes, failures, and duplicates.
"""
def __init__(self):
self.new_imported_items: int = 0 # Count of new successfully imported items
self.total_num_imports: int = 0 # Total number of attempted imports
self.failed_imports: list[Widget | ColorSet] = []
self.skipped_imports: list[Widget | ColorSet] = []
self.imported_items: list[Widget | ColorSet] = []
self.duplicate_imports: list[Widget | ColorSet] = []
self.import_type: str | None = None # Type of import operation (None if undefined)
self.json_import_error: bool = False # Flag for JSON parsing errors
def imported(self) -> int:
"""Returns the number of newly imported items or total imported items."""
return self.new_imported_items or len(self.imported_items)
def skipped(self) -> int:
"""Returns the number of skipped imports."""
return len(self.skipped_imports)
def failed(self) -> int:
"""Returns the number of failed imports."""
return len(self.failed_imports)
def total(self) -> int:
"""Returns the total number of attempted imports."""
return self.total_num_imports
def reset_imports(self) -> None:
"""Resets import tracking data, clearing skipped, imported, and duplicate items."""
self.skipped_imports = []
self.imported_items = {}
self.duplicate_imports = {}
class Widget:
def __init__(self, name: str, widget_dict: dict):
self._name: str = name if name else "Unnamed Widget"
self._vertices: list[list[float]] = widget_dict.get("vertices", [[]]) # Ensure list structure
self._edges: list[list[int]] = widget_dict.get("edges", [[]])
self._faces: list[list[int]] = widget_dict.get("faces", [[]])
self._image: str = widget_dict.get("image", "") or "user_defined.png"
@property
def name(self) -> str:
"""Returns the widget name."""
return self._name
@name.setter
def name(self, new_name: str) -> None:
self._name = new_name
@property
def vertices(self) -> list[list[float]]:
"""Returns the list of vertices."""
return self._vertices
@property
def edges(self) -> list[list[int]]:
"""Returns the list of edges."""
return self._edges
@property
def faces(self) -> list[list[int]]:
"""Returns the list of faces."""
return self._faces
@property
def image(self) -> str:
"""Returns the image filename."""
return self._image
def __repr__(self):
return f"Widget({self.name})"
def __eq__(self, other: object) -> bool:
if not isinstance(other, Widget):
return False
return (
self.vertices == other.vertices and
self.edges == other.edges and
self.faces == other.faces and
self.image == other.image
)
def to_dict(self) -> dict[str, dict[str, list | str]]:
"""
Returns a dictionary with the widget's name as the key,
and its attributes as the value. Matches the structure of the
original widgets collection.
"""
return {
self.name: {
"vertices": self.vertices,
"edges": self.edges,
"faces": self.faces,
"image": self.image
}
}
class ColorSet:
def __init__(self, color_dict: dict[str, list[float]]):
self._name: str = color_dict.get("name", "Unnamed ColorSet")
self._normal: list[float] = color_dict.get("normal", [])
self._select: list[float] = color_dict.get("select", [])
self._active: list[float] = color_dict.get("active", [])
@property
def name(self) -> str:
"""Returns the color set name."""
return self._name
@name.setter
def name(self, new_name: str) -> None:
"""Sets a new color set name."""
self._name = new_name
@property
def normal(self) -> list[float]:
"""Returns the normal color values."""
return self._normal
@property
def select(self) -> list[float]:
"""Returns the select color values."""
return self._select
@property
def active(self) -> list[float]:
"""Returns the active color values."""
return self._active
def __repr__(self) -> str:
return f"ColorSet({self.name})"
def __eq__(self, other: object) -> bool:
if not isinstance(other, ColorSet):
return False
return (
self.normal == other.normal and
self.select == other.select and
self.active == other.active
)
@classmethod
def from_pg(cls, pg: ImportColorSet) -> "ColorSet":
"""
Creates a ColorSet instance from a Blender PropertyGroup -> ImportColorSet.
Args:
pg (ImportColorSet): The PropertyGroup holding color data.
Returns:
ColorSet: A new instance based on the property group.
"""
return cls({
"name": pg.name,
"normal": list(pg.normal),
"select": list(pg.select),
"active": list(pg.active)
})
@@ -0,0 +1 @@
@@ -0,0 +1,647 @@
import bpy
import os
import json
import numpy
import re
from .main_functions import get_preferences
from ..classes import BoneWidgetImportData, Widget, ColorSet
from .. import __package__
JSON_DEFAULT_WIDGETS = "widgets.json"
JSON_USER_WIDGETS = "user_widgets.json"
JSON_COLOR_PRESETS = "custom_color_sets.json"
widget_data = {}
def get_addon_dir():
return os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
def get_custom_dir():
pref = get_preferences(bpy.context)
if pref.use_default_location:
return bpy.utils.extension_path_user(
package=__package__, path="bone_widget_custom_data", create=True
)
else:
return pref.user_data_location
def get_default_image_dir(image_folder):
return os.path.abspath(os.path.join(get_addon_dir(), image_folder))
def get_custom_image_dir(image_folder):
return os.path.abspath(os.path.join(get_custom_dir(), image_folder))
def get_custom_color_preset_dir():
return os.path.abspath(os.path.join(get_custom_dir(), JSON_COLOR_PRESETS))
def get_widget_directory(file):
if file == JSON_DEFAULT_WIDGETS:
return os.path.join(get_addon_dir(), file)
elif file == JSON_USER_WIDGETS:
return os.path.join(get_custom_dir(), file)
def validate_json_data(data: dict, required_keys: tuple, can_be_empty: bool = True) -> bool:
required_keys = set(required_keys)
if not isinstance(data, dict):
return False
# Check if all required keys are present
if not required_keys.issubset(data.keys()):
return False
if not can_be_empty:
# Check if values are not empty
if any(not data[key] for key in required_keys):
return False
return True
def update_preview_collection():
from .functions.preview_functions import create_preview_collection
create_preview_collection()
def objectDataToDico(object, custom_image):
verts = []
depsgraph = bpy.context.evaluated_depsgraph_get()
mesh = object.evaluated_get(depsgraph).to_mesh()
for v in mesh.vertices:
verts.append(tuple(numpy.array(tuple(v.co)) *
(object.scale[0], object.scale[1], object.scale[2])))
polygons = []
for p in mesh.polygons:
polygons.append(tuple(p.vertices))
edges = []
for e in mesh.edges:
edges.append(e.key)
custom_image = custom_image if custom_image != "" else "user_defined.png"
wgts = {"vertices": verts, "edges": edges,
"faces": polygons, "image": custom_image}
return (wgts)
def read_widgets(filename=""):
global widget_data
wgts = {}
if not filename:
files = [JSON_DEFAULT_WIDGETS, JSON_USER_WIDGETS]
else:
files = [filename]
for file in files:
jsonFile = get_widget_directory(file)
if os.path.exists(jsonFile):
f = open(jsonFile, 'r')
wgts.update(json.load(f))
f.close()
if not filename: # if both files have been read
widget_data = wgts.copy()
return (wgts)
def get_widget_data(widget):
return widget_data[widget]
def write_widgets(wgts, file):
jsonFile = get_widget_directory(file)
# if os.path.exists(jsonFile):
f = open(jsonFile, 'w')
f.write(json.dumps(wgts))
f.close()
def add_remove_widgets(context, addOrRemove, items, widgets, widget_name="", custom_image=""):
wgts = {}
# file from where the widget should be read or written to
file = JSON_USER_WIDGETS
widget_items = []
for widget_item in items:
widget_items.append(widget_item[1])
activeShape = None
ob_name = None
return_message = ""
if addOrRemove == 'add':
wgts = read_widgets(file)
bw_widget_prefix = get_preferences(context).widget_prefix
for ob in widgets:
if not widget_name:
if ob.name.startswith(bw_widget_prefix):
ob_name = ob.name[len(bw_widget_prefix):]
else:
ob_name = ob.name
else:
ob_name = widget_name
if (ob_name) not in widget_items:
widget_items.append(ob_name)
wgts[ob_name] = objectDataToDico(ob, custom_image)
activeShape = ob_name
return_message = "Widget - " + ob_name + " has been added!"
elif addOrRemove == 'remove':
user_widgets = read_widgets(file)
if widgets in user_widgets:
wgts = user_widgets
else:
file = JSON_DEFAULT_WIDGETS
wgts = read_widgets(file)
del wgts[widgets]
if widgets in widget_items:
widget_index = widget_items.index(widgets)
activeShape = widget_items[widget_index +
1] if widget_index == 0 else widget_items[widget_index - 1]
widget_items.remove(widgets)
return_message = "Widget - " + widgets + " has been removed!"
if activeShape is not None:
write_widgets(wgts, file)
# update the preview panel
update_preview_collection()
# trigger an update and display widget
bpy.context.window_manager.widget_list = activeShape
return 'INFO', return_message
elif ob_name is not None:
return 'WARNING', "Widget - " + ob_name + " already exists!"
def export_widget_library(filepath):
wgts = read_widgets(JSON_USER_WIDGETS)
if wgts:
# variables needed for exporting widgets
dest_dir = os.path.dirname(filepath)
json_dir = get_custom_dir()
image_folder = 'custom_thumbnails'
custom_image_dir = get_custom_image_dir(image_folder)
filename = os.path.basename(filepath)
if not filename:
filename = "widget_library.zip"
elif not filename.endswith('.zip'):
filename += ".zip"
# start the zipping process
try:
from zipfile import ZipFile
with ZipFile(os.path.join(dest_dir, filename), "w") as zip:
# write the json file
file = os.path.join(json_dir, JSON_USER_WIDGETS)
arcname = os.path.basename(file)
zip.write(file, arcname=arcname)
# write the custom images if present
if os.path.exists(custom_image_dir):
from pathlib import Path
for filepath in Path(custom_image_dir).iterdir():
arcname = os.path.join(
image_folder, os.path.basename(filepath))
zip.write(filepath, arcname=arcname)
except Exception as e:
print("Error exporting widget library: ", e)
return 0
return len(wgts)
def import_widget_library(filepath, action=""):
required_data_keys = ("vertices", "faces", "edges", "image") # json data
wgts = {}
from zipfile import ZipFile
# dest_dir = os.path.abspath(os.path.join(get_addon_dir(), '..'))
dest_dir = bpy.app.tempdir
widget_import = BoneWidgetImportData()
widget_import.import_type = "widget"
if os.path.exists(filepath) and action:
try:
with ZipFile(filepath, 'r') as zip_file:
# extract images
for file in zip_file.namelist():
if file.startswith('custom_thumbnails/'):
zip_file.extract(file, dest_dir)
elif file.endswith('.json'): # extract data from the .json file
f = zip_file.read(file)
json_data = f.decode('utf8').replace("'", '"')
wgts = json.loads(json_data)
# validate wgts data type
if not isinstance(wgts, dict):
raise TypeError(
f"Expected a dictionary, but got {type(wgts).__name__}")
current_wgts = read_widgets(JSON_USER_WIDGETS)
# check for duplicate names
for name, data in sorted(wgts.items()): # sorting by keys
widget_import.total_num_imports += 1
# validate json data
if not validate_json_data(data, required_data_keys):
widget_import.failed_imports.append(Widget(name, data))
continue
if action == "ASK":
widget_import.skipped_imports.append(Widget(name, data))
elif action == "OVERWRITE":
widget_import.imported_items.append(Widget(name, data))
elif action == "SKIP":
# check for duplicates
data_match = data == current_wgts[name]
if data_match:
widget_import.skipped_imports.append(
Widget(name, data))
# widget_import.duplicate_imports.update({name : data})
elif name not in current_wgts:
widget_import.imported_items.append(Widget(name, data))
else:
widget_import.skipped_imports.append(
Widget(name, data))
else:
widget_import.failed_imports.append(Widget(name, data))
except TypeError as e: # Handle data type errors specifically
print(f"Error while importing widget library: {e}")
widget_import.json_import_error = True
except Exception as e:
print(f"Error while importing widget library: {e}")
for name, data in wgts.items():
widget_import.failed_imports.append(Widget(name, data))
widget_import.total_num_imports = widget_import.failed()
return widget_import
def update_widget_library(new_widgets: dict[str, dict[str, list | str]],
new_images: set[str], zip_filepath: str) -> None:
# store the currently selected widget
current_widget = bpy.context.window_manager.widget_list
wgts = read_widgets(JSON_USER_WIDGETS)
wgts.update(new_widgets)
write_widgets(wgts, JSON_USER_WIDGETS)
# extract any images needed from zip library
if new_images:
from zipfile import ZipFile
dest_dir = get_custom_dir()
if os.path.exists(zip_filepath):
try:
with ZipFile(zip_filepath, 'r') as zip_file:
for file in zip_file.namelist():
if file.startswith('custom_thumbnails/') and file.split("/")[1] in new_images:
zip_file.extract(file, dest_dir)
except Exception as e:
print("Failed to extract custom images: ", e)
else:
print("zip file path doesn't exist!! - ", zip_filepath)
# update the preview panel
update_preview_collection()
# trigger an update and display original but updated widget
bpy.context.window_manager.widget_list = current_widget
def update_custom_image(image_name):
current_widget = bpy.context.window_manager.widget_list
current_widget_data = get_widget_data(current_widget)
# swap out the image
current_widget_data['image'] = image_name
# update and write the new data
wgts = read_widgets(JSON_USER_WIDGETS)
if current_widget in wgts:
wgts[current_widget] = current_widget_data
write_widgets(wgts, JSON_USER_WIDGETS)
else:
wgts = read_widgets(JSON_DEFAULT_WIDGETS)
wgts[current_widget] = current_widget_data
write_widgets(wgts, JSON_DEFAULT_WIDGETS)
# update the preview panel
update_preview_collection()
# trigger an update and display original but updated widget
bpy.context.window_manager.widget_list = current_widget
def reset_default_images():
current_widget = bpy.context.window_manager.widget_list
wgts = read_widgets(JSON_DEFAULT_WIDGETS)
for name, data in wgts.items():
image = f"{name}.png"
data["image"] = image
write_widgets(wgts, JSON_DEFAULT_WIDGETS)
# update the preview panel
update_preview_collection()
# trigger an update and display original but updated widget
bpy.context.window_manager.widget_list = current_widget
################ COLOR PRESETS ################
def read_color_presets():
presets = {}
# Read the JSON file
json_file = get_custom_color_preset_dir()
if os.path.exists(json_file):
with open(json_file, "r") as file:
presets = json.load(file)
presets = {item["name"]: item for item in presets} # convert to dictionary
return presets
def update_color_presets(new_presets, zip_filepath):
for preset in new_presets:
add_color_set(bpy.context, preset)
# extract any images needed from zip library
# if new_images:
# from zipfile import ZipFile
# dest_dir = os.path.abspath(os.path.join(get_addon_dir(), '..'))
# if os.path.exists(zip_filepath):
# try:
# with ZipFile(zip_filepath, 'r') as zip_file:
# for file in zip_file.namelist():
# if file.startswith('custom_thumbnails/') and file.split("/")[1] in new_images:
# zip_file.extract(file, dest_dir)
# except:
# pass
def import_color_presets(filepath, action=""):
required_data_keys = ("name", "normal", "select", "active") # json data
presets = None
from zipfile import ZipFile
dest_dir = get_custom_dir()
presets_import = BoneWidgetImportData()
presets_import.import_type = "colorset"
if os.path.exists(filepath) and action:
try:
with ZipFile(filepath, 'r') as zip_file:
# extract images
for file in zip_file.namelist():
# if file.startswith('preset_thumbnails/'):
# zip_file.extract(file, dest_dir)
if file.endswith('.json'): # extract data from the .json file
f = zip_file.read(file)
json_data = f.decode('utf8').replace("'", '"')
presets = json.loads(json_data)
# validate presets data type
if not isinstance(presets, list):
raise TypeError(
f"Expected a list, but got {type(presets).__name__}")
current_presets = read_color_presets()
# check for duplicate presets
for preset in presets:
presets_import.total_num_imports += 1
# validate json data
if not validate_json_data(preset, required_data_keys, False):
presets_import.failed_imports.append(ColorSet(preset))
continue
name = preset['name']
if action == "ASK":
presets_import.skipped_imports.append(ColorSet(preset))
elif action == "OVERWRITE":
presets_import.imported_items.append(ColorSet(preset))
elif action == "SKIP":
# name and colors match or just colors match
if colors_match(preset, current_presets[name]):
presets_import.skipped_imports.append(ColorSet(preset))
elif not name in current_presets:
presets_import.imported_items.append(ColorSet(preset))
else:
presets_import.skipped_imports.append(ColorSet(preset))
else:
presets_import.failed_imports.append(ColorSet(preset))
except TypeError as e: # Handle data type errors specifically
print(f"Error while importing color presets: {e}")
presets_import.json_import_error = True
except Exception as e:
print(f"Error while importing color presets: {e}")
for preset in presets:
presets_import.failed_imports.append(ColorSet(preset))
presets_import.total_num_imports = presets_import.failed()
return presets_import
def colors_match(set1, set2):
if isinstance(set1, dict):
return set1['normal'] == set2['normal'] \
and set1['select'] == set2['select'] \
and set1['active'] == set2['active']
elif isinstance(set1, bpy.types.ThemeBoneColorSet):
return set1.normal == set2.normal \
and set1.select == set2.select \
and set1.active == set2.active
def scan_armature_color_presets(context, armature):
found_color_sets = set()
colorsets_import = BoneWidgetImportData()
colorsets_import.import_type = "colorset"
current_color_sets = context.window_manager.custom_color_presets
# edit bones
for bone in armature.bones:
if bone.color.is_custom:
is_unique_colorset = True
for color_set in current_color_sets:
if colors_match(bone.color.custom, color_set):
is_unique_colorset = False # not unique
break
color_data = (tuple(bone.color.custom.normal), tuple(
bone.color.custom.select), tuple(bone.color.custom.active))
if is_unique_colorset and not color_data in found_color_sets:
color_set = {attr: list(getattr(bone.color.custom, attr)[:3]) for attr in [
"normal", "active", "select"]}
color_set['name'] = bone.name
colorsets_import.skipped_imports.append(ColorSet(color_set))
found_color_sets.add(color_data)
# pose bones
pose_bone = context.object.pose.bones.get(bone.name)
if pose_bone.color.is_custom:
is_unique_colorset = True
for color_set in current_color_sets:
if colors_match(pose_bone.color.custom, color_set):
is_unique_colorset = False # not unique
break
color_data = (tuple(pose_bone.color.custom.normal), tuple(
pose_bone.color.custom.select), tuple(pose_bone.color.custom.active))
if is_unique_colorset and not color_data in found_color_sets:
color_set = {attr: list(getattr(pose_bone.color.custom, attr)[
:3]) for attr in ["normal", "active", "select"]}
color_set['name'] = bone.name
colorsets_import.skipped_imports.append(ColorSet(color_set))
found_color_sets.add(color_data)
return colorsets_import
def export_color_presets(filepath, context):
color_presets = len(context.window_manager.custom_color_presets)
if color_presets:
dest_dir = os.path.dirname(filepath)
json_dir = get_custom_dir()
# image_folder = 'preset_thumbnails'
# custom_image_dir = get_custom_image_dir(image_folder)
filename = os.path.basename(filepath)
if not filename:
filename = "color_presets.zip"
elif not filename.endswith('.zip'):
filename += ".zip"
# start the zipping process
try:
from zipfile import ZipFile
with ZipFile(os.path.join(dest_dir, filename), "w") as zip:
# write the json file
file = os.path.join(json_dir, JSON_COLOR_PRESETS)
arcname = os.path.basename(file)
zip.write(file, arcname=arcname)
except Exception as e:
print("Error exporting color presets: ", e)
return 0
return color_presets
def add_color_set_from_bone(context, bone, suffix_name):
new_item = context.window_manager.custom_color_presets.add()
color_set = bone.color.custom
new_name = bone.name + suffix_name # CHANGE LATER
# check if the name already ends with an incremented number
match = re.match(r"^(.*)\.(\d{3})$", new_name)
count = int(match.group(2)) if match else 1
base_name = match.group(1) if match else new_name
while any(item.name == new_name for item in context.window_manager.custom_color_presets):
new_name = f"{base_name}.{count:03d}"
count += 1
new_item.name = new_name
if not color_set: # new default color set
new_item.normal = (1.0, 0.0, 0.0)
new_item.select = (0.0, 1.0, 0.0)
new_item.active = (0.0, 0.0, 1.0)
else:
new_item.normal = color_set.normal
new_item.select = color_set.select
new_item.active = color_set.active
def add_color_set(context, color_set=None):
new_item = context.window_manager.custom_color_presets.add()
base_name = "Color Set" if not color_set else color_set.name
new_name = base_name
# check if the name already ends with an incremented number
match = re.match(r"^(.*)\.(\d{3})$", base_name)
count = int(match.group(2)) if match else 1
base_name = match.group(1) if match else new_name
while any(item.name == new_name for item in context.window_manager.custom_color_presets):
new_name = f"{base_name}.{count:03d}"
count += 1
new_item.name = new_name
if not color_set: # new default color set
new_item.normal = (1.0, 0.0, 0.0)
new_item.select = (0.0, 1.0, 0.0)
new_item.active = (0.0, 0.0, 1.0)
else:
new_item.normal = color_set.normal
new_item.select = color_set.select
new_item.active = color_set.active
def save_color_sets(context):
if not bpy.context.window_manager.turn_off_colorset_save:
bpy.context.window_manager.turn_off_colorset_save = True
color_sets = [{
"name": item.name,
"normal": list(item.normal),
"select": list(item.select),
"active": list(item.active)
} for item in context.window_manager.custom_color_presets]
filepath = get_custom_color_preset_dir()
with open(filepath, 'w') as f:
json.dump(color_sets, f, indent=4)
bpy.context.window_manager.turn_off_colorset_save = False
def load_color_presets():
filepath = get_custom_color_preset_dir()
if os.path.exists(filepath):
with open(filepath, 'r') as f:
color_sets = json.load(f)
bpy.context.window_manager.custom_color_presets.clear()
bpy.context.window_manager.turn_off_colorset_save = True
for item in color_sets:
new_item = bpy.context.window_manager.custom_color_presets.add()
new_item.name = item["name"]
new_item.normal = item["normal"]
new_item.select = item["select"]
new_item.active = item["active"]
bpy.context.window_manager.turn_off_colorset_save = False
@@ -0,0 +1,614 @@
import bpy
import numpy
from mathutils import Matrix, Vector
from .. import __package__
def get_collection(context):
# check user preferences for the name of the collection
if not get_preferences(context).use_rigify_defaults:
bw_collection_name = get_preferences(
context).bonewidget_collection_name
else:
bw_collection_name = "WGTS_" + context.active_object.name
collection = recursive_layer_collection(
context.scene.collection, bw_collection_name)
if collection: # if it already exists
return collection
collection = bpy.data.collections.get(bw_collection_name)
if collection: # if it exists but not linked to scene
context.scene.collection.children.link(collection)
return collection
else: # create a new collection
collection = bpy.data.collections.new(bw_collection_name)
context.scene.collection.children.link(collection)
# hide new collection
viewlayer_collection = context.view_layer.layer_collection.children[collection.name]
viewlayer_collection.hide_viewport = True
return collection
def recursive_layer_collection(layer_collection, collection_name):
found = None
if (layer_collection.name == collection_name):
return layer_collection
for layer in layer_collection.children:
found = recursive_layer_collection(layer, collection_name)
if found:
return found
def get_view_layer_collection(context, widget=None):
widget_collection = bpy.data.collections[bpy.data.objects[widget.name].users_collection[0].name]
# save current active layer_collection
saved_layer_collection = bpy.context.view_layer.layer_collection
# actually find the view_layer we want
layer_collection = recursive_layer_collection(
saved_layer_collection, widget_collection.name)
# make sure the collection (data level) is not hidden
widget_collection.hide_viewport = False
# change the active view layer
bpy.context.view_layer.active_layer_collection = layer_collection
# make sure it isn't excluded so it can be edited
layer_collection.exclude = False
# return the active view layer to what it was
bpy.context.view_layer.active_layer_collection = saved_layer_collection
return layer_collection
def match_bone_matrix(widget, match_bone):
if widget == None:
return
widget.matrix_local = match_bone.bone.matrix_local
widget.matrix_world = match_bone.id_data.matrix_world @ match_bone.bone.matrix_local
if match_bone.custom_shape_transform:
# if it has a transform override, apply this to the widget loc and rot
org_scale = widget.matrix_world.to_scale()
org_scale_mat = Matrix.Scale(1, 4, org_scale)
target_matrix = match_bone.custom_shape_transform.id_data.matrix_world @ match_bone.custom_shape_transform.bone.matrix_local
loc = target_matrix.to_translation()
loc_mat = Matrix.Translation(loc)
rot = target_matrix.to_euler().to_matrix()
widget.matrix_world = loc_mat @ rot.to_4x4() @ org_scale_mat
if match_bone.use_custom_shape_bone_size:
ob_scale = bpy.context.scene.objects[match_bone.id_data.name].scale
widget.scale = [match_bone.bone.length * ob_scale[0],
match_bone.bone.length * ob_scale[1], match_bone.bone.length * ob_scale[2]]
# if the user has added any custom transforms to the bone widget display - calculate this too
loc = match_bone.custom_shape_translation
rot = match_bone.custom_shape_rotation_euler
scale = match_bone.custom_shape_scale_xyz
widget.scale *= scale
widget.matrix_world = widget.matrix_world @ Matrix.LocRotScale(
loc, rot, widget.scale)
widget.data.update()
def from_widget_find_bone(widget):
match_bone = None
for ob in bpy.context.scene.objects:
if ob.type == "ARMATURE":
for bone in ob.pose.bones:
if bone.custom_shape == widget:
match_bone = bone
return match_bone
def create_widget(bone, widget, relative, size, slide, rotation, collection, use_face_data, wireframe_width):
if not get_preferences(bpy.context).use_rigify_defaults:
bw_widget_prefix = get_preferences(bpy.context).widget_prefix
else:
bw_widget_prefix = "WGT-" + bpy.context.active_object.name + "_"
matrix_bone = bone
# delete the existing shape
if bone.custom_shape:
bpy.data.objects.remove(
bpy.data.objects[bone.custom_shape.name], do_unlink=True)
# make the data name include the prefix
new_data = bpy.data.meshes.new(bw_widget_prefix + bone.name)
bone.use_custom_shape_bone_size = relative
# deal with face data
faces = widget['faces'] if use_face_data else []
# add the verts
new_data.from_pydata(numpy.array(
widget['vertices']) * size, widget['edges'], faces)
# Create transform matrices (slide vector and rotation)
widget_matrix = Matrix()
# make the slide value always relative to the bone length
if not relative: # TODO: shift this to user preference?
slide = Vector(slide) # turn slide into a vector
slide *= bone.length
trans = Matrix.Translation(slide)
rot = rotation.to_matrix().to_4x4()
# Translate then rotate the matrix
widget_matrix = widget_matrix @ trans
widget_matrix = widget_matrix @ rot
# transform the widget with this matrix
new_data.transform(widget_matrix)
new_data.update(calc_edges=True)
new_object = bpy.data.objects.new(bw_widget_prefix + bone.name, new_data)
new_object.data = new_data
new_object.name = bw_widget_prefix + bone.name
collection.objects.link(new_object)
new_object.matrix_world = bpy.context.active_object.matrix_world @ matrix_bone.bone.matrix_local
new_object.scale = [matrix_bone.bone.length,
matrix_bone.bone.length, matrix_bone.bone.length]
layer = bpy.context.view_layer
layer.update()
bone.custom_shape = new_object
# show faces if use face data is enabled
bone.bone.show_wire = not use_face_data
if bpy.app.version >= (4, 2, 0):
bone.custom_shape_wire_width = wireframe_width
def symmetrize_widget(bone, collection):
if not get_preferences(bpy.context).use_rigify_defaults:
bw_widget_prefix = get_preferences(bpy.context).widget_prefix
rigify_object_name = ''
else:
bw_widget_prefix = "WGT-"
rigify_object_name = bpy.context.active_object.name + "_"
mirror_bone = find_mirror_object(bone)
if not mirror_bone:
return
widget = bone.custom_shape
if not widget or not widget.data:
return
# clean up existing mirrored widget if it's different
mirror_widget = mirror_bone.custom_shape
if mirror_widget and mirror_widget != widget:
existing = bpy.context.scene.objects.get(mirror_widget.name)
if existing:
bpy.data.objects.remove(existing)
# create mirrored mesh data
new_data = widget.data.copy()
for vert in new_data.vertices:
vert.co.x *= -1 # mirror along X-axis
new_object = widget.copy()
new_object.data = new_data
new_object.name = bw_widget_prefix + rigify_object_name + mirror_bone.name
bpy.data.collections[collection.name].objects.link(new_object)
# use custom shape transform if available
transform_bone = mirror_bone.custom_shape_transform or mirror_bone
new_object.matrix_local = transform_bone.bone.matrix_local
new_object.scale = [transform_bone.bone.length] * 3
new_object.data.flip_normals()
bpy.context.view_layer.update()
mirror_bone.custom_shape = new_object
mirror_bone.bone.show_wire = bone.bone.show_wire
mirror_bone.use_custom_shape_bone_size = bone.use_custom_shape_bone_size
symmetrize_color = get_preferences(bpy.context).symmetrize_color
if bpy.app.version >= (4, 0, 0) and symmetrize_color:
# pose bone colors
mirror_bone.bone.color.custom.normal = bone.bone.color.custom.normal
mirror_bone.bone.color.custom.select = bone.bone.color.custom.select
mirror_bone.bone.color.custom.active = bone.bone.color.custom.active
mirror_bone.bone.color.palette = bone.bone.color.palette
# edit bone colors
mirror_bone.color.custom.normal = bone.color.custom.normal
mirror_bone.color.custom.select = bone.color.custom.select
mirror_bone.color.custom.active = bone.color.custom.active
mirror_bone.color.palette = bone.color.palette
if bpy.app.version >= (4, 2, 0):
mirror_bone.custom_shape_wire_width = bone.custom_shape_wire_width
def symmetrize_widget_helper(bone, collection, active_object, widgets_and_bones):
bw_symmetry_suffix = get_preferences(bpy.context).symmetry_suffix
bw_symmetry_suffix = bw_symmetry_suffix.split(";")
suffix_1 = bw_symmetry_suffix[0].replace(" ", "")
suffix_2 = bw_symmetry_suffix[1].replace(" ", "")
if active_object.name.endswith(suffix_1):
if bone.name.endswith(suffix_1) and widgets_and_bones[bone]:
symmetrize_widget(bone, collection)
elif active_object.name.endswith(suffix_2):
if bone.name.endswith(suffix_2) and widgets_and_bones[bone]:
symmetrize_widget(bone, collection)
def delete_unused_widgets():
if not get_preferences(bpy.context).use_rigify_defaults:
bw_collection_name = get_preferences(
bpy.context).bonewidget_collection_name
else:
bw_collection_name = 'WGTS_' + bpy.context.active_object.name
collection = recursive_layer_collection(
bpy.context.scene.collection, bw_collection_name)
widget_list = []
for ob in bpy.data.objects:
if ob.type == 'ARMATURE':
for bone in ob.pose.bones:
if bone.custom_shape:
widget_list.append(bone.custom_shape)
unwanted_list = [
ob for ob in collection.all_objects if ob not in widget_list]
for ob in unwanted_list:
bpy.data.objects.remove(bpy.data.objects[ob.name], do_unlink=True)
return
def edit_widget(active_bone):
widget = active_bone.custom_shape
collection = get_view_layer_collection(bpy.context, widget)
collection.hide_viewport = False
# hide all other objects in collection
for obj in collection.collection.all_objects:
if obj.name != widget.name:
obj.hide_set(True)
else:
obj.hide_set(False) # in case user manually hid it
armature = active_bone.id_data
bpy.ops.object.mode_set(mode='OBJECT')
bpy.context.active_object.select_set(False)
if bpy.context.space_data.local_view:
bpy.ops.view3d.localview()
# select object and make it active
widget.select_set(True)
bpy.context.view_layer.objects.active = widget
bpy.ops.object.mode_set(mode='EDIT')
bpy.context.tool_settings.mesh_select_mode = (
True, False, False) # enter vertex mode
def return_to_armature(widget):
bone = from_widget_find_bone(widget)
armature = bone.id_data
if bpy.context.active_object.mode == 'EDIT':
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='DESELECT')
collection = get_view_layer_collection(bpy.context, widget)
collection.hide_viewport = True
# unhide all objects in the collection
for obj in collection.collection.all_objects:
obj.hide_set(False)
if bpy.context.space_data.local_view:
bpy.ops.view3d.localview()
bpy.context.view_layer.objects.active = armature
armature.select_set(True)
bpy.ops.object.mode_set(mode='POSE')
if bpy.app.version < (5, 0, 0):
armature.data.bones[bone.name].select = True
armature.data.bones.active = armature.data.bones[bone.name]
def find_mirror_object(object):
bw_symmetry_suffix = get_preferences(bpy.context).symmetry_suffix
bw_symmetry_suffix = bw_symmetry_suffix.split(";")
suffix_1 = bw_symmetry_suffix[0].replace(" ", "")
suffix_2 = bw_symmetry_suffix[1].replace(" ", "")
if object.name.endswith(suffix_1):
suffix = suffix_2
suffix_length = len(suffix_1)
elif object.name.endswith(suffix_2):
suffix = suffix_1
suffix_length = len(suffix_2)
elif object.name.endswith(suffix_1.lower()):
suffix = suffix_2.lower()
suffix_length = len(suffix_1)
elif object.name.endswith(suffix_2.lower()):
suffix = suffix_1.lower()
suffix_length = len(suffix_2)
else: # what if the widget ends in .001?
print('Object suffix unknown, using blank')
suffix = ''
object_name = list(object.name)
object_base_name = object_name[:-suffix_length]
mirrored_object_name = "".join(object_base_name) + suffix
if object.id_data.type == 'ARMATURE':
return object.id_data.pose.bones.get(mirrored_object_name)
else:
return bpy.context.scene.objects.get(mirrored_object_name)
def find_match_bones():
bw_symmetry_suffix = get_preferences(bpy.context).symmetry_suffix
bw_symmetry_suffix = bw_symmetry_suffix.split(";")
suffix_1 = bw_symmetry_suffix[0].replace(" ", "")
suffix_2 = bw_symmetry_suffix[1].replace(" ", "")
widgets_and_bones = {}
if bpy.context.object.type == 'ARMATURE':
for bone in bpy.context.selected_pose_bones:
if bone.name.endswith(suffix_1) or bone.name.endswith(suffix_2):
widgets_and_bones[bone] = bone.custom_shape
mirror_bone = find_mirror_object(bone)
if mirror_bone:
widgets_and_bones[mirror_bone] = mirror_bone.custom_shape
armature = bpy.context.object
active_object = bpy.context.active_pose_bone
else:
for shape in bpy.context.selected_objects:
bone = from_widget_find_bone(shape)
if bone.name.endswith(("L", "R")):
widgets_and_bones[from_widget_find_bone(shape)] = shape
mirrorShape = find_mirror_object(shape)
if mirrorShape:
widgets_and_bones[mirrorShape] = mirrorShape
active_object = from_widget_find_bone(bpy.context.object)
armature = active_object.id_data
return (widgets_and_bones, active_object, armature)
def resync_widget_names():
if not get_preferences(bpy.context).use_rigify_defaults:
bw_collection_name = get_preferences(
bpy.context).bonewidget_collection_name
bw_widget_prefix = get_preferences(bpy.context).widget_prefix
else:
bw_collection_name = 'WGTS_' + bpy.context.active_object.name
bw_widget_prefix = 'WGT-' + bpy.context.active_object.name + '_'
widgets_and_bones = {}
if bpy.context.object.type == 'ARMATURE':
for bone in bpy.context.active_object.pose.bones:
if bone.custom_shape:
widgets_and_bones[bone] = bone.custom_shape
for k, v in widgets_and_bones.items():
if k.name != (bw_widget_prefix + k.name):
bpy.data.objects[v.name].name = str(bw_widget_prefix + k.name)
def clear_bone_widgets():
if bpy.context.object.type == 'ARMATURE':
for bone in bpy.context.selected_pose_bones:
if bone.custom_shape:
bone.custom_shape = None
bone.custom_shape_transform = None
def add_object_as_widget(context, collection):
selected_objects = bpy.context.selected_objects
if len(selected_objects) != 2:
print('Only a widget object and the pose bone(s)')
return {'FINISHED'}
allowed_object_types = ['MESH', 'CURVE']
widget_object = None
for ob in selected_objects:
if ob.type in allowed_object_types:
widget_object = ob
if widget_object:
active_bone = context.active_pose_bone
# deal with any existing shape
if active_bone.custom_shape:
bpy.data.objects.remove(
bpy.data.objects[active_bone.custom_shape.name], do_unlink=True)
# duplicate shape
widget = widget_object.copy()
widget.data = widget.data.copy()
# rename it
bw_widget_prefix = get_preferences(context).widget_prefix
widget_name = bw_widget_prefix + active_bone.name
widget.name = widget_name
widget.data.name = widget_name
# link it
collection.objects.link(widget)
# match transforms
widget.matrix_world = bpy.context.active_object.matrix_world @ active_bone.bone.matrix_local
widget.scale = [active_bone.bone.length,
active_bone.bone.length, active_bone.bone.length]
layer = bpy.context.view_layer
layer.update()
active_bone.custom_shape = widget
active_bone.bone.show_wire = True
# deselect original object
widget_object.select_set(False)
def set_bone_color(context, color, clear_both_modes=None):
if context.object.mode == "POSE":
if color == 'DEFAULT' and clear_both_modes != None:
for bone in context.selected_pose_bones:
bone.color.palette = 'DEFAULT'
if clear_both_modes:
bone.bone.color.palette = 'DEFAULT'
return
for bone in context.selected_pose_bones:
bone.color.palette = color # this will get the selected bone color
if color == "CUSTOM":
bone.color.custom.normal = context.scene.bw_settings.custom_pose_color_set.normal
bone.color.custom.select = context.scene.bw_settings.custom_pose_color_set.select
bone.color.custom.active = context.scene.bw_settings.custom_pose_color_set.active
# set the edit bone colors if applicable (while in pose mode)
if get_preferences(context).edit_bone_colors == 'DEFAULT':
bone.bone.color.palette = 'DEFAULT' # this will reset the edit bone color
elif get_preferences(context).edit_bone_colors == 'LINKED':
bone.bone.color.palette = color # set the edit bone colors
# Set the custom color to edit bones (if applicable)
if color == "CUSTOM":
bone.bone.color.custom.normal = context.scene.bw_settings.custom_pose_color_set.normal
bone.bone.color.custom.select = context.scene.bw_settings.custom_pose_color_set.select
bone.bone.color.custom.active = context.scene.bw_settings.custom_pose_color_set.active
elif context.object.mode == "EDIT":
if color == 'DEFAULT' and clear_both_modes != None:
for edit_bone in context.selected_bones:
edit_bone.color.palette = 'DEFAULT'
if clear_both_modes:
pose_bone = context.object.pose.bones.get(edit_bone.name)
pose_bone.color.palette = 'DEFAULT'
return
for edit_bone in context.selected_bones:
if get_preferences(context).edit_bone_colors == 'DEFAULT':
# this will get the edit bone color back to default
edit_bone.color.palette = 'DEFAULT'
elif get_preferences(context).edit_bone_colors == 'LINKED':
edit_bone.color.palette = color # set the edit mode color
# get the pose bone
pose_bone = context.object.pose.bones.get(edit_bone.name)
pose_bone.color.palette = color # set the pose mode color
if color == "CUSTOM":
# set edit bone custom colors
edit_bone.color.custom.normal = context.scene.bw_settings.custom_edit_color_set.normal
edit_bone.color.custom.select = context.scene.bw_settings.custom_edit_color_set.select
edit_bone.color.custom.active = context.scene.bw_settings.custom_edit_color_set.active
# set pose bone custom colors
pose_bone.color.custom.normal = context.scene.bw_settings.custom_edit_color_set.normal
pose_bone.color.custom.select = context.scene.bw_settings.custom_edit_color_set.select
pose_bone.color.custom.active = context.scene.bw_settings.custom_edit_color_set.active
elif get_preferences(context).edit_bone_colors == 'SEPARATE':
edit_bone.color.palette = color # set the edit mode color
if color == "CUSTOM":
# set edit bone custom colors
edit_bone.color.custom.normal = context.scene.bw_settings.custom_edit_color_set.normal
edit_bone.color.custom.select = context.scene.bw_settings.custom_edit_color_set.select
edit_bone.color.custom.active = context.scene.bw_settings.custom_edit_color_set.active
def copy_bone_color(context, bone):
live_update_current_state = context.scene.bw_settings.live_update_on
context.scene.bw_settings.live_update_on = False
if bone.color.is_custom:
if context.object.mode == 'POSE':
context.scene.bw_settings.custom_pose_color_set.normal = bone.color.custom.normal
context.scene.bw_settings.custom_pose_color_set.select = bone.color.custom.select
context.scene.bw_settings.custom_pose_color_set.active = bone.color.custom.active
else:
context.scene.bw_settings.custom_edit_color_set.normal = bone.color.custom.normal
context.scene.bw_settings.custom_edit_color_set.select = bone.color.custom.select
context.scene.bw_settings.custom_edit_color_set.active = bone.color.custom.active
elif bone.color.palette != "DEFAULT": # bone has a theme assigned
theme = bone.color.palette
theme_id = int(theme[-2:]) - 1
theme_color_set = bpy.context.preferences.themes[0].bone_color_sets[theme_id]
palette = context.scene.bw_settings.custom_pose_color_set if context.object.mode == 'POSE' \
else context.scene.bw_settings.custom_edit_color_set
palette.normal = theme_color_set.normal
palette.select = theme_color_set.select
palette.active = theme_color_set.active
context.scene.bw_settings.live_update_on = live_update_current_state
def update_bone_color(self, context):
if context.scene.bw_settings.live_update_on:
set_bone_color(context, "CUSTOM")
def advanced_options_toggled(self, context):
if self.advanced_options:
self.global_size_advanced = (self.global_size_simple,) * 3
self.slide_advanced[1] = self.slide_simple
else:
self.global_size_simple = self.global_size_advanced[1]
self.slide_simple = self.slide_advanced[1]
def bone_color_items(self, context):
items = [("DEFAULT", "Default Colors", "", "", 0)]
for i in range(1, 16):
items.append((f"THEME{i:02}", f"Theme {i:02}",
"", f"COLORSET_{i:02}_VEC", i))
return items
def bone_color_items_short(self, context):
items = []
for i in range(1, 16):
items.append((f"THEME{i:02}", f"Theme {i:02}",
"", f"COLORSET_{i:02}_VEC", i))
items.append(("CUSTOM", "Custom", "", "COLOR", 16))
return items
def live_update_toggle(self, context):
context.scene.bw_settings.live_update_on = self.live_update_toggle
def get_preferences(context):
return context.preferences.addons[__package__].preferences
@@ -0,0 +1,301 @@
import bpy
import bpy.utils.previews
from .json_functions import read_widgets, get_widget_data, get_default_image_dir, get_custom_image_dir, JSON_USER_WIDGETS
import os
from .. import __package__
from mathutils import Vector
preview_collections = {}
def create_preview_collection():
if preview_collections:
del bpy.types.WindowManager.widget_list
for pcoll in preview_collections.values():
bpy.utils.previews.remove(pcoll)
preview_collections.clear()
pcoll = bpy.utils.previews.new()
pcoll.widget_list = ()
preview_collections["widgets"] = pcoll
bpy.types.WindowManager.widget_list = bpy.props.EnumProperty(
items=generate_previews(), name="Shape", description="Shape", update=preview_update
)
def generate_previews():
enum_items = []
pcoll = preview_collections["widgets"]
if pcoll.widget_list:
return pcoll.widget_list
directory = get_default_image_dir('thumbnails')
custom_directory = get_custom_image_dir("custom_thumbnails")
if directory and os.path.exists(directory):
widget_data = {item[0]: item[1].get(
"image", "missing_image.png") for item in read_widgets().items()}
widget_names = sorted(widget_data.keys())
for i, name in enumerate(widget_names):
image = widget_data.get(name, "")
if image is not None:
filepath = os.path.join(directory, image)
# try in custom_thumbnails if above failed
if not os.path.exists(filepath):
filepath = os.path.join(custom_directory, image)
# if image still not found, let the user know
if not os.path.exists(filepath):
filepath = os.path.join(directory, "missing_image.png")
icon = pcoll.get(name)
if not icon:
thumb = pcoll.load(name, filepath, 'IMAGE')
else:
thumb = pcoll[name]
face_data_info = "Contains Face Data" if get_widget_data(
name).get("faces") else ""
enum_items.append((name, name, face_data_info, thumb.icon_id, i))
pcoll.widget_list = enum_items
return enum_items
def preview_update(self, context):
generate_previews()
def get_preview_default():
return bpy.context.preferences.addons[__package__].preferences.preview_default
def copy_custom_image(filepath, filename):
if os.path.exists(filepath):
image_directory = get_custom_image_dir('custom_thumbnails')
destination_path = os.path.join(image_directory, filename)
try:
# create custom thumbnail folder if not existing
if not os.path.exists(image_directory):
os.makedirs(image_directory)
import shutil
shutil.copyfile(filepath, destination_path)
return True
except:
pass
return False
def remove_custom_image(filename):
image_directory = get_custom_image_dir('custom_thumbnails')
destination_path = os.path.join(image_directory, filename)
if os.path.isfile(destination_path):
# make sure the image is only used once - else stop
count = 0
for v in read_widgets(JSON_USER_WIDGETS).values():
if v.get("image") == filename:
count += 1
if count > 1:
return False
try:
os.remove(destination_path)
return True
except:
pass
return False
#### Thumbnail Render Functions ####
def create_wireframe_copy(obj, use_color, color, thickness):
copy = obj.copy()
copy.data = obj.data.copy()
if not use_color:
copy.color = color
# Create a new Geometry Nodes modifier
geo_mod = copy.modifiers.new(name="BoneWidget_WireFrame", type='NODES')
# Create a new node group and assign it to the modifier
node_group = bpy.data.node_groups.new(
name="BONEWIDGET_GeometryGroup", type='GeometryNodeTree')
geo_mod.node_group = node_group
# Add input and output sockets
node_group.interface.new_socket(
name="Geometry", in_out="INPUT", socket_type="NodeSocketGeometry")
node_group.interface.new_socket(
name="Geometry", in_out="OUTPUT", socket_type="NodeSocketGeometry")
# Add Thickness input
thickness_socket = node_group.interface.new_socket(
name="Thickness", in_out="INPUT", socket_type="NodeSocketFloat")
thickness_socket.default_value = 0.5
thickness_socket.min_value = 0.01
thickness_socket.max_value = 2
# Create nodes
node_input = node_group.nodes.new('NodeGroupInput')
node_output = node_group.nodes.new('NodeGroupOutput')
node_uv_sphere = node_group.nodes.new('GeometryNodeMeshUVSphere')
node_mesh_to_curve = node_group.nodes.new('GeometryNodeMeshToCurve')
node_curve_circle = node_group.nodes.new(
'GeometryNodeCurvePrimitiveCircle')
node_instance_on_points = node_group.nodes.new(
'GeometryNodeInstanceOnPoints')
node_curve_to_mesh = node_group.nodes.new('GeometryNodeCurveToMesh')
node_join_geometry = node_group.nodes.new('GeometryNodeJoinGeometry')
# Set initial values (internal)
node_uv_sphere.inputs["Segments"].default_value = 8
node_uv_sphere.inputs["Rings"].default_value = 8
node_curve_circle.inputs["Resolution"].default_value = 8
# Position nodes for better visualization (optional)
node_input.location = (-400, 0)
node_uv_sphere.location = (-150, 100)
node_mesh_to_curve.location = (-150, -50)
node_curve_circle.location = (-150, -150)
node_instance_on_points.location = (100, 250)
node_curve_to_mesh.location = (100, -100)
node_join_geometry.location = (350, 0)
node_output.location = (550, 0)
# Connect nodes
node_group.links.new(
node_input.outputs["Geometry"], node_instance_on_points.inputs["Points"])
node_group.links.new(
node_input.outputs["Geometry"], node_mesh_to_curve.inputs["Mesh"])
node_group.links.new(
node_input.outputs["Thickness"], node_uv_sphere.inputs["Radius"])
node_group.links.new(
node_input.outputs["Thickness"], node_curve_circle.inputs["Radius"])
node_group.links.new(
node_uv_sphere.outputs["Mesh"], node_instance_on_points.inputs["Instance"])
node_group.links.new(
node_mesh_to_curve.outputs["Curve"], node_curve_to_mesh.inputs["Curve"])
node_group.links.new(
node_curve_circle.outputs["Curve"], node_curve_to_mesh.inputs["Profile Curve"])
node_group.links.new(
node_instance_on_points.outputs["Instances"], node_join_geometry.inputs["Geometry"])
node_group.links.new(
node_curve_to_mesh.outputs["Mesh"], node_join_geometry.inputs["Geometry"])
node_group.links.new(
node_join_geometry.outputs["Geometry"], node_output.inputs["Geometry"])
# scale this so it isn't so sensitive
geo_mod["Socket_2"] = (thickness / 10)
return copy
def setup_viewport(context):
area = context.area
space = context.space_data
region_3d = space.region_3d
original_view_matrix = region_3d.view_matrix.copy()
bpy.ops.view3d.view_selected()
return original_view_matrix
def restore_viewport_position(context, view_matrix, view_perspective):
if context.space_data.type == 'VIEW_3D':
region_3d = context.space_data.region_3d
# Restore viewport matrix position
region_3d.view_matrix = view_matrix
# Restore viewport perspective
region_3d.view_perspective = view_perspective
def render_widget_thumbnail(image_name, widget_object, image_directory):
if image_directory: # If True save to the current directory but...
if bpy.data.filepath: # Check the file has been saved
image_directory = os.path.dirname(bpy.data.filepath)
else:
# Fall back if it hasn't been saved
image_directory = os.path.expanduser("~")
# add '.png' to the name
image_name = image_name + '.png'
else: # if False use the add-on location
image_directory = get_custom_image_dir('custom_thumbnails')
destination_path = os.path.join(image_directory, image_name)
scene = bpy.context.scene
scene.render.engine = 'BLENDER_WORKBENCH'
scene.render.resolution_x, scene.render.resolution_y = (512, 512)
scene.render.resolution_percentage = 100
scene.render.image_settings.file_format = 'PNG'
scene.render.image_settings.color_mode = 'RGBA'
scene.view_settings.view_transform = 'Standard'
scene.render.film_transparent = True
scene.display.shading.light = 'FLAT'
scene.display.shading.color_type = 'OBJECT'
scene.render.filepath = image_directory
# Reframe Camera
camera = scene.camera
obj = widget_object
frame_object_with_padding(camera, obj, padding=0.1)
bpy.ops.render.render(write_still=False)
bpy.data.images['Render Result'].save_render(
filepath=bpy.path.abspath(destination_path))
return bpy.path.abspath(destination_path)
def add_camera_from_view(context):
name = "BoneWidget_Thumbnail_Camera"
region_3d = context.region_data
space = context.space_data
if region_3d is None or space.type != 'VIEW_3D':
print("This must be run from a 3D Viewport.")
return None
# Create camera data and object
cam_data = bpy.data.cameras.new(name)
cam_obj = bpy.data.objects.new(name, cam_data)
context.scene.collection.objects.link(cam_obj)
# Align camera to current viewport
cam_obj.matrix_world = region_3d.view_matrix.inverted()
# Make it the active camera
context.scene.camera = cam_obj
return cam_obj
def frame_object_with_padding(camera, obj, padding=0.1):
depsgraph = bpy.context.evaluated_depsgraph_get()
# Get bounding box corners in world space
coords = [obj.matrix_world @ Vector(corner) for corner in obj.bound_box]
# Find center of bounding box
center = sum(coords, Vector()) / len(coords)
# Scale each point away from the center to apply padding
scaled_coords = [(center + (co - center) * (1 + padding)) for co in coords]
# Flatten the list of Vectors into a list of floats
flat_coords = [v for co in scaled_coords for v in co]
# Use the camera fitting function
cam_location, _ = camera.camera_fit_coords(depsgraph, flat_coords)
camera.location = cam_location
@@ -0,0 +1,59 @@
from bpy.types import Menu
class BONEWIDGET_MT_bw_specials(Menu):
bl_label = "Bone Widget Specials"
def draw(self, context):
layout = self.layout
layout.operator("bonewidget.add_widgets", icon="ADD",
text="Add Widget to Library")
layout.operator("bonewidget.remove_widgets", icon="REMOVE",
text="Remove Widget from Library")
layout.separator()
layout.operator("bonewidget.add_custom_image", icon="FILE_IMAGE",
text="Add Custom Image to Widget")
layout.operator("bonewidget.render_widget_thumbnail", icon="RESTRICT_RENDER_OFF",
text="Render Object as Thumbnail")
layout.separator()
layout.operator("bonewidget.import_widget_library",
icon="IMPORT", text="Import Widget Library")
layout.operator("bonewidget.export_widget_library",
icon="EXPORT", text="Export Widget Library")
class BONEWIDGET_MT_bw_color_presets_specials(Menu):
bl_label = "Color Presets Specials"
def draw(self, context):
layout = self.layout
btn_text = "Add Preset from Theme" if "THEME" in context.scene.bw_settings.bone_widget_colors else "Add Preset from Palette"
layout.operator("bonewidget.add_color_set_from",
text=btn_text, icon="ADD")
layout.operator("bonewidget.add_preset_from_bone",
icon="ADD", text="Add Preset from Bone")
layout.operator("bonewidget.add_presets_from_armature",
icon="ADD", text="Add Preset from Armature")
layout.separator()
layout.operator("bonewidget.import_color_presets",
icon="IMPORT", text="Import Color Presets")
layout.operator("bonewidget.export_color_presets",
icon="EXPORT", text="Export Color Presets")
classes = (
BONEWIDGET_MT_bw_specials,
BONEWIDGET_MT_bw_color_presets_specials,
)
def register():
from bpy.utils import register_class
for cls in classes:
register_class(cls)
def unregister():
from bpy.utils import unregister_class
for cls in classes:
unregister_class(cls)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,280 @@
import bpy
import bpy.utils.previews
from .props import PresetColorSetItem
from .functions.main_functions import (
recursive_layer_collection,
get_preferences,
)
from .functions.preview_functions import (
create_preview_collection,
preview_collections,
get_preview_default,
)
from .functions.json_functions import load_color_presets
from .menus import BONEWIDGET_MT_bw_specials
class BONEWIDGET_PT_bw_panel:
"""BoneWidget Addon UI"""
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = "Rigging"
bl_label = "Bone Widget"
class BONEWIDGET_PT_bw_panel_main(BONEWIDGET_PT_bw_panel, bpy.types.Panel):
bl_idname = 'BONEWIDGET_PT_bw_panel_main'
bl_label = "Bone Widget"
def draw(self, context):
if context.window_manager.load_presets_on_startup:
load_color_presets()
context.window_manager.load_presets_on_startup = False
# cache call to get preferences
preferences = get_preferences(context)
layout = self.layout
# preview toggle checkbox
row = layout.row(align=True)
row.prop(context.window_manager, "toggle_preview")
# preview view
if context.window_manager.toggle_preview:
row = layout.row(align=True)
preview_panel_size = preferences.preview_panel_size
preview_popup_size = preferences.preview_popup_size
row.template_icon_view(context.window_manager, "widget_list", show_labels=True,
scale=preview_panel_size, scale_popup=preview_popup_size)
# dropdown list
row = layout.row(align=True)
row.prop(context.window_manager, "widget_list", expand=False, text="")
row = layout.row(align=True)
row.menu("BONEWIDGET_MT_bw_specials", icon='DOWNARROW_HLT', text="")
row.operator("bonewidget.create_widget",
icon="OBJECT_DATAMODE", text="Create")
if context.mode == "POSE":
row.operator("bonewidget.edit_widget", icon="OUTLINER_DATA_MESH")
else:
row.operator("bonewidget.return_to_armature",
icon="LOOP_BACK", text='To bone')
layout.separator()
# Symmetry buttons etc
row = layout.row(align=True)
row.operator("bonewidget.symmetrize_shape",
icon='MOD_MIRROR', text="Symmetrize Shape")
icon = 'RESTRICT_COLOR_OFF'
if preferences.symmetrize_color:
icon = 'RESTRICT_COLOR_ON'
row.prop(preferences, "symmetrize_color",
icon=icon, text='', toggle=True)
row = layout.row()
row.operator("bonewidget.match_bone_transforms",
icon='GROUP_BONE', text="Match Bone Transforms")
row = layout.row()
row.operator("bonewidget.resync_widget_names",
icon='FILE_REFRESH', text="Resync Widget Names")
# Clear Bone Widget buttons etc
layout.separator()
layout.operator("bonewidget.clear_widgets",
icon='X', text="Clear Bone Widget")
layout.operator("bonewidget.delete_unused_widgets",
icon='TRASH', text="Delete Unused Widgets")
if context.mode == 'POSE':
layout.operator("bonewidget.add_as_widget",
text="Use Selected Object",
icon='RESTRICT_SELECT_OFF')
# if the bw collection exists, show the visibility toggle
if not preferences.use_rigify_defaults: # rigify
bw_collection_name = preferences.bonewidget_collection_name
elif context.active_object: # active object
bw_collection_name = 'WGTS_' + context.active_object.name
else: # this is needed because sometimes there is no active object
bw_collection_name = None
bw_collection = recursive_layer_collection(
context.view_layer.layer_collection, bw_collection_name)
if bw_collection is not None:
if bw_collection.hide_viewport:
icon = "HIDE_ON"
text = "Show Collection"
else:
icon = "HIDE_OFF"
text = "Hide Collection"
row = layout.row()
row.separator()
row = layout.row()
row.operator("bonewidget.toggle_collection_visibilty",
icon=icon, text=text)
# BONE COLORS
if bpy.app.version >= (4, 0, 0):
layout.separator()
row = layout.row(align=True)
row.operator("bonewidget.set_bone_color",
text="Set Bone Color", icon="BRUSHES_ALL")
row.scale_x = 3.0
icon_row = row.row()
icon_row.enabled = (context.object is not None and context.object.type == 'ARMATURE' and
context.object.mode in {'POSE', 'EDIT'})
icon_row.template_icon_view(
context.scene.bw_settings, "bone_widget_colors", show_labels=False, scale=1, scale_popup=1.8)
if context.scene.bw_settings.bone_widget_colors == "CUSTOM":
custom_pose_color = context.scene.bw_settings.custom_pose_color_set
custom_edit_color = context.scene.bw_settings.custom_edit_color_set
if context.object.mode == 'POSE': # display pose bone colors
row = layout.row(align=True)
row.prop(custom_pose_color, "normal", text="")
row.prop(custom_pose_color, "select", text="")
row.prop(custom_pose_color, "active", text="")
# edit bone colors
elif context.object.mode == "EDIT" and preferences.edit_bone_colors != 'DEFAULT':
row = layout.row(align=True)
row.prop(custom_edit_color, "normal", text="")
row.prop(custom_edit_color, "select", text="")
row.prop(custom_edit_color, "active", text="")
if context.object.mode == 'POSE' or (context.object.mode == 'EDIT' and
preferences.edit_bone_colors != 'DEFAULT'):
row.separator(factor=0.5)
row.prop(context.scene.bw_settings, "live_update_toggle",
text="", icon="UV_SYNC_SELECT")
row = layout.row()
row.operator("bonewidget.copy_bone_color",
text="Copy Bone Color", icon="COPYDOWN")
row = layout.row(align=True)
row.operator("bonewidget.clear_bone_color",
text="Clear Bone Color", icon="PANEL_CLOSE")
icon = 'BONE_DATA'
if preferences.clear_both_modes:
icon = 'GROUP_BONE'
row.prop(preferences, "clear_both_modes",
icon=icon, text='', toggle=True)
class BONEWIDGET_PT_bw_custom_color_presets(BONEWIDGET_PT_bw_panel, bpy.types.Panel):
bl_idname = "BONEWIDGET_PT_bw_custom_color_presets"
bl_parent_id = "BONEWIDGET_PT_bw_panel_main"
bl_label = "Custom Color Presets"
bl_options = {'DEFAULT_CLOSED'}
@classmethod
def poll(self, context):
return bpy.app.version >= (4, 0, 0)
def draw(self, context):
layout = self.layout
row = layout.row()
row.template_list("BONEWIDGET_UL_colorset_items", "", context.window_manager, "custom_color_presets",
context.window_manager, "colorset_list_index")
col = row.column(align=True)
col.operator("bonewidget.add_default_custom_colorset",
icon='ADD', text="")
col.operator("bonewidget.remove_custom_item", icon='REMOVE', text="")
col.separator()
col.menu("BONEWIDGET_MT_bw_color_presets_specials",
icon="DOWNARROW_HLT", text="")
col.separator()
col.operator("bonewidget.move_custom_item_up", icon="TRIA_UP", text="")
col.operator("bonewidget.move_custom_item_down",
icon="TRIA_DOWN", text="")
row = layout.row()
row.operator("bonewidget.add_colorset_to_bone",
text="Apply To Selected Bones")
class BONEWIDGET_UL_colorset_items(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_property, index):
# set the size of each color set field
split = layout.split(factor=0.58)
split.prop(item, "name", text="", emboss=False)
row = split.row(align=True)
row.prop(item, "normal", text="")
row.prop(item, "select", text="")
row.prop(item, "active", text="")
classes = (
BONEWIDGET_PT_bw_panel_main,
BONEWIDGET_PT_bw_custom_color_presets,
BONEWIDGET_UL_colorset_items,
)
def register():
if not hasattr(bpy.types.WindowManager, "widget_list"):
create_preview_collection()
bpy.types.WindowManager.toggle_preview = bpy.props.BoolProperty(
name="Preview Panel",
default=get_preview_default(),
description="Show thumbnail previews"
)
bpy.utils.register_class(PresetColorSetItem)
bpy.types.WindowManager.custom_color_presets = bpy.props.CollectionProperty(
type=PresetColorSetItem)
bpy.types.WindowManager.colorset_list_index = bpy.props.IntProperty(
name="Index", default=0)
bpy.types.WindowManager.turn_off_colorset_save = bpy.props.BoolProperty(
name="Turn Off ColorSet Save",
description="Disable automatic saving of color sets",
default=False
)
bpy.types.WindowManager.load_presets_on_startup = bpy.props.BoolProperty(
name="Load Presets on Startup",
description="Load color presets when Blender starts",
default=True
)
from bpy.utils import register_class
for cls in classes:
try:
register_class(cls)
except:
pass
def unregister():
if hasattr(bpy.types.WindowManager, "widget_list"):
del bpy.types.WindowManager.widget_list
del bpy.types.WindowManager.toggle_preview
del bpy.types.WindowManager.custom_color_presets
del bpy.types.WindowManager.colorset_list_index
del bpy.types.WindowManager.turn_off_colorset_save
del bpy.types.WindowManager.load_presets_on_startup
bpy.utils.unregister_class(PresetColorSetItem)
for pcoll in preview_collections.values():
bpy.utils.previews.remove(pcoll)
preview_collections.clear()
from bpy.utils import unregister_class
for cls in classes:
try:
unregister_class(cls)
except:
pass
+203
View File
@@ -0,0 +1,203 @@
import bpy
from bpy.types import AddonPreferences
from bpy.props import StringProperty, BoolProperty, FloatProperty, EnumProperty
from .panels import BONEWIDGET_PT_bw_panel_main
from .operators import BONEWIDGET_OT_reset_default_images, BONEWIDGET_OT_user_data_filebrowser
class BoneWidget_preferences(AddonPreferences):
bl_idname = __package__
# Use Rigify Defaults
use_rigify_defaults: BoolProperty(
name="Use Rigify Defaults",
description="Use the same naming convention for widget creation (disable if you prefer your naming convention)",
default=True,
)
# widget prefix
widget_prefix: StringProperty(
name="Bone Widget prefix",
description="Choose a prefix for the widget objects",
default="WGT-",
)
# symmetry suffix
symmetry_suffix: StringProperty(
name="Bone Widget symmetry suffix",
description="Choose a naming convention for the symmetrical widgets, separate by semicolon.",
default="L; R",
)
# collection name
bonewidget_collection_name: StringProperty(
name="Bone Widget collection name",
description="Choose a name for the collection the widgets will appear",
default="WGTS",
)
def panel_category_update_fn(self, context):
has_panel = hasattr(bpy.types, BONEWIDGET_PT_bw_panel_main.bl_idname)
if has_panel:
try:
bpy.utils.unregister_class(BONEWIDGET_PT_bw_panel_main)
except:
pass
BONEWIDGET_PT_bw_panel_main.bl_category = self.panel_category
bpy.utils.register_class(BONEWIDGET_PT_bw_panel_main)
panel_category: StringProperty(
name="Panel Category",
description="Category to show Bone-Widgets panel",
default="Rigging",
update=panel_category_update_fn,
)
preview_panel_size: FloatProperty(
name="Preview Panel Size",
description="Size of the Preview Panel",
default=6.0,
min=1.0,
max=10.0,
precision=1,
)
preview_popup_size: FloatProperty(
name="Preview Popup Size",
description="Size of the Preview Popup Thumbnails",
default=3.5,
min=1.0,
max=10.0,
precision=1,
)
preview_default: BoolProperty(
name="Default Preview State",
description="Default state of preview panel",
default=True,
)
edit_bone_colors: EnumProperty(
name="Edit Bone Colors",
description="Behavior of Edit Bone colors",
items=[
('DEFAULT', "Default", "Set the Edit Bone color to the default colors"),
('LINKED', "Linked",
"Use the same colors for both the Edit bones and Pose bones"),
('SEPARATE', "Separate",
"Edit bones and Pose bones will have their own colors"),
],
default='DEFAULT'
)
clear_both_modes: bpy.props.BoolProperty(
name="Clear All Bone Color",
description='When enabled, bone colors from Edit mode and Pose mode will be cleared. When disabled, only the color from the current mode will be cleared',
default=True
)
symmetrize_color: bpy.props.BoolProperty(
name="Symmetrize Bone Colors",
description='When enabled, bone colors will be copied when you symmetrize a widget. When disabled, only the shape will be symmetrized',
default=True
)
use_default_location: bpy.props.BoolProperty(
name="Use default location",
description='When enabled, user widgets and color sets will be saved to extensions/.user/{repository_name}/bone_widget/bone_widget_custom_data',
default=True
)
user_data_location: StringProperty(
name="User Data Location",
description="Choose a location where you want to save custom data",
default="",
)
def draw(self, context):
layout = self.layout
box = layout.box()
box.label(text="Widget Naming Convention:")
box.prop(self, "use_rigify_defaults", text="Use Rigify Defaults")
box_row = box.row()
box_col = box_row.column()
box_col.prop(self, "widget_prefix", text="Widget Prefix")
box_col.prop(self, "bonewidget_collection_name",
text="Collection name")
box_row.enabled = not self.use_rigify_defaults
box_row = box.row()
box_row = box.row()
box_row.prop(self, "symmetry_suffix", text="Symmetry suffix")
row = layout.row()
box = layout.box()
box_col = box.column()
box_col.label(text="Set the category to show Bone-Widgets panel:")
box_col.prop(self, "panel_category")
# edit bone colors
row = layout.row()
box = layout.box()
box.label(text="Bone Color Behavior:")
row = box.row()
row.prop(self, "edit_bone_colors")
row = box.row()
row.label(text="Clearing Colors:")
row.prop(self, "clear_both_modes")
row = box.row()
row.label(text="Symmetrize Colors:")
row.prop(self, "symmetrize_color")
# preview area
row = layout.row()
box = layout.box()
box.label(text="Thumbnail Previews:")
box_row = box.row()
box_row.prop(self, "preview_default",
text="Display Previews by Default")
box_row = box.row()
box_col = box_row.column()
box_col.label(text="Preview Panel Size:")
box_row.prop(self, "preview_panel_size", text="")
box_row = box.row()
box_col = box_row.column()
box_col.label(text="Preview Popup Size:")
box_row.prop(self, "preview_popup_size", text="")
# custom data
row = layout.row()
box = layout.box()
box.label(text="Custom Data:")
box_row = box.row()
box_row.prop(self, "use_default_location", text="Use Default Location")
box_row = box.row()
box_col = box_row.column()
box_col.prop(self, "user_data_location", text="Custom Path")
box_row.operator("bonewidget.user_data_filebrowser",
icon="FILEBROWSER", text="")
box_row.enabled = not self.use_default_location
# reset button
layout.separator()
row = layout.row()
row = row.split(factor=.75)
row.label(text="Reset Default Widget Thumbnails")
row.operator("bonewidget.reset_default_images", icon="ERROR")
def register():
bpy.utils.register_class(BoneWidget_preferences)
def unregister():
bpy.utils.unregister_class(BoneWidget_preferences)
+197
View File
@@ -0,0 +1,197 @@
import bpy
from .functions.main_functions import (
update_bone_color,
bone_color_items_short,
live_update_toggle
)
from bpy.types import PropertyGroup
from bpy.props import BoolProperty, EnumProperty, PointerProperty
class CustomColorSet(bpy.types.PropertyGroup):
name: bpy.props.StringProperty(name="Name", default="Untitled")
normal: bpy.props.FloatVectorProperty(
name="Normal",
subtype='COLOR_GAMMA',
default=(0.0, 0.0, 0.0),
size=3,
min=0.0, max=1.0,
description="Color used for the surface of bones",
update=update_bone_color,
)
select: bpy.props.FloatVectorProperty(
name="Select",
subtype='COLOR_GAMMA',
default=(0.0, 0.0, 0.0),
size=3,
min=0.0, max=1.0,
description="Color used for selected bones",
update=update_bone_color,
)
active: bpy.props.FloatVectorProperty(
name="Active",
subtype='COLOR_GAMMA',
default=(0.0, 0.0, 0.0),
size=3,
min=0.0, max=1.0,
description="Color used for active bones",
update=update_bone_color,
)
class BW_Settings(PropertyGroup):
live_update_on: BoolProperty(
name="Live Update On",
description="Enable live widget updates",
default=False
)
live_update_toggle: BoolProperty(
name="Live Update Toggle",
description="Toggle live updates in the UI",
default=False,
update=live_update_toggle,
)
lock_colorset_color_changes: BoolProperty(
name="Lock ColorSet Color Changes",
description="Prevent modifying the current color set",
default=False
)
# Blender's bone color themes
bone_widget_colors: EnumProperty(
name="Colors",
description="Select a Bone Color",
items=bone_color_items_short, # get the themes minus the blank ones
default=1, # THEME01
)
# Nested Property Groups
custom_edit_color_set: PointerProperty(type=CustomColorSet)
custom_pose_color_set: PointerProperty(type=CustomColorSet)
save_timer = None
def debounce_save(context):
"""Schedule saving the color sets 1 second after the last change."""
from .functions.json_functions import save_color_sets
global save_timer
if save_timer is not None:
try:
bpy.app.timers.unregister(save_timer)
except ValueError:
pass
def delayed_save():
save_color_sets(context)
return None # stop the timer
save_timer = delayed_save
bpy.app.timers.register(save_timer, first_interval=1.0)
class PresetColorSetItem(bpy.types.PropertyGroup):
def update_colorset_list(self, context):
if not context.window_manager.turn_off_colorset_save and not context.scene.bw_settings.lock_colorset_color_changes:
debounce_save(context)
name: bpy.props.StringProperty(
name="Name", default="Untitled", update=update_colorset_list)
normal: bpy.props.FloatVectorProperty(
name="Normal",
subtype='COLOR_GAMMA',
default=(0.0, 0.0, 0.0),
size=3,
min=0.0, max=1.0,
description="Color used for the surface of bones",
update=update_colorset_list,
)
select: bpy.props.FloatVectorProperty(
name="Select",
subtype='COLOR_GAMMA',
default=(0.0, 0.0, 0.0),
size=3,
min=0.0, max=1.0,
description="Color used for selected bones",
update=update_colorset_list,
)
active: bpy.props.FloatVectorProperty(
name="Active",
subtype='COLOR_GAMMA',
default=(0.0, 0.0, 0.0),
size=3,
min=0.0, max=1.0,
description="Color used for active bones",
update=update_colorset_list,
)
class ImportColorSet(bpy.types.PropertyGroup):
normal: bpy.props.FloatVectorProperty(
name="Normal",
subtype='COLOR_GAMMA',
default=(0.0, 0.0, 0.0),
size=3,
min=0.0, max=1.0,
description="Color used for the surface of bones",
)
select: bpy.props.FloatVectorProperty(
name="Select",
subtype='COLOR_GAMMA',
default=(0.0, 0.0, 0.0),
size=3,
min=0.0, max=1.0,
description="Color used for selected bones",
)
active: bpy.props.FloatVectorProperty(
name="Active",
subtype='COLOR_GAMMA',
default=(0.0, 0.0, 0.0),
size=3,
min=0.0, max=1.0,
description="Color used for active bones",
)
def get_import_options():
return [
("OVERWRITE", "Add/Overwrite", "Add or Overwrite existing item"),
("SKIP", "Skip", "Skip item"),
("RENAME", "Rename", "Rename item"),
]
class ImportItemData(bpy.types.PropertyGroup):
name: bpy.props.StringProperty(
name="Unnamed",
description="The name of the imported item"
)
import_option: bpy.props.EnumProperty(
name="Options",
description="Choose an option",
items=get_import_options(),
default="SKIP"
)
def register():
bpy.utils.register_class(CustomColorSet)
bpy.utils.register_class(BW_Settings)
bpy.types.Scene.bw_settings = bpy.props.PointerProperty(type=BW_Settings)
def unregister():
del bpy.types.Scene.bw_settings
bpy.utils.unregister_class(BW_Settings)
bpy.utils.unregister_class(CustomColorSet)
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,979 @@
# SPDX-FileCopyrightText: 2016-2022 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later
# authors: dudecon, jambay
# This module contains the UI definition, display,
# and processing (create mesh) functions.
# The routines to generate the vertices for the wall
# are found in the "Blocks" module.
import bpy
from bpy.types import Operator
from bpy.props import (
BoolProperty,
FloatProperty,
StringProperty,
)
from .Blocks import (
NOTZERO, PI,
dims,
settings,
shelfSpecs,
stepSpecs,
createWall,
radialized,
slope,
openingSpecs,
bigBlock,
shelfExt,
stepMod,
stepLeft,
shelfBack,
stepOnly,
stepBack,
)
from bpy_extras import object_utils
from .interface import draw_transform_props
class add_mesh_wallb(Operator, object_utils.AddObjectHelper):
bl_idname = "mesh.wall_add"
bl_label = "Add a Masonry Wall"
bl_description = "Create a block (masonry) wall mesh"
bl_options = {'REGISTER', 'UNDO'}
# UI items - API for properties - User accessible variables...
# not all options are via UI, and some operations just don't work yet
Wall : BoolProperty(name = "Wall",
default = True,
description = "Wall")
#### change properties
name : StringProperty(name = "Name",
description = "Name")
change : BoolProperty(name = "Change",
default = False,
description = "change Wall")
# only create object when True
# False allows modifying several parameters without creating object
ConstructTog: BoolProperty(
name="Construct",
description="Generate the object",
default=True
)
# need to modify so radial makes a tower (normal);
# want "flat" setting to make disk (alternate)
# make the wall circular - if not sloped it's a flat disc
RadialTog: BoolProperty(
name="Radial",
description="Make masonry radial",
default=False
)
# curve the wall - if radial creates dome.
SlopeTog: BoolProperty(
name="Curved",
description="Make masonry sloped, or curved",
default=False
)
# need to review defaults and limits for all of these UI objects
# wall area/size
WallStart: FloatProperty(
name="Start",
description="Left side, or start angle",
default=-10.0,
min=-100, max=100.0
)
WallEnd: FloatProperty(
name="End",
description="Right side, or end angle",
default=10.0,
min=0.0, max=100.0
)
WallBottom: FloatProperty(
name="Bottom",
description="Lower height or radius",
default=0.0,
min=-100, max=100
)
WallTop: FloatProperty(
name="Top",
description="Upper height or radius",
default=15.0,
min=0.0, max=100.0
)
EdgeOffset: FloatProperty(
name="Edging",
description="Block staggering on wall sides",
default=0.6, min=0.0, max=100.0
)
# block sizing
Width: FloatProperty(
name="Width",
description="Average width of each block",
default=1.5,
min=0.01, max=100.0
)
WidthVariance: FloatProperty(
name="Variance",
description="Random variance of block width",
default=0.5,
min=0.0, max=100.0
)
WidthMinimum: FloatProperty(
name="Minimum",
description="Absolute minimum block width",
default=0.5,
min=0.01, max=100.0
)
Height: FloatProperty(
name="Height",
description="Average Height of each block",
default=0.7,
min=0.01, max=100.0
)
HeightVariance: FloatProperty(
name="Variance",
description="Random variance of block Height",
default=0.3,
min=0.0, max=100.0
)
HeightMinimum: FloatProperty(
name="Minimum",
description="Absolute minimum block Height",
default=0.25,
min=0.01, max=100.0
)
Depth: FloatProperty(
name="Depth",
description="Average Depth of each block",
default=2.0,
min=0.01, max=100.0
)
DepthVariance: FloatProperty(
name="Variance",
description="Random variance of block Depth",
default=0.1,
min=0.0, max=100.0
)
DepthMinimum: FloatProperty(
name="Minimum",
description="Absolute minimum block Depth",
default=1.0,
min=0.01, max=100.0
)
MergeBlock: BoolProperty(
name="Merge Blocks",
description="Make big blocks (merge closely adjoining blocks)",
default=False
)
# edging for blocks
Grout: FloatProperty(
name="Thickness",
description="Distance between blocks",
default=0.1,
min=-10.0, max=10.0
)
GroutVariance: FloatProperty(
name="Variance",
description="Random variance of block Grout",
default=0.03,
min=0.0, max=100.0
)
GroutDepth: FloatProperty(
name="Depth",
description="Grout Depth from the face of the blocks",
default=0.1,
min=0.0001, max=10.0
)
GroutDepthVariance: FloatProperty(
name="Variance",
description="Random variance of block Grout Depth",
default=0.03,
min=0.0, max=100.0
)
GroutEdge: BoolProperty(
name="Edging",
description="Grout perimiter",
default=False
)
# properties for openings
Opening1Tog: BoolProperty(
name="Opening(s)",
description="Make windows or doors",
default=True
)
Opening1Width: FloatProperty(
name="Width",
description="The Width of the first opening",
default=2.5,
min=0.01, max=100.0
)
Opening1Height: FloatProperty(
name="Height",
description="The Height of the first opening",
default=3.5,
min=0.01, max=100.0
)
Opening1X: FloatProperty(
name="Indent",
description="The x position or spacing of the first opening",
default=5.0,
min=-100, max=100.0
)
Opening1Z: FloatProperty(
name="Bottom",
description="The z position of the First opening",
default=5.0,
min=-100, max=100.0
)
Opening1Repeat: BoolProperty(
name="Repeat",
description="make multiple openings, with spacing X1",
default=False
)
Opening1TopArchTog: BoolProperty(
name="Top Arch",
description="Add an arch to the top of the first opening",
default=True
)
Opening1TopArch: FloatProperty(
name="Curve",
description="Height of the arch on the top of the opening",
default=2.5,
min=0.001, max=100.0
)
Opening1TopArchThickness: FloatProperty(
name="Thickness",
description="Thickness of the arch on the top of the opening",
default=0.75,
min=0.001, max=100.0
)
Opening1BtmArchTog: BoolProperty(
name="Bottom Arch",
description="Add an arch to the bottom of opening 1",
default=False
)
Opening1BtmArch: FloatProperty(
name="Curve",
description="Height of the arch on the bottom of the opening",
default=1.0,
min=0.01, max=100.0
)
Opening1BtmArchThickness: FloatProperty(
name="Thickness",
description="Thickness of the arch on the bottom of the opening",
default=0.5,
min=0.01, max=100.0
)
Opening1Bevel: FloatProperty(
name="Bevel",
description="Angle block face",
default=0.25,
min=-10.0, max=10.0
)
# openings on top of wall
CrenelTog: BoolProperty(
name="Crenels",
description="Make openings along top of wall",
default=False
)
CrenelXP: FloatProperty(
name="Width",
description="Gap width in wall based the percentage of wall width",
default=0.25,
min=0.10, max=1.0,
subtype="PERCENTAGE"
)
CrenelZP: FloatProperty(
name="Height",
description="Crenel Height as the percentage of wall height",
default=0.10,
min=0.10, max=1.0,
subtype="PERCENTAGE"
)
# narrow openings in wall.
# need to prevent overlap with arch openings - though inversion is an interesting effect.
SlotTog: BoolProperty(
name="Slots",
description="Make narrow openings in wall",
default=False
)
SlotRpt: BoolProperty(
name="Repeat",
description="Repeat slots along wall",
default=False
)
SlotWdg: BoolProperty(
name="Wedged (n/a)",
description="Bevel edges of slots",
default=False
)
SlotX: FloatProperty(
name="Indent",
description="The x position or spacing of slots",
default=0.0, min=-100, max=100.0
)
SlotGap: FloatProperty(
name="Opening",
description="The opening size of slots",
default=0.5, min=0.10, max=100.0
)
SlotV: BoolProperty(
name="Vertical",
description="Vertical slots",
default=True
)
SlotVH: FloatProperty(
name="Height",
description="Height of vertical slot",
default=3.5,
min=0.10, max=100.0
)
SlotVBtm: FloatProperty(
name="Bottom",
description="Z position for slot",
default=5.00,
min=-100.0, max=100.0
)
SlotH: BoolProperty(
name="Horizontal",
description="Horizontal slots",
default=False
)
SlotHW: FloatProperty(
name="Width",
description="Width of horizontal slot",
default=2.5,
min=0.10, max=100.0
)
# this should offset from VBtm... maybe make a % like crenels?
SlotHBtm: FloatProperty(
name="Bottom",
description="Z position for horizontal slot",
default=5.50,
min=-100.0, max=100.0
)
# properties for shelf (extend blocks in area)
ShelfTog: BoolProperty(
name="Shelf",
description="Add blocks in area by depth to make shelf/platform",
default=False
)
ShelfX: FloatProperty(
name="Left",
description="The x position of Shelf",
default=-5.00,
min=-100, max=100.0
)
ShelfZ: FloatProperty(
name="Bottom",
description="The z position of Shelf",
default=10.0,
min=-100, max=100.0
)
ShelfH: FloatProperty(
name="Height",
description="The Height of Shelf area",
default=1.0,
min=0.01, max=100.0
)
ShelfW: FloatProperty(
name="Width",
description="The Width of shelf area",
default=5.0,
min=0.01, max=100.0
)
ShelfD: FloatProperty(
name="Depth",
description="Depth of each block for shelf (from cursor + 1/2 wall depth)",
default=2.0,
min=0.01, max=100.0
)
ShelfBack: BoolProperty(
name="Backside",
description="Shelf on backside of wall",
default=False
)
# properties for steps (extend blocks in area, progressive width)
StepTog: BoolProperty(
name="Steps",
description="Add blocks in area by depth with progressive width to make steps",
default=False
)
StepX: FloatProperty(
name="Left",
description="The x position of steps",
default=-9.00,
min=-100, max=100.0
)
StepZ: FloatProperty(
name="Bottom",
description="The z position of steps",
default=0.0,
min=-100, max=100.0
)
StepH: FloatProperty(
name="Height",
description="The Height of step area",
default=10.0,
min=0.01, max=100.0
)
StepW: FloatProperty(
name="Width",
description="The Width of step area",
default=8.0,
min=0.01, max=100.0
)
StepD: FloatProperty(
name="Depth",
description="Depth of each block for steps (from cursor + 1/2 wall depth)",
default=1.0,
min=0.01, max=100.0
)
StepV: FloatProperty(
name="Riser",
description="Height of each step",
default=0.70,
min=0.01, max=100.0
)
StepT: FloatProperty(
name="Tread",
description="Width of each step",
default=1.0,
min=0.01, max=100.0
)
StepLeft: BoolProperty(
name="Direction",
description="If checked, flip steps direction towards the -X axis",
default=False
)
StepOnly: BoolProperty(
name="Steps Only",
description="Steps only, no supporting blocks",
default=False
)
StepBack: BoolProperty(
name="Backside",
description="Steps on backside of wall",
default=False
)
# Display the toolbox options
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
# Wall shape modifiers
layout.prop(self, 'ConstructTog')
# Wall area (size/position)
header, panel = layout.panel("WALLFACTORY_PT_AREA", default_closed=False)
header.label(text="Wall Size")
if panel:
panel.prop(self, "RadialTog")
panel.prop(self, "SlopeTog")
col = panel.column(align=True)
col.prop(self, "WallStart")
col.prop(self, "WallEnd")
col = panel.column(align=True)
col.prop(self, "WallBottom")
col.prop(self, "WallTop")
panel.prop(self, "EdgeOffset")
# Wall block sizing
header, panel = layout.panel("WALLFACTORY_PT_BLOCKS", default_closed=False)
header.label(text="Block Size")
if panel:
panel.prop(self, "MergeBlock")
# add checkbox for "fixed" sizing (ignore variance) a.k.a. bricks
col = panel.column(align=True)
col.prop(self, "Width")
col.prop(self, "WidthVariance")
col.prop(self, "WidthMinimum")
col = panel.column(align=True)
col.prop(self, "Height")
col.prop(self, "HeightVariance")
col.prop(self, "HeightMinimum")
col = panel.column(align=True)
col.prop(self, "Depth")
col.prop(self, "DepthVariance")
col.prop(self, "DepthMinimum")
# grout settings
header, panel = layout.panel("WALLFACTORY_PT_GROUT", default_closed=True)
header.label(text="Grout")
if panel:
col = panel.column(align=True)
col.prop(self, "Grout")
col.prop(self, "GroutVariance")
col = panel.column(align=True)
col.prop(self, "GroutDepth")
col.prop(self, "GroutDepthVariance")
# Openings (doors, windows; arched)
header, panel = layout.panel("WALLFACTORY_PT_OPENINGS", default_closed=True)
header.use_property_split = False
header.prop(self, 'Opening1Tog', text='')
header.label(text="Openings")
if panel:
openings_col = panel.column()
openings_col.enabled = self.Opening1Tog
openings_col.use_property_split = True
col = openings_col.column(align=True)
col.prop(self, "Opening1Width")
col.prop(self, "Opening1Height")
col.prop(self, "Opening1X")
col.prop(self, "Opening1Z")
col.prop(self, "Opening1Bevel")
openings_col.prop(self, "Opening1Repeat", toggle=True)
openings_col.prop(self, "Opening1TopArchTog")
col = openings_col.column(align=True)
col.enabled = self.Opening1TopArchTog
col.prop(self, "Opening1TopArch")
col.prop(self, "Opening1TopArchThickness")
openings_col.prop(self, "Opening1BtmArchTog")
col = openings_col.column(align=True)
col.enabled = self.Opening1BtmArchTog
col.prop(self, "Opening1BtmArch")
col.prop(self, "Opening1BtmArchThickness")
# Slots (narrow openings)
header, panel = layout.panel("WALLFACTORY_PT_SLOTS", default_closed=True)
header.use_property_split = False
header.prop(self, 'SlotTog', text='')
header.label(text="Slots")
if panel:
panel.enabled = self.SlotTog
panel.prop(self, "SlotRpt")
col = panel.column(align=True)
col.prop(self, "SlotX")
col.prop(self, "SlotGap")
panel.prop(self, "SlotV")
col = panel.column(align=True)
col.enabled = self.SlotV
col.prop(self, "SlotVH")
col.prop(self, "SlotVBtm")
panel.prop(self, "SlotH")
col = panel.column(align=True)
col.enabled = self.SlotH
col.prop(self, "SlotHW")
col.prop(self, "SlotHBtm")
# Crenels, gaps in top of wall
header, panel = layout.panel("WALLFACTORY_PT_CRENELS", default_closed=True)
header.use_property_split = False
header.prop(self, 'CrenelTog', text='')
header.label(text="Crenels")
if panel:
panel.enabled = self.CrenelTog
col = panel.column(align=True)
col.prop(self, "CrenelXP")
col.prop(self, "CrenelZP")
# Shelfing (protrusions)
header, panel = layout.panel("WALLFACTORY_PT_SHELF", default_closed=True)
header.use_property_split = False
header.prop(self, 'ShelfTog', text='')
header.label(text="Shelf")
if panel:
panel.enabled = self.ShelfTog
col = panel.column(align=True)
col.prop(self, "ShelfX")
col.prop(self, "ShelfZ")
col = panel.column(align=True)
col.prop(self, "ShelfW")
col.prop(self, "ShelfH")
col.prop(self, "ShelfD")
panel.prop(self, "ShelfBack")
# Steps
header, panel = layout.panel("WALLFACTORY_PT_STEPS", default_closed=True)
header.use_property_split = False
header.prop(self, 'StepTog', text='')
header.label(text="Steps")
if panel:
panel.enabled = self.StepTog
col = panel.column(align=True)
col.prop(self, "StepX")
col.prop(self, "StepZ")
col = panel.column(align=True)
col.prop(self, "StepH")
col.prop(self, "StepW")
col.prop(self, "StepD")
col = panel.column(align=True)
col.prop(self, "StepV")
col.prop(self, "StepT")
col = panel.column(align=True)
panel.prop(self, "StepLeft")
panel.prop(self, "StepOnly")
panel.prop(self, "StepBack")
if self.change == False:
header, panel = layout.panel("WALLFACTORY_PT_TRANSFORM", default_closed=True)
header.label(text="Transform")
if panel:
draw_transform_props(self, panel)
# Respond to UI - get the properties set by user.
# Check and process UI settings to generate masonry
def execute(self, context):
global radialized
global slope
global openingSpecs
global bigBlock
global shelfExt
global stepMod
global stepLeft
global shelfBack
global stepOnly
global stepBack
# Create the wall when enabled (skip regen iterations when off)
if not self.ConstructTog:
return {'FINISHED'}
# turn off 'Enter Edit Mode'
use_enter_edit_mode = bpy.context.preferences.edit.use_enter_edit_mode
bpy.context.preferences.edit.use_enter_edit_mode = False
# enter the settings for the wall dimensions (area)
# start can't be zero - min/max don't matter [if max less than end] but zero don't workie.
# start can't exceed end.
if not self.WallStart or self.WallStart >= self.WallEnd:
self.WallStart = NOTZERO # Reset UI if input out of bounds...
dims['s'] = self.WallStart
dims['e'] = self.WallEnd
dims['b'] = self.WallBottom
dims['t'] = self.WallTop
settings['eoff'] = self.EdgeOffset
# retrieve the settings for the wall block properties
settings['w'] = self.Width
settings['wv'] = self.WidthVariance
settings['wm'] = self.WidthMinimum
if not radialized:
settings['sdv'] = settings['w']
else:
settings['sdv'] = 0.12
settings['h'] = self.Height
settings['hv'] = self.HeightVariance
settings['hm'] = self.HeightMinimum
settings['d'] = self.Depth
settings['dv'] = self.DepthVariance
settings['dm'] = self.DepthMinimum
if self.MergeBlock:
bigBlock = 1
else:
bigBlock = 0
settings['g'] = self.Grout
settings['gv'] = self.GroutVariance
settings['gd'] = self.GroutDepth
settings['gdv'] = self.GroutDepthVariance
if self.GroutEdge:
settings['ge'] = 1
else:
settings['ge'] = 0
# set wall shape modifiers
if self.RadialTog:
radialized = 1
# eliminate to allow user control for start/completion?
dims['s'] = 0.0 # complete radial
if dims['e'] > PI * 2:
dims['e'] = PI * 2 # max end for circle
if dims['b'] < settings['g']:
dims['b'] = settings['g'] # min bottom for grout extension
else:
radialized = 0
if self.SlopeTog:
slope = 1
else:
slope = 0
shelfExt = 0
shelfBack = 0
# Add shelf if enabled
if self.ShelfTog:
shelfExt = 1
shelfSpecs['h'] = self.ShelfH
shelfSpecs['w'] = self.ShelfW
shelfSpecs['d'] = self.ShelfD
shelfSpecs['x'] = self.ShelfX
shelfSpecs['z'] = self.ShelfZ
if self.ShelfBack:
shelfBack = 1
stepMod = 0
stepLeft = 0
stepOnly = 0
stepBack = 0
# Make steps if enabled
if self.StepTog:
stepMod = 1
stepSpecs['x'] = self.StepX
stepSpecs['z'] = self.StepZ
stepSpecs['h'] = self.StepH
stepSpecs['w'] = self.StepW
stepSpecs['d'] = self.StepD
stepSpecs['v'] = self.StepV
stepSpecs['t'] = self.StepT
if self.StepLeft:
stepLeft = 1
if self.StepOnly:
stepOnly = 1
if self.StepBack:
stepBack = 1
# enter the settings for the openings
# when openings overlap they create inverse stonework - interesting but not the desired effect :)
# if opening width == indent * 2 the edge blocks fail (row of blocks cross opening) - bug.
openingSpecs = []
openingIdx = 0 # track opening array references for multiple uses
# general openings with arch options - can be windows or doors.
if self.Opening1Tog:
# set defaults...
openingSpecs += [{'w': 0.5, 'h': 0.5, 'x': 0.8, 'z': 2.7, 'rp': 1,
'b': 0.0, 'v': 0, 'vl': 0, 't': 0, 'tl': 0}]
openingSpecs[openingIdx]['w'] = self.Opening1Width
openingSpecs[openingIdx]['h'] = self.Opening1Height
openingSpecs[openingIdx]['x'] = self.Opening1X
openingSpecs[openingIdx]['z'] = self.Opening1Z
openingSpecs[openingIdx]['rp'] = self.Opening1Repeat
if self.Opening1TopArchTog:
openingSpecs[openingIdx]['v'] = self.Opening1TopArch
openingSpecs[openingIdx]['t'] = self.Opening1TopArchThickness
if self.Opening1BtmArchTog:
openingSpecs[openingIdx]['vl'] = self.Opening1BtmArch
openingSpecs[openingIdx]['tl'] = self.Opening1BtmArchThickness
openingSpecs[openingIdx]['b'] = self.Opening1Bevel
openingIdx += 1 # count window/door/arch openings
# Slots (narrow openings)
if self.SlotTog:
if self.SlotV: # vertical slots
# set defaults...
openingSpecs += [{'w': 0.5, 'h': 0.5, 'x': 0.0, 'z': 2.7, 'rp': 0,
'b': 0.0, 'v': 0, 'vl': 0, 't': 0, 'tl': 0}]
openingSpecs[openingIdx]['w'] = self.SlotGap
openingSpecs[openingIdx]['h'] = self.SlotVH
openingSpecs[openingIdx]['x'] = self.SlotX
openingSpecs[openingIdx]['z'] = self.SlotVBtm
openingSpecs[openingIdx]['rp'] = self.SlotRpt
# make them pointy...
openingSpecs[openingIdx]['v'] = self.SlotGap
openingSpecs[openingIdx]['t'] = self.SlotGap / 2
openingSpecs[openingIdx]['vl'] = self.SlotGap
openingSpecs[openingIdx]['tl'] = self.SlotGap / 2
openingIdx += 1 # count vertical slot openings
# need to handle overlap of H and V slots...
if self.SlotH: # Horizontal slots
# set defaults...
openingSpecs += [{'w': 0.5, 'h': 0.5, 'x': 0.0, 'z': 2.7, 'rp': 0,
'b': 0.0, 'v': 0, 'vl': 0, 't': 0, 'tl': 0}]
openingSpecs[openingIdx]['w'] = self.SlotHW
openingSpecs[openingIdx]['h'] = self.SlotGap
openingSpecs[openingIdx]['x'] = self.SlotX
openingSpecs[openingIdx]['z'] = self.SlotHBtm
# horizontal repeat isn't same spacing as vertical...
openingSpecs[openingIdx]['rp'] = self.SlotRpt
# make them pointy...
openingIdx += 1 # count horizontal slot openings
# Crenellations (top row openings)
if self.CrenelTog:
# add bottom arch option?
# perhaps a repeat toggle...
# if crenel opening overlaps with arch opening it fills with blocks...
# set defaults...
openingSpecs += [{'w': 0.5, 'h': 0.5, 'x': 0.0, 'z': 2.7, 'rp': 1,
'b': 0.0, 'v': 0, 'vl': 0, 't': 0, 'tl': 0}]
wallW = self.WallEnd - self.WallStart
crenelW = wallW * self.CrenelXP # Width % opening.
wallH = self.WallTop - self.WallBottom
crenelH = wallH * self.CrenelZP # % proportional height.
openingSpecs[openingIdx]['w'] = crenelW
openingSpecs[openingIdx]['h'] = crenelH
# calculate the spacing between openings.
# this isn't the absolute start (left),
# it's opening center offset relative to cursor (space between openings)...
openingSpecs[openingIdx]['x'] = crenelW * 2 - 1 # assume standard spacing
if not radialized: # normal wall?
# set indent 0 (center) if opening is 50% or more of wall width, no repeat.
if crenelW * 2 >= wallW:
openingSpecs[openingIdx]['x'] = 0
openingSpecs[openingIdx]['rp'] = 0
# set bottom of opening (center of hole)
openingSpecs[openingIdx]['z'] = self.WallTop - (crenelH / 2)
openingIdx += 1 # count crenel openings
# Process the user settings to generate a wall
# generate the list of vertices for the wall...
verts_array, faces_array = createWall(
radialized, slope, openingSpecs, bigBlock,
shelfExt, shelfBack, stepMod, stepLeft, stepOnly,
stepBack
)
if bpy.context.mode == "OBJECT":
if context.selected_objects != [] and context.active_object and \
(context.active_object.data is not None) and ('Wall' in context.active_object.data.keys()) and \
(self.change == True):
obj = context.active_object
oldmesh = obj.data
oldmeshname = obj.data.name
mesh = bpy.data.meshes.new("Wall")
mesh.from_pydata(verts_array, [], faces_array)
obj.data = mesh
for material in oldmesh.materials:
obj.data.materials.append(material)
bpy.data.meshes.remove(oldmesh)
obj.data.name = oldmeshname
else:
mesh = bpy.data.meshes.new("Wall")
mesh.from_pydata(verts_array, [], faces_array)
obj = object_utils.object_data_add(context, mesh, operator=self)
mesh.update()
obj.data["Wall"] = True
obj.data["change"] = False
for prm in WallParameters():
obj.data[prm] = getattr(self, prm)
if bpy.context.mode == "EDIT_MESH":
active_object = context.active_object
name_active_object = active_object.name
bpy.ops.object.mode_set(mode='OBJECT')
mesh = bpy.data.meshes.new("TMP")
mesh.from_pydata(verts_array, [], faces_array)
obj = object_utils.object_data_add(context, mesh, operator=self)
obj.select_set(True)
active_object.select_set(True)
bpy.context.view_layer.objects.active = active_object
bpy.ops.object.join()
context.active_object.name = name_active_object
bpy.ops.object.mode_set(mode='EDIT')
if use_enter_edit_mode:
bpy.ops.object.mode_set(mode = 'EDIT')
# restore pre operator state
bpy.context.preferences.edit.use_enter_edit_mode = use_enter_edit_mode
return {'FINISHED'}
def WallParameters():
WallParameters = [
"ConstructTog",
"RadialTog",
"SlopeTog",
"WallStart",
"WallEnd",
"WallBottom",
"WallTop",
"EdgeOffset",
"Width",
"WidthVariance",
"WidthMinimum",
"Height",
"HeightVariance",
"HeightMinimum",
"Depth",
"DepthVariance",
"DepthMinimum",
"MergeBlock",
"Grout",
"GroutVariance",
"GroutDepth",
"GroutDepthVariance",
"GroutEdge",
"Opening1Tog",
"Opening1Width",
"Opening1Height",
"Opening1X",
"Opening1Z",
"Opening1Repeat",
"Opening1TopArchTog",
"Opening1TopArch",
"Opening1TopArchThickness",
"Opening1BtmArchTog",
"Opening1BtmArch",
"Opening1BtmArchThickness",
"CrenelTog",
"CrenelXP",
"CrenelZP",
"SlotTog",
"SlotRpt",
"SlotWdg",
"SlotX",
"SlotGap",
"SlotV",
"SlotVH",
"SlotVBtm",
"SlotH",
"SlotHW",
"SlotHBtm",
"ShelfTog",
"ShelfX",
"ShelfZ",
"ShelfH",
"ShelfW",
"ShelfD",
"ShelfBack",
"StepTog",
"StepX",
"StepZ",
"StepH",
"StepW",
"StepD",
"StepV",
"StepT",
"StepLeft",
"StepOnly",
"StepBack",
]
return WallParameters
@@ -0,0 +1,460 @@
# SPDX-FileCopyrightText: 2011-2023 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later
# Contributed to by:
# Pontiac, Fourmadmen, varkenvarken, tuga3d, meta-androcto, metalliandy #
# dreampainter, cotejrp1, liero, Kayo Phoenix, sugiany, dommetysk, Jambay #
# Phymec, Anthony D'Agostino, Pablo Vazquez, Richard Wilks, lijenstina, #
# Sjaak-de-Draak, Phil Cote, cotejrp1, xyz presets by elfnor, revolt_randy, #
# Vladimir Spivak (cwolf3d), Jonathan Lampel #
# Note: Blocks has to be loaded before the WallFactory or the script
# will not work properly after (F8) reload
if "bpy" in locals():
import importlib
importlib.reload(add_mesh_star)
importlib.reload(add_mesh_twisted_torus)
importlib.reload(add_mesh_gemstones)
importlib.reload(add_mesh_gears)
importlib.reload(add_mesh_3d_function_surface)
importlib.reload(add_mesh_round_cube)
importlib.reload(add_mesh_supertoroid)
importlib.reload(add_mesh_pyramid)
importlib.reload(add_mesh_torusknot)
importlib.reload(add_mesh_honeycomb)
importlib.reload(add_mesh_teapot)
importlib.reload(add_mesh_pipe_joint)
importlib.reload(add_mesh_solid)
importlib.reload(add_mesh_round_brilliant)
importlib.reload(add_mesh_menger_sponge)
importlib.reload(add_mesh_vertex)
importlib.reload(add_empty_as_parent)
importlib.reload(add_mesh_beam_builder)
importlib.reload(Blocks)
importlib.reload(Wallfactory)
importlib.reload(add_mesh_triangles)
importlib.reload(preferences)
else:
from . import add_mesh_star
from . import add_mesh_twisted_torus
from . import add_mesh_gemstones
from . import add_mesh_gears
from . import add_mesh_3d_function_surface
from . import add_mesh_round_cube
from . import add_mesh_supertoroid
from . import add_mesh_pyramid
from . import add_mesh_torusknot
from . import add_mesh_honeycomb
from . import add_mesh_teapot
from . import add_mesh_pipe_joint
from . import add_mesh_solid
from . import add_mesh_round_brilliant
from . import add_mesh_menger_sponge
from . import add_mesh_vertex
from . import add_empty_as_parent
from . import add_mesh_beam_builder
from . import Blocks
from . import Wallfactory
from . import add_mesh_triangles
from . import preferences
from .add_mesh_rocks import __init__
from .add_mesh_rocks import rockgen
import bpy
from bpy.types import Menu
class VIEW3D_MT_mesh_vert_add(Menu):
# Define the "Single Vert" menu
bl_idname = "VIEW3D_MT_mesh_vert_add"
bl_label = "Single Vert"
def draw(self, context):
layout = self.layout
layout.operator_context = 'INVOKE_REGION_WIN'
layout.operator("mesh.primitive_vert_add",
text="Add Single Vert")
layout.separator()
layout.operator("mesh.primitive_emptyvert_add",
text="Object Origin Only")
layout.operator("mesh.primitive_symmetrical_vert_add",
text="Origin & Vert Mirrored")
layout.operator("mesh.primitive_symmetrical_empty_add",
text="Object Origin Mirrored")
class VIEW3D_MT_mesh_gears_add(Menu):
# Define the "Gears" menu
bl_idname = "VIEW3D_MT_mesh_gears_add"
bl_label = "Gears"
def draw(self, context):
layout = self.layout
layout.operator_context = 'INVOKE_REGION_WIN'
oper = layout.operator("mesh.primitive_gear", text="Gear")
oper.change = False
oper = layout.operator("mesh.primitive_worm_gear", text="Worm")
oper.change = False
class VIEW3D_MT_mesh_gemstones_add(Menu):
# Define the "Gemstones" menu
bl_idname = "VIEW3D_MT_mesh_gemstones_add"
bl_label = "Gemstones"
def draw(self, context):
layout = self.layout
layout.operator_context = 'INVOKE_REGION_WIN'
oper = layout.operator("mesh.primitive_brilliant_add", text="Brilliant")
oper.change = False
oper = layout.operator("mesh.primitive_diamond_add", text="Diamond")
oper.change = False
oper = layout.operator("mesh.primitive_gem_add", text="Gem")
oper.change = False
class VIEW3D_MT_mesh_math_add(Menu):
# Define the "Math Function" menu
bl_idname = "VIEW3D_MT_mesh_math_add"
bl_label = "Math Functions"
def draw(self, context):
layout = self.layout
layout.operator_context = 'INVOKE_REGION_WIN'
layout.operator("mesh.primitive_z_function_surface",
text="Z Math Surface")
layout.operator("mesh.primitive_xyz_function_surface",
text="XYZ Math Surface")
self.layout.operator("mesh.primitive_solid_add", text="Regular Solid")
self.layout.operator("mesh.make_triangle", text="Triangle")
class VIEW3D_MT_mesh_extras_add(Menu):
# Define the "Extra Objects" menu
bl_idname = "VIEW3D_MT_mesh_extras_add"
bl_label = "Extras"
def draw(self, context):
layout = self.layout
layout.operator_context = 'INVOKE_REGION_WIN'
oper = layout.operator("mesh.add_mesh_rock", text="Rock Generator")
oper = layout.operator("mesh.add_beam", text="Beam Builder")
oper.change = False
oper = layout.operator("mesh.wall_add", text="Wall Factory")
oper.change = False
layout.separator()
oper = layout.operator("mesh.primitive_star_add", text="Simple Star")
oper.change = False
oper = layout.operator("mesh.primitive_steppyramid_add", text="Step Pyramid")
oper.change = False
oper = layout.operator("mesh.honeycomb_add", text="Honeycomb")
oper.change = False
oper = layout.operator("mesh.primitive_teapot_add", text="Teapot+")
oper = layout.operator("mesh.menger_sponge_add", text="Menger Sponge")
class VIEW3D_MT_mesh_torus_add(Menu):
# Define the "Torus Objects" menu
bl_idname = "VIEW3D_MT_mesh_torus_add"
bl_label = "Torus Objects"
def draw(self, context):
layout = self.layout
layout.operator_context = 'INVOKE_REGION_WIN'
oper = layout.operator("mesh.primitive_twisted_torus_add", text="Twisted Torus")
oper.change = False
oper = layout.operator("mesh.primitive_supertoroid_add", text="Supertoroid")
oper.change = False
oper = layout.operator("mesh.primitive_torusknot_add", text="Torus Knot")
oper.change = False
class VIEW3D_MT_mesh_pipe_joints_add(Menu):
# Define the "Pipe Joints" menu
bl_idname = "VIEW3D_MT_mesh_pipe_joints_add"
bl_label = "Pipe Joints"
def draw(self, context):
layout = self.layout
layout.operator_context = 'INVOKE_REGION_WIN'
oper = layout.operator("mesh.primitive_elbow_joint_add", text="Elbow")
oper.change = False
oper = layout.operator("mesh.primitive_tee_joint_add", text="T-Joint")
oper.change = False
oper = layout.operator("mesh.primitive_wye_joint_add", text="Y-Joint")
oper.change = False
oper = layout.operator("mesh.primitive_cross_joint_add", text="Cross-Joint")
oper.change = False
oper = layout.operator("mesh.primitive_n_joint_add", text="N-Joint")
oper.change = False
# Register all operators and panels
# Define "Extras" menu
def menu_func(self, context):
layout = self.layout
layout.operator_context = 'INVOKE_REGION_WIN'
prefs = bpy.context.preferences.addons[__package__].preferences
if prefs.show_round_cube:
oper = layout.operator("mesh.primitive_round_cube_add", text="Round Cube", icon='SPHERE')
oper.change = False
layout.separator()
if prefs.show_single_vert:
layout.menu("VIEW3D_MT_mesh_vert_add", text="Single Vert", icon='DECORATE')
if prefs.show_torus_objects:
layout.menu("VIEW3D_MT_mesh_torus_add", text="Torus Objects", icon='MESH_TORUS')
if prefs.show_math_functions:
layout.menu("VIEW3D_MT_mesh_math_add", text="Math Functions", icon='GRAPH')
if prefs.show_gears:
layout.menu("VIEW3D_MT_mesh_gears_add", text="Gears", icon='PREFERENCES')
if prefs.show_pipe_joints:
layout.menu("VIEW3D_MT_mesh_pipe_joints_add", text="Pipe Joints", icon='IPO_CONSTANT')
if prefs.show_gemstones:
layout.menu("VIEW3D_MT_mesh_gemstones_add", text="Gemstones", icon="MESH_ICOSPHERE")
if prefs.show_extras:
layout.menu("VIEW3D_MT_mesh_extras_add", text="Extras", icon="PACKAGE")
if prefs.show_parent_to_empty:
layout.separator()
layout.operator("object.parent_to_empty", text="Parent to Empty", icon="OUTLINER_OB_EMPTY")
def Extras_contex_menu(self, context):
bl_label = 'Change'
obj = context.object
layout = self.layout
if obj is None or obj.data is None:
return
if 'Gear' in obj.data.keys():
props = layout.operator("mesh.primitive_gear", text="Change Gear")
props.change = True
for prm in add_mesh_gears.GearParameters():
setattr(props, prm, obj.data[prm])
layout.separator()
if 'WormGear' in obj.data.keys():
props = layout.operator("mesh.primitive_worm_gear", text="Change WormGear")
props.change = True
for prm in add_mesh_gears.WormGearParameters():
setattr(props, prm, obj.data[prm])
layout.separator()
if 'Beam' in obj.data.keys():
props = layout.operator("mesh.add_beam", text="Change Beam")
props.change = True
for prm in add_mesh_beam_builder.BeamParameters():
setattr(props, prm, obj.data[prm])
layout.separator()
if 'Wall' in obj.data.keys():
props = layout.operator("mesh.wall_add", text="Change Wall")
props.change = True
for prm in Wallfactory.WallParameters():
setattr(props, prm, obj.data[prm])
layout.separator()
if 'ElbowJoint' in obj.data.keys():
props = layout.operator("mesh.primitive_elbow_joint_add", text="Change ElbowJoint")
props.change = True
for prm in add_mesh_pipe_joint.ElbowJointParameters():
setattr(props, prm, obj.data[prm])
layout.separator()
if 'TeeJoint' in obj.data.keys():
props = layout.operator("mesh.primitive_tee_joint_add", text="Change TeeJoint")
props.change = True
for prm in add_mesh_pipe_joint.TeeJointParameters():
setattr(props, prm, obj.data[prm])
layout.separator()
if 'WyeJoint' in obj.data.keys():
props = layout.operator("mesh.primitive_wye_joint_add", text="Change WyeJoint")
props.change = True
for prm in add_mesh_pipe_joint.WyeJointParameters():
setattr(props, prm, obj.data[prm])
layout.separator()
if 'CrossJoint' in obj.data.keys():
props = layout.operator("mesh.primitive_cross_joint_add", text="Change CrossJoint")
props.change = True
for prm in add_mesh_pipe_joint.CrossJointParameters():
setattr(props, prm, obj.data[prm])
layout.separator()
if 'NJoint' in obj.data.keys():
props = layout.operator("mesh.primitive_n_joint_add", text="Change NJoint")
props.change = True
for prm in add_mesh_pipe_joint.NJointParameters():
setattr(props, prm, obj.data[prm])
layout.separator()
if 'Diamond' in obj.data.keys():
props = layout.operator("mesh.primitive_diamond_add", text="Change Diamond")
props.change = True
for prm in add_mesh_gemstones.DiamondParameters():
setattr(props, prm, obj.data[prm])
layout.separator()
if 'Gem' in obj.data.keys():
props = layout.operator("mesh.primitive_gem_add", text="Change Gem")
props.change = True
for prm in add_mesh_gemstones.GemParameters():
setattr(props, prm, obj.data[prm])
layout.separator()
if 'Brilliant' in obj.data.keys():
props = layout.operator("mesh.primitive_brilliant_add", text="Change Brilliant")
props.change = True
for prm in add_mesh_round_brilliant.BrilliantParameters():
setattr(props, prm, obj.data[prm])
layout.separator()
if 'Roundcube' in obj.data.keys():
props = layout.operator("mesh.primitive_round_cube_add", text="Change Roundcube")
props.change = True
for prm in add_mesh_round_cube.RoundCubeParameters():
setattr(props, prm, obj.data[prm])
layout.separator()
if 'TorusKnot' in obj.data.keys():
props = layout.operator("mesh.primitive_torusknot_add", text="Change TorusKnot")
props.change = True
for prm in add_mesh_torusknot.TorusKnotParameters():
setattr(props, prm, obj.data[prm])
layout.separator()
if 'SuperToroid' in obj.data.keys():
props = layout.operator("mesh.primitive_supertoroid_add", text="Change SuperToroid")
props.change = True
for prm in add_mesh_supertoroid.SuperToroidParameters():
setattr(props, prm, obj.data[prm])
layout.separator()
if 'TwistedTorus' in obj.data.keys():
props = layout.operator("mesh.primitive_twisted_torus_add", text="Change TwistedTorus")
props.change = True
for prm in add_mesh_twisted_torus.TwistedTorusParameters():
setattr(props, prm, obj.data[prm])
layout.separator()
if 'Star' in obj.data.keys():
props = layout.operator("mesh.primitive_star_add", text="Change Star")
props.change = True
for prm in add_mesh_star.StarParameters():
setattr(props, prm, obj.data[prm])
layout.separator()
if 'Pyramid' in obj.data.keys():
props = layout.operator("mesh.primitive_steppyramid_add", text="Change Pyramid")
props.change = True
for prm in add_mesh_pyramid.PyramidParameters():
setattr(props, prm, obj.data[prm])
layout.separator()
if 'HoneyComb' in obj.data.keys():
props = layout.operator("mesh.honeycomb_add", text="Change HoneyComb")
props.change = True
for prm in add_mesh_honeycomb.HoneyCombParameters():
setattr(props, prm, obj.data[prm])
layout.separator()
# Register
classes = [
VIEW3D_MT_mesh_vert_add,
VIEW3D_MT_mesh_gears_add,
VIEW3D_MT_mesh_gemstones_add,
VIEW3D_MT_mesh_math_add,
VIEW3D_MT_mesh_extras_add,
VIEW3D_MT_mesh_torus_add,
VIEW3D_MT_mesh_pipe_joints_add,
add_mesh_star.AddStar,
add_mesh_twisted_torus.AddTwistedTorus,
add_mesh_gemstones.AddDiamond,
add_mesh_gemstones.AddGem,
add_mesh_gears.AddGear,
add_mesh_gears.AddWormGear,
add_mesh_3d_function_surface.AddZFunctionSurface,
add_mesh_3d_function_surface.AddXYZFunctionSurface,
add_mesh_round_cube.AddRoundCube,
add_mesh_supertoroid.add_supertoroid,
add_mesh_pyramid.AddPyramid,
add_mesh_torusknot.AddTorusKnot,
add_mesh_honeycomb.add_mesh_honeycomb,
add_mesh_teapot.AddTeapot,
add_mesh_pipe_joint.AddElbowJoint,
add_mesh_pipe_joint.AddTeeJoint,
add_mesh_pipe_joint.AddWyeJoint,
add_mesh_pipe_joint.AddCrossJoint,
add_mesh_pipe_joint.AddNJoint,
add_mesh_solid.Solids,
add_mesh_round_brilliant.MESH_OT_primitive_brilliant_add,
add_mesh_menger_sponge.AddMengerSponge,
add_mesh_vertex.AddVert,
add_mesh_vertex.AddEmptyVert,
add_mesh_vertex.AddSymmetricalEmpty,
add_mesh_vertex.AddSymmetricalVert,
add_empty_as_parent.P2E,
add_empty_as_parent.PreFix,
add_mesh_beam_builder.addBeam,
Wallfactory.add_mesh_wallb,
add_mesh_triangles.MakeTriangle,
preferences.AddMeshExtraObjectsPreferences,
]
def register():
import os
from bpy.utils import register_class
for cls in classes:
register_class(cls)
add_mesh_rocks.register()
# Add "Extras" menu to the "Add Mesh" menu and context menu.
bpy.types.VIEW3D_MT_mesh_add.append(menu_func)
bpy.types.VIEW3D_MT_object_context_menu.prepend(Extras_contex_menu)
# Part of 4.3 may be back-ported to 4.2.
if register_preset_path := getattr(bpy.utils, "register_preset_path", None):
register_preset_path(os.path.join(os.path.dirname(__file__)))
def unregister():
import os
# Remove "Extras" menu from the "Add Mesh" menu and context menu.
bpy.types.VIEW3D_MT_object_context_menu.remove(Extras_contex_menu)
bpy.types.VIEW3D_MT_mesh_add.remove(menu_func)
from bpy.utils import unregister_class
for cls in reversed(classes):
unregister_class(cls)
add_mesh_rocks.unregister()
# Part of 4.3 may be back-ported to 4.2.
if unregister_preset_path := getattr(bpy.utils, "unregister_preset_path", None):
unregister_preset_path(os.path.join(os.path.dirname(__file__)))
if __name__ == "__main__":
register()
@@ -0,0 +1,133 @@
# SPDX-FileCopyrightText: 2015-2022 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later
# Original Author Liero
import bpy
from bpy.types import Operator
from bpy.props import (
StringProperty,
BoolProperty,
EnumProperty,
)
def centro(sel):
x = sum([obj.location[0] for obj in sel]) / len(sel)
y = sum([obj.location[1] for obj in sel]) / len(sel)
z = sum([obj.location[2] for obj in sel]) / len(sel)
return (x, y, z)
class P2E(Operator):
bl_idname = "object.parent_to_empty"
bl_label = "Parent to Empty"
bl_description = "Parent selected objects to a new Empty"
bl_options = {"REGISTER", "UNDO"}
nombre: StringProperty(
name="",
default='OBJECTS',
description='Give the empty / group a name'
)
grupo: BoolProperty(
name="Create Group",
default=False,
description="Also add objects to a group"
)
locat: EnumProperty(
name='',
items=[('CURSOR', 'Cursor', 'Cursor'), ('ACTIVE', 'Active', 'Active'),
('CENTER', 'Center', 'Selection Center')],
description='Empty location',
default='CENTER'
)
renom: BoolProperty(
name="Add Prefix",
default=False,
description="Add prefix to objects name"
)
@classmethod
def poll(cls, context):
objs = context.selected_objects
return (len(objs) > 0)
def draw(self, context):
layout = self.layout
layout.prop(self, "nombre")
column = layout.column(align=True)
column.prop(self, "locat")
column.prop(self, "grupo")
column.prop(self, "renom")
def execute(self, context):
objs = context.selected_objects
act = context.object
sce = context.scene
try:
bpy.ops.object.mode_set()
except:
pass
if self.locat == 'CURSOR':
loc = sce.cursor.location
elif self.locat == 'ACTIVE':
loc = act.location
else:
loc = centro(objs)
bpy.ops.object.add(type='EMPTY', location=loc)
context.object.name = self.nombre
context.object.show_name = True
context.object.show_in_front = True
if self.grupo:
bpy.ops.collection.create(name=self.nombre)
bpy.ops.collection.objects_add_active()
for o in objs:
o.select_set(True)
if not o.parent:
bpy.ops.object.parent_set(type='OBJECT')
if self.grupo:
bpy.ops.collection.objects_add_active()
o.select_set(False)
for o in objs:
if self.renom:
o.name = self.nombre + '_' + o.name
return {'FINISHED'}
class PreFix(Operator):
bl_idname = "object.toggle_prefix"
bl_label = "Toggle Sufix"
bl_description = "Toggle parent name as sufix for c"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
act = context.object
return (act and act.type == 'EMPTY')
def execute(self, context):
act = context.object
objs = act.children
prefix = act.name + '_'
remove = False
for o in objs:
if o.name.startswith(prefix):
remove = True
break
if remove is True:
for o in objs:
if o.name.startswith(prefix):
o.name = o.name.partition(prefix)[2]
else:
for o in objs:
o.name = prefix + o.name
return {'FINISHED'}
@@ -0,0 +1,612 @@
# SPDX-FileCopyrightText: 2010-2022 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later
# Original by Buerbaum Martin (Pontiac), Elod Csirmaz
import bpy
import math
import numpy
from mathutils import *
from math import *
from bpy.types import Operator
from bpy.props import (
StringProperty,
IntProperty,
FloatProperty,
BoolProperty,
)
# List of safe functions for eval()
safe_list = ['acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'cosh',
'degrees', 'e', 'exp', 'fabs', 'floor', 'fmod', 'frexp', 'hypot',
'ldexp', 'log', 'log10', 'modf', 'pi', 'pow', 'radians',
'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'gcd']
# Use the list to filter the local namespace
safe_dict = dict((k, globals().get(k, None)) for k in safe_list)
safe_dict['math'] = math
safe_dict['numpy'] = safe_dict['np'] = numpy
safe_dict['lcm'] = numpy.lcm
safe_dict['max'] = max
safe_dict['min'] = min
# Stores the values of a list of properties and the
# operator id in a property group ('recall_op') inside the object
# Could (in theory) be used for non-objects.
# Note: Replaces any existing property group with the same name!
# ob ... Object to store the properties in
# op ... The operator that should be used
# op_args ... A dictionary with valid Blender
# properties (operator arguments/parameters)
# Create a new mesh (object) from verts/edges/faces
# verts/edges/faces ... List of vertices/edges/faces for the
# new mesh (as used in from_pydata)
# name ... Name of the new mesh (& object)
def create_mesh_object(context, verts, edges, faces, name):
# Create new mesh
mesh = bpy.data.meshes.new(name)
# Make a mesh from a list of verts/edges/faces
mesh.from_pydata(verts, edges, faces)
# Update mesh geometry after adding stuff
mesh.update()
from bpy_extras import object_utils
return object_utils.object_data_add(context, mesh, operator=None)
# A very simple "bridge" tool
def createFaces(vertIdx1, vertIdx2, closed=False, flipped=False):
faces = []
if not vertIdx1 or not vertIdx2:
return None
if len(vertIdx1) < 2 and len(vertIdx2) < 2:
return None
fan = False
if (len(vertIdx1) != len(vertIdx2)):
if (len(vertIdx1) == 1 and len(vertIdx2) > 1):
fan = True
else:
return None
total = len(vertIdx2)
if closed:
# Bridge the start with the end
if flipped:
face = [
vertIdx1[0],
vertIdx2[0],
vertIdx2[total - 1]]
if not fan:
face.append(vertIdx1[total - 1])
faces.append(face)
else:
face = [vertIdx2[0], vertIdx1[0]]
if not fan:
face.append(vertIdx1[total - 1])
face.append(vertIdx2[total - 1])
faces.append(face)
# Bridge the rest of the faces
for num in range(total - 1):
if flipped:
if fan:
face = [vertIdx2[num], vertIdx1[0], vertIdx2[num + 1]]
else:
face = [vertIdx2[num], vertIdx1[num],
vertIdx1[num + 1], vertIdx2[num + 1]]
faces.append(face)
else:
if fan:
face = [vertIdx1[0], vertIdx2[num], vertIdx2[num + 1]]
else:
face = [vertIdx1[num], vertIdx2[num],
vertIdx2[num + 1], vertIdx1[num + 1]]
faces.append(face)
return faces
class AddZFunctionSurface(Operator):
bl_idname = "mesh.primitive_z_function_surface"
bl_label = "Add Z Function Surface"
bl_description = "Add a surface defined defined by a function z=f(x,y)"
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
equation: StringProperty(
name="Z Equation",
description="Equation for z=f(x,y)",
default="1 - ( x**2 + y**2 )"
)
div_x: IntProperty(
name="X Subdivisions",
description="Number of vertices in x direction",
default=16,
min=3,
max=256
)
div_y: IntProperty(
name="Y Subdivisions",
description="Number of vertices in y direction",
default=16,
min=3,
max=256
)
size_x: FloatProperty(
name="X Size",
description="Size of the x axis",
default=2.0,
min=0.01,
max=100.0,
unit="LENGTH"
)
size_y: FloatProperty(
name="Y Size",
description="Size of the y axis",
default=2.0,
min=0.01,
max=100.0,
unit="LENGTH"
)
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
layout.separator()
layout.prop(self, 'equation')
col = layout.column(align=True)
col.prop(self, 'div_x', text='Subdivisions X')
col.prop(self, 'div_y', text='Y')
col = layout.column(align=True)
col.prop(self, 'size_x', text='Size X')
col.prop(self, 'size_y', text='Y')
def execute(self, context):
equation = self.equation
div_x = self.div_x
div_y = self.div_y
size_x = self.size_x
size_y = self.size_y
verts = []
faces = []
delta_x = size_x / (div_x - 1)
delta_y = size_y / (div_y - 1)
start_x = -(size_x / 2.0)
start_y = -(size_y / 2.0)
edgeloop_prev = []
if equation:
try:
expr_args = (
compile(equation, __file__, 'eval'),
{"__builtins__": None},
safe_dict)
except:
import traceback
# WARNING is used to prevent the constant pop-up spam
self.report({'WARNING'},
"Error parsing expression: {} "
"(Check the console for more info)".format(equation))
print("\n[Add Z Function Surface]:\n\n", traceback.format_exc(limit=1))
return {'CANCELLED'}
for row_x in range(div_x):
edgeloop_cur = []
x = start_x + row_x * delta_x
for row_y in range(div_y):
y = start_y + row_y * delta_y
z = 0.0
safe_dict['x'] = x
safe_dict['y'] = y
# Try to evaluate the equation.
try:
z = float(eval(*expr_args))
except:
import traceback
self.report({'WARNING'},
"Error evaluating expression: {} "
"(Check the console for more info)".format(equation))
print("\n[Add Z Function Surface]:\n\n", traceback.format_exc(limit=1))
return {'CANCELLED'}
edgeloop_cur.append(len(verts))
verts.append((x, y, z))
if len(edgeloop_prev) > 0:
faces_row = createFaces(edgeloop_prev, edgeloop_cur)
faces.extend(faces_row)
edgeloop_prev = edgeloop_cur
base = create_mesh_object(context, verts, [], faces, "Z Function")
else:
self.report({'WARNING'}, "Z Equation - No expression is given")
return {'CANCELLED'}
return {'FINISHED'}
def xyz_function_surface_faces(self, x_eq, y_eq, z_eq,
range_u_min, range_u_max, range_u_step, wrap_u,
range_v_min, range_v_max, range_v_step, wrap_v,
a_eq, b_eq, c_eq, f_eq, g_eq, h_eq, n, close_v):
verts = []
faces = []
# Distance of each step in Blender Units
uStep = (range_u_max - range_u_min) / range_u_step
vStep = (range_v_max - range_v_min) / range_v_step
# Number of steps in the vertex creation loops.
# Number of steps is the number of faces
# => Number of points is +1 unless wrapped.
uRange = range_u_step + 1
vRange = range_v_step + 1
if wrap_u:
uRange = uRange - 1
if wrap_v:
vRange = vRange - 1
try:
expr_args_x = (
compile(x_eq, __file__.replace(".py", "_x.py"), 'eval'),
{"__builtins__": None},
safe_dict)
expr_args_y = (
compile(y_eq, __file__.replace(".py", "_y.py"), 'eval'),
{"__builtins__": None},
safe_dict)
expr_args_z = (
compile(z_eq, __file__.replace(".py", "_z.py"), 'eval'),
{"__builtins__": None},
safe_dict)
expr_args_a = (
compile(a_eq, __file__.replace(".py", "_a.py"), 'eval'),
{"__builtins__": None},
safe_dict)
expr_args_b = (
compile(b_eq, __file__.replace(".py", "_b.py"), 'eval'),
{"__builtins__": None},
safe_dict)
expr_args_c = (
compile(c_eq, __file__.replace(".py", "_c.py"), 'eval'),
{"__builtins__": None},
safe_dict)
expr_args_f = (
compile(f_eq, __file__.replace(".py", "_f.py"), 'eval'),
{"__builtins__": None},
safe_dict)
expr_args_g = (
compile(g_eq, __file__.replace(".py", "_g.py"), 'eval'),
{"__builtins__": None},
safe_dict)
expr_args_h = (
compile(h_eq, __file__.replace(".py", "_h.py"), 'eval'),
{"__builtins__": None},
safe_dict)
except:
import traceback
self.report({'WARNING'}, "Error parsing expression(s) - "
"Check the console for more info")
print("\n[Add X, Y, Z Function Surface]:\n\n", traceback.format_exc(limit=1))
return [], []
for vN in range(vRange):
v = range_v_min + (vN * vStep)
for uN in range(uRange):
u = range_u_min + (uN * uStep)
safe_dict['u'] = u
safe_dict['v'] = v
safe_dict['n'] = n
# Try to evaluate the equations.
try:
safe_dict['a'] = float(eval(*expr_args_a))
safe_dict['b'] = float(eval(*expr_args_b))
safe_dict['c'] = float(eval(*expr_args_c))
safe_dict['f'] = float(eval(*expr_args_f))
safe_dict['g'] = float(eval(*expr_args_g))
safe_dict['h'] = float(eval(*expr_args_h))
verts.append((
float(eval(*expr_args_x)),
float(eval(*expr_args_y)),
float(eval(*expr_args_z))))
except:
import traceback
self.report({'WARNING'}, "Error evaluating expression(s) - "
"Check the console for more info")
print("\n[Add X, Y, Z Function Surface]:\n\n", traceback.format_exc(limit=1))
return [], []
for vN in range(range_v_step):
vNext = vN + 1
if wrap_v and (vNext >= vRange):
vNext = 0
for uN in range(range_u_step):
uNext = uN + 1
if wrap_u and (uNext >= uRange):
uNext = 0
faces.append([(vNext * uRange) + uNext,
(vNext * uRange) + uN,
(vN * uRange) + uN,
(vN * uRange) + uNext])
if close_v and wrap_u and (not wrap_v):
for uN in range(1, range_u_step - 1):
faces.append([
range_u_step - 1,
range_u_step - 1 - uN,
range_u_step - 2 - uN])
faces.append([
range_v_step * uRange,
range_v_step * uRange + uN,
range_v_step * uRange + uN + 1])
return verts, faces
# Original Script "Parametric.py" by Ed Mackey.
# -> http://www.blinken.com/blender-plugins.php
# Partly converted for Blender 2.5 by tuga3d.
#
# Sphere:
# x = sin(2*pi*u)*sin(pi*v)
# y = cos(2*pi*u)*sin(pi*v)
# z = cos(pi*v)
# u_min = v_min = 0
# u_max = v_max = 1
#
# "Snail shell"
# x = 1.2**v*(sin(u)**2 *sin(v))
# y = 1.2**v*(sin(u)*cos(u))
# z = 1.2**v*(sin(u)**2 *cos(v))
# u_min = 0
# u_max = pi
# v_min = -pi/4,
# v max = 5*pi/2
class AddXYZFunctionSurface(Operator):
bl_idname = "mesh.primitive_xyz_function_surface"
bl_label = "Add XYZ Function Surface"
bl_description = ("Add a surface defined defined by 3 functions:\n"
"x=F1(u,v), y=F2(u,v) and z=F3(u,v)")
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
x_eq: StringProperty(
name="X Equation",
description="Equation for x=F(u,v). "
"Also available: n, a, b, c, f, g, h",
default="cos(v)*(1+cos(u))*sin(v/8)"
)
y_eq: StringProperty(
name="Y Equation",
description="Equation for y=F(u,v). "
"Also available: n, a, b, c, f, g, h",
default="sin(u)*sin(v/8)+cos(v/8)*1.5"
)
z_eq: StringProperty(
name="Z Equation",
description="Equation for z=F(u,v). "
"Also available: n, a, b, c, f, g, h",
default="sin(v)*(1+cos(u))*sin(v/8)"
)
range_u_min: FloatProperty(
name="U Min",
description="Minimum U value. Lower boundary of U range",
min=-100.00,
max=0.00,
default=0.00
)
range_u_max: FloatProperty(
name="U Max",
description="Maximum U value. Upper boundary of U range",
min=0.00,
max=100.00,
default=2 * pi
)
range_u_step: IntProperty(
name="U Step",
description="U Subdivisions",
min=1,
max=1024,
default=32
)
wrap_u: BoolProperty(
name="U Wrap",
description="U Wrap around",
default=True
)
range_v_min: FloatProperty(
name="V Min",
description="Minimum V value. Lower boundary of V range",
min=-100.00,
max=0.00,
default=0.00
)
range_v_max: FloatProperty(
name="V Max",
description="Maximum V value. Upper boundary of V range",
min=0.00,
max=100.00,
default=4 * pi
)
range_v_step: IntProperty(
name="V Step",
description="V Subdivisions",
min=1,
max=1024,
default=128
)
wrap_v: BoolProperty(
name="V Wrap",
description="V Wrap around",
default=False
)
close_v: BoolProperty(
name="Close V",
description="Create faces for first and last "
"V values (only if U is wrapped)",
default=False
)
n_eq: IntProperty(
name="Number of Objects (n=0..N-1)",
description="The parameter n will be the index "
"of the current object, 0 to N-1",
min=1,
max=100,
default=1
)
a_eq: StringProperty(
name="A Helper Function",
description="Equation for a=F(u,v). Also available: n",
default="0"
)
b_eq: StringProperty(
name="B Helper Function",
description="Equation for b=F(u,v). Also available: n",
default="0"
)
c_eq: StringProperty(
name="C Helper Function",
description="Equation for c=F(u,v). Also available: n",
default="0"
)
f_eq: StringProperty(
name="F Helper Function",
description="Equation for f=F(u,v). Also available: n, a, b, c",
default="0"
)
g_eq: StringProperty(
name="G Helper Function",
description="Equation for g=F(u,v). Also available: n, a, b, c",
default="0"
)
h_eq: StringProperty(
name="H Helper Function",
description="Equation for h=F(u,v). Also available: n, a, b, c",
default="0"
)
show_wire : BoolProperty(
name="Show Wireframe",
default=True,
description="Add the objects wireframe over solid drawing"
)
edit_mode : BoolProperty(
name="Show in Edit Mode",
default=True,
description="Show in Edit Mode"
)
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
layout.separator()
col = layout.column()
col.prop(self, 'x_eq', text='Equation X')
col.prop(self, 'y_eq', text='Y')
col.prop(self, 'z_eq', text='Z')
layout.separator()
col = layout.column(align=True)
col.prop(self, 'range_u_min', text='U Min')
col.prop(self, 'range_u_max', text='Max')
col.prop(self, 'range_u_step', text='Step')
col.prop(self, 'wrap_u', text='Wrap')
layout.separator()
col = layout.column(align=True)
col.prop(self, 'range_v_min', text='V Min')
col.prop(self, 'range_v_max', text='Max')
col.prop(self, 'range_v_step', text='Step')
col.prop(self, 'wrap_v', text='Wrap')
col.prop(self, 'close_v', text='Close')
layout.separator()
col = layout.column()
col.prop(self, 'n_eq', text='Objects')
col.prop(self, 'a_eq', text='Helper Function A')
col.prop(self, 'b_eq', text='B')
col.prop(self, 'c_eq', text='C')
col.prop(self, 'f_eq', text='F')
col.prop(self, 'g_eq', text='G')
col.prop(self, 'h_eq', text='H')
layout.separator()
row = layout.row(heading='Show')
row.prop(self, 'show_wire', text='Wireframe')
layout.prop(self, 'edit_mode', text='In Edit Mode')
def execute(self, context):
for n in range(0, self.n_eq):
verts, faces = xyz_function_surface_faces(
self,
self.x_eq,
self.y_eq,
self.z_eq,
self.range_u_min,
self.range_u_max,
self.range_u_step,
self.wrap_u,
self.range_v_min,
self.range_v_max,
self.range_v_step,
self.wrap_v,
self.a_eq,
self.b_eq,
self.c_eq,
self.f_eq,
self.g_eq,
self.h_eq,
n,
self.close_v
)
if not verts:
return {'CANCELLED'}
obj = create_mesh_object(context, verts, [], faces, "XYZ Function")
if self.show_wire:
context.active_object.show_wire = True
else:
context.active_object.show_wire = False
if self.edit_mode:
bpy.ops.object.mode_set(mode = 'EDIT')
else:
bpy.ops.object.mode_set(mode = 'OBJECT')
return {'FINISHED'}
@@ -0,0 +1,822 @@
# SPDX-FileCopyrightText: 2016-2022 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later
# Author: revolt_randy, Jambay
# Create "Beam" primitives. Based on original script by revolt_randy
import bpy
from bpy.types import Operator
from bpy.props import (
BoolProperty,
EnumProperty,
FloatProperty,
IntProperty,
StringProperty,
)
from bpy_extras import object_utils
from .interface import draw_transform_props
# #####################
# Create vertices for end of mesh
#
# y_off - verts y-axis origin
#
# returns:
# endVs - x,y,z list
def beamEndVs(sRef, y_off):
thick = sRef.beamW * 2
if sRef.Type == '2': # swap width and height for C shape
bEndX2 = sRef.beamZ / 2
bEndXInr = ((sRef.beamZ - thick) / 2)
bEndZ2 = sRef.beamX / 2
bEndZInr = ((sRef.beamX - thick) / 2)
else:
bEndX2 = sRef.beamX / 2
bEndXInr = ((sRef.beamX - thick) / 2)
bEndZ2 = sRef.beamZ / 2
bEndZInr = ((sRef.beamZ - thick) / 2)
endVs = []
# outer ...
endVs.append((bEndX2, y_off, bEndZ2))
endVs.append((-bEndX2, y_off, bEndZ2))
endVs.append((-bEndX2, y_off, -bEndZ2))
endVs.append((bEndX2, y_off, -bEndZ2))
# innner ...
endVs.append((bEndXInr, y_off, bEndZInr))
endVs.append((-bEndXInr, y_off, bEndZInr))
endVs.append((-bEndXInr, y_off, -bEndZInr))
endVs.append((bEndXInr, y_off, -bEndZInr))
return endVs
# #####################
# Create End Faces
#
# verts_list - list of vertices
#
# returns:
# beamFs, a list of tuples defining the end faces.
def beamEndFaces(verts_list):
beamFs = []
num_of_verts = int(len(verts_list) / 2)
# Create list of faces
for index in range(num_of_verts):
faces_temp = []
if index == (num_of_verts - 1):
faces_temp.append(verts_list[index])
faces_temp.append(verts_list[index - index])
faces_temp.append(verts_list[index + 1])
faces_temp.append(verts_list[index * 2 + 1])
else:
faces_temp.append(verts_list[index])
faces_temp.append(verts_list[index + 1])
faces_temp.append(verts_list[index + num_of_verts + 1])
faces_temp.append(verts_list[index + num_of_verts])
beamFs.append(tuple(faces_temp))
return beamFs
# #####################
# Bridge vertices to create side faces.
#
# front_verts - front face vertices
# back_verts - back face vertices
# front & back must be ordered in same direction
# with respect to y-axis
#
# returns:
# sideFaces, a list of the bridged faces
def beamSides(front_verts, back_verts):
sideFaces = []
num_of_faces = (len(front_verts))
# add first value to end of lists for looping
front_verts.append(front_verts[0])
back_verts.append(back_verts[0])
# Build the faces
for index in range(num_of_faces):
facestemp = (front_verts[index], front_verts[index + 1], back_verts[index + 1], back_verts[index])
sideFaces.append(facestemp)
return sideFaces
# #####################
# Creates a box beam
#
# returns:
# beamVs - x, y, z, location of each vertice
# beamFs - vertices that make up each face
def create_beam(sRef):
frontVs = []
frontFs = []
backVs = []
y_off = sRef.beamY / 2 # offset from center for vertices
frontVs = beamEndVs(sRef, y_off)
backVs = beamEndVs(sRef, -y_off)
# Combine vertices
beamVs = frontVs + backVs
# Create front face
numofverts = len(frontVs)
verts_front_list = []
for index in range(numofverts):
verts_front_list.append(index)
frontFs = beamEndFaces(verts_front_list)
# Create back face
faces_back_temp = []
verts_back_list = []
numofverts = len(backVs)
for index in range(numofverts):
verts_back_list.append(index + numofverts)
faces_back_temp = beamEndFaces(verts_back_list)
# Create side faces
faces_side_temp = []
# Object has thickness, create list of outside vertices
numofverts = len(verts_front_list)
halfVerts = int(numofverts / 2)
frontVs = verts_front_list[0:halfVerts]
backVs = verts_back_list[0:halfVerts]
faces_side_temp = beamSides(frontVs, backVs)
# Create list of inside vertices
frontVs = verts_front_list[halfVerts:numofverts]
backVs = verts_back_list[halfVerts:numofverts]
faces_side_temp += beamSides(frontVs, backVs)
# Combine all faces
beamFs = frontFs + faces_back_temp + faces_side_temp
return beamVs, beamFs
# #####################
# Taper/angle faces of beam.
# inner vert toward outer vert
# based on percentage of taper.
#
# returns:
# adVert - the calculated vertex
def beamSlant(sRef, outV, inV):
bTaper = 100 - sRef.edgeA
# calculate variance & adjust vertex
deltaV = ((inV - outV) / 100)
adVert = outV + (deltaV * bTaper)
return adVert
# #####################
# Modify location to shape beam.
#
# verts - tuples for one end of beam
#
# returns:
# verts - modified tuples for beam shape.
def beamSquareEnds(sRef, verts):
# match 5th & 6th z locations to 1st & 2nd
vert_orig = verts[0]
vert_temp = verts[4]
vert_x = beamSlant(sRef, vert_orig[0], vert_temp[0])
verts[4] = (vert_x, vert_temp[1], vert_orig[2])
vert_orig = verts[1]
vert_temp = verts[5]
vert_x = beamSlant(sRef, vert_orig[0], vert_temp[0])
verts[5] = (vert_x, vert_temp[1], vert_orig[2])
return verts
# #####################
#
# Create U shaped beam
# Shared with C shape - see beamEndVs
# for sizing and rotate in addBeamObj.
#
# returns:
# beamVs - vertice x, y, z, locations
# beamFs - face vertices
def create_u_beam(sRef):
# offset vertices from center
y_off = sRef.beamY / 2
frontVtemp = []
frontFtemp = []
frontVlist = []
backVtemp = []
backFtemp = []
backVlist = []
sideFs = []
frontVtemp = beamEndVs(sRef, y_off) # Box beam
frontVtemp = beamSquareEnds(sRef, frontVtemp) # U shape
backVtemp = beamEndVs(sRef, -y_off)
backVtemp = beamSquareEnds(sRef, backVtemp)
beamVs = frontVtemp + backVtemp
# Create front face
for index in range(len(frontVtemp)): # Build vert list
frontVlist.append(index)
frontFtemp = beamEndFaces(frontVlist)
frontFtemp = frontFtemp[1:4] # Remove 1st face
# Create back face
numofverts = len(backVtemp)
for index in range(numofverts): # Build vertex list
backVlist.append(index + numofverts)
backFtemp = beamEndFaces(backVlist)
backFtemp = backFtemp[1:4] # Remove face
# Create list vertices for outside faces
numofverts = int(len(frontVlist))
halfVerts = int(numofverts / 2)
frontVtemp = frontVlist[0:halfVerts]
backVtemp = backVlist[0:halfVerts]
sideFs = beamSides(frontVtemp, backVtemp)
sideFs = sideFs[1:] # Remove face
# Create inside verts
frontVtemp = frontVlist[halfVerts:numofverts]
backVtemp = backVlist[halfVerts:numofverts]
sideFs += beamSides(frontVtemp, backVtemp)
sideFs = sideFs[0:3] + sideFs[4:] # Remove face
# fill in faces
sideFs.append((0, 4, 12, 8))
sideFs.append((5, 1, 9, 13))
beamFs = frontFtemp + backFtemp + sideFs # Combine faces
return beamVs, beamFs
# #####################
# returns:
# verts_final - x, y, z, location of each vertice
# faces_final - vertices that make up each face
def create_L_beam(sRef):
thick = sRef.beamW
# offset vertices from center
x_off = sRef.beamX / 2
y_off = sRef.beamY / 2
z_off = sRef.beamZ / 2
# Create temporarylists to hold vertices locations
verts_front_temp = []
verts_back_temp = []
# Create front vertices by calculation
verts_front_temp = [
(-x_off, -y_off, z_off),
(-(x_off - thick), -y_off, z_off),
(-(x_off - thick), -y_off, -(z_off - thick)),
(x_off, -y_off, -(z_off - thick)),
(x_off, -y_off, -z_off),
(-x_off, -y_off, -z_off)
]
# Adjust taper
vert_outside = verts_front_temp[0]
vert_inside = verts_front_temp[1]
vert_taper = beamSlant(sRef, vert_outside[0], vert_inside[0])
verts_front_temp[1] = [vert_taper, vert_inside[1], vert_inside[2]]
vert_outside = verts_front_temp[4]
vert_inside = verts_front_temp[3]
vert_taper = beamSlant(sRef, vert_outside[2], vert_inside[2])
verts_front_temp[3] = [vert_inside[0], vert_inside[1], vert_taper]
# Create back vertices by calculation
verts_back_temp = [
(-x_off, y_off, z_off),
(-(x_off - thick), y_off, z_off),
(-(x_off - thick), y_off, -(z_off - thick)),
(x_off, y_off, -(z_off - thick)),
(x_off, y_off, -z_off),
(-x_off, y_off, -z_off)
]
# Adjust taper
vert_outside = verts_back_temp[0]
vert_inside = verts_back_temp[1]
vert_taper = beamSlant(sRef, vert_outside[0], vert_inside[0])
verts_back_temp[1] = [vert_taper, vert_inside[1], vert_inside[2]]
vert_outside = verts_back_temp[4]
vert_inside = verts_back_temp[3]
vert_taper = beamSlant(sRef, vert_outside[2], vert_inside[2])
verts_back_temp[3] = [vert_inside[0], vert_inside[1], vert_taper]
verts_final = verts_front_temp + verts_back_temp
# define end faces, only 4 so just coded
faces_front_temp = []
faces_back_temp = []
faces_side_temp = []
faces_front_temp = [(0, 1, 2, 5), (2, 3, 4, 5)]
faces_back_temp = [(6, 7, 8, 11), (8, 9, 10, 11)]
verts_front_list = []
verts_back_list = []
num_of_verts = len(verts_front_temp)
# build lists of back and front verts for beamSides function
for index in range(num_of_verts):
verts_front_list.append(index)
for index in range(num_of_verts):
verts_back_list.append(index + 6)
faces_side_temp = beamSides(verts_front_list, verts_back_list)
faces_final = faces_front_temp + faces_back_temp + faces_side_temp
return verts_final, faces_final
# #####################
# returns:
# verts_final - a list of tuples of the x, y, z, location of each vertice
# faces_final - a list of tuples of the vertices that make up each face
def create_T_beam(sRef):
thick = sRef.beamW
# Get offset of vertices from center
x_off = sRef.beamX / 2
y_off = sRef.beamY / 2
z_off = sRef.beamZ / 2
thick_off = thick / 2
# Create temporarylists to hold vertices locations
verts_front_temp = []
verts_back_temp = []
# Create front vertices
verts_front_temp = [
(-x_off, -y_off, z_off),
(-thick_off, -y_off, z_off),
(thick_off, -y_off, z_off),
(x_off, -y_off, z_off),
(x_off, -y_off, z_off - thick),
(thick_off, -y_off, z_off - thick),
(thick_off, -y_off, -z_off),
(-thick_off, -y_off, -z_off),
(-thick_off, -y_off, z_off - thick),
(-x_off, -y_off, z_off - thick)
]
# Adjust taper
vert_outside = verts_front_temp[0]
vert_inside = verts_front_temp[9]
vert_taper = (beamSlant(sRef, vert_outside[2], vert_inside[2]))
verts_front_temp[9] = [vert_inside[0], vert_inside[1], vert_taper]
vert_outside = verts_front_temp[3]
vert_inside = verts_front_temp[4]
verts_front_temp[4] = [vert_inside[0], vert_inside[1], vert_taper]
# Adjust taper of bottom of beam, so 0 the center
# now becomes vert_outside, and vert_inside is calculated
# 1/2 way towards center
vert_outside = (0, -y_off, -z_off)
vert_inside = verts_front_temp[6]
vert_taper = (beamSlant(sRef, vert_outside[0], vert_inside[0]))
verts_front_temp[6] = [vert_taper, vert_inside[1], vert_inside[2]]
vert_outside = (0, -y_off, -z_off)
vert_inside = verts_front_temp[7]
vert_taper = beamSlant(sRef, vert_outside[0], vert_inside[0])
verts_front_temp[7] = [vert_taper, vert_inside[1], vert_inside[2]]
# Create fack vertices by calculation
verts_back_temp = [
(-x_off, y_off, z_off),
(-thick_off, y_off, z_off),
(thick_off, y_off, z_off),
(x_off, y_off, z_off),
(x_off, y_off, z_off - thick),
(thick_off, y_off, z_off - thick),
(thick_off, y_off, -z_off),
(-thick_off, y_off, -z_off),
(-thick_off, y_off, z_off - thick),
(-x_off, y_off, z_off - thick)
]
# Adjust taper
vert_outside = verts_back_temp[0]
vert_inside = verts_back_temp[9]
vert_taper = (beamSlant(sRef, vert_outside[2], vert_inside[2]))
verts_back_temp[9] = [vert_inside[0], vert_inside[1], vert_taper]
vert_outside = verts_back_temp[3]
vert_inside = verts_back_temp[4]
vert_taper = (beamSlant(sRef, vert_outside[2], vert_inside[2]))
verts_back_temp[4] = [vert_inside[0], vert_inside[1], vert_taper]
# Adjust taper of bottom of beam, so 0 the center
# now becomes vert_outside, and vert_inside is calculated
# 1/2 way towards center
vert_outside = (0, -y_off, -z_off)
vert_inside = verts_back_temp[6]
vert_taper = (beamSlant(sRef, vert_outside[0], vert_inside[0]))
verts_back_temp[6] = [vert_taper, vert_inside[1], vert_inside[2]]
vert_outside = (0, -y_off, -z_off)
vert_inside = verts_back_temp[7]
vert_taper = (beamSlant(sRef, vert_outside[0], vert_inside[0]))
verts_back_temp[7] = [vert_taper, vert_inside[1], vert_inside[2]]
verts_final = verts_front_temp + verts_back_temp
# define end faces, only 8 so just coded
faces_front_temp = []
faces_back_temp = []
faces_side_temp = []
faces_front_temp = [(0, 1, 8, 9), (1, 2, 5, 8),
(2, 3, 4, 5), (5, 6, 7, 8)]
faces_back_temp = [(10, 11, 18, 19), (11, 12, 15, 18),
(12, 13, 14, 15), (15, 16, 17, 18)]
verts_front_list = []
verts_back_list = []
num_of_verts = len(verts_front_temp)
# build lists of back and front verts for beamSides function
for index in range(num_of_verts):
verts_front_list.append(index)
for index in range(num_of_verts):
verts_back_list.append(index + 10)
faces_side_temp = beamSides(verts_front_list, verts_back_list)
faces_final = faces_front_temp + faces_back_temp + faces_side_temp
return verts_final, faces_final
# #####################
# returns:
# verts_final - a list of tuples of the x, y, z, location of each vertice
# faces_final - a list of tuples of the vertices that make up each face
def create_I_beam(sRef):
thick = sRef.beamW
# Get offset of vertices from center
x_off = sRef.beamX / 2
y_off = sRef.beamY / 2
z_off = sRef.beamZ / 2
thick_off = thick / 2
# Create temporarylists to hold vertices locations
verts_front_temp = []
verts_back_temp = []
# Create front vertices by calculation
verts_front_temp = [
(-x_off, -y_off, z_off),
(-thick_off, -y_off, z_off),
(thick_off, -y_off, z_off),
(x_off, -y_off, z_off),
(x_off, -y_off, z_off - thick),
(thick_off, -y_off, z_off - thick),
(thick_off, -y_off, -z_off + thick),
(x_off, -y_off, -z_off + thick),
(x_off, -y_off, -z_off),
(thick_off, -y_off, -z_off),
(-thick_off, -y_off, -z_off),
(-x_off, -y_off, -z_off),
(-x_off, -y_off, -z_off + thick),
(-thick_off, -y_off, -z_off + thick),
(-thick_off, -y_off, z_off - thick),
(-x_off, -y_off, z_off - thick)
]
# Adjust taper
vert_outside = verts_front_temp[0]
vert_inside = verts_front_temp[15]
vert_taper = (beamSlant(sRef, vert_outside[2], vert_inside[2]))
verts_front_temp[15] = [vert_inside[0], vert_inside[1], vert_taper]
vert_outside = verts_front_temp[3]
vert_inside = verts_front_temp[4]
vert_taper = (beamSlant(sRef, vert_outside[2], vert_inside[2]))
verts_front_temp[4] = [vert_inside[0], vert_inside[1], vert_taper]
vert_outside = verts_front_temp[8]
vert_inside = verts_front_temp[7]
vert_taper = (beamSlant(sRef, vert_outside[2], vert_inside[2]))
verts_front_temp[7] = [vert_inside[0], vert_inside[1], vert_taper]
vert_outside = verts_front_temp[11]
vert_inside = verts_front_temp[12]
vert_taper = (beamSlant(sRef, vert_outside[2], vert_inside[2]))
verts_front_temp[12] = [vert_inside[0], vert_inside[1], vert_taper]
# Create back vertices by calculation
verts_back_temp = [
(-x_off, y_off, z_off),
(-thick_off, y_off, z_off),
(thick_off, y_off, z_off),
(x_off, y_off, z_off),
(x_off, y_off, z_off - thick),
(thick_off, y_off, z_off - thick),
(thick_off, y_off, -z_off + thick),
(x_off, y_off, -z_off + thick),
(x_off, y_off, -z_off),
(thick_off, y_off, -z_off),
(-thick_off, y_off, -z_off),
(-x_off, y_off, -z_off),
(-x_off, y_off, -z_off + thick),
(-thick_off, y_off, -z_off + thick),
(-thick_off, y_off, z_off - thick),
(-x_off, y_off, z_off - thick)
]
# Adjust taper
vert_outside = verts_back_temp[0]
vert_inside = verts_back_temp[15]
vert_taper = (beamSlant(sRef, vert_outside[2], vert_inside[2]))
verts_back_temp[15] = [vert_inside[0], vert_inside[1], vert_taper]
vert_outside = verts_back_temp[3]
vert_inside = verts_back_temp[4]
vert_taper = (beamSlant(sRef, vert_outside[2], vert_inside[2]))
verts_back_temp[4] = [vert_inside[0], vert_inside[1], vert_taper]
vert_outside = verts_back_temp[8]
vert_inside = verts_back_temp[7]
vert_taper = (beamSlant(sRef, vert_outside[2], vert_inside[2]))
verts_back_temp[7] = [vert_inside[0], vert_inside[1], vert_taper]
vert_outside = verts_back_temp[11]
vert_inside = verts_back_temp[12]
vert_taper = (beamSlant(sRef, vert_outside[2], vert_inside[2]))
verts_back_temp[12] = [vert_inside[0], vert_inside[1], vert_taper]
verts_final = verts_front_temp + verts_back_temp
# define end faces, only 7 per end, so just coded
faces_front_temp = []
faces_back_temp = []
faces_side_temp = []
faces_front_temp = [(0, 1, 14, 15), (1, 2, 5, 14),
(2, 3, 4, 5), (6, 7, 8, 9),
(6, 9, 10, 13), (12, 13, 10, 11),
(5, 6, 13, 14)]
faces_back_temp = [(16, 17, 30, 31), (17, 18, 21, 30),
(18, 19, 20, 21), (22, 23, 24, 25),
(22, 25, 26, 29), (28, 29, 26, 27),
(21, 22, 29, 30)]
verts_front_list = []
verts_back_list = []
num_of_verts = len(verts_front_temp)
# build lists of back and front verts for beamSides function
for index in range(num_of_verts):
verts_front_list.append(index)
for index in range(num_of_verts):
verts_back_list.append(index + 16)
faces_side_temp = beamSides(verts_front_list, verts_back_list)
faces_final = faces_front_temp + faces_back_temp + faces_side_temp
return verts_final, faces_final
# ######################
#
# Generate beam mesh.
def addBeamMesh(sRef, context):
verts = []
faces = []
# type of beam to add
if sRef.Type == '0':
verts, faces = create_beam(sRef)
elif sRef.Type == '1':
verts, faces = create_u_beam(sRef)
elif sRef.Type == '2':
verts, faces = create_u_beam(sRef)
elif sRef.Type == '3':
verts, faces = create_L_beam(sRef)
elif sRef.Type == '4':
verts, faces = create_I_beam(sRef)
elif sRef.Type == '5':
verts, faces = create_T_beam(sRef)
else: # unknown type, use default.
verts, faces = create_beam(sRef)
beamMesh = bpy.data.meshes.new("Beam")
beamMesh.from_pydata(verts, [], faces)
beamMesh.update(calc_edges=True)
return beamMesh
# ######################
# Create a beam primitive.
#
# UI functions and object creation.
class addBeam(Operator, object_utils.AddObjectHelper):
bl_idname = "mesh.add_beam"
bl_label = "Beam Builder"
bl_description = "Create beam meshes of various profiles"
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
Beam : BoolProperty(name = "Beam",
default = True,
description = "Beam")
change : BoolProperty(name = "Change",
default = False,
description = "change Beam")
Type: EnumProperty(
name="Beam Type",
items=(
('0', "Box Profile", "Square Beam"),
("1", "U Profile", "U Profile Beam"),
("2", "C Profile", "C Profile Beam"),
("3", "L Profile", "L Profile Beam"),
("4", "I Profile", "I Profile Beam"),
("5", "T Profile", "T Profile Beam")
),
description="Beam form"
)
beamZ: FloatProperty(
name="Height",
min=0.01,
#max=100,
default=1
)
beamX: FloatProperty(
name="Width",
min=0.01,
#max=100,
default=.5
)
beamY: FloatProperty(
name="Depth",
min=0.01,
#max=100,
default=2
)
beamW: FloatProperty(
name="Thickness",
min=0.01,
#max=1,
default=0.1
)
edgeA: IntProperty(
name="Taper",
min=0,
#max=100,
default=0,
description="Angle beam edges"
)
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
layout.separator()
layout.prop(self, "Type")
layout.separator()
layout.prop(self, "beamZ")
layout.prop(self, "beamX")
layout.prop(self, "beamY")
layout.prop(self, "beamW")
if self.Type != '0':
layout.prop(self, "edgeA")
if self.change == False:
# generic transform props
layout.separator()
draw_transform_props(self, layout)
def execute(self, context):
# turn off 'Enter Edit Mode'
use_enter_edit_mode = bpy.context.preferences.edit.use_enter_edit_mode
bpy.context.preferences.edit.use_enter_edit_mode = False
if bpy.context.mode == "OBJECT":
if context.selected_objects != [] and context.active_object and \
(context.active_object.data is not None) and ('Beam' in context.active_object.data.keys()) and \
(self.change == True):
obj = context.active_object
oldmesh = obj.data
oldmeshname = obj.data.name
mesh = addBeamMesh(self, context)
obj.data = mesh
for material in oldmesh.materials:
obj.data.materials.append(material)
bpy.data.meshes.remove(oldmesh)
obj.data.name = oldmeshname
else:
mesh = addBeamMesh(self, context)
obj = object_utils.object_data_add(context, mesh, operator=self)
if self.Type == '2': # Rotate C shape
bpy.ops.transform.rotate(value=1.570796, constraint_axis=[False, True, False])
bpy.ops.object.transform_apply(location=False, rotation=True, scale=False)
obj.data["Beam"] = True
obj.data["change"] = False
for prm in BeamParameters():
obj.data[prm] = getattr(self, prm)
if bpy.context.mode == "EDIT_MESH":
active_object = context.active_object
name_active_object = active_object.name
bpy.ops.object.mode_set(mode='OBJECT')
mesh = addBeamMesh(self, context)
obj = object_utils.object_data_add(context, mesh, operator=self)
obj.select_set(True)
active_object.select_set(True)
bpy.context.view_layer.objects.active = active_object
bpy.ops.object.join()
context.active_object.name = name_active_object
bpy.ops.object.mode_set(mode='EDIT')
if use_enter_edit_mode:
bpy.ops.object.mode_set(mode = 'EDIT')
# restore pre operator state
bpy.context.preferences.edit.use_enter_edit_mode = use_enter_edit_mode
return {'FINISHED'}
def BeamParameters():
BeamParameters = [
"Type",
"beamZ",
"beamX",
"beamY",
"beamW",
"edgeA",
]
return BeamParameters
@@ -0,0 +1,987 @@
# SPDX-FileCopyrightText: 2009-2010 Michel J. Anders (varkenvarken)
#
# SPDX-License-Identifier: GPL-2.0-or-later
import bpy
from bpy.types import Operator
from math import (
atan, asin, cos,
sin, tan, pi,
radians,
)
from bpy.props import (
FloatProperty,
IntProperty,
BoolProperty,
StringProperty,
FloatVectorProperty
)
from mathutils import (
Vector,
Matrix,
)
from bpy_extras import object_utils
from .interface import draw_transform_props
# A very simple "bridge" tool.
# Connects two equally long vertex rows with faces.
# Returns a list of the new faces (list of lists)
#
# vertIdx1 ... First vertex list (list of vertex indices)
# vertIdx2 ... Second vertex list (list of vertex indices)
# closed ... Creates a loop (first & last are closed)
# flipped ... Invert the normal of the face(s)
#
# Note: You can set vertIdx1 to a single vertex index to create
# a fan/star of faces
# Note: If both vertex idx list are the same length they have
# to have at least 2 vertices
def createFaces(vertIdx1, vertIdx2, closed=False, flipped=False):
faces = []
if not vertIdx1 or not vertIdx2:
return None
if len(vertIdx1) < 2 and len(vertIdx2) < 2:
return None
fan = False
if (len(vertIdx1) != len(vertIdx2)):
if (len(vertIdx1) == 1 and len(vertIdx2) > 1):
fan = True
else:
return None
total = len(vertIdx2)
if closed:
# Bridge the start with the end.
if flipped:
face = [
vertIdx1[0],
vertIdx2[0],
vertIdx2[total - 1]]
if not fan:
face.append(vertIdx1[total - 1])
faces.append(face)
else:
face = [vertIdx2[0], vertIdx1[0]]
if not fan:
face.append(vertIdx1[total - 1])
face.append(vertIdx2[total - 1])
faces.append(face)
# Bridge the rest of the faces.
for num in range(total - 1):
if flipped:
if fan:
face = [vertIdx2[num], vertIdx1[0], vertIdx2[num + 1]]
else:
face = [vertIdx2[num], vertIdx1[num],
vertIdx1[num + 1], vertIdx2[num + 1]]
faces.append(face)
else:
if fan:
face = [vertIdx1[0], vertIdx2[num], vertIdx2[num + 1]]
else:
face = [vertIdx1[num], vertIdx2[num],
vertIdx2[num + 1], vertIdx1[num + 1]]
faces.append(face)
return faces
# Calculate the vertex coordinates for a single
# section of a gear tooth.
# Returns 4 lists of vertex coords (list of tuples):
# *-*---*---* (1.) verts_inner_base
# | | | |
# *-*---*---* (2.) verts_outer_base
# | | |
# *---*---* (3.) verts_middle_tooth
# \ | /
# *-*-* (4.) verts_tip_tooth
#
# a
# t
# d
# radius
# Ad
# De
# base
# p_angle
# rack
# crown
def add_tooth(a, t, d, radius, Ad, De, base, p_angle, rack=0, crown=0.0):
A = [a, a + t / 4, a + t / 2, a + 3 * t / 4]
C = [cos(i) for i in A]
S = [sin(i) for i in A]
Ra = radius + Ad
Rd = radius - De
Rb = Rd - base
# Pressure angle calc
O = Ad * tan(p_angle)
if Ra != 0:
p_angle = atan(O / Ra)
else:
p_angle = atan(O)
if radius < 0:
p_angle = -p_angle
if rack:
S = [sin(t / 4) * I for I in range(-2, 3)]
Sp = [0, sin(-t / 4 + p_angle), 0, sin(t / 4 - p_angle)]
verts_inner_base = [(Rb, radius * S[I], d) for I in range(4)]
verts_outer_base = [(Rd, radius * S[I], d) for I in range(4)]
verts_middle_tooth = [(radius, radius * S[I], d) for I in range(1, 4)]
verts_tip_tooth = [(Ra, radius * Sp[I], d) for I in range(1, 4)]
else:
Cp = [
0,
cos(a + t / 4 + p_angle),
cos(a + t / 2),
cos(a + 3 * t / 4 - p_angle)]
Sp = [0,
sin(a + t / 4 + p_angle),
sin(a + t / 2),
sin(a + 3 * t / 4 - p_angle)]
verts_inner_base = [(Rb * C[I], Rb * S[I], d)
for I in range(4)]
verts_outer_base = [(Rd * C[I], Rd * S[I], d)
for I in range(4)]
verts_middle_tooth = [(radius * C[I], radius * S[I], d + crown / 3)
for I in range(1, 4)]
verts_tip_tooth = [(Ra * Cp[I], Ra * Sp[I], d + crown)
for I in range(1, 4)]
return (verts_inner_base, verts_outer_base,
verts_middle_tooth, verts_tip_tooth)
# EXPERIMENTAL Calculate the vertex coordinates for a single
# section of a gearspoke.
# Returns them as a list of tuples
#
# a
# t
# d
# radius
# De
# base
# s
# w
# l
# gap
# width
#
# @todo Finish this.
def add_spoke(a, t, d, radius, De, base, s, w, l, gap=0, width=19):
Rd = radius - De
Rb = Rd - base
verts = []
edgefaces = []
edgefaces2 = []
sf = []
if not gap:
for N in range(width, 1, -2):
edgefaces.append(len(verts))
ts = t / 4
tm = a + 2 * ts
te = asin(w / Rb)
td = te - ts
t4 = ts + td * (width - N) / (width - 3.0)
A = [tm + (i - int(N / 2)) * t4 for i in range(N)]
C = [cos(i) for i in A]
S = [sin(i) for i in A]
verts.extend((Rb * I, Rb * J, d) for (I, J) in zip(C, S))
edgefaces2.append(len(verts) - 1)
Rb = Rb - s
n = 0
for N in range(width, 3, -2):
sf.extend([(i + n, i + 1 + n, i + 2 + n, i + N + n)
for i in range(0, N - 1, 2)])
sf.extend([(i + 2 + n, i + N + n, i + N + 1 + n, i + N + 2 + n)
for i in range(0, N - 3, 2)])
n = n + N
return verts, edgefaces, edgefaces2, sf
# Create gear geometry.
# Returns:
# * A list of vertices (list of tuples)
# * A list of faces (list of lists)
# * A list (group) of vertices of the tip (list of vertex indices)
# * A list (group) of vertices of the valley (list of vertex indices)
#
# teethNum ... Number of teeth on the gear
# radius ... Radius of the gear, negative for crown gear
# Ad ... Addendum, extent of tooth above radius
# De ... Dedendum, extent of tooth below radius
# base ... Base, extent of gear below radius
# p_angle ... Pressure angle. Skewness of tooth tip. (radiant)
# width ... Width, thickness of gear
# skew ... Skew of teeth. (radiant)
# conangle ... Conical angle of gear. (radiant)
# rack
# crown ... Inward pointing extend of crown teeth
#
# inner radius = radius - (De + base)
def add_gear(teethNum, radius, Ad, De, base, p_angle,
width=1, skew=0, conangle=0, rack=0, crown=0.0):
if teethNum < 2:
return None, None, None, None
t = 2 * pi / teethNum
if rack:
teethNum = 1
#print(radius, width, conangle)
if radius != 0:
scale = (radius - 2 * width * tan(conangle)) / radius
else:
scale = radius - 2 * width * tan(conangle)
verts = []
faces = []
vgroup_top = [] # Vertex group of top/tip? vertices.
vgroup_valley = [] # Vertex group of valley vertices
verts_bridge_prev = []
for toothCnt in range(teethNum):
a = toothCnt * t
verts_bridge_start = []
verts_bridge_end = []
verts_outside_top = []
verts_outside_bottom = []
for (s, d, c, top) \
in [(0, -width, 1, True), (skew, width, scale, False)]:
verts1, verts2, verts3, verts4 = add_tooth(a + s, t, d,
radius * c, Ad * c, De * c, base * c, p_angle,
rack, crown)
vertsIdx1 = list(range(len(verts), len(verts) + len(verts1)))
verts.extend(verts1)
vertsIdx2 = list(range(len(verts), len(verts) + len(verts2)))
verts.extend(verts2)
vertsIdx3 = list(range(len(verts), len(verts) + len(verts3)))
verts.extend(verts3)
vertsIdx4 = list(range(len(verts), len(verts) + len(verts4)))
verts.extend(verts4)
verts_outside = []
verts_outside.extend(vertsIdx2[:2])
verts_outside.append(vertsIdx3[0])
verts_outside.extend(vertsIdx4)
verts_outside.append(vertsIdx3[-1])
verts_outside.append(vertsIdx2[-1])
if top:
# verts_inside_top = vertsIdx1
verts_outside_top = verts_outside
verts_bridge_start.append(vertsIdx1[0])
verts_bridge_start.append(vertsIdx2[0])
verts_bridge_end.append(vertsIdx1[-1])
verts_bridge_end.append(vertsIdx2[-1])
else:
# verts_inside_bottom = vertsIdx1
verts_outside_bottom = verts_outside
verts_bridge_start.append(vertsIdx2[0])
verts_bridge_start.append(vertsIdx1[0])
verts_bridge_end.append(vertsIdx2[-1])
verts_bridge_end.append(vertsIdx1[-1])
# Valley = first 2 vertices of outer base:
vgroup_valley.extend(vertsIdx2[:1])
# Top/tip vertices:
vgroup_top.extend(vertsIdx4)
faces_tooth_middle_top = createFaces(vertsIdx2[1:], vertsIdx3,
flipped=top)
faces_tooth_outer_top = createFaces(vertsIdx3, vertsIdx4,
flipped=top)
faces_base_top = createFaces(vertsIdx1, vertsIdx2, flipped=top)
faces.extend(faces_base_top)
faces.extend(faces_tooth_middle_top)
faces.extend(faces_tooth_outer_top)
# faces_inside = createFaces(verts_inside_top, verts_inside_bottom)
# faces.extend(faces_inside)
faces_outside = createFaces(verts_outside_top, verts_outside_bottom,
flipped=True)
faces.extend(faces_outside)
if toothCnt == 0:
verts_bridge_first = verts_bridge_start
# Bridge one tooth to the next
if verts_bridge_prev:
faces_bridge = createFaces(verts_bridge_prev, verts_bridge_start)
faces.extend(faces_bridge)
# Remember "end" vertices for next tooth.
verts_bridge_prev = verts_bridge_end
# Bridge the first to the last tooth.
faces_bridge_f_l = createFaces(verts_bridge_prev, verts_bridge_first)
faces.extend(faces_bridge_f_l)
return verts, faces, vgroup_top, vgroup_valley
# Create spokes geometry
# Returns:
# * A list of vertices (list of tuples)
# * A list of faces (list of lists)
#
# teethNum ... Number of teeth on the gear.
# radius ... Radius of the gear, negative for crown gear
# De ... Dedendum, extent of tooth below radius
# base ... Base, extent of gear below radius
# width ... Width, thickness of gear
# conangle ... Conical angle of gear. (radiant)
# rack
# spoke
# spbevel
# spwidth
# splength
# spresol
#
# @todo Finish this
# @todo Create a function that takes a "Gear" and creates a
# matching "Gear Spokes" object
def add_spokes(teethNum, radius, De, base, width=1, conangle=0, rack=0,
spoke=3, spbevel=0.1, spwidth=0.2, splength=1.0, spresol=9):
if teethNum < 2:
return None, None, None, None
if spoke < 2:
return None, None, None, None
t = 2 * pi / teethNum
if rack:
teethNum = 1
scale = (radius - 2 * width * tan(conangle)) / radius
verts = []
faces = []
c = scale # debug
fl = len(verts)
for toothCnt in range(teethNum):
a = toothCnt * t
s = 0 # For test
if toothCnt % spoke == 0:
for d in (-width, width):
sv, edgefaces, edgefaces2, sf = add_spoke(a + s, t, d,
radius * c, De * c, base * c,
spbevel, spwidth, splength, 0, spresol)
verts.extend(sv)
faces.extend([j + fl for j in i] for i in sf)
fl += len(sv)
d1 = fl - len(sv)
d2 = fl - 2 * len(sv)
faces.extend([(i + d2, j + d2, j + d1, i + d1)
for (i, j) in zip(edgefaces[:-1], edgefaces[1:])])
faces.extend([(i + d2, j + d2, j + d1, i + d1)
for (i, j) in zip(edgefaces2[:-1], edgefaces2[1:])])
else:
for d in (-width, width):
sv, edgefaces, edgefaces2, sf = add_spoke(a + s, t, d,
radius * c, De * c, base * c,
spbevel, spwidth, splength, 1, spresol)
verts.extend(sv)
fl += len(sv)
d1 = fl - len(sv)
d2 = fl - 2 * len(sv)
faces.extend([[i + d2, i + 1 + d2, i + 1 + d1, i + d1]
for (i) in range(0, 3)])
faces.extend([[i + d2, i + 1 + d2, i + 1 + d1, i + d1]
for (i) in range(5, 8)])
return verts, faces
# Create worm geometry.
# Returns:
# * A list of vertices
# * A list of faces
# * A list (group) of vertices of the tip
# * A list (group) of vertices of the valley
#
# teethNum ... Number of teeth on the worm
# radius ... Radius of the gear, negative for crown gear
# Ad ... Addendum, extent of tooth above radius
# De ... Dedendum, extent of tooth below radius
# p_angle ... Pressure angle. Skewness of tooth tip. (radiant)
# width ... Width, thickness of gear
# crown ... Inward pointing extend of crown teeth
#
# @todo: Fix teethNum. Some numbers are not possible yet
# @todo: Create start & end geometry (closing faces)
def add_worm(teethNum, rowNum, radius, Ad, De, p_angle,
width=1, skew=radians(11.25), crown=0.0):
worm = teethNum
teethNum = 24
t = 2 * pi / teethNum
verts = []
faces = []
vgroup_top = [] # Vertex group of top/tip? vertices.
vgroup_valley = [] # Vertex group of valley vertices
# width = width / 2.0
edgeloop_prev = []
for Row in range(rowNum):
edgeloop = []
for toothCnt in range(teethNum):
a = toothCnt * t
s = Row * skew
d = Row * width
c = 1
isTooth = False
if toothCnt % (teethNum / worm) != 0:
# Flat
verts1, verts2, verts3, verts4 = add_tooth(a + s, t, d,
radius - De, 0.0, 0.0, 0, p_angle)
# Ignore other verts than the "other base".
verts1 = verts3 = verts4 = []
else:
# Tooth
isTooth = True
verts1, verts2, verts3, verts4 = add_tooth(a + s, t, d,
radius * c, Ad * c, De * c, 0 * c, p_angle, 0, crown)
# Remove various unneeded verts (if we are "inside" the tooth)
del(verts2[2]) # Central vertex in the base of the tooth.
del(verts3[1]) # Central vertex in the middle of the tooth.
vertsIdx2 = list(range(len(verts), len(verts) + len(verts2)))
verts.extend(verts2)
vertsIdx3 = list(range(len(verts), len(verts) + len(verts3)))
verts.extend(verts3)
vertsIdx4 = list(range(len(verts), len(verts) + len(verts4)))
verts.extend(verts4)
if isTooth:
verts_current = []
verts_current.extend(vertsIdx2[:2])
verts_current.append(vertsIdx3[0])
verts_current.extend(vertsIdx4)
verts_current.append(vertsIdx3[-1])
verts_current.append(vertsIdx2[-1])
# Valley = first 2 vertices of outer base:
vgroup_valley.extend(vertsIdx2[:1])
# Top/tip vertices:
vgroup_top.extend(vertsIdx4)
else:
# Flat
verts_current = vertsIdx2
# Valley - all of them.
vgroup_valley.extend(vertsIdx2)
edgeloop.extend(verts_current)
# Create faces between rings/rows.
if edgeloop_prev:
faces_row = createFaces(edgeloop, edgeloop_prev, closed=True)
faces.extend(faces_row)
# Remember last ring/row of vertices for next ring/row iteration.
edgeloop_prev = edgeloop
return verts, faces, vgroup_top, vgroup_valley
def AddGearMesh(self, context):
verts, faces, verts_tip, verts_valley = add_gear(
self.number_of_teeth,
self.radius,
self.addendum,
self.dedendum,
self.base,
self.angle,
width=self.width,
skew=self.skew,
conangle=self.conangle,
crown=self.crown
)
mesh = bpy.data.meshes.new("Gear")
mesh.from_pydata(verts, [], faces)
return mesh, verts_tip, verts_valley
class AddGear(Operator, object_utils.AddObjectHelper):
bl_idname = "mesh.primitive_gear"
bl_label = "Add Gear"
bl_description = "Construct a gear mesh"
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
Gear : BoolProperty(name = "Gear",
default = True,
description = "Gear")
#### change properties
name : StringProperty(name = "Name",
description = "Name")
change : BoolProperty(name = "Change",
default = False,
description = "change Gear")
number_of_teeth: IntProperty(name="Teeth",
description="Number of teeth on the gear",
min=2,
soft_max=1000,
default=12
)
radius: FloatProperty(name="Radius",
description="Radius of the gear, negative for crown gear",
soft_min=-1000.0,
soft_max=1000.0,
unit='LENGTH',
default=1.0
)
addendum: FloatProperty(name="Addendum",
description="Addendum, extent of tooth above radius",
soft_min=-1000.0,
soft_max=1000.0,
unit='LENGTH',
default=0.1
)
dedendum: FloatProperty(name="Dedendum",
description="Dedendum, extent of tooth below radius",
soft_min=-1000.0,
soft_max=1000.0,
unit='LENGTH',
default=0.1
)
angle: FloatProperty(name="Pressure Angle",
description="Pressure angle, skewness of tooth tip",
soft_min=radians(-45.0),
soft_max=radians(45.0),
unit='ROTATION',
default=radians(20.0)
)
base: FloatProperty(name="Base",
description="Base, extent of gear below radius",
soft_min=-1000.0,
soft_max=1000.0,
unit='LENGTH',
default=0.2
)
width: FloatProperty(name="Width",
description="Width, thickness of gear",
soft_min=-1000.0,
soft_max=1000.0,
unit='LENGTH',
default=0.2
)
skew: FloatProperty(name="Skewness",
description="Skew of teeth",
soft_min=radians(-360.0),
soft_max=radians(360.0),
unit='ROTATION',
default=radians(0.0)
)
conangle: FloatProperty(name="Conical Angle",
description="Conical angle of gear",
soft_min=radians(-360.0),
soft_max=radians(360.0),
unit='ROTATION',
default=radians(0.0)
)
crown: FloatProperty(name="Crown",
description="Inward pointing extend of crown teeth",
soft_min=-1000.0,
soft_max=1000.0,
unit='LENGTH',
default=0.0
)
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
layout.separator()
layout.prop(self, 'number_of_teeth')
layout.separator()
layout.prop(self, 'radius')
layout.prop(self, 'width')
layout.prop(self, 'base')
layout.separator()
layout.prop(self, 'dedendum')
layout.prop(self, 'addendum')
layout.separator()
layout.prop(self, 'angle')
layout.prop(self, 'skew')
layout.prop(self, 'conangle')
layout.prop(self, 'crown')
if self.change == False:
layout.separator()
draw_transform_props(self, layout)
@classmethod
def poll(cls, context):
return context.scene is not None
def execute(self, context):
# turn off 'Enter Edit Mode'
use_enter_edit_mode = bpy.context.preferences.edit.use_enter_edit_mode
bpy.context.preferences.edit.use_enter_edit_mode = False
if bpy.context.mode == "OBJECT":
if context.selected_objects != [] and context.active_object and \
(context.active_object.data is not None) and ('Gear' in context.active_object.data.keys()) and \
(self.change == True):
obj = context.active_object
oldmesh = obj.data
oldmeshname = obj.data.name
mesh, verts_tip, verts_valley = AddGearMesh(self, context)
obj.data = mesh
try:
bpy.ops.object.vertex_group_remove(all=True)
except:
pass
for material in oldmesh.materials:
obj.data.materials.append(material)
bpy.data.meshes.remove(oldmesh)
obj.data.name = oldmeshname
else:
mesh, verts_tip, verts_valley = AddGearMesh(self, context)
obj = object_utils.object_data_add(context, mesh, operator=self)
# Create vertex groups from stored vertices.
tipGroup = obj.vertex_groups.new(name='Tips')
tipGroup.add(verts_tip, 1.0, 'ADD')
valleyGroup = obj.vertex_groups.new(name='Valleys')
valleyGroup.add(verts_valley, 1.0, 'ADD')
obj.data["Gear"] = True
obj.data["change"] = False
for prm in GearParameters():
obj.data[prm] = getattr(self, prm)
if bpy.context.mode == "EDIT_MESH":
active_object = context.active_object
name_active_object = active_object.name
bpy.ops.object.mode_set(mode='OBJECT')
mesh, verts_tip, verts_valley = AddGearMesh(self, context)
obj = object_utils.object_data_add(context, mesh, operator=self)
# Create vertex groups from stored vertices.
tipGroup = obj.vertex_groups.new(name='Tips')
tipGroup.add(verts_tip, 1.0, 'ADD')
valleyGroup = obj.vertex_groups.new(name='Valleys')
valleyGroup.add(verts_valley, 1.0, 'ADD')
obj.select_set(True)
active_object.select_set(True)
bpy.context.view_layer.objects.active = active_object
bpy.ops.object.join()
context.active_object.name = name_active_object
bpy.ops.object.mode_set(mode='EDIT')
if use_enter_edit_mode:
bpy.ops.object.mode_set(mode = 'EDIT')
# restore pre operator state
bpy.context.preferences.edit.use_enter_edit_mode = use_enter_edit_mode
return {'FINISHED'}
def invoke(self, context, event):
self.execute(context)
return {'FINISHED'}
def GearParameters():
GearParameters = [
"number_of_teeth",
"radius",
"addendum",
"dedendum",
"base",
"angle",
"width",
"skew",
"conangle",
"crown",
]
return GearParameters
def AddWormGearMesh(self, context):
verts, faces, verts_tip, verts_valley = add_worm(
self.number_of_teeth,
self.number_of_rows,
self.radius,
self.addendum,
self.dedendum,
self.angle,
width=self.row_height,
skew=self.skew,
crown=self.crown
)
mesh = bpy.data.meshes.new("Worm Gear")
mesh.from_pydata(verts, [], faces)
return mesh, verts_tip, verts_valley
class AddWormGear(Operator, object_utils.AddObjectHelper):
bl_idname = "mesh.primitive_worm_gear"
bl_label = "Add Worm Gear"
bl_description = "Construct a worm gear mesh"
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
WormGear : BoolProperty(name = "WormGear",
default = True,
description = "WormGear")
#### change properties
name : StringProperty(name = "Name",
description = "Name")
change : BoolProperty(name = "Change",
default = False,
description = "change WormGear")
number_of_teeth: IntProperty(
name="Teeth",
description="Number of teeth on the gear",
min=1,
soft_max=1000,
default=12
)
number_of_rows: IntProperty(
name="Rows",
description="Number of rows on the worm gear",
min=0,
soft_max=1000,
default=32
)
radius: FloatProperty(
name="Radius",
description="Radius of the gear, negative for crown gear",
soft_min=-1000.0,
soft_max=1000.0,
unit='LENGTH',
default=1.0
)
addendum: FloatProperty(
name="Addendum",
description="Addendum, extent of tooth above radius",
soft_min=-1000.0,
soft_max=1000.0,
unit='LENGTH',
default=0.1
)
dedendum: FloatProperty(
name="Dedendum",
description="Dedendum, extent of tooth below radius",
soft_min=-1000.0,
soft_max=1000.0,
unit='LENGTH',
default=0.1
)
angle: FloatProperty(
name="Pressure Angle",
description="Pressure angle, skewness of tooth tip",
soft_min=radians(-45.0),
soft_max=radians(45.0),
default=radians(20.0),
unit='ROTATION'
)
row_height: FloatProperty(
name="Row Height",
description="Height of each Row",
soft_min=-1000.0,
soft_max=1000.0,
unit='LENGTH',
default=0.2
)
skew: FloatProperty(
name="Skewness per Row",
description="Skew of each row",
soft_min=radians(-360.0),
soft_max=radians(360.0),
default=radians(11.25),
unit='ROTATION'
)
crown: FloatProperty(
name="Crown",
description="Inward pointing extend of crown teeth",
soft_min=-1000.0,
soft_max=1000.0,
unit='LENGTH',
default=0.0
)
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
layout.separator()
layout.prop(self, "number_of_teeth")
layout.prop(self, "number_of_rows")
layout.separator()
layout.prop(self, "radius")
layout.prop(self, "row_height")
layout.separator()
layout.prop(self, "addendum")
layout.prop(self, "dedendum")
layout.separator()
layout.prop(self, "angle")
layout.prop(self, "skew")
layout.prop(self, "crown")
if self.change == False:
layout.separator()
draw_transform_props(self, layout)
def execute(self, context):
# turn off 'Enter Edit Mode'
use_enter_edit_mode = bpy.context.preferences.edit.use_enter_edit_mode
bpy.context.preferences.edit.use_enter_edit_mode = False
if bpy.context.mode == "OBJECT":
if context.selected_objects != [] and context.active_object and \
(context.active_object.data is not None) and ('WormGear' in context.active_object.data.keys()) and \
(self.change == True):
obj = context.active_object
oldmesh = obj.data
oldmeshname = obj.data.name
mesh, verts_tip, verts_valley = AddWormGearMesh(self, context)
obj.data = mesh
try:
bpy.ops.object.vertex_group_remove(all=True)
except:
pass
for material in oldmesh.materials:
obj.data.materials.append(material)
bpy.data.meshes.remove(oldmesh)
obj.data.name = oldmeshname
else:
mesh, verts_tip, verts_valley = AddWormGearMesh(self, context)
obj = object_utils.object_data_add(context, mesh, operator=self)
# Create vertex groups from stored vertices.
tipGroup = obj.vertex_groups.new(name = 'Tips')
tipGroup.add(verts_tip, 1.0, 'ADD')
valleyGroup = obj.vertex_groups.new(name = 'Valleys')
valleyGroup.add(verts_valley, 1.0, 'ADD')
obj.data["WormGear"] = True
obj.data["change"] = False
for prm in WormGearParameters():
obj.data[prm] = getattr(self, prm)
if bpy.context.mode == "EDIT_MESH":
active_object = context.active_object
name_active_object = active_object.name
bpy.ops.object.mode_set(mode='OBJECT')
mesh, verts_tip, verts_valley = AddWormGearMesh(self, context)
obj = object_utils.object_data_add(context, mesh, operator=self)
# Create vertex groups from stored vertices.
tipGroup = obj.vertex_groups.new(name = 'Tips')
tipGroup.add(verts_tip, 1.0, 'ADD')
valleyGroup = obj.vertex_groups.new(name = 'Valleys')
valleyGroup.add(verts_valley, 1.0, 'ADD')
obj.select_set(True)
active_object.select_set(True)
bpy.context.view_layer.objects.active = active_object
bpy.ops.object.join()
context.active_object.name = name_active_object
bpy.ops.object.mode_set(mode='EDIT')
if use_enter_edit_mode:
bpy.ops.object.mode_set(mode = 'EDIT')
# restore pre operator state
bpy.context.preferences.edit.use_enter_edit_mode = use_enter_edit_mode
return {'FINISHED'}
def WormGearParameters():
WormGearParameters = [
"number_of_teeth",
"number_of_rows",
"radius",
"addendum",
"dedendum",
"angle",
"row_height",
"skew",
"crown",
]
return WormGearParameters
@@ -0,0 +1,516 @@
# SPDX-FileCopyrightText: 2010-2022 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later
# Author: Pontiac, Fourmadmen, Dreampainter
import bpy
from bpy.types import Operator
from mathutils import (
Vector,
Quaternion,
)
from math import cos, sin, pi
from bpy.props import (
FloatProperty,
IntProperty,
BoolProperty,
StringProperty,
)
from bpy_extras import object_utils
from .interface import draw_transform_props
# Create a new mesh (object) from verts/edges/faces.
# verts/edges/faces ... List of vertices/edges/faces for the
# new mesh (as used in from_pydata)
# name ... Name of the new mesh (& object)
def create_mesh_object(context, self, verts, edges, faces, name):
# Create new mesh
mesh = bpy.data.meshes.new(name)
# Make a mesh from a list of verts/edges/faces.
mesh.from_pydata(verts, edges, faces)
# Update mesh geometry after adding stuff.
mesh.update()
from bpy_extras import object_utils
return object_utils.object_data_add(context, mesh, operator=self)
# A very simple "bridge" tool.
def createFaces(vertIdx1, vertIdx2, closed=False, flipped=False):
faces = []
if not vertIdx1 or not vertIdx2:
return None
if len(vertIdx1) < 2 and len(vertIdx2) < 2:
return None
fan = False
if (len(vertIdx1) != len(vertIdx2)):
if (len(vertIdx1) == 1 and len(vertIdx2) > 1):
fan = True
else:
return None
total = len(vertIdx2)
if closed:
# Bridge the start with the end
if flipped:
face = [
vertIdx1[0],
vertIdx2[0],
vertIdx2[total - 1]]
if not fan:
face.append(vertIdx1[total - 1])
faces.append(face)
else:
face = [vertIdx2[0], vertIdx1[0]]
if not fan:
face.append(vertIdx1[total - 1])
face.append(vertIdx2[total - 1])
faces.append(face)
# Bridge the rest of the faces
for num in range(total - 1):
if flipped:
if fan:
face = [vertIdx2[num], vertIdx1[0], vertIdx2[num + 1]]
else:
face = [vertIdx2[num], vertIdx1[num],
vertIdx1[num + 1], vertIdx2[num + 1]]
faces.append(face)
else:
if fan:
face = [vertIdx1[0], vertIdx2[num], vertIdx2[num + 1]]
else:
face = [vertIdx1[num], vertIdx2[num],
vertIdx2[num + 1], vertIdx1[num + 1]]
faces.append(face)
return faces
# @todo Clean up vertex&face creation process a bit.
def add_gem(r1, r2, seg, h1, h2):
"""
r1 = pavilion radius
r2 = crown radius
seg = number of segments
h1 = pavilion height
h2 = crown height
Generates the vertices and faces of the gem
"""
verts = []
a = 2.0 * pi / seg # Angle between segments
offset = a / 2.0 # Middle between segments
r3 = ((r1 + r2) / 2.0) / cos(offset) # Middle of crown
r4 = (r1 / 2.0) / cos(offset) # Middle of pavilion
h3 = h2 / 2.0 # Middle of crown height
h4 = -h1 / 2.0 # Middle of pavilion height
# Tip
vert_tip = len(verts)
verts.append(Vector((0.0, 0.0, -h1)))
# Middle vertex of the flat side (crown)
vert_flat = len(verts)
verts.append(Vector((0.0, 0.0, h2)))
edgeloop_flat = []
for i in range(seg):
s1 = sin(i * a)
s2 = sin(offset + i * a)
c1 = cos(i * a)
c2 = cos(offset + i * a)
verts.append((r4 * s1, r4 * c1, h4)) # Middle of pavilion
verts.append((r1 * s2, r1 * c2, 0.0)) # Pavilion
verts.append((r3 * s1, r3 * c1, h3)) # Middle crown
edgeloop_flat.append(len(verts))
verts.append((r2 * s2, r2 * c2, h2)) # Crown
faces = []
for index in range(seg):
i = index * 4
j = ((index + 1) % seg) * 4
faces.append([j + 2, vert_tip, i + 2, i + 3]) # Tip -> Middle of pav
faces.append([j + 2, i + 3, j + 3]) # Middle of pav -> pav
faces.append([j + 3, i + 3, j + 4]) # Pav -> Middle crown
faces.append([j + 4, i + 3, i + 4, i + 5]) # Crown quads
faces.append([j + 4, i + 5, j + 5]) # Middle crown -> crown
faces_flat = createFaces([vert_flat], edgeloop_flat, closed=True, flipped=True)
faces.extend(faces_flat)
return verts, faces
def add_diamond(segments, girdle_radius, table_radius,
crown_height, pavilion_height):
PI_2 = pi * 2.0
z_axis = (0.0, 0.0, -1.0)
verts = []
faces = []
height_flat = crown_height
height_middle = 0.0
height_tip = -pavilion_height
# Middle vertex of the flat side (crown)
vert_flat = len(verts)
verts.append(Vector((0.0, 0.0, height_flat)))
# Tip
vert_tip = len(verts)
verts.append(Vector((0.0, 0.0, height_tip)))
verts_flat = []
verts_girdle = []
for index in range(segments):
quat = Quaternion(z_axis, (index / segments) * PI_2)
# angle = PI_2 * index / segments # UNUSED
# Row for flat side
verts_flat.append(len(verts))
vec = quat @ Vector((table_radius, 0.0, height_flat))
verts.append(vec)
# Row for the middle/girdle
verts_girdle.append(len(verts))
vec = quat @ Vector((girdle_radius, 0.0, height_middle))
verts.append(vec)
# Flat face
faces_flat = createFaces([vert_flat], verts_flat, closed=True,
flipped=True)
# Side face
faces_side = createFaces(verts_girdle, verts_flat, closed=True)
# Tip faces
faces_tip = createFaces([vert_tip], verts_girdle, closed=True)
faces.extend(faces_tip)
faces.extend(faces_side)
faces.extend(faces_flat)
return verts, faces
class AddDiamond(Operator, object_utils.AddObjectHelper):
bl_idname = "mesh.primitive_diamond_add"
bl_label = "Add Diamond"
bl_description = "Construct a diamond mesh"
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
Diamond : BoolProperty(name = "Diamond",
default = True,
description = "Diamond")
#### change properties
name : StringProperty(name = "Name",
description = "Name")
change : BoolProperty(name = "Change",
default = False,
description = "change Diamond")
segments: IntProperty(
name="Segments",
description="Number of segments for the diamond",
min=3,
max=256,
default=32
)
girdle_radius: FloatProperty(
name="Girdle Radius",
description="Girdle radius of the diamond",
min=0.01,
max=9999.0,
default=1.0
)
table_radius: FloatProperty(
name="Table Radius",
description="Girdle radius of the diamond",
min=0.01,
max=9999.0,
default=0.6
)
crown_height: FloatProperty(
name="Crown Height",
description="Crown height of the diamond",
min=0.01,
max=9999.0,
default=0.35
)
pavilion_height: FloatProperty(
name="Pavilion Height",
description="Pavilion height of the diamond",
min=0.01,
max=9999.0,
default=0.8
)
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
layout.separator()
layout.prop(self, "segments")
col = layout.column(align=True)
col.prop(self, "girdle_radius", text='Radius Girdle')
col.prop(self, "table_radius", text='Table')
col = layout.column(align=True)
col.prop(self, "crown_height", text='Height Crown')
col.prop(self, "pavilion_height", text='Pavilion')
if self.change == False:
layout.separator()
draw_transform_props(self, layout)
def execute(self, context):
# turn off 'Enter Edit Mode'
use_enter_edit_mode = bpy.context.preferences.edit.use_enter_edit_mode
bpy.context.preferences.edit.use_enter_edit_mode = False
if bpy.context.mode == "OBJECT":
if context.selected_objects != [] and context.active_object and \
(context.active_object.data is not None) and ('Diamond' in context.active_object.data.keys()) and \
(self.change == True):
obj = context.active_object
oldmesh = obj.data
oldmeshname = obj.data.name
verts, faces = add_diamond(self.segments,
self.girdle_radius,
self.table_radius,
self.crown_height,
self.pavilion_height)
mesh = bpy.data.meshes.new("TMP")
mesh.from_pydata(verts, [], faces)
mesh.update()
obj.data = mesh
for material in oldmesh.materials:
obj.data.materials.append(material)
bpy.data.meshes.remove(oldmesh)
obj.data.name = oldmeshname
else:
verts, faces = add_diamond(self.segments,
self.girdle_radius,
self.table_radius,
self.crown_height,
self.pavilion_height)
obj = create_mesh_object(context, self, verts, [], faces, "Diamond")
obj.data["Diamond"] = True
obj.data["change"] = False
for prm in DiamondParameters():
obj.data[prm] = getattr(self, prm)
if bpy.context.mode == "EDIT_MESH":
active_object = context.active_object
name_active_object = active_object.name
bpy.ops.object.mode_set(mode='OBJECT')
verts, faces = add_diamond(self.segments,
self.girdle_radius,
self.table_radius,
self.crown_height,
self.pavilion_height)
obj = create_mesh_object(context, self, verts, [], faces, "TMP")
obj.select_set(True)
active_object.select_set(True)
bpy.context.view_layer.objects.active = active_object
bpy.ops.object.join()
context.active_object.name = name_active_object
bpy.ops.object.mode_set(mode='EDIT')
if use_enter_edit_mode:
bpy.ops.object.mode_set(mode = 'EDIT')
# restore pre operator state
bpy.context.preferences.edit.use_enter_edit_mode = use_enter_edit_mode
return {'FINISHED'}
def DiamondParameters():
DiamondParameters = [
"segments",
"girdle_radius",
"table_radius",
"crown_height",
"pavilion_height",
]
return DiamondParameters
class AddGem(Operator, object_utils.AddObjectHelper):
bl_idname = "mesh.primitive_gem_add"
bl_label = "Add Gem"
bl_description = "Construct an offset faceted gem mesh"
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
Gem : BoolProperty(name = "Gem",
default = True,
description = "Gem")
#### change properties
name : StringProperty(name = "Name",
description = "Name")
change : BoolProperty(name = "Change",
default = False,
description = "change Gem")
segments: IntProperty(
name="Segments",
description="Longitudial segmentation",
min=3,
max=265,
default=8
)
pavilion_radius: FloatProperty(
name="Radius",
description="Radius of the gem",
min=0.01,
max=9999.0,
default=1.0
)
crown_radius: FloatProperty(
name="Table Radius",
description="Radius of the table(top)",
min=0.01,
max=9999.0,
default=0.6
)
crown_height: FloatProperty(
name="Table height",
description="Height of the top half",
min=0.01,
max=9999.0,
default=0.35
)
pavilion_height: FloatProperty(
name="Pavilion height",
description="Height of bottom half",
min=0.01,
max=9999.0,
default=0.8
)
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
layout.separator()
layout.prop(self, "segments")
col = layout.column(align=True)
col.prop(self, "crown_radius", text='Radius Crown')
col.prop(self, "pavilion_radius", text='Pavilion')
col = layout.column(align=True)
col.prop(self, "crown_height", text='Height Crown')
col.prop(self, "pavilion_height", text='Pavilion')
if self.change == False:
layout.separator()
draw_transform_props(self, layout)
def execute(self, context):
# turn off 'Enter Edit Mode'
use_enter_edit_mode = bpy.context.preferences.edit.use_enter_edit_mode
bpy.context.preferences.edit.use_enter_edit_mode = False
if bpy.context.mode == "OBJECT":
if context.selected_objects != [] and context.active_object and \
(context.active_object.data is not None) and ('Gem' in context.active_object.data.keys()) and \
(self.change == True):
obj = context.active_object
oldmesh = obj.data
oldmeshname = obj.data.name
verts, faces = add_gem(
self.pavilion_radius,
self.crown_radius,
self.segments,
self.pavilion_height,
self.crown_height)
mesh = bpy.data.meshes.new("TMP")
mesh.from_pydata(verts, [], faces)
mesh.update()
obj.data = mesh
for material in oldmesh.materials:
obj.data.materials.append(material)
bpy.data.meshes.remove(oldmesh)
obj.data.name = oldmeshname
else:
verts, faces = add_gem(
self.pavilion_radius,
self.crown_radius,
self.segments,
self.pavilion_height,
self.crown_height)
obj = create_mesh_object(context, self, verts, [], faces, "Gem")
obj.data["Gem"] = True
obj.data["change"] = False
for prm in GemParameters():
obj.data[prm] = getattr(self, prm)
if bpy.context.mode == "EDIT_MESH":
active_object = context.active_object
name_active_object = active_object.name
bpy.ops.object.mode_set(mode='OBJECT')
verts, faces = add_gem(
self.pavilion_radius,
self.crown_radius,
self.segments,
self.pavilion_height,
self.crown_height)
obj = create_mesh_object(context, self, verts, [], faces, "TMP")
obj.select_set(True)
active_object.select_set(True)
bpy.context.view_layer.objects.active = active_object
bpy.ops.object.join()
context.active_object.name = name_active_object
bpy.ops.object.mode_set(mode='EDIT')
if use_enter_edit_mode:
bpy.ops.object.mode_set(mode = 'EDIT')
# restore pre operator state
bpy.context.preferences.edit.use_enter_edit_mode = use_enter_edit_mode
return {'FINISHED'}
def GemParameters():
GemParameters = [
"segments",
"pavilion_radius",
"crown_radius",
"crown_height",
"pavilion_height",
]
return GemParameters
@@ -0,0 +1,341 @@
# SPDX-FileCopyrightText: 2012-2022 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later
# Author: Kayo Phoenix
import bpy
from bpy_extras import object_utils
from math import (
pi, sin,
cos,
)
from bpy.props import (
IntProperty,
BoolProperty,
BoolVectorProperty,
FloatProperty,
FloatVectorProperty,
StringProperty,
)
from .interface import draw_transform_props
class honeycomb_geometry():
def __init__(self, rows, cols, D, E):
self.rows = rows
self.cols = cols
self.D = D
self.E = E
self.hE = 0.5 * self.E
self.R = 0.5 * self.D
self.a = sin(pi / 3)
self.d = self.a * self.D
self.hd = 0.5 * self.d
self.e = self.hE / self.a
self.he = 0.5 * self.e
self.r = self.R - self.e
self.hr = 0.5 * self.r
self.H = self.R * (1.5 * self.rows + 0.5) + self.e
if self.rows > 1:
self.W = self.d * (self.cols + 0.5) + self.E
else:
self.W = self.d * self.cols + self.E
self.hH = 0.5 * self.H
self.hW = 0.5 * self.W
self.sy = -self.hH + self.he + self.R
self.sx = -self.hW + self.hE + self.hd
self.gx = self.hd
self.dy = 1.5 * self.R
self.dx = self.d
def vert(self, row, col):
# full cell
if row >= 0 and row < self.rows and col >= 0 and col < self.cols:
return [0, 1, 2, 3, 4, 5]
# right down corner
if row == -1 and col == self.cols - 1:
return [1, 2]
if row == 0 and self.rows > 1 and col == self.cols:
return [1, 2, 3]
# left down corner
if row == -1 and col == -1:
return [0, 1]
if self.rows % 2:
# left up corner
if row == self.rows and col == -1:
return [4, 5]
# right up corner
if row == self.rows and col == self.cols - 1:
return [3, 4]
if row == self.rows - 1 and self.rows > 1 and col == self.cols:
return [2, 3, 4]
else:
# left up corner
if row == self.rows and col == 0:
return [4, 5]
if row == self.rows - 1 and self.rows > 1 and col == -1:
return [0, 4, 5]
# right up corner
if row == self.rows and col == self.cols:
return [3, 4]
# horizontal lines
if col >= 0 and col < self.cols:
if row == -1:
return [0, 1, 2]
if row == self.rows:
return [3, 4, 5]
# vertical lines
if row >= 0 and row < self.rows:
if col == -1:
if row % 2:
return [0, 1, 4, 5]
else:
return [0, 5]
if col == self.cols:
if row % 2 or self.rows == 1:
return [2, 3]
else:
return [1, 2, 3, 4]
return []
def cell(self, row, col, idx):
cp = [self.sx + self.dx * col, self.sy + self.dy * row, 0] # central point
if row % 2:
cp[0] += self.gx
co = [] # vertices coords
vi = self.vert(row, col)
ap = {}
for i in vi:
a = pi / 6 + i * pi / 3 # angle
ap[i] = idx + len(co)
co.append((cp[0] + cos(a) * self.r, cp[1] + sin(a) * self.r, cp[2]))
return co, ap
def generate(self):
ar = 1
ac = 1
cells = []
verts = []
faces = []
for row in range(-ar, self.rows + ar):
level = []
for col in range(-ac, self.cols + ac):
co, ap = self.cell(row, col, len(verts))
verts += co
level.append(ap)
cells.append(level)
# bottom row
row = 0
for col in range(1, len(cells[row]) - 1):
s = cells[row][col]
l = cells[row][col - 1]
u = cells[row + 1][col]
faces.append((s[1], u[5], u[4], s[2]))
faces.append((s[2], u[4], l[0]))
# top row
row = len(cells) - 1
cs = 0
if row % 2:
cs += 1
for col in range(1 + cs, len(cells[row]) - 1):
s = cells[row][col]
l = cells[row][col - 1]
d = cells[row - 1][col - cs]
faces.append((s[3], l[5], d[1]))
faces.append([s[3], d[1], d[0], s[4]])
# middle rows
for row in range(1, len(cells) - 1):
cs = 0
if row % 2:
cs += 1
for col in range(1, len(cells[row]) - 1):
s = cells[row][col]
l = cells[row][col - 1]
u = cells[row + 1][col - cs]
d = cells[row - 1][col - cs]
faces.append((s[1], u[5], u[4], s[2]))
faces.append((s[2], u[4], l[0]))
faces.append([s[2], l[0], l[5], s[3]])
faces.append((s[3], l[5], d[1]))
faces.append([s[3], d[1], d[0], s[4]])
# right column
row = 0
col = len(cells[row]) - 1
for row in range(1, len(cells) - 1):
cs = 0
if row % 2:
cs += 1
s = cells[row][col]
l = cells[row][col - 1]
u = cells[row + 1][col - cs]
d = cells[row - 1][col - cs]
if row % 2 and row < len(cells) - 2:
faces.append((s[1], u[5], u[4], s[2]))
faces.append((s[2], u[4], l[0]))
faces.append([s[2], l[0], l[5], s[3]])
faces.append((s[3], l[5], d[1]))
if row % 2 and row > 1:
faces.append([s[3], d[1], d[0], s[4]])
# final fix
if not self.rows % 2:
row = len(cells) - 1
s = cells[row][col]
l = cells[row][col - 1]
d = cells[row - 1][col - 1]
faces.append((s[3], l[5], d[1]))
faces.append([s[3], d[1], d[0], s[4]])
return verts, faces
def edge_max(diam):
return diam * sin(pi / 3)
class add_mesh_honeycomb(bpy.types.Operator, object_utils.AddObjectHelper):
bl_idname = "mesh.honeycomb_add"
bl_label = "Add Honeycomb"
bl_description = "Simple honeycomb mesh generator"
bl_options = {'REGISTER', 'UNDO'}
def fix_edge(self, context):
m = edge_max(self.diam)
if self.edge > m:
self.edge = m
HoneyComb : BoolProperty(name = "HoneyComb",
default = True,
description = "HoneyComb")
change : BoolProperty(name = "Change",
default = False,
description = "change HoneyComb")
rows: IntProperty(
name="Rows",
default=2,
min=1, max=100,
description='Number of the rows'
)
cols: IntProperty(
name='Columns',
default=2,
min=1, max=100,
description='Number of the columns'
)
diam: FloatProperty(
name='Cell Diameter',
default=1.0,
min=0.0, update=fix_edge,
description='Diameter of the cell'
)
edge: FloatProperty(
name='Edge Width',
default=0.1,
min=0.0, update=fix_edge,
description='Width of the edge'
)
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
layout.prop(self, 'rows', expand=True)
layout.prop(self, 'cols', expand=True)
layout.prop(self, 'diam', expand=True)
layout.prop(self, 'edge', expand=True)
if self.change == False:
layout.separator()
draw_transform_props(self, layout)
@classmethod
def poll(cls, context):
return context.scene is not None
def execute(self, context):
# turn off 'Enter Edit Mode'
use_enter_edit_mode = bpy.context.preferences.edit.use_enter_edit_mode
bpy.context.preferences.edit.use_enter_edit_mode = False
if bpy.context.mode == "OBJECT":
if context.selected_objects != [] and context.active_object and \
(context.active_object.data is not None) and ('HoneyComb' in context.active_object.data.keys()) and \
(self.change == True):
obj = context.active_object
oldmesh = obj.data
oldmeshname = obj.data.name
comb = honeycomb_geometry(self.rows, self.cols, self.diam, self.edge)
verts, faces = comb.generate()
mesh = bpy.data.meshes.new('HoneyComb')
mesh.from_pydata(verts, [], faces)
obj.data = mesh
for material in oldmesh.materials:
obj.data.materials.append(material)
bpy.data.meshes.remove(oldmesh)
obj.data.name = oldmeshname
else:
comb = honeycomb_geometry(self.rows, self.cols, self.diam, self.edge)
verts, faces = comb.generate()
mesh = bpy.data.meshes.new('HoneyComb')
mesh.from_pydata(verts, [], faces)
obj = object_utils.object_data_add(context, mesh, operator=self)
obj.data["HoneyComb"] = True
obj.data["change"] = False
for prm in HoneyCombParameters():
obj.data[prm] = getattr(self, prm)
if bpy.context.mode == "EDIT_MESH":
active_object = context.active_object
name_active_object = active_object.name
bpy.ops.object.mode_set(mode='OBJECT')
comb = honeycomb_geometry(self.rows, self.cols, self.diam, self.edge)
verts, faces = comb.generate()
mesh = bpy.data.meshes.new('HoneyComb')
mesh.from_pydata(verts, [], faces)
obj = object_utils.object_data_add(context, mesh, operator=self)
obj.select_set(True)
active_object.select_set(True)
bpy.context.view_layer.objects.active = active_object
bpy.ops.object.join()
context.active_object.name = name_active_object
bpy.ops.object.mode_set(mode='EDIT')
if use_enter_edit_mode:
bpy.ops.object.mode_set(mode = 'EDIT')
# restore pre operator state
bpy.context.preferences.edit.use_enter_edit_mode = use_enter_edit_mode
return {'FINISHED'}
def HoneyCombParameters():
HoneyCombParameters = [
"rows",
"cols",
"diam",
"edge",
]
return HoneyCombParameters
@@ -0,0 +1,198 @@
# SPDX-FileCopyrightText: 2015 Sugiany
#
# SPDX-License-Identifier: MIT
import bpy
from bpy_extras.object_utils import (
AddObjectHelper,
object_data_add,
)
from bpy.props import (
IntProperty,
BoolProperty,
BoolVectorProperty,
FloatVectorProperty,
FloatProperty,
)
import mathutils
import copy
from .interface import draw_transform_props
class MengerSponge(object):
FACE_INDICES = [
[3, 7, 4, 0],
[5, 6, 2, 1],
[1, 2, 3, 0],
[7, 6, 5, 4],
[4, 5, 1, 0],
[2, 6, 7, 3],
]
def __init__(self, level):
self.__level = level
self.__max_point_number = 3 ** level
self.__vertices_map = {}
self.__indices = []
self.__face_visibility = {}
self.__faces = []
for x in range(3):
for y in range(3):
for z in range(3):
self.__face_visibility[(x, y, z)] = [
x == 0 or x == 2 and (y == 1 or z == 1),
x == 2 or x == 0 and (y == 1 or z == 1),
y == 0 or y == 2 and (x == 1 or z == 1),
y == 2 or y == 0 and (x == 1 or z == 1),
z == 0 or z == 2 and (y == 1 or x == 1),
z == 2 or z == 0 and (y == 1 or x == 1),
]
def create(self, width, height):
m = self.__max_point_number
points = [
(0, 0, 0),
(m, 0, 0),
(m, 0, m),
(0, 0, m),
(0, m, 0),
(m, m, 0),
(m, m, m),
(0, m, m),
]
self.__make_sub_sponge(points, None, self.__level)
vertices = self.__make_vertices(width, height)
return vertices, self.__faces
def __get_vindex(self, p):
if p in self.__vertices_map:
return self.__vertices_map[p]
index = len(self.__vertices_map)
self.__vertices_map[p] = index
return index
def __make_vertices(self, width, height):
vertices = [None] * len(self.__vertices_map)
w2 = width / 2
h2 = height / 2
w_step = width / self.__max_point_number
h_step = height / self.__max_point_number
for p, i in sorted(self.__vertices_map.items(), key=lambda x: x[1]):
vertices[i] = mathutils.Vector([
p[0] * w_step - w2,
p[1] * w_step - w2,
p[2] * h_step - h2,
])
return vertices
def __make_sub_sponge(self, cur_points, face_vis, depth):
if depth <= 0:
if not face_vis:
face_vis = [True] * 6
cur_point_indices = []
for p in cur_points:
cur_point_indices.append(self.__get_vindex(p))
for i, vis in enumerate(face_vis):
if vis:
f = []
for vi in self.FACE_INDICES[i]:
f.append(cur_point_indices[vi])
self.__faces.append(f)
return
base = cur_points[0]
width = (cur_points[1][0] - base[0]) / 3
local_vert_map = {}
for z in range(4):
for y in range(4):
for x in range(4):
local_vert_map[(x, y, z)] = (
width * x + base[0],
width * y + base[1],
width * z + base[2],
)
for x in range(3):
for y in range(3):
for z in range(3):
if [x, y, z].count(1) > 1:
continue
next_points = [
local_vert_map[(x, y, z)],
local_vert_map[(x + 1, y, z)],
local_vert_map[(x + 1, y, z + 1)],
local_vert_map[(x, y, z + 1)],
local_vert_map[(x, y + 1, z)],
local_vert_map[(x + 1, y + 1, z)],
local_vert_map[(x + 1, y + 1, z + 1)],
local_vert_map[(x, y + 1, z + 1)],
]
visibility = copy.copy(self.__face_visibility[(x, y, z)])
if face_vis:
visibility[0] = visibility[0] and (face_vis[0] or x != 0)
visibility[1] = visibility[1] and (face_vis[1] or x != 2)
visibility[2] = visibility[2] and (face_vis[2] or y != 0)
visibility[3] = visibility[3] and (face_vis[3] or y != 2)
visibility[4] = visibility[4] and (face_vis[4] or z != 0)
visibility[5] = visibility[5] and (face_vis[5] or z != 2)
self.__make_sub_sponge(
next_points,
visibility,
depth - 1)
class AddMengerSponge(bpy.types.Operator, AddObjectHelper):
bl_idname = "mesh.menger_sponge_add"
bl_label = "Menger Sponge"
bl_description = "Construct a menger sponge mesh"
bl_options = {'REGISTER', 'UNDO'}
level: IntProperty(
name="Level",
description="Sponge Level",
min=0, max=4,
default=1,
)
radius: FloatProperty(
name="Width",
description="Sponge Radius",
min=0.01, max=100.0,
default=1.0,
)
layers: BoolVectorProperty(
name="Layers",
size=20,
subtype='LAYER',
options={'HIDDEN', 'SKIP_SAVE'},
)
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
layout.prop(self, 'level')
layout.prop(self, 'radius')
layout.separator()
draw_transform_props(self, layout)
def execute(self, context):
sponger = MengerSponge(self.level)
vertices, faces = sponger.create(self.radius * 2, self.radius * 2)
del sponger
mesh = bpy.data.meshes.new(name='Sponge')
mesh.from_pydata(vertices, [], faces)
uvs = [(0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0)]
mesh.uv_layers.new()
for i, uvloop in enumerate(mesh.uv_layers.active.data):
uvloop.uv = uvs[i % 4]
object_data_add(context, mesh, operator=self)
return {'FINISHED'}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,223 @@
# SPDX-FileCopyrightText: 2011-2022 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later
# Author: Phil Cote, cotejrp1, (http://www.blenderaddons.com)
import bpy
import bmesh
from bpy.props import (
FloatProperty,
IntProperty,
StringProperty,
BoolProperty,
)
from math import pi
from mathutils import (
Quaternion,
Vector,
)
from bpy_extras import object_utils
from .interface import draw_transform_props
def create_step(width, base_level, step_height, num_sides):
axis = [0, 0, -1]
PI2 = pi * 2
rad = width / 2
quat_angles = [(cur_side / num_sides) * PI2
for cur_side in range(num_sides)]
quaternions = [Quaternion(axis, quat_angle)
for quat_angle in quat_angles]
init_vectors = [Vector([rad, 0, base_level])] * len(quaternions)
quat_vector_pairs = list(zip(quaternions, init_vectors))
vectors = [quaternion @ vec for quaternion, vec in quat_vector_pairs]
bottom_list = [(vec.x, vec.y, vec.z) for vec in vectors]
top_list = [(vec.x, vec.y, vec.z + step_height) for vec in vectors]
full_list = bottom_list + top_list
return full_list
def split_list(l, n):
"""
split the blocks up. Credit to oremj for this one.
http://stackoverflow.com/questions/312443/how-do-you-split-a-list-into-evenly-sized-chunks-in-python
"""
n *= 2
returned_list = [l[i: i + n] for i in range(0, len(l), n)]
return returned_list
def get_connector_pairs(lst, n_sides):
# chop off the verts that get used for the base and top
lst = lst[n_sides:]
lst = lst[:-n_sides]
lst = split_list(lst, n_sides)
return lst
def pyramid_mesh(self, context):
all_verts = []
height_offset = 0
cur_width = self.width
for i in range(self.num_steps):
verts_loc = create_step(cur_width, height_offset, self.height,
self.num_sides)
height_offset += self.height
cur_width -= self.reduce_by
all_verts.extend(verts_loc)
mesh = bpy.data.meshes.new("Pyramid")
bm = bmesh.new()
for v_co in all_verts:
bm.verts.new(v_co)
def add_faces(n, block_vert_sets):
for bvs in block_vert_sets:
for i in range(self.num_sides - 1):
bm.faces.new([bvs[i], bvs[i + n], bvs[i + n + 1], bvs[i + 1]])
bm.faces.new([bvs[n - 1], bvs[(n * 2) - 1], bvs[n], bvs[0]])
# get the base and cap faces done.
bm.faces.new(bm.verts[0:self.num_sides])
bm.faces.new(reversed(bm.verts[-self.num_sides:])) # otherwise normal faces intern... T44619.
# side faces
block_vert_sets = split_list(bm.verts, self.num_sides)
add_faces(self.num_sides, block_vert_sets)
# connector faces between faces and faces of the block above it.
connector_pairs = get_connector_pairs(bm.verts, self.num_sides)
add_faces(self.num_sides, connector_pairs)
bm.to_mesh(mesh)
mesh.update()
return mesh
class AddPyramid(bpy.types.Operator, object_utils.AddObjectHelper):
bl_idname = "mesh.primitive_steppyramid_add"
bl_label = "Pyramid"
bl_description = "Construct a step pyramid mesh"
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
Pyramid : BoolProperty(name = "Pyramid",
default = True,
description = "Pyramid")
change : BoolProperty(name = "Change",
default = False,
description = "change Pyramid")
num_sides: IntProperty(
name="Sides",
description="How many sides each step will have",
min=3,
default=4
)
num_steps: IntProperty(
name="Steps",
description="How many steps for the overall pyramid",
min=1,
default=10
)
width: FloatProperty(
name="Width",
description="Initial base step width",
min=0.01,
default=2
)
height: FloatProperty(
name="Height",
description="How tall each step will be",
min=0.01,
default=0.1
)
reduce_by: FloatProperty(
name="Taper",
description="How much to reduce each succeeding step by",
min=.01,
default=.20
)
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
layout.separator()
layout.prop(self, 'num_sides')
layout.prop(self, 'num_steps')
layout.prop(self, 'width')
layout.prop(self, 'height')
layout.prop(self, 'reduce_by')
if self.change == False:
layout.separator()
draw_transform_props(self, layout)
def execute(self, context):
# turn off 'Enter Edit Mode'
use_enter_edit_mode = bpy.context.preferences.edit.use_enter_edit_mode
bpy.context.preferences.edit.use_enter_edit_mode = False
if bpy.context.mode == "OBJECT":
if context.selected_objects != [] and context.active_object and \
(context.active_object.data is not None) and ('Pyramid' in context.active_object.data.keys()) and \
(self.change == True):
obj = context.active_object
oldmesh = obj.data
oldmeshname = obj.data.name
obj.data = pyramid_mesh(self, context)
for material in oldmesh.materials:
obj.data.materials.append(material)
bpy.data.meshes.remove(oldmesh)
obj.data.name = oldmeshname
else:
mesh = pyramid_mesh(self, context)
obj = object_utils.object_data_add(context, mesh, operator=self)
obj.data["Pyramid"] = True
obj.data["change"] = False
for prm in PyramidParameters():
obj.data[prm] = getattr(self, prm)
if bpy.context.mode == "EDIT_MESH":
active_object = context.active_object
name_active_object = active_object.name
bpy.ops.object.mode_set(mode='OBJECT')
mesh = pyramid_mesh(self, context)
obj = object_utils.object_data_add(context, mesh, operator=self)
obj.select_set(True)
active_object.select_set(True)
bpy.context.view_layer.objects.active = active_object
bpy.ops.object.join()
context.active_object.name = name_active_object
bpy.ops.object.mode_set(mode='EDIT')
if use_enter_edit_mode:
bpy.ops.object.mode_set(mode = 'EDIT')
# restore pre operator state
bpy.context.preferences.edit.use_enter_edit_mode = use_enter_edit_mode
return {'FINISHED'}
def PyramidParameters():
PyramidParameters = [
"num_sides",
"num_steps",
"width",
"height",
"reduce_by",
]
return PyramidParameters
@@ -0,0 +1,36 @@
# SPDX-FileCopyrightText: 2019-2022 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later
# Paul "BrikBot" Marshall
# Created: July 1, 2011
# Last Modified: September 26, 2013
# Homepage (blog): http://post.darkarsenic.com/
# //blog.darkarsenic.com/
# Thanks to Meta-Androco, RickyBlender, Ace Dragon, and PKHG for ideas
# and testing.
#
# Coded in IDLE, tested in Blender 2.68a. NumPy Recommended.
# Search for "@todo" to quickly find sections that need work.
if "bpy" in locals():
import importlib
importlib.reload(rockgen)
else:
from . import rockgen
import bpy
# Register:
def register():
rockgen.register()
def unregister():
rockgen.unregister()
if __name__ == "__main__":
register()
@@ -0,0 +1,403 @@
<?xml version="1.0" ?>
<!DOCTYPE settings [
<!ELEMENT settings (default,preset*)>
<!ELEMENT default (title,size,shape,material,random)>
<!ELEMENT preset (title,size,shape,material,random)>
<!ELEMENT title (#PCDATA)>
<!ELEMENT size (scale+,skew+,use_scale_dis,scale_fac)>
<!ELEMENT scale (axis,lower,upper)>
<!ELEMENT axis (#PCDATA)>
<!ELEMENT lower (#PCDATA)>
<!ELEMENT upper (#PCDATA)>
<!ELEMENT skew (axis,value)>
<!ELEMENT value (#PCDATA)>
<!ELEMENT use_scale_dis (#PCDATA)>
<!ELEMENT scale_fac (#PCDATA)>
<!ELEMENT shape (deform,rough,detail,display_detail,smooth_fac,smooth_it)>
<!ELEMENT deform (#PCDATA)>
<!ELEMENT rough (#PCDATA)>
<!ELEMENT detail (#PCDATA)>
<!ELEMENT display_detail (#PCDATA)>
<!ELEMENT smooth_fac (#PCDATA)>
<!ELEMENT smooth_it (#PCDATA)>
<!ELEMENT material (mat_enable,mat_color,mat_bright,mat_rough,mat_spec,mat_hard,mat_use_trans,mat_alpha,mat_cloudy,mat_IOR,mat_mossy)>
<!ELEMENT mat_enable (#PCDATA)>
<!ELEMENT mat_color (#PCDATA)>
<!ELEMENT mat_bright (#PCDATA)>
<!ELEMENT mat_rough (#PCDATA)>
<!ELEMENT mat_spec (#PCDATA)>
<!ELEMENT mat_hard (#PCDATA)>
<!ELEMENT mat_use_trans (#PCDATA)>
<!ELEMENT mat_alpha (#PCDATA)>
<!ELEMENT mat_cloudy (#PCDATA)>
<!ELEMENT mat_IOR (#PCDATA)>
<!ELEMENT mat_mossy (#PCDATA)>
<!ELEMENT random (use_random_seed,user_seed)>
<!ELEMENT use_generate (#PCDATA)>
<!ELEMENT use_random_seed (#PCDATA)>
<!ELEMENT user_seed (#PCDATA)>
<!ATTLIST preset id ID #REQUIRED>
]>
<settings>
<default>
<title>Default</title>
<size>
<scale>
<axis>X</axis>
<lower>1.0</lower>
<upper>1.0</upper>
</scale>
<scale>
<axis>Y</axis>
<lower>1.0</lower>
<upper>1.0</upper>
</scale>
<scale>
<axis>Z</axis>
<lower>1.0</lower>
<upper>1.0</upper>
</scale>
<skew>
<axis>X</axis>
<value>0.0</value>
</skew>
<skew>
<axis>Y</axis>
<value>0.0</value>
</skew>
<skew>
<axis>Z</axis>
<value>0.0</value>
</skew>
<use_scale_dis>False</use_scale_dis>
<scale_fac>[1.0, 1.0, 1.0]</scale_fac>
</size>
<shape>
<deform>5.0</deform>
<rough>2.5</rough>
<detail>3</detail>
<display_detail>2</display_detail>
<smooth_fac>0.0</smooth_fac>
<smooth_it>0</smooth_it>
</shape>
<material>
<mat_enable>False</mat_enable>
<mat_color>[0.5, 0.5, 0.5]</mat_color>
<mat_bright>0.85</mat_bright>
<mat_rough>1.0</mat_rough>
<mat_spec>0.2</mat_spec>
<mat_hard>50</mat_hard>
<mat_use_trans>False</mat_use_trans>
<mat_alpha>0.0</mat_alpha>
<mat_cloudy>0.0</mat_cloudy>
<mat_IOR>1.0</mat_IOR>
<mat_mossy>0.0</mat_mossy>
</material>
<random>
<use_generate>True</use_generate>
<use_random_seed>True</use_random_seed>
<user_seed>1</user_seed>
</random>
</default>
<preset id="1">
<title>River Rock</title>
<size>
<scale>
<axis>X</axis>
<lower>0.5</lower>
<upper>1.25</upper>
</scale>
<scale>
<axis>Y</axis>
<lower>0.5</lower>
<upper>1.25</upper>
</scale>
<scale>
<axis>Z</axis>
<lower>0.5</lower>
<upper>1.25</upper>
</scale>
<skew>
<axis>X</axis>
<value>-0.5</value>
</skew>
<skew>
<axis>Y</axis>
<value>-0.5</value>
</skew>
<skew>
<axis>Z</axis>
<value>-0.5</value>
</skew>
<use_scale_dis>False</use_scale_dis>
<scale_fac>[1.0, 1.0, 1.0]</scale_fac>
</size>
<shape>
<deform>3.0</deform>
<rough>2.0</rough>
<detail>2</detail>
<display_detail>2</display_detail>
<smooth_fac>2.0</smooth_fac>
<smooth_it>2</smooth_it>
</shape>
<material>
<mat_enable>True</mat_enable>
<mat_color>[0.5, 0.5, 0.5]</mat_color>
<mat_bright>0.85</mat_bright>
<mat_rough>0.125</mat_rough>
<mat_spec>0.5</mat_spec>
<mat_hard>50</mat_hard>
<mat_use_trans>False</mat_use_trans>
<mat_alpha>0.0</mat_alpha>
<mat_cloudy>0.0</mat_cloudy>
<mat_IOR>1.0</mat_IOR>
<mat_mossy>0.0</mat_mossy>
</material>
<random>
<use_generate>True</use_generate>
<use_random_seed>True</use_random_seed>
<user_seed>1</user_seed>
</random>
</preset>
<preset id="2">
<title>Asteroid</title>
<size>
<scale>
<axis>X</axis>
<lower>1.0</lower>
<upper>5.0</upper>
</scale>
<scale>
<axis>Y</axis>
<lower>1.0</lower>
<upper>5.0</upper>
</scale>
<scale>
<axis>Z</axis>
<lower>1.0</lower>
<upper>5.0</upper>
</scale>
<skew>
<axis>X</axis>
<value>0.0</value>
</skew>
<skew>
<axis>Y</axis>
<value>0.0</value>
</skew>
<skew>
<axis>Z</axis>
<value>0.0</value>
</skew>
<use_scale_dis>False</use_scale_dis>
<scale_fac>[1.0, 1.0, 1.0]</scale_fac>
</size>
<shape>
<deform>7.5</deform>
<rough>3.0</rough>
<detail>4</detail>
<display_detail>3</display_detail>
<smooth_fac>0.0</smooth_fac>
<smooth_it>0</smooth_it>
</shape>
<material>
<mat_enable>True</mat_enable>
<mat_color>[0.3, 0.25, 0.2]</mat_color>
<mat_bright>0.85</mat_bright>
<mat_rough>1.5</mat_rough>
<mat_spec>0.25</mat_spec>
<mat_hard>30</mat_hard>
<mat_use_trans>False</mat_use_trans>
<mat_alpha>0.0</mat_alpha>
<mat_cloudy>0.0</mat_cloudy>
<mat_IOR>1.0</mat_IOR>
<mat_mossy>0.0</mat_mossy>
</material>
<random>
<use_generate>True</use_generate>
<use_random_seed>True</use_random_seed>
<user_seed>1</user_seed>
</random>
</preset>
<preset id="3">
<title>Sandstone</title>
<size>
<scale>
<axis>X</axis>
<lower>1.0</lower>
<upper>1.0</upper>
</scale>
<scale>
<axis>Y</axis>
<lower>1.0</lower>
<upper>1.0</upper>
</scale>
<scale>
<axis>Z</axis>
<lower>1.0</lower>
<upper>1.0</upper>
</scale>
<skew>
<axis>X</axis>
<value>0.0</value>
</skew>
<skew>
<axis>Y</axis>
<value>0.0</value>
</skew>
<skew>
<axis>Z</axis>
<value>0.0</value>
</skew>
<use_scale_dis>True</use_scale_dis>
<scale_fac>[5.0, 5.0, 0.1]</scale_fac>
</size>
<shape>
<deform>0.5</deform>
<rough>1.0</rough>
<detail>3</detail>
<display_detail>3</display_detail>
<smooth_fac>2.0</smooth_fac>
<smooth_it>2</smooth_it>
</shape>
<material>
<mat_enable>True</mat_enable>
<mat_color>[0.5, 0.4, 0.35]</mat_color>
<mat_bright>0.85</mat_bright>
<mat_rough>0.1</mat_rough>
<mat_spec>0.2</mat_spec>
<mat_hard>50</mat_hard>
<mat_use_trans>False</mat_use_trans>
<mat_alpha>0.0</mat_alpha>
<mat_cloudy>0.0</mat_cloudy>
<mat_IOR>1.0</mat_IOR>
<mat_mossy>0.0</mat_mossy>
</material>
<random>
<use_generate>True</use_generate>
<use_random_seed>True</use_random_seed>
<user_seed>1</user_seed>
</random>
</preset>
<preset id="4">
<title>Ice</title>
<size>
<scale>
<axis>X</axis>
<lower>0.0</lower>
<upper>2.0</upper>
</scale>
<scale>
<axis>Y</axis>
<lower>0.0</lower>
<upper>2.0</upper>
</scale>
<scale>
<axis>Z</axis>
<lower>0.0</lower>
<upper>2.0</upper>
</scale>
<skew>
<axis>X</axis>
<value>0.0</value>
</skew>
<skew>
<axis>Y</axis>
<value>0.0</value>
</skew>
<skew>
<axis>Z</axis>
<value>0.0</value>
</skew>
<use_scale_dis>False</use_scale_dis>
<scale_fac>[1.0, 1.0, 1.0]</scale_fac>
</size>
<shape>
<deform>5.0</deform>
<rough>1.0</rough>
<detail>3</detail>
<display_detail>2</display_detail>
<smooth_fac>2.0</smooth_fac>
<smooth_it>1</smooth_it>
</shape>
<material>
<mat_enable>True</mat_enable>
<mat_color>[0.9, 0.95, 1.0]</mat_color>
<mat_bright>0.85</mat_bright>
<mat_rough>0.25</mat_rough>
<mat_spec>0.2</mat_spec>
<mat_hard>50</mat_hard>
<mat_use_trans>True</mat_use_trans>
<mat_alpha>0.9</mat_alpha>
<mat_cloudy>0.1</mat_cloudy>
<mat_IOR>1.31</mat_IOR>
<mat_mossy>0.0</mat_mossy>
</material>
<random>
<use_generate>True</use_generate>
<use_random_seed>True</use_random_seed>
<user_seed>1</user_seed>
</random>
</preset>
<preset id="5">
<title>Fake Ocean</title>
<size>
<scale>
<axis>X</axis>
<lower>10.0</lower>
<upper>10.0</upper>
</scale>
<scale>
<axis>Y</axis>
<lower>10.0</lower>
<upper>10.0</upper>
</scale>
<scale>
<axis>Z</axis>
<lower>0.0</lower>
<upper>0.0</upper>
</scale>
<skew>
<axis>X</axis>
<value>0.0</value>
</skew>
<skew>
<axis>Y</axis>
<value>0.0</value>
</skew>
<skew>
<axis>Z</axis>
<value>0.0</value>
</skew>
<use_scale_dis>False</use_scale_dis>
<scale_fac>[1.0, 1.0, 1.0]</scale_fac>
</size>
<shape>
<deform>7.5</deform>
<rough>3.0</rough>
<detail>4</detail>
<display_detail>3</display_detail>
<smooth_fac>0.0</smooth_fac>
<smooth_it>0</smooth_it>
</shape>
<material>
<mat_enable>True</mat_enable>
<mat_color>[0.1, 0.12, 0.125]</mat_color>
<mat_bright>0.85</mat_bright>
<mat_rough>1.5</mat_rough>
<mat_spec>0.25</mat_spec>
<mat_hard>30</mat_hard>
<mat_use_trans>True</mat_use_trans>
<mat_alpha>0.5</mat_alpha>
<mat_cloudy>0.5</mat_cloudy>
<mat_IOR>1.333</mat_IOR>
<mat_mossy>0.0</mat_mossy>
</material>
<random>
<use_generate>True</use_generate>
<use_random_seed>True</use_random_seed>
<user_seed>1</user_seed>
</random>
</preset>
</settings>
@@ -0,0 +1,403 @@
<?xml version="1.0" ?>
<!DOCTYPE settings [
<!ELEMENT settings (default,preset*)>
<!ELEMENT default (title,size,shape,material,random)>
<!ELEMENT preset (title,size,shape,material,random)>
<!ELEMENT title (#PCDATA)>
<!ELEMENT size (scale+,skew+,use_scale_dis,scale_fac)>
<!ELEMENT scale (axis,lower,upper)>
<!ELEMENT axis (#PCDATA)>
<!ELEMENT lower (#PCDATA)>
<!ELEMENT upper (#PCDATA)>
<!ELEMENT skew (axis,value)>
<!ELEMENT value (#PCDATA)>
<!ELEMENT use_scale_dis (#PCDATA)>
<!ELEMENT scale_fac (#PCDATA)>
<!ELEMENT shape (deform,rough,detail,display_detail,smooth_fac,smooth_it)>
<!ELEMENT deform (#PCDATA)>
<!ELEMENT rough (#PCDATA)>
<!ELEMENT detail (#PCDATA)>
<!ELEMENT display_detail (#PCDATA)>
<!ELEMENT smooth_fac (#PCDATA)>
<!ELEMENT smooth_it (#PCDATA)>
<!ELEMENT material (mat_enable,mat_color,mat_bright,mat_rough,mat_spec,mat_hard,mat_use_trans,mat_alpha,mat_cloudy,mat_IOR,mat_mossy)>
<!ELEMENT mat_enable (#PCDATA)>
<!ELEMENT mat_color (#PCDATA)>
<!ELEMENT mat_bright (#PCDATA)>
<!ELEMENT mat_rough (#PCDATA)>
<!ELEMENT mat_spec (#PCDATA)>
<!ELEMENT mat_hard (#PCDATA)>
<!ELEMENT mat_use_trans (#PCDATA)>
<!ELEMENT mat_alpha (#PCDATA)>
<!ELEMENT mat_cloudy (#PCDATA)>
<!ELEMENT mat_IOR (#PCDATA)>
<!ELEMENT mat_mossy (#PCDATA)>
<!ELEMENT random (use_random_seed,user_seed)>
<!ELEMENT use_generate (#PCDATA)>
<!ELEMENT use_random_seed (#PCDATA)>
<!ELEMENT user_seed (#PCDATA)>
<!ATTLIST preset id ID #REQUIRED>
]>
<settings>
<default>
<title>Default</title>
<size>
<scale>
<axis>X</axis>
<lower>1.0</lower>
<upper>1.0</upper>
</scale>
<scale>
<axis>Y</axis>
<lower>1.0</lower>
<upper>1.0</upper>
</scale>
<scale>
<axis>Z</axis>
<lower>1.0</lower>
<upper>1.0</upper>
</scale>
<skew>
<axis>X</axis>
<value>0.0</value>
</skew>
<skew>
<axis>Y</axis>
<value>0.0</value>
</skew>
<skew>
<axis>Z</axis>
<value>0.0</value>
</skew>
<use_scale_dis>False</use_scale_dis>
<scale_fac>[1.0, 1.0, 1.0]</scale_fac>
</size>
<shape>
<deform>5.0</deform>
<rough>2.5</rough>
<detail>3</detail>
<display_detail>2</display_detail>
<smooth_fac>0.0</smooth_fac>
<smooth_it>0</smooth_it>
</shape>
<material>
<mat_enable>False</mat_enable>
<mat_color>[0.5, 0.5, 0.5]</mat_color>
<mat_bright>0.85</mat_bright>
<mat_rough>1.0</mat_rough>
<mat_spec>0.2</mat_spec>
<mat_hard>50</mat_hard>
<mat_use_trans>False</mat_use_trans>
<mat_alpha>0.0</mat_alpha>
<mat_cloudy>0.0</mat_cloudy>
<mat_IOR>1.0</mat_IOR>
<mat_mossy>0.0</mat_mossy>
</material>
<random>
<use_generate>True</use_generate>
<use_random_seed>True</use_random_seed>
<user_seed>1</user_seed>
</random>
</default>
<preset id="1">
<title>River Rock</title>
<size>
<scale>
<axis>X</axis>
<lower>0.5</lower>
<upper>1.25</upper>
</scale>
<scale>
<axis>Y</axis>
<lower>0.5</lower>
<upper>1.25</upper>
</scale>
<scale>
<axis>Z</axis>
<lower>0.5</lower>
<upper>1.25</upper>
</scale>
<skew>
<axis>X</axis>
<value>-0.5</value>
</skew>
<skew>
<axis>Y</axis>
<value>-0.5</value>
</skew>
<skew>
<axis>Z</axis>
<value>-0.5</value>
</skew>
<use_scale_dis>False</use_scale_dis>
<scale_fac>[1.0, 1.0, 1.0]</scale_fac>
</size>
<shape>
<deform>3.0</deform>
<rough>2.0</rough>
<detail>2</detail>
<display_detail>2</display_detail>
<smooth_fac>2.0</smooth_fac>
<smooth_it>2</smooth_it>
</shape>
<material>
<mat_enable>True</mat_enable>
<mat_color>[0.5, 0.5, 0.5]</mat_color>
<mat_bright>0.85</mat_bright>
<mat_rough>0.125</mat_rough>
<mat_spec>0.5</mat_spec>
<mat_hard>50</mat_hard>
<mat_use_trans>False</mat_use_trans>
<mat_alpha>0.0</mat_alpha>
<mat_cloudy>0.0</mat_cloudy>
<mat_IOR>1.0</mat_IOR>
<mat_mossy>0.0</mat_mossy>
</material>
<random>
<use_generate>True</use_generate>
<use_random_seed>True</use_random_seed>
<user_seed>1</user_seed>
</random>
</preset>
<preset id="2">
<title>Asteroid</title>
<size>
<scale>
<axis>X</axis>
<lower>1.0</lower>
<upper>5.0</upper>
</scale>
<scale>
<axis>Y</axis>
<lower>1.0</lower>
<upper>5.0</upper>
</scale>
<scale>
<axis>Z</axis>
<lower>1.0</lower>
<upper>5.0</upper>
</scale>
<skew>
<axis>X</axis>
<value>0.0</value>
</skew>
<skew>
<axis>Y</axis>
<value>0.0</value>
</skew>
<skew>
<axis>Z</axis>
<value>0.0</value>
</skew>
<use_scale_dis>False</use_scale_dis>
<scale_fac>[1.0, 1.0, 1.0]</scale_fac>
</size>
<shape>
<deform>7.5</deform>
<rough>3.0</rough>
<detail>4</detail>
<display_detail>3</display_detail>
<smooth_fac>0.0</smooth_fac>
<smooth_it>0</smooth_it>
</shape>
<material>
<mat_enable>True</mat_enable>
<mat_color>[0.3, 0.25, 0.2]</mat_color>
<mat_bright>0.85</mat_bright>
<mat_rough>1.5</mat_rough>
<mat_spec>0.25</mat_spec>
<mat_hard>30</mat_hard>
<mat_use_trans>False</mat_use_trans>
<mat_alpha>0.0</mat_alpha>
<mat_cloudy>0.0</mat_cloudy>
<mat_IOR>1.0</mat_IOR>
<mat_mossy>0.0</mat_mossy>
</material>
<random>
<use_generate>True</use_generate>
<use_random_seed>True</use_random_seed>
<user_seed>1</user_seed>
</random>
</preset>
<preset id="3">
<title>Sandstone</title>
<size>
<scale>
<axis>X</axis>
<lower>1.0</lower>
<upper>1.0</upper>
</scale>
<scale>
<axis>Y</axis>
<lower>1.0</lower>
<upper>1.0</upper>
</scale>
<scale>
<axis>Z</axis>
<lower>1.0</lower>
<upper>1.0</upper>
</scale>
<skew>
<axis>X</axis>
<value>0.0</value>
</skew>
<skew>
<axis>Y</axis>
<value>0.0</value>
</skew>
<skew>
<axis>Z</axis>
<value>0.0</value>
</skew>
<use_scale_dis>True</use_scale_dis>
<scale_fac>[5.0, 5.0, 0.1]</scale_fac>
</size>
<shape>
<deform>0.5</deform>
<rough>1.0</rough>
<detail>3</detail>
<display_detail>3</display_detail>
<smooth_fac>2.0</smooth_fac>
<smooth_it>2</smooth_it>
</shape>
<material>
<mat_enable>True</mat_enable>
<mat_color>[0.5, 0.4, 0.35]</mat_color>
<mat_bright>0.85</mat_bright>
<mat_rough>0.1</mat_rough>
<mat_spec>0.2</mat_spec>
<mat_hard>50</mat_hard>
<mat_use_trans>False</mat_use_trans>
<mat_alpha>0.0</mat_alpha>
<mat_cloudy>0.0</mat_cloudy>
<mat_IOR>1.0</mat_IOR>
<mat_mossy>0.0</mat_mossy>
</material>
<random>
<use_generate>True</use_generate>
<use_random_seed>True</use_random_seed>
<user_seed>1</user_seed>
</random>
</preset>
<preset id="4">
<title>Ice</title>
<size>
<scale>
<axis>X</axis>
<lower>0.0</lower>
<upper>2.0</upper>
</scale>
<scale>
<axis>Y</axis>
<lower>0.0</lower>
<upper>2.0</upper>
</scale>
<scale>
<axis>Z</axis>
<lower>0.0</lower>
<upper>2.0</upper>
</scale>
<skew>
<axis>X</axis>
<value>0.0</value>
</skew>
<skew>
<axis>Y</axis>
<value>0.0</value>
</skew>
<skew>
<axis>Z</axis>
<value>0.0</value>
</skew>
<use_scale_dis>False</use_scale_dis>
<scale_fac>[1.0, 1.0, 1.0]</scale_fac>
</size>
<shape>
<deform>5.0</deform>
<rough>1.0</rough>
<detail>3</detail>
<display_detail>2</display_detail>
<smooth_fac>2.0</smooth_fac>
<smooth_it>1</smooth_it>
</shape>
<material>
<mat_enable>True</mat_enable>
<mat_color>[0.9, 0.95, 1.0]</mat_color>
<mat_bright>0.85</mat_bright>
<mat_rough>0.25</mat_rough>
<mat_spec>0.2</mat_spec>
<mat_hard>50</mat_hard>
<mat_use_trans>True</mat_use_trans>
<mat_alpha>0.9</mat_alpha>
<mat_cloudy>0.1</mat_cloudy>
<mat_IOR>1.31</mat_IOR>
<mat_mossy>0.0</mat_mossy>
</material>
<random>
<use_generate>True</use_generate>
<use_random_seed>True</use_random_seed>
<user_seed>1</user_seed>
</random>
</preset>
<preset id="5">
<title>Fake Ocean</title>
<size>
<scale>
<axis>X</axis>
<lower>10.0</lower>
<upper>10.0</upper>
</scale>
<scale>
<axis>Y</axis>
<lower>10.0</lower>
<upper>10.0</upper>
</scale>
<scale>
<axis>Z</axis>
<lower>0.0</lower>
<upper>0.0</upper>
</scale>
<skew>
<axis>X</axis>
<value>0.0</value>
</skew>
<skew>
<axis>Y</axis>
<value>0.0</value>
</skew>
<skew>
<axis>Z</axis>
<value>0.0</value>
</skew>
<use_scale_dis>False</use_scale_dis>
<scale_fac>[1.0, 1.0, 1.0]</scale_fac>
</size>
<shape>
<deform>7.5</deform>
<rough>3.0</rough>
<detail>4</detail>
<display_detail>3</display_detail>
<smooth_fac>0.0</smooth_fac>
<smooth_it>0</smooth_it>
</shape>
<material>
<mat_enable>True</mat_enable>
<mat_color>[0.1, 0.12, 0.125]</mat_color>
<mat_bright>0.85</mat_bright>
<mat_rough>1.5</mat_rough>
<mat_spec>0.25</mat_spec>
<mat_hard>30</mat_hard>
<mat_use_trans>True</mat_use_trans>
<mat_alpha>0.5</mat_alpha>
<mat_cloudy>0.5</mat_cloudy>
<mat_IOR>1.333</mat_IOR>
<mat_mossy>0.0</mat_mossy>
</material>
<random>
<use_generate>True</use_generate>
<use_random_seed>True</use_random_seed>
<user_seed>1</user_seed>
</random>
</preset>
</settings>
@@ -0,0 +1,161 @@
# SPDX-FileCopyrightText: 2019-2022 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later
# This try block allows for the script to psudo-intelligently select the
# appropriate random to use. If Numpy's random is present it will use that.
# If Numpy's random is not present, it will through a "module not found"
# exception and instead use the slower built-in random that Python has.
try:
from numpy.random import random_integers as randint
from numpy.random import normal as gauss
from numpy.random import (
beta,
uniform,
)
except:
from random import (
randint,
gauss,
uniform,
)
from random import betavariate as beta
from .utils import skewedGauss
def randomizeTexture(texture, level=1):
'''
Set the values for a texture from parameters.
param: texture - bpy.data.texture to modify.
level - designated tweaked settings to use
-> Below 10 is a displacement texture
-> Between 10 and 20 is a base material texture
'''
noises = ['BLENDER_ORIGINAL', 'ORIGINAL_PERLIN', 'IMPROVED_PERLIN',
'VORONOI_F1', 'VORONOI_F2', 'VORONOI_F3', 'VORONOI_F4',
'VORONOI_F2_F1', 'VORONOI_CRACKLE']
if texture.type == 'CLOUDS':
if randint(0, 1) == 0:
texture.noise_type = 'SOFT_NOISE'
else:
texture.noise_type = 'HARD_NOISE'
if level != 11:
tempInt = randint(0, 6)
else:
tempInt = randint(0, 8)
texture.noise_basis = noises[tempInt]
texture.noise_depth = 8
if level == 0:
texture.noise_scale = gauss(0.625, 1 / 24)
elif level == 2:
texture.noise_scale = 0.15
elif level == 11:
texture.noise_scale = gauss(0.5, 1 / 24)
if texture.noise_basis in ['BLENDER_ORIGINAL', 'ORIGINAL_PERLIN',
'IMPROVED_PERLIN', 'VORONOI_F1']:
texture.intensity = gauss(1, 1 / 6)
texture.contrast = gauss(4, 1 / 3)
elif texture.noise_basis in ['VORONOI_F2', 'VORONOI_F3', 'VORONOI_F4']:
texture.intensity = gauss(0.25, 1 / 12)
texture.contrast = gauss(2, 1 / 6)
elif texture.noise_basis == 'VORONOI_F2_F1':
texture.intensity = gauss(0.5, 1 / 6)
texture.contrast = gauss(2, 1 / 6)
elif texture.noise_basis == 'VORONOI_CRACKLE':
texture.intensity = gauss(0.5, 1 / 6)
texture.contrast = gauss(2, 1 / 6)
elif texture.type == 'MUSGRAVE':
# musgraveType = ['MULTIFRACTAL', 'RIDGED_MULTIFRACTAL',
# 'HYBRID_MULTIFRACTAL', 'FBM', 'HETERO_TERRAIN']
texture.musgrave_type = 'MULTIFRACTAL'
texture.dimension_max = abs(gauss(0, 0.6)) + 0.2
texture.lacunarity = beta(3, 8) * 8.2 + 1.8
if level == 0:
texture.noise_scale = gauss(0.625, 1 / 24)
texture.noise_intensity = 0.2
texture.octaves = 1.0
elif level == 2:
texture.intensity = gauss(1, 1 / 6)
texture.contrast = 0.2
texture.noise_scale = 0.15
texture.octaves = 8.0
elif level == 10:
texture.intensity = gauss(0.25, 1 / 12)
texture.contrast = gauss(1.5, 1 / 6)
texture.noise_scale = 0.5
texture.octaves = 8.0
elif level == 12:
texture.octaves = uniform(1, 3)
elif level > 12:
texture.octaves = uniform(2, 8)
else:
texture.intensity = gauss(1, 1 / 6)
texture.contrast = 0.2
texture.octaves = 8.0
elif texture.type == 'DISTORTED_NOISE':
tempInt = randint(0, 8)
texture.noise_distortion = noises[tempInt]
tempInt = randint(0, 8)
texture.noise_basis = noises[tempInt]
texture.distortion = skewedGauss(2.0, 2.6666, (0.0, 10.0), False)
if level == 0:
texture.noise_scale = gauss(0.625, 1 / 24)
elif level == 2:
texture.noise_scale = 0.15
elif level >= 12:
texture.noise_scale = gauss(0.2, 1 / 48)
elif texture.type == 'STUCCI':
stucciTypes = ['PLASTIC', 'WALL_IN', 'WALL_OUT']
if randint(0, 1) == 0:
texture.noise_type = 'SOFT_NOISE'
else:
texture.noise_type = 'HARD_NOISE'
tempInt = randint(0, 2)
texture.stucci_type = stucciTypes[tempInt]
if level == 0:
tempInt = randint(0, 6)
texture.noise_basis = noises[tempInt]
texture.noise_scale = gauss(0.625, 1 / 24)
elif level == 2:
tempInt = randint(0, 6)
texture.noise_basis = noises[tempInt]
texture.noise_scale = 0.15
elif level >= 12:
tempInt = randint(0, 6)
texture.noise_basis = noises[tempInt]
texture.noise_scale = gauss(0.2, 1 / 30)
else:
tempInt = randint(0, 6)
texture.noise_basis = noises[tempInt]
elif texture.type == 'VORONOI':
metrics = ['DISTANCE', 'DISTANCE_SQUARED', 'MANHATTAN', 'CHEBYCHEV',
'MINKOVSKY_HALF', 'MINKOVSKY_FOUR', 'MINKOVSKY']
# Settings for first displacement level:
if level == 0:
tempInt = randint(0, 1)
texture.distance_metric = metrics[tempInt]
texture.noise_scale = gauss(0.625, 1 / 24)
texture.contrast = 0.5
texture.intensity = 0.7
elif level == 2:
texture.noise_scale = 0.15
tempInt = randint(0, 6)
texture.distance_metric = metrics[tempInt]
elif level >= 12:
tempInt = randint(0, 1)
texture.distance_metric = metrics[tempInt]
texture.noise_scale = gauss(0.125, 1 / 48)
texture.contrast = 0.5
texture.intensity = 0.7
else:
tempInt = randint(0, 6)
texture.distance_metric = metrics[tempInt]
return
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,144 @@
# SPDX-FileCopyrightText: 2019-2022 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later
# Paul "BrikBot" Marshall
# Created: July 1, 2011
# Last Modified: November 17, 2011
# Homepage (blog): http://post.darkarsenic.com/
# //blog.darkarsenic.com/
# Thanks to Meta-Androco, RickyBlender, Ace Dragon, and PKHG for ideas
# and testing.
#
# Coded in IDLE, tested in Blender 2.59. NumPy Recommended.
# Search for "@todo" to quickly find sections that need work.
import inspect
import shutil
from . import utils
from xml.dom import minidom
basePath = inspect.getfile(inspect.currentframe())[0:-len("settings.py")]
path = basePath + "add_mesh_rocks.xml"
try:
source = minidom.parse(path)
# print("Rock generator settings file found:\n" + path)
except:
print("Rock generator settings file not found. Creating settings file.")
shutil.copy(basePath + "factory.xml", path)
source = minidom.parse(path)
xmlDefault = source.getElementsByTagName('default')[0]
xmlPresets = source.getElementsByTagName('preset')
default = []
presets = []
# ----- Gets and Sets -----#
def getDefault():
global default
return default
def getPresetLists():
global presets
return presets
def getPreset(ID=0):
global presets
return presets[ID]
# ---------- Core ----------#
def parse():
global xmlDefault
global xmlPresets
global default
global presets
# Parse default values
default = parseNode(xmlDefault)
# Parse preset values
for setting in xmlPresets:
presets.append(parseNode(setting))
return '{FINISHED}'
# Takes a node and parses it for data. Relies on that setting.xml has
# a valid format as specified by the DTD.
# For some reason minidom places an empty child node for every other node.
def parseNode(setting, title=True):
loc = 1
if title:
# Preset name (xmlPreset.childNodes[1]):
title = setting.childNodes[loc].childNodes[0].data
loc += 2
# Preset size values (xmlPreset.childNodes[3]):
scaleX = [float(setting.childNodes[loc].childNodes[1].childNodes[3].childNodes[0].data),
float(setting.childNodes[loc].childNodes[1].childNodes[5].childNodes[0].data)]
scaleY = [float(setting.childNodes[loc].childNodes[3].childNodes[3].childNodes[0].data),
float(setting.childNodes[loc].childNodes[3].childNodes[5].childNodes[0].data)]
scaleZ = [float(setting.childNodes[loc].childNodes[5].childNodes[3].childNodes[0].data),
float(setting.childNodes[loc].childNodes[5].childNodes[5].childNodes[0].data)]
skewX = float(setting.childNodes[loc].childNodes[7].childNodes[3].childNodes[0].data)
skewY = float(setting.childNodes[loc].childNodes[9].childNodes[3].childNodes[0].data)
skewZ = float(setting.childNodes[loc].childNodes[11].childNodes[3].childNodes[0].data)
if setting.childNodes[loc].childNodes[13].childNodes[0].data == 'False':
use_scale_dis = False
else:
use_scale_dis = True
scale_fac = utils.toList(setting.childNodes[loc].childNodes[15].childNodes[0].data)
loc += 2
# Presst shape values (xmlPreset.childNodes[5]):
deform = float(setting.childNodes[loc].childNodes[1].childNodes[0].data)
rough = float(setting.childNodes[loc].childNodes[3].childNodes[0].data)
detail = int(setting.childNodes[loc].childNodes[5].childNodes[0].data)
display_detail = int(setting.childNodes[loc].childNodes[7].childNodes[0].data)
smooth_fac = float(setting.childNodes[loc].childNodes[9].childNodes[0].data)
smooth_it = int(setting.childNodes[loc].childNodes[11].childNodes[0].data)
loc += 2
# Preset material values (xmlPreset.childNodes[7]):
loc += 2
# Preset random values (xmlPreset.childNodes[9]):
if setting.childNodes[loc].childNodes[1].childNodes[0].data == 'True':
use_generate = True
else:
use_generate = False
if setting.childNodes[loc].childNodes[3].childNodes[0].data == 'False':
use_random_seed = False
else:
use_random_seed = True
user_seed = int(setting.childNodes[loc].childNodes[5].childNodes[0].data)
if title:
parsed = [title, scaleX, scaleY, scaleZ, skewX, skewY, skewZ,
use_scale_dis, scale_fac, deform, rough, detail,
display_detail, smooth_fac, smooth_it,
use_generate, use_random_seed, user_seed]
else:
parsed = [scaleX, scaleY, scaleZ, skewX, skewY, skewZ, use_scale_dis,
scale_fac, deform, rough, detail, display_detail, smooth_fac,
smooth_it, use_generate, use_random_seed, user_seed]
return parsed
def save():
return '{FINISHED}'
def _print():
for i in presets:
print(i)
return '{FINISHED}'
@@ -0,0 +1,141 @@
# SPDX-FileCopyrightText: 2019-2022 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later
# Converts a formatted string to a float tuple:
# IN - '(0.5, 0.2)' -> CONVERT -> OUT - (0.5, 0.2)
def toTuple(stringIn):
sTemp = str(stringIn)[1:len(str(stringIn)) - 1].split(', ')
fTemp = []
for i in sTemp:
fTemp.append(float(i))
return tuple(fTemp)
# Converts a formatted string to a float tuple:
# IN - '[0.5, 0.2]' -> CONVERT -> OUT - [0.5, 0.2]
def toList(stringIn):
sTemp = str(stringIn)[1:len(str(stringIn)) - 1].split(', ')
fTemp = []
for i in sTemp:
fTemp.append(float(i))
return fTemp
# Converts each item of a list into a float:
def toFloats(inList):
outList = []
for i in inList:
outList.append(float(i))
return outList
# Converts each item of a list into an integer:
def toInts(inList):
outList = []
for i in inList:
outList.append(int(i))
return outList
# Sets all faces smooth. Done this way since I can't
# find a simple way without using bpy.ops:
def smooth(mesh):
import bmesh
bm = bmesh.new()
bm.from_mesh(mesh)
for f in bm.faces:
f.smooth = True
bm.to_mesh(mesh)
return mesh
# This try block allows for the script to psudo-intelligently select the
# appropriate random to use. If Numpy's random is present it will use that.
# If Numpy's random is not present, it will through a "module not found"
# exception and instead use the slower built-in random that Python has.
try:
# from numpy.random import random_integers as randint
from numpy.random import normal as gauss
# from numpy.random import (beta,
# uniform,
# seed,
# weibull)
# print("Rock Generator: Numpy found.")
numpy = True
except:
from random import (
# randint,
gauss,
# uniform,
# seed
)
# from random import betavariate as beta
# from random import weibullvariate as weibull
print("Rock Generator: Numpy not found. Using Python's random.")
numpy = False
# Artificially skews a normal (gaussian) distribution. This will not create
# a continuous distribution curve but instead acts as a piecewise finction.
# This linearly scales the output on one side to fit the bounds.
#
# Example output histograms:
#
# Upper skewed: Lower skewed:
# | ▄ | _
# | █ | █
# | █_ | █
# | ██ | _█
# | _██ | ██
# | _▄███_ | ██ _
# | ▄██████ | ▄██▄█▄_
# | _█▄███████ | ███████
# | _██████████_ | ████████▄▄█_ _
# | _▄▄████████████ | ████████████▄█_
# | _▄_ ▄███████████████▄_ | _▄███████████████▄▄_
# ------------------------- -----------------------
# |mu |mu
# Histograms were generated in R (http://www.r-project.org/) based on the
# calculations below and manually duplicated here.
#
# param: mu - mu is the mean of the distribution.
# sigma - sigma is the standard deviation of the distribution.
# bounds - bounds[0] is the lower bound and bounds[1]
# is the upper bound.
# upperSkewed - if the distribution is upper skewed.
# return: out - Rondomly generated value from the skewed distribution.
#
# @todo: Because NumPy's random value generators are faster when called
# a bunch of times at once, maybe allow this to generate and return
# multiple values at once?
def skewedGauss(mu, sigma, bounds, upperSkewed=True):
raw = gauss(mu, sigma)
# Quicker to check an extra condition than do unnecessary math. . . .
if raw < mu and not upperSkewed:
out = ((mu - bounds[0]) / (3 * sigma)) * raw + ((mu * (bounds[0] - (mu - 3 * sigma))) / (3 * sigma))
elif raw > mu and upperSkewed:
out = ((mu - bounds[1]) / (3 * -sigma)) * raw + ((mu * (bounds[1] - (mu + 3 * sigma))) / (3 * -sigma))
else:
out = raw
return out
# @todo create a def for generating an alpha and beta for a beta distribution
# given a mu, sigma, and an upper and lower bound. This proved faster in
# profiling in addition to providing a much better distribution curve
# provided multiple iterations happen within this function; otherwise it was
# slower.
# This might be a scratch because of the bounds placed on mu and sigma:
#
# For alpha > 1 and beta > 1:
# mu^2 - mu^3 mu^3 - mu^2 + mu
# ----------- < sigma < ----------------
# 1 + mu 2 - mu
#
# def generateBeta(mu, sigma, scale, repitions=1):
# results = []
#
# return results
@@ -0,0 +1,500 @@
# SPDX-FileCopyrightText: 2015-2023 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later
import bpy
from math import (
pi, sin,
cos, tan,
)
from bpy.types import Operator
from mathutils import (
Vector,
Euler,
)
from bpy.props import (
IntProperty,
FloatProperty,
BoolProperty,
StringProperty,
)
from bpy_extras import object_utils
from .interface import draw_transform_props
# mesh generating function, returns mesh
def add_mesh_Brilliant(context, s, table_w, crown_h, girdle_t, pavi_d, bezel_f,
pavi_f, culet, girdle_real, keep_lga, g_real_smooth):
# # possible user inputs ( output 100% = 2 blender units )
# s # no. of girdle facets (steps) default: 16
# table_w # table width default: 0.530
# crown_h # crown height default: 0.162
# girdle_t # girdle thickness default: 0.017
# pavi_d # pavilion depth default: 0.431
# bezel_f # bezel factor default: 0.250
# pavi_f # pavilion factor default: 0.400
# culet # culet size default: 0.000
# girdle_real # type of girdle flat/real default: True
# g_real_smooth # smooth or flat shading default: False
# keep_lga # when culet > 0, keep lga default: False
# variables / shortcuts
if s % 2: # prevent odd number of steps (messes up mesh)
s = s - 1
if not girdle_real:
g_real_smooth = False
ang = 2 * pi / s # angle step size
Verts = [] # collect all vertices
Faces = [] # collect all faces
ca = cos(ang)
ca2 = cos(ang / 2)
sa4 = sin(ang / 4)
ta4 = tan(ang / 4)
ta8 = tan(ang / 8)
def fa(*vs): # shortcut Faces.append
v = []
for u in vs:
v.append(u)
Faces.append(v)
def va(vx, vz, iang, sang, n): # shortcut Verts.append
for i in range(n):
v = Vector((vx, 0, vz))
ai = sang + iang * i
E_rot = Euler((0, 0, ai), 'XYZ')
v.rotate(E_rot)
Verts.append((v.x, v.y, v.z))
# upper girdle angle
uga = (1 - bezel_f) * crown_h * 2 / (ca2 -
(table_w + (1 - table_w) * bezel_f) * ca2 / ca)
# lower girdle angle
if keep_lga:
if pavi_f > 0 and pavi_f < 1:
lga = (1 - pavi_f) * pavi_d * 2 / (ca2 - pavi_f * ca2 / ca)
elif pavi_f == 1:
lga = 0
else:
lga = 2 * pavi_d * ca
else:
lga = (1 - pavi_f) * pavi_d * 2 / (ca2 -
(culet + (1 - culet) * pavi_f) * ca2 / ca)
# append girdle vertices
va(1, 0, ang, 0, s)
va(1, 2 * girdle_t, ang, 0, s)
# append real girdle vertices
if girdle_real:
dnu = uga * (1 - ca2)
dfu = uga * (ta8 + ta4) * sa4
dnl = lga * (1 - ca2)
dfl = lga * (ta8 + ta4) * sa4
if abs(dnu) + abs(dnl) > 2 * girdle_t or dnu < 0 or dnl < 0:
girdle_real = False
else:
va(1, dnl, ang, ang / 2, s)
va(1, 2 * girdle_t - dnu, ang, ang / 2, s)
va(1, dfl, ang / 2, ang / 4, 2 * s)
va(1, 2 * girdle_t - dfu, ang / 2, ang / 4, 2 * s)
# make girdle faces
l1 = len(Verts) # 2*s / 8*s
for i in range(l1):
if girdle_real:
if i < s:
fa(i, i + s, 2 * i + 6 * s, 2 * i + 4 * s)
if i == 0:
fa(i, s, l1 - 1, 6 * s - 1)
else:
fa(i, i + s, 2 * i + 6 * s - 1, 2 * i + 4 * s - 1)
elif i > 2 * s - 1 and i < 3 * s:
fa(i, i + s, 2 * (i + s), 2 * i)
fa(i, i + s, 2 * (i + s) + 1, 2 * i + 1)
else:
if i < s - 1:
fa(i, i + s, i + s + 1, i + 1)
elif i == s - 1:
fa(i, i + s, s, 0)
# append upper girdle facet vertices
va((table_w + (1 - table_w) * bezel_f) / ca, (1 - bezel_f) * 2 * crown_h +
2 * girdle_t, 2 * ang, ang, int(s / 2))
# make upper girdle facet faces
l2 = len(Verts) # 2.5*s / 8.5*s
for i in range(l2):
if i > s and i < 2 * s - 1 and i % 2 != 0:
if girdle_real:
fa(i, 2 * (i + 2 * s), i + 2 * s, 2 * (i + 2 * s) + 1, i + 1,
int(7.5 * s) + int((i - 1) / 2))
fa(i, 2 * (i + 2 * s) - 1, i + 2 * s - 1, 2 * (i + 2 * s - 1),
i - 1, int(7.5 * s) + int((i - 1) / 2))
else:
fa(i, i + 1, int((i + 3 * s) / 2))
fa(i, i - 1, int((i + 3 * s) / 2))
elif i == s:
if girdle_real:
fa(i, l1 - 1, 4 * s - 1, l1 - 2, 2 * i - 1, l2 - 1)
fa(2 * i - 2, l1 - 4, 4 * s - 2, l1 - 3, 2 * i - 1, l2 - 1)
else:
fa(i, 2 * i - 1, l2 - 1)
fa(2 * i - 1, 2 * i - 2, l2 - 1)
# append table vertices
va(table_w, (crown_h + girdle_t) * 2, 2 * ang, 0, int(s / 2))
# make bezel facet faces and star facet faces
l3 = len(Verts) # 3*s / 9*s
for i in range(l3):
if i > l2 - 1 and i < l3 - 1:
fa(i, i + 1, i - int(s / 2))
fa(i + 1, i - int(s / 2), 2 * (i - l2) + 2 + s, i - int(s / 2) + 1)
elif i == l3 - 1:
fa(i, l2, l2 - 1)
fa(s, l2 - 1, l2, l2 - int(s / 2))
# make table facet face
tf = []
for i in range(l3):
if i > l2 - 1:
tf.append(i)
fa(*tf)
# append lower girdle facet vertices
if keep_lga:
va(pavi_f / ca, (pavi_f - 1) * pavi_d * 2, 2 * ang, ang, int(s / 2))
else:
va((pavi_f * (1 - culet) + culet) / ca, (pavi_f - 1) * pavi_d * 2, 2 * ang,
ang, int(s / 2))
# make lower girdle facet faces
l4 = len(Verts) # 3.5*s / 9.5*s
for i in range(l4):
if i > 0 and i < s - 1 and i % 2 == 0:
if girdle_real:
fa(i, 2 * (i + 2 * s), i + 2 * s, 2 * (i + 2 * s) + 1, i + 1,
int(i / 2) + 9 * s)
fa(i, 2 * (i + 2 * s) - 1, i + 2 * s - 1, 2 * (i + 2 * s - 1),
i - 1, int(i / 2) + 9 * s - 1)
else:
fa(i, i + 1, int(i / 2) + l4 - int(s / 2))
fa(i, i - 1, int(i / 2) + l4 - int(s / 2) - 1)
elif i == 0:
if girdle_real:
fa(0, 4 * s, 2 * s, 4 * s + 1, 1, 9 * s)
fa(0, 6 * s - 1, 3 * s - 1, 6 * s - 2, s - 1, l4 - 1)
else:
fa(0, 1, l4 - int(s / 2))
fa(0, s - 1, l4 - 1)
# append culet vertice(s)
if culet == 0:
va(0, pavi_d * (-2), 0, 0, 1)
else:
if keep_lga:
va(culet * pavi_f / ca, pavi_d * (-2) + culet * pavi_f * 2 * pavi_d,
2 * ang, ang, int(s / 2))
else:
va(culet / ca, pavi_d * (-2), 2 * ang, ang, int(s / 2))
# make pavilion facet face
l5 = len(Verts) # 4*s / 10*s //if !culet: 3.5*s+1 / 9.5*s+1
for i in range(l5):
if i > 0 and i < s - 1 and i % 2 == 0:
if culet:
fa(i, l3 + int(i / 2), l3 + int((s + i) / 2),
l3 + int((s + i) / 2) - 1, l3 + int(i / 2) - 1)
else:
fa(i, l3 + int(i / 2), l5 - 1, l3 + int(i / 2) - 1)
elif i == 0:
if culet:
fa(i, l3, l4, l5 - 1, l4 - 1)
else:
fa(i, l3, l5 - 1, l4 - 1)
# make culet facet face
if culet:
cf = []
for i in range(l5):
if i > l4 - 1:
cf.append(i)
fa(*cf)
# create actual mesh and object based on Verts and Faces given
dmesh = bpy.data.meshes.new("dmesh")
dmesh.from_pydata(Verts, [], Faces)
dmesh.update()
return dmesh
# object generating function, returns final object
def addBrilliant(context, self, s, table_w, crown_h, girdle_t, pavi_d, bezel_f,
pavi_f, culet, girdle_real, keep_lga, g_real_smooth):
# deactivate possible active Objects
bpy.context.view_layer.objects.active = None
# create actual mesh and object based on Verts and Faces given
dmesh = add_mesh_Brilliant(context, s, table_w, crown_h, girdle_t, pavi_d, bezel_f,
pavi_f, culet, girdle_real, keep_lga, g_real_smooth)
# Create object and link it into scene.
dobj = object_utils.object_data_add(context, dmesh, operator=self, name="dobj")
# activate and select object
bpy.context.view_layer.objects.active = dobj
dobj.select_set(True)
obj = bpy.context.active_object
# flip all face normals outside
bpy.ops.object.mode_set(mode='EDIT', toggle=False)
sel_mode = bpy.context.tool_settings.mesh_select_mode
bpy.context.tool_settings.mesh_select_mode = [False, False, True]
bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
for i, face in enumerate(obj.data.polygons):
face.select = True
bpy.ops.object.mode_set(mode='EDIT', toggle=False)
bpy.ops.mesh.normals_make_consistent(inside=False)
bpy.context.tool_settings.mesh_select_mode = sel_mode
bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
# make girdle smooth for complex girdle
if girdle_real and g_real_smooth:
bpy.ops.object.mode_set(mode='EDIT', toggle=False)
bpy.ops.mesh.select_all(action='DESELECT') # deselect all mesh data
bpy.ops.object.mode_set(mode='OBJECT')
pls = []
dp = obj.data.polygons[:4 * s] # only consider faces of girdle
ov = obj.data.vertices
for i, p in enumerate(dp):
pls.extend(p.vertices) # list all verts of girdle
for i, e in enumerate(obj.data.edges): # select edges to mark sharp
if e.vertices[0] in pls and e.vertices[1] in pls and abs(
ov[e.vertices[0]].co.x - ov[e.vertices[1]].co.x):
obj.data.edges[i].select = True
continue
obj.data.edges[i].select = False
bpy.ops.object.mode_set(mode='EDIT', toggle=False)
bpy.ops.mesh.mark_sharp()
bpy.context.tool_settings.mesh_select_mode = [False, False, True]
bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
bpy.ops.object.select_all(action='DESELECT')
for i, face in enumerate(obj.data.polygons):
if i < 4 * s:
face.select = True
continue
face.select = False
bpy.ops.object.mode_set(mode='EDIT', toggle=False)
bpy.ops.mesh.faces_shade_smooth()
edge_split_modifier = context.object.modifiers.new("", 'EDGE_SPLIT')
bpy.context.tool_settings.mesh_select_mode = sel_mode
bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
bpy.ops.object.modifier_apply(modifier=edge_split_modifier.name)
return dobj
# add new operator for object
class MESH_OT_primitive_brilliant_add(Operator, object_utils.AddObjectHelper):
bl_idname = "mesh.primitive_brilliant_add"
bl_label = "Brilliant"
bl_description = "Construct a custom brilliant mesh"
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
Brilliant : BoolProperty(name = "Brilliant",
default = True,
description = "Brilliant")
change : BoolProperty(name = "Change",
default = False,
description = "change Brilliant")
s: IntProperty(
name="Segments",
description="Longitudial segmentation",
step=1,
min=6,
max=128,
default=16
)
table_w: FloatProperty(
name="Table Width",
description="Width of table",
min=0.001,
max=1.0,
default=0.53,
subtype='FACTOR'
)
crown_h: FloatProperty(
name="Crown Height",
description="Height of crown",
min=0.0,
max=1.0,
default=0.162,
subtype='FACTOR'
)
girdle_t: FloatProperty(
name="Girdle Height",
description="Height of girdle",
min=0.0,
max=0.5,
default=0.017,
subtype='FACTOR'
)
girdle_real: BoolProperty(
name="Real Girdle",
description="More beautiful girdle; has more polygons",
default=True
)
g_real_smooth: BoolProperty(
name="Smooth Girdle",
description="smooth shading for girdle, only available for real girdle",
default=False
)
pavi_d: FloatProperty(
name="Pavilion Depth",
description="Height of pavilion",
min=0.0,
max=1.0,
default=0.431,
subtype='FACTOR'
)
bezel_f: FloatProperty(
name="Upper Facet Factor",
description="Determines the form of bezel and upper girdle facets",
min=0.0,
max=1.0,
default=0.250,
subtype='FACTOR'
)
pavi_f: FloatProperty(
name="Lower Facet Factor",
description="Determines the form of pavilion and lower girdle facets",
min=0.001,
max=1.0,
default=0.400,
subtype='FACTOR'
)
culet: FloatProperty(
name="Culet Size",
description="0: no culet (default)",
min=0.0,
max=0.999,
default=0.0,
subtype='FACTOR'
)
keep_lga: BoolProperty(
name="Retain Lower Angle",
description="If culet > 0, retains angle of pavilion facets",
default=False
)
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
layout.separator()
layout.prop(self, "s")
layout.prop(self, "table_w")
col = layout.column(align=True)
col.prop(self, "crown_h", text='Height Crown')
col.prop(self, "girdle_t", text='Girdle')
col.prop(self, "pavi_d", text='Pavilion')
layout.prop(self, "girdle_real")
layout.prop(self, "g_real_smooth")
col = layout.column(align=True)
col.prop(self, "bezel_f", text='Facet Upper')
col.prop(self, "pavi_f", text='Lower')
layout.prop(self, "culet")
layout.prop(self, "keep_lga")
if self.change == False:
layout.separator()
draw_transform_props(self, layout)
# call mesh/object generator function with user inputs
def execute(self, context):
# turn off 'Enter Edit Mode'
use_enter_edit_mode = bpy.context.preferences.edit.use_enter_edit_mode
bpy.context.preferences.edit.use_enter_edit_mode = False
if bpy.context.mode == "OBJECT":
if context.selected_objects != [] and context.active_object and \
(context.active_object.data is not None) and ('Brilliant' in context.active_object.data.keys()) and \
(self.change == True):
obj = context.active_object
oldmesh = obj.data
oldmeshname = obj.data.name
mesh = add_mesh_Brilliant(context, self.s, self.table_w, self.crown_h,
self.girdle_t, self.pavi_d, self.bezel_f,
self.pavi_f, self.culet, self.girdle_real,
self.keep_lga, self.g_real_smooth
)
obj.data = mesh
for material in oldmesh.materials:
obj.data.materials.append(material)
bpy.data.meshes.remove(oldmesh)
obj.data.name = oldmeshname
else:
obj = addBrilliant(context, self, self.s, self.table_w, self.crown_h,
self.girdle_t, self.pavi_d, self.bezel_f,
self.pavi_f, self.culet, self.girdle_real,
self.keep_lga, self.g_real_smooth
)
obj.data["Brilliant"] = True
obj.data["change"] = False
for prm in BrilliantParameters():
obj.data[prm] = getattr(self, prm)
if bpy.context.mode == "EDIT_MESH":
active_object = context.active_object
name_active_object = active_object.name
bpy.ops.object.mode_set(mode='OBJECT')
obj = addBrilliant(context, self, self.s, self.table_w, self.crown_h,
self.girdle_t, self.pavi_d, self.bezel_f,
self.pavi_f, self.culet, self.girdle_real,
self.keep_lga, self.g_real_smooth
)
obj.select_set(True)
active_object.select_set(True)
bpy.context.view_layer.objects.active = active_object
bpy.ops.object.join()
context.active_object.name = name_active_object
bpy.ops.object.mode_set(mode='EDIT')
if use_enter_edit_mode:
bpy.ops.object.mode_set(mode = 'EDIT')
# restore pre operator state
bpy.context.preferences.edit.use_enter_edit_mode = use_enter_edit_mode
return {'FINISHED'}
def BrilliantParameters():
BrilliantParameters = [
"s",
"table_w",
"crown_h",
"girdle_t",
"girdle_real",
"g_real_smooth",
"pavi_d",
"bezel_f",
"pavi_f",
"culet",
"keep_lga",
]
return BrilliantParameters
@@ -0,0 +1,521 @@
# SPDX-FileCopyrightText: 2015-2022 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later
# Author: Alain Ducharme (phymec)
import bpy
from bpy_extras import object_utils
from itertools import permutations
from math import (
copysign, pi,
sqrt,
)
from bpy.types import Operator
from bpy.props import (
BoolProperty,
EnumProperty,
FloatProperty,
FloatVectorProperty,
IntProperty,
StringProperty,
)
from .interface import draw_transform_props
def round_cube(radius=1.0, arcdiv=4, lindiv=0., size=(0., 0., 0.),
div_type='CORNERS', odd_axis_align=False, info_only=False):
# subdiv bitmasks
CORNERS, EDGES, ALL = 0, 1, 2
try:
subdiv = ('CORNERS', 'EDGES', 'ALL').index(div_type)
except ValueError:
subdiv = CORNERS # fallback
radius = max(radius, 0.)
if not radius:
# No sphere
arcdiv = 1
odd_axis_align = False
if arcdiv <= 0:
arcdiv = max(round(pi * radius * lindiv * 0.5), 1)
arcdiv = max(round(arcdiv), 1)
if lindiv <= 0. and radius:
lindiv = 1. / (pi / (arcdiv * 2.) * radius)
lindiv = max(lindiv, 0.)
if not lindiv:
subdiv = CORNERS
odd = arcdiv % 2 # even = arcdiv % 2 ^ 1
step_size = 2. / arcdiv
odd_aligned = 0
vi = -1.
steps = arcdiv + 1
if odd_axis_align and odd:
odd_aligned = 1
vi += 0.5 * step_size
steps = arcdiv
axis_aligned = not odd or odd_aligned
if arcdiv == 1 and not odd_aligned and subdiv == EDGES:
subdiv = CORNERS
half_chord = 0. # ~ spherical cap base radius
sagitta = 0. # ~ spherical cap height
if not axis_aligned:
half_chord = sqrt(3.) * radius / (3. * arcdiv)
id2 = 1. / (arcdiv * arcdiv)
sagitta = radius - radius * sqrt(id2 * id2 / 3. - id2 + 1.)
# Extrusion per axis
exyz = [0. if s < 2. * (radius - sagitta) else (s - 2. * (radius - sagitta)) * 0.5 for s in size]
ex, ey, ez = exyz
dxyz = [0, 0, 0] # extrusion divisions per axis
dssxyz = [0., 0., 0.] # extrusion division step sizes per axis
for i in range(3):
sc = 2. * (exyz[i] + half_chord)
dxyz[i] = round(sc * lindiv) if subdiv else 0
if dxyz[i]:
dssxyz[i] = sc / dxyz[i]
dxyz[i] -= 1
else:
dssxyz[i] = sc
if info_only:
ec = sum(1 for n in exyz if n)
if subdiv:
fxyz = [d + (e and axis_aligned) for d, e in zip(dxyz, exyz)]
dvc = arcdiv * 4 * sum(fxyz)
if subdiv == ALL:
dvc += sum(p1 * p2 for p1, p2 in permutations(fxyz, 2))
elif subdiv == EDGES and axis_aligned:
# (0, 0, 2, 4) * sum(dxyz) + (0, 0, 2, 6)
dvc += ec * ec // 2 * sum(dxyz) + ec * (ec - 1)
else:
dvc = (arcdiv * 4) * ec + ec * (ec - 1) if axis_aligned else 0
vert_count = int(6 * arcdiv * arcdiv + (0 if odd_aligned else 2) + dvc)
if not radius and not max(size) > 0:
vert_count = 1
return arcdiv, lindiv, vert_count
if not radius and not max(size) > 0:
# Single vertex
return [(0, 0, 0)], []
# uv lookup table
uvlt = []
v = vi
for j in range(1, steps + 1):
v2 = v * v
uvlt.append((v, v2, radius * sqrt(18. - 6. * v2) / 6.))
v = vi + j * step_size # v += step_size # instead of accumulating errors
# clear fp errors / signs at axis
if abs(v) < 1e-10:
v = 0.0
# Sides built left to right bottom up
# xp yp zp xd yd zd
sides = ((0, 2, 1, (-1, 1, 1)), # Y+ Front
(1, 2, 0, (-1, -1, 1)), # X- Left
(0, 2, 1, (1, -1, 1)), # Y- Back
(1, 2, 0, (1, 1, 1)), # X+ Right
(0, 1, 2, (-1, 1, -1)), # Z- Bottom
(0, 1, 2, (-1, -1, 1))) # Z+ Top
# side vertex index table (for sphere)
svit = [[[] for i in range(steps)] for i in range(6)]
# Extend svit rows for extrusion
yer = zer = 0
if ey:
yer = axis_aligned + (dxyz[1] if subdiv else 0)
svit[4].extend([[] for i in range(yer)])
svit[5].extend([[] for i in range(yer)])
if ez:
zer = axis_aligned + (dxyz[2] if subdiv else 0)
for side in range(4):
svit[side].extend([[] for i in range(zer)])
# Extend svit rows for odd_aligned
if odd_aligned:
for side in range(4):
svit[side].append([])
hemi = steps // 2
# Create vertices and svit without dups
vert = [0., 0., 0.]
verts = []
if arcdiv == 1 and not odd_aligned and subdiv == ALL:
# Special case: Grid Cuboid
for side, (xp, yp, zp, dir) in enumerate(sides):
svitc = svit[side]
rows = len(svitc)
if rows < dxyz[yp] + 2:
svitc.extend([[] for i in range(dxyz[yp] + 2 - rows)])
vert[zp] = (half_chord + exyz[zp]) * dir[zp]
for j in range(dxyz[yp] + 2):
vert[yp] = (j * dssxyz[yp] - half_chord - exyz[yp]) * dir[yp]
for i in range(dxyz[xp] + 2):
vert[xp] = (i * dssxyz[xp] - half_chord - exyz[xp]) * dir[xp]
if (side == 5) or ((i < dxyz[xp] + 1 and j < dxyz[yp] + 1) and (side < 4 or (i and j))):
svitc[j].append(len(verts))
verts.append(tuple(vert))
else:
for side, (xp, yp, zp, dir) in enumerate(sides):
svitc = svit[side]
exr = exyz[xp]
eyr = exyz[yp]
ri = 0 # row index
rij = zer if side < 4 else yer
if side == 5:
span = range(steps)
elif side < 4 or odd_aligned:
span = range(arcdiv)
else:
span = range(1, arcdiv)
ri = 1
for j in span: # rows
v, v2, mv2 = uvlt[j]
tv2mh = 1. / 3. * v2 - 0.5
hv2 = 0.5 * v2
if j == hemi and rij:
# Jump over non-edge row indices
ri += rij
for i in span: # columns
u, u2, mu2 = uvlt[i]
vert[xp] = u * mv2
vert[yp] = v * mu2
vert[zp] = radius * sqrt(u2 * tv2mh - hv2 + 1.)
vert[0] = (vert[0] + copysign(ex, vert[0])) * dir[0]
vert[1] = (vert[1] + copysign(ey, vert[1])) * dir[1]
vert[2] = (vert[2] + copysign(ez, vert[2])) * dir[2]
rv = tuple(vert)
if exr and i == hemi:
rx = vert[xp] # save rotated x
vert[xp] = rxi = (-exr - half_chord) * dir[xp]
if axis_aligned:
svitc[ri].append(len(verts))
verts.append(tuple(vert))
if subdiv:
offsetx = dssxyz[xp] * dir[xp]
for k in range(dxyz[xp]):
vert[xp] += offsetx
svitc[ri].append(len(verts))
verts.append(tuple(vert))
if eyr and j == hemi and axis_aligned:
vert[xp] = rxi
vert[yp] = -eyr * dir[yp]
svitc[hemi].append(len(verts))
verts.append(tuple(vert))
if subdiv:
offsety = dssxyz[yp] * dir[yp]
ry = vert[yp]
for k in range(dxyz[yp]):
vert[yp] += offsety
svitc[hemi + axis_aligned + k].append(len(verts))
verts.append(tuple(vert))
vert[yp] = ry
for k in range(dxyz[xp]):
vert[xp] += offsetx
svitc[hemi].append(len(verts))
verts.append(tuple(vert))
if subdiv & ALL:
for l in range(dxyz[yp]):
vert[yp] += offsety
svitc[hemi + axis_aligned + l].append(len(verts))
verts.append(tuple(vert))
vert[yp] = ry
vert[xp] = rx # restore
if eyr and j == hemi:
vert[yp] = (-eyr - half_chord) * dir[yp]
if axis_aligned:
svitc[hemi].append(len(verts))
verts.append(tuple(vert))
if subdiv:
offsety = dssxyz[yp] * dir[yp]
for k in range(dxyz[yp]):
vert[yp] += offsety
if exr and i == hemi and not axis_aligned and subdiv & ALL:
vert[xp] = rxi
for l in range(dxyz[xp]):
vert[xp] += offsetx
svitc[hemi + k].append(len(verts))
verts.append(tuple(vert))
vert[xp] = rx
svitc[hemi + axis_aligned + k].append(len(verts))
verts.append(tuple(vert))
svitc[ri].append(len(verts))
verts.append(rv)
ri += 1
# Complete svit edges (shared vertices)
# Sides' right edge
for side, rows in enumerate(svit[:4]):
for j, row in enumerate(rows[:-1]):
svit[3 if not side else side - 1][j].append(row[0])
# Sides' top edge
svit[0][-1].extend(svit[5][0])
svit[2][-1].extend(svit[5][-1][::-1])
for row in svit[5]:
svit[3][-1].insert(0, row[0])
svit[1][-1].append(row[-1])
if odd_aligned:
for side in svit[:4]:
side[-1].append(-1)
# Bottom edges
if odd_aligned:
svit[4].insert(0, [-1] + svit[2][0][-2::-1] + [-1])
for i, col in enumerate(svit[3][0][:-1]):
svit[4][i + 1].insert(0, col)
svit[4][i + 1].append(svit[1][0][-i - 2])
svit[4].append([-1] + svit[0][0][:-1] + [-1])
else:
svit[4][0].extend(svit[2][0][::-1])
for i, col in enumerate(svit[3][0][1:-1]):
svit[4][i + 1].insert(0, col)
svit[4][i + 1].append(svit[1][0][-i - 2])
svit[4][-1].extend(svit[0][0])
# Build faces
faces = []
if not axis_aligned:
hemi -= 1
for side, rows in enumerate(svit):
xp, yp = sides[side][:2]
oa4 = odd_aligned and side == 4
if oa4: # special case
hemi += 1
for j, row in enumerate(rows[:-1]):
tri = odd_aligned and (oa4 and not j or rows[j + 1][-1] < 0)
for i, vi in enumerate(row[:-1]):
# odd_aligned triangle corners
if vi < 0:
if not j and not i:
faces.append((row[i + 1], rows[j + 1][i + 1], rows[j + 1][i]))
elif oa4 and not i and j == len(rows) - 2:
faces.append((vi, row[i + 1], rows[j + 1][i + 1]))
elif tri and i == len(row) - 2:
if j:
faces.append((vi, row[i + 1], rows[j + 1][i]))
else:
if oa4 or arcdiv > 1:
faces.append((vi, rows[j + 1][i + 1], rows[j + 1][i]))
else:
faces.append((vi, row[i + 1], rows[j + 1][i]))
# subdiv = EDGES (not ALL)
elif subdiv and len(rows[j + 1]) < len(row) and (i >= hemi):
if (i == hemi):
faces.append((vi, row[i + 1 + dxyz[xp]], rows[j + 1 + dxyz[yp]][i + 1 + dxyz[xp]],
rows[j + 1 + dxyz[yp]][i]))
elif i > hemi + dxyz[xp]:
faces.append((vi, row[i + 1], rows[j + 1][i + 1 - dxyz[xp]], rows[j + 1][i - dxyz[xp]]))
elif subdiv and len(rows[j + 1]) > len(row) and (i >= hemi):
if (i > hemi):
faces.append((vi, row[i + 1], rows[j + 1][i + 1 + dxyz[xp]], rows[j + 1][i + dxyz[xp]]))
elif subdiv and len(row) < len(rows[0]) and i == hemi:
pass
else:
# Most faces...
faces.append((vi, row[i + 1], rows[j + 1][i + 1], rows[j + 1][i]))
if oa4:
hemi -= 1
return verts, faces
class AddRoundCube(Operator, object_utils.AddObjectHelper):
bl_idname = "mesh.primitive_round_cube_add"
bl_label = "Add Round Cube"
bl_description = ("Create mesh primitives: Quadspheres, "
"Capsules, Rounded Cuboids, 3D Grids etc")
bl_options = {"REGISTER", "UNDO", "PRESET"}
sanity_check_verts = 200000
vert_count = 0
Roundcube : BoolProperty(name = "Roundcube",
default = True,
description = "Roundcube")
change : BoolProperty(name = "Change",
default = False,
description = "change Roundcube")
radius: FloatProperty(
name="Radius",
description="Radius of vertices for sphere, capsule or cuboid bevel",
default=1, min=0.0, soft_min=0.01, step=10
)
size: FloatVectorProperty(
name="Size",
description="Size",
subtype='XYZ',
default=(0.0, 0.0, 0.0),
)
arc_div: IntProperty(
name="Arc Divisions",
description="Arc curve divisions, per quadrant, 0=derive from Linear",
default=8, min=1
)
lin_div: FloatProperty(
name="Linear Divisions",
description="Linear unit divisions (Edges/Faces), 0=derive from Arc",
default=0.0, min=0.0, step=100, precision=1
)
no_limit: BoolProperty(
name='No Vertex Limit',
description='Do not limit to ' + str(sanity_check_verts) + ' vertices (sanity check)',
options={'HIDDEN'},
default=False
)
div_type: EnumProperty(
name='Type',
description='Division type',
items=(
('CORNERS', 'Corners', 'Sphere / Corners'),
('EDGES', 'Edges', 'Sphere / Corners and extruded edges (size)'),
('ALL', 'All', 'Sphere / Corners, extruded edges and faces (size)')),
default='CORNERS',
)
odd_axis_align: BoolProperty(
name='Odd Axis Align',
description='Align odd arc divisions with axes (Note: triangle corners!)',
)
def execute(self, context):
# turn off 'Enter Edit Mode'
use_enter_edit_mode = bpy.context.preferences.edit.use_enter_edit_mode
bpy.context.preferences.edit.use_enter_edit_mode = False
if self.arc_div <= 0 and self.lin_div <= 0:
self.report({'ERROR'},
"Either Arc Divisions or Linear Divisions must be greater than zero")
return {'CANCELLED'}
if not self.no_limit and self.vert_count > self.sanity_check_verts:
self.report({'ERROR'}, 'More than ' + str(self.sanity_check_verts) +
' vertices! Check "No Limit" to proceed')
return {'CANCELLED'}
if bpy.context.mode == "OBJECT":
if context.selected_objects != [] and context.active_object and \
(context.active_object.data is not None) and ('Roundcube' in context.active_object.data.keys()) and \
(self.change == True):
obj = context.active_object
oldmesh = obj.data
oldmeshname = obj.data.name
verts, faces = round_cube(self.radius, self.arc_div, self.lin_div,
self.size, self.div_type, self.odd_axis_align)
mesh = bpy.data.meshes.new('Roundcube')
mesh.from_pydata(verts, [], faces)
obj.data = mesh
for material in oldmesh.materials:
obj.data.materials.append(material)
bpy.data.meshes.remove(oldmesh)
obj.data.name = oldmeshname
else:
verts, faces = round_cube(self.radius, self.arc_div, self.lin_div,
self.size, self.div_type, self.odd_axis_align)
mesh = bpy.data.meshes.new('Roundcube')
mesh.from_pydata(verts, [], faces)
obj = object_utils.object_data_add(context, mesh, operator=self)
obj.data["Roundcube"] = True
obj.data["change"] = False
for prm in RoundCubeParameters():
obj.data[prm] = getattr(self, prm)
if bpy.context.mode == "EDIT_MESH":
active_object = context.active_object
name_active_object = active_object.name
bpy.ops.object.mode_set(mode='OBJECT')
verts, faces = round_cube(self.radius, self.arc_div, self.lin_div,
self.size, self.div_type, self.odd_axis_align)
mesh = bpy.data.meshes.new('Roundcube')
mesh.from_pydata(verts, [], faces)
obj = object_utils.object_data_add(context, mesh, operator=self)
obj.select_set(True)
active_object.select_set(True)
bpy.context.view_layer.objects.active = active_object
bpy.ops.object.join()
context.active_object.name = name_active_object
bpy.ops.object.mode_set(mode='EDIT')
if use_enter_edit_mode:
bpy.ops.object.mode_set(mode = 'EDIT')
# restore pre operator state
bpy.context.preferences.edit.use_enter_edit_mode = use_enter_edit_mode
return {'FINISHED'}
def check(self, context):
self.arcdiv, self.lindiv, self.vert_count = round_cube(
self.radius, self.arc_div, self.lin_div,
self.size, self.div_type, self.odd_axis_align,
True
)
return True
def invoke(self, context, event):
self.check(context)
return self.execute(context)
def draw(self, context):
self.check(context)
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
layout.separator()
layout.prop(self, 'radius')
layout.column().prop(self, 'size', expand=True)
layout.separator()
layout.prop(self, 'div_type', text='Division Method')
layout.prop(self, 'arc_div', text='Arc')
row = layout.row()
row.enabled = (
self.div_type != 'CORNERS' and
(
self.size[0] > self.radius*2 or
self.size[1] > self.radius*2 or
self.size[2] > self.radius*2
)
)
row.prop(self, 'lin_div', text='Linear')
row = layout.row()
row.alert = self.vert_count > self.sanity_check_verts
row.prop(self, 'no_limit')
row = layout.row()
row.active = self.arcdiv % 2
row.prop(self, 'odd_axis_align', text='Triangle Corners')
if self.change == False:
layout.separator()
draw_transform_props(self, layout)
def RoundCubeParameters():
RoundCubeParameters = [
"radius",
"size",
"arc_div",
"lin_div",
"div_type",
"odd_axis_align",
"no_limit",
]
return RoundCubeParameters
@@ -0,0 +1,405 @@
# SPDX-FileCopyrightText: 2010-2022 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later
# Author: DreamPainter
import bpy
from math import sqrt
from mathutils import Vector
from functools import reduce
from bpy.props import (
FloatProperty,
EnumProperty,
BoolProperty,
)
from bpy_extras.object_utils import object_data_add
# function to make the reduce function work as a workaround to sum a list of vectors
def vSum(list):
return reduce(lambda a, b: a + b, list)
# Get a copy of the input faces, but with the normals flipped by reversing the order of the vertex indices of each face.
def flippedFaceNormals(faces):
return [list(reversed(vertexIndices)) for vertexIndices in faces]
# creates the 5 platonic solids as a base for the rest
# plato: should be one of {"4","6","8","12","20"}. decides what solid the
# outcome will be.
# returns a list of vertices and faces
def source(plato):
verts = []
faces = []
# Tetrahedron
if plato == "4":
# Calculate the necessary constants
s = sqrt(2) / 3.0
t = -1 / 3
u = sqrt(6) / 3
# create the vertices and faces
v = [(0, 0, 1), (2 * s, 0, t), (-s, u, t), (-s, -u, t)]
faces = [[0, 1, 2], [0, 2, 3], [0, 3, 1], [1, 3, 2]]
# Hexahedron (cube)
elif plato == "6":
# Calculate the necessary constants
s = 1 / sqrt(3)
# create the vertices and faces
v = [(-s, -s, -s), (s, -s, -s), (s, s, -s), (-s, s, -s), (-s, -s, s), (s, -s, s), (s, s, s), (-s, s, s)]
faces = [[0, 3, 2, 1], [0, 1, 5, 4], [0, 4, 7, 3], [6, 5, 1, 2], [6, 2, 3, 7], [6, 7, 4, 5]]
# Octahedron
elif plato == "8":
# create the vertices and faces
v = [(1, 0, 0), (-1, 0, 0), (0, 1, 0), (0, -1, 0), (0, 0, 1), (0, 0, -1)]
faces = [[4, 0, 2], [4, 2, 1], [4, 1, 3], [4, 3, 0], [5, 2, 0], [5, 1, 2], [5, 3, 1], [5, 0, 3]]
# Dodecahedron
elif plato == "12":
# Calculate the necessary constants
s = 1 / sqrt(3)
t = sqrt((3 - sqrt(5)) / 6)
u = sqrt((3 + sqrt(5)) / 6)
# create the vertices and faces
v = [(s, s, s), (s, s, -s), (s, -s, s), (s, -s, -s), (-s, s, s), (-s, s, -s), (-s, -s, s), (-s, -s, -s),
(t, u, 0), (-t, u, 0), (t, -u, 0), (-t, -u, 0), (u, 0, t), (u, 0, -t), (-u, 0, t), (-u, 0, -t), (0, t, u),
(0, -t, u), (0, t, -u), (0, -t, -u)]
faces = [[0, 8, 9, 4, 16], [0, 12, 13, 1, 8], [0, 16, 17, 2, 12], [8, 1, 18, 5, 9], [12, 2, 10, 3, 13],
[16, 4, 14, 6, 17], [9, 5, 15, 14, 4], [6, 11, 10, 2, 17], [3, 19, 18, 1, 13], [7, 15, 5, 18, 19],
[7, 11, 6, 14, 15], [7, 19, 3, 10, 11]]
# Icosahedron
elif plato == "20":
# Calculate the necessary constants
s = (1 + sqrt(5)) / 2
t = sqrt(1 + s * s)
s = s / t
t = 1 / t
# create the vertices and faces
v = [(s, t, 0), (-s, t, 0), (s, -t, 0), (-s, -t, 0), (t, 0, s), (t, 0, -s), (-t, 0, s), (-t, 0, -s),
(0, s, t), (0, -s, t), (0, s, -t), (0, -s, -t)]
faces = [[0, 8, 4], [0, 5, 10], [2, 4, 9], [2, 11, 5], [1, 6, 8], [1, 10, 7], [3, 9, 6], [3, 7, 11],
[0, 10, 8], [1, 8, 10], [2, 9, 11], [3, 11, 9], [4, 2, 0], [5, 0, 2], [6, 1, 3], [7, 3, 1],
[8, 6, 4], [9, 4, 6], [10, 5, 7], [11, 7, 5]]
# convert the tuples to Vectors
verts = [Vector(i) for i in v]
return verts, faces
# processes the raw data from source
def createSolid(plato, vtrunc, etrunc, dual, snub):
# the duals from each platonic solid
dualSource = {"4": "4",
"6": "8",
"8": "6",
"12": "20",
"20": "12"}
# constants saving space and readability
vtrunc *= 0.5
etrunc *= 0.5
supposedSize = 0
noSnub = (snub == "None") or (etrunc == 0.5) or (etrunc == 0)
lSnub = (snub == "Left") and (0 < etrunc < 0.5)
rSnub = (snub == "Right") and (0 < etrunc < 0.5)
# no truncation
if vtrunc == 0:
if dual: # dual is as simple as another, but mirrored platonic solid
vInput, fInput = source(dualSource[plato])
supposedSize = vSum(vInput[i] for i in fInput[0]).length / len(fInput[0])
vInput = [-i * supposedSize for i in vInput] # mirror it
# Inverting vInput turns the mesh inside-out, so normals need to be flipped.
return vInput, flippedFaceNormals(fInput)
return source(plato)
elif 0 < vtrunc <= 0.5: # simple truncation of the source
vInput, fInput = source(plato)
else:
# truncation is now equal to simple truncation of the dual of the source
vInput, fInput = source(dualSource[plato])
supposedSize = vSum(vInput[i] for i in fInput[0]).length / len(fInput[0])
vtrunc = 1 - vtrunc # account for the source being a dual
if vtrunc == 0: # no truncation needed
if dual:
vInput, fInput = source(plato)
vInput = [-i * supposedSize for i in vInput]
# Inverting vInput turns the mesh inside-out, so normals need to be flipped.
return vInput, flippedFaceNormals(fInput)
# generate connection database
vDict = [{} for i in vInput]
# for every face, store what vertex comes after and before the current vertex
for x in range(len(fInput)):
i = fInput[x]
for j in range(len(i)):
vDict[i[j - 1]][i[j]] = [i[j - 2], x]
if len(vDict[i[j - 1]]) == 1:
vDict[i[j - 1]][-1] = i[j]
# the actual connection database: exists out of:
# [vtrunc pos, etrunc pos, connected vert IDs, connected face IDs]
vData = [[[], [], [], []] for i in vInput]
fvOutput = [] # faces created from truncated vertices
feOutput = [] # faces created from truncated edges
vOutput = [] # newly created vertices
for x in range(len(vInput)):
i = vDict[x] # lookup the current vertex
current = i[-1]
while True: # follow the chain to get a ccw order of connected verts and faces
vData[x][2].append(i[current][0])
vData[x][3].append(i[current][1])
# create truncated vertices
vData[x][0].append((1 - vtrunc) * vInput[x] + vtrunc * vInput[vData[x][2][-1]])
current = i[current][0]
if current == i[-1]:
break # if we're back at the first: stop the loop
fvOutput.append([]) # new face from truncated vert
fOffset = x * (len(i) - 1) # where to start off counting faceVerts
# only create one vert where one is needed (v1 todo: done)
if etrunc == 0.5:
for j in range(len(i) - 1):
vOutput.append((vData[x][0][j] + vData[x][0][j - 1]) * etrunc) # create vert
fvOutput[x].append(fOffset + j) # add to face
fvOutput[x] = fvOutput[x][1:] + [fvOutput[x][0]] # rotate face for ease later on
# create faces from truncated edges.
for j in range(len(i) - 1):
if x > vData[x][2][j]: # only create when other vertex has been added
index = vData[vData[x][2][j]][2].index(x)
feOutput.append([fvOutput[x][j], fvOutput[x][j - 1],
fvOutput[vData[x][2][j]][index],
fvOutput[vData[x][2][j]][index - 1]])
# edge truncation between none and full
elif etrunc > 0:
for j in range(len(i) - 1):
# create snubs from selecting verts from rectified meshes
if rSnub:
vOutput.append(etrunc * vData[x][0][j] + (1 - etrunc) * vData[x][0][j - 1])
fvOutput[x].append(fOffset + j)
elif lSnub:
vOutput.append((1 - etrunc) * vData[x][0][j] + etrunc * vData[x][0][j - 1])
fvOutput[x].append(fOffset + j)
else: # noSnub, select both verts from rectified mesh
vOutput.append(etrunc * vData[x][0][j] + (1 - etrunc) * vData[x][0][j - 1])
vOutput.append((1 - etrunc) * vData[x][0][j] + etrunc * vData[x][0][j - 1])
fvOutput[x].append(2 * fOffset + 2 * j)
fvOutput[x].append(2 * fOffset + 2 * j + 1)
# rotate face for ease later on
if noSnub:
fvOutput[x] = fvOutput[x][2:] + fvOutput[x][:2]
else:
fvOutput[x] = fvOutput[x][1:] + [fvOutput[x][0]]
# create single face for each edge
if noSnub:
for j in range(len(i) - 1):
if x > vData[x][2][j]:
index = vData[vData[x][2][j]][2].index(x)
feOutput.append([fvOutput[x][j * 2], fvOutput[x][2 * j - 1],
fvOutput[vData[x][2][j]][2 * index],
fvOutput[vData[x][2][j]][2 * index - 1]])
# create 2 tri's for each edge for the snubs
elif rSnub:
for j in range(len(i) - 1):
if x > vData[x][2][j]:
index = vData[vData[x][2][j]][2].index(x)
feOutput.append([fvOutput[x][j], fvOutput[x][j - 1],
fvOutput[vData[x][2][j]][index]])
feOutput.append([fvOutput[x][j], fvOutput[vData[x][2][j]][index],
fvOutput[vData[x][2][j]][index - 1]])
elif lSnub:
for j in range(len(i) - 1):
if x > vData[x][2][j]:
index = vData[vData[x][2][j]][2].index(x)
feOutput.append([fvOutput[x][j], fvOutput[x][j - 1],
fvOutput[vData[x][2][j]][index - 1]])
feOutput.append([fvOutput[x][j - 1], fvOutput[vData[x][2][j]][index],
fvOutput[vData[x][2][j]][index - 1]])
# special rules for birectified mesh (v1 todo: done)
elif vtrunc == 0.5:
for j in range(len(i) - 1):
if x < vData[x][2][j]: # use current vert, since other one has not passed yet
vOutput.append(vData[x][0][j])
fvOutput[x].append(len(vOutput) - 1)
else:
# search for other edge to avoid duplicity
connectee = vData[x][2][j]
fvOutput[x].append(fvOutput[connectee][vData[connectee][2].index(x)])
else: # vert truncation only
vOutput.extend(vData[x][0]) # use generated verts from way above
for j in range(len(i) - 1): # create face from them
fvOutput[x].append(fOffset + j)
# calculate supposed vertex length to ensure continuity
if supposedSize and not dual: # this to make the vtrunc > 1 work
supposedSize *= len(fvOutput[0]) / vSum(vOutput[i] for i in fvOutput[0]).length
vOutput = [-i * supposedSize for i in vOutput]
# Inverting vOutput turns the mesh inside-out, so normals need to be flipped.
flipNormals = True
else:
flipNormals = False
# create new faces by replacing old vert IDs by newly generated verts
ffOutput = [[] for i in fInput]
for x in range(len(fInput)):
# only one generated vert per vertex, so choose accordingly
if etrunc == 0.5 or (etrunc == 0 and vtrunc == 0.5) or lSnub or rSnub:
ffOutput[x] = [fvOutput[i][vData[i][3].index(x) - 1] for i in fInput[x]]
# two generated verts per vertex
elif etrunc > 0:
for i in fInput[x]:
ffOutput[x].append(fvOutput[i][2 * vData[i][3].index(x) - 1])
ffOutput[x].append(fvOutput[i][2 * vData[i][3].index(x) - 2])
else: # cutting off corners also makes 2 verts
for i in fInput[x]:
ffOutput[x].append(fvOutput[i][vData[i][3].index(x)])
ffOutput[x].append(fvOutput[i][vData[i][3].index(x) - 1])
if not dual:
fOutput = fvOutput + feOutput + ffOutput
if flipNormals:
fOutput = flippedFaceNormals(fOutput)
return vOutput, fOutput
else:
# do the same procedure as above, only now on the generated mesh
# generate connection database
vDict = [{} for i in vOutput]
dvOutput = [0 for i in fvOutput + feOutput + ffOutput]
dfOutput = []
for x in range(len(dvOutput)): # for every face
i = (fvOutput + feOutput + ffOutput)[x] # choose face to work with
# find vertex from face
normal = (vOutput[i[0]] - vOutput[i[1]]).cross(vOutput[i[2]] - vOutput[i[1]]).normalized()
dvOutput[x] = normal / (normal.dot(vOutput[i[0]]))
for j in range(len(i)): # create vert chain
vDict[i[j - 1]][i[j]] = [i[j - 2], x]
if len(vDict[i[j - 1]]) == 1:
vDict[i[j - 1]][-1] = i[j]
# calculate supposed size for continuity
supposedSize = vSum([vInput[i] for i in fInput[0]]).length / len(fInput[0])
supposedSize /= dvOutput[-1].length
dvOutput = [i * supposedSize for i in dvOutput]
# use chains to create faces
for x in range(len(vOutput)):
i = vDict[x]
current = i[-1]
face = []
while True:
face.append(i[current][1])
current = i[current][0]
if current == i[-1]:
break
dfOutput.append(face)
return dvOutput, dfOutput
class Solids(bpy.types.Operator):
"""Add one of the (regular) solids (mesh)"""
bl_idname = "mesh.primitive_solid_add"
bl_label = "Add Regular Solid"
bl_description = "Add one of the Platonic, Archimedean or Catalan solids"
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
source: EnumProperty(
items=(("4", "Tetrahedron", ""),
("6", "Hexahedron", ""),
("8", "Octahedron", ""),
("12", "Dodecahedron", ""),
("20", "Icosahedron", "")),
name="Source",
description="Starting point of your solid"
)
size: FloatProperty(
name="Size",
description="Radius of the sphere through the vertices",
min=0.01,
soft_min=0.01,
max=100,
soft_max=100,
default=1.0
)
vTrunc: FloatProperty(
name="Vertex Truncation",
description="Amount of vertex truncation",
min=0.0,
soft_min=0.0,
max=2.0,
soft_max=2.0,
default=0.0,
precision=3,
step=0.5
)
eTrunc: FloatProperty(
name="Edge Truncation",
description="Amount of edge truncation",
min=0.0,
soft_min=0.0,
max=1.0,
soft_max=1.0,
default=0.0,
precision=3,
step=0.2
)
snub: EnumProperty(
items=(("None", "No Snub", ""),
("Left", "Left Snub", ""),
("Right", "Right Snub", "")),
name="Snub",
description="Create the snub version"
)
dual: BoolProperty(
name="Dual",
description="Create the dual of the current solid",
default=False
)
keepSize: BoolProperty(
name="Keep Size",
description="Keep the whole solid at a constant size",
default=False
)
def execute(self, context):
# generate mesh
verts, faces = createSolid(self.source,
self.vTrunc,
self.eTrunc,
self.dual,
self.snub
)
# resize to normal size, or if keepSize, make sure all verts are of length 'size'
if self.keepSize:
rad = self.size / verts[-1 if self.dual else 0].length
else:
rad = self.size
verts = [i * rad for i in verts]
# generate object
# Create new mesh
mesh = bpy.data.meshes.new("Solid")
# Make a mesh from a list of verts/edges/faces.
mesh.from_pydata(verts, [], faces)
# Update mesh geometry after adding stuff.
mesh.update()
object_data_add(context, mesh, operator=None)
# object generation done
return {'FINISHED'}
@@ -0,0 +1,285 @@
# SPDX-FileCopyrightText: 2015-2022 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later
# Original by Fourmadmen
import bpy
from mathutils import (
Vector,
Quaternion,
)
from math import pi
from bpy.props import (
IntProperty,
FloatProperty,
StringProperty,
BoolProperty,
)
from bpy_extras import object_utils
from .interface import draw_transform_props
# Create a new mesh (object) from verts/edges/faces.
# verts/edges/faces ... List of vertices/edges/faces for the
# new mesh (as used in from_pydata)
# name ... Name of the new mesh (& object)
def create_mesh_object(context, verts, edges, faces, name):
# Create new mesh
mesh = bpy.data.meshes.new(name)
# Make a mesh from a list of verts/edges/faces.
mesh.from_pydata(verts, edges, faces)
# Update mesh geometry after adding stuff.
mesh.update()
from bpy_extras import object_utils
return object_utils.object_data_add(context, mesh, operator=None)
# A very simple "bridge" tool.
def createFaces(vertIdx1, vertIdx2, closed=False, flipped=False):
faces = []
if not vertIdx1 or not vertIdx2:
return None
if len(vertIdx1) < 2 and len(vertIdx2) < 2:
return None
fan = False
if (len(vertIdx1) != len(vertIdx2)):
if (len(vertIdx1) == 1 and len(vertIdx2) > 1):
fan = True
else:
return None
total = len(vertIdx2)
if closed:
# Bridge the start with the end.
if flipped:
face = [
vertIdx1[0],
vertIdx2[0],
vertIdx2[total - 1]]
if not fan:
face.append(vertIdx1[total - 1])
faces.append(face)
else:
face = [vertIdx2[0], vertIdx1[0]]
if not fan:
face.append(vertIdx1[total - 1])
face.append(vertIdx2[total - 1])
faces.append(face)
# Bridge the rest of the faces.
for num in range(total - 1):
if flipped:
if fan:
face = [vertIdx2[num], vertIdx1[0], vertIdx2[num + 1]]
else:
face = [vertIdx2[num], vertIdx1[num],
vertIdx1[num + 1], vertIdx2[num + 1]]
faces.append(face)
else:
if fan:
face = [vertIdx1[0], vertIdx2[num], vertIdx2[num + 1]]
else:
face = [vertIdx1[num], vertIdx2[num],
vertIdx2[num + 1], vertIdx1[num + 1]]
faces.append(face)
return faces
# @todo Clean up vertex&face creation process a bit.
def add_star(points, outer_radius, inner_radius, height):
PI_2 = pi * 2
z_axis = (0, 0, 1)
verts = []
faces = []
segments = points * 2
half_height = height / 2.0
vert_idx_top = len(verts)
verts.append(Vector((0.0, 0.0, half_height)))
vert_idx_bottom = len(verts)
verts.append(Vector((0.0, 0.0, -half_height)))
edgeloop_top = []
edgeloop_bottom = []
for index in range(segments):
quat = Quaternion(z_axis, (index / segments) * PI_2)
if index % 2:
# Uneven
radius = outer_radius
else:
# Even
radius = inner_radius
edgeloop_top.append(len(verts))
vec = quat @ Vector((radius, 0, half_height))
verts.append(vec)
edgeloop_bottom.append(len(verts))
vec = quat @ Vector((radius, 0, -half_height))
verts.append(vec)
faces_top = createFaces([vert_idx_top], edgeloop_top, closed=True)
faces_outside = createFaces(edgeloop_top, edgeloop_bottom, closed=True)
faces_bottom = createFaces([vert_idx_bottom], edgeloop_bottom,
flipped=True, closed=True)
faces.extend(faces_top)
faces.extend(faces_outside)
faces.extend(faces_bottom)
return verts, faces
class AddStar(bpy.types.Operator, object_utils.AddObjectHelper):
bl_idname = "mesh.primitive_star_add"
bl_label = "Simple Star"
bl_description = "Construct a star mesh"
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
Star : BoolProperty(name = "Star",
default = True,
description = "Star")
change : BoolProperty(name = "Change",
default = False,
description = "change Star")
points: IntProperty(
name="Points",
description="Number of points for the star",
min=2,
max=256,
default=5
)
outer_radius: FloatProperty(
name="Outer Radius",
description="Outer radius of the star",
min=0.01,
max=9999.0,
default=1.0
)
innter_radius: FloatProperty(
name="Inner Radius",
description="Inner radius of the star",
min=0.01,
max=9999.0,
default=0.5
)
height: FloatProperty(name="Height",
description="Height of the star",
min=0.01,
max=9999.0,
default=0.5
)
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
layout.separator()
layout.prop(self, 'points')
layout.prop(self, 'height')
col = layout.column(align=True)
col.prop(self, 'outer_radius', text='Radius Outer')
col.prop(self, 'innter_radius', text='Inner')
if self.change == False:
layout.separator()
draw_transform_props(self, layout)
def execute(self, context):
# turn off 'Enter Edit Mode'
use_enter_edit_mode = bpy.context.preferences.edit.use_enter_edit_mode
bpy.context.preferences.edit.use_enter_edit_mode = False
if bpy.context.mode == "OBJECT":
if context.selected_objects != [] and context.active_object and \
(context.active_object.data is not None) and ('Star' in context.active_object.data.keys()) and \
(self.change == True):
obj = context.active_object
oldmesh = obj.data
oldmeshname = obj.data.name
verts, faces = add_star(
self.points,
self.outer_radius,
self.innter_radius,
self.height
)
mesh = bpy.data.meshes.new('Star')
mesh.from_pydata(verts, [], faces)
obj.data = mesh
for material in oldmesh.materials:
obj.data.materials.append(material)
bpy.data.meshes.remove(oldmesh)
obj.data.name = oldmeshname
else:
verts, faces = add_star(
self.points,
self.outer_radius,
self.innter_radius,
self.height
)
mesh = bpy.data.meshes.new('Star')
mesh.from_pydata(verts, [], faces)
obj = object_utils.object_data_add(context, mesh, operator=self)
obj.data["Star"] = True
obj.data["change"] = False
for prm in StarParameters():
obj.data[prm] = getattr(self, prm)
if bpy.context.mode == "EDIT_MESH":
active_object = context.active_object
name_active_object = active_object.name
bpy.ops.object.mode_set(mode='OBJECT')
verts, faces = add_star(
self.points,
self.outer_radius,
self.innter_radius,
self.height
)
mesh = bpy.data.meshes.new('Star')
mesh.from_pydata(verts, [], faces)
obj = object_utils.object_data_add(context, mesh, operator=self)
obj.select_set(True)
active_object.select_set(True)
bpy.context.view_layer.objects.active = active_object
bpy.ops.object.join()
context.active_object.name = name_active_object
bpy.ops.object.mode_set(mode='EDIT')
if use_enter_edit_mode:
bpy.ops.object.mode_set(mode = 'EDIT')
# restore pre operator state
bpy.context.preferences.edit.use_enter_edit_mode = use_enter_edit_mode
return {'FINISHED'}
def StarParameters():
StarParameters = [
"points",
"outer_radius",
"innter_radius",
"height",
]
return StarParameters
@@ -0,0 +1,331 @@
# SPDX-FileCopyrightText: 2011-2022 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later
# Author: DreamPainter
import bpy
from bpy.props import (
FloatProperty,
BoolProperty,
IntProperty,
EnumProperty,
)
from math import pi, cos, sin
from mathutils import Vector
from bpy_extras import object_utils
from .interface import draw_transform_props
# A very simple "bridge" tool
def createFaces(vertIdx1, vertIdx2, closed=False, flipped=False):
faces = []
if not vertIdx1 or not vertIdx2:
return None
if len(vertIdx1) < 2 and len(vertIdx2) < 2:
return None
fan = False
if (len(vertIdx1) != len(vertIdx2)):
if (len(vertIdx1) == 1 and len(vertIdx2) > 1):
fan = True
else:
return None
total = len(vertIdx2)
if closed:
# Bridge the start with the end.
if flipped:
face = [
vertIdx1[0],
vertIdx2[0],
vertIdx2[total - 1]]
if not fan:
face.append(vertIdx1[total - 1])
faces.append(face)
else:
face = [vertIdx2[0], vertIdx1[0]]
if not fan:
face.append(vertIdx1[total - 1])
face.append(vertIdx2[total - 1])
faces.append(face)
# Bridge the rest of the faces.
for num in range(total - 1):
if flipped:
if fan:
face = [vertIdx2[num], vertIdx1[0], vertIdx2[num + 1]]
else:
face = [vertIdx2[num], vertIdx1[num],
vertIdx1[num + 1], vertIdx2[num + 1]]
faces.append(face)
else:
if fan:
face = [vertIdx1[0], vertIdx2[num], vertIdx2[num + 1]]
else:
face = [vertIdx1[num], vertIdx2[num],
vertIdx2[num + 1], vertIdx1[num + 1]]
faces.append(face)
return faces
def power(a, b):
if a < 0:
return -((-a) ** b)
return a ** b
def supertoroid(R, r, u, v, n1, n2):
"""
R = big radius
r = small radius
u = lateral segmentation
v = radial segmentation
n1 = value determines the shape of the torus
n2 = value determines the shape of the cross-section
"""
# create the necessary constants
a = 2 * pi / u
b = 2 * pi / v
verts = []
faces = []
# create each cross-section by calculating each vector on the
# the wannabe circle
# x = (cos(theta) ** n1)*(R + r * (cos(phi) ** n2))
# y = (sin(theta) ** n1)*(R + r * (cos(phi) ** n2))
# z = (r * sin(phi) ** n2)
# with theta and phi ranging from 0 to 2pi
for i in range(u):
s = power(sin(i * a), n1)
c = power(cos(i * a), n1)
for j in range(v):
c2 = R + r * power(cos(j * b), n2)
s2 = r * power(sin(j * b), n2)
verts.append(Vector((c * c2, s * c2, s2)))
# bridge the last circle with the previous circle
if i > 0: # but not for the first circle, 'cus there's no previous before the first
f = createFaces(range((i - 1) * v, i * v), range(i * v, (i + 1) * v), closed=True)
faces.extend(f)
# bridge the last circle with the first
f = createFaces(range((u - 1) * v, u * v), range(v), closed=True)
faces.extend(f)
return verts, faces
class add_supertoroid(bpy.types.Operator, object_utils.AddObjectHelper):
bl_idname = "mesh.primitive_supertoroid_add"
bl_label = "Add SuperToroid"
bl_description = "Construct a supertoroid mesh"
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
SuperToroid : BoolProperty(name = "SuperToroid",
default = True,
description = "SuperToroid")
change : BoolProperty(name = "Change",
default = False,
description = "change SuperToroid")
method: EnumProperty(
name='Method',
description='Method for determining the size and thickness of the torus',
items=(
('MAJOR-MINOR', 'Major / Minor', 'Uses the major radius for the overall size and the minor for the thickness'),
('INT-EXT', 'Interior / Exterior', 'Uses the absolute size of the inner and outer circles to determine the size and thickness'),
),
default='MAJOR-MINOR',
)
R: FloatProperty(
name="Big radius",
description="The radius inside the tube",
default=1.0,
min=0.01, max=100.0
)
r: FloatProperty(
name="Small radius",
description="The radius of the tube",
default=0.3,
min=0.01, max=100.0
)
outer_r: FloatProperty(
name="Exterior Radius",
description="Total Exterior Radius of the torus",
min=0.01,
max=100.0,
default=1.3
)
inner_r: FloatProperty(
name="Inside Radius",
description="Total Interior Radius of the torus",
min=0.01,
max=100.0,
default=0.7
)
u: IntProperty(
name="U-segments",
description="Radial segmentation",
default=16,
min=3, max=265
)
v: IntProperty(
name="V-segments",
description="Lateral segmentation",
default=8,
min=3, max=265
)
n1: FloatProperty(
name="Ring manipulator",
description="Manipulates the shape of the Ring",
default=1.0,
min=0.01, max=100.0
)
n2: FloatProperty(
name="Cross manipulator",
description="Manipulates the shape of the cross-section",
default=1.0,
min=0.01, max=100.0
)
edit: BoolProperty(
name="",
description="",
default=False,
options={'HIDDEN'}
)
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
layout.separator()
layout.prop(self, 'method', text='Dimensions Mode')
col = layout.column(align=True)
if self.method == 'MAJOR-MINOR':
col.prop(self, 'R', text='Radius Major')
col.prop(self, 'r', text='Minor')
else:
col.prop(self, 'outer_r', text='Radius Exterior')
col.prop(self, 'inner_r', text='Interior')
col = layout.column(align=True)
col.prop(self, 'u', text='Segments Major')
col.prop(self, 'v', text='Minor')
layout.prop(self, 'n1', text='Ring')
layout.prop(self, 'n2', text='Cross')
if self.change == False:
draw_transform_props(self, layout)
def execute(self, context):
# turn off 'Enter Edit Mode'
use_enter_edit_mode = bpy.context.preferences.edit.use_enter_edit_mode
bpy.context.preferences.edit.use_enter_edit_mode = False
props = self.properties
# check how the radii properties must be used
if props.method == 'INT-EXT':
rad1 = (props.outer_r + props.inner_r) / 2
rad2 = (props.outer_r - props.inner_r) / 2
# for consistency in the mesh, ie no crossing faces, make the largest of the two
# the outer radius
if rad2 > rad1:
[rad1, rad2] = [rad2, rad1]
else:
rad1 = props.R
rad2 = props.r
# again for consistency, make the radius in the tube,
# at least as big as the radius of the tube
if rad2 > rad1:
rad1 = rad2
if bpy.context.mode == "OBJECT":
if context.selected_objects != [] and context.active_object and \
(context.active_object.data is not None) and ('SuperToroid' in context.active_object.data.keys()) and \
(self.change == True):
obj = context.active_object
oldmesh = obj.data
oldmeshname = obj.data.name
verts, faces = supertoroid(rad1,
rad2,
props.u,
props.v,
props.n1,
props.n2
)
mesh = bpy.data.meshes.new('SuperToroid')
mesh.from_pydata(verts, [], faces)
obj.data = mesh
for material in oldmesh.materials:
obj.data.materials.append(material)
bpy.data.meshes.remove(oldmesh)
obj.data.name = oldmeshname
else:
verts, faces = supertoroid(rad1,
rad2,
props.u,
props.v,
props.n1,
props.n2
)
mesh = bpy.data.meshes.new('SuperToroid')
mesh.from_pydata(verts, [], faces)
obj = object_utils.object_data_add(context, mesh, operator=self)
obj.data["SuperToroid"] = True
obj.data["change"] = False
for prm in SuperToroidParameters():
obj.data[prm] = getattr(self, prm)
if bpy.context.mode == "EDIT_MESH":
active_object = context.active_object
name_active_object = active_object.name
bpy.ops.object.mode_set(mode='OBJECT')
verts, faces = supertoroid(rad1,
rad2,
props.u,
props.v,
props.n1,
props.n2
)
mesh = bpy.data.meshes.new('SuperToroid')
mesh.from_pydata(verts, [], faces)
obj = object_utils.object_data_add(context, mesh, operator=self)
obj.select_set(True)
active_object.select_set(True)
bpy.context.view_layer.objects.active = active_object
bpy.ops.object.join()
context.active_object.name = name_active_object
bpy.ops.object.mode_set(mode='EDIT')
if use_enter_edit_mode:
bpy.ops.object.mode_set(mode = 'EDIT')
# restore pre operator state
bpy.context.preferences.edit.use_enter_edit_mode = use_enter_edit_mode
return {'FINISHED'}
def SuperToroidParameters():
SuperToroidParameters = [
"R",
"r",
"u",
"v",
"n1",
"n2",
"method",
"edit",
"inner_r",
"outer_r",
]
return SuperToroidParameters
@@ -0,0 +1,865 @@
# SPDX-FileCopyrightText: 2012-2022 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later
# Author, Anthony D'Agostino
import bpy
from bpy.props import (
IntProperty,
EnumProperty,
)
import mathutils
import io
import operator
import functools
from bpy_extras import object_utils
from .interface import draw_transform_props
class AddTeapot(bpy.types.Operator, object_utils.AddObjectHelper):
bl_idname = "mesh.primitive_teapot_add"
bl_label = "Add Teapot"
bl_description = "Construct a teapot or teaspoon mesh"
bl_options = {"REGISTER", "UNDO"}
resolution: IntProperty(
name="Resolution",
description="Resolution of the Teapot",
default=5,
min=2, max=15,
)
objecttype: EnumProperty(
name="Type",
description="Type of Bezier Object",
items=(('1', "Teapot", "Construct a teapot mesh"),
('2', "Tea Spoon", "Construct a teaspoon mesh")),
default='1',
)
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
layout.prop(self, 'objecttype')
layout.prop(self, 'resolution')
layout.separator()
draw_transform_props(self, layout)
def execute(self, context):
# turn off 'Enter Edit Mode'
use_enter_edit_mode = bpy.context.preferences.edit.use_enter_edit_mode
bpy.context.preferences.edit.use_enter_edit_mode = False
cmode = bpy.context.mode
verts, faces = make_teapot(self.objecttype,
self.resolution)
# Actually create the mesh object from this geometry data.
obj = create_mesh_object(self, context, verts, [], faces, "Teapot")
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.remove_doubles()
if cmode != "EDIT_MESH":
bpy.ops.object.mode_set(mode=cmode)
if use_enter_edit_mode:
bpy.ops.object.mode_set(mode = 'EDIT')
# restore pre operator state
bpy.context.preferences.edit.use_enter_edit_mode = use_enter_edit_mode
return {'FINISHED'}
def create_mesh_face_hack(faces):
# FIXME, faces with duplicate vertices shouldn't be created in the first place.
faces_copy = []
for f in faces:
f_copy = []
for i in f:
if i not in f_copy:
f_copy.append(i)
faces_copy.append(f_copy)
faces[:] = faces_copy
def create_mesh_object(self, context, verts, edges, faces, name):
create_mesh_face_hack(faces)
# Create new mesh
mesh = bpy.data.meshes.new(name)
# Make a mesh from a list of verts/edges/faces.
mesh.from_pydata(verts, edges, faces)
# Update mesh geometry after adding stuff.
mesh.update()
return object_utils.object_data_add(context, mesh, operator=self)
# ==========================
# === Bezier patch Block ===
# ==========================
def read_indexed_patch_file(filename):
file = io.StringIO(filename)
rawpatches = []
patches = []
numpatches = int(file.readline())
for i in range(numpatches):
line = file.readline()
(a, b, c, d,
e, f, g, h,
i, j, k, l,
m, n, o, p,
) = map(int, line.split(","))
patches.append([[a, b, c, d], [e, f, g, h], [i, j, k, l], [m, n, o, p]])
rawpatches.append([[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]])
verts = []
numverts = int(file.readline())
for i in range(numverts):
line = file.readline()
v1, v2, v3 = map(float, line.split(","))
verts.append((v1, v2, v3))
for i in range(len(patches)):
for j in range(4): # len(patches[i])):
for k in range(4): # len(patches[i][j])):
index = patches[i][j][k] - 1
rawpatches[i][j][k] = verts[index]
return rawpatches
def patches_to_raw(patches, resolution):
raw = []
for patch in patches:
verts = make_verts(patch, resolution)
faces = make_faces(resolution)
rawquads = indexed_to_rawquads(verts, faces)
raw.append(rawquads)
raw = functools.reduce(operator.add, raw) # flatten the list
return raw
def make_bezier(ctrlpnts, resolution):
def b1(t):
return t * t * t
def b2(t):
return 3.0 * t * t * (1.0 - t)
def b3(t):
return 3.0 * t * (1.0 - t) * (1.0 - t)
def b4(t):
return (1.0 - t) * (1.0 - t) * (1.0 - t)
p1, p2, p3, p4 = map(mathutils.Vector, ctrlpnts)
def makevert(t):
x, y, z = b1(t) * p1 + b2(t) * p2 + b3(t) * p3 + b4(t) * p4
return (x, y, z)
curveverts = [makevert(i / resolution) for i in range(resolution + 1)]
return curveverts
def make_verts(a, resolution):
s = []
for i in a:
c = make_bezier(i, resolution)
s.append(c)
b = transpose(s)
s = []
for i in b:
c = make_bezier(i, resolution)
s.append(c)
verts = s
verts = functools.reduce(operator.add, verts) # flatten the list
return verts
def make_faces(resolution):
n = resolution + 1
faces = []
for i in range(resolution):
for j in range(resolution):
v1 = (i + 1) * n + j
v2 = (i + 1) * n + j + 1
v3 = i * n + j + 1
v4 = i * n + j
faces.append([v1, v2, v3, v4])
return faces
def indexed_to_rawquads(verts, faces):
rows = len(faces)
cols = len(faces[0]) # or 4
rawquads = [[None] * cols for i in range(rows)]
for i in range(rows):
for j in range(cols):
index = faces[i][j]
rawquads[i][j] = verts[index]
return rawquads
def raw_to_indexed(rawfaces):
# Generate verts and faces lists, without dups
verts = []
coords = {}
index = 0
for i in range(len(rawfaces)):
for j in range(len(rawfaces[i])):
vertex = rawfaces[i][j]
if vertex not in coords:
coords[vertex] = index
index += 1
verts.append(vertex)
rawfaces[i][j] = coords[vertex]
return verts, rawfaces
def transpose(rowsbycols):
rows = len(rowsbycols)
cols = len(rowsbycols[0])
colsbyrows = [[None] * rows for i in range(cols)]
for i in range(cols):
for j in range(rows):
colsbyrows[i][j] = rowsbycols[j][i]
return colsbyrows
def make_teapot(enumname, resolution):
filenames = [None, teapot, teaspoon]
try:
indexes = int(enumname)
filename = filenames[indexes]
except:
print("Add Teapot Error: EnumProperty could not be set")
filename = filenames[1]
patches = read_indexed_patch_file(filename)
raw = patches_to_raw(patches, resolution)
verts, faces = raw_to_indexed(raw)
return (verts, faces)
# =================================
# === Indexed Bezier Data Block ===
# =================================
teapot = """32
1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16
4,17,18,19,8,20,21,22,12,23,24,25,16,26,27,28
19,29,30,31,22,32,33,34,25,35,36,37,28,38,39,40
31,41,42,1,34,43,44,5,37,45,46,9,40,47,48,13
13,14,15,16,49,50,51,52,53,54,55,56,57,58,59,60
16,26,27,28,52,61,62,63,56,64,65,66,60,67,68,69
28,38,39,40,63,70,71,72,66,73,74,75,69,76,77,78
40,47,48,13,72,79,80,49,75,81,82,53,78,83,84,57
57,58,59,60,85,86,87,88,89,90,91,92,93,94,95,96
60,67,68,69,88,97,98,99,92,100,101,102,96,103,104,105
69,76,77,78,99,106,107,108,102,109,110,111,105,112,113,114
78,83,84,57,108,115,116,85,111,117,118,89,114,119,120,93
121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136
124,137,138,121,128,139,140,125,132,141,142,129,136,143,144,133
133,134,135,136,145,146,147,148,149,150,151,152,69,153,154,155
136,143,144,133,148,156,157,145,152,158,159,149,155,160,161,69
162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177
165,178,179,162,169,180,181,166,173,182,183,170,177,184,185,174
174,175,176,177,186,187,188,189,190,191,192,193,194,195,196,197
177,184,185,174,189,198,199,186,193,200,201,190,197,202,203,194
204,204,204,204,207,208,209,210,211,211,211,211,212,213,214,215
204,204,204,204,210,217,218,219,211,211,211,211,215,220,221,222
204,204,204,204,219,224,225,226,211,211,211,211,222,227,228,229
204,204,204,204,226,230,231,207,211,211,211,211,229,232,233,212
212,213,214,215,234,235,236,237,238,239,240,241,242,243,244,245
215,220,221,222,237,246,247,248,241,249,250,251,245,252,253,254
222,227,228,229,248,255,256,257,251,258,259,260,254,261,262,263
229,232,233,212,257,264,265,234,260,266,267,238,263,268,269,242
270,270,270,270,279,280,281,282,275,276,277,278,271,272,273,274
270,270,270,270,282,289,290,291,278,286,287,288,274,283,284,285
270,270,270,270,291,298,299,300,288,295,296,297,285,292,293,294
270,270,270,270,300,305,306,279,297,303,304,275,294,301,302,271
306
1.4,0.0,2.4
1.4,-0.784,2.4
0.784,-1.4,2.4
0.0,-1.4,2.4
1.3375,0.0,2.53125
1.3375,-0.749,2.53125
0.749,-1.3375,2.53125
0.0,-1.3375,2.53125
1.4375,0.0,2.53125
1.4375,-0.805,2.53125
0.805,-1.4375,2.53125
0.0,-1.4375,2.53125
1.5,0.0,2.4
1.5,-0.84,2.4
0.84,-1.5,2.4
0.0,-1.5,2.4
-0.784,-1.4,2.4
-1.4,-0.784,2.4
-1.4,0.0,2.4
-0.749,-1.3375,2.53125
-1.3375,-0.749,2.53125
-1.3375,0.0,2.53125
-0.805,-1.4375,2.53125
-1.4375,-0.805,2.53125
-1.4375,0.0,2.53125
-0.84,-1.5,2.4
-1.5,-0.84,2.4
-1.5,0.0,2.4
-1.4,0.784,2.4
-0.784,1.4,2.4
0.0,1.4,2.4
-1.3375,0.749,2.53125
-0.749,1.3375,2.53125
0.0,1.3375,2.53125
-1.4375,0.805,2.53125
-0.805,1.4375,2.53125
0.0,1.4375,2.53125
-1.5,0.84,2.4
-0.84,1.5,2.4
0.0,1.5,2.4
0.784,1.4,2.4
1.4,0.784,2.4
0.749,1.3375,2.53125
1.3375,0.749,2.53125
0.805,1.4375,2.53125
1.4375,0.805,2.53125
0.84,1.5,2.4
1.5,0.84,2.4
1.75,0.0,1.875
1.75,-0.98,1.875
0.98,-1.75,1.875
0.0,-1.75,1.875
2.0,0.0,1.35
2.0,-1.12,1.35
1.12,-2.0,1.35
0.0,-2.0,1.35
2.0,0.0,0.9
2.0,-1.12,0.9
1.12,-2.0,0.9
0.0,-2.0,0.9
-0.98,-1.75,1.875
-1.75,-0.98,1.875
-1.75,0.0,1.875
-1.12,-2.0,1.35
-2.0,-1.12,1.35
-2.0,0.0,1.35
-1.12,-2.0,0.9
-2.0,-1.12,0.9
-2.0,0.0,0.9
-1.75,0.98,1.875
-0.98,1.75,1.875
0.0,1.75,1.875
-2.0,1.12,1.35
-1.12,2.0,1.35
0.0,2.0,1.35
-2.0,1.12,0.9
-1.12,2.0,0.9
0.0,2.0,0.9
0.98,1.75,1.875
1.75,0.98,1.875
1.12,2.0,1.35
2.0,1.12,1.35
1.12,2.0,0.9
2.0,1.12,0.9
2.0,0.0,0.45
2.0,-1.12,0.45
1.12,-2.0,0.45
0.0,-2.0,0.45
1.5,0.0,0.225
1.5,-0.84,0.225
0.84,-1.5,0.225
0.0,-1.5,0.225
1.5,0.0,0.15
1.5,-0.84,0.15
0.84,-1.5,0.15
0.0,-1.5,0.15
-1.12,-2.0,0.45
-2.0,-1.12,0.45
-2.0,0.0,0.45
-0.84,-1.5,0.225
-1.5,-0.84,0.225
-1.5,0.0,0.225
-0.84,-1.5,0.15
-1.5,-0.84,0.15
-1.5,0.0,0.15
-2.0,1.12,0.45
-1.12,2.0,0.45
0.0,2.0,0.45
-1.5,0.84,0.225
-0.84,1.5,0.225
0.0,1.5,0.225
-1.5,0.84,0.15
-0.84,1.5,0.15
0.0,1.5,0.15
1.12,2.0,0.45
2.0,1.12,0.45
0.84,1.5,0.225
1.5,0.84,0.225
0.84,1.5,0.15
1.5,0.84,0.15
-1.6,0.0,2.025
-1.6,-0.3,2.025
-1.5,-0.3,2.25
-1.5,0.0,2.25
-2.3,0.0,2.025
-2.3,-0.3,2.025
-2.5,-0.3,2.25
-2.5,0.0,2.25
-2.7,0.0,2.025
-2.7,-0.3,2.025
-3.0,-0.3,2.25
-3.0,0.0,2.25
-2.7,0.0,1.8
-2.7,-0.3,1.8
-3.0,-0.3,1.8
-3.0,0.0,1.8
-1.5,0.3,2.25
-1.6,0.3,2.025
-2.5,0.3,2.25
-2.3,0.3,2.025
-3.0,0.3,2.25
-2.7,0.3,2.025
-3.0,0.3,1.8
-2.7,0.3,1.8
-2.7,0.0,1.575
-2.7,-0.3,1.575
-3.0,-0.3,1.35
-3.0,0.0,1.35
-2.5,0.0,1.125
-2.5,-0.3,1.125
-2.65,-0.3,0.9375
-2.65,0.0,0.9375
-2.0,-0.3,0.9
-1.9,-0.3,0.6
-1.9,0.0,0.6
-3.0,0.3,1.35
-2.7,0.3,1.575
-2.65,0.3,0.9375
-2.5,0.3,1.125
-1.9,0.3,0.6
-2.0,0.3,0.9
1.7,0.0,1.425
1.7,-0.66,1.425
1.7,-0.66,0.6
1.7,0.0,0.6
2.6,0.0,1.425
2.6,-0.66,1.425
3.1,-0.66,0.825
3.1,0.0,0.825
2.3,0.0,2.1
2.3,-0.25,2.1
2.4,-0.25,2.025
2.4,0.0,2.025
2.7,0.0,2.4
2.7,-0.25,2.4
3.3,-0.25,2.4
3.3,0.0,2.4
1.7,0.66,0.6
1.7,0.66,1.425
3.1,0.66,0.825
2.6,0.66,1.425
2.4,0.25,2.025
2.3,0.25,2.1
3.3,0.25,2.4
2.7,0.25,2.4
2.8,0.0,2.475
2.8,-0.25,2.475
3.525,-0.25,2.49375
3.525,0.0,2.49375
2.9,0.0,2.475
2.9,-0.15,2.475
3.45,-0.15,2.5125
3.45,0.0,2.5125
2.8,0.0,2.4
2.8,-0.15,2.4
3.2,-0.15,2.4
3.2,0.0,2.4
3.525,0.25,2.49375
2.8,0.25,2.475
3.45,0.15,2.5125
2.9,0.15,2.475
3.2,0.15,2.4
2.8,0.15,2.4
0.0,0.0,3.15
0.0,-0.002,3.15
0.002,0.0,3.15
0.8,0.0,3.15
0.8,-0.45,3.15
0.45,-0.8,3.15
0.0,-0.8,3.15
0.0,0.0,2.85
0.2,0.0,2.7
0.2,-0.112,2.7
0.112,-0.2,2.7
0.0,-0.2,2.7
-0.002,0.0,3.15
-0.45,-0.8,3.15
-0.8,-0.45,3.15
-0.8,0.0,3.15
-0.112,-0.2,2.7
-0.2,-0.112,2.7
-0.2,0.0,2.7
0.0,0.002,3.15
-0.8,0.45,3.15
-0.45,0.8,3.15
0.0,0.8,3.15
-0.2,0.112,2.7
-0.112,0.2,2.7
0.0,0.2,2.7
0.45,0.8,3.15
0.8,0.45,3.15
0.112,0.2,2.7
0.2,0.112,2.7
0.4,0.0,2.55
0.4,-0.224,2.55
0.224,-0.4,2.55
0.0,-0.4,2.55
1.3,0.0,2.55
1.3,-0.728,2.55
0.728,-1.3,2.55
0.0,-1.3,2.55
1.3,0.0,2.4
1.3,-0.728,2.4
0.728,-1.3,2.4
0.0,-1.3,2.4
-0.224,-0.4,2.55
-0.4,-0.224,2.55
-0.4,0.0,2.55
-0.728,-1.3,2.55
-1.3,-0.728,2.55
-1.3,0.0,2.55
-0.728,-1.3,2.4
-1.3,-0.728,2.4
-1.3,0.0,2.4
-0.4,0.224,2.55
-0.224,0.4,2.55
0.0,0.4,2.55
-1.3,0.728,2.55
-0.728,1.3,2.55
0.0,1.3,2.55
-1.3,0.728,2.4
-0.728,1.3,2.4
0.0,1.3,2.4
0.224,0.4,2.55
0.4,0.224,2.55
0.728,1.3,2.55
1.3,0.728,2.55
0.728,1.3,2.4
1.3,0.728,2.4
0.0,0.0,0.0
1.5,0.0,0.15
1.5,0.84,0.15
0.84,1.5,0.15
0.0,1.5,0.15
1.5,0.0,0.075
1.5,0.84,0.075
0.84,1.5,0.075
0.0,1.5,0.075
1.425,0.0,0.0
1.425,0.798,0.0
0.798,1.425,0.0
0.0,1.425,0.0
-0.84,1.5,0.15
-1.5,0.84,0.15
-1.5,0.0,0.15
-0.84,1.5,0.075
-1.5,0.84,0.075
-1.5,0.0,0.075
-0.798,1.425,0.0
-1.425,0.798,0.0
-1.425,0.0,0.0
-1.5,-0.84,0.15
-0.84,-1.5,0.15
0.0,-1.5,0.15
-1.5,-0.84,0.075
-0.84,-1.5,0.075
0.0,-1.5,0.075
-1.425,-0.798,0.0
-0.798,-1.425,0.0
0.0,-1.425,0.0
0.84,-1.5,0.15
1.5,-0.84,0.15
0.84,-1.5,0.075
1.5,-0.84,0.075
0.798,-1.425,0.0
1.425,-0.798,0.0
"""
teaspoon = """16
1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16
17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32
33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48
49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64
65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80
81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96
97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112
113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128
129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144
145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160
161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176
177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192
193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208
209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224
225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240
241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256
256
-0.000107143,0.205357,0.0
0.0,0.196429,-0.0178571
0.0,0.196429,-0.0178571
0.000107143,0.205357,0.0
-0.0535714,0.205357,0.0
-0.0222714,0.178571,-0.0534286
0.0222714,0.178571,-0.0534286
0.0535714,0.205357,0.0
-0.107143,0.0952429,-0.0178571
-0.0446429,0.0952429,-0.0892857
0.0446429,0.0952429,-0.0892857
0.107143,0.0952429,-0.0178571
-0.107143,0.0,-0.0178571
-0.0446429,0.0,-0.0892857
0.0446429,0.0,-0.0892857
0.107143,0.0,-0.0178571
0.000107143,0.205357,0.0
0.000135714,0.207589,0.00446429
0.000157143,0.216518,0.00446429
0.000125,0.214286,0.0
0.0535714,0.205357,0.0
0.0613964,0.212054,0.0133571
0.0714286,0.220982,0.015625
0.0625,0.214286,0.0
0.107143,0.0952429,-0.0178571
0.122768,0.0952429,0.0
0.142857,0.0952429,0.00446429
0.125,0.0952429,-0.0178571
0.107143,0.0,-0.0178571
0.122768,0.0,0.0
0.142857,0.0,0.00446429
0.125,0.0,-0.0178571
0.000125,0.214286,0.0
0.0,0.205357,-0.0178571
0.0,0.205357,-0.0178571
-0.000125,0.214286,0.0
0.0625,0.214286,0.0
0.0267857,0.1875,-0.0625
-0.0267857,0.1875,-0.0625
-0.0625,0.214286,0.0
0.125,0.0952429,-0.0178571
0.0535714,0.0952429,-0.107143
-0.0535714,0.0952429,-0.107143
-0.125,0.0952429,-0.0178571
0.125,0.0,-0.0178571
0.0535714,0.0,-0.107143
-0.0535714,0.0,-0.107143
-0.125,0.0,-0.0178571
-0.000125,0.214286,0.0
-0.000157143,0.216518,0.00446429
-0.000135714,0.207589,0.00446429
-0.000107143,0.205357,0.0
-0.0625,0.214286,0.0
-0.0714286,0.220982,0.015625
-0.0613964,0.212054,0.0133571
-0.0535714,0.205357,0.0
-0.125,0.0952429,-0.0178571
-0.142857,0.0952429,0.00446429
-0.122768,0.0952429,0.0
-0.107143,0.0952429,-0.0178571
-0.125,0.0,-0.0178571
-0.142857,0.0,0.00446429
-0.122768,0.0,0.0
-0.107143,0.0,-0.0178571
-0.107143,0.0,-0.0178571
-0.0446429,0.0,-0.0892857
0.0446429,0.0,-0.0892857
0.107143,0.0,-0.0178571
-0.107143,-0.142857,-0.0178571
-0.0446429,-0.142857,-0.0892857
0.0446429,-0.142857,-0.0892857
0.107143,-0.142857,-0.0178571
-0.0133929,-0.160714,0.0386893
-0.00557857,-0.160714,0.0386893
0.00557857,-0.160714,0.0386893
0.0133929,-0.160714,0.0386893
-0.0133929,-0.25,0.0535714
-0.00557857,-0.25,0.0535714
0.00557857,-0.25,0.0535714
0.0133929,-0.25,0.0535714
0.107143,0.0,-0.0178571
0.122768,0.0,0.0
0.142857,0.0,0.00446429
0.125,0.0,-0.0178571
0.107143,-0.142857,-0.0178571
0.122768,-0.142857,0.0
0.142857,-0.142857,0.00446429
0.125,-0.142857,-0.0178571
0.0133929,-0.160714,0.0386893
0.0153464,-0.160714,0.0386893
0.0178571,-0.160714,0.0314357
0.015625,-0.160714,0.0297607
0.0133929,-0.25,0.0535714
0.0153464,-0.25,0.0535714
0.0178571,-0.25,0.0463179
0.015625,-0.25,0.0446429
0.125,0.0,-0.0178571
0.0535714,0.0,-0.107143
-0.0535714,0.0,-0.107143
-0.125,0.0,-0.0178571
0.125,-0.142857,-0.0178571
0.0535714,-0.142857,-0.107143
-0.0535714,-0.142857,-0.107143
-0.125,-0.142857,-0.0178571
0.015625,-0.160714,0.0297607
0.00669643,-0.160714,0.0230643
-0.00781071,-0.160714,0.0208321
-0.015625,-0.160714,0.0297607
0.015625,-0.25,0.0446429
0.00669643,-0.25,0.0379464
-0.00781071,-0.25,0.0357143
-0.015625,-0.25,0.0446429
-0.125,0.0,-0.0178571
-0.142857,0.0,0.00446429
-0.122768,0.0,0.0
-0.107143,0.0,-0.0178571
-0.125,-0.142857,-0.0178571
-0.142857,-0.142857,0.00446429
-0.122768,-0.142857,0.0
-0.107143,-0.142857,-0.0178571
-0.015625,-0.160714,0.0297607
-0.0175786,-0.160714,0.0319929
-0.0153464,-0.160714,0.0386893
-0.0133929,-0.160714,0.0386893
-0.015625,-0.25,0.0446429
-0.0175786,-0.25,0.046875
-0.0153464,-0.25,0.0535714
-0.0133929,-0.25,0.0535714
-0.0133929,-0.25,0.0535714
-0.00557857,-0.25,0.0535714
0.00557857,-0.25,0.0535714
0.0133929,-0.25,0.0535714
-0.0133929,-0.46425,0.0892857
-0.00557857,-0.46425,0.0892857
0.00557857,-0.46425,0.0892857
0.0133929,-0.46425,0.0892857
-0.0446429,-0.678571,0.0535714
-0.00892857,-0.678571,0.0625
0.00892857,-0.678571,0.0625
0.0446429,-0.678571,0.0535714
-0.0446429,-0.857143,0.0357143
-0.00892857,-0.857143,0.0446429
0.00892857,-0.857143,0.0446429
0.0446429,-0.857143,0.0357143
0.0133929,-0.25,0.0535714
0.0153464,-0.25,0.0535714
0.0178571,-0.25,0.0463179
0.015625,-0.25,0.0446429
0.0133929,-0.46425,0.0892857
0.0153464,-0.464286,0.0892857
0.0178571,-0.46425,0.0820321
0.015625,-0.46425,0.0803571
0.0446429,-0.678571,0.0535714
0.0535714,-0.678571,0.0513393
0.0535714,-0.678571,0.0334821
0.0446429,-0.678571,0.0357143
0.0446429,-0.857143,0.0357143
0.0535714,-0.857143,0.0334821
0.0535714,-0.857143,0.015625
0.0446429,-0.857143,0.0178571
0.015625,-0.25,0.0446429
0.00669643,-0.25,0.0379464
-0.00781071,-0.25,0.0357143
-0.015625,-0.25,0.0446429
0.015625,-0.46425,0.0803571
0.00669643,-0.464286,0.0736607
-0.00781071,-0.46425,0.0714286
-0.015625,-0.46425,0.0803571
0.0446429,-0.678571,0.0357143
0.00892857,-0.678571,0.0446429
-0.00892857,-0.678571,0.0446429
-0.0446429,-0.678571,0.0357143
0.0446429,-0.857143,0.0178571
0.00892857,-0.857143,0.0267857
-0.00892857,-0.857143,0.0267857
-0.0446429,-0.857143,0.0178571
-0.015625,-0.25,0.0446429
-0.0175786,-0.25,0.046875
-0.0153464,-0.25,0.0535714
-0.0133929,-0.25,0.0535714
-0.015625,-0.46425,0.0803571
-0.0175786,-0.464286,0.0825893
-0.0153464,-0.464286,0.0892857
-0.0133929,-0.46425,0.0892857
-0.0446429,-0.678571,0.0357143
-0.0535714,-0.678571,0.0334821
-0.0535714,-0.678571,0.0513393
-0.0446429,-0.678571,0.0535714
-0.0446429,-0.857143,0.0178571
-0.0535714,-0.857143,0.015625
-0.0535714,-0.857143,0.0334821
-0.0446429,-0.857143,0.0357143
-0.0446429,-0.857143,0.0357143
-0.00892857,-0.857143,0.0446429
0.00892857,-0.857143,0.0446429
0.0446429,-0.857143,0.0357143
-0.0446429,-0.928571,0.0285714
-0.00892857,-0.928571,0.0375
0.00892857,-0.928571,0.0375
0.0446429,-0.928571,0.0285714
-0.0539286,-0.999643,0.0178571
0.000357143,-0.999643,0.0178571
0.0,-0.999643,0.0178571
0.0535714,-0.999643,0.0178571
-0.000357143,-1,0.0178571
0.000357143,-1,0.0178571
0.0,-1,0.0178571
0.0,-1,0.0178571
0.0446429,-0.857143,0.0357143
0.0535714,-0.857143,0.0334821
0.0535714,-0.857143,0.015625
0.0446429,-0.857143,0.0178571
0.0446429,-0.928571,0.0285714
0.0535714,-0.928571,0.0263393
0.0535714,-0.928571,0.00848214
0.0446429,-0.928571,0.0107143
0.0535714,-0.999643,0.0178571
0.0669643,-0.999643,0.0178571
0.0673214,-0.999643,0.0
0.0539286,-0.999643,0.0
0.0,-1,0.0178571
0.0,-1,0.0178571
0.000357143,-1,0.0
0.000357143,-1,0.0
0.0446429,-0.857143,0.0178571
0.00892857,-0.857143,0.0267857
-0.00892857,-0.857143,0.0267857
-0.0446429,-0.857143,0.0178571
0.0446429,-0.928571,0.0107143
0.00892857,-0.928571,0.0196429
-0.00892857,-0.928571,0.0196429
-0.0446429,-0.928571,0.0107143
0.0539286,-0.999643,0.0
0.000357143,-0.999643,0.0
-0.000357143,-0.999643,0.0
-0.0539286,-0.999643,0.0
0.000357143,-1,0.0
0.000357143,-1,0.0
-0.000357143,-1,0.0
-0.000357143,-1,0.0
-0.0446429,-0.857143,0.0178571
-0.0535714,-0.857143,0.015625
-0.0535714,-0.857143,0.0334821
-0.0446429,-0.857143,0.0357143
-0.0446429,-0.928571,0.0107143
-0.0535714,-0.928571,0.00848214
-0.0535714,-0.928571,0.0263393
-0.0446429,-0.928571,0.0285714
-0.0539286,-0.999643,0.0
-0.0673214,-0.999643,0.0
-0.0675,-0.999643,0.0178571
-0.0539286,-0.999643,0.0178571
-0.000357143,-1,0.0
-0.000357143,-1,0.0
-0.000535714,-1,0.0178571
-0.000357143,-1,0.0178571
"""
@@ -0,0 +1,201 @@
# SPDX-FileCopyrightText: 2012-2022 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later
# Author: Anthony D'Agostino
import bpy
from mathutils import Vector
from math import sin, cos, pi
from bpy.props import (
BoolProperty,
IntProperty,
StringProperty,
)
from bpy_extras import object_utils
from .interface import draw_transform_props
def create_mesh_object(context, verts, edges, faces, name):
# Create new mesh
mesh = bpy.data.meshes.new(name)
# Make a mesh from a list of verts/edges/faces.
mesh.from_pydata(verts, edges, faces)
# Update mesh geometry after adding stuff.
mesh.update()
from bpy_extras import object_utils
return object_utils.object_data_add(context, mesh, operator=None)
# ========================
# === Torus Knot Block ===
# ========================
def k1(t):
x = cos(t) - 2 * cos(2 * t)
y = sin(t) + 2 * sin(2 * t)
z = sin(3 * t)
return Vector([x, y, z])
def k2(t):
x = 10 * (cos(t) + cos(3 * t)) + cos(2 * t) + cos(4 * t)
y = 6 * sin(t) + 10 * sin(3 * t)
z = 4 * sin(3 * t) * sin(5 * t / 2) + 4 * sin(4 * t) - 2 * sin(6 * t)
return Vector([x, y, z]) * 0.2
def k3(t):
x = 2.5 * cos(t + pi) / 3 + 2 * cos(3 * t)
y = 2.5 * sin(t) / 3 + 2 * sin(3 * t)
z = 1.5 * sin(4 * t) + sin(2 * t) / 3
return Vector([x, y, z])
def make_verts(ures, vres, r2, knotfunc):
verts = []
for i in range(ures):
t1 = (i + 0) * 2 * pi / ures
t2 = (i + 1) * 2 * pi / ures
a = knotfunc(t1) # curr point
b = knotfunc(t2) # next point
a, b = map(Vector, (a, b))
e = a - b
f = a + b
g = e.cross(f)
h = e.cross(g)
g.normalize()
h.normalize()
for j in range(vres):
k = j * 2 * pi / vres
l = (cos(k), 0.0, sin(k))
l = Vector(l)
m = l * r2
x, y, z = m
n = h * x
o = g * z
p = n + o
q = a + p
verts.append(q)
return verts
def make_faces(ures, vres):
faces = []
for u in range(0, ures):
for v in range(0, vres):
p1 = v + u * vres
p2 = v + ((u + 1) % ures) * vres
p4 = (v + 1) % vres + u * vres
p3 = (v + 1) % vres + ((u + 1) % ures) * vres
faces.append([p4, p3, p2, p1])
return faces
def make_knot(knotidx, ures):
knots = [k1, k2, k3]
knotfunc = knots[knotidx - 1]
vres = ures // 10
r2 = 0.5
verts = make_verts(ures, vres, r2, knotfunc)
faces = make_faces(ures, vres)
return (verts, faces)
class AddTorusKnot(bpy.types.Operator, object_utils.AddObjectHelper):
bl_idname = "mesh.primitive_torusknot_add"
bl_label = "Add Torus Knot"
bl_description = "Construct a torus knot mesh"
bl_options = {"REGISTER", "UNDO"}
TorusKnot : BoolProperty(name = "TorusKnot",
default = True,
description = "TorusKnot")
change : BoolProperty(name = "Change",
default = False,
description = "change TorusKnot")
resolution: IntProperty(
name="Resolution",
description="Resolution of the Torus Knot",
default=80,
min=30, max=256
)
objecttype: IntProperty(
name="Knot Type",
description="Type of Knot",
default=1,
min=1, max=3
)
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
layout.prop(self, 'resolution', expand=True)
layout.prop(self, 'objecttype', expand=True)
if self.change == False:
draw_transform_props(self, layout)
def execute(self, context):
# turn off 'Enter Edit Mode'
use_enter_edit_mode = bpy.context.preferences.edit.use_enter_edit_mode
bpy.context.preferences.edit.use_enter_edit_mode = False
if bpy.context.mode == "OBJECT":
if context.selected_objects != [] and context.active_object and \
(context.active_object.data is not None) and ('TorusKnot' in context.active_object.data.keys()) and \
(self.change == True):
obj = context.active_object
oldmesh = obj.data
oldmeshname = obj.data.name
verts, faces = make_knot(self.objecttype, self.resolution)
mesh = bpy.data.meshes.new('TorusKnot')
mesh.from_pydata(verts, [], faces)
obj.data = mesh
for material in oldmesh.materials:
obj.data.materials.append(material)
bpy.data.meshes.remove(oldmesh)
obj.data.name = oldmeshname
else:
verts, faces = make_knot(self.objecttype, self.resolution)
mesh = bpy.data.meshes.new('TorusKnot')
mesh.from_pydata(verts, [], faces)
obj = object_utils.object_data_add(context, mesh, operator=self)
obj.data["TorusKnot"] = True
obj.data["change"] = False
for prm in TorusKnotParameters():
obj.data[prm] = getattr(self, prm)
if bpy.context.mode == "EDIT_MESH":
active_object = context.active_object
name_active_object = active_object.name
bpy.ops.object.mode_set(mode='OBJECT')
verts, faces = make_knot(self.objecttype, self.resolution)
mesh = bpy.data.meshes.new('TorusKnot')
mesh.from_pydata(verts, [], faces)
obj = object_utils.object_data_add(context, mesh, operator=self)
obj.select_set(True)
active_object.select_set(True)
bpy.context.view_layer.objects.active = active_object
bpy.ops.object.join()
context.active_object.name = name_active_object
bpy.ops.object.mode_set(mode='EDIT')
if use_enter_edit_mode:
bpy.ops.object.mode_set(mode = 'EDIT')
# restore pre operator state
bpy.context.preferences.edit.use_enter_edit_mode = use_enter_edit_mode
return {'FINISHED'}
def TorusKnotParameters():
TorusKnotParameters = [
"resolution",
"objecttype",
]
return TorusKnotParameters
@@ -0,0 +1,313 @@
# SPDX-FileCopyrightText: 2017-2023 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later
"""
This script provides a triangle mesh primitive
and a toolbar menu to further specify settings
"""
import math
import bpy
from mathutils import Vector
from bpy.types import Operator
from bpy.props import (
BoolProperty,
EnumProperty,
FloatProperty,
)
from bpy_extras import object_utils
from .interface import draw_transform_props
def checkEditMode():
# Check if we are in edit mode
# Returns: 1 if True
# 0 if False
if (bpy.context.active_object.mode == 'EDIT'):
return 1
return 0
def exitEditMode():
# Check if we are in edit mode (cuz we don't want this when creating a new Mesh)
# If we are then toggle back to object mode
# Check if there are active objects
if bpy.context.active_object is not None:
# Only the active object should be in edit mode
if (bpy.context.active_object.mode == 'EDIT'):
bpy.ops.object.editmode_toggle()
class MakeTriangle(Operator, object_utils.AddObjectHelper):
bl_idname = "mesh.make_triangle"
bl_label = "Add Triangle"
bl_description = "Construct different types of Triangle Meshes"
bl_options = {"REGISTER", "UNDO"}
nothing = 0
Ya = 0.0
Xb = 0.0
Xc = 0.0
Vertices = []
Faces = []
triangleTypeList = [
('ISOSCELES', "Isosceles", "Two equal sides", 0),
('EQUILATERAL', "Equilateral", "Three equal sides and angles (60°)", 1),
('ISOSCELESRIGHTANGLE', "Isosceles right angled", "90° angle and two equal sides", 2),
('SCALENERIGHTANGLE', "Scalene right angled", "90° angle, no equal sides", 3)
]
triangleFaceList = [
('DEFAULT', "Triangle", "1 Triangle face", 0),
('TRIANGLES', "3 Triangles", "4 Vertices & 3 Triangle faces", 1),
('QUADS', "3 Quads", "7 Vertices & 3 Quad faces", 2),
('SAFEQUADS', "6 Quads", "12 Vertices & 6 Quad faces", 3)
]
# add definitions for some manipulation buttons
flipX: BoolProperty(
name="Flip X sign",
description="Draw on the other side of the X axis (Mirror on Y axis)",
default=False
)
flipY: BoolProperty(
name="Flip Y sign",
description="Draw on the other side of the Y axis (Mirror on X axis)",
default=False
)
scale: FloatProperty(
name="Scale",
description="Triangle scale",
default=1.0,
min=1.0
)
triangleType: EnumProperty(
items=triangleTypeList,
name="Type",
description="Triangle Type"
)
triangleFace: EnumProperty(
items=triangleFaceList,
name="Face Types",
description="Triangle Face Types"
)
at_3Dcursor: BoolProperty(
name="Use 3D Cursor",
description="Draw the triangle where the 3D cursor is",
default=True
)
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
col = layout.column()
col.prop(self, "triangleType", text="Type")
col.prop(self, "triangleFace", text="Fill Type")
col.prop(self, "scale")
col.separator()
row = col.row(heading='At')
row.prop(self, "at_3Dcursor", text="3D Cursor")
col.separator()
row = col.row(heading='Flip')
row.prop(self, "flipX", text='X')
col.prop(self, "flipY", text='Y')
col.separator()
draw_transform_props(self, col)
def drawBasicTriangleShape(self):
# set everything to 0
Xb = Xc = 0.0
Ya = 0.0
scale = self.scale
Xsign = -1 if self.flipX else 1
Ysign = -1 if self.flipY else 1
# Isosceles (2 equal sides)
if (self.triangleType == 'ISOSCELES'):
# below a simple triangle containing 2 triangles with 1:2 side ratio
Ya = (1 * Ysign * scale)
A = Vector([0.0, Ya, 0.0])
Xb = (0.5 * Xsign * scale)
B = Vector([Xb, 0.0, 0.0])
Xc = (-0.5 * Xsign * scale)
C = Vector([Xc, 0.0, 0.0])
self.Ya = Ya
self.Xb = Xb
self.Xc = Xc
self.Vertices = [A, B, C, ]
return True
# Equilateral (all sides equal)
if (self.triangleType == 'EQUILATERAL'):
Ya = (math.sqrt(0.75) * Ysign * scale)
A = Vector([0.0, Ya, 0.0])
Xb = (0.5 * Xsign * scale)
B = Vector([Xb, 0.0, 0.0])
Xc = (-0.5 * Xsign * scale)
C = Vector([Xc, 0.0, 0.0])
self.Ya = Ya
self.Xb = Xb
self.Xc = Xc
self.Vertices = [A, B, C, ]
return True
# Isosceles right angled (1, 1, sqrt(2))
if (self.triangleType == 'ISOSCELESRIGHTANGLE'):
Ya = (1 * Ysign * scale)
A = Vector([0.0, Ya, 0.0])
Xb = 0.0
B = Vector([Xb, 0.0, 0.0])
Xc = (1 * Xsign * scale)
C = Vector([Xc, 0.0, 0.0])
self.Ya = Ya
self.Xb = Xb
self.Xc = Xc
self.Vertices = [A, B, C, ]
return True
# Scalene right angled (3, 4, 5)
if (self.triangleType == 'SCALENERIGHTANGLE'):
Ya = (1 * Ysign * scale)
A = Vector([0.0, Ya, 0.0])
Xb = 0
B = Vector([Xb, 0.0, 0.0])
Xc = (0.75 * Xsign * scale)
C = Vector([Xc, 0.0, 0.0])
self.Ya = Ya
self.Xb = Xb
self.Xc = Xc
self.Vertices = [A, B, C, ]
return True
return False
def addFaces(self, fType=None):
Ya = self.Ya
Xb = self.Xb
Xc = self.Xc
if (self.triangleFace == 'DEFAULT'):
self.Faces = [[0, 1, 2]]
return True
if (self.triangleFace == 'TRIANGLES'):
A = Vector([0.0, Ya, 0.0])
B = Vector([Xb, 0.0, 0.0])
C = Vector([Xc, 0.0, 0.0])
D = Vector([((A.x + B.x + C.x) / 3), ((A.y + B.y + C.y) / 3), ((A.z + B.z + C.z) / 3)])
self.Vertices = [A, B, C, D, ]
self.Faces = [[0, 1, 3], [1, 2, 3], [2, 0, 3]]
return True
if (self.triangleFace == 'QUADS'):
A = Vector([0.0, Ya, 0.0])
B = Vector([Xb, 0.0, 0.0])
C = Vector([Xc, 0.0, 0.0])
D = Vector([((A.x + B.x + C.x) / 3), ((A.y + B.y + C.y) / 3), ((A.z + B.z + C.z) / 3)])
AB = A.lerp(B, 0.5)
AC = A.lerp(C, 0.5)
BC = B.lerp(C, 0.5)
self.Vertices = [A, AB, B, BC, C, AC, D, ]
self.Faces = [[0, 1, 6, 5], [1, 2, 3, 6], [3, 4, 5, 6]]
return True
if (self.triangleFace == 'SAFEQUADS'):
A = Vector([0.0, Ya, 0.0])
B = Vector([Xb, 0.0, 0.0])
C = Vector([Xc, 0.0, 0.0])
D = Vector([((A.x + B.x + C.x) / 3), ((A.y + B.y + C.y) / 3), ((A.z + B.z + C.z) / 3)])
E = A.lerp(D, 0.5)
AB = A.lerp(B, 0.5)
AC = A.lerp(C, 0.5)
BC = B.lerp(C, 0.5)
AAB = AB.lerp(A, 0.5)
AAC = AC.lerp(A, 0.5)
BBA = AB.lerp(B, 0.5)
BBC = BC.lerp(B, 0.5)
BCC = BC.lerp(C, 0.5)
CCA = AC.lerp(C, 0.5)
self.Vertices = [A, AAB, BBA, B, BBC, BC, BCC, C, CCA, AAC, D, E, ]
self.Faces = [[0, 1, 11, 9], [1, 2, 10, 11], [2, 3, 4, 10],
[4, 5, 6, 10], [6, 7, 8, 10], [8, 9, 11, 10]]
return True
return False
def action_common(self, context):
# definitions:
# a triangle consists of 3 points: A, B, C
# a 'safer' subdividable triangle consists of 4 points: A, B, C, D
# a subdivide friendly triangle consists of 7 points: A, B, C, D, AB, AC, BC
# a truly subdivide friendly triangle consists of (3 x 4 = )12 points:
# A, B, C, D, E, BC, AAB, AAC, BBA, BBC, BCC, CCA
BasicShapeCreated = False
ShapeFacesCreated = False
go = 0
#
# call the functions for creating the triangles and test if successful
#
BasicShapeCreated = self.drawBasicTriangleShape()
if (BasicShapeCreated):
ShapeFacesCreated = self.addFaces()
if ShapeFacesCreated:
go = 1
if (go == 1):
NewMesh = bpy.data.meshes.new("Triangle")
NewMesh.from_pydata(self.Vertices, [], self.Faces)
NewMesh.update()
NewObj = bpy.data.objects.new("Triangle", NewMesh)
context.collection.objects.link(NewObj)
# before doing the deselect make sure edit mode isn't active
exitEditMode()
bpy.ops.object.select_all(action="DESELECT")
NewObj.select_set(True)
context.view_layer.objects.active = NewObj
if self.at_3Dcursor is True:
# we'll need to be sure there is actually an object selected
if NewObj.select_get() is True:
# we also have to check if we're considered to be in 3D View (view3d)
if bpy.ops.view3d.snap_selected_to_cursor.poll() is True:
bpy.ops.view3d.snap_selected_to_cursor()
else:
# as we weren't considered to be in 3D View
# the object couldn't be moved to the 3D cursor
# so to avoid confusion we change the at_3Dcursor boolean to false
self.at_3Dcursor = False
else:
self.report({'WARNING'},
"Triangle could not be completed. (See Console for more Info)")
print("\n[Add Mesh Extra Objects]\n\nModule: add_mesh_triangle")
print("Triangle type: %s\n" % self.triangleType,
"Face type: %s\n" % self.triangleFace,
"Ya: %s, Xb: %s, Xc: %s\n" % (self.Ya, self.Xb, self.Xc),
"Vertices: %s\n" % self.Vertices,
"Faces: %s\n" % self.Faces)
def execute(self, context):
self.action_common(context)
return {"FINISHED"}
def invoke(self, context, event):
self.action_common(context)
return {"FINISHED"}
@@ -0,0 +1,327 @@
# SPDX-FileCopyrightText: 2010-2022 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later
# Author: Paulo_Gomes
import bpy
from mathutils import Quaternion, Vector
from math import cos, sin, pi
from bpy.props import (
FloatProperty,
IntProperty,
BoolProperty,
EnumProperty,
)
from bpy_extras import object_utils
from .interface import draw_transform_props
# Create a new mesh (object) from verts/edges/faces
# verts/edges/faces ... List of vertices/edges/faces for the
# new mesh (as used in from_pydata)
# name ... Name of the new mesh (& object)
def create_mesh_object(context, verts, edges, faces, name):
# Create new mesh
mesh = bpy.data.meshes.new(name)
# Make a mesh from a list of verts/edges/faces
mesh.from_pydata(verts, edges, faces)
# Update mesh geometry after adding stuff
mesh.update()
from bpy_extras import object_utils
return object_utils.object_data_add(context, mesh, operator=None)
# A very simple "bridge" tool
def createFaces(vertIdx1, vertIdx2, closed=False, flipped=False):
faces = []
if not vertIdx1 or not vertIdx2:
return None
if len(vertIdx1) < 2 and len(vertIdx2) < 2:
return None
fan = False
if (len(vertIdx1) != len(vertIdx2)):
if (len(vertIdx1) == 1 and len(vertIdx2) > 1):
fan = True
else:
return None
total = len(vertIdx2)
if closed:
# Bridge the start with the end
if flipped:
face = [
vertIdx1[0],
vertIdx2[0],
vertIdx2[total - 1]]
if not fan:
face.append(vertIdx1[total - 1])
faces.append(face)
else:
face = [vertIdx2[0], vertIdx1[0]]
if not fan:
face.append(vertIdx1[total - 1])
face.append(vertIdx2[total - 1])
faces.append(face)
# Bridge the rest of the faces
for num in range(total - 1):
if flipped:
if fan:
face = [vertIdx2[num], vertIdx1[0], vertIdx2[num + 1]]
else:
face = [vertIdx2[num], vertIdx1[num],
vertIdx1[num + 1], vertIdx2[num + 1]]
faces.append(face)
else:
if fan:
face = [vertIdx1[0], vertIdx2[num], vertIdx2[num + 1]]
else:
face = [vertIdx1[num], vertIdx2[num],
vertIdx2[num + 1], vertIdx1[num + 1]]
faces.append(face)
return faces
def add_twisted_torus(major_rad, minor_rad, major_seg, minor_seg, twists):
PI_2 = pi * 2.0
z_axis = (0.0, 0.0, 1.0)
verts = []
faces = []
edgeloop_prev = []
for major_index in range(major_seg):
quat = Quaternion(z_axis, (major_index / major_seg) * PI_2)
rot_twists = PI_2 * major_index / major_seg * twists
edgeloop = []
# Create section ring
for minor_index in range(minor_seg):
angle = (PI_2 * minor_index / minor_seg) + rot_twists
vec = Vector((
major_rad + (cos(angle) * minor_rad),
0.0,
sin(angle) * minor_rad))
vec = quat @ vec
edgeloop.append(len(verts))
verts.append(vec)
# Remember very first edgeloop
if major_index == 0:
edgeloop_first = edgeloop
# Bridge last with current ring
if edgeloop_prev:
f = createFaces(edgeloop_prev, edgeloop, closed=True)
faces.extend(f)
edgeloop_prev = edgeloop
# Bridge first and last ring
f = createFaces(edgeloop_prev, edgeloop_first, closed=True)
faces.extend(f)
return verts, faces
class AddTwistedTorus(bpy.types.Operator, object_utils.AddObjectHelper):
bl_idname = "mesh.primitive_twisted_torus_add"
bl_label = "Add Twisted Torus"
bl_description = "Construct a twisted torus mesh"
bl_options = {'REGISTER', 'UNDO', 'PRESET'}
TwistedTorus : BoolProperty(name = "TwistedTorus",
default = True,
description = "TwistedTorus")
change : BoolProperty(name = "Change",
default = False,
description = "change TwistedTorus")
method: EnumProperty(
name='Method',
description='Method for determining the size and thickness of the torus',
items=(
('MAJOR-MINOR', 'Major / Minor', 'Uses the major radius for the overall size and the minor for the thickness'),
('INT-EXT', 'Interior / Exterior', 'Uses the absolute size of the inner and outer circles to determine the size and thickness'),
),
default='MAJOR-MINOR',
)
major_radius: FloatProperty(
name="Major Radius",
description="Radius from the origin to the"
" center of the cross section",
min=0.01,
max=100.0,
default=1.0
)
minor_radius: FloatProperty(
name="Minor Radius",
description="Radius of the torus' cross section",
min=0.01,
max=100.0,
default=0.25
)
major_segments: IntProperty(
name="Major Segments",
description="Number of segments for the main ring of the torus",
min=3,
max=256,
default=48
)
minor_segments: IntProperty(
name="Minor Segments",
description="Number of segments for the minor ring of the torus",
min=3,
max=256,
default=12
)
twists: IntProperty(
name="Twists",
description="Number of twists of the torus",
min=0,
max=256,
default=1
)
abso_major_rad: FloatProperty(
name="Exterior Radius",
description="Total Exterior Radius of the torus",
min=0.01,
max=100.0,
default=1.0
)
abso_minor_rad: FloatProperty(
name="Inside Radius",
description="Total Interior Radius of the torus",
min=0.01,
max=100.0,
default=0.5
)
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
layout.separator()
layout.prop(self, 'method', text='Dimensions Mode')
col = layout.column(align=True)
if self.method == 'MAJOR-MINOR':
col.prop(self, 'major_radius', text='Radius Major')
col.prop(self, 'minor_radius', text='Minor')
else:
col.prop(self, 'abso_major_rad', text='Radius Exterior')
col.prop(self, 'abso_minor_rad', text='Interior')
col = layout.column(align=True)
col.prop(self, 'major_segments', text='Segments Major')
col.prop(self, 'minor_segments', text='Minor')
layout.prop(self, 'twists', expand=True)
if self.change == False:
draw_transform_props(self, layout)
def execute(self, context):
# turn off 'Enter Edit Mode'
use_enter_edit_mode = bpy.context.preferences.edit.use_enter_edit_mode
bpy.context.preferences.edit.use_enter_edit_mode = False
if self.method == 'INT-EXT':
extra_helper = (self.abso_major_rad - self.abso_minor_rad) * 0.5
self.major_radius = self.abso_minor_rad + extra_helper
self.minor_radius = extra_helper
if bpy.context.mode == "OBJECT":
if context.selected_objects != [] and context.active_object and \
(context.active_object.data is not None) and ('TwistedTorus' in context.active_object.data.keys()) and \
(self.change == True):
obj = context.active_object
oldmesh = obj.data
oldmeshname = obj.data.name
verts, faces = add_twisted_torus(
self.major_radius,
self.minor_radius,
self.major_segments,
self.minor_segments,
self.twists
)
mesh = bpy.data.meshes.new('TwistedTorus')
mesh.from_pydata(verts, [], faces)
obj.data = mesh
for material in oldmesh.materials:
obj.data.materials.append(material)
bpy.data.meshes.remove(oldmesh)
obj.data.name = oldmeshname
else:
verts, faces = add_twisted_torus(
self.major_radius,
self.minor_radius,
self.major_segments,
self.minor_segments,
self.twists
)
mesh = bpy.data.meshes.new('TwistedTorus')
mesh.from_pydata(verts, [], faces)
obj = object_utils.object_data_add(context, mesh, operator=self)
obj.data["TwistedTorus"] = True
obj.data["change"] = False
for prm in TwistedTorusParameters():
obj.data[prm] = getattr(self, prm)
if bpy.context.mode == "EDIT_MESH":
active_object = context.active_object
name_active_object = active_object.name
bpy.ops.object.mode_set(mode='OBJECT')
verts, faces = add_twisted_torus(
self.major_radius,
self.minor_radius,
self.major_segments,
self.minor_segments,
self.twists
)
mesh = bpy.data.meshes.new('TwistedTorus')
mesh.from_pydata(verts, [], faces)
obj = object_utils.object_data_add(context, mesh, operator=self)
obj.select_set(True)
active_object.select_set(True)
bpy.context.view_layer.objects.active = active_object
bpy.ops.object.join()
context.active_object.name = name_active_object
bpy.ops.object.mode_set(mode='EDIT')
if use_enter_edit_mode:
bpy.ops.object.mode_set(mode = 'EDIT')
# restore pre operator state
bpy.context.preferences.edit.use_enter_edit_mode = use_enter_edit_mode
return {'FINISHED'}
def TwistedTorusParameters():
TwistedTorusParameters = [
"major_radius",
"minor_radius",
"major_segments",
"minor_segments",
"twists",
"method",
"abso_major_rad",
"abso_minor_rad",
]
return TwistedTorusParameters
@@ -0,0 +1,144 @@
# SPDX-FileCopyrightText: 2015-2023 Blender Foundation
#
# SPDX-License-Identifier: GPL-2.0-or-later
# Originals by meta-androcto, Pablo Vazquez, Liero, Richard Wilks
import bpy
from bpy.types import Operator
from bpy_extras import object_utils
def object_origin(width, height, depth):
"""
This function takes inputs and returns vertex and face arrays.
no actual mesh data creation is done here.
"""
verts = [(+0.0, +0.0, +0.0)]
faces = []
# apply size
for i, v in enumerate(verts):
verts[i] = v[0] * width, v[1] * depth, v[2] * height
return verts, faces
class AddVert(Operator):
bl_idname = "mesh.primitive_vert_add"
bl_label = "Single Vert"
bl_description = "Add a Single Vertice to Edit Mode"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
mesh = bpy.data.meshes.new("Vert")
mesh.vertices.add(1)
object_utils.object_data_add(context, mesh, operator=None)
bpy.ops.object.mode_set(mode='EDIT')
return {'FINISHED'}
class AddEmptyVert(Operator):
bl_idname = "mesh.primitive_emptyvert_add"
bl_label = "Empty Object Origin"
bl_description = "Add an Object Origin to Edit Mode"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
mesh = bpy.data.meshes.new("Vert")
mesh.vertices.add(1)
object_utils.object_data_add(context, mesh, operator=None)
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.delete(type='VERT')
return {'FINISHED'}
def Add_Symmetrical_Empty():
bpy.ops.mesh.primitive_plane_add(enter_editmode=True)
sempty = bpy.context.object
sempty.name = "SymmEmpty"
# check if we have a mirror modifier, otherwise add
if not any(mod.type == 'MIRROR' for mod in sempty.modifiers):
bpy.ops.object.modifier_add(type='MIRROR')
# Delete all!
bpy.ops.mesh.select_all(action='TOGGLE')
bpy.ops.mesh.select_all(action='TOGGLE')
bpy.ops.mesh.delete(type='VERT')
def Add_Symmetrical_Vert():
bpy.ops.mesh.primitive_plane_add(enter_editmode=True)
sempty = bpy.context.object
sempty.name = "SymmVert"
# check if we have a mirror modifier, otherwise add
if not any(mod.type == 'MIRROR' for mod in sempty.modifiers):
bpy.ops.object.modifier_add(type='MIRROR')
# Delete all!
bpy.ops.mesh.select_all(action='TOGGLE')
bpy.ops.mesh.select_all(action='TOGGLE')
bpy.ops.mesh.merge(type='CENTER')
class AddSymmetricalEmpty(Operator):
bl_idname = "mesh.primitive_symmetrical_empty_add"
bl_label = "Add Symmetrical Object Origin"
bl_description = "Object Origin with a Mirror Modifier for symmetrical modeling"
bl_options = {'UNDO'}
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
mirror = next(mod for mod in bpy.context.object.modifiers
if mod.type == 'MIRROR')
layout.prop(mirror, "use_clip", text="Use Clipping")
layout.label(text="Mirror Axis")
col = layout.column(align=True)
col.prop(mirror, "use_axis")
col.prop(mirror, "use_axis")
col.prop(mirror, "use_axis")
def execute(self, context):
Add_Symmetrical_Empty()
return {'FINISHED'}
class AddSymmetricalVert(Operator):
bl_idname = "mesh.primitive_symmetrical_vert_add"
bl_label = "Add Symmetrical Origin & Vert"
bl_description = "Object Origin with a Mirror Modifier for symmetrical modeling"
bl_options = {'UNDO'}
def draw(self, context):
layout = self.layout
mirror = next(mod for mod in bpy.context.object.modifiers
if mod.type == 'MIRROR')
layout.prop(mirror, "use_clip", text="Use Clipping")
layout.label(text="Mirror Axis")
col = layout.column(align=True)
col.prop(mirror, "use_axis")
col.prop(mirror, "use_axis")
col.prop(mirror, "use_axis")
def execute(self, context):
Add_Symmetrical_Vert()
return {'FINISHED'}
@@ -0,0 +1,12 @@
schema_version = "1.0.0"
id = "extra_mesh_objects"
name = "Extra Mesh Objects"
version = "0.4.0"
tagline = "Add extra mesh object types"
maintainer = "Community"
type = "add-on"
tags = ["Add Mesh"]
blender_version_min = "4.2.0"
license = ["SPDX:GPL-3.0-or-later"]
website = "https://projects.blender.org/extensions/add_mesh_extra_objects"
copyright = ["2024 Multiple Authors"]
@@ -0,0 +1,9 @@
import bpy
def draw_transform_props(self, layout):
if hasattr(self, 'align'):
layout.prop(self, 'align', expand=False)
if hasattr(self, 'location'):
layout.prop(self, 'location', expand=True)
if hasattr(self, 'rotation'):
layout.prop(self, 'rotation', expand=True)
@@ -0,0 +1,58 @@
import bpy
class AddMeshExtraObjectsPreferences(bpy.types.AddonPreferences):
bl_idname = __package__
show_round_cube: bpy.props.BoolProperty(
name = "Round Cube",
default = True,
)
show_single_vert: bpy.props.BoolProperty(
name = "Single Vert Menu",
default = True,
)
show_torus_objects: bpy.props.BoolProperty(
name = "Torus Objects Menu",
default = True,
)
show_math_functions: bpy.props.BoolProperty(
name = "Math Functions Menu",
default = True,
)
show_gears: bpy.props.BoolProperty(
name = "Gears Menu",
default = True,
)
show_pipe_joints: bpy.props.BoolProperty(
name = "Pipe Joints Menu",
default = True,
)
show_gemstones: bpy.props.BoolProperty(
name = "Gemstones Menu",
default = True,
)
show_extras: bpy.props.BoolProperty(
name = "Extras Menu",
default = True,
)
show_parent_to_empty: bpy.props.BoolProperty(
name = "Parent to Empty",
default = True,
)
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
col = layout.column(heading="Filter Add Menu Items")
col.prop(self, "show_round_cube")
col.prop(self, "show_single_vert")
col.prop(self, "show_torus_objects")
col.prop(self, "show_math_functions")
col.prop(self, "show_gears")
col.prop(self, "show_pipe_joints")
col.prop(self, "show_gemstones")
col.prop(self, "show_extras")
col.prop(self, "show_parent_to_empty")
@@ -0,0 +1,8 @@
import bpy
op = bpy.context.active_operator
op.radius = 0.5
op.arc_div = 8
op.lin_div = 0
op.size = (0.0, 0.0, 3.0)
op.div_type = 'CORNERS'
@@ -0,0 +1,8 @@
import bpy
op = bpy.context.active_operator
op.radius = 0.4
op.arc_div = 8
op.lin_div = 0
op.size = (1.5, 3.0, 1.0)
op.div_type = 'ALL'
@@ -0,0 +1,9 @@
import bpy
op = bpy.context.active_operator
op.radius = 0.0
op.arc_div = 1
op.lin_div = 0
op.size = (2.0, 2.0, 2.0)
op.div_type = 'CORNERS'
op.odd_axis_align = False
@@ -0,0 +1,7 @@
import bpy
op = bpy.context.active_operator
op.radius = 0
op.size = (2, 2, 2)
op.lin_div = 5
op.div_type = 'ALL'
@@ -0,0 +1,9 @@
import bpy
op = bpy.context.active_operator
op.radius = 1.0
op.arc_div = 1
op.lin_div = 0
op.size = (0.0, 0.0, 0.0)
op.div_type = 'CORNERS'
op.odd_axis_align = True
@@ -0,0 +1,8 @@
import bpy
op = bpy.context.active_operator
op.radius = 1.0
op.arc_div = 8
op.lin_div = 0
op.size = (0.0, 0.0, 0.0)
op.div_type = 'CORNERS'
@@ -0,0 +1,8 @@
import bpy
op = bpy.context.active_operator
op.radius = 0.25
op.arc_div = 8
op.lin_div = 0
op.size = (2.0, 2.0, 2.0)
op.div_type = 'CORNERS'
@@ -0,0 +1,8 @@
import bpy
op = bpy.context.active_operator
op.source = "4"
op.vTrunc = 1
op.eTrunc = 1
op.dual = 0
op.snub = "None"
@@ -0,0 +1,8 @@
import bpy
op = bpy.context.active_operator
op.source = "12"
op.vTrunc = 1.1338
op.eTrunc = 1
op.dual = 1
op.snub = "None"
@@ -0,0 +1,8 @@
import bpy
op = bpy.context.active_operator
op.source = "6"
op.vTrunc = 1.0938
op.eTrunc = 1
op.dual = 1
op.snub = "None"
@@ -0,0 +1,8 @@
import bpy
op = bpy.context.active_operator
op.source = "6"
op.vTrunc = 1.0572
op.eTrunc = 0.585786
op.dual = 1
op.snub = "None"
@@ -0,0 +1,8 @@
import bpy
op = bpy.context.active_operator
op.source = "20"
op.vTrunc = 0.921
op.eTrunc = 0.553
op.dual = 1
op.snub = "None"

Some files were not shown because too many files have changed in this diff Show More