Update Files
0
leenkx/blender/__init__.py
Normal file
BIN
leenkx/blender/__pycache__/start.cpython-311.pyc
Normal file
104
leenkx/blender/data/haxelogic.py
Normal file
@ -0,0 +1,104 @@
|
||||
# Convert Python logic node definition to Haxe
|
||||
|
||||
import json
|
||||
import glob
|
||||
import sys
|
||||
|
||||
def socket_type(s):
|
||||
if s == 'LnxNodeSocketAction':
|
||||
return 'ACTION'
|
||||
elif s == 'LnxNodeSocketObject':
|
||||
return 'OBJECT'
|
||||
elif s == 'LnxNodeSocketAnimAction':
|
||||
return 'ANIMACTION'
|
||||
elif s == 'LnxNodeSocketArray':
|
||||
return 'ARRAY'
|
||||
elif s == 'NodeSocketShader':
|
||||
return 'SHADER'
|
||||
elif s == 'NodeSocketInt':
|
||||
return 'INTEGER'
|
||||
elif s == 'NodeSocketFloat':
|
||||
return 'VALUE'
|
||||
elif s == 'NodeSocketString':
|
||||
return 'STRING'
|
||||
elif s == 'NodeSocketBool':
|
||||
return 'BOOL'
|
||||
elif s == 'NodeSocketVector':
|
||||
return 'VECTOR'
|
||||
elif s == 'NodeSocketColor':
|
||||
return 'RGBA'
|
||||
else:
|
||||
return s
|
||||
|
||||
# path = '/Users/onek8/Downloads/Leenkx/lnxsdk/leenkx/blender/lnx.logicnode'
|
||||
path = sys.argv[1]
|
||||
modules = glob.glob(path + "/*.py")
|
||||
out = {}
|
||||
out['categories'] = []
|
||||
|
||||
for m in modules:
|
||||
if m == '__init__.py':
|
||||
continue
|
||||
if m == 'lnx_nodes.py':
|
||||
continue
|
||||
with open(m) as f:
|
||||
n = {}
|
||||
n['inputs'] = []
|
||||
n['outputs'] = []
|
||||
n['buttons'] = []
|
||||
but = None
|
||||
lines = f.read().splitlines()
|
||||
for l in lines:
|
||||
l = l.strip()
|
||||
if l == '' or l == '],':
|
||||
continue
|
||||
|
||||
# if l.startswith('property'):
|
||||
if 'EnumProperty' in l: # TODO: enum only for now
|
||||
but = {}
|
||||
but['name'] = 'property' + l.split(' = ', 1)[0][-1]
|
||||
but['type'] = 'ENUM'
|
||||
but['default_value'] = 0
|
||||
but['data'] = []
|
||||
n['buttons'].append(but)
|
||||
continue
|
||||
elif but != None:
|
||||
if l.endswith(')'):
|
||||
but = None
|
||||
continue
|
||||
ar = l.split("'")
|
||||
but['data'].append(ar[1])
|
||||
|
||||
if l.startswith('bl_idname'):
|
||||
ar = l.split("'")
|
||||
n['type'] = ar[1][2:]
|
||||
if l.startswith('bl_label'):
|
||||
ar = l.split("'")
|
||||
n['name'] = ar[1]
|
||||
if l.startswith('self.inputs.new('):
|
||||
ar = l.split("'")
|
||||
soc = {}
|
||||
soc['type'] = socket_type(ar[1])
|
||||
soc['name'] = ar[3]
|
||||
n['inputs'].append(soc)
|
||||
if l.startswith('self.outputs.new('):
|
||||
ar = l.split("'")
|
||||
soc = {}
|
||||
soc['type'] = socket_type(ar[1])
|
||||
soc['name'] = ar[3]
|
||||
n['outputs'].append(soc)
|
||||
if l.startswith('add_node('):
|
||||
ar = l.split("'")
|
||||
cat = None
|
||||
for c in out['categories']:
|
||||
if c['name'] == ar[1]:
|
||||
cat = c
|
||||
break
|
||||
if cat == None:
|
||||
cat = {}
|
||||
cat['name'] = ar[1]
|
||||
cat['nodes'] = []
|
||||
out['categories'].append(cat)
|
||||
cat['nodes'].append(n)
|
||||
|
||||
print(json.dumps(out))
|
BIN
leenkx/blender/data/lnx_data.blend
Normal file
BIN
leenkx/blender/data/skydome.blend
Normal file
340
leenkx/blender/lnx/LICENSE.md
Normal file
@ -0,0 +1,340 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) 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
|
||||
this service 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 make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. 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.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
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
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the 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 a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE 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.
|
||||
|
||||
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
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
{description}
|
||||
Copyright (C) {year} {fullname}
|
||||
|
||||
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 2 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, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision 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, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
{signature of Ty Coon}, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This 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.
|
||||
|
44
leenkx/blender/lnx/__init__.py
Normal file
@ -0,0 +1,44 @@
|
||||
import importlib
|
||||
import sys
|
||||
import types
|
||||
|
||||
# This gets cleared if this package/the __init__ module is reloaded
|
||||
_module_cache: dict[str, types.ModuleType] = {}
|
||||
|
||||
|
||||
def enable_reload(module_name: str):
|
||||
"""Enable reloading for the next time the module with `module_name`
|
||||
is executed.
|
||||
"""
|
||||
mod = sys.modules[module_name]
|
||||
setattr(mod, module_name.replace('.', '_') + "_DO_RELOAD_MODULE", True)
|
||||
|
||||
|
||||
def is_reload(module_name: str) -> bool:
|
||||
"""True if the module given by `module_name` should reload the
|
||||
modules it imports. This is the case if `enable_reload()` was called
|
||||
for the module before.
|
||||
"""
|
||||
mod = sys.modules[module_name]
|
||||
return hasattr(mod, module_name.replace('.', '_') + "_DO_RELOAD_MODULE")
|
||||
|
||||
|
||||
def reload_module(module: types.ModuleType) -> types.ModuleType:
|
||||
"""Wrapper around importlib.reload() to make sure no module is
|
||||
reloaded twice.
|
||||
|
||||
Make sure to call this function in the same order in which the
|
||||
modules are imported to make sure that the reloading respects the
|
||||
module dependencies. Otherwise modules could depend on other modules
|
||||
that are not yet reloaded.
|
||||
|
||||
If you import classes or functions from a module, make sure to
|
||||
re-import them after the module is reloaded.
|
||||
"""
|
||||
mod = _module_cache.get(module.__name__, None)
|
||||
|
||||
if mod is None:
|
||||
mod = importlib.reload(module)
|
||||
_module_cache[module.__name__] = mod
|
||||
|
||||
return mod
|
BIN
leenkx/blender/lnx/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
leenkx/blender/lnx/__pycache__/api.cpython-311.pyc
Normal file
BIN
leenkx/blender/lnx/__pycache__/assets.cpython-311.pyc
Normal file
BIN
leenkx/blender/lnx/__pycache__/exporter.cpython-311.pyc
Normal file
BIN
leenkx/blender/lnx/__pycache__/exporter_opt.cpython-311.pyc
Normal file
BIN
leenkx/blender/lnx/__pycache__/handlers.cpython-311.pyc
Normal file
BIN
leenkx/blender/lnx/__pycache__/keymap.cpython-311.pyc
Normal file
BIN
leenkx/blender/lnx/__pycache__/live_patch.cpython-311.pyc
Normal file
BIN
leenkx/blender/lnx/__pycache__/log.cpython-311.pyc
Normal file
BIN
leenkx/blender/lnx/__pycache__/make.cpython-311.pyc
Normal file
BIN
leenkx/blender/lnx/__pycache__/make_logic.cpython-311.pyc
Normal file
BIN
leenkx/blender/lnx/__pycache__/make_renderpath.cpython-311.pyc
Normal file
BIN
leenkx/blender/lnx/__pycache__/make_state.cpython-311.pyc
Normal file
BIN
leenkx/blender/lnx/__pycache__/make_world.cpython-311.pyc
Normal file
BIN
leenkx/blender/lnx/__pycache__/node_utils.cpython-311.pyc
Normal file
BIN
leenkx/blender/lnx/__pycache__/nodes_logic.cpython-311.pyc
Normal file
BIN
leenkx/blender/lnx/__pycache__/nodes_material.cpython-311.pyc
Normal file
BIN
leenkx/blender/lnx/__pycache__/profiler.cpython-311.pyc
Normal file
BIN
leenkx/blender/lnx/__pycache__/props.cpython-311.pyc
Normal file
BIN
leenkx/blender/lnx/__pycache__/props_action.cpython-311.pyc
Normal file
BIN
leenkx/blender/lnx/__pycache__/props_bake.cpython-311.pyc
Normal file
BIN
leenkx/blender/lnx/__pycache__/props_exporter.cpython-311.pyc
Normal file
BIN
leenkx/blender/lnx/__pycache__/props_lod.cpython-311.pyc
Normal file
BIN
leenkx/blender/lnx/__pycache__/props_properties.cpython-311.pyc
Normal file
BIN
leenkx/blender/lnx/__pycache__/props_renderpath.cpython-311.pyc
Normal file
BIN
leenkx/blender/lnx/__pycache__/props_tilesheet.cpython-311.pyc
Normal file
BIN
leenkx/blender/lnx/__pycache__/props_traits.cpython-311.pyc
Normal file
BIN
leenkx/blender/lnx/__pycache__/props_ui.cpython-311.pyc
Normal file
BIN
leenkx/blender/lnx/__pycache__/ui_icons.cpython-311.pyc
Normal file
BIN
leenkx/blender/lnx/__pycache__/utils.cpython-311.pyc
Normal file
BIN
leenkx/blender/lnx/__pycache__/utils_vs.cpython-311.pyc
Normal file
BIN
leenkx/blender/lnx/__pycache__/write_data.cpython-311.pyc
Normal file
BIN
leenkx/blender/lnx/__pycache__/write_probes.cpython-311.pyc
Normal file
55
leenkx/blender/lnx/api.py
Normal file
@ -0,0 +1,55 @@
|
||||
from typing import Callable, Dict, Optional
|
||||
|
||||
import bpy
|
||||
from bpy.types import Material, UILayout
|
||||
|
||||
import lnx
|
||||
from lnx.material.shader import ShaderContext
|
||||
|
||||
if lnx.is_reload(__name__):
|
||||
lnx.material.shader = lnx.reload_module(lnx.material.shader)
|
||||
from lnx.material.shader import ShaderContext
|
||||
else:
|
||||
drivers: Dict[str, Dict] = {} #dict()
|
||||
|
||||
lnx.enable_reload(__name__)
|
||||
|
||||
|
||||
def add_driver(driver_name: str,
|
||||
make_rpass: Callable[[str], Optional[ShaderContext]],
|
||||
make_rpath: Callable[[], None],
|
||||
draw_props: Optional[Callable[[UILayout], None]],
|
||||
draw_mat_props: Optional[Callable[[UILayout, Material], None]]) -> None:
|
||||
"""Register a new driver. If there already exists a driver with the given name, nothing happens.
|
||||
|
||||
@param driver_name Unique name for the new driver that will be displayed in the UI.
|
||||
@param make_rpass Function to create render passes. Takes the rpass name as a parameter and may return `None`.
|
||||
@param make_rpath Function to setup the render path.
|
||||
@param draw_props Function to draw global driver properties inside the render path panel, may be `None`.
|
||||
@param draw_mat_props Function to draw per-material driver properties in the material tab, may be `None`.
|
||||
"""
|
||||
global drivers
|
||||
|
||||
if driver_name in drivers:
|
||||
return
|
||||
|
||||
drivers[driver_name] = {
|
||||
'driver_name': driver_name,
|
||||
'make_rpass': make_rpass,
|
||||
'make_rpath': make_rpath,
|
||||
'draw_props': draw_props,
|
||||
'draw_mat_props': draw_mat_props
|
||||
}
|
||||
|
||||
wrd = bpy.data.worlds['Lnx']
|
||||
if len(wrd.rp_driver_list) == 0:
|
||||
wrd.rp_driver_list.add().name = 'Leenkx' # Add default driver
|
||||
wrd.rp_driver_list.add().name = driver_name
|
||||
|
||||
|
||||
def remove_drivers():
|
||||
wrd = bpy.data.worlds['Lnx']
|
||||
wrd.rp_driver_list.clear()
|
||||
wrd.rp_driver_list.add().name = 'Leenkx'
|
||||
|
||||
drivers.clear()
|
204
leenkx/blender/lnx/assets.py
Normal file
@ -0,0 +1,204 @@
|
||||
import shutil
|
||||
import os
|
||||
import stat
|
||||
import bpy
|
||||
import lnx.utils
|
||||
from lnx import log
|
||||
|
||||
if lnx.is_reload(__name__):
|
||||
log = lnx.reload_module(log)
|
||||
lnx.utils = lnx.reload_module(lnx.utils)
|
||||
else:
|
||||
lnx.enable_reload(__name__)
|
||||
|
||||
assets = []
|
||||
reserved_names = ['return.']
|
||||
khafile_params = []
|
||||
khafile_defs = []
|
||||
khafile_defs_last = []
|
||||
embedded_data = []
|
||||
shaders = []
|
||||
shaders_last = []
|
||||
shaders_external = []
|
||||
shader_datas = []
|
||||
shader_passes = []
|
||||
shader_passes_assets = {}
|
||||
shader_cons = {}
|
||||
|
||||
def reset():
|
||||
global assets
|
||||
global khafile_params
|
||||
global khafile_defs
|
||||
global khafile_defs_last
|
||||
global embedded_data
|
||||
global shaders
|
||||
global shaders_last
|
||||
global shaders_external
|
||||
global shader_datas
|
||||
global shader_passes
|
||||
global shader_cons
|
||||
assets = []
|
||||
khafile_params = []
|
||||
khafile_defs_last = khafile_defs
|
||||
khafile_defs = []
|
||||
embedded_data = []
|
||||
shaders_last = shaders
|
||||
shaders = []
|
||||
shaders_external = []
|
||||
shader_datas = []
|
||||
shader_passes = []
|
||||
shader_cons = {}
|
||||
shader_cons['mesh_vert'] = []
|
||||
shader_cons['depth_vert'] = []
|
||||
shader_cons['depth_frag'] = []
|
||||
shader_cons['voxel_vert'] = []
|
||||
shader_cons['voxel_frag'] = []
|
||||
shader_cons['voxel_geom'] = []
|
||||
|
||||
def add(asset_file):
|
||||
global assets
|
||||
|
||||
# Asset already exists, do nothing
|
||||
if asset_file in assets:
|
||||
return
|
||||
|
||||
asset_file_base = os.path.basename(asset_file)
|
||||
for f in assets:
|
||||
f_file_base = os.path.basename(f)
|
||||
if f_file_base == asset_file_base:
|
||||
return
|
||||
|
||||
assets.append(asset_file)
|
||||
|
||||
# Reserved file name
|
||||
for f in reserved_names:
|
||||
if f in asset_file:
|
||||
log.warn(f'File "{asset_file}" contains reserved keyword, this will break C++ builds!')
|
||||
|
||||
def add_khafile_def(d):
|
||||
global khafile_defs
|
||||
if d not in khafile_defs:
|
||||
khafile_defs.append(d)
|
||||
|
||||
def add_khafile_param(p):
|
||||
global khafile_params
|
||||
if p not in khafile_params:
|
||||
khafile_params.append(p)
|
||||
|
||||
def add_embedded_data(file):
|
||||
global embedded_data
|
||||
if file not in embedded_data:
|
||||
embedded_data.append(file)
|
||||
|
||||
def add_shader(file):
|
||||
global shaders
|
||||
global shaders_last
|
||||
if file not in shaders:
|
||||
shaders.append(file)
|
||||
|
||||
def add_shader_data(file):
|
||||
global shader_datas
|
||||
if file not in shader_datas:
|
||||
shader_datas.append(file)
|
||||
|
||||
def add_shader_pass(data_name):
|
||||
global shader_passes
|
||||
# Shader data for passes are written into single shader_datas.lnx file
|
||||
add_shader_data(lnx.utils.get_fp_build() + '/compiled/Shaders/shader_datas.lnx')
|
||||
if data_name not in shader_passes:
|
||||
shader_passes.append(data_name)
|
||||
|
||||
def add_shader_external(file):
|
||||
global shaders_external
|
||||
shaders_external.append(file)
|
||||
name = file.split('/')[-1].split('\\')[-1]
|
||||
add_shader(lnx.utils.get_fp_build() + '/compiled/Shaders/' + name)
|
||||
|
||||
invalidate_enabled = True # Disable invalidating during build process
|
||||
|
||||
def remove_readonly(func, path, excinfo):
|
||||
os.chmod(path, stat.S_IWRITE)
|
||||
func(path)
|
||||
|
||||
def invalidate_shader_cache(self, context):
|
||||
# compiled.inc changed, recompile all shaders next time
|
||||
global invalidate_enabled
|
||||
if invalidate_enabled is False:
|
||||
return
|
||||
fp = lnx.utils.get_fp_build()
|
||||
if os.path.isdir(fp + '/compiled/Shaders'):
|
||||
shutil.rmtree(fp + '/compiled/Shaders', onerror=remove_readonly)
|
||||
if os.path.isdir(fp + '/debug/html5-resources'):
|
||||
shutil.rmtree(fp + '/debug/html5-resources', onerror=remove_readonly)
|
||||
if os.path.isdir(fp + '/krom-resources'):
|
||||
shutil.rmtree(fp + '/krom-resources', onerror=remove_readonly)
|
||||
if os.path.isdir(fp + '/debug/krom-resources'):
|
||||
shutil.rmtree(fp + '/debug/krom-resources', onerror=remove_readonly)
|
||||
if os.path.isdir(fp + '/windows-resources'):
|
||||
shutil.rmtree(fp + '/windows-resources', onerror=remove_readonly)
|
||||
if os.path.isdir(fp + '/linux-resources'):
|
||||
shutil.rmtree(fp + '/linux-resources', onerror=remove_readonly)
|
||||
if os.path.isdir(fp + '/osx-resources'):
|
||||
shutil.rmtree(fp + '/osx-resources', onerror=remove_readonly)
|
||||
|
||||
def invalidate_compiled_data(self, context):
|
||||
global invalidate_enabled
|
||||
if invalidate_enabled is False:
|
||||
return
|
||||
fp = lnx.utils.get_fp_build()
|
||||
if os.path.isdir(fp + '/compiled'):
|
||||
shutil.rmtree(fp + '/compiled', onerror=remove_readonly)
|
||||
|
||||
def invalidate_mesh_data(self, context):
|
||||
fp = lnx.utils.get_fp_build()
|
||||
if os.path.isdir(fp + '/compiled/Assets/meshes'):
|
||||
shutil.rmtree(fp + '/compiled/Assets/meshes', onerror=remove_readonly)
|
||||
|
||||
def invalidate_envmap_data(self, context):
|
||||
fp = lnx.utils.get_fp_build()
|
||||
if os.path.isdir(fp + '/compiled/Assets/envmaps'):
|
||||
shutil.rmtree(fp + '/compiled/Assets/envmaps', onerror=remove_readonly)
|
||||
|
||||
def invalidate_unpacked_data(self, context):
|
||||
fp = lnx.utils.get_fp_build()
|
||||
if os.path.isdir(fp + '/compiled/Assets/unpacked'):
|
||||
shutil.rmtree(fp + '/compiled/Assets/unpacked', onerror=remove_readonly)
|
||||
|
||||
def invalidate_mesh_cache(self, context):
|
||||
if context.object is None or context.object.data is None:
|
||||
return
|
||||
context.object.data.lnx_cached = False
|
||||
|
||||
def invalidate_instance_cache(self, context):
|
||||
if context.object is None or context.object.data is None:
|
||||
return
|
||||
invalidate_mesh_cache(self, context)
|
||||
for slot in context.object.material_slots:
|
||||
slot.material.lnx_cached = False
|
||||
|
||||
def invalidate_compiler_cache(self, context):
|
||||
bpy.data.worlds['Lnx'].lnx_recompile = True
|
||||
|
||||
def shader_equal(sh, ar, shtype):
|
||||
# Merge equal shaders
|
||||
for e in ar:
|
||||
if sh.is_equal(e):
|
||||
sh.context.data[shtype] = e.context.data[shtype]
|
||||
sh.is_linked = True
|
||||
return
|
||||
ar.append(sh)
|
||||
|
||||
def vs_equal(c, ar):
|
||||
shader_equal(c.vert, ar, 'vertex_shader')
|
||||
|
||||
def fs_equal(c, ar):
|
||||
shader_equal(c.frag, ar, 'fragment_shader')
|
||||
|
||||
def gs_equal(c, ar):
|
||||
shader_equal(c.geom, ar, 'geometry_shader')
|
||||
|
||||
def tcs_equal(c, ar):
|
||||
shader_equal(c.tesc, ar, 'tesscontrol_shader')
|
||||
|
||||
def tes_equal(c, ar):
|
||||
shader_equal(c.tese, ar, 'tesseval_shader')
|
BIN
leenkx/blender/lnx/custom_icons/bundle.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
leenkx/blender/lnx/custom_icons/haxe.png
Normal file
After Width: | Height: | Size: 673 B |
BIN
leenkx/blender/lnx/custom_icons/wasm.png
Normal file
After Width: | Height: | Size: 414 B |
3338
leenkx/blender/lnx/exporter.py
Normal file
445
leenkx/blender/lnx/exporter_opt.py
Normal file
@ -0,0 +1,445 @@
|
||||
"""
|
||||
Exports smaller geometry but is slower.
|
||||
To be replaced with https://github.com/zeux/meshoptimizer
|
||||
"""
|
||||
from typing import Optional
|
||||
|
||||
import bpy
|
||||
from mathutils import Vector
|
||||
import numpy as np
|
||||
|
||||
import lnx.utils
|
||||
from lnx import log
|
||||
|
||||
if lnx.is_reload(__name__):
|
||||
log = lnx.reload_module(log)
|
||||
lnx.utils = lnx.reload_module(lnx.utils)
|
||||
else:
|
||||
lnx.enable_reload(__name__)
|
||||
|
||||
|
||||
class Vertex:
|
||||
__slots__ = ("co", "normal", "uvs", "col", "loop_indices", "index", "bone_weights", "bone_indices", "bone_count", "vertex_index")
|
||||
|
||||
def __init__(self, mesh: bpy.types.Mesh, loop: bpy.types.MeshLoop, vcol0: Optional[bpy.types.Attribute]):
|
||||
self.vertex_index = loop.vertex_index
|
||||
loop_idx = loop.index
|
||||
self.co = mesh.vertices[self.vertex_index].co[:]
|
||||
self.normal = loop.normal[:]
|
||||
self.uvs = tuple(layer.data[loop_idx].uv[:] for layer in mesh.uv_layers)
|
||||
self.col = [0.0, 0.0, 0.0] if vcol0 is None else vcol0.data[loop_idx].color[:]
|
||||
self.loop_indices = [loop_idx]
|
||||
self.index = 0
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.co, self.normal, self.uvs))
|
||||
|
||||
def __eq__(self, other):
|
||||
eq = (
|
||||
(self.co == other.co) and
|
||||
(self.normal == other.normal) and
|
||||
(self.uvs == other.uvs) and
|
||||
(self.col == other.col)
|
||||
)
|
||||
if eq:
|
||||
indices = self.loop_indices + other.loop_indices
|
||||
self.loop_indices = indices
|
||||
other.loop_indices = indices
|
||||
return eq
|
||||
|
||||
|
||||
def calc_tangents(posa, nora, uva, ias, scale_pos):
|
||||
num_verts = int(len(posa) / 4)
|
||||
tangents = np.empty(num_verts * 3, dtype='<f4')
|
||||
# bitangents = np.empty(num_verts * 3, dtype='<f4')
|
||||
for ar in ias:
|
||||
ia = ar['values']
|
||||
num_tris = int(len(ia) / 3)
|
||||
for i in range(0, num_tris):
|
||||
i0 = ia[i * 3 ]
|
||||
i1 = ia[i * 3 + 1]
|
||||
i2 = ia[i * 3 + 2]
|
||||
v0 = Vector((posa[i0 * 4], posa[i0 * 4 + 1], posa[i0 * 4 + 2]))
|
||||
v1 = Vector((posa[i1 * 4], posa[i1 * 4 + 1], posa[i1 * 4 + 2]))
|
||||
v2 = Vector((posa[i2 * 4], posa[i2 * 4 + 1], posa[i2 * 4 + 2]))
|
||||
uv0 = Vector((uva[i0 * 2], uva[i0 * 2 + 1]))
|
||||
uv1 = Vector((uva[i1 * 2], uva[i1 * 2 + 1]))
|
||||
uv2 = Vector((uva[i2 * 2], uva[i2 * 2 + 1]))
|
||||
|
||||
deltaPos1 = v1 - v0
|
||||
deltaPos2 = v2 - v0
|
||||
deltaUV1 = uv1 - uv0
|
||||
deltaUV2 = uv2 - uv0
|
||||
d = (deltaUV1.x * deltaUV2.y - deltaUV1.y * deltaUV2.x)
|
||||
if d != 0:
|
||||
r = 1.0 / d
|
||||
else:
|
||||
r = 1.0
|
||||
tangent = (deltaPos1 * deltaUV2.y - deltaPos2 * deltaUV1.y) * r
|
||||
# bitangent = (deltaPos2 * deltaUV1.x - deltaPos1 * deltaUV2.x) * r
|
||||
|
||||
tangents[i0 * 3 ] += tangent.x
|
||||
tangents[i0 * 3 + 1] += tangent.y
|
||||
tangents[i0 * 3 + 2] += tangent.z
|
||||
tangents[i1 * 3 ] += tangent.x
|
||||
tangents[i1 * 3 + 1] += tangent.y
|
||||
tangents[i1 * 3 + 2] += tangent.z
|
||||
tangents[i2 * 3 ] += tangent.x
|
||||
tangents[i2 * 3 + 1] += tangent.y
|
||||
tangents[i2 * 3 + 2] += tangent.z
|
||||
# bitangents[i0 * 3 ] += bitangent.x
|
||||
# bitangents[i0 * 3 + 1] += bitangent.y
|
||||
# bitangents[i0 * 3 + 2] += bitangent.z
|
||||
# bitangents[i1 * 3 ] += bitangent.x
|
||||
# bitangents[i1 * 3 + 1] += bitangent.y
|
||||
# bitangents[i1 * 3 + 2] += bitangent.z
|
||||
# bitangents[i2 * 3 ] += bitangent.x
|
||||
# bitangents[i2 * 3 + 1] += bitangent.y
|
||||
# bitangents[i2 * 3 + 2] += bitangent.z
|
||||
# Orthogonalize
|
||||
for i in range(0, num_verts):
|
||||
t = Vector((tangents[i * 3], tangents[i * 3 + 1], tangents[i * 3 + 2]))
|
||||
# b = Vector((bitangents[i * 3], bitangents[i * 3 + 1], bitangents[i * 3 + 2]))
|
||||
n = Vector((nora[i * 2], nora[i * 2 + 1], posa[i * 4 + 3] / scale_pos))
|
||||
v = t - n * n.dot(t)
|
||||
v.normalize()
|
||||
# Calculate handedness
|
||||
# cnv = n.cross(v)
|
||||
# if cnv.dot(b) < 0.0:
|
||||
# v = v * -1.0
|
||||
tangents[i * 3 ] = v.x
|
||||
tangents[i * 3 + 1] = v.y
|
||||
tangents[i * 3 + 2] = v.z
|
||||
return tangents
|
||||
|
||||
|
||||
def export_mesh_data(self, export_mesh: bpy.types.Mesh, bobject: bpy.types.Object, o, has_armature=False):
|
||||
if bpy.app.version < (4, 1, 0):
|
||||
export_mesh.calc_normals_split()
|
||||
else:
|
||||
updated_normals = export_mesh.corner_normals
|
||||
# exportMesh.calc_loop_triangles()
|
||||
vcol0 = self.get_nth_vertex_colors(export_mesh, 0)
|
||||
vert_list = {Vertex(export_mesh, loop, vcol0): 0 for loop in export_mesh.loops}.keys()
|
||||
num_verts = len(vert_list)
|
||||
num_uv_layers = len(export_mesh.uv_layers)
|
||||
# Check if shape keys were exported
|
||||
has_morph_target = self.get_shape_keys(bobject.data)
|
||||
if has_morph_target:
|
||||
# Shape keys UV are exported separately, so reduce UV count by 1
|
||||
num_uv_layers -= 1
|
||||
morph_uv_index = self.get_morph_uv_index(bobject.data)
|
||||
has_tex = self.get_export_uvs(export_mesh) and num_uv_layers > 0
|
||||
if self.has_baked_material(bobject, export_mesh.materials):
|
||||
has_tex = True
|
||||
has_tex1 = has_tex and num_uv_layers > 1
|
||||
num_colors = self.get_num_vertex_colors(export_mesh)
|
||||
has_col = self.get_export_vcols(export_mesh) and num_colors > 0
|
||||
has_tang = self.has_tangents(export_mesh)
|
||||
|
||||
pdata = np.empty(num_verts * 4, dtype='<f4') # p.xyz, n.z
|
||||
ndata = np.empty(num_verts * 2, dtype='<f4') # n.xy
|
||||
if has_tex or has_morph_target:
|
||||
uv_layers = export_mesh.uv_layers
|
||||
maxdim = 1.0
|
||||
maxdim_uvlayer = None
|
||||
if has_tex:
|
||||
t0map = 0 # Get active uvmap
|
||||
t0data = np.empty(num_verts * 2, dtype='<f4')
|
||||
if uv_layers is not None:
|
||||
if 'UVMap_baked' in uv_layers:
|
||||
for i in range(0, len(uv_layers)):
|
||||
if uv_layers[i].name == 'UVMap_baked':
|
||||
t0map = i
|
||||
break
|
||||
else:
|
||||
for i in range(0, len(uv_layers)):
|
||||
if uv_layers[i].active_render and uv_layers[i].name != 'UVMap_shape_key':
|
||||
t0map = i
|
||||
break
|
||||
if has_tex1:
|
||||
for i in range(0, len(uv_layers)):
|
||||
# Not UVMap 0
|
||||
if i != t0map:
|
||||
# Not Shape Key UVMap
|
||||
if has_morph_target and uv_layers[i].name == 'UVMap_shape_key':
|
||||
continue
|
||||
# Neither UVMap 0 Nor Shape Key Map
|
||||
t1map = i
|
||||
t1data = np.empty(num_verts * 2, dtype='<f4')
|
||||
# Scale for packed coords
|
||||
lay0 = uv_layers[t0map]
|
||||
maxdim_uvlayer = lay0
|
||||
for v in lay0.data:
|
||||
if abs(v.uv[0]) > maxdim:
|
||||
maxdim = abs(v.uv[0])
|
||||
if abs(v.uv[1]) > maxdim:
|
||||
maxdim = abs(v.uv[1])
|
||||
if has_tex1:
|
||||
lay1 = uv_layers[t1map]
|
||||
for v in lay1.data:
|
||||
if abs(v.uv[0]) > maxdim:
|
||||
maxdim = abs(v.uv[0])
|
||||
maxdim_uvlayer = lay1
|
||||
if abs(v.uv[1]) > maxdim:
|
||||
maxdim = abs(v.uv[1])
|
||||
maxdim_uvlayer = lay1
|
||||
if has_morph_target:
|
||||
morph_data = np.empty(num_verts * 2, dtype='<f4')
|
||||
lay2 = uv_layers[morph_uv_index]
|
||||
for v in lay2.data:
|
||||
if abs(v.uv[0]) > maxdim:
|
||||
maxdim = abs(v.uv[0])
|
||||
maxdim_uvlayer = lay2
|
||||
if abs(v.uv[1]) > maxdim:
|
||||
maxdim = abs(v.uv[1])
|
||||
maxdim_uvlayer = lay2
|
||||
if maxdim > 1:
|
||||
o['scale_tex'] = maxdim
|
||||
invscale_tex = (1 / o['scale_tex']) * 32767
|
||||
else:
|
||||
invscale_tex = 1 * 32767
|
||||
self.check_uv_precision(export_mesh, maxdim, maxdim_uvlayer, invscale_tex)
|
||||
|
||||
if has_col:
|
||||
cdata = np.empty(num_verts * 3, dtype='<f4')
|
||||
|
||||
# Save aabb
|
||||
self.calc_aabb(bobject)
|
||||
|
||||
# Scale for packed coords
|
||||
maxdim = max(bobject.data.lnx_aabb[0], max(bobject.data.lnx_aabb[1], bobject.data.lnx_aabb[2]))
|
||||
if maxdim > 2:
|
||||
o['scale_pos'] = maxdim / 2
|
||||
else:
|
||||
o['scale_pos'] = 1.0
|
||||
if has_armature: # Allow up to 2x bigger bounds for skinned mesh
|
||||
o['scale_pos'] *= 2.0
|
||||
|
||||
scale_pos = o['scale_pos']
|
||||
invscale_pos = (1 / scale_pos) * 32767
|
||||
|
||||
# Make arrays
|
||||
for i, v in enumerate(vert_list):
|
||||
v.index = i
|
||||
co = v.co
|
||||
normal = v.normal
|
||||
i4 = i * 4
|
||||
i2 = i * 2
|
||||
pdata[i4 ] = co[0]
|
||||
pdata[i4 + 1] = co[1]
|
||||
pdata[i4 + 2] = co[2]
|
||||
pdata[i4 + 3] = normal[2] * scale_pos # Cancel scale
|
||||
ndata[i2 ] = normal[0]
|
||||
ndata[i2 + 1] = normal[1]
|
||||
if has_tex:
|
||||
uv = v.uvs[t0map]
|
||||
t0data[i2 ] = uv[0]
|
||||
t0data[i2 + 1] = 1.0 - uv[1] # Reverse Y
|
||||
if has_tex1:
|
||||
uv = v.uvs[t1map]
|
||||
t1data[i2 ] = uv[0]
|
||||
t1data[i2 + 1] = 1.0 - uv[1]
|
||||
if has_morph_target:
|
||||
uv = v.uvs[morph_uv_index]
|
||||
morph_data[i2 ] = uv[0]
|
||||
morph_data[i2 + 1] = 1.0 - uv[1]
|
||||
if has_col:
|
||||
i3 = i * 3
|
||||
cdata[i3 ] = v.col[0]
|
||||
cdata[i3 + 1] = v.col[1]
|
||||
cdata[i3 + 2] = v.col[2]
|
||||
|
||||
# Indices
|
||||
# Create dict for every material slot
|
||||
prims = {ma.name if ma else '': [] for ma in export_mesh.materials}
|
||||
v_maps = {ma.name if ma else '': [] for ma in export_mesh.materials}
|
||||
if not prims:
|
||||
# No materials
|
||||
prims = {'': []}
|
||||
v_maps = {'': []}
|
||||
|
||||
# Create dict of {loop_indices : vertex} with each loop_index in each vertex in Vertex_list
|
||||
vert_dict = {i : v for v in vert_list for i in v.loop_indices}
|
||||
# For each polygon in a mesh
|
||||
for poly in export_mesh.polygons:
|
||||
# Index of the first loop of this polygon
|
||||
first = poly.loop_start
|
||||
# No materials assigned
|
||||
if len(export_mesh.materials) == 0:
|
||||
# Get prim
|
||||
prim = prims['']
|
||||
v_map = v_maps['']
|
||||
else:
|
||||
# First material
|
||||
mat = export_mesh.materials[min(poly.material_index, len(export_mesh.materials) - 1)]
|
||||
# Get prim for this material
|
||||
prim = prims[mat.name if mat else '']
|
||||
v_map = v_maps[mat.name if mat else '']
|
||||
# List of indices for each loop_index belonging to this polygon
|
||||
indices = [vert_dict[i].index for i in range(first, first+poly.loop_total)]
|
||||
v_indices = [vert_dict[i].vertex_index for i in range(first, first+poly.loop_total)]
|
||||
|
||||
# If 3 loops per polygon (Triangle?)
|
||||
if poly.loop_total == 3:
|
||||
prim += indices
|
||||
v_map += v_indices
|
||||
# If > 3 loops per polygon (Non-Triangular?)
|
||||
elif poly.loop_total > 3:
|
||||
for i in range(poly.loop_total-2):
|
||||
prim += (indices[-1], indices[i], indices[i + 1])
|
||||
v_map += (v_indices[-1], v_indices[i], v_indices[i + 1])
|
||||
|
||||
# Write indices
|
||||
o['index_arrays'] = []
|
||||
for mat, prim in prims.items():
|
||||
idata = [0] * len(prim)
|
||||
v_map_data = [0] * len(prim)
|
||||
v_map_sub = v_maps[mat]
|
||||
for i, v in enumerate(prim):
|
||||
idata[i] = v
|
||||
v_map_data[i] = v_map_sub[i]
|
||||
if len(idata) == 0: # No face assigned
|
||||
continue
|
||||
ia = {'values': idata, 'material': 0, 'vertex_map': v_map_data}
|
||||
# Find material index for multi-mat mesh
|
||||
if len(export_mesh.materials) > 1:
|
||||
for i in range(0, len(export_mesh.materials)):
|
||||
if (export_mesh.materials[i] is not None and mat == export_mesh.materials[i].name) or \
|
||||
(export_mesh.materials[i] is None and mat == ''): # Default material for empty slots
|
||||
ia['material'] = i
|
||||
break
|
||||
o['index_arrays'].append(ia)
|
||||
|
||||
if has_tang:
|
||||
tangdata = calc_tangents(pdata, ndata, t0data, o['index_arrays'], scale_pos)
|
||||
|
||||
pdata *= invscale_pos
|
||||
ndata *= 32767
|
||||
pdata = np.array(pdata, dtype='<i2')
|
||||
ndata = np.array(ndata, dtype='<i2')
|
||||
if has_tex:
|
||||
t0data *= invscale_tex
|
||||
t0data = np.array(t0data, dtype='<i2')
|
||||
if has_tex1:
|
||||
t1data *= invscale_tex
|
||||
t1data = np.array(t1data, dtype='<i2')
|
||||
if has_morph_target:
|
||||
morph_data *= invscale_tex
|
||||
morph_data = np.array(morph_data, dtype='<i2')
|
||||
if has_col:
|
||||
cdata *= 32767
|
||||
cdata = np.array(cdata, dtype='<i2')
|
||||
if has_tang:
|
||||
tangdata *= 32767
|
||||
tangdata = np.array(tangdata, dtype='<i2')
|
||||
|
||||
# Output
|
||||
o['vertex_arrays'] = []
|
||||
o['vertex_arrays'].append({ 'attrib': 'pos', 'values': pdata, 'data': 'short4norm' })
|
||||
o['vertex_arrays'].append({ 'attrib': 'nor', 'values': ndata, 'data': 'short2norm' })
|
||||
if has_tex:
|
||||
o['vertex_arrays'].append({ 'attrib': 'tex', 'values': t0data, 'data': 'short2norm' })
|
||||
if has_tex1:
|
||||
o['vertex_arrays'].append({ 'attrib': 'tex1', 'values': t1data, 'data': 'short2norm' })
|
||||
if has_morph_target:
|
||||
o['vertex_arrays'].append({ 'attrib': 'morph', 'values': morph_data, 'data': 'short2norm' })
|
||||
if has_col:
|
||||
o['vertex_arrays'].append({ 'attrib': 'col', 'values': cdata, 'data': 'short4norm', 'padding': 1 })
|
||||
if has_tang:
|
||||
o['vertex_arrays'].append({ 'attrib': 'tang', 'values': tangdata, 'data': 'short4norm', 'padding': 1 })
|
||||
|
||||
return vert_list
|
||||
|
||||
def export_skin(self, bobject, armature, vert_list, o):
|
||||
# This function exports all skinning data, which includes the skeleton
|
||||
# and per-vertex bone influence data
|
||||
oskin = {}
|
||||
o['skin'] = oskin
|
||||
|
||||
# Write the skin bind pose transform
|
||||
otrans = {}
|
||||
oskin['transform'] = otrans
|
||||
otrans['values'] = self.write_matrix(bobject.matrix_world)
|
||||
|
||||
# Write the bone object reference array
|
||||
oskin['bone_ref_array'] = []
|
||||
oskin['bone_len_array'] = []
|
||||
|
||||
bone_array = armature.data.bones
|
||||
bone_count = len(bone_array)
|
||||
rpdat = lnx.utils.get_rp()
|
||||
max_bones = rpdat.lnx_skin_max_bones
|
||||
if bone_count > max_bones:
|
||||
log.warn(bobject.name + ' - ' + str(bone_count) + ' bones found, exceeds maximum of ' + str(max_bones) + ' bones defined - raise the value in Camera Data - Leenkx Render Props - Max Bones')
|
||||
|
||||
for i in range(bone_count):
|
||||
boneRef = self.find_bone(bone_array[i].name)
|
||||
if boneRef:
|
||||
oskin['bone_ref_array'].append(boneRef[1]["structName"])
|
||||
oskin['bone_len_array'].append(bone_array[i].length)
|
||||
else:
|
||||
oskin['bone_ref_array'].append("")
|
||||
oskin['bone_len_array'].append(0.0)
|
||||
|
||||
# Write the bind pose transform array
|
||||
oskin['transformsI'] = []
|
||||
for i in range(bone_count):
|
||||
skeletonI = (armature.matrix_world @ bone_array[i].matrix_local).inverted_safe()
|
||||
skeletonI = (skeletonI @ bobject.matrix_world)
|
||||
oskin['transformsI'].append(self.write_matrix(skeletonI))
|
||||
|
||||
# Export the per-vertex bone influence data
|
||||
group_remap = []
|
||||
for group in bobject.vertex_groups:
|
||||
for i in range(bone_count):
|
||||
if bone_array[i].name == group.name:
|
||||
group_remap.append(i)
|
||||
break
|
||||
else:
|
||||
group_remap.append(-1)
|
||||
|
||||
bone_count_array = np.empty(len(vert_list), dtype='<i2')
|
||||
bone_index_array = np.empty(len(vert_list) * 4, dtype='<i2')
|
||||
bone_weight_array = np.empty(len(vert_list) * 4, dtype='<i2')
|
||||
|
||||
vertices = bobject.data.vertices
|
||||
count = 0
|
||||
for index, v in enumerate(vert_list):
|
||||
bone_count = 0
|
||||
total_weight = 0.0
|
||||
bone_values = []
|
||||
for g in vertices[v.vertex_index].groups:
|
||||
bone_index = group_remap[g.group]
|
||||
bone_weight = g.weight
|
||||
if bone_index >= 0: #and bone_weight != 0.0:
|
||||
bone_values.append((bone_weight, bone_index))
|
||||
total_weight += bone_weight
|
||||
bone_count += 1
|
||||
|
||||
if bone_count > 4:
|
||||
bone_count = 4
|
||||
bone_values.sort(reverse=True)
|
||||
bone_values = bone_values[:4]
|
||||
|
||||
bone_count_array[index] = bone_count
|
||||
for bv in bone_values:
|
||||
bone_weight_array[count] = bv[0] * 32767
|
||||
bone_index_array[count] = bv[1]
|
||||
count += 1
|
||||
|
||||
if total_weight not in (0.0, 1.0):
|
||||
normalizer = 1.0 / total_weight
|
||||
for i in range(bone_count):
|
||||
bone_weight_array[count - i - 1] *= normalizer
|
||||
|
||||
oskin['bone_count_array'] = bone_count_array
|
||||
oskin['bone_index_array'] = bone_index_array[:count]
|
||||
oskin['bone_weight_array'] = bone_weight_array[:count]
|
||||
|
||||
# Bone constraints
|
||||
for bone in armature.pose.bones:
|
||||
if len(bone.constraints) > 0:
|
||||
if 'constraints' not in oskin:
|
||||
oskin['constraints'] = []
|
||||
self.add_constraints(bone, oskin, bone=True)
|
313
leenkx/blender/lnx/handlers.py
Normal file
@ -0,0 +1,313 @@
|
||||
import importlib
|
||||
import os
|
||||
import queue
|
||||
import sys
|
||||
import types
|
||||
|
||||
import bpy
|
||||
from bpy.app.handlers import persistent
|
||||
|
||||
import lnx
|
||||
import lnx.api
|
||||
import lnx.nodes_logic
|
||||
import lnx.make_state as state
|
||||
import lnx.utils
|
||||
import lnx.utils_vs
|
||||
from lnx import live_patch, log, make, props
|
||||
from lnx.logicnode import lnx_nodes
|
||||
|
||||
if lnx.is_reload(__name__):
|
||||
lnx.api = lnx.reload_module(lnx.api)
|
||||
live_patch = lnx.reload_module(live_patch)
|
||||
log = lnx.reload_module(log)
|
||||
lnx_nodes = lnx.reload_module(lnx_nodes)
|
||||
lnx.nodes_logic = lnx.reload_module(lnx.nodes_logic)
|
||||
make = lnx.reload_module(make)
|
||||
state = lnx.reload_module(state)
|
||||
props = lnx.reload_module(props)
|
||||
lnx.utils = lnx.reload_module(lnx.utils)
|
||||
lnx.utils_vs = lnx.reload_module(lnx.utils_vs)
|
||||
else:
|
||||
lnx.enable_reload(__name__)
|
||||
|
||||
|
||||
@persistent
|
||||
def on_depsgraph_update_post(self):
|
||||
if state.proc_build is not None:
|
||||
return
|
||||
|
||||
# Recache
|
||||
depsgraph = bpy.context.evaluated_depsgraph_get()
|
||||
|
||||
for update in depsgraph.updates:
|
||||
uid = update.id
|
||||
if hasattr(uid, 'lnx_cached'):
|
||||
# uid.lnx_cached = False # TODO: does not trigger update
|
||||
if isinstance(uid, bpy.types.Mesh) and uid.name in bpy.data.meshes:
|
||||
bpy.data.meshes[uid.name].lnx_cached = False
|
||||
elif isinstance(uid, bpy.types.Curve) and uid.name in bpy.data.curves:
|
||||
bpy.data.curves[uid.name].lnx_cached = False
|
||||
elif isinstance(uid, bpy.types.MetaBall) and uid.name in bpy.data.metaballs:
|
||||
bpy.data.metaballs[uid.name].lnx_cached = False
|
||||
elif isinstance(uid, bpy.types.Armature) and uid.name in bpy.data.armatures:
|
||||
bpy.data.armatures[uid.name].lnx_cached = False
|
||||
elif isinstance(uid, bpy.types.NodeTree) and uid.name in bpy.data.node_groups:
|
||||
bpy.data.node_groups[uid.name].lnx_cached = False
|
||||
elif isinstance(uid, bpy.types.Material) and uid.name in bpy.data.materials:
|
||||
bpy.data.materials[uid.name].lnx_cached = False
|
||||
|
||||
# Send last operator to Krom
|
||||
wrd = bpy.data.worlds['Lnx']
|
||||
if state.proc_play is not None and state.target == 'krom' and wrd.lnx_live_patch:
|
||||
ops = bpy.context.window_manager.operators
|
||||
if len(ops) > 0 and ops[-1] is not None:
|
||||
live_patch.on_operator(ops[-1].bl_idname)
|
||||
|
||||
# Hacky solution to update leenkx props after operator executions.
|
||||
# bpy.context.active_operator doesn't always exist, in some cases
|
||||
# like marking assets for example, this code is also executed before
|
||||
# the operator actually finishes and sets the variable
|
||||
last_operator = getattr(bpy.context, 'active_operator', None)
|
||||
if last_operator is not None:
|
||||
on_operator_post(last_operator.bl_idname)
|
||||
|
||||
|
||||
def on_operator_post(operator_id: str) -> None:
|
||||
"""Called after operator execution. Does not work for operators
|
||||
executed in another context. Warning: this function is also called
|
||||
when the operator execution raised an exception!"""
|
||||
# 3D View > Object > Rigid Body > Copy from Active
|
||||
if operator_id == "RIGIDBODY_OT_object_settings_copy":
|
||||
# Copy leenkx rigid body settings
|
||||
source_obj = bpy.context.active_object
|
||||
for target_obj in bpy.context.selected_objects:
|
||||
target_obj.lnx_rb_linear_factor = source_obj.lnx_rb_linear_factor
|
||||
target_obj.lnx_rb_angular_factor = source_obj.lnx_rb_angular_factor
|
||||
target_obj.lnx_rb_angular_friction = source_obj.lnx_rb_angular_friction
|
||||
target_obj.lnx_rb_trigger = source_obj.lnx_rb_trigger
|
||||
target_obj.lnx_rb_deactivation_time = source_obj.lnx_rb_deactivation_time
|
||||
target_obj.lnx_rb_ccd = source_obj.lnx_rb_ccd
|
||||
target_obj.lnx_rb_collision_filter_mask = source_obj.lnx_rb_collision_filter_mask
|
||||
|
||||
elif operator_id == "NODE_OT_new_node_tree":
|
||||
if bpy.context.space_data.tree_type == lnx.nodes_logic.LnxLogicTree.bl_idname:
|
||||
# In Blender 3.5+, new node trees are no longer called "NodeTree"
|
||||
# but follow the bl_label attribute by default. New logic trees
|
||||
# are thus called "Leenkx Logic Editor" which conflicts with Haxe's
|
||||
# class naming convention. To avoid this, we listen for the
|
||||
# creation of a node tree and then rename it.
|
||||
# Unfortunately, manually naming the tree has the unfortunate
|
||||
# side effect of not basing the new name on the name of the
|
||||
# previously opened node tree, as it is the case for Blender trees...
|
||||
bpy.context.space_data.edit_tree.name = "LogicTree"
|
||||
|
||||
|
||||
def send_operator(op):
|
||||
if hasattr(bpy.context, 'object') and bpy.context.object is not None:
|
||||
obj = bpy.context.object.name
|
||||
if op.name == 'Move':
|
||||
vec = bpy.context.object.location
|
||||
js = 'var o = iron.Scene.active.getChild("' + obj + '"); o.transform.loc.set(' + str(vec[0]) + ', ' + str(vec[1]) + ', ' + str(vec[2]) + '); o.transform.dirty = true;'
|
||||
make.write_patch(js)
|
||||
elif op.name == 'Resize':
|
||||
vec = bpy.context.object.scale
|
||||
js = 'var o = iron.Scene.active.getChild("' + obj + '"); o.transform.scale.set(' + str(vec[0]) + ', ' + str(vec[1]) + ', ' + str(vec[2]) + '); o.transform.dirty = true;'
|
||||
make.write_patch(js)
|
||||
elif op.name == 'Rotate':
|
||||
vec = bpy.context.object.rotation_euler.to_quaternion()
|
||||
js = 'var o = iron.Scene.active.getChild("' + obj + '"); o.transform.rot.set(' + str(vec[1]) + ', ' + str(vec[2]) + ', ' + str(vec[3]) + ' ,' + str(vec[0]) + '); o.transform.dirty = true;'
|
||||
make.write_patch(js)
|
||||
else: # Rebuild
|
||||
make.patch()
|
||||
|
||||
|
||||
def always() -> float:
|
||||
# Force ui redraw
|
||||
if state.redraw_ui:
|
||||
for area in bpy.context.screen.areas:
|
||||
if area.type in ('NODE_EDITOR', 'PROPERTIES', 'VIEW_3D'):
|
||||
area.tag_redraw()
|
||||
state.redraw_ui = False
|
||||
|
||||
return 0.5
|
||||
|
||||
|
||||
def poll_threads() -> float:
|
||||
"""Polls the thread callback queue and if a thread has finished, it
|
||||
is joined with the main thread and the corresponding callback is
|
||||
executed in the main thread.
|
||||
"""
|
||||
try:
|
||||
thread, callback = make.thread_callback_queue.get(block=False)
|
||||
except queue.Empty:
|
||||
return 0.25
|
||||
|
||||
thread.join()
|
||||
|
||||
try:
|
||||
callback()
|
||||
except Exception as e:
|
||||
# If there is an exception, we can no longer return the time to
|
||||
# the next call to this polling function, so to keep it running
|
||||
# we re-register it and then raise the original exception.
|
||||
bpy.app.timers.unregister(poll_threads)
|
||||
bpy.app.timers.register(poll_threads, first_interval=0.01, persistent=True)
|
||||
raise e
|
||||
|
||||
# Quickly check if another thread has finished
|
||||
return 0.01
|
||||
|
||||
|
||||
loaded_py_libraries: dict[str, types.ModuleType] = {}
|
||||
context_screen = None
|
||||
|
||||
|
||||
@persistent
|
||||
def on_save_pre(context):
|
||||
# Ensure that files are saved with the correct version number
|
||||
# (e.g. startup files with an "Arm" world may have old version numbers)
|
||||
wrd = bpy.data.worlds['Lnx']
|
||||
wrd.lnx_version = props.lnx_version
|
||||
wrd.lnx_commit = props.lnx_commit
|
||||
|
||||
|
||||
@persistent
|
||||
def on_load_pre(context):
|
||||
unload_py_libraries()
|
||||
log.clear(clear_warnings=True, clear_errors=True)
|
||||
|
||||
|
||||
@persistent
|
||||
def on_load_post(context):
|
||||
global context_screen
|
||||
context_screen = bpy.context.screen
|
||||
|
||||
props.init_properties_on_load()
|
||||
reload_blend_data()
|
||||
lnx.utils.fetch_bundled_script_names()
|
||||
|
||||
wrd = bpy.data.worlds['Lnx']
|
||||
wrd.lnx_recompile = True
|
||||
lnx.api.remove_drivers()
|
||||
|
||||
load_py_libraries()
|
||||
|
||||
# Show trait users as collections
|
||||
lnx.utils.update_trait_collections()
|
||||
props.update_leenkx_world()
|
||||
|
||||
|
||||
def load_py_libraries():
|
||||
if bpy.data.filepath == '':
|
||||
# When a blend file is opened from the file explorer, Blender
|
||||
# first opens the default file and then the actual blend file,
|
||||
# so this function is called twice. Because the cwd is already
|
||||
# that of the folder containing the blend file, libraries would
|
||||
# be loaded/unloaded once for the default file which is not needed.
|
||||
return
|
||||
|
||||
lib_path = os.path.join(lnx.utils.get_fp(), 'Libraries')
|
||||
if os.path.exists(lib_path):
|
||||
# Don't register nodes twice when calling register_nodes()
|
||||
lnx_nodes.reset_globals()
|
||||
|
||||
# Make sure that Leenkx's categories are registered first (on top of the menu)
|
||||
lnx.logicnode.init_categories()
|
||||
|
||||
libs = os.listdir(lib_path)
|
||||
for lib_name in libs:
|
||||
fp = os.path.join(lib_path, lib_name)
|
||||
if os.path.isdir(fp):
|
||||
if os.path.exists(os.path.join(fp, 'blender.py')):
|
||||
sys.path.append(fp)
|
||||
|
||||
lib_module = importlib.import_module('blender')
|
||||
importlib.reload(lib_module)
|
||||
if hasattr(lib_module, 'register'):
|
||||
lib_module.register()
|
||||
|
||||
log.debug(f'Leenkx: Loaded Python library {lib_name}')
|
||||
loaded_py_libraries[lib_name] = lib_module
|
||||
|
||||
sys.path.remove(fp)
|
||||
|
||||
# Register newly added nodes and node categories
|
||||
lnx.nodes_logic.register_nodes()
|
||||
|
||||
|
||||
def unload_py_libraries():
|
||||
for lib_name, lib_module in loaded_py_libraries.items():
|
||||
if hasattr(lib_module, 'unregister'):
|
||||
lib_module.unregister()
|
||||
lnx.log.debug(f'Leenkx: Unloaded Python library {lib_name}')
|
||||
|
||||
loaded_py_libraries.clear()
|
||||
|
||||
|
||||
def reload_blend_data():
|
||||
leenkx_pbr = bpy.data.node_groups.get('Leenkx PBR')
|
||||
if leenkx_pbr is None:
|
||||
load_library('Leenkx PBR')
|
||||
custom_tilesheet = bpy.data.node_groups.get('CustomTilesheet')
|
||||
if custom_tilesheet is None:
|
||||
load_library('CustomTilesheet')
|
||||
|
||||
|
||||
def load_library(asset_name):
|
||||
if bpy.data.filepath.endswith('lnx_data.blend'): # Prevent load in library itself
|
||||
return
|
||||
sdk_path = lnx.utils.get_sdk_path()
|
||||
data_path = sdk_path + '/leenkx/blender/data/lnx_data.blend'
|
||||
data_names = [asset_name]
|
||||
|
||||
# Import
|
||||
data_refs = data_names.copy()
|
||||
with bpy.data.libraries.load(data_path, link=False) as (data_from, data_to):
|
||||
data_to.node_groups = data_refs
|
||||
|
||||
for ref in data_refs:
|
||||
ref.use_fake_user = True
|
||||
|
||||
|
||||
def post_register():
|
||||
"""Called in start.py after all Leenkx modules have been registered.
|
||||
It is also called in case of add-on reloads. Put code here that
|
||||
needs to be run once at the beginning of each session.
|
||||
"""
|
||||
if lnx.utils.get_os_is_windows():
|
||||
lnx.utils_vs.fetch_installed_vs(silent=True)
|
||||
|
||||
|
||||
def register():
|
||||
bpy.app.handlers.save_pre.append(on_save_pre)
|
||||
bpy.app.handlers.load_pre.append(on_load_pre)
|
||||
bpy.app.handlers.load_post.append(on_load_post)
|
||||
bpy.app.handlers.depsgraph_update_post.append(on_depsgraph_update_post)
|
||||
# bpy.app.handlers.undo_post.append(on_undo_post)
|
||||
|
||||
bpy.app.timers.register(always, persistent=True)
|
||||
bpy.app.timers.register(poll_threads, persistent=True)
|
||||
|
||||
if lnx.utils.get_fp() != '':
|
||||
# TODO: On windows, on_load_post is not called when opening .blend file from explorer
|
||||
if lnx.utils.get_os() == 'win':
|
||||
on_load_post(None)
|
||||
else:
|
||||
# load_py_libraries() is called by on_load_post(). This call makes sure that libraries are also loaded
|
||||
# when a file is already opened during add-on registration
|
||||
load_py_libraries()
|
||||
|
||||
reload_blend_data()
|
||||
|
||||
|
||||
def unregister():
|
||||
unload_py_libraries()
|
||||
|
||||
bpy.app.timers.unregister(poll_threads)
|
||||
bpy.app.timers.unregister(always)
|
||||
|
||||
bpy.app.handlers.load_post.remove(on_load_post)
|
||||
bpy.app.handlers.load_pre.remove(on_load_pre)
|
||||
bpy.app.handlers.save_pre.remove(on_save_pre)
|
||||
bpy.app.handlers.depsgraph_update_post.remove(on_depsgraph_update_post)
|
||||
# bpy.app.handlers.undo_post.remove(on_undo_post)
|
54
leenkx/blender/lnx/keymap.py
Normal file
@ -0,0 +1,54 @@
|
||||
import bpy
|
||||
import lnx
|
||||
from lnx import log, props_ui
|
||||
|
||||
if lnx.is_reload(__name__):
|
||||
props_ui = lnx.reload_module(props_ui)
|
||||
else:
|
||||
lnx.enable_reload(__name__)
|
||||
|
||||
lnx.keymaps = []
|
||||
|
||||
|
||||
def register():
|
||||
wm = bpy.context.window_manager
|
||||
addon_keyconfig = wm.keyconfigs.addon
|
||||
|
||||
# Keyconfigs are not available in background mode. If the keyconfig
|
||||
# was not found despite running _not_ in background mode, a warning
|
||||
# is printed
|
||||
if addon_keyconfig is None:
|
||||
if not bpy.app.background:
|
||||
log.warn("No keyconfig path found")
|
||||
return
|
||||
|
||||
km = addon_keyconfig.keymaps.new(name='Window', space_type='EMPTY', region_type="WINDOW")
|
||||
km.keymap_items.new(props_ui.LeenkxPlayButton.bl_idname, type='F5', value='PRESS')
|
||||
km.keymap_items.new("tlm.build_lightmaps", type='F6', value='PRESS')
|
||||
km.keymap_items.new("tlm.clean_lightmaps", type='F7', value='PRESS')
|
||||
lnx.keymaps.append(km)
|
||||
|
||||
km = addon_keyconfig.keymaps.new(name='Node Editor', space_type='NODE_EDITOR')
|
||||
|
||||
# shift+G: Create a new node call group node
|
||||
km.keymap_items.new('lnx.add_call_group_node', 'G', 'PRESS', shift=True)
|
||||
|
||||
# ctrl+G: make node group from selected
|
||||
km.keymap_items.new('lnx.add_group_tree_from_selected', 'G', 'PRESS', ctrl=True)
|
||||
|
||||
# TAB: enter node groups depending on selection
|
||||
km.keymap_items.new('lnx.edit_group_tree', 'TAB', 'PRESS')
|
||||
|
||||
# ctrl+TAB: exit node groups depending on selectio
|
||||
km.keymap_items.new('node.tree_path_parent', 'TAB', 'PRESS', ctrl=True)
|
||||
|
||||
# alt+G: ungroup node tree
|
||||
km.keymap_items.new('lnx.ungroup_group_tree', 'G', 'PRESS', alt=True)
|
||||
lnx.keymaps.append(km)
|
||||
|
||||
|
||||
def unregister():
|
||||
wm = bpy.context.window_manager
|
||||
for km in lnx.keymaps:
|
||||
wm.keyconfigs.addon.keymaps.remove(km)
|
||||
del lnx.keymaps[:]
|
0
leenkx/blender/lnx/lib/__init__.py
Normal file
BIN
leenkx/blender/lnx/lib/__pycache__/__init__.cpython-311.pyc
Normal file
BIN
leenkx/blender/lnx/lib/__pycache__/lnxpack.cpython-311.pyc
Normal file
BIN
leenkx/blender/lnx/lib/__pycache__/lz4.cpython-311.pyc
Normal file
BIN
leenkx/blender/lnx/lib/__pycache__/make_datas.cpython-311.pyc
Normal file
BIN
leenkx/blender/lnx/lib/__pycache__/server.cpython-311.pyc
Normal file
175
leenkx/blender/lnx/lib/lnxpack.py
Normal file
@ -0,0 +1,175 @@
|
||||
"""Msgpack parser with typed arrays"""
|
||||
|
||||
# Based on u-msgpack-python v2.4.1 - v at sergeev.io
|
||||
# https://github.com/vsergeev/u-msgpack-python
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#
|
||||
import io
|
||||
import struct
|
||||
import numpy as np
|
||||
|
||||
|
||||
def _pack_integer(obj, fp):
|
||||
if obj < 0:
|
||||
if obj >= -32:
|
||||
fp.write(struct.pack("b", obj))
|
||||
elif obj >= -(2 ** (8 - 1)):
|
||||
fp.write(b"\xd0" + struct.pack("b", obj))
|
||||
elif obj >= -(2 ** (16 - 1)):
|
||||
fp.write(b"\xd1" + struct.pack("<h", obj))
|
||||
elif obj >= -(2 ** (32 - 1)):
|
||||
fp.write(b"\xd2" + struct.pack("<i", obj))
|
||||
elif obj >= -(2 ** (64 - 1)):
|
||||
fp.write(b"\xd3" + struct.pack("<q", obj))
|
||||
else:
|
||||
raise Exception("huge signed int")
|
||||
else:
|
||||
if obj <= 127:
|
||||
fp.write(struct.pack("B", obj))
|
||||
elif obj <= 2**8 - 1:
|
||||
fp.write(b"\xcc" + struct.pack("B", obj))
|
||||
elif obj <= 2**16 - 1:
|
||||
fp.write(b"\xcd" + struct.pack("<H", obj))
|
||||
elif obj <= 2**32 - 1:
|
||||
fp.write(b"\xce" + struct.pack("<I", obj))
|
||||
elif obj <= 2**64 - 1:
|
||||
fp.write(b"\xcf" + struct.pack("<Q", obj))
|
||||
else:
|
||||
raise Exception("huge unsigned int")
|
||||
|
||||
|
||||
def _pack_nil(obj, fp):
|
||||
fp.write(b"\xc0")
|
||||
|
||||
|
||||
def _pack_boolean(obj, fp):
|
||||
fp.write(b"\xc3" if obj else b"\xc2")
|
||||
|
||||
|
||||
def _pack_float(obj, fp):
|
||||
# NOTE: forced 32-bit floats for Leenkx
|
||||
# fp.write(b"\xcb" + struct.pack("<d", obj)) # Double
|
||||
fp.write(b"\xca" + struct.pack("<f", obj))
|
||||
|
||||
|
||||
def _pack_string(obj, fp):
|
||||
obj = obj.encode("utf-8")
|
||||
if len(obj) <= 31:
|
||||
fp.write(struct.pack("B", 0xA0 | len(obj)) + obj)
|
||||
elif len(obj) <= 2**8 - 1:
|
||||
fp.write(b"\xd9" + struct.pack("B", len(obj)) + obj)
|
||||
elif len(obj) <= 2**16 - 1:
|
||||
fp.write(b"\xda" + struct.pack("<H", len(obj)) + obj)
|
||||
elif len(obj) <= 2**32 - 1:
|
||||
fp.write(b"\xdb" + struct.pack("<I", len(obj)) + obj)
|
||||
else:
|
||||
raise Exception("huge string")
|
||||
|
||||
|
||||
def _pack_binary(obj, fp):
|
||||
if len(obj) <= 2**8 - 1:
|
||||
fp.write(b"\xc4" + struct.pack("B", len(obj)) + obj)
|
||||
elif len(obj) <= 2**16 - 1:
|
||||
fp.write(b"\xc5" + struct.pack("<H", len(obj)) + obj)
|
||||
elif len(obj) <= 2**32 - 1:
|
||||
fp.write(b"\xc6" + struct.pack("<I", len(obj)) + obj)
|
||||
else:
|
||||
raise Exception("huge binary string")
|
||||
|
||||
|
||||
def _pack_array(obj, fp):
|
||||
if len(obj) <= 15:
|
||||
fp.write(struct.pack("B", 0x90 | len(obj)))
|
||||
elif len(obj) <= 2**16 - 1:
|
||||
fp.write(b"\xdc" + struct.pack("<H", len(obj)))
|
||||
elif len(obj) <= 2**32 - 1:
|
||||
fp.write(b"\xdd" + struct.pack("<I", len(obj)))
|
||||
else:
|
||||
raise Exception("huge array")
|
||||
|
||||
if len(obj) > 0 and isinstance(obj[0], float):
|
||||
fp.write(b"\xca")
|
||||
for e in obj:
|
||||
fp.write(struct.pack("<f", e))
|
||||
elif len(obj) > 0 and isinstance(obj[0], bool):
|
||||
for e in obj:
|
||||
pack(e, fp)
|
||||
elif len(obj) > 0 and isinstance(obj[0], int):
|
||||
fp.write(b"\xd2")
|
||||
for e in obj:
|
||||
fp.write(struct.pack("<i", e))
|
||||
# Float32
|
||||
elif len(obj) > 0 and isinstance(obj[0], np.float32):
|
||||
fp.write(b"\xca")
|
||||
fp.write(obj.tobytes())
|
||||
# Int32
|
||||
elif len(obj) > 0 and isinstance(obj[0], np.int32):
|
||||
fp.write(b"\xd2")
|
||||
fp.write(obj.tobytes())
|
||||
# Int16
|
||||
elif len(obj) > 0 and isinstance(obj[0], np.int16):
|
||||
fp.write(b"\xd1")
|
||||
fp.write(obj.tobytes())
|
||||
# Regular
|
||||
else:
|
||||
for e in obj:
|
||||
pack(e, fp)
|
||||
|
||||
|
||||
def _pack_map(obj, fp):
|
||||
if len(obj) <= 15:
|
||||
fp.write(struct.pack("B", 0x80 | len(obj)))
|
||||
elif len(obj) <= 2**16 - 1:
|
||||
fp.write(b"\xde" + struct.pack("<H", len(obj)))
|
||||
elif len(obj) <= 2**32 - 1:
|
||||
fp.write(b"\xdf" + struct.pack("<I", len(obj)))
|
||||
else:
|
||||
raise Exception("huge array")
|
||||
|
||||
for k, v in obj.items():
|
||||
pack(k, fp)
|
||||
pack(v, fp)
|
||||
|
||||
|
||||
def pack(obj, fp):
|
||||
if obj is None:
|
||||
_pack_nil(obj, fp)
|
||||
elif isinstance(obj, bool):
|
||||
_pack_boolean(obj, fp)
|
||||
elif isinstance(obj, int):
|
||||
_pack_integer(obj, fp)
|
||||
elif isinstance(obj, float):
|
||||
_pack_float(obj, fp)
|
||||
elif isinstance(obj, str):
|
||||
_pack_string(obj, fp)
|
||||
elif isinstance(obj, bytes):
|
||||
_pack_binary(obj, fp)
|
||||
elif isinstance(obj, (list, np.ndarray, tuple)):
|
||||
_pack_array(obj, fp)
|
||||
elif isinstance(obj, dict):
|
||||
_pack_map(obj, fp)
|
||||
else:
|
||||
raise Exception(f"unsupported type: {str(type(obj))}")
|
||||
|
||||
|
||||
def packb(obj):
|
||||
fp = io.BytesIO()
|
||||
pack(obj, fp)
|
||||
return fp.getvalue()
|
181
leenkx/blender/lnx/lib/lz4.py
Normal file
@ -0,0 +1,181 @@
|
||||
"""
|
||||
Port of the Iron LZ4 compression module based on
|
||||
https://github.com/gorhill/lz4-wasm. Original license:
|
||||
|
||||
BSD 2-Clause License
|
||||
Copyright (c) 2018, Raymond Hill
|
||||
All rights reserved.
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
"""
|
||||
import numpy as np
|
||||
from numpy import uint8, int32, uint32
|
||||
|
||||
|
||||
class LZ4RangeException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class LZ4:
|
||||
hash_table = None
|
||||
|
||||
@staticmethod
|
||||
def encode_bound(size: int) -> int:
|
||||
return 0 if size > 0x7E000000 else size + (size // 255 | 0) + 16
|
||||
|
||||
@staticmethod
|
||||
def encode(b: bytes) -> bytes:
|
||||
i_buf: np.ndarray = np.frombuffer(b, dtype=uint8)
|
||||
i_len = i_buf.size
|
||||
|
||||
if i_len >= 0x7E000000:
|
||||
raise LZ4RangeException("Input buffer is too large")
|
||||
|
||||
# "The last match must start at least 12 bytes before end of block"
|
||||
last_match_pos = i_len - 12
|
||||
|
||||
# "The last 5 bytes are always literals"
|
||||
last_literal_pos = i_len - 5
|
||||
|
||||
if LZ4.hash_table is None:
|
||||
LZ4.hash_table = np.full(shape=65536, fill_value=-65536, dtype=int32)
|
||||
|
||||
LZ4.hash_table.fill(-65536)
|
||||
|
||||
o_len = LZ4.encode_bound(i_len)
|
||||
o_buf = np.full(shape=o_len, fill_value=0, dtype=uint8)
|
||||
i_pos = 0
|
||||
o_pos = 0
|
||||
anchor_pos = 0
|
||||
|
||||
# Sequence-finding loop
|
||||
while True:
|
||||
ref_pos = int32(0)
|
||||
m_offset = 0
|
||||
sequence = uint32(
|
||||
i_buf[i_pos] << 8 | i_buf[i_pos + 1] << 16 | i_buf[i_pos + 2] << 24
|
||||
)
|
||||
|
||||
# Match-finding loop
|
||||
while i_pos <= last_match_pos:
|
||||
# Conversion to uint32 is mandatory to ensure correct
|
||||
# unsigned right shift (compare with .hx implementation)
|
||||
sequence = uint32(
|
||||
uint32(sequence) >> uint32(8) | i_buf[i_pos + 3] << 24
|
||||
)
|
||||
hash_val = (sequence * 0x9E37 & 0xFFFF) + (
|
||||
uint32(sequence * 0x79B1) >> uint32(16)
|
||||
) & 0xFFFF
|
||||
ref_pos = LZ4.hash_table[hash_val]
|
||||
LZ4.hash_table[hash_val] = i_pos
|
||||
m_offset = i_pos - ref_pos
|
||||
if (
|
||||
m_offset < 65536
|
||||
and i_buf[ref_pos + 0] == (sequence & 0xFF)
|
||||
and i_buf[ref_pos + 1] == ((sequence >> uint32(8)) & 0xFF)
|
||||
and i_buf[ref_pos + 2] == ((sequence >> uint32(16)) & 0xFF)
|
||||
and i_buf[ref_pos + 3] == ((sequence >> uint32(24)) & 0xFF)
|
||||
):
|
||||
break
|
||||
|
||||
i_pos += 1
|
||||
|
||||
# No match found
|
||||
if i_pos > last_match_pos:
|
||||
break
|
||||
|
||||
# Match found
|
||||
l_len = i_pos - anchor_pos
|
||||
m_len = i_pos
|
||||
i_pos += 4
|
||||
ref_pos += 4
|
||||
while i_pos < last_literal_pos and i_buf[i_pos] == i_buf[ref_pos]:
|
||||
i_pos += 1
|
||||
ref_pos += 1
|
||||
|
||||
m_len = i_pos - m_len
|
||||
token = m_len - 4 if m_len < 19 else 15
|
||||
|
||||
# Write token, length of literals if needed
|
||||
if l_len >= 15:
|
||||
o_buf[o_pos] = 0xF0 | token
|
||||
o_pos += 1
|
||||
l = l_len - 15
|
||||
while l >= 255:
|
||||
o_buf[o_pos] = 255
|
||||
o_pos += 1
|
||||
l -= 255
|
||||
o_buf[o_pos] = l
|
||||
o_pos += 1
|
||||
else:
|
||||
o_buf[o_pos] = (l_len << 4) | token
|
||||
o_pos += 1
|
||||
|
||||
# Write literals
|
||||
while l_len > 0:
|
||||
l_len -= 1
|
||||
o_buf[o_pos] = i_buf[anchor_pos]
|
||||
o_pos += 1
|
||||
anchor_pos += 1
|
||||
|
||||
if m_len == 0:
|
||||
break
|
||||
|
||||
# Write offset of match
|
||||
o_buf[o_pos + 0] = m_offset
|
||||
o_buf[o_pos + 1] = m_offset >> 8
|
||||
o_pos += 2
|
||||
|
||||
# Write length of match if needed
|
||||
if m_len >= 19:
|
||||
l = m_len - 19
|
||||
while l >= 255:
|
||||
o_buf[o_pos] = 255
|
||||
o_pos += 1
|
||||
l -= 255
|
||||
|
||||
o_buf[o_pos] = l
|
||||
o_pos += 1
|
||||
|
||||
anchor_pos = i_pos
|
||||
|
||||
# Last sequence is literals only
|
||||
l_len = i_len - anchor_pos
|
||||
if l_len >= 15:
|
||||
o_buf[o_pos] = 0xF0
|
||||
o_pos += 1
|
||||
l = l_len - 15
|
||||
while l >= 255:
|
||||
o_buf[o_pos] = 255
|
||||
o_pos += 1
|
||||
l -= 255
|
||||
|
||||
o_buf[o_pos] = l
|
||||
o_pos += 1
|
||||
|
||||
else:
|
||||
o_buf[o_pos] = l_len << 4
|
||||
o_pos += 1
|
||||
|
||||
while l_len > 0:
|
||||
l_len -= 1
|
||||
o_buf[o_pos] = i_buf[anchor_pos]
|
||||
o_pos += 1
|
||||
anchor_pos += 1
|
||||
|
||||
return np.resize(o_buf, o_pos).tobytes()
|
328
leenkx/blender/lnx/lib/make_datas.py
Normal file
@ -0,0 +1,328 @@
|
||||
import lnx.utils
|
||||
from lnx import assets
|
||||
|
||||
def parse_context(
|
||||
c: dict,
|
||||
sres: dict,
|
||||
asset,
|
||||
defs: list[str],
|
||||
vert: list[str] = None,
|
||||
frag: list[str] = None,
|
||||
):
|
||||
con = {
|
||||
"name": c["name"],
|
||||
"constants": [],
|
||||
"texture_units": [],
|
||||
"vertex_elements": [],
|
||||
}
|
||||
sres["contexts"].append(con)
|
||||
|
||||
# Names
|
||||
con["vertex_shader"] = c["vertex_shader"].rsplit(".", 1)[0].split("/")[-1]
|
||||
if con["vertex_shader"] not in asset:
|
||||
asset.append(con["vertex_shader"])
|
||||
|
||||
con["fragment_shader"] = c["fragment_shader"].rsplit(".", 1)[0].split("/")[-1]
|
||||
if con["fragment_shader"] not in asset:
|
||||
asset.append(con["fragment_shader"])
|
||||
|
||||
if "geometry_shader" in c:
|
||||
con["geometry_shader"] = c["geometry_shader"].rsplit(".", 1)[0].split("/")[-1]
|
||||
if con["geometry_shader"] not in asset:
|
||||
asset.append(con["geometry_shader"])
|
||||
|
||||
if "tesscontrol_shader" in c:
|
||||
con["tesscontrol_shader"] = (
|
||||
c["tesscontrol_shader"].rsplit(".", 1)[0].split("/")[-1]
|
||||
)
|
||||
if con["tesscontrol_shader"] not in asset:
|
||||
asset.append(con["tesscontrol_shader"])
|
||||
|
||||
if "tesseval_shader" in c:
|
||||
con["tesseval_shader"] = c["tesseval_shader"].rsplit(".", 1)[0].split("/")[-1]
|
||||
if con["tesseval_shader"] not in asset:
|
||||
asset.append(con["tesseval_shader"])
|
||||
|
||||
if "color_attachments" in c:
|
||||
con["color_attachments"] = c["color_attachments"]
|
||||
for i in range(len(con["color_attachments"])):
|
||||
if con["color_attachments"][i] == "_HDR":
|
||||
con["color_attachments"][i] = "RGBA32" if "_LDR" in defs else "RGBA64"
|
||||
|
||||
# Params
|
||||
params = [
|
||||
"depth_write",
|
||||
"compare_mode",
|
||||
"cull_mode",
|
||||
"blend_source",
|
||||
"blend_destination",
|
||||
"blend_operation",
|
||||
"alpha_blend_source",
|
||||
"alpha_blend_destination",
|
||||
"alpha_blend_operation",
|
||||
"color_writes_red",
|
||||
"color_writes_green",
|
||||
"color_writes_blue",
|
||||
"color_writes_alpha",
|
||||
"conservative_raster",
|
||||
]
|
||||
|
||||
for p in params:
|
||||
if p in c:
|
||||
con[p] = c[p]
|
||||
|
||||
# Parse shaders
|
||||
if vert is None:
|
||||
with open(c["vertex_shader"], encoding="utf-8") as f:
|
||||
vert = f.read().splitlines()
|
||||
parse_shader(sres, c, con, defs, vert, True) # Parse attribs for vertex shader
|
||||
|
||||
if frag is None:
|
||||
with open(c["fragment_shader"], encoding="utf-8") as f:
|
||||
frag = f.read().splitlines()
|
||||
parse_shader(sres, c, con, defs, frag, False)
|
||||
|
||||
if "geometry_shader" in c:
|
||||
with open(c["geometry_shader"], encoding="utf-8") as f:
|
||||
geom = f.read().splitlines()
|
||||
parse_shader(sres, c, con, defs, geom, False)
|
||||
|
||||
if "tesscontrol_shader" in c:
|
||||
with open(c["tesscontrol_shader"], encoding="utf-8") as f:
|
||||
tesc = f.read().splitlines()
|
||||
parse_shader(sres, c, con, defs, tesc, False)
|
||||
|
||||
if "tesseval_shader" in c:
|
||||
with open(c["tesseval_shader"], encoding="utf-8") as f:
|
||||
tese = f.read().splitlines()
|
||||
parse_shader(sres, c, con, defs, tese, False)
|
||||
|
||||
|
||||
def parse_shader(
|
||||
sres, c: dict, con: dict, defs: list[str], lines: list[str], parse_attributes: bool
|
||||
):
|
||||
"""Parses the given shader to get information about the used vertex
|
||||
elements, uniforms and constants. This information is later used in
|
||||
Iron to check what data each shader requires.
|
||||
|
||||
@param defs A list of set defines for the preprocessor
|
||||
@param lines The list of lines of the shader file
|
||||
@param parse_attributes Whether to parse vertex elements
|
||||
"""
|
||||
vertex_elements_parsed = False
|
||||
vertex_elements_parsing = False
|
||||
|
||||
# Stack of the state of all preprocessor conditions for the current
|
||||
# line. If there is a `False` in the stack, at least one surrounding
|
||||
# condition is false and the line must not be parsed
|
||||
stack: list[bool] = []
|
||||
|
||||
if not parse_attributes:
|
||||
vertex_elements_parsed = True
|
||||
|
||||
for line in lines:
|
||||
line = line.lstrip()
|
||||
|
||||
# Preprocessor
|
||||
if line.startswith("#if"): # if, ifdef, ifndef
|
||||
s = line.split(" ")[1]
|
||||
found = s in defs
|
||||
if line.startswith("#ifndef"):
|
||||
found = not found
|
||||
stack.append(found)
|
||||
continue
|
||||
|
||||
if line.startswith("#else"):
|
||||
stack[-1] = not stack[-1]
|
||||
continue
|
||||
|
||||
if line.startswith("#endif"):
|
||||
stack.pop()
|
||||
continue
|
||||
|
||||
# Skip lines if the stack contains at least one preprocessor
|
||||
# condition that is not fulfilled
|
||||
skip = False
|
||||
for condition in stack:
|
||||
if not condition:
|
||||
skip = True
|
||||
break
|
||||
if skip:
|
||||
continue
|
||||
|
||||
if not vertex_elements_parsed and line.startswith("in "):
|
||||
vertex_elements_parsing = True
|
||||
s = line.split(" ")
|
||||
con["vertex_elements"].append(
|
||||
{
|
||||
"data": "float" + s[1][-1:],
|
||||
"name": s[2][:-1], # [:1] to get rid of the semicolon
|
||||
}
|
||||
)
|
||||
|
||||
# Stop the vertex element parsing if no other vertex elements
|
||||
# follow directly (assuming all vertex elements are positioned
|
||||
# directly after each other apart from empty lines and comments)
|
||||
if (
|
||||
vertex_elements_parsing
|
||||
and len(line) > 0
|
||||
and not line.startswith("//")
|
||||
and not line.startswith("in ")
|
||||
):
|
||||
vertex_elements_parsed = True
|
||||
|
||||
if line.startswith("uniform ") or line.startswith(
|
||||
"//!uniform"
|
||||
): # Uniforms included from header files
|
||||
s = line.split(" ")
|
||||
# Examples:
|
||||
# uniform sampler2D myname;
|
||||
# uniform layout(RGBA8) image3D myname;
|
||||
if s[1].startswith("layout"):
|
||||
ctype = s[2]
|
||||
cid = s[3]
|
||||
if cid[-1] == ";":
|
||||
cid = cid[:-1]
|
||||
else:
|
||||
ctype = s[1]
|
||||
cid = s[2]
|
||||
if cid[-1] == ";":
|
||||
cid = cid[:-1]
|
||||
|
||||
found = False # Uniqueness check
|
||||
if (
|
||||
ctype.startswith("sampler")
|
||||
or ctype.startswith("image")
|
||||
or ctype.startswith("uimage")
|
||||
): # Texture unit
|
||||
for tu in con["texture_units"]:
|
||||
if tu["name"] == cid:
|
||||
# Texture already present
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
if cid[-1] == "]": # Array of samplers - sampler2D mySamplers[2]
|
||||
# Add individual units - mySamplers[0], mySamplers[1]
|
||||
for i in range(int(cid[-2])):
|
||||
tu = {"name": cid[:-2] + str(i) + "]"}
|
||||
con["texture_units"].append(tu)
|
||||
else:
|
||||
tu = {"name": cid}
|
||||
con["texture_units"].append(tu)
|
||||
if ctype.startswith("image") or ctype.startswith("uimage"):
|
||||
tu["is_image"] = True
|
||||
|
||||
check_link(c, defs, cid, tu)
|
||||
|
||||
else: # Constant
|
||||
if cid.find("[") != -1: # Float arrays
|
||||
cid = cid.split("[")[0]
|
||||
ctype = "floats"
|
||||
for const in con["constants"]:
|
||||
if const["name"] == cid:
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
const = {"type": ctype, "name": cid}
|
||||
con["constants"].append(const)
|
||||
|
||||
check_link(c, defs, cid, const)
|
||||
|
||||
|
||||
def check_link(source_context: dict, defs: list[str], cid: str, out: dict):
|
||||
"""Checks whether the uniform/constant with the given name (`cid`)
|
||||
has a link stated in the json (`source_context`) that can be safely
|
||||
included based on the given defines (`defs`). If that is the case,
|
||||
the found link is written to the `out` dictionary.
|
||||
"""
|
||||
for link in source_context["links"]:
|
||||
if link["name"] == cid:
|
||||
valid_link = True
|
||||
|
||||
# Optionally only use link if at least
|
||||
# one of the given defines is set
|
||||
if "ifdef" in link:
|
||||
def_found = False
|
||||
for d in defs:
|
||||
for link_def in link["ifdef"]:
|
||||
if d == link_def:
|
||||
def_found = True
|
||||
break
|
||||
if def_found:
|
||||
break
|
||||
if not def_found:
|
||||
valid_link = False
|
||||
|
||||
# Optionally only use link if none of
|
||||
# the given defines are set
|
||||
if "ifndef" in link:
|
||||
def_found = False
|
||||
for d in defs:
|
||||
for link_def in link["ifndef"]:
|
||||
if d == link_def:
|
||||
def_found = True
|
||||
break
|
||||
if def_found:
|
||||
break
|
||||
if def_found:
|
||||
valid_link = False
|
||||
|
||||
if valid_link:
|
||||
out["link"] = link["link"]
|
||||
break
|
||||
|
||||
|
||||
def make(
|
||||
res: dict, base_name: str, json_data: dict, fp, defs: list[str], make_variants: bool
|
||||
):
|
||||
sres = {"name": base_name, "contexts": []}
|
||||
res["shader_datas"].append(sres)
|
||||
asset = assets.shader_passes_assets[base_name]
|
||||
|
||||
vert = None
|
||||
frag = None
|
||||
has_variants = "variants" in json_data and len(json_data["variants"]) > 0
|
||||
if make_variants and has_variants:
|
||||
d = json_data["variants"][0]
|
||||
if d in defs:
|
||||
# Write shader variant with define
|
||||
c = json_data["contexts"][0]
|
||||
with open(c["vertex_shader"], encoding="utf-8") as f:
|
||||
vert = f.read().split("\n", 1)[1]
|
||||
vert = "#version 450\n#define " + d + "\n" + vert
|
||||
|
||||
with open(c["fragment_shader"], encoding="utf-8") as f:
|
||||
frag = f.read().split("\n", 1)[1]
|
||||
frag = "#version 450\n#define " + d + "\n" + frag
|
||||
|
||||
with open(
|
||||
lnx.utils.get_fp_build()
|
||||
+ "/compiled/Shaders/"
|
||||
+ base_name
|
||||
+ d
|
||||
+ ".vert.glsl",
|
||||
"w",
|
||||
encoding="utf-8",
|
||||
) as f:
|
||||
f.write(vert)
|
||||
|
||||
with open(
|
||||
lnx.utils.get_fp_build()
|
||||
+ "/compiled/Shaders/"
|
||||
+ base_name
|
||||
+ d
|
||||
+ ".frag.glsl",
|
||||
"w",
|
||||
encoding="utf-8",
|
||||
) as f:
|
||||
f.write(frag)
|
||||
|
||||
# Add context variant
|
||||
c2 = c.copy()
|
||||
c2["vertex_shader"] = base_name + d + ".vert.glsl"
|
||||
c2["fragment_shader"] = base_name + d + ".frag.glsl"
|
||||
c2["name"] = c["name"] + d
|
||||
parse_context(c2, sres, asset, defs, vert.splitlines(), frag.splitlines())
|
||||
|
||||
for c in json_data["contexts"]:
|
||||
parse_context(c, sres, asset, defs)
|
33
leenkx/blender/lnx/lib/server.py
Normal file
@ -0,0 +1,33 @@
|
||||
import atexit
|
||||
import http.server
|
||||
import socketserver
|
||||
import subprocess
|
||||
|
||||
haxe_server = None
|
||||
|
||||
|
||||
def run_tcp(port: int, do_log: bool):
|
||||
class HTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
|
||||
def log_message(self, format, *args):
|
||||
if do_log:
|
||||
print(format % args)
|
||||
|
||||
try:
|
||||
http_server = socketserver.TCPServer(("", port), HTTPRequestHandler)
|
||||
http_server.serve_forever()
|
||||
except:
|
||||
print("Server already running")
|
||||
|
||||
|
||||
def run_haxe(haxe_path, port=6000):
|
||||
global haxe_server
|
||||
if haxe_server is None:
|
||||
haxe_server = subprocess.Popen([haxe_path, "--wait", str(port)])
|
||||
atexit.register(kill_haxe)
|
||||
|
||||
|
||||
def kill_haxe():
|
||||
global haxe_server
|
||||
if haxe_server is not None:
|
||||
haxe_server.kill()
|
||||
haxe_server = None
|
1
leenkx/blender/lnx/lightmapper/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
__all__ = ('Operators', 'Panels', 'Properties', 'Preferences', 'Utility', 'Keymap')
|
BIN
leenkx/blender/lnx/lightmapper/assets/TLM_Overlay.png
Normal file
After Width: | Height: | Size: 24 KiB |
BIN
leenkx/blender/lnx/lightmapper/assets/dash.ogg
Normal file
BIN
leenkx/blender/lnx/lightmapper/assets/gentle.ogg
Normal file
BIN
leenkx/blender/lnx/lightmapper/assets/noot.ogg
Normal file
BIN
leenkx/blender/lnx/lightmapper/assets/pingping.ogg
Normal file
BIN
leenkx/blender/lnx/lightmapper/assets/sound.ogg
Normal file
BIN
leenkx/blender/lnx/lightmapper/assets/tlm_data.blend
Normal file
BIN
leenkx/blender/lnx/lightmapper/icons/bake.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
leenkx/blender/lnx/lightmapper/icons/clean.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
BIN
leenkx/blender/lnx/lightmapper/icons/explore.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
7
leenkx/blender/lnx/lightmapper/keymap/__init__.py
Normal file
@ -0,0 +1,7 @@
|
||||
from . import keymap
|
||||
|
||||
def register():
|
||||
keymap.register()
|
||||
|
||||
def unregister():
|
||||
keymap.unregister()
|
22
leenkx/blender/lnx/lightmapper/keymap/keymap.py
Normal file
@ -0,0 +1,22 @@
|
||||
import bpy
|
||||
|
||||
tlm_keymaps = []
|
||||
|
||||
def register():
|
||||
|
||||
if not bpy.app.background:
|
||||
|
||||
winman = bpy.context.window_manager
|
||||
keyman = winman.keyconfigs.addon.keymaps.new(name='Window', space_type='EMPTY', region_type="WINDOW")
|
||||
|
||||
|
||||
#TODO - In Leenkx3D, merge with keymap.py
|
||||
keyman.keymap_items.new('tlm.build_lightmaps', type='F6', value='PRESS')
|
||||
keyman.keymap_items.new('tlm.clean_lightmaps', type='F7', value='PRESS')
|
||||
tlm_keymaps.append(keyman)
|
||||
|
||||
def unregister():
|
||||
winman = bpy.context.window_manager
|
||||
for keyman in tlm_keymaps:
|
||||
winman.keyconfigs.addon.keymaps.remove(keyman)
|
||||
del tlm_keymaps[:]
|
27
leenkx/blender/lnx/lightmapper/network/client.py
Normal file
@ -0,0 +1,27 @@
|
||||
import socket, json, os
|
||||
|
||||
def connect_client(machine, port, blendpath, obj_num):
|
||||
|
||||
# Create a socket
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
|
||||
# Connect to the remote host and port
|
||||
sock.connect((machine, port))
|
||||
|
||||
#0: Blendpath,
|
||||
#1: For all designated objects, run from 0 to number; 0 indicates all
|
||||
|
||||
args = [blendpath, obj_num]
|
||||
|
||||
command = json.dumps({'call':1, 'command':1, 'args':args})
|
||||
|
||||
# Send a request to the host
|
||||
sock.send((command).encode())
|
||||
|
||||
# Get the host's response, no more than, say, 1,024 bytes
|
||||
response_data = sock.recv(1024)
|
||||
|
||||
print(response_data.decode())
|
||||
|
||||
# Terminate
|
||||
sock.close()
|
71
leenkx/blender/lnx/lightmapper/network/server.py
Normal file
@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import bpy, socket, json, subprocess, os, platform, subprocess, select
|
||||
|
||||
def startServer():
|
||||
|
||||
active = True
|
||||
baking = False
|
||||
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.bind(('localhost', 9898))
|
||||
sock.listen(1)
|
||||
|
||||
print("Server started")
|
||||
|
||||
while active:
|
||||
connection,address = sock.accept()
|
||||
|
||||
data = connection.recv(1024)
|
||||
|
||||
if data:
|
||||
|
||||
parsed_data = json.loads(data.decode())
|
||||
|
||||
if parsed_data["call"] == 0: #Ping
|
||||
|
||||
print("Pinged by: " + str(connection.getsockname()))
|
||||
connection.sendall(("Ping callback").encode())
|
||||
|
||||
elif parsed_data["call"] == 1: #Command
|
||||
|
||||
if parsed_data["command"] == 0: #Shutdown
|
||||
|
||||
print("Server shutdown")
|
||||
active = False
|
||||
|
||||
if parsed_data["command"] == 1: #Baking
|
||||
|
||||
print("Baking...")
|
||||
|
||||
args = parsed_data["args"]
|
||||
|
||||
blenderpath = bpy.app.binary_path
|
||||
|
||||
if not baking:
|
||||
|
||||
baking = True
|
||||
|
||||
pipe = subprocess.Popen([blenderpath, "-b", str(args[0]), "--python-expr", 'import bpy; import thelightmapper; thelightmapper.addon.utility.build.prepare_build(0, True);'], shell=True, stdout=subprocess.PIPE)
|
||||
|
||||
stdout = pipe.communicate()[0]
|
||||
|
||||
print("Baking finished...")
|
||||
|
||||
active = False
|
||||
|
||||
else:
|
||||
|
||||
print("Request denied, server busy...")
|
||||
|
||||
print("Data received: " + data.decode())
|
||||
|
||||
connection.send(('Callback from: ' + str(socket.gethostname())).encode())
|
||||
|
||||
connection.close()
|
||||
|
||||
print("Connection closed.")
|
||||
|
||||
sock.close()
|
||||
|
||||
print("Server closed.")
|
50
leenkx/blender/lnx/lightmapper/operators/__init__.py
Normal file
@ -0,0 +1,50 @@
|
||||
import bpy
|
||||
from bpy.utils import register_class, unregister_class
|
||||
from . import tlm, installopencv, imagetools
|
||||
|
||||
classes = [
|
||||
tlm.TLM_BuildLightmaps,
|
||||
tlm.TLM_CleanLightmaps,
|
||||
tlm.TLM_ExploreLightmaps,
|
||||
tlm.TLM_EnableSet,
|
||||
tlm.TLM_DisableSelection,
|
||||
tlm.TLM_RemoveLightmapUV,
|
||||
tlm.TLM_SelectLightmapped,
|
||||
tlm.TLM_ToggleTexelDensity,
|
||||
installopencv.TLM_Install_OpenCV,
|
||||
tlm.TLM_AtlasListNewItem,
|
||||
tlm.TLM_AtlastListDeleteItem,
|
||||
tlm.TLM_AtlasListMoveItem,
|
||||
tlm.TLM_PostAtlasListNewItem,
|
||||
tlm.TLM_PostAtlastListDeleteItem,
|
||||
tlm.TLM_PostAtlasListMoveItem,
|
||||
tlm.TLM_StartServer,
|
||||
tlm.TLM_BuildEnvironmentProbes,
|
||||
tlm.TLM_CleanBuildEnvironmentProbes,
|
||||
tlm.TLM_PrepareUVMaps,
|
||||
tlm.TLM_LoadLightmaps,
|
||||
tlm.TLM_DisableSpecularity,
|
||||
tlm.TLM_DisableMetallic,
|
||||
tlm.TLM_RemoveEmptyImages,
|
||||
tlm.TLM_AddCollectionsPost,
|
||||
tlm.TLM_AddSelectedCollectionsPost,
|
||||
tlm.TLM_PostAtlasSpecialsMenu,
|
||||
tlm.TLM_AddCollections,
|
||||
tlm.TLM_AddSelectedCollections,
|
||||
tlm.TLM_AtlasSpecialsMenu,
|
||||
tlm.TLM_Reset,
|
||||
tlm.TLM_CalcTexDex,
|
||||
imagetools.TLM_ImageUpscale,
|
||||
imagetools.TLM_ImageDownscale,
|
||||
tlm.TLM_AddGLTFNode,
|
||||
tlm.TLM_ShiftMultiplyLinks
|
||||
|
||||
]
|
||||
|
||||
def register():
|
||||
for cls in classes:
|
||||
register_class(cls)
|
||||
|
||||
def unregister():
|
||||
for cls in classes:
|
||||
unregister_class(cls)
|
193
leenkx/blender/lnx/lightmapper/operators/imagetools.py
Normal file
@ -0,0 +1,193 @@
|
||||
import bpy, os, time, importlib
|
||||
|
||||
class TLM_ImageUpscale(bpy.types.Operator):
|
||||
bl_idname = "tlm.image_upscale"
|
||||
bl_label = "Upscale image"
|
||||
bl_description = "Upscales the image to double resolution"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
|
||||
cv2 = importlib.util.find_spec("cv2")
|
||||
|
||||
if cv2 is None:
|
||||
print("CV2 not found - Ignoring filtering")
|
||||
return 0
|
||||
else:
|
||||
cv2 = importlib.__import__("cv2")
|
||||
|
||||
for area in bpy.context.screen.areas:
|
||||
if area.type == "IMAGE_EDITOR":
|
||||
active_image = area.spaces.active.image
|
||||
|
||||
if active_image.source == "FILE":
|
||||
img_path = active_image.filepath_raw
|
||||
filename = os.path.basename(img_path)
|
||||
|
||||
basename = os.path.splitext(filename)[0]
|
||||
extension = os.path.splitext(filename)[1]
|
||||
|
||||
size_x = active_image.size[0]
|
||||
size_y = active_image.size[1]
|
||||
|
||||
dir_path = os.path.dirname(os.path.realpath(img_path))
|
||||
|
||||
#newfile = os.path.join(dir_path, basename + "_" + str(size_x) + "_" + str(size_y) + extension)
|
||||
newfile = os.path.join(dir_path, basename + extension)
|
||||
os.rename(img_path, newfile)
|
||||
|
||||
basefile = cv2.imread(newfile, cv2.IMREAD_UNCHANGED)
|
||||
|
||||
scale_percent = 200 # percent of original size
|
||||
width = int(basefile.shape[1] * scale_percent / 100)
|
||||
height = int(basefile.shape[0] * scale_percent / 100)
|
||||
dim = (width, height)
|
||||
|
||||
if active_image.TLM_ImageProperties.tlm_image_scale_method == "Nearest":
|
||||
interp = cv2.INTER_NEAREST
|
||||
elif active_image.TLM_ImageProperties.tlm_image_scale_method == "Area":
|
||||
interp = cv2.INTER_AREA
|
||||
elif active_image.TLM_ImageProperties.tlm_image_scale_method == "Linear":
|
||||
interp = cv2.INTER_LINEAR
|
||||
elif active_image.TLM_ImageProperties.tlm_image_scale_method == "Cubic":
|
||||
interp = cv2.INTER_CUBIC
|
||||
elif active_image.TLM_ImageProperties.tlm_image_scale_method == "Lanczos":
|
||||
interp = cv2.INTER_LANCZOS4
|
||||
|
||||
resized = cv2.resize(basefile, dim, interpolation = interp)
|
||||
|
||||
#resizedFile = os.path.join(dir_path, basename + "_" + str(width) + "_" + str(height) + extension)
|
||||
resizedFile = os.path.join(dir_path, basename + extension)
|
||||
|
||||
cv2.imwrite(resizedFile, resized)
|
||||
|
||||
active_image.filepath_raw = resizedFile
|
||||
bpy.ops.image.reload()
|
||||
|
||||
print(newfile)
|
||||
print(img_path)
|
||||
|
||||
else:
|
||||
|
||||
print("Please save image")
|
||||
|
||||
print("Upscale")
|
||||
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
class TLM_ImageDownscale(bpy.types.Operator):
|
||||
bl_idname = "tlm.image_downscale"
|
||||
bl_label = "Downscale image"
|
||||
bl_description = "Downscales the image to double resolution"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
|
||||
cv2 = importlib.util.find_spec("cv2")
|
||||
|
||||
if cv2 is None:
|
||||
print("CV2 not found - Ignoring filtering")
|
||||
return 0
|
||||
else:
|
||||
cv2 = importlib.__import__("cv2")
|
||||
|
||||
for area in bpy.context.screen.areas:
|
||||
if area.type == "IMAGE_EDITOR":
|
||||
active_image = area.spaces.active.image
|
||||
|
||||
if active_image.source == "FILE":
|
||||
img_path = active_image.filepath_raw
|
||||
filename = os.path.basename(img_path)
|
||||
|
||||
basename = os.path.splitext(filename)[0]
|
||||
extension = os.path.splitext(filename)[1]
|
||||
|
||||
size_x = active_image.size[0]
|
||||
size_y = active_image.size[1]
|
||||
|
||||
dir_path = os.path.dirname(os.path.realpath(img_path))
|
||||
|
||||
#newfile = os.path.join(dir_path, basename + "_" + str(size_x) + "_" + str(size_y) + extension)
|
||||
newfile = os.path.join(dir_path, basename + extension)
|
||||
os.rename(img_path, newfile)
|
||||
|
||||
basefile = cv2.imread(newfile, cv2.IMREAD_UNCHANGED)
|
||||
|
||||
scale_percent = 50 # percent of original size
|
||||
width = int(basefile.shape[1] * scale_percent / 100)
|
||||
height = int(basefile.shape[0] * scale_percent / 100)
|
||||
dim = (width, height)
|
||||
|
||||
if dim[0] > 1 or dim[1] > 1:
|
||||
|
||||
if active_image.TLM_ImageProperties.tlm_image_scale_method == "Nearest":
|
||||
interp = cv2.INTER_NEAREST
|
||||
elif active_image.TLM_ImageProperties.tlm_image_scale_method == "Area":
|
||||
interp = cv2.INTER_AREA
|
||||
elif active_image.TLM_ImageProperties.tlm_image_scale_method == "Linear":
|
||||
interp = cv2.INTER_LINEAR
|
||||
elif active_image.TLM_ImageProperties.tlm_image_scale_method == "Cubic":
|
||||
interp = cv2.INTER_CUBIC
|
||||
elif active_image.TLM_ImageProperties.tlm_image_scale_method == "Lanczos":
|
||||
interp = cv2.INTER_LANCZOS4
|
||||
|
||||
resized = cv2.resize(basefile, dim, interpolation = interp)
|
||||
|
||||
#resizedFile = os.path.join(dir_path, basename + "_" + str(width) + "_" + str(height) + extension)
|
||||
resizedFile = os.path.join(dir_path, basename + extension)
|
||||
|
||||
cv2.imwrite(resizedFile, resized)
|
||||
|
||||
active_image.filepath_raw = resizedFile
|
||||
bpy.ops.image.reload()
|
||||
|
||||
print(newfile)
|
||||
print(img_path)
|
||||
|
||||
else:
|
||||
|
||||
print("Please save image")
|
||||
|
||||
print("Upscale")
|
||||
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
class TLM_ImageSwitchUp(bpy.types.Operator):
|
||||
bl_idname = "tlm.image_switchup"
|
||||
bl_label = "Quickswitch Up"
|
||||
bl_description = "Switches to a cached upscaled image"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
|
||||
for area in bpy.context.screen.areas:
|
||||
if area.type == "IMAGE_EDITOR":
|
||||
active_image = area.spaces.active.image
|
||||
|
||||
if active_image.source == "FILE":
|
||||
img_path = active_image.filepath_raw
|
||||
filename = os.path.basename(img_path)
|
||||
|
||||
print("Switch up")
|
||||
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
class TLM_ImageSwitchDown(bpy.types.Operator):
|
||||
bl_idname = "tlm.image_switchdown"
|
||||
bl_label = "Quickswitch Down"
|
||||
bl_description = "Switches to a cached downscaled image"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
|
||||
for area in bpy.context.screen.areas:
|
||||
if area.type == "IMAGE_EDITOR":
|
||||
active_image = area.spaces.active.image
|
||||
|
||||
if active_image.source == "FILE":
|
||||
img_path = active_image.filepath_raw
|
||||
filename = os.path.basename(img_path)
|
||||
|
||||
print("Switch Down")
|
||||
|
||||
return {'RUNNING_MODAL'}
|
81
leenkx/blender/lnx/lightmapper/operators/installopencv.py
Normal file
@ -0,0 +1,81 @@
|
||||
import bpy, math, os, platform, subprocess, sys, re, shutil
|
||||
|
||||
def ShowMessageBox(message = "", title = "Message Box", icon = 'INFO'):
|
||||
|
||||
def draw(self, context):
|
||||
self.layout.label(text=message)
|
||||
|
||||
bpy.context.window_manager.popup_menu(draw, title = title, icon = icon)
|
||||
|
||||
class TLM_Install_OpenCV(bpy.types.Operator):
|
||||
"""Install OpenCV"""
|
||||
bl_idname = "tlm.install_opencv_lightmaps"
|
||||
bl_label = "Install OpenCV"
|
||||
bl_description = "Install OpenCV"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
scene = context.scene
|
||||
cycles = bpy.data.scenes[scene.name].cycles
|
||||
|
||||
print("Module OpenCV")
|
||||
|
||||
if (2, 91, 0) > bpy.app.version:
|
||||
pythonbinpath = bpy.app.binary_path_python
|
||||
else:
|
||||
pythonbinpath = sys.executable
|
||||
|
||||
if platform.system() == "Windows":
|
||||
pythonlibpath = os.path.join(os.path.dirname(os.path.dirname(pythonbinpath)), "lib")
|
||||
else:
|
||||
pythonlibpath = os.path.join(os.path.dirname(os.path.dirname(pythonbinpath)), "lib", os.path.basename(pythonbinpath))
|
||||
|
||||
ensurepippath = os.path.join(pythonlibpath, "ensurepip")
|
||||
|
||||
cmda = [pythonbinpath, ensurepippath, "--upgrade", "--user"]
|
||||
pip = subprocess.run(cmda)
|
||||
cmdc = [pythonbinpath, "-m", "pip", "install", "--upgrade", "pip"]
|
||||
pipc = subprocess.run(cmdc)
|
||||
|
||||
if pip.returncode == 0:
|
||||
print("Sucessfully installed pip!\n")
|
||||
else:
|
||||
|
||||
try:
|
||||
import pip
|
||||
module_pip = True
|
||||
except ImportError:
|
||||
#pip
|
||||
module_pip = False
|
||||
|
||||
if not module_pip:
|
||||
print("Failed to install pip!\n")
|
||||
if platform.system() == "Windows":
|
||||
ShowMessageBox("Failed to install pip - Please start Blender as administrator", "Restart", 'PREFERENCES')
|
||||
else:
|
||||
ShowMessageBox("Failed to install pip - Try starting Blender with SUDO", "Restart", 'PREFERENCES')
|
||||
return{'FINISHED'}
|
||||
|
||||
cmdb = [pythonbinpath, "-m", "pip", "install", "opencv-python"]
|
||||
|
||||
#opencv = subprocess.run(cmdb, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
opencv = subprocess.run(cmdb)
|
||||
|
||||
if opencv.returncode == 0:
|
||||
print("Successfully installed OpenCV!\n")
|
||||
else:
|
||||
print("Failed to install OpenCV!\n")
|
||||
|
||||
if platform.system() == "Windows":
|
||||
ShowMessageBox("Failed to install opencv - Please start Blender as administrator", "Restart", 'PREFERENCES')
|
||||
else:
|
||||
ShowMessageBox("Failed to install opencv - Try starting Blender with SUDO", "Restart", 'PREFERENCES')
|
||||
|
||||
return{'FINISHED'}
|
||||
|
||||
module_opencv = True
|
||||
print("Sucessfully installed OpenCV!\n")
|
||||
ShowMessageBox("Please restart blender to enable OpenCV filtering", "Restart", 'PREFERENCES')
|
||||
|
||||
return{'FINISHED'}
|
1731
leenkx/blender/lnx/lightmapper/operators/tlm.py
Normal file
0
leenkx/blender/lnx/lightmapper/panels/__init__.py
Normal file
66
leenkx/blender/lnx/lightmapper/panels/image.py
Normal file
@ -0,0 +1,66 @@
|
||||
import bpy, os, math, importlib
|
||||
|
||||
from bpy.types import Menu, Operator, Panel, UIList
|
||||
|
||||
from bpy.props import (
|
||||
StringProperty,
|
||||
BoolProperty,
|
||||
IntProperty,
|
||||
FloatProperty,
|
||||
FloatVectorProperty,
|
||||
EnumProperty,
|
||||
PointerProperty,
|
||||
)
|
||||
|
||||
class TLM_PT_Imagetools(bpy.types.Panel):
|
||||
bl_label = "TLM Imagetools"
|
||||
bl_space_type = "IMAGE_EDITOR"
|
||||
bl_region_type = 'UI'
|
||||
bl_category = "TLM Imagetools"
|
||||
|
||||
def draw_header(self, _):
|
||||
layout = self.layout
|
||||
row = layout.row(align=True)
|
||||
row.label(text ="Image Tools")
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
activeImg = None
|
||||
|
||||
for area in bpy.context.screen.areas:
|
||||
if area.type == 'IMAGE_EDITOR':
|
||||
activeImg = area.spaces.active.image
|
||||
|
||||
if activeImg is not None and activeImg.name != "Render Result" and activeImg.name != "Viewer Node":
|
||||
|
||||
cv2 = importlib.util.find_spec("cv2")
|
||||
|
||||
if cv2 is None:
|
||||
row = layout.row(align=True)
|
||||
row.label(text ="OpenCV not installed.")
|
||||
else:
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text ="Method")
|
||||
row = layout.row(align=True)
|
||||
row.prop(activeImg.TLM_ImageProperties, "tlm_image_scale_engine")
|
||||
row = layout.row(align=True)
|
||||
row.prop(activeImg.TLM_ImageProperties, "tlm_image_cache_switch")
|
||||
row = layout.row(align=True)
|
||||
row.operator("tlm.image_upscale")
|
||||
if activeImg.TLM_ImageProperties.tlm_image_cache_switch:
|
||||
row = layout.row(align=True)
|
||||
row.label(text ="Switch up.")
|
||||
row = layout.row(align=True)
|
||||
row.operator("tlm.image_downscale")
|
||||
if activeImg.TLM_ImageProperties.tlm_image_cache_switch:
|
||||
row = layout.row(align=True)
|
||||
row.label(text ="Switch down.")
|
||||
if activeImg.TLM_ImageProperties.tlm_image_scale_engine == "OpenCV":
|
||||
row = layout.row(align=True)
|
||||
row.prop(activeImg.TLM_ImageProperties, "tlm_image_scale_method")
|
||||
|
||||
else:
|
||||
row = layout.row(align=True)
|
||||
row.label(text ="Select an image")
|
17
leenkx/blender/lnx/lightmapper/panels/light.py
Normal file
@ -0,0 +1,17 @@
|
||||
import bpy
|
||||
from bpy.props import *
|
||||
from bpy.types import Menu, Panel
|
||||
|
||||
class TLM_PT_LightMenu(bpy.types.Panel):
|
||||
bl_label = "The Lightmapper"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "light"
|
||||
bl_options = {'DEFAULT_CLOSED'}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
scene = context.scene
|
||||
obj = bpy.context.object
|
||||
layout.use_property_split = True
|
||||
layout.use_property_decorate = False
|
126
leenkx/blender/lnx/lightmapper/panels/object.py
Normal file
@ -0,0 +1,126 @@
|
||||
import bpy
|
||||
from bpy.props import *
|
||||
from bpy.types import Menu, Panel
|
||||
|
||||
class TLM_PT_ObjectMenu(bpy.types.Panel):
|
||||
bl_label = "The Lightmapper"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "object"
|
||||
bl_options = {'DEFAULT_CLOSED'}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
scene = context.scene
|
||||
obj = bpy.context.object
|
||||
layout.use_property_split = True
|
||||
layout.use_property_decorate = False
|
||||
|
||||
if obj.type == "MESH":
|
||||
row = layout.row(align=True)
|
||||
row.prop(obj.TLM_ObjectProperties, "tlm_mesh_lightmap_use")
|
||||
|
||||
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
|
||||
|
||||
row = layout.row()
|
||||
row.prop(obj.TLM_ObjectProperties, "tlm_use_default_channel")
|
||||
|
||||
if not obj.TLM_ObjectProperties.tlm_use_default_channel:
|
||||
|
||||
row = layout.row()
|
||||
row.prop_search(obj.TLM_ObjectProperties, "tlm_uv_channel", obj.data, "uv_layers", text='UV Channel')
|
||||
|
||||
row = layout.row()
|
||||
row.prop(obj.TLM_ObjectProperties, "tlm_mesh_lightmap_resolution")
|
||||
if obj.TLM_ObjectProperties.tlm_use_default_channel:
|
||||
row = layout.row()
|
||||
row.prop(obj.TLM_ObjectProperties, "tlm_mesh_lightmap_unwrap_mode")
|
||||
row = layout.row()
|
||||
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA":
|
||||
|
||||
if scene.TLM_AtlasListItem >= 0 and len(scene.TLM_AtlasList) > 0:
|
||||
row = layout.row()
|
||||
item = scene.TLM_AtlasList[scene.TLM_AtlasListItem]
|
||||
row.prop_search(obj.TLM_ObjectProperties, "tlm_atlas_pointer", scene, "TLM_AtlasList", text='Atlas Group')
|
||||
row = layout.row()
|
||||
else:
|
||||
row = layout.label(text="Add Atlas Groups from the scene lightmapping settings.")
|
||||
row = layout.row()
|
||||
|
||||
else:
|
||||
row = layout.row()
|
||||
row.prop(obj.TLM_ObjectProperties, "tlm_postpack_object")
|
||||
row = layout.row()
|
||||
|
||||
|
||||
if obj.TLM_ObjectProperties.tlm_postpack_object and obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode != "AtlasGroupA":
|
||||
if scene.TLM_PostAtlasListItem >= 0 and len(scene.TLM_PostAtlasList) > 0:
|
||||
row = layout.row()
|
||||
item = scene.TLM_PostAtlasList[scene.TLM_PostAtlasListItem]
|
||||
row.prop_search(obj.TLM_ObjectProperties, "tlm_postatlas_pointer", scene, "TLM_PostAtlasList", text='Atlas Group')
|
||||
row = layout.row()
|
||||
|
||||
else:
|
||||
row = layout.label(text="Add Atlas Groups from the scene lightmapping settings.")
|
||||
row = layout.row()
|
||||
|
||||
row.prop(obj.TLM_ObjectProperties, "tlm_mesh_unwrap_margin")
|
||||
row = layout.row()
|
||||
row.prop(obj.TLM_ObjectProperties, "tlm_mesh_filter_override")
|
||||
row = layout.row()
|
||||
if obj.TLM_ObjectProperties.tlm_mesh_filter_override:
|
||||
row = layout.row(align=True)
|
||||
row.prop(obj.TLM_ObjectProperties, "tlm_mesh_filtering_mode")
|
||||
row = layout.row(align=True)
|
||||
if obj.TLM_ObjectProperties.tlm_mesh_filtering_mode == "Gaussian":
|
||||
row.prop(obj.TLM_ObjectProperties, "tlm_mesh_filtering_gaussian_strength")
|
||||
row = layout.row(align=True)
|
||||
row.prop(obj.TLM_ObjectProperties, "tlm_mesh_filtering_iterations")
|
||||
elif obj.TLM_ObjectProperties.tlm_mesh_filtering_mode == "Box":
|
||||
row.prop(obj.TLM_ObjectProperties, "tlm_mesh_filtering_box_strength")
|
||||
row = layout.row(align=True)
|
||||
row.prop(obj.TLM_ObjectProperties, "tlm_mesh_filtering_iterations")
|
||||
elif obj.TLM_ObjectProperties.tlm_mesh_filtering_mode == "Bilateral":
|
||||
row.prop(obj.TLM_ObjectProperties, "tlm_mesh_filtering_bilateral_diameter")
|
||||
row = layout.row(align=True)
|
||||
row.prop(obj.TLM_ObjectProperties, "tlm_mesh_filtering_bilateral_color_deviation")
|
||||
row = layout.row(align=True)
|
||||
row.prop(obj.TLM_ObjectProperties, "tlm_mesh_filtering_bilateral_coordinate_deviation")
|
||||
row = layout.row(align=True)
|
||||
row.prop(obj.TLM_ObjectProperties, "tlm_mesh_filtering_iterations")
|
||||
else:
|
||||
row.prop(obj.TLM_ObjectProperties, "tlm_mesh_filtering_median_kernel", expand=True)
|
||||
row = layout.row(align=True)
|
||||
row.prop(obj.TLM_ObjectProperties, "tlm_mesh_filtering_iterations")
|
||||
|
||||
#If UV Packer installed
|
||||
if "UV-Packer" in bpy.context.preferences.addons.keys():
|
||||
row.prop(obj.TLM_ObjectProperties, "tlm_use_uv_packer")
|
||||
if obj.TLM_ObjectProperties.tlm_use_uv_packer:
|
||||
row = layout.row(align=True)
|
||||
row.prop(obj.TLM_ObjectProperties, "tlm_uv_packer_padding")
|
||||
row = layout.row(align=True)
|
||||
row.prop(obj.TLM_ObjectProperties, "tlm_uv_packer_packing_engine")
|
||||
|
||||
class TLM_PT_MaterialMenu(bpy.types.Panel):
|
||||
bl_label = "The Lightmapper"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "material"
|
||||
bl_options = {'DEFAULT_CLOSED'}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
scene = context.scene
|
||||
obj = bpy.context.object
|
||||
layout.use_property_split = True
|
||||
layout.use_property_decorate = False
|
||||
|
||||
mat = bpy.context.material
|
||||
if mat == None:
|
||||
return
|
||||
|
||||
if obj.type == "MESH":
|
||||
|
||||
row = layout.row()
|
||||
row.prop(mat, "TLM_ignore")
|
756
leenkx/blender/lnx/lightmapper/panels/scene.py
Normal file
@ -0,0 +1,756 @@
|
||||
import bpy, importlib, math
|
||||
from bpy.props import *
|
||||
from bpy.types import Menu, Panel
|
||||
from .. utility import icon
|
||||
from .. properties.denoiser import oidn, optix
|
||||
|
||||
class TLM_PT_Panel(bpy.types.Panel):
|
||||
bl_label = "The Lightmapper"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "render"
|
||||
bl_options = {'DEFAULT_CLOSED'}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
scene = context.scene
|
||||
layout.use_property_split = True
|
||||
layout.use_property_decorate = False
|
||||
sceneProperties = scene.TLM_SceneProperties
|
||||
|
||||
class TLM_PT_Groups(bpy.types.Panel):
|
||||
bl_label = "Lightmap Groups"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "render"
|
||||
bl_options = {'DEFAULT_CLOSED'}
|
||||
bl_parent_id = "TLM_PT_Panel"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
scene = context.scene
|
||||
layout.use_property_split = True
|
||||
layout.use_property_decorate = False
|
||||
sceneProperties = scene.TLM_SceneProperties
|
||||
|
||||
if sceneProperties.tlm_lightmap_engine == "Cycles":
|
||||
|
||||
rows = 2
|
||||
#if len(atlasList) > 1:
|
||||
# rows = 4
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="Lightmap Group List")
|
||||
row = layout.row(align=True)
|
||||
row.template_list("TLM_UL_GroupList", "Lightmap Groups", scene, "TLM_GroupList", scene, "TLM_GroupListItem", rows=rows)
|
||||
col = row.column(align=True)
|
||||
col.operator("tlm_atlaslist.new_item", icon='ADD', text="")
|
||||
#col.operator("tlm_atlaslist.delete_item", icon='REMOVE', text="")
|
||||
#col.menu("TLM_MT_AtlasListSpecials", icon='DOWNARROW_HLT', text="")
|
||||
|
||||
class TLM_PT_Settings(bpy.types.Panel):
|
||||
bl_label = "Settings"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "render"
|
||||
bl_options = {'DEFAULT_CLOSED'}
|
||||
bl_parent_id = "LNX_PT_BakePanel"
|
||||
|
||||
@classmethod
|
||||
def poll(self, context):
|
||||
scene = context.scene
|
||||
return scene.lnx_bakemode == "Lightmap"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
scene = context.scene
|
||||
layout.use_property_split = True
|
||||
layout.use_property_decorate = False
|
||||
sceneProperties = scene.TLM_SceneProperties
|
||||
|
||||
row = layout.row(align=True)
|
||||
|
||||
#We list LuxCoreRender as available, by default we assume Cycles exists
|
||||
row.prop(sceneProperties, "tlm_lightmap_engine")
|
||||
|
||||
if sceneProperties.tlm_lightmap_engine == "Cycles":
|
||||
|
||||
#CYCLES SETTINGS HERE
|
||||
engineProperties = scene.TLM_EngineProperties
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="General Settings")
|
||||
row = layout.row(align=True)
|
||||
row.operator("tlm.build_lightmaps")
|
||||
row = layout.row(align=True)
|
||||
row.operator("tlm.clean_lightmaps")
|
||||
row = layout.row(align=True)
|
||||
row.operator("tlm.explore_lightmaps")
|
||||
row = layout.row(align=True)
|
||||
row.prop(sceneProperties, "tlm_apply_on_unwrap")
|
||||
row = layout.row(align=True)
|
||||
row.prop(sceneProperties, "tlm_headless")
|
||||
row = layout.row(align=True)
|
||||
row.prop(sceneProperties, "tlm_alert_on_finish")
|
||||
|
||||
if sceneProperties.tlm_alert_on_finish:
|
||||
row = layout.row(align=True)
|
||||
row.prop(sceneProperties, "tlm_alert_sound")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(sceneProperties, "tlm_verbose")
|
||||
#row = layout.row(align=True)
|
||||
#row.prop(sceneProperties, "tlm_compile_statistics")
|
||||
row = layout.row(align=True)
|
||||
row.prop(sceneProperties, "tlm_override_bg_color")
|
||||
if sceneProperties.tlm_override_bg_color:
|
||||
row = layout.row(align=True)
|
||||
row.prop(sceneProperties, "tlm_override_color")
|
||||
row = layout.row(align=True)
|
||||
row.prop(sceneProperties, "tlm_reset_uv")
|
||||
row = layout.row(align=True)
|
||||
row.prop(sceneProperties, "tlm_apply_modifiers")
|
||||
row = layout.row(align=True)
|
||||
row.prop(sceneProperties, "tlm_keep_baked_files")
|
||||
row = layout.row(align=True)
|
||||
row.prop(sceneProperties, "tlm_repartition_on_clean")
|
||||
row = layout.row(align=True)
|
||||
row.prop(sceneProperties, "tlm_save_preprocess_lightmaps")
|
||||
|
||||
row = layout.row(align=True)
|
||||
try:
|
||||
if bpy.context.scene["TLM_Buildstat"] is not None:
|
||||
row.label(text="Last build completed in: " + str(bpy.context.scene["TLM_Buildstat"][0]))
|
||||
except:
|
||||
pass
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="Cycles Settings")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(engineProperties, "tlm_mode")
|
||||
row = layout.row(align=True)
|
||||
row.prop(engineProperties, "tlm_quality")
|
||||
row = layout.row(align=True)
|
||||
row.prop(engineProperties, "tlm_resolution_scale")
|
||||
row = layout.row(align=True)
|
||||
row.prop(engineProperties, "tlm_bake_mode")
|
||||
row = layout.row(align=True)
|
||||
row.prop(engineProperties, "tlm_target")
|
||||
row = layout.row(align=True)
|
||||
row.prop(engineProperties, "tlm_lighting_mode")
|
||||
# if scene.TLM_EngineProperties.tlm_lighting_mode == "combinedao" or scene.TLM_EngineProperties.tlm_lighting_mode == "indirectao":
|
||||
# row = layout.row(align=True)
|
||||
# row.prop(engineProperties, "tlm_premultiply_ao")
|
||||
if scene.TLM_EngineProperties.tlm_bake_mode == "Background":
|
||||
row = layout.row(align=True)
|
||||
row.label(text="Warning! Background mode is currently unstable", icon_value=2)
|
||||
row = layout.row(align=True)
|
||||
row.prop(sceneProperties, "tlm_network_render")
|
||||
if sceneProperties.tlm_network_render:
|
||||
row = layout.row(align=True)
|
||||
row.prop(sceneProperties, "tlm_network_paths")
|
||||
#row = layout.row(align=True)
|
||||
#row.prop(sceneProperties, "tlm_network_dir")
|
||||
row = layout.row(align=True)
|
||||
row.prop(engineProperties, "tlm_caching_mode")
|
||||
row = layout.row(align=True)
|
||||
row.prop(engineProperties, "tlm_directional_mode")
|
||||
row = layout.row(align=True)
|
||||
row.prop(engineProperties, "tlm_lightmap_savedir")
|
||||
row = layout.row(align=True)
|
||||
row.prop(engineProperties, "tlm_dilation_margin")
|
||||
row = layout.row(align=True)
|
||||
row.prop(engineProperties, "tlm_exposure_multiplier")
|
||||
row = layout.row(align=True)
|
||||
row.prop(engineProperties, "tlm_setting_supersample")
|
||||
row = layout.row(align=True)
|
||||
row.prop(sceneProperties, "tlm_metallic_clamp")
|
||||
row = layout.row(align=True)
|
||||
row.prop(sceneProperties, "tlm_texture_interpolation")
|
||||
row = layout.row(align=True)
|
||||
row.prop(sceneProperties, "tlm_texture_extrapolation")
|
||||
|
||||
|
||||
|
||||
# elif sceneProperties.tlm_lightmap_engine == "LuxCoreRender":
|
||||
|
||||
# engineProperties = scene.TLM_Engine2Properties
|
||||
# row = layout.row(align=True)
|
||||
# row.prop(engineProperties, "tlm_luxcore_dir")
|
||||
# row = layout.row(align=True)
|
||||
# row.operator("tlm.build_lightmaps")
|
||||
# #LUXCORE SETTINGS HERE
|
||||
# #luxcore_available = False
|
||||
|
||||
# #Look for Luxcorerender in the renderengine classes
|
||||
# # for engine in bpy.types.RenderEngine.__subclasses__():
|
||||
# # if engine.bl_idname == "LUXCORE":
|
||||
# # luxcore_available = True
|
||||
# # break
|
||||
|
||||
# # row = layout.row(align=True)
|
||||
# # if not luxcore_available:
|
||||
# # row.label(text="Please install BlendLuxCore.")
|
||||
# # else:
|
||||
# # row.label(text="LuxCoreRender not yet available.")
|
||||
|
||||
elif sceneProperties.tlm_lightmap_engine == "OctaneRender":
|
||||
|
||||
engineProperties = scene.TLM_Engine3Properties
|
||||
|
||||
#LUXCORE SETTINGS HERE
|
||||
octane_available = True
|
||||
|
||||
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("tlm.build_lightmaps")
|
||||
row = layout.row(align=True)
|
||||
row.operator("tlm.clean_lightmaps")
|
||||
row = layout.row(align=True)
|
||||
row.operator("tlm.explore_lightmaps")
|
||||
row = layout.row(align=True)
|
||||
row.prop(sceneProperties, "tlm_verbose")
|
||||
row = layout.row(align=True)
|
||||
row.prop(engineProperties, "tlm_lightmap_savedir")
|
||||
row = layout.row(align=True)
|
||||
|
||||
class TLM_PT_Denoise(bpy.types.Panel):
|
||||
bl_label = "Denoise"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "render"
|
||||
bl_options = {'DEFAULT_CLOSED'}
|
||||
bl_parent_id = "LNX_PT_BakePanel"
|
||||
|
||||
@classmethod
|
||||
def poll(self, context):
|
||||
scene = context.scene
|
||||
return scene.lnx_bakemode == "Lightmap"
|
||||
|
||||
def draw_header(self, context):
|
||||
scene = context.scene
|
||||
sceneProperties = scene.TLM_SceneProperties
|
||||
self.layout.prop(sceneProperties, "tlm_denoise_use", text="")
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
scene = context.scene
|
||||
layout.use_property_split = True
|
||||
layout.use_property_decorate = False
|
||||
sceneProperties = scene.TLM_SceneProperties
|
||||
layout.active = sceneProperties.tlm_denoise_use
|
||||
|
||||
row = layout.row(align=True)
|
||||
|
||||
#row.prop(sceneProperties, "tlm_denoiser", expand=True)
|
||||
#row = layout.row(align=True)
|
||||
row.prop(sceneProperties, "tlm_denoise_engine", expand=True)
|
||||
row = layout.row(align=True)
|
||||
|
||||
if sceneProperties.tlm_denoise_engine == "Integrated":
|
||||
row.label(text="No options for Integrated.")
|
||||
elif sceneProperties.tlm_denoise_engine == "OIDN":
|
||||
denoiseProperties = scene.TLM_OIDNEngineProperties
|
||||
row.prop(denoiseProperties, "tlm_oidn_path")
|
||||
row = layout.row(align=True)
|
||||
row.prop(denoiseProperties, "tlm_oidn_verbose")
|
||||
row = layout.row(align=True)
|
||||
row.prop(denoiseProperties, "tlm_oidn_threads")
|
||||
row = layout.row(align=True)
|
||||
row.prop(denoiseProperties, "tlm_oidn_maxmem")
|
||||
row = layout.row(align=True)
|
||||
row.prop(denoiseProperties, "tlm_oidn_affinity")
|
||||
# row = layout.row(align=True)
|
||||
# row.prop(denoiseProperties, "tlm_denoise_ao")
|
||||
elif sceneProperties.tlm_denoise_engine == "Optix":
|
||||
denoiseProperties = scene.TLM_OptixEngineProperties
|
||||
row.prop(denoiseProperties, "tlm_optix_path")
|
||||
row = layout.row(align=True)
|
||||
row.prop(denoiseProperties, "tlm_optix_verbose")
|
||||
row = layout.row(align=True)
|
||||
row.prop(denoiseProperties, "tlm_optix_maxmem")
|
||||
#row = layout.row(align=True)
|
||||
#row.prop(denoiseProperties, "tlm_denoise_ao")
|
||||
|
||||
class TLM_PT_Filtering(bpy.types.Panel):
|
||||
bl_label = "Filtering"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "render"
|
||||
bl_options = {'DEFAULT_CLOSED'}
|
||||
bl_parent_id = "LNX_PT_BakePanel"
|
||||
|
||||
@classmethod
|
||||
def poll(self, context):
|
||||
scene = context.scene
|
||||
return scene.lnx_bakemode == "Lightmap"
|
||||
|
||||
def draw_header(self, context):
|
||||
scene = context.scene
|
||||
sceneProperties = scene.TLM_SceneProperties
|
||||
self.layout.prop(sceneProperties, "tlm_filtering_use", text="")
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
scene = context.scene
|
||||
layout.use_property_split = True
|
||||
layout.use_property_decorate = False
|
||||
sceneProperties = scene.TLM_SceneProperties
|
||||
layout.active = sceneProperties.tlm_filtering_use
|
||||
#row = layout.row(align=True)
|
||||
#row.label(text="TODO MAKE CHECK")
|
||||
#row = layout.row(align=True)
|
||||
#row.prop(sceneProperties, "tlm_filtering_engine", expand=True)
|
||||
row = layout.row(align=True)
|
||||
|
||||
if sceneProperties.tlm_filtering_engine == "OpenCV":
|
||||
|
||||
cv2 = importlib.util.find_spec("cv2")
|
||||
|
||||
if cv2 is None:
|
||||
row = layout.row(align=True)
|
||||
row.label(text="OpenCV is not installed. Install it through preferences.")
|
||||
else:
|
||||
row = layout.row(align=True)
|
||||
row.prop(scene.TLM_SceneProperties, "tlm_filtering_mode")
|
||||
row = layout.row(align=True)
|
||||
if scene.TLM_SceneProperties.tlm_filtering_mode == "Gaussian":
|
||||
row.prop(scene.TLM_SceneProperties, "tlm_filtering_gaussian_strength")
|
||||
row = layout.row(align=True)
|
||||
row.prop(scene.TLM_SceneProperties, "tlm_filtering_iterations")
|
||||
elif scene.TLM_SceneProperties.tlm_filtering_mode == "Box":
|
||||
row.prop(scene.TLM_SceneProperties, "tlm_filtering_box_strength")
|
||||
row = layout.row(align=True)
|
||||
row.prop(scene.TLM_SceneProperties, "tlm_filtering_iterations")
|
||||
|
||||
elif scene.TLM_SceneProperties.tlm_filtering_mode == "Bilateral":
|
||||
row.prop(scene.TLM_SceneProperties, "tlm_filtering_bilateral_diameter")
|
||||
row = layout.row(align=True)
|
||||
row.prop(scene.TLM_SceneProperties, "tlm_filtering_bilateral_color_deviation")
|
||||
row = layout.row(align=True)
|
||||
row.prop(scene.TLM_SceneProperties, "tlm_filtering_bilateral_coordinate_deviation")
|
||||
row = layout.row(align=True)
|
||||
row.prop(scene.TLM_SceneProperties, "tlm_filtering_iterations")
|
||||
else:
|
||||
row.prop(scene.TLM_SceneProperties, "tlm_filtering_median_kernel", expand=True)
|
||||
row = layout.row(align=True)
|
||||
row.prop(scene.TLM_SceneProperties, "tlm_filtering_iterations")
|
||||
else:
|
||||
row = layout.row(align=True)
|
||||
row.prop(scene.TLM_SceneProperties, "tlm_numpy_filtering_mode")
|
||||
|
||||
|
||||
class TLM_PT_Encoding(bpy.types.Panel):
|
||||
bl_label = "Encoding"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "render"
|
||||
bl_options = {'DEFAULT_CLOSED'}
|
||||
bl_parent_id = "LNX_PT_BakePanel"
|
||||
|
||||
@classmethod
|
||||
def poll(self, context):
|
||||
scene = context.scene
|
||||
return scene.lnx_bakemode == "Lightmap"
|
||||
|
||||
def draw_header(self, context):
|
||||
scene = context.scene
|
||||
sceneProperties = scene.TLM_SceneProperties
|
||||
self.layout.prop(sceneProperties, "tlm_encoding_use", text="")
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
scene = context.scene
|
||||
layout.use_property_split = True
|
||||
layout.use_property_decorate = False
|
||||
sceneProperties = scene.TLM_SceneProperties
|
||||
layout.active = sceneProperties.tlm_encoding_use
|
||||
|
||||
sceneProperties = scene.TLM_SceneProperties
|
||||
row = layout.row(align=True)
|
||||
|
||||
if scene.TLM_EngineProperties.tlm_bake_mode == "Background":
|
||||
row.label(text="Encoding options disabled in background mode")
|
||||
row = layout.row(align=True)
|
||||
|
||||
else:
|
||||
|
||||
row.prop(sceneProperties, "tlm_encoding_device", expand=True)
|
||||
row = layout.row(align=True)
|
||||
|
||||
if sceneProperties.tlm_encoding_device == "CPU":
|
||||
row.prop(sceneProperties, "tlm_encoding_mode_a", expand=True)
|
||||
else:
|
||||
row.prop(sceneProperties, "tlm_encoding_mode_b", expand=True)
|
||||
|
||||
if sceneProperties.tlm_encoding_device == "CPU":
|
||||
if sceneProperties.tlm_encoding_mode_a == "RGBM":
|
||||
row = layout.row(align=True)
|
||||
row.prop(sceneProperties, "tlm_encoding_range")
|
||||
row = layout.row(align=True)
|
||||
row.prop(sceneProperties, "tlm_decoder_setup")
|
||||
if sceneProperties.tlm_encoding_mode_a == "RGBD":
|
||||
pass
|
||||
if sceneProperties.tlm_encoding_mode_a == "HDR":
|
||||
row = layout.row(align=True)
|
||||
row.prop(sceneProperties, "tlm_format")
|
||||
else:
|
||||
|
||||
if sceneProperties.tlm_encoding_mode_b == "RGBM":
|
||||
row = layout.row(align=True)
|
||||
row.prop(sceneProperties, "tlm_encoding_range")
|
||||
row = layout.row(align=True)
|
||||
row.prop(sceneProperties, "tlm_decoder_setup")
|
||||
|
||||
if sceneProperties.tlm_encoding_mode_b == "LogLuv" and sceneProperties.tlm_encoding_device == "GPU":
|
||||
row = layout.row(align=True)
|
||||
row.prop(sceneProperties, "tlm_decoder_setup")
|
||||
if sceneProperties.tlm_decoder_setup:
|
||||
row = layout.row(align=True)
|
||||
row.prop(sceneProperties, "tlm_split_premultiplied")
|
||||
if sceneProperties.tlm_encoding_mode_b == "HDR":
|
||||
row = layout.row(align=True)
|
||||
row.prop(sceneProperties, "tlm_format")
|
||||
|
||||
class TLM_PT_Utility(bpy.types.Panel):
|
||||
bl_label = "Utilities"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "render"
|
||||
bl_options = {'DEFAULT_CLOSED'}
|
||||
bl_parent_id = "LNX_PT_BakePanel"
|
||||
|
||||
@classmethod
|
||||
def poll(self, context):
|
||||
scene = context.scene
|
||||
return scene.lnx_bakemode == "Lightmap"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
scene = context.scene
|
||||
layout.use_property_split = True
|
||||
layout.use_property_decorate = False
|
||||
sceneProperties = scene.TLM_SceneProperties
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="Enable Lightmaps for set")
|
||||
row = layout.row(align=True)
|
||||
row.prop(sceneProperties, "tlm_utility_context")
|
||||
row = layout.row(align=True)
|
||||
|
||||
if sceneProperties.tlm_utility_context == "SetBatching":
|
||||
|
||||
row.operator("tlm.enable_set")
|
||||
row = layout.row(align=True)
|
||||
row.prop(sceneProperties, "tlm_utility_set")
|
||||
row = layout.row(align=True)
|
||||
#row.label(text="ABCD")
|
||||
row.prop(sceneProperties, "tlm_mesh_lightmap_unwrap_mode")
|
||||
|
||||
if sceneProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA":
|
||||
|
||||
if scene.TLM_AtlasListItem >= 0 and len(scene.TLM_AtlasList) > 0:
|
||||
row = layout.row()
|
||||
item = scene.TLM_AtlasList[scene.TLM_AtlasListItem]
|
||||
row.prop_search(sceneProperties, "tlm_atlas_pointer", scene, "TLM_AtlasList", text='Atlas Group')
|
||||
else:
|
||||
row = layout.label(text="Add Atlas Groups from the scene lightmapping settings.")
|
||||
|
||||
else:
|
||||
|
||||
row = layout.row()
|
||||
row.prop(sceneProperties, "tlm_postpack_object")
|
||||
row = layout.row()
|
||||
|
||||
if sceneProperties.tlm_postpack_object and sceneProperties.tlm_mesh_lightmap_unwrap_mode != "AtlasGroupA":
|
||||
|
||||
if scene.TLM_PostAtlasListItem >= 0 and len(scene.TLM_PostAtlasList) > 0:
|
||||
row = layout.row()
|
||||
item = scene.TLM_PostAtlasList[scene.TLM_PostAtlasListItem]
|
||||
row.prop_search(sceneProperties, "tlm_postatlas_pointer", scene, "TLM_PostAtlasList", text='Atlas Group')
|
||||
row = layout.row()
|
||||
|
||||
else:
|
||||
row = layout.label(text="Add Atlas Groups from the scene lightmapping settings.")
|
||||
row = layout.row()
|
||||
|
||||
row.prop(sceneProperties, "tlm_mesh_unwrap_margin")
|
||||
row = layout.row()
|
||||
row.prop(sceneProperties, "tlm_resolution_weight")
|
||||
|
||||
if sceneProperties.tlm_resolution_weight == "Single":
|
||||
row = layout.row()
|
||||
row.prop(sceneProperties, "tlm_mesh_lightmap_resolution")
|
||||
else:
|
||||
row = layout.row()
|
||||
row.prop(sceneProperties, "tlm_resolution_min")
|
||||
row = layout.row()
|
||||
row.prop(sceneProperties, "tlm_resolution_max")
|
||||
|
||||
row = layout.row()
|
||||
row.operator("tlm.disable_selection")
|
||||
row = layout.row(align=True)
|
||||
row.operator("tlm.select_lightmapped_objects")
|
||||
row = layout.row(align=True)
|
||||
row.operator("tlm.remove_uv_selection")
|
||||
|
||||
elif sceneProperties.tlm_utility_context == "EnvironmentProbes":
|
||||
|
||||
row.label(text="Environment Probes")
|
||||
row = layout.row()
|
||||
row.operator("tlm.build_environmentprobe")
|
||||
row = layout.row()
|
||||
row.operator("tlm.clean_environmentprobe")
|
||||
row = layout.row()
|
||||
row.prop(sceneProperties, "tlm_environment_probe_engine")
|
||||
row = layout.row()
|
||||
row.prop(sceneProperties, "tlm_cmft_path")
|
||||
row = layout.row()
|
||||
row.prop(sceneProperties, "tlm_environment_probe_resolution")
|
||||
row = layout.row()
|
||||
row.prop(sceneProperties, "tlm_create_spherical")
|
||||
|
||||
if sceneProperties.tlm_create_spherical:
|
||||
|
||||
row = layout.row()
|
||||
row.prop(sceneProperties, "tlm_invert_direction")
|
||||
row = layout.row()
|
||||
row.prop(sceneProperties, "tlm_write_sh")
|
||||
row = layout.row()
|
||||
row.prop(sceneProperties, "tlm_write_radiance")
|
||||
|
||||
elif sceneProperties.tlm_utility_context == "LoadLightmaps":
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="Load lightmaps")
|
||||
row = layout.row()
|
||||
row.prop(sceneProperties, "tlm_load_folder")
|
||||
row = layout.row()
|
||||
row.operator("tlm.load_lightmaps")
|
||||
row = layout.row()
|
||||
row.prop(sceneProperties, "tlm_load_atlas")
|
||||
|
||||
elif sceneProperties.tlm_utility_context == "MaterialAdjustment":
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(sceneProperties, "tlm_utility_set")
|
||||
row = layout.row(align=True)
|
||||
row.operator("tlm.disable_specularity")
|
||||
row.operator("tlm.disable_metallic")
|
||||
row = layout.row(align=True)
|
||||
row.prop(sceneProperties, "tlm_remove_met_spec_link")
|
||||
row = layout.row(align=True)
|
||||
row.operator("tlm.remove_empty_images")
|
||||
row = layout.row(align=True)
|
||||
|
||||
elif sceneProperties.tlm_utility_context == "NetworkRender":
|
||||
|
||||
row.label(text="Network Rendering")
|
||||
row = layout.row()
|
||||
row.operator("tlm.start_server")
|
||||
layout.label(text="Atlas Groups")
|
||||
|
||||
elif sceneProperties.tlm_utility_context == "TexelDensity":
|
||||
|
||||
row.label(text="Texel Density Utilies")
|
||||
row = layout.row()
|
||||
|
||||
elif sceneProperties.tlm_utility_context == "GLTFUtil":
|
||||
|
||||
row.label(text="GLTF material utilities")
|
||||
row = layout.row()
|
||||
row.operator("tlm.add_gltf_node")
|
||||
row = layout.row()
|
||||
row.operator("tlm.shift_multiply_links")
|
||||
|
||||
class TLM_PT_Selection(bpy.types.Panel):
|
||||
bl_label = "Selection"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "render"
|
||||
bl_options = {'DEFAULT_CLOSED'}
|
||||
bl_parent_id = "LNX_PT_BakePanel"
|
||||
|
||||
@classmethod
|
||||
def poll(self, context):
|
||||
scene = context.scene
|
||||
return scene.lnx_bakemode == "Lightmap"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
scene = context.scene
|
||||
layout.use_property_split = True
|
||||
layout.use_property_decorate = False
|
||||
sceneProperties = scene.TLM_SceneProperties
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("tlm.enable_selection")
|
||||
row = layout.row(align=True)
|
||||
row.operator("tlm.disable_selection")
|
||||
row = layout.row(align=True)
|
||||
row.prop(sceneProperties, "tlm_override_object_settings")
|
||||
|
||||
if sceneProperties.tlm_override_object_settings:
|
||||
|
||||
row = layout.row(align=True)
|
||||
row = layout.row()
|
||||
row.prop(sceneProperties, "tlm_mesh_lightmap_unwrap_mode")
|
||||
row = layout.row()
|
||||
|
||||
if sceneProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA":
|
||||
|
||||
if scene.TLM_AtlasListItem >= 0 and len(scene.TLM_AtlasList) > 0:
|
||||
row = layout.row()
|
||||
item = scene.TLM_AtlasList[scene.TLM_AtlasListItem]
|
||||
row.prop_search(sceneProperties, "tlm_atlas_pointer", scene, "TLM_AtlasList", text='Atlas Group')
|
||||
else:
|
||||
row = layout.label(text="Add Atlas Groups from the scene lightmapping settings.")
|
||||
|
||||
else:
|
||||
row = layout.row()
|
||||
row.prop(sceneProperties, "tlm_postpack_object")
|
||||
row = layout.row()
|
||||
|
||||
if sceneProperties.tlm_postpack_object and sceneProperties.tlm_mesh_lightmap_unwrap_mode != "AtlasGroupA":
|
||||
if scene.TLM_PostAtlasListItem >= 0 and len(scene.TLM_PostAtlasList) > 0:
|
||||
row = layout.row()
|
||||
item = scene.TLM_PostAtlasList[scene.TLM_PostAtlasListItem]
|
||||
row.prop_search(sceneProperties, "tlm_postatlas_pointer", scene, "TLM_PostAtlasList", text='Atlas Group')
|
||||
row = layout.row()
|
||||
|
||||
else:
|
||||
row = layout.label(text="Add Atlas Groups from the scene lightmapping settings.")
|
||||
row = layout.row()
|
||||
|
||||
if sceneProperties.tlm_mesh_lightmap_unwrap_mode != "AtlasGroupA":
|
||||
row.prop(sceneProperties, "tlm_mesh_lightmap_resolution")
|
||||
row = layout.row()
|
||||
row.prop(sceneProperties, "tlm_mesh_unwrap_margin")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("tlm.remove_uv_selection")
|
||||
row = layout.row(align=True)
|
||||
row.operator("tlm.select_lightmapped_objects")
|
||||
# row = layout.row(align=True)
|
||||
# for addon in bpy.context.preferences.addons.keys():
|
||||
# if addon.startswith("Texel_Density"):
|
||||
# row.operator("tlm.toggle_texel_density")
|
||||
# row = layout.row(align=True)
|
||||
|
||||
class TLM_PT_Additional(bpy.types.Panel):
|
||||
bl_label = "Additional"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "render"
|
||||
bl_options = {'DEFAULT_CLOSED'}
|
||||
bl_parent_id = "LNX_PT_BakePanel"
|
||||
|
||||
@classmethod
|
||||
def poll(self, context):
|
||||
scene = context.scene
|
||||
return scene.lnx_bakemode == "Lightmap"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
scene = context.scene
|
||||
sceneProperties = scene.TLM_SceneProperties
|
||||
atlasListItem = scene.TLM_AtlasListItem
|
||||
atlasList = scene.TLM_AtlasList
|
||||
postatlasListItem = scene.TLM_PostAtlasListItem
|
||||
postatlasList = scene.TLM_PostAtlasList
|
||||
|
||||
row = layout.row()
|
||||
row.prop(sceneProperties, "tlm_atlas_mode", expand=True)
|
||||
|
||||
if sceneProperties.tlm_atlas_mode == "Prepack":
|
||||
|
||||
rows = 2
|
||||
if len(atlasList) > 1:
|
||||
rows = 4
|
||||
row = layout.row()
|
||||
row.template_list("TLM_UL_AtlasList", "Atlas List", scene, "TLM_AtlasList", scene, "TLM_AtlasListItem", rows=rows)
|
||||
col = row.column(align=True)
|
||||
col.operator("tlm_atlaslist.new_item", icon='ADD', text="")
|
||||
col.operator("tlm_atlaslist.delete_item", icon='REMOVE', text="")
|
||||
col.menu("TLM_MT_AtlasListSpecials", icon='DOWNARROW_HLT', text="")
|
||||
|
||||
if atlasListItem >= 0 and len(atlasList) > 0:
|
||||
item = atlasList[atlasListItem]
|
||||
layout.prop(item, "tlm_atlas_lightmap_unwrap_mode")
|
||||
layout.prop(item, "tlm_atlas_lightmap_resolution")
|
||||
layout.prop(item, "tlm_atlas_unwrap_margin")
|
||||
|
||||
amount = 0
|
||||
|
||||
for obj in bpy.context.scene.objects:
|
||||
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
|
||||
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA":
|
||||
if obj.TLM_ObjectProperties.tlm_atlas_pointer == item.name:
|
||||
amount = amount + 1
|
||||
|
||||
layout.label(text="Objects: " + str(amount))
|
||||
layout.prop(item, "tlm_atlas_merge_samemat")
|
||||
|
||||
layout.prop(item, "tlm_use_uv_packer")
|
||||
layout.prop(item, "tlm_uv_packer_padding")
|
||||
layout.prop(item, "tlm_uv_packer_packing_engine")
|
||||
|
||||
else:
|
||||
|
||||
layout.label(text="Postpacking is unstable.")
|
||||
|
||||
cv2 = importlib.util.find_spec("cv2")
|
||||
|
||||
if cv2 is None:
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="OpenCV is not installed. Install it through preferences.")
|
||||
|
||||
else:
|
||||
|
||||
rows = 2
|
||||
if len(atlasList) > 1:
|
||||
rows = 4
|
||||
row = layout.row()
|
||||
row.template_list("TLM_UL_PostAtlasList", "PostList", scene, "TLM_PostAtlasList", scene, "TLM_PostAtlasListItem", rows=rows)
|
||||
col = row.column(align=True)
|
||||
col.operator("tlm_postatlaslist.new_item", icon='ADD', text="")
|
||||
col.operator("tlm_postatlaslist.delete_item", icon='REMOVE', text="")
|
||||
col.menu("TLM_MT_PostAtlasListSpecials", icon='DOWNARROW_HLT', text="")
|
||||
|
||||
if postatlasListItem >= 0 and len(postatlasList) > 0:
|
||||
item = postatlasList[postatlasListItem]
|
||||
layout.prop(item, "tlm_atlas_lightmap_resolution")
|
||||
|
||||
#Below list object counter
|
||||
amount = 0
|
||||
utilized = 0
|
||||
atlasUsedArea = 0
|
||||
atlasSize = item.tlm_atlas_lightmap_resolution
|
||||
|
||||
for obj in bpy.context.scene.objects:
|
||||
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
|
||||
if obj.TLM_ObjectProperties.tlm_postpack_object:
|
||||
if obj.TLM_ObjectProperties.tlm_postatlas_pointer == item.name:
|
||||
amount = amount + 1
|
||||
|
||||
atlasUsedArea += int(obj.TLM_ObjectProperties.tlm_mesh_lightmap_resolution) ** 2
|
||||
|
||||
row = layout.row()
|
||||
row.prop(item, "tlm_atlas_repack_on_cleanup")
|
||||
|
||||
#TODO SET A CHECK FOR THIS! ADD A CV2 CHECK TO UTILITY!
|
||||
cv2 = True
|
||||
|
||||
if cv2:
|
||||
row = layout.row()
|
||||
row.prop(item, "tlm_atlas_dilation")
|
||||
layout.label(text="Objects: " + str(amount))
|
||||
|
||||
utilized = atlasUsedArea / (int(atlasSize) ** 2)
|
||||
layout.label(text="Utilized: " + str(utilized * 100) + "%")
|
||||
|
||||
if (utilized * 100) > 100:
|
||||
layout.label(text="Warning! Overflow not yet supported")
|
17
leenkx/blender/lnx/lightmapper/panels/world.py
Normal file
@ -0,0 +1,17 @@
|
||||
import bpy
|
||||
from bpy.props import *
|
||||
from bpy.types import Menu, Panel
|
||||
|
||||
class TLM_PT_WorldMenu(bpy.types.Panel):
|
||||
bl_label = "The Lightmapper"
|
||||
bl_space_type = "PROPERTIES"
|
||||
bl_region_type = "WINDOW"
|
||||
bl_context = "world"
|
||||
bl_options = {'DEFAULT_CLOSED'}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
scene = context.scene
|
||||
obj = bpy.context.object
|
||||
layout.use_property_split = True
|
||||
layout.use_property_decorate = False
|
16
leenkx/blender/lnx/lightmapper/preferences/__init__.py
Normal file
@ -0,0 +1,16 @@
|
||||
import bpy
|
||||
from bpy.utils import register_class, unregister_class
|
||||
from . import addon_preferences
|
||||
#from . import build, clean, explore, encode, installopencv
|
||||
|
||||
classes = [
|
||||
addon_preferences.TLM_AddonPreferences
|
||||
]
|
||||
|
||||
def register():
|
||||
for cls in classes:
|
||||
register_class(cls)
|
||||
|
||||
def unregister():
|
||||
for cls in classes:
|
||||
unregister_class(cls)
|
106
leenkx/blender/lnx/lightmapper/preferences/addon_preferences.py
Normal file
@ -0,0 +1,106 @@
|
||||
import bpy, platform
|
||||
from os.path import basename, dirname
|
||||
from bpy.types import AddonPreferences
|
||||
from bpy.props import *
|
||||
from .. operators import installopencv
|
||||
from . import addon_preferences
|
||||
import importlib
|
||||
|
||||
class TLM_AddonPreferences(AddonPreferences):
|
||||
|
||||
bl_idname = __name__.split(".")[0]
|
||||
|
||||
tlm_ui_mode: EnumProperty(
|
||||
items=[('simple', 'Simple', 'Simple UI'),
|
||||
('advanced', 'Advanced', 'Advanced UI')],
|
||||
name='UI mode', default='simple', description='Choose UI mode')
|
||||
|
||||
def draw(self, context):
|
||||
|
||||
layout = self.layout
|
||||
|
||||
box = layout.box()
|
||||
row = box.row()
|
||||
|
||||
row.label(text="UI Mode")
|
||||
row.prop(self, "tlm_ui_mode")
|
||||
row = box.row()
|
||||
row.label(text="Simple: Only the basic setup for Blender/Eevee baking with non-experimental features.")
|
||||
row = box.row()
|
||||
row.label(text="Full set of options available.")
|
||||
row = box.row()
|
||||
|
||||
row.label(text="OpenCV")
|
||||
|
||||
cv2 = importlib.util.find_spec("cv2")
|
||||
|
||||
if cv2 is not None:
|
||||
row.label(text="OpenCV installed")
|
||||
else:
|
||||
if platform.system() == "Windows":
|
||||
row.label(text="OpenCV not found - Install as administrator!", icon_value=2)
|
||||
else:
|
||||
row.label(text="OpenCV not found - Click to install!", icon_value=2)
|
||||
row = box.row()
|
||||
row.operator("tlm.install_opencv_lightmaps", icon="PREFERENCES")
|
||||
|
||||
box = layout.box()
|
||||
row = box.row()
|
||||
row.label(text="Blender Xatlas")
|
||||
if "blender_xatlas" in bpy.context.preferences.addons.keys():
|
||||
row.label(text="Blender Xatlas installed and available")
|
||||
else:
|
||||
row.label(text="Blender Xatlas not installed", icon_value=2)
|
||||
row = box.row()
|
||||
row.label(text="Github: https://github.com/mattedicksoncom/blender-xatlas")
|
||||
|
||||
box = layout.box()
|
||||
row = box.row()
|
||||
row.label(text="RizomUV Bridge")
|
||||
row.label(text="Coming soon")
|
||||
|
||||
box = layout.box()
|
||||
row = box.row()
|
||||
row.label(text="UVPackmaster")
|
||||
row.label(text="Coming soon")
|
||||
|
||||
uvpacker_addon = False
|
||||
for addon in bpy.context.preferences.addons.keys():
|
||||
if addon.startswith("UV-Packer"):
|
||||
uvpacker_addon = True
|
||||
|
||||
box = layout.box()
|
||||
row = box.row()
|
||||
row.label(text="UV Packer")
|
||||
if uvpacker_addon:
|
||||
row.label(text="UV Packer installed and available")
|
||||
else:
|
||||
row.label(text="UV Packer not installed", icon_value=2)
|
||||
row = box.row()
|
||||
row.label(text="Github: https://www.uv-packer.com/blender/")
|
||||
|
||||
texel_density_addon = False
|
||||
for addon in bpy.context.preferences.addons.keys():
|
||||
if addon.startswith("Texel_Density"):
|
||||
texel_density_addon = True
|
||||
|
||||
box = layout.box()
|
||||
row = box.row()
|
||||
row.label(text="Texel Density Checker")
|
||||
if texel_density_addon:
|
||||
row.label(text="Texel Density Checker installed and available")
|
||||
else:
|
||||
row.label(text="Texel Density Checker", icon_value=2)
|
||||
row.label(text="Coming soon")
|
||||
row = box.row()
|
||||
row.label(text="Github: https://github.com/mrven/Blender-Texel-Density-Checker")
|
||||
|
||||
box = layout.box()
|
||||
row = box.row()
|
||||
row.label(text="LuxCoreRender")
|
||||
row.label(text="Coming soon")
|
||||
|
||||
box = layout.box()
|
||||
row = box.row()
|
||||
row.label(text="OctaneRender")
|
||||
row.label(text="Coming soon")
|
62
leenkx/blender/lnx/lightmapper/properties/__init__.py
Normal file
@ -0,0 +1,62 @@
|
||||
import bpy
|
||||
from bpy.utils import register_class, unregister_class
|
||||
from . import scene, object, atlas, image
|
||||
from . renderer import cycles, luxcorerender, octanerender
|
||||
from . denoiser import oidn, optix
|
||||
|
||||
classes = [
|
||||
scene.TLM_SceneProperties,
|
||||
object.TLM_ObjectProperties,
|
||||
cycles.TLM_CyclesSceneProperties,
|
||||
luxcorerender.TLM_LuxCoreSceneProperties,
|
||||
octanerender.TLM_OctanerenderSceneProperties,
|
||||
oidn.TLM_OIDNEngineProperties,
|
||||
optix.TLM_OptixEngineProperties,
|
||||
atlas.TLM_AtlasListItem,
|
||||
atlas.TLM_UL_AtlasList,
|
||||
atlas.TLM_PostAtlasListItem,
|
||||
atlas.TLM_UL_PostAtlasList,
|
||||
image.TLM_ImageProperties,
|
||||
scene.TLM_UL_GroupList,
|
||||
scene.TLM_GroupListItem
|
||||
]
|
||||
|
||||
def register():
|
||||
for cls in classes:
|
||||
register_class(cls)
|
||||
|
||||
bpy.types.Scene.TLM_SceneProperties = bpy.props.PointerProperty(type=scene.TLM_SceneProperties)
|
||||
bpy.types.Object.TLM_ObjectProperties = bpy.props.PointerProperty(type=object.TLM_ObjectProperties)
|
||||
bpy.types.Scene.TLM_EngineProperties = bpy.props.PointerProperty(type=cycles.TLM_CyclesSceneProperties)
|
||||
bpy.types.Scene.TLM_Engine2Properties = bpy.props.PointerProperty(type=luxcorerender.TLM_LuxCoreSceneProperties)
|
||||
bpy.types.Scene.TLM_Engine3Properties = bpy.props.PointerProperty(type=octanerender.TLM_OctanerenderSceneProperties)
|
||||
bpy.types.Scene.TLM_OIDNEngineProperties = bpy.props.PointerProperty(type=oidn.TLM_OIDNEngineProperties)
|
||||
bpy.types.Scene.TLM_OptixEngineProperties = bpy.props.PointerProperty(type=optix.TLM_OptixEngineProperties)
|
||||
bpy.types.Scene.TLM_AtlasListItem = bpy.props.IntProperty(name="Index for my_list", default=0)
|
||||
bpy.types.Scene.TLM_AtlasList = bpy.props.CollectionProperty(type=atlas.TLM_AtlasListItem)
|
||||
bpy.types.Scene.TLM_PostAtlasListItem = bpy.props.IntProperty(name="Index for my_list", default=0)
|
||||
bpy.types.Scene.TLM_PostAtlasList = bpy.props.CollectionProperty(type=atlas.TLM_PostAtlasListItem)
|
||||
bpy.types.Image.TLM_ImageProperties = bpy.props.PointerProperty(type=image.TLM_ImageProperties)
|
||||
bpy.types.Scene.TLM_GroupListItem = bpy.props.IntProperty(name="Index for my_list", default=0)
|
||||
bpy.types.Scene.TLM_GroupList = bpy.props.CollectionProperty(type=scene.TLM_GroupListItem)
|
||||
|
||||
bpy.types.Material.TLM_ignore = bpy.props.BoolProperty(name="Skip material", description="Ignore material for lightmapped object", default=False)
|
||||
|
||||
def unregister():
|
||||
for cls in classes:
|
||||
unregister_class(cls)
|
||||
|
||||
del bpy.types.Scene.TLM_SceneProperties
|
||||
del bpy.types.Object.TLM_ObjectProperties
|
||||
del bpy.types.Scene.TLM_EngineProperties
|
||||
del bpy.types.Scene.TLM_Engine2Properties
|
||||
del bpy.types.Scene.TLM_Engine3Properties
|
||||
del bpy.types.Scene.TLM_OIDNEngineProperties
|
||||
del bpy.types.Scene.TLM_OptixEngineProperties
|
||||
del bpy.types.Scene.TLM_AtlasListItem
|
||||
del bpy.types.Scene.TLM_AtlasList
|
||||
del bpy.types.Scene.TLM_PostAtlasListItem
|
||||
del bpy.types.Scene.TLM_PostAtlasList
|
||||
del bpy.types.Image.TLM_ImageProperties
|
||||
del bpy.types.Scene.TLM_GroupListItem
|
||||
del bpy.types.Scene.TLM_GroupList
|