174 Commits
v1.7 ... master

Author SHA1 Message Date
Terrev
fd02e3240e disable subsurfacescattering on opaque as it doesnt play nicely with the scaling 2025-01-14 21:05:45 -05:00
Terrev
bbdfafc2b6 Merge pull request #23 from Squareville/lod-color
Coloring vertices per lod group, and repeating the state/seed so each…
2024-11-26 02:43:07 -05:00
473879e900 Coloring vertices per lod group, and repeating the state/seed so each lod is the same color. 2024-11-25 20:27:24 -05:00
Terrev
5c0c04e4a0 Merge pull request #22 from Squareville/reset-orientation
Option to apply a 90° rotation transform, fixes smashable vfx.
2024-11-12 02:04:53 -05:00
9a9b5fe58e Option to apply a 90° rotation transform, fixes smashable vfx. 2024-11-12 01:24:52 -05:00
Terrev
f9ce932bed version 2.0.0 -> 2.1.0 2024-02-19 01:21:29 -05:00
Terrev
1b3f6672e9 only increase sample count if we need to 2024-02-19 01:20:51 -05:00
Terrev
7a9a8e2cbb silly fix for transparent bricks in icons being opaque 2024-02-18 02:35:54 -05:00
Terrev
61ba3c69dd improve transparent colors 2023-12-20 01:31:53 -05:00
Terrev
88e6159f40 fix transparent colors and 111 tbrown (lol), may adjust some values next 2023-12-19 22:30:17 -05:00
6ac59d4be3 Update README.md 2022-12-27 18:18:13 -05:00
569761f972 Update version number to 2.0.0 2022-12-27 13:07:24 -05:00
Terrev
223e607628 transparent color rgb updates 2022-11-08 22:59:50 -05:00
Terrev
8138542fe0 black color variation strength 2022-11-08 13:42:03 -05:00
5fb6338ca9 fix normals 2022-11-07 21:38:28 -06:00
Bobbe
24d5708d7a Fix icon material color space 2022-05-08 19:58:34 +02:00
Bobbe
22df835784 Move color conversion functions to their own file 2022-05-08 19:58:22 +02:00
db5b172f95 Added icon render materials for black and white. 2022-05-08 00:22:57 -04:00
ba9beb1212 Updated resources.blend. Some art polish for icon render scene. 2022-05-08 00:22:38 -04:00
081c6aac0f Updated resources.blend ItemRender camera FOV and render resolution. 2022-05-07 11:37:00 -04:00
Bobbe
713620a30e Remove leftover print 2022-05-05 16:08:58 +02:00
Bobbe
fe1ea064a8 Automatically center and scale model for icon_render 2022-05-04 17:10:45 +02:00
Bobbe
0cc478b5e4 Add set to disable subdivision for certain bricks in icon render 2022-05-03 20:06:17 +02:00
Bobbe
52a40f3d9f Get rid of the now deprecated premerging code in process model 2022-05-03 19:56:47 +02:00
Bobbe
67aa93348a Remove brick empties, premerge bricks and their materials 2022-05-03 19:56:29 +02:00
Bobbe
d8ad70de8c Fix importer error handling 2022-05-03 18:52:56 +02:00
Bobbe
5d41b3e199 Add semi working icon render operator 2022-05-03 18:51:26 +02:00
Bobbe
79d4bf454b Optimize/Simplify precombining bricks/materials 2022-03-25 21:46:30 +01:00
Bobbe
bbabeac716 Add "Ignore Lights" option to HSR 2022-03-25 17:08:46 +01:00
Bobbe
30e3c78bde Fix potential edge cases that could lead to errors in bake lighting 2022-03-25 16:51:49 +01:00
Bobbe
a961554aeb Fix #18 HSR info print not working if no hidden faces were detected 2022-03-25 16:30:45 +01:00
Bobbe
66b5f24d9d Add more shader types, remove leading "S" 2022-03-25 16:13:34 +01:00
Bobbe
34ebba2ff5 Retriangulate faces after HSR (only when autoremoving) 2022-03-25 16:01:34 +01:00
Bobbe
4009b815c7 Reorganize bake lighting UI 2022-03-24 14:29:57 +01:00
Bobbe
33018f7d12 Add "selected only" option to bake lighting 2022-03-24 14:29:31 +01:00
8860921d66 Update resources.blend. Added materials for colliders. 2022-03-21 13:01:43 -04:00
d6c137dba0 Increased AO radius to better match LU (5.0m) 2022-03-18 22:28:23 -04:00
bc869eb9a7 Resolves #9 2022-03-17 23:17:58 -05:00
eafde9ac0b Update README.md 2022-03-17 01:11:39 -04:00
30fb77888e Update README.md 2022-03-17 01:07:46 -04:00
479891617b Update README.md
Added Toolbox features description.
2022-03-17 01:01:12 -04:00
26af3e241b actuallu show the option for LOD3 2022-03-14 22:53:30 -05:00
996c0210e9 Dynamic LOD 3 rendering
(god forgive me)
2022-03-14 22:49:34 -05:00
3b8f27ad08 LOD3
linting cleaning
2022-03-14 21:20:54 -05:00
bd5f00bf4b Update resources.blend. Added ItemRender materials. Added ModelRender and ItemRender scenes. 2022-03-14 15:57:29 -04:00
Bobbe
6976646685 Remove ambient world light when baking with AO only 2022-03-12 17:44:22 +01:00
Bobbe
98f34e53c6 Fix not linking HSR ground plane 2022-03-12 17:28:09 +01:00
Bobbe
19c058f038 Fix division by zero if no faces remain after pre pass in HSR 2022-03-12 17:21:40 +01:00
8f761df032 Update resources.blend. Set nif mat emissive value to 0.1 on VertexColor and VertexColorAO materials. Added GlowOnly material. 2022-03-12 00:03:07 -05:00
5c359a6af7 Toggle to use normals when importing
make clear collections variable to be consistent with wording
fix typo in readme
2022-03-11 13:15:15 -06:00
2b5b809f21 Update README.md
Minor spelling edits.
2022-03-11 13:10:07 -05:00
08116d705e Merge pull request #6 from Squareville/issue-4
Update Readme with info about the importer
2022-03-11 13:08:12 -05:00
250341bffa Resolves #3
Have enable autosmooth for the custom normals to be respected
and actually do anything
2022-03-11 11:36:18 -06:00
Bobbe
5be7335ac8 Rework ao only, Add force white option to bake lighting 2022-03-11 17:56:19 +01:00
d5f60af5e6 Update resources.blend. Added ForceWhite material. 2022-03-11 10:54:04 -05:00
e5f9e19dfc Update Readme with info about the importer
Naming consistency/sensibility
2022-03-10 23:16:10 -06:00
de78dcf350 use the correct custom normal setting method 2022-03-10 17:14:31 -06:00
bee8515d6e Removed material 294 from transparent materials. 2022-03-10 13:47:21 -05:00
Bobbe
4a79913ab4 Make pre-pass actually work on per vertex basis 2022-03-10 17:54:47 +01:00
Bobbe
6cef63975a Optimize hsr uv setup, image evaluation 2022-03-10 17:28:46 +01:00
Bobbe
42f5cc87f8 Implement vc pre pass for hsr 2022-03-10 17:12:20 +01:00
Bobbe
3ad5ba95ed Refactor process model, Replace np zeros calls with empty if possible 2022-03-09 20:16:17 +01:00
Bobbe
3a5c46f44a Finish refactoring HSR code, Prepare for vc pre pass 2022-03-09 20:15:21 +01:00
d6384d77d0 Renamed "Clear Colllection" to "Overwrite Scene". 2022-03-06 23:48:33 -05:00
5cdab7f109 option to clear scene during import
resolves #2
2022-03-06 19:45:38 -06:00
c163e9e21f remove hacky normals code 2022-03-06 11:58:02 -06:00
Bobbe
5228e89f0e Refactor HSR code 2022-03-05 20:39:51 +01:00
Bobbe
010d483bee Add/Edit a bunch of tooltips 2022-03-05 18:01:55 +01:00
Bobbe
9421dfbbfd Improve duplicate color correction handling 2022-03-05 17:57:54 +01:00
Bobbe
1f0abd97a3 Disable fast GI and clamping during HSR, Set bounces for HSR 2022-03-04 17:26:53 +01:00
Bobbe
14a6d6348b Use context overrides for baking, Disable denoising for baking 2022-03-04 17:25:43 +01:00
Bobbe
d388a495c6 Simplify dynamic lod distance handling 2022-03-04 16:17:58 +01:00
1782ece2c8 normals!
cleanup
2022-03-03 14:43:02 -06:00
9009add828 cleanup, map missing colors 2022-03-03 10:29:29 -06:00
40152fa3ee Changed name of lxf import in import menu to LEGO Exchange Format 2022-03-03 10:34:36 -05:00
7a0a34d52f ldd BEGONE
more cleanup
2022-03-02 22:25:57 -06:00
dabd59e430 fix typo in comment and stuff 2022-02-28 13:47:28 -06:00
028f02276d fix error catching on conversion 2022-02-28 11:10:33 -06:00
edf276583f remove unused file
fixed typo in dynamic extent logic
credit to jonnysp and stnng for lxfml reader in modified importer
2022-02-27 22:53:37 -06:00
5db193869f Swapped two color IDs (120 replaces 326, not vice versa). 2022-02-27 23:45:19 -05:00
e44eb7d76a dynamic extent distanct handling
to avoid gaps in render dinstance when not using all 3 LODS
2022-02-27 20:25:50 -06:00
5b82376938 res compatability
filter by the file extension that we want to look for
fix db timing
2022-02-27 18:24:21 -06:00
951e1ff48f organize dupe materials 2022-02-27 17:12:17 -06:00
01d517dfe6 Merge pull request #1 from Squareville/ldd-importer-merge
Ldd importer merge
2022-02-27 17:45:43 -05:00
31118a46e5 add dupe glow colors 2022-02-27 16:42:42 -06:00
03185db87d default to all 3 LODS importing 2022-02-27 16:30:08 -06:00
b21ee54c5b prefer opaque colors if they differ 2022-02-27 15:42:19 -06:00
4d8f32a03c Updated some Glow color RGB values. 2022-02-27 16:36:13 -05:00
f1207a51d0 Updated color ID 50 Phosphorescent White 2022-02-27 16:21:35 -05:00
8c4763f534 dupe glow color mapping
add missing colors
2022-02-27 15:13:52 -06:00
b9048e4d96 Revert "Added material ID 0."
This reverts commit 84bc41174e.
2022-02-27 15:45:37 -05:00
84bc41174e Added material ID 0. 2022-02-27 15:45:15 -05:00
362c7cac21 Fixed some missing duplicate materials. 2022-02-27 15:36:32 -05:00
af4c747f37 Merge branch 'ldd-importer-merge' of https://github.com/Squareville/lu-toolbox into ldd-importer-merge 2022-02-27 14:53:28 -05:00
a190efb8a1 Added RGB values for metallic colors. Made opaque color ID 50 its own unique color. Added some missing RGB values. 2022-02-27 14:53:21 -05:00
101ec96e00 fix missing geo skipping 2022-02-27 13:37:44 -06:00
76b912f9fc fix somme entries
formatting
2022-02-27 02:30:59 -06:00
7442cf8e44 material types?
formatting
2022-02-27 01:58:47 -06:00
1e824151a3 better import
Dupe color mapping to only LU colors
No more need for Materials.xml
Color Error Handling
Tmp metallics
2022-02-27 01:41:19 -06:00
794503ad76 timeing and info messages
cleanup some garbo
2022-02-26 21:18:09 -06:00
3dcedb14a8 fix raised exceptions 2022-02-26 19:31:52 -06:00
217fef1912 select ldd file location
(only uses the LU one for now)
editorconfig for my own sanity
removed color csv
2022-02-26 19:17:53 -06:00
2d828bfe03 v3-v4 lxfml handling (broken, but imports stuff) 2022-02-26 17:27:11 -06:00
0e80c02263 fix typo 2022-02-26 14:11:44 -06:00
55608812c2 raise when we cant do it 2022-02-26 13:56:18 -06:00
92c89d0792 imperfect v4 handler 2022-02-26 13:49:05 -06:00
e0da989907 attempt to handle laternate lxfml trans format 2022-02-26 00:48:48 -06:00
e3f53e89fe chop chop chop
pull out find db
in prep for hardcoding paths
2022-02-25 23:49:58 -06:00
1977bf17d4 Barry the butcher is in town~
removed camera and logo on stud stuff
removed a buch od dead code
removed progress bar (printing to console costs cycles!!!)
Important stuff up top
Removed comment version block junk
2022-02-25 22:50:15 -06:00
c9425fc869 integrate knight's changes for multi lod selection
all materials object
2022-02-25 22:22:26 -06:00
c92887809b set to unknown then update if we can 2022-02-25 21:15:58 -06:00
b3e63b8f0b make it more robust
fallback about metadata
Dont care about cameras
some better debug info
2022-02-25 21:04:54 -06:00
e6b72b91a1 missing color fix 2022-02-25 20:15:54 -06:00
04cb56ef0a fixes 2022-02-25 20:05:46 -06:00
82c3828c31 Don't try this at home kids
smashed together POC
2022-02-25 19:16:18 -06:00
2a5012b66a Fixed lime green and medium lilac RGB values. Whoops. 2022-02-22 13:35:30 -05:00
d2b69730f7 Added Lime Green to materials table and adjusted Medium Lilac to match its new RGB. 2022-02-22 13:17:23 -05:00
Bobbe
c4cd4f9eb1 Fix ground plane being hidden during HSR 2022-01-30 01:25:15 +01:00
Bobbe
8fd6c33df6 Hide other objects during HSR 2022-01-30 01:10:40 +01:00
30350n
6efb0576aa Fix blender screenshot scaling in readme 2022-01-25 20:07:32 +01:00
30350n
2a35e9e6e1 Update image scaling in readme 2022-01-25 20:06:45 +01:00
Bobbe
f96686434e Resize images again 2022-01-25 20:03:21 +01:00
Bobbe
211a3c59c9 Resize images 2022-01-25 19:54:44 +01:00
30350n
e21557ab59 Add readme 2022-01-25 19:50:06 +01:00
Bobbe
3d75938db8 Add images for readme 2022-01-25 19:13:03 +01:00
30350n
5378334e60 Add license 2022-01-25 18:45:09 +01:00
Bobbe
9b7f745f33 Fix error with vertex mask selection in bake lighting 2022-01-25 17:04:14 +01:00
Bobbe
ac62a2c5fe Enable backface culling when applying vertex colors 2022-01-25 17:04:06 +01:00
Bobbe
60011527c0 Select everything before "delete loose" in HSR and smooth vertex colors 2022-01-24 17:55:21 +01:00
eb607179e2 Added new custom variation entries. 2022-01-23 23:27:00 -05:00
85efb7f68e Corrected typo in print bake lighting timer. 2022-01-22 23:17:45 -05:00
Bobbe
505c880a64 Add custom color variation 2022-01-23 01:35:50 +01:00
Bobbe
97e1c07f73 Fix wrong vc colorspace for single material objects, Fix selection bug 2022-01-23 00:27:31 +01:00
Bobbe
694d95a7a3 Hide other lod collections when baking 2022-01-20 15:32:47 +01:00
Bobbe
d74a1212a5 Adjust default LOD ranges, default transparent opacity 2022-01-20 13:59:25 +01:00
b6c258cd5b Update transparent material RGB values. 2022-01-19 20:50:05 -05:00
3ba6f2268d Update resources.blend. Set Alpha Flag on all materials to 237.
Transparent bricks should now appear transparent in-game.
2022-01-19 14:11:45 -05:00
Bobbe
a83b897480 Remove unused code, Refactor dictionary gets 2022-01-19 15:17:02 +01:00
Bobbe
393a7d591c Fix UI inconsistent logic with "Apply Vertex Colors" 2022-01-19 15:16:24 +01:00
Bobbe
97dd99cf5c Make color variations consistent over all LODs 2022-01-19 15:15:44 +01:00
Bobbe
75148b6628 Add "Keep UVs" parameter to process model, Delete UVs by default 2022-01-19 15:14:53 +01:00
Bobbe
2d8c5d7c82 Add "Fast GI Bounces" parameter to bake lighting 2022-01-19 15:13:16 +01:00
Bobbe
22e68940c3 Fix error with removing uvmap after HSR, Reuse bmesh in HSR 2022-01-19 15:12:22 +01:00
Bobbe
8502a97f59 Use "VertexColor" as main bake material 2022-01-19 15:10:36 +01:00
Bobbe
0ff337f421 Remove uvmap after HSR 2022-01-18 20:14:01 +01:00
Bobbe
f8478d6145 Set light bounces to 0 when baking with "ao only" 2022-01-18 17:33:45 +01:00
Bobbe
6fa80585a4 Fix color space when applying vcs (add lin2srgb conversion) 2022-01-18 17:19:08 +01:00
Bobbe
f68f7d5f96 Fix weird bug with mesh splitting 2022-01-18 17:17:50 +01:00
dde0fd116c Update resources.blend
Updated materials.
2022-01-16 18:32:39 -05:00
Bobbe
0ee867608b Fix setting brick name (using shader prefix etc.) 2022-01-16 22:44:49 +01:00
Bobbe
bef78957d8 Fix alpha setting for opaque objects after baking 2022-01-16 22:23:50 +01:00
Bobbe
75370d4cae Fix opacity setting for transparent bricks 2022-01-16 22:14:34 +01:00
Bobbe
44f77df397 Keep collections when setting up lod structure 2022-01-16 21:28:06 +01:00
Bobbe
50347bf629 Add transparent material to transparent objects 2022-01-16 21:03:46 +01:00
Bobbe
da5375dad7 Remove "-" from main package name 2022-01-16 20:03:27 +01:00
Bobbe
0271c92a6f Make minimum supported blender version 2.93 2022-01-16 20:02:36 +01:00
Bobbe
ce30aeccba Add correct orientation option 2022-01-16 20:02:05 +01:00
Bobbe
925cfb1809 Add ground plane option to hidden surface removal 2022-01-16 17:18:52 +01:00
Bobbe
868cd66ab4 Add ao only and material override options to bake lighting 2022-01-14 14:11:29 +01:00
Bobbe
1d85f54b65 Fix bake material auto selection, ui 2022-01-14 14:06:40 +01:00
Bobbe
aa2ba6f637 Fix delete loose issue 2022-01-14 14:05:17 +01:00
Bobbe
bcc0278d5c Use alpha layer after bake lighting, Fix typo 2022-01-14 13:02:30 +01:00
Bobbe
345ca4fb87 Modernize UI, Add shader prefix property 2022-01-14 12:20:03 +01:00
Bobbe
a3dd6ae02b Auto append materials from resources.blend 2022-01-14 11:48:01 +01:00
Bobbe
0be4c7486a Simplify, Refactor lod setup 2022-01-14 11:45:30 +01:00
Bobbe
31eeca319d Add mesh splitting, Fix lod structure, Improve mat name detection 2022-01-14 11:41:15 +01:00
Bobbe
181f8b8142 Ensure triangulate modifier exists during bake lighting 2022-01-13 17:04:14 +01:00
Bobbe
563db8c0c5 Add transparent opacity, Optimize apply vertex colors 2022-01-13 17:03:44 +01:00
Bobbe
895438cb2f Restructure process model UI, Fix not deleting lod properties 2022-01-13 17:00:26 +01:00
Bobbe
c0d3ba0f09 Add timing information 2022-01-13 16:57:36 +01:00
Bobbe
8123e31c83 Move bake environment setup to bake lighting 2022-01-13 16:56:46 +01:00
Bobbe
c06a5e34c5 Move material dicts to their own file, Add glow materials 2022-01-13 16:45:39 +01:00
Bobbe
d3698a1d12 Disable baked lighting smoothing if edge split mod is present 2021-12-15 17:54:18 +01:00
Bobbe
bda07bb8b4 Support new input structure, transparent bricks, Rework lods 2021-12-15 17:27:05 +01:00
23 changed files with 3944 additions and 817 deletions

37
.editorconfig Normal file
View File

@@ -0,0 +1,37 @@
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true
# Matches multiple files with brace expansion notation
# Set default charset
[*.{js,py}]
charset = utf-8
# 4 space indentation
[*.py]
indent_style = space
indent_size = 4
[{*.jinja2,*.html.j2}]
indent_style = space
indent_size = 2
# Tab indentation (no size specified)
[Makefile]
indent_style = tab
# Indentation override for all JS under lib directory
[lib/**.js]
indent_style = space
indent_size = 2
# Matches the exact files either package.json or .travis.yml
[{package.json,.travis.yml}]
indent_style = space
indent_size = 2

674
LICENSE Normal file
View File

@@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.

64
README.md Normal file
View File

@@ -0,0 +1,64 @@
[![blender](https://img.shields.io/badge/blender-3.1.0-success)](https://download.blender.org/release/Blender3.1/)
[![gpl](https://img.shields.io/github/license/30350n/lu-toolbox)](https://github.com/30350n/lu-toolbox/blob/master/LICENSE)
# LEGO Universe Toolbox
A Blender Add-on which adds a bunch of useful tools to prepare models for use in LEGO Universe.
## Features
* Custom LEGO Exchange Format (.lxf/.lxfml) Importer.
* Automatic model preparation with many useful processes.
* Custom workflow for baking materials, lighting, ambient occlusion, alpha, and more to vertex colors.
* Automatic pathtraced hidden surface removal to clean out model interiors of unseen geometry.
* Many options and toggles to fit a variety of artist workflows.
<hr>
![banner](images/banner.png)
## Installation
1. Download the latest release from [here](https://github.com/Squareville/lu-toolbox/releases/latest).
2. Start Blender and navigate to "Edit -> Preferences -> Add-ons"
3. (optional) If you already have an older version of the add-on installed, disable it, remove it and restart blender.
4. Hit "Install" and select the zip archive you downloaded.
5. In the Add-on's preferences, set `Brick DB` to your LU client res folder or extracted Brick DB
After installation you can find the add-on in the right sidepanel (N-panel) of the 3D Viewport.
#### Blender Version
The minimum compatible Blender version for LU Toolbox v2.0 is Blender 3.1.
## Documentation
- [DLU - Asset Creation Guide](https://docs.google.com/document/d/15YDtHg3-i3Pn6HTEFkpAjKsvUF6u49ZsQam5b614YRw) by [cdmpants](https://github.com/cdmpants)
### LEGO Exchange Format Importer (.lxf/.lxfml)
This importer is derived from [sttng's ImportLDD Add-on](https://github.com/sttng/ImportLDD) with a few changes:
* Support for importing one or multiple LODs at a time
* Enchanced Brick DB handling:
* Support for defining a path to any Brick DB via Add-on Preferences
* Direct support for using LU's brick db without needing to extract it manually
* You can use the `client/res/` folder directly as a Brick DB source
* the `brickdb.zip` will automatically be unzipped for you if it's not already
* Dropped support for using LDD's `db.lif` directly since it doesn't provide LODs
* Consolidated color support for LU's color palette:
* Colors outside of LU's supported palette will be coerced to the closest color
* Please report any instances of missing colors so that they can be added
* Missing data handling:
* Bricks missing from brick database will be skipped
* Completely missing colors will be set to black (Color ID 26)
* Color missing from secondary brick geo is handled correctly
* Overwrite Scene Option:
* Delete all objects and collections from Blender scene before importing.
## Screenshots
<div float="left">
<img src="images/blender.PNG" width="52.5%" />
<img src="images/windmill.png" width="35%" />
<img src="images/castle.png" width="35%" />
<img src="images/mech.png" width="23.33%" />
</div>

BIN
images/banner.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 MiB

BIN
images/blender.PNG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 342 KiB

BIN
images/castle.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

BIN
images/mech.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 706 KiB

BIN
images/windmill.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

View File

@@ -1,29 +0,0 @@
bl_info = {
"name": "LU Toolbox",
"author": "Bobbe",
"version": (1, 7, 0),
"blender": (2, 92, 0),
"location": "3D View -> Sidebar -> LU Toolbox",
"category": "Import-Export",
"support": "COMMUNITY",
}
import bpy
import importlib
module_names = ("process_model", "bake_lighting", "lods", "remove_hidden_faces")
modules = []
for module_name in module_names:
if module_name in locals():
modules.append(importlib.reload(locals()[module_name]))
else:
modules.append(importlib.import_module("." + module_name, package=__package__))
def register():
for module in modules:
module.register()
def unregister():
for module in modules:
module.unregister()

View File

@@ -1,85 +0,0 @@
import bpy
from bpy.props import BoolProperty, IntProperty
class LUTB_PT_bake_lighting(bpy.types.Panel):
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = "LU Toolbox"
bl_label = "Bake Lighting"
def draw(self, context):
layout = self.layout
scene = context.scene
layout.prop(scene, "lutb_bake_use_gpu")
layout.prop(scene, "lutb_smooth_lit")
layout.prop(scene, "lutb_bake_samples")
layout.operator("lutb.bake_lighting")
class LUTB_OT_bake_lighting(bpy.types.Operator):
"""Bake lighting"""
bl_idname = "lutb.bake_lighting"
bl_label = "Bake Lighting"
@classmethod
def poll(cls, context):
return context.mode == "OBJECT"
def execute(self, context):
scene = context.scene
scene.render.engine = "CYCLES"
scene.cycles.bake_type = "COMBINED"
scene.render.bake.use_pass_direct = True
scene.render.bake.use_pass_indirect = True
scene.render.bake.use_pass_diffuse = True
scene.render.bake.use_pass_glossy = False
scene.render.bake.use_pass_transmission = True
scene.render.bake.use_pass_ambient_occlusion = True
scene.render.bake.use_pass_emit = True
scene.render.bake.target = "VERTEX_COLORS"
scene.cycles.device = "GPU" if scene.lutb_process_use_gpu else "CPU"
previous_samples = scene.cycles.samples
scene.cycles.samples = scene.lutb_bake_samples
for obj in context.selected_objects:
if obj.type == "MESH":
mesh = obj.data
vc_lit = mesh.vertex_colors.get("Lit")
if vc_lit:
mesh.vertex_colors.active = vc_lit
context.view_layer.objects.active = obj
bpy.ops.object.select_all(action="DESELECT")
obj.select_set(True)
bpy.ops.object.bake()
if scene.lutb_smooth_lit:
bpy.ops.object.mode_set(mode="VERTEX_PAINT")
bpy.ops.paint.vertex_color_smooth()
bpy.ops.object.mode_set(mode="OBJECT")
scene.cycles.samples = previous_samples
return {"FINISHED"}
def register():
bpy.utils.register_class(LUTB_OT_bake_lighting)
bpy.utils.register_class(LUTB_PT_bake_lighting)
bpy.types.Scene.lutb_bake_use_gpu = BoolProperty(name="Use GPU", default=True)
bpy.types.Scene.lutb_smooth_lit = BoolProperty(name="Smooth Vertex Colors", default=True)
bpy.types.Scene.lutb_bake_samples = IntProperty(name="Samples", default=256)
def unregister():
del bpy.types.Scene.lutb_bake_use_gpu
del bpy.types.Scene.lutb_smooth_lit
del bpy.types.Scene.lutb_bake_samples
bpy.utils.unregister_class(LUTB_PT_bake_lighting)
bpy.utils.unregister_class(LUTB_OT_bake_lighting)

View File

@@ -1,227 +0,0 @@
import bpy
from bpy.props import BoolProperty, FloatProperty
from math import radians
class LUTB_PT_lods(bpy.types.Panel):
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = "LU Toolbox"
bl_label = "LODs"
def draw(self, context):
layout = self.layout
scene = context.scene
layout.prop(scene, "lutb_decimate")
layout.prop(scene, "lutb_use_lod2")
layout.operator("lutb.create_sort_lods")
layout.separator()
layout.label(text="Distances")
layout.prop(scene, "lutb_lod0")
layout.prop(scene, "lutb_lod1")
layout.prop(scene, "lutb_lod2")
layout.prop(scene, "lutb_cull")
layout.operator("lutb.setup_lod_data")
class LUTB_OT_create_sort_lods(bpy.types.Operator):
"""Sort objects into LOD collections"""
bl_idname = "lutb.create_sort_lods"
bl_label = "Create and Sort LODs"
@classmethod
def poll(cls, context):
return context.mode == "OBJECT"
def execute(self, context):
scene = context.scene
collections = []
objects = list(sorted(filter(lambda obj: obj.name[-6:-1] == "_LOD_", context.selected_objects), key=lambda obj: obj.name[-1]))
for i in range(3 if scene.lutb_use_lod2 else 2):
name = f"LOD_{i}"
collection = bpy.data.collections.get(name)
if not collection:
collection = bpy.data.collections.new(name)
if not name in scene.collection.children:
scene.collection.children.link(collection)
collections.append(collection)
lod_memory = {}
for obj in objects:
previous_users_collection = obj.users_collection
name, lod = obj.name.split("_LOD_")
if lod == "0":
collections[0].objects.link(obj)
lod_memory[name] = [obj, None, None]
elif lod == "1":
if name in lod_memory:
collections[1].objects.link(obj)
lod_memory[name][1] = obj
else:
self.report({"WARNING"}, f"Failed sorting \"{obj.name}\", \"{name}_LOD_0\" is not selected.")
continue
elif scene.lutb_use_lod2 and obj.name.endswith("LOD_2"):
if name in lod_memory:
collections[2].objects.link(obj)
lod_memory[name][2] = obj
else:
self.report({"WARNING"}, f"Failed sorting \"{obj.name}\", \"{name}_LOD_0\" is not selected.")
continue
else:
continue
for collection in previous_users_collection:
collection.objects.unlink(obj)
selection = []
for name, (lod_0, lod_1, lod_2) in lod_memory.items():
if not lod_1:
lod_1 = lod_0.copy()
lod_1.data = lod_0.data.copy()
lod_1.name = f"{lod_0.name[:-6]}_LOD_1"
lod_1.data.name = f"{lod_0.name[:-6]}_LOD_1"
collections[1].objects.link(lod_1)
if scene.lutb_decimate:
context.view_layer.objects.active = lod_1
bpy.ops.object.select_all(action="DESELECT")
lod_1.select_set(True)
bpy.ops.object.mode_set(mode="EDIT")
bpy.ops.mesh.select_all(action="SELECT")
bpy.ops.mesh.remove_doubles()
bpy.ops.mesh.tris_convert_to_quads()
bpy.ops.object.mode_set(mode="OBJECT")
decimate = lod_1.modifiers.new("Decimate", "DECIMATE")
decimate.decimate_type = "COLLAPSE"
decimate.ratio = 0.6
edge_split = lod_1.modifiers.new("EdgeSplit", "EDGE_SPLIT")
edge_split.split_angle = radians(48)
if scene.lutb_use_lod2 and not lod_2:
lod_2 = lod_0.copy()
lod_2.data = lod_0.data.copy()
lod_2.name = f"{lod_0.name[:-6]}_LOD_2"
lod_2.data.name = f"{lod_0.name[:-6]}_LOD_2"
collections[2].objects.link(lod_2)
if scene.lutb_decimate:
context.view_layer.objects.active = lod_2
bpy.ops.object.select_all(action="DESELECT")
lod_2.select_set(True)
bpy.ops.object.mode_set(mode="EDIT")
bpy.ops.mesh.select_all(action="SELECT")
bpy.ops.mesh.remove_doubles()
bpy.ops.mesh.tris_convert_to_quads()
bpy.ops.object.mode_set(mode="OBJECT")
decimate = lod_2.modifiers.new("Decimate", "DECIMATE")
decimate.decimate_type = "COLLAPSE"
decimate.ratio = 0.4
edge_split = lod_2.modifiers.new("EdgeSplit", "EDGE_SPLIT")
edge_split.split_angle = radians(55)
selection.append(lod_0)
selection.append(lod_1)
if scene.lutb_use_lod2:
selection.append(lod_2)
bpy.ops.object.select_all(action="DESELECT")
for obj in selection:
obj.select_set(True)
return {"FINISHED"}
class LUTB_OT_setup_lod_data(bpy.types.Operator):
"""Group LODs into \"SceneNode\"s and assign custom props"""
bl_idname = "lutb.setup_lod_data"
bl_label = "Setup LOD Data"
@classmethod
def poll(cls, context):
return context.mode == "OBJECT"
def execute(self, context):
scene = context.scene
lod_memory = {}
for obj in filter(lambda obj: obj.name[-6:-1] == "_LOD_" and obj.name[-1] in ("0", "1", "2"), context.selected_objects):
name = obj.name[:-6]
if not name in lod_memory:
lod_memory[name] = [obj]
else:
lod_memory[name].append(obj)
for name, lod_objects in lod_memory.items():
parents = set(map(lambda obj: obj.parent, lod_objects)) - {None}
if len(parents) == 0:
parent = bpy.data.objects.new("SceneNode", None)
scene.collection.objects.link(parent)
elif len(parents) == 1:
parent = parents.pop()
if not parent.name.startswith("SceneNode"):
self.report({"ERROR"}, f"Fatal: LOD for \"{name}\" is parented to non \"SceneNode\" object.")
return {"CANCELLED"}
else:
self.report({"ERROR"}, f"Fatal: LODs for \"{name}\" have multiple different parents.")
return {"CANCELLED"}
parent["type"] = "NiLODNode"
for obj in lod_objects:
obj.parent = parent
if obj.name.endswith("_LOD_0"):
obj["near_extent"] = scene.lutb_lod0
obj["far_extent"] = scene.lutb_lod1
elif obj.name.endswith("_LOD_1"):
obj["near_extent"] = scene.lutb_lod1
obj["far_extent"] = scene.lutb_lod2
elif obj.name.endswith("_LOD_2"):
obj["near_extent"] = scene.lutb_lod2
obj["far_extent"] = scene.lutb_cull
return {"FINISHED"}
def register():
bpy.utils.register_class(LUTB_OT_create_sort_lods)
bpy.utils.register_class(LUTB_OT_setup_lod_data)
bpy.utils.register_class(LUTB_PT_lods)
bpy.types.Scene.lutb_decimate = BoolProperty(name="Decimate", default=True)
bpy.types.Scene.lutb_use_lod2 = BoolProperty(name="Use LOD 2", default=True)
bpy.types.Scene.lutb_lod0 = FloatProperty(name="LOD 0", soft_min=0.0, default=0.0, soft_max=25.0)
bpy.types.Scene.lutb_lod1 = FloatProperty(name="LOD 1", soft_min=0.0, default=25.0, soft_max=50.0)
bpy.types.Scene.lutb_lod2 = FloatProperty(name="LOD 2", soft_min=0.0, default=50.0, soft_max=500.0)
bpy.types.Scene.lutb_cull = FloatProperty(name="Cull", soft_min=1000.0, default=10000.0, soft_max=50000.0)
def unregister():
del bpy.types.Scene.lutb_decimate
del bpy.types.Scene.lutb_use_lod2
del bpy.types.Scene.lutb_lod0
del bpy.types.Scene.lutb_lod1
del bpy.types.Scene.lutb_lod2
del bpy.types.Scene.lutb_cull
bpy.utils.unregister_class(LUTB_PT_lods)
bpy.utils.unregister_class(LUTB_OT_setup_lod_data)
bpy.utils.unregister_class(LUTB_OT_create_sort_lods)

View File

@@ -1,273 +0,0 @@
import bpy
from bpy.props import BoolProperty, FloatProperty, PointerProperty, IntProperty
from mathutils import Color
import random
color_correction = {
"26": (0.006, 0.006, 0.006, 1.0),
"199": (0.072272, 0.082283, 0.093059, 1.0),
"194": (0.332452, 0.283149, 0.283149, 1.0),
"208": (0.768151, 0.768151, 0.693872, 1.0),
"1": (0.904661, 0.904661, 0.904661, 1.0),
"154": (0.215861, 0.002428, 0.01096, 1.0),
"21": (0.730461, 0.0, 0.004025, 1.0),
"308": (0.03434, 0.015209, 0.0, 1.0),
"192": (0.104617, 0.011612, 0.003677, 1.0),
"138": (0.262251, 0.177888, 0.084376, 1.0),
"5": (0.693872, 0.491021, 0.194618, 1.0),
"38": (0.391572, 0.046665, 0.007499, 1.0),
"18": (0.672443, 0.168269, 0.051269, 1.0),
"106": (0.799103, 0.124772, 0.009134, 1.0),
"191": (0.887923, 0.318547, 0.0, 1.0),
"283": (0.913099, 0.533276, 0.250158, 1.0),
"24": (0.991102, 0.552011, 0.0, 1.0),
"226": (1.0, 0.768151, 0.141263, 1.0),
"329": (0.913099, 0.896269, 0.679542, 1.0),
"141": (0.0, 0.03434, 0.008023, 1.0),
"151": (0.114435, 0.223228, 0.130137, 1.0),
"28": (0.0, 0.198069, 0.021219, 1.0),
"37": (0.0, 0.304987, 0.017642, 1.0),
"119": (0.296138, 0.47932, 0.003035, 1.0),
"326": (0.760525, 0.947307, 0.323143, 1.0),
"140": (0.0, 0.0185, 0.052861, 1.0),
"23": (0.0, 0.095307, 0.391572, 1.0),
"102": (0.06301, 0.262251, 0.564712, 1.0),
"135": (0.111932, 0.174647, 0.262251, 1.0),
"212": (0.242281, 0.520996, 0.83077, 1.0),
"268": (0.025187, 0.007499, 0.184475, 1.0),
"124": (0.332452, 0.0, 0.147027, 1.0),
"221": (0.730461, 0.038204, 0.258183, 1.0),
"222": (0.846873, 0.341914, 0.53948, 1.0),
}
class LUTB_PT_process_model(bpy.types.Panel):
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = "LU Toolbox"
bl_label = "Process Model"
def draw(self, context):
layout = self.layout
scene = context.scene
layout.prop(scene, "lutb_process_use_gpu")
layout.prop(scene, "lutb_combine_objects")
layout.prop(scene, "lutb_correct_colors")
layout.prop(scene, "lutb_use_color_variation")
col = layout.column()
col.prop(scene, "lutb_color_variation")
col.enabled = scene.lutb_use_color_variation
layout.prop(scene, "lutb_apply_vertex_colors")
layout.prop(scene, "lutb_setup_bake_mats")
col = layout.column()
col.prop(scene, "lutb_bake_mat", text="")
col.enabled = scene.lutb_setup_bake_mats
layout.prop(scene, "lutb_setup_bake_env")
col = layout.column()
col.prop(scene, "lutb_bake_env", text="")
col.enabled = scene.lutb_setup_bake_env
layout.prop(scene, "lutb_remove_hidden_faces")
col = layout.column()
col.prop(scene, "lutb_autoremove_hidden_faces", toggle=1)
col.prop(scene, "lutb_hidden_surfaces_tris_to_quads", toggle=1)
col.prop(scene, "lutb_pixels_between_verts", slider=True)
col.prop(scene, "lutb_hidden_surfaces_samples", slider=True)
col.enabled = scene.lutb_remove_hidden_faces
layout.operator("lutb.process_model")
class LUTB_OT_process_model(bpy.types.Operator):
"""Process LU model"""
bl_idname = "lutb.process_model"
bl_label = "Process Model"
@classmethod
def poll(cls, context):
return context.mode == "OBJECT"
def execute(self, context):
scene = context.scene
if not scene.lutb_bake_mat:
scene.lutb_bake_mat = bpy.data.materials.get("VertexColor")
if not scene.lutb_bake_env:
world = bpy.data.worlds.new(name="AmbientOcclusion")
world.use_nodes = True
world.node_tree.nodes["Background"].inputs["Color"].default_value = (1.0, 1.0, 1.0, 1.0)
scene.lutb_bake_env = world
scene.render.engine = "CYCLES"
scene.cycles.device = "GPU" if scene.lutb_process_use_gpu else "CPU"
if scene.lutb_combine_objects:
for collection in scene.collection.children:
if len(collection.all_objects) > 1:
bpy.ops.object.select_all(action="DESELECT")
for obj in collection.all_objects:
if obj.type == "MESH":
obj.select_set(True)
context.view_layer.objects.active = obj
bpy.ops.object.join()
matrix_world = context.object.matrix_world.copy()
context.object.parent = None
context.object.matrix_world = matrix_world
bpy.ops.object.transform_apply()
for obj in collection.all_objects:
if obj.type == "EMPTY":
bpy.data.objects.remove(obj)
if len(collection.objects) > 0:
obj = collection.objects[0]
obj.name = f"{collection.name}_LOD_0"
obj.data.name = obj.name
else:
self.report({"WARNING"}, f"Collection \"{collection.name}\" is empty.")
context.view_layer.update()
objects = list(filter(lambda obj: obj.type == "MESH", scene.collection.all_objects))
for obj in objects:
for material in obj.data.materials:
if scene.lutb_correct_colors:
name = material.name if not "." in material.name else material.name.split(".")[0]
if name in color_correction:
material.diffuse_color = color_correction[name]
if scene.lutb_use_color_variation:
color = Color(material.diffuse_color[:3])
gamma = color.v ** (1 / 2.224)
gamma += random.uniform(-scene.lutb_color_variation / 200, scene.lutb_color_variation / 200)
color.v = min(max(0, gamma), 1) ** 2.224
material.diffuse_color = (*color, 1.0)
if scene.lutb_apply_vertex_colors:
scene.render.engine = "CYCLES"
scene.cycles.bake_type = "DIFFUSE"
scene.render.bake.use_pass_direct = False
scene.render.bake.use_pass_indirect = False
scene.render.bake.use_pass_color = True
scene.render.bake.target = "VERTEX_COLORS"
for obj in objects:
mesh = obj.data
vc_lit = mesh.vertex_colors.get("Lit")
if not vc_lit:
vc_lit = mesh.vertex_colors.new(name="Lit")
vc_col = mesh.vertex_colors.get("Col")
if not vc_col:
vc_col = mesh.vertex_colors.new(name="Col")
mesh.vertex_colors.active = vc_col
context.view_layer.objects.active = obj
bpy.ops.object.select_all(action="DESELECT")
obj.select_set(True)
bpy.ops.object.bake(type="DIFFUSE")
context.area.spaces[0].shading.type = "SOLID"
context.area.spaces[0].shading.light = "FLAT"
context.area.spaces[0].shading.color_type = "VERTEX"
if scene.lutb_setup_bake_mats:
for obj in objects:
mesh = obj.data
mesh.materials.clear()
if scene.lutb_bake_mat:
mesh.materials.append(scene.lutb_bake_mat)
if scene.lutb_setup_bake_env:
if scene.lutb_bake_env:
scene.world = scene.lutb_bake_env
scene.render.engine = "CYCLES"
scene.cycles.bake_type = "COMBINED"
scene.render.bake.use_pass_direct = True
scene.render.bake.use_pass_indirect = True
scene.render.bake.use_pass_diffuse = True
scene.render.bake.use_pass_glossy = False
scene.render.bake.use_pass_transmission = True
scene.render.bake.use_pass_ambient_occlusion = True
scene.render.bake.use_pass_emit = True
scene.render.bake.target = "VERTEX_COLORS"
if scene.lutb_remove_hidden_faces:
for obj in objects:
context.view_layer.objects.active = obj
bpy.ops.object.select_all(action="DESELECT")
obj.select_set(True)
bpy.ops.lutb.remove_hidden_faces(
autoremove=scene.lutb_autoremove_hidden_faces,
tris_to_quads=scene.lutb_hidden_surfaces_tris_to_quads,
pixels_between_verts=scene.lutb_pixels_between_verts,
samples=scene.lutb_hidden_surfaces_samples,
)
bpy.ops.object.select_all(action="DESELECT")
for obj in objects:
obj.select_set(True)
return {"FINISHED"}
def register():
bpy.utils.register_class(LUTB_OT_process_model)
bpy.utils.register_class(LUTB_PT_process_model)
bpy.types.Scene.lutb_process_use_gpu = BoolProperty(name="Use GPU", default=True)
bpy.types.Scene.lutb_combine_objects = BoolProperty(name="Combine Objects", default=True)
bpy.types.Scene.lutb_correct_colors = BoolProperty(name="Correct Colors", default=True)
bpy.types.Scene.lutb_use_color_variation = BoolProperty(name="Apply Color Variation", default=True)
bpy.types.Scene.lutb_color_variation = FloatProperty(name="Color Variation", subtype="PERCENTAGE", min=0.0, soft_max=15.0, max=100.0, default=5.0)
bpy.types.Scene.lutb_apply_vertex_colors = BoolProperty(name="Apply Vertex Colors", default=True)
bpy.types.Scene.lutb_setup_bake_mats = BoolProperty(name="Setup Bake Materials", default=True)
bpy.types.Scene.lutb_bake_mat= PointerProperty(name="Bake Material", type=bpy.types.Material)
bpy.types.Scene.lutb_remove_hidden_faces = BoolProperty(name="Remove Hidden Faces", default=True)
bpy.types.Scene.lutb_autoremove_hidden_faces = BoolProperty(name="Autoremove", default=True)
bpy.types.Scene.lutb_hidden_surfaces_tris_to_quads = BoolProperty(name="Tris to Quads", default=True)
bpy.types.Scene.lutb_pixels_between_verts = IntProperty(name="Pixels Between Vertices", min=0, default=5, soft_max=15)
bpy.types.Scene.lutb_hidden_surfaces_samples = IntProperty(name="Samples", min=0, default=8, soft_max=32)
bpy.types.Scene.lutb_setup_bake_env = BoolProperty(name="Setup Bake Environment", default=True)
bpy.types.Scene.lutb_bake_env = PointerProperty(name="Bake Environment", type=bpy.types.World)
def unregister():
del bpy.types.Scene.lutb_process_use_gpu
del bpy.types.Scene.lutb_combine_objects
del bpy.types.Scene.lutb_correct_colors
del bpy.types.Scene.lutb_use_color_variation
del bpy.types.Scene.lutb_color_variation
del bpy.types.Scene.lutb_apply_vertex_colors
del bpy.types.Scene.lutb_setup_bake_mats
del bpy.types.Scene.lutb_bake_mat
del bpy.types.Scene.lutb_remove_hidden_faces
del bpy.types.Scene.lutb_autoremove_hidden_faces
del bpy.types.Scene.lutb_hidden_surfaces_tris_to_quads
del bpy.types.Scene.lutb_pixels_between_verts
del bpy.types.Scene.lutb_hidden_surfaces_samples
del bpy.types.Scene.lutb_setup_bake_env
del bpy.types.Scene.lutb_bake_env
bpy.utils.unregister_class(LUTB_PT_process_model)
bpy.utils.unregister_class(LUTB_OT_process_model)

View File

@@ -1,203 +0,0 @@
import bpy, bmesh
from mathutils import Vector
from bpy.props import IntProperty, FloatProperty, BoolProperty
import math
import numpy as np
class LUTB_OT_remove_hidden_faces(bpy.types.Operator):
bl_idname = "lutb.remove_hidden_faces"
bl_label = "Remove Hidden Faces"
autoremove : BoolProperty(default=True)
tris_to_quads : BoolProperty(default=True)
pixels_between_verts : IntProperty(min=0, default=5)
samples : IntProperty(min=0, default=8)
threshold : FloatProperty(min=0, default=0.01, max=1)
@classmethod
def poll(cls, context):
return context.object and context.object.type == "MESH" and context.mode == "OBJECT" and context.scene.render.engine == "CYCLES"
def execute(self, context):
scene = context.scene
obj = context.object
mesh = obj.data
if self.tris_to_quads:
bpy.ops.object.mode_set(mode="EDIT")
bpy.ops.mesh.tris_convert_to_quads()
bpy.ops.object.mode_set(mode="OBJECT")
bm = bmesh.new(use_operators=False)
bm.from_mesh(mesh)
faceCount = len(bm.faces)
for face in bm.faces:
if len(face.verts) > 4:
self.report({"ERROR"}, "Mesh needs to consist of tris or quads only!")
return {"CANCELLED"}
size = math.ceil(math.sqrt(faceCount))
quadrant_size = 2 + self.pixels_between_verts
size_pixels = size * quadrant_size
imageName = "LUTB_OVEREXPOSED_TARGET"
image = bpy.data.images.get(imageName)
if not image:
image = bpy.data.images.new(imageName, size_pixels, size_pixels, alpha=False, float_buffer=False)
elif image.size[0] != size_pixels or image.size[1] != size_pixels:
bpy.data.images.remove(image)
image = bpy.data.images.new(imageName, size_pixels, size_pixels, alpha=False, float_buffer=False)
uvlayer = bm.loops.layers.uv.active
if not uvlayer:
uvlayer = bm.loops.layers.uv.new()
pixelSize = 1 / size_pixels
pbv_p_1 = self.pixels_between_verts + 1
offsets = (
pixelSize * Vector((0 - 0.01 * pbv_p_1, 0 + 0.00 * pbv_p_1)),
pixelSize * Vector((1 + 1.00 * pbv_p_1, 0 + 0.00 * pbv_p_1)),
pixelSize * Vector((1 + 1.00 * pbv_p_1, 1 + 1.01 * pbv_p_1)),
pixelSize * Vector((0 - 0.01 * pbv_p_1, 1 + 1.01 * pbv_p_1)),
)
bm.faces.ensure_lookup_table()
for i, face in enumerate(bm.faces):
target = Vector((i % size, i // size)) * quadrant_size / size_pixels
for j, loop in enumerate(face.loops):
loop[uvlayer].uv = target + offsets[j]
bm.to_mesh(mesh)
# baking
originalMaterials = []
for i, material_slot in enumerate(obj.material_slots):
originalMaterials.append(material_slot.material)
obj.material_slots[i].material = getOverexposedMaterial(obj, image)
originalWorld = scene.world
scene.world = getOverexposedWorld()
originalSamples = scene.cycles.samples
scene.cycles.samples = self.samples
originalTarget = scene.render.bake.target
scene.render.bake.target = "IMAGE_TEXTURES"
passes = ("use_pass_direct", "use_pass_indirect", "use_pass_diffuse")
originalPasses = []
for p in passes:
originalPasses.append(getattr(scene.render.bake, p))
setattr(scene.render.bake, p, True)
context.view_layer.update()
bpy.ops.object.bake(type="DIFFUSE", margin=0, use_clear=True)
for i, material in enumerate(originalMaterials):
obj.material_slots[i].material = material
scene.world = originalWorld
scene.cycles.samples = originalSamples
scene.render.bake.target = originalTarget
for p, originalValue in zip(passes, originalPasses):
setattr(scene.render.bake, p, originalValue)
bm = bmesh.new(use_operators=False)
bm.from_mesh(mesh)
pixels = np.array(image.pixels)
size_sq = size ** 2
size_pixels_sq = size_pixels ** 2
sum_per_face = pixels.copy()
sum_per_face = np.reshape(sum_per_face, (size_pixels_sq, 4))
sum_per_face = np.delete(sum_per_face, 3, 1)
sum_per_face = np.reshape(sum_per_face, (size_pixels_sq // quadrant_size, quadrant_size * 3))
sum_per_face = np.sum(sum_per_face, axis=1)
sum_per_face = np.reshape(sum_per_face, (size_pixels, size))
sum_per_face = np.swapaxes(sum_per_face, 0, 1)
sum_per_face = np.reshape(sum_per_face, (size_sq, quadrant_size))
sum_per_face = np.sum(sum_per_face, axis=1)
sum_per_face = np.reshape(sum_per_face, (size, size))
sum_per_face = np.swapaxes(sum_per_face, 0, 1)
sum_per_face = np.reshape(sum_per_face, (1, size_sq))[0][:len(bm.faces)]
pixels_per_quad = quadrant_size ** 2
pixels_per_tri = (pixels_per_quad + quadrant_size) / 2
average_per_face = np.zeros(sum_per_face.shape)
for i, face in enumerate(bm.faces):
if len(face.verts) == 4:
average_per_face[i] = sum_per_face[i] / pixels_per_quad
else:
average_per_face[i] = sum_per_face[i] / pixels_per_tri
average_per_face = average_per_face / 3
if self.autoremove:
for face, value in reversed(list(zip(bm.faces, average_per_face))):
if value < self.threshold:
bm.faces.remove(face)
bm.to_mesh(mesh)
bm.free()
bpy.ops.object.mode_set(mode="EDIT")
bpy.ops.mesh.delete_loose(use_verts=True, use_edges=True, use_faces=False)
bpy.ops.object.mode_set(mode="OBJECT")
else:
bpy.ops.object.mode_set(mode="EDIT")
context.tool_settings.mesh_select_mode = (False, False, True)
bpy.ops.mesh.select_all(action="DESELECT")
bpy.ops.object.mode_set(mode="OBJECT")
for polygon, value in zip(mesh.polygons, average_per_face):
polygon.select = value < self.threshold
return {"FINISHED"}
def getOverexposedMaterial(obj, image):
name = "LUTB_overexposed"
material = bpy.data.materials.get(name)
if not material:
material = bpy.data.materials.new(name)
material.use_nodes = True
nodes = material.node_tree.nodes
node_texture = nodes.new("ShaderNodeTexImage")
node_texture.name = "LUTB_TARGET"
nodes = material.node_tree.nodes
node_texture = nodes["LUTB_TARGET"]
node_texture.image = image
return material
def getOverexposedWorld():
name = "LUTB_overexposed"
world = bpy.data.worlds.get(name)
if not world:
world = bpy.data.worlds.new(name)
world.use_nodes = True
nodes = world.node_tree.nodes
node_background = nodes["Background"]
node_background.inputs["Color"].default_value = (1, 1, 1, 1)
node_background.inputs["Strength"].default_value = 100000
return world
def register():
bpy.utils.register_class(LUTB_OT_remove_hidden_faces)
def unregister():
bpy.utils.unregister_class(LUTB_OT_remove_hidden_faces)

35
lu_toolbox/__init__.py Normal file
View File

@@ -0,0 +1,35 @@
bl_info = {
"name": "LU Toolbox",
"author": "Bobbe",
"version": (2, 4, 0),
"blender": (2, 93, 0),
"location": "3D View -> Sidebar -> LU Toolbox",
"category": "Import-Export",
"support": "COMMUNITY",
}
import importlib
module_names = (
"process_model",
"icon_render",
"bake_lighting",
"remove_hidden_faces",
"importldd"
)
modules = []
for module_name in module_names:
if module_name in locals():
modules.append(importlib.reload(locals()[module_name]))
else:
modules.append(importlib.import_module("." + module_name, package=__package__))
def register():
for module in modules:
module.register()
def unregister():
for module in modules:
module.unregister()

300
lu_toolbox/bake_lighting.py Normal file
View File

@@ -0,0 +1,300 @@
import bpy
from bpy.props import *
import numpy as np
from timeit import default_timer as timer
from .process_model import IS_TRANSPARENT
from .materials import get_lutb_force_white_mat
WHITE_AMBIENT = "LUTB_WHITE_AMBIENT"
class LUTB_PT_bake_lighting(bpy.types.Panel):
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = "LU Toolbox"
bl_label = "Bake Lighting"
def draw(self, context):
scene = context.scene
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
layout.operator("lutb.bake_lighting")
layout.separator()
layout.prop(scene, "lutb_bake_use_gpu")
layout.prop(scene, "lutb_bake_selected_only")
col = layout.column()
col.prop(scene, "lutb_bake_use_white_ambient")
col.active = not scene.lutb_bake_ao_only
layout.prop(scene, "lutb_bake_smooth_lit")
col = layout.column()
col.prop(scene, "lutb_bake_force_to_white")
col.active = not scene.lutb_bake_use_mat_override
layout.prop(scene, "lutb_bake_samples")
layout.prop(scene, "lutb_bake_fast_gi_bounces")
layout.prop(scene, "lutb_bake_glow_strength")
class LUTB_PT_bake_ao_only(bpy.types.Panel):
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = "LU Toolbox"
bl_label = "AO Only"
bl_parent_id = "LUTB_PT_bake_lighting"
bl_options = {"DEFAULT_CLOSED"}
def draw_header(self, context):
self.layout.prop(context.scene, "lutb_bake_ao_only", text="")
def draw(self, context):
scene = context.scene
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
layout.active = scene.lutb_bake_ao_only
layout.prop(scene, "lutb_bake_glow_multiplier")
layout.prop(scene, "lutb_bake_ao_samples")
class LUTB_PT_bake_mat_override(bpy.types.Panel):
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = "LU Toolbox"
bl_label = "Material Override"
bl_parent_id = "LUTB_PT_bake_lighting"
bl_options = {"DEFAULT_CLOSED"}
def draw_header(self, context):
self.layout.prop(context.scene, "lutb_bake_use_mat_override", text="")
def draw(self, context):
scene = context.scene
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
layout.active = scene.lutb_bake_use_mat_override
layout.prop(scene, "lutb_bake_mat_override", text="")
class LUTB_OT_bake_lighting(bpy.types.Operator):
"""Bake scene lighting to vertex color layer named \"Lit\" on all selected objects"""
bl_idname = "lutb.bake_lighting"
bl_label = "Bake Lighting"
@classmethod
def poll(cls, context):
return context.mode == "OBJECT"
def execute(self, context):
start = timer()
scene = context.scene
scene_override = scene.copy()
render = scene_override.render
cycles = scene_override.cycles
render.engine = "CYCLES"
cycles.use_denoising = False
cycles.bake_type = "COMBINED"
cycles.caustics_reflective = False
cycles.caustics_refractive = False
render.bake.use_pass_direct = True
render.bake.use_pass_indirect = True
render.bake.use_pass_diffuse = True
render.bake.use_pass_glossy = False
render.bake.use_pass_transmission = True
render.bake.use_pass_emit = True
render.bake.target = "VERTEX_COLORS"
cycles.use_fast_gi = True
cycles.ao_bounces_render = scene.lutb_bake_fast_gi_bounces
cycles.device = "GPU" if scene.lutb_process_use_gpu else "CPU"
cycles.samples = scene.lutb_bake_samples
if scene.lutb_bake_use_white_ambient:
if not (world := bpy.data.worlds.get(WHITE_AMBIENT)):
world = bpy.data.worlds.new(WHITE_AMBIENT)
world.color = (1.0, 1.0, 1.0)
scene_override.world = world
emission_strength = scene.lutb_bake_glow_strength
ao_only_world_override = None
if scene.lutb_bake_ao_only:
cycles.max_bounces = 0
cycles.fast_gi_method = "ADD"
cycles.samples = scene.lutb_bake_ao_samples
ao_only_world_override = bpy.data.worlds.new("AO_ONLY")
ao_only_world_override.color = (0.0, 0.0, 0.0)
ao_only_world_override.light_settings.ao_factor = 1.0
ao_only_world_override.light_settings.distance = 5.0
scene_override.world = ao_only_world_override
emission_strength *= scene.lutb_bake_glow_multiplier
hidden_objects = []
for obj in list(scene.collection.all_objects):
if obj.type == "MESH" and obj.get(IS_TRANSPARENT) and not obj.hide_render:
obj.hide_render = True
hidden_objects.append(obj)
target_objects = scene.collection.all_objects
if scene.lutb_bake_selected_only:
target_objects = context.selected_objects
old_active_obj = context.object
old_selected_objects = context.selected_objects
for obj in list(target_objects):
if obj.type != "MESH" or obj.get(IS_TRANSPARENT):
continue
if not obj.name in context.view_layer.objects:
self.report({"WARNING"}, f"Skipping \"{obj.name}\". (not in viewlayer)")
continue
mesh = obj.data
if not mesh.materials:
self.report({"WARNING"}, f"Skipping \"{obj.name}\". (has no materials)")
continue
triangulate_mods = [mod for mod in obj.modifiers if mod.type == "TRIANGULATE"]
for modifier in triangulate_mods:
modifier.show_render = False
if not triangulate_mods or not obj.modifiers[-1] in triangulate_mods:
modifier = obj.modifiers.new("Triangulate", "TRIANGULATE")
modifier.show_render = False
if vc_lit := mesh.vertex_colors.get("Lit"):
mesh.vertex_colors.active_index = mesh.vertex_colors.keys().index(vc_lit.name)
other_lod_colls = set()
for lod_collection in obj.users_collection:
for collection in bpy.data.collections:
if lod_collection.name in collection.children:
other_lod_colls |= set(collection.children) - {lod_collection,}
for other_lod_coll in list(other_lod_colls):
if other_lod_coll.hide_render:
other_lod_colls.remove(other_lod_coll)
else:
other_lod_coll.hide_render = True
old_material = mesh.materials[0]
if scene.lutb_bake_use_mat_override:
mesh.materials[0] = scene.lutb_bake_mat_override
elif scene.lutb_bake_force_to_white:
if material := get_lutb_force_white_mat(self):
mesh.materials[0] = material
if mesh.materials[0].use_nodes:
for node in mesh.materials[0].node_tree.nodes:
if node.type == "BSDF_PRINCIPLED":
node.inputs['Emission Strength'].default_value = emission_strength
bpy.ops.object.select_all(action="DESELECT")
obj.select_set(True)
context.view_layer.objects.active = obj
context_override = context.copy()
context_override["scene"] = scene_override
try:
bpy.ops.object.bake(context_override)
except RuntimeError as e:
if "is not enabled for rendering" in str(e):
self.report({"WARNING"}, f"Skipping \"{obj.name}\". (not enabled for rendering)")
continue
else:
raise
finally:
mesh.materials[0] = old_material
for other_lod_coll in other_lod_colls:
other_lod_coll.hide_render = False
has_edge_split_modifier = "EDGE_SPLIT" in {mod.type for mod in obj.modifiers}
if scene.lutb_bake_smooth_lit and not has_edge_split_modifier:
bpy.ops.object.mode_set(mode="VERTEX_PAINT")
if mesh.use_paint_mask or mesh.use_paint_mask_vertex:
bpy.ops.paint.vert_select_all(action="SELECT")
bpy.ops.paint.vertex_color_smooth()
bpy.ops.object.mode_set(mode="OBJECT")
if vc_lit and (vc_alpha := mesh.vertex_colors.get("Alpha")):
n_loops = len(mesh.loops)
lit_data = np.empty(n_loops * 4)
alpha_data = np.empty(n_loops * 4)
vc_lit.data.foreach_get("color", lit_data)
vc_alpha.data.foreach_get("color", alpha_data)
lit_data = lit_data.reshape((n_loops, 4))
lit_data[:, 3] = alpha_data.reshape((n_loops, 4))[:, 0]
vc_lit.data.foreach_set("color", lit_data.flatten())
bpy.data.scenes.remove(scene_override)
if ao_only_world_override:
bpy.data.worlds.remove(ao_only_world_override)
for obj in hidden_objects:
obj.hide_render = False
bpy.ops.object.select_all(action="DESELECT")
for obj in old_selected_objects:
obj.select_set(True)
context.view_layer.objects.active = old_active_obj
end = timer()
print(f"finished bake lighting in {end - start:.2f}s")
return {"FINISHED"}
def register():
bpy.utils.register_class(LUTB_OT_bake_lighting)
bpy.utils.register_class(LUTB_PT_bake_lighting)
bpy.utils.register_class(LUTB_PT_bake_ao_only)
bpy.utils.register_class(LUTB_PT_bake_mat_override)
bpy.types.Scene.lutb_bake_use_gpu = BoolProperty(name="Use GPU", default=True)
bpy.types.Scene.lutb_bake_selected_only = BoolProperty(name="Selected Only")
bpy.types.Scene.lutb_bake_smooth_lit = BoolProperty(name="Smooth Vertex Colors", default=True)
bpy.types.Scene.lutb_bake_samples = IntProperty(name="Samples", default=256, min=1, description=""\
"Number of samples to render for each vertex")
bpy.types.Scene.lutb_bake_fast_gi_bounces = IntProperty(name="Fast GI Bounces", default=3, min=0)
bpy.types.Scene.lutb_bake_glow_strength = FloatProperty(name="Glow Strength Global", default=3.0, min=0, soft_min=0.5, soft_max=5.0)
bpy.types.Scene.lutb_bake_use_white_ambient = BoolProperty(name="White Ambient", default=True, description=""\
"Sets ambient light to pure white while baking")
bpy.types.Scene.lutb_bake_ao_only = BoolProperty(name="AO Only", default=True)
bpy.types.Scene.lutb_bake_glow_multiplier = FloatProperty(name="Glow Multiplier Global", default=2.0, min=0, soft_min=0.5, soft_max=5.0)
bpy.types.Scene.lutb_bake_ao_samples = IntProperty(name="AO Samples", default=64, min=1)
bpy.types.Scene.lutb_bake_use_mat_override = BoolProperty(name="Material Override")
bpy.types.Scene.lutb_bake_force_to_white = BoolProperty(name="Force to White")
bpy.types.Scene.lutb_bake_mat_override = PointerProperty(name="Override Material", type=bpy.types.Material)
def unregister():
del bpy.types.Scene.lutb_bake_use_gpu
del bpy.types.Scene.lutb_bake_selected_only
del bpy.types.Scene.lutb_bake_smooth_lit
del bpy.types.Scene.lutb_bake_samples
del bpy.types.Scene.lutb_bake_fast_gi_bounces
del bpy.types.Scene.lutb_bake_glow_strength
del bpy.types.Scene.lutb_bake_use_white_ambient
del bpy.types.Scene.lutb_bake_ao_only
del bpy.types.Scene.lutb_bake_force_to_white
del bpy.types.Scene.lutb_bake_glow_multiplier
del bpy.types.Scene.lutb_bake_ao_samples
del bpy.types.Scene.lutb_bake_use_mat_override
del bpy.types.Scene.lutb_bake_mat_override
bpy.utils.unregister_class(LUTB_PT_bake_mat_override)
bpy.utils.unregister_class(LUTB_PT_bake_ao_only)
bpy.utils.unregister_class(LUTB_PT_bake_lighting)
bpy.utils.unregister_class(LUTB_OT_bake_lighting)

51
lu_toolbox/divide_mesh.py Normal file
View File

@@ -0,0 +1,51 @@
import bpy, bmesh
import numpy as np
def divide_mesh(context, mesh_obj, max_verts=65536, min_div_rate=0.1):
def divide_rec(obj):
mesh = obj.data
n_verts = len(mesh.vertices)
if n_verts < max_verts:
return []
buffer_co = np.empty(n_verts * 3)
mesh.vertices.foreach_get("co", buffer_co)
vecs = buffer_co.reshape((n_verts, 3))
mean = np.sum(vecs, axis=0) / n_verts
bound_box = np.array(obj.bound_box)
target_axis = np.argmax((bound_box[0] - bound_box[6]) ** 2)
select = vecs[:,target_axis] < mean[target_axis]
bpy.ops.object.select_all(action="DESELECT")
obj.select_set(True)
context.view_layer.objects.active = obj
bpy.ops.object.mode_set(mode="EDIT")
bpy.ops.mesh.select_all(action="DESELECT")
bm = bmesh.from_edit_mesh(mesh)
for vert, v_select in zip(bm.verts, select):
vert.select = v_select
bm.select_mode = {"VERT"}
bm.select_flush_mode()
bmesh.update_edit_mesh(mesh)
bm.free()
bpy.ops.mesh.select_linked()
bpy.ops.mesh.separate()
bpy.ops.object.mode_set(mode="OBJECT")
new_obj = (set(context.selected_objects) - set((obj,))).pop()
div_rate = len(new_obj.data.vertices) / n_verts
div_rate = min(div_rate, 1 - div_rate)
if div_rate < min_div_rate:
raise Exception(f"fatal: failed to divide mesh: {div_rate} < {min_div_rate} (div_rate < min_div_rate)")
return divide_rec(obj) + divide_rec(new_obj) + [new_obj]
new_objects = divide_rec(mesh_obj)
return new_objects

209
lu_toolbox/icon_render.py Normal file
View File

@@ -0,0 +1,209 @@
import bpy, bmesh
from bpy.props import BoolProperty
from mathutils import Vector, Matrix
from math import radians
import numpy as np
from .process_model import LOD_SUFFIXES
from .materials import *
class LUTB_OT_setup_icon_render(bpy.types.Operator):
"""Setup Icon Render for LU Model"""
bl_idname = "lutb.setup_icon_render"
bl_label = "Setup Icon Render"
@classmethod
def poll(cls, context):
return context.mode == "OBJECT"
def execute(self, context):
# we will only increase the sample count for rendering if we need to (currently only if there are any transparent pieces) - jamie
increase_samples = False
scene = context.scene
for collection in scene.collection.children:
for lod_collection in collection.children[:]:
if lod_collection.name[-5:] in LOD_SUFFIXES[1:]:
collection.children.unlink(lod_collection)
combine_objects_before = scene.lutb_combine_objects
scene.lutb_combine_objects = False
apply_vertex_colors_before = scene.lutb_apply_vertex_colors
scene.lutb_apply_vertex_colors = True
correct_colors_before = scene.lutb_correct_colors
scene.lutb_correct_colors = scene.lutb_ir_correct_colors
color_variation_before = scene.lutb_color_variation
scene.lutb_color_variation = scene.lutb_ir_color_variation
setup_bake_mat_before = scene.lutb_setup_bake_mat
scene.lutb_setup_bake_mat = False
remove_hidden_faces_before = scene.lutb_remove_hidden_faces
scene.lutb_remove_hidden_faces = False
# hacky way to inject modified color corrections
if scene.lutb_ir_correct_colors:
color_corrections = (
[MATERIALS_OPAQUE, ICON_MATERIALS_OPAQUE, None],
[MATERIALS_TRANSPARENT, ICON_MATERIALS_TRANSPARENT, None],
[MATERIALS_GLOW, ICON_MATERIALS_GLOW, None],
[MATERIALS_METALLIC, ICON_MATERIALS_METALLIC, None],
)
for color_correction in color_corrections:
target, updates, _ = color_correction
color_correction[2] = target.copy()
target.update(updates)
bpy.ops.lutb.process_model()
if scene.lutb_ir_correct_colors:
for target, _, original in color_corrections:
target.update(original)
scene.lutb_combine_objects = combine_objects_before
scene.lutb_apply_vertex_colors = apply_vertex_colors_before
scene.lutb_correct_colors = correct_colors_before
scene.lutb_color_variation = color_variation_before
scene.lutb_setup_bake_mat = setup_bake_mat_before
scene.lutb_remove_hidden_faces = remove_hidden_faces_before
bpy.ops.object.mode_set(mode="EDIT")
bpy.ops.mesh.select_all(action="SELECT")
bpy.ops.mesh.tris_convert_to_quads(shape_threshold=radians(50))
for obj in context.selected_objects:
if obj.type != "MESH":
continue
bm = bmesh.from_edit_mesh(obj.data)
bevel_weight = bm.edges.layers.bevel_weight.new("Bevel Weight")
for edge in bm.edges:
if len(edge.link_faces) == 1:
edge[bevel_weight] = 1.0
bmesh.update_edit_mesh(obj.data, loop_triangles=False, destructive=False)
bpy.ops.mesh.select_all(action="SELECT")
bpy.ops.mesh.remove_doubles()
bpy.ops.object.mode_set(mode="OBJECT")
for obj in context.selected_objects:
if obj.type != "MESH":
continue
if scene.lutb_ir_bevel_edges:
bevel_mod = obj.modifiers.new("Bevel", "BEVEL")
bevel_mod.width = 0.02
bevel_mod.segments = 4
bevel_mod.limit_method = "WEIGHT"
bevel_mod.harden_normals = True
if scene.lutb_ir_subdivide:
brick_id = obj.name.split("brick_")[-1].split("_")[1]
if not brick_id in ICON_RENDER_DISABLE_SUBDIV:
subdiv_mod = obj.modifiers.new("Subdivision", "SUBSURF")
subdiv_mod.levels = 1
subdiv_mod.render_levels = 2
mesh = obj.data
obj.data.use_auto_smooth = True
obj.data.auto_smooth_angle = radians(180)
for i, material in enumerate(mesh.materials):
name = material.name.rsplit(".", 1)[0]
if name in MATERIALS_OPAQUE:
mesh.materials[i] = get_lutb_ir_opaque_mat(self)
# another hack to change a material setting, for the same reasons as the comment below...
# anyway, it was deemed that having this enabled was making the plastic look too soft and washed-out in many scenarios - jamie
if mesh.materials[i].node_tree:
for node in mesh.materials[i].node_tree.nodes:
if node.type == "BSDF_PRINCIPLED":
node.inputs["Subsurface"].default_value = 0
elif name in MATERIALS_TRANSPARENT:
mesh.materials[i] = get_lutb_ir_transparent_mat(self)
# next two lines are a silly hacky fix cause i dont wanna mess with the magical mystery box that is resources.blend, and hollis didnt know why it was broken anyway - jamie
mesh.materials[i].blend_method = "HASHED"
mesh.materials[i].shadow_method = "HASHED"
increase_samples = True
elif name in MATERIALS_METALLIC:
mesh.materials[i] = get_lutb_ir_metal_mat(self)
ir_scene = get_lutb_ir_scene(self)
for collection in scene.collection.children[:]:
for obj in collection.objects:
if obj.type == "EMPTY" and obj.name.startswith("SceneNode_"):
break
else:
continue
lod_collection = collection.children[0]
obj_bounds = np.empty((len(lod_collection.objects) * 2, 3))
for i, obj in enumerate(lod_collection.objects):
obj_bounds[i * 2 + 0] = obj.matrix_world @ Vector(obj.bound_box[0])
obj_bounds[i * 2 + 1] = obj.matrix_world @ Vector(obj.bound_box[6])
dimensions = obj_bounds.max(0) - obj_bounds.min(0)
offset = Matrix.Translation(-(obj_bounds.min(0) + dimensions * 0.5))
scale = Matrix.Scale(1 / np.abs(dimensions).max(), 4)
for obj in lod_collection.objects:
obj.matrix_world = scale @ offset @ obj.matrix_world
scene.collection.children.unlink(collection)
ir_scene.collection.children.link(collection)
context.window.scene = ir_scene
for area in context.screen.areas:
if area.type == "VIEW_3D":
area.spaces[0].shading.type = "RENDERED"
if increase_samples:
bpy.context.scene.eevee.taa_render_samples = 1024
return {"FINISHED"}
class LUTB_PT_icon_render(bpy.types.Panel):
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = "LU Icon Render"
bl_label = "Icon Render"
def draw(self, context):
scene = context.scene
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
layout.operator(LUTB_OT_setup_icon_render.bl_idname)
layout.separator(factor=0.5)
layout.prop(scene, "lutb_ir_correct_colors")
layout.prop(scene, "lutb_ir_color_variation")
layout.prop(scene, "lutb_ir_bevel_edges")
layout.prop(scene, "lutb_ir_subdivide")
def register():
bpy.utils.register_class(LUTB_OT_setup_icon_render)
bpy.utils.register_class(LUTB_PT_icon_render)
bpy.types.Scene.lutb_ir_correct_colors = BoolProperty(name="Correct Colors", default=True,
description=bpy.types.Scene.lutb_correct_colors.keywords["description"])
bpy.types.Scene.lutb_ir_color_variation = BoolProperty(name="Apply Color Variation", default=False,
description=bpy.types.Scene.lutb_use_color_variation.keywords["description"])
bpy.types.Scene.lutb_ir_bevel_edges = BoolProperty(name="Bevel Edges", default=True)
bpy.types.Scene.lutb_ir_subdivide = BoolProperty(name="Subdivide", default=True)
def unregister():
del bpy.types.Scene.lutb_ir_correct_colors
del bpy.types.Scene.lutb_ir_color_variation
del bpy.types.Scene.lutb_ir_bevel_edges
del bpy.types.Scene.lutb_ir_subdivide
bpy.utils.unregister_class(LUTB_PT_icon_render)
bpy.utils.unregister_class(LUTB_OT_setup_icon_render)

1182
lu_toolbox/importldd.py Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,300 @@
from pathlib import Path
import bpy
from .color_conversions import *
LUTB_BAKE_MAT = "VertexColor"
LUTB_TRANSPARENT_MAT = "VertexColorTransparent"
LUTB_FORCE_WHITE_MAT = "ForceWhite"
LUTB_OTHER_MATS = ["VertexColorAO"]
LUTB_BAKE_MATS = (LUTB_BAKE_MAT, LUTB_TRANSPARENT_MAT, LUTB_FORCE_WHITE_MAT, *LUTB_OTHER_MATS)
LUTB_IR_OPAQUE_MAT = "ItemRender_Opaque"
LUTB_IR_TRANSPARENT_MAT = "ItemRender_Transparent"
LUTB_IR_METAL_MAT = "ItemRender_Metal"
LUTB_IR_MATS = (LUTB_IR_OPAQUE_MAT, LUTB_IR_TRANSPARENT_MAT, LUTB_IR_METAL_MAT)
LUTB_IR_SCENE = "ItemRender"
# COLORS HERE ARE EXPECTED TO BE IN LINEAR COLOR SPACE
# IF YOU INPUT AN SRGB COLOR LIKE LU/LDD USE IT MUST BE CONVERTED TO LINEAR EITHER MANUALLY OR BY CALLING SRGB2LIN AS YOU WILL OCCASIONALLY SEE BELOW
# SIGNED, JAMIE (WHO WAS A SMIDGE ANNOYED AT THIS BUT ULTIMATELY PASSES NO JUDGEMENT ON THE AUTHORS OF THIS TOOL)
# Solid/Opaque
MATERIALS_OPAQUE = {
"1" : (0.904661, 0.904661, 0.904661, 1.0),
"5" : (0.693872, 0.491021, 0.194618, 1.0),
"18" : (0.672443, 0.168269, 0.051269, 1.0),
"21" : (0.730461, 0.0, 0.004025, 1.0),
"23" : (0.0, 0.095307, 0.391572, 1.0),
"24" : (0.991102, 0.552011, 0.0, 1.0),
"26" : (0.006, 0.006, 0.006, 1.0),
"28" : (0.0, 0.198069, 0.021219, 1.0),
"37" : (0.0, 0.304987, 0.017642, 1.0),
"38" : (0.391572, 0.046665, 0.007499, 1.0),
"50" : (0.7399, 0.7399, 0.7399, 1.0),
"102" : (0.06301, 0.262251, 0.564712, 1.0),
"106" : (0.799103, 0.124772, 0.009134, 1.0),
"107" : (0.002732, 0.462077, 0.462077, 1.0),
"119" : (0.296138, 0.47932, 0.003035, 1.0),
"120" : (0.672444, 0.768151, 0.262251, 1.0),
"124" : (0.332452, 0.0, 0.147027, 1.0),
"135" : (0.111932, 0.174647, 0.262251, 1.0),
"138" : (0.262251, 0.177888, 0.084376, 1.0),
"140" : (0.0, 0.0185, 0.052861, 1.0),
"141" : (0.0, 0.03434, 0.008023, 1.0),
"151" : (0.114435, 0.223228, 0.130137, 1.0),
"154" : (0.215861, 0.002428, 0.01096, 1.0),
"191" : (0.887923, 0.318547, 0.0, 1.0),
"192" : (0.104617, 0.011612, 0.003677, 1.0),
"194" : (0.332452, 0.283149, 0.283149, 1.0),
"199" : (0.072272, 0.082283, 0.093059, 1.0),
"208" : (0.768151, 0.768151, 0.693872, 1.0),
"212" : (0.242281, 0.520996, 0.83077, 1.0),
"221" : (0.730461, 0.038204, 0.258183, 1.0),
"222" : (0.846873, 0.341914, 0.53948, 1.0),
"226" : (1.0, 0.768151, 0.141263, 1.0),
"268" : (0.066626, 0.011612, 0.341914, 1.0),
"283" : (0.913099, 0.533276, 0.250158, 1.0),
"294" : (0.991102, 0.973445, 0.665388, 1.0),
"308" : (0.03434, 0.015209, 0.0, 1.0),
"329" : (1.0, 1.0, 1.0, 1.0),
"330" : (0.184475, 0.184475, 0.076185, 1.0),
"9013" : (1.0, 0.009721, 0.020289, 1.0),
"9014" : (0.799103, 0.088655, 0.0, 1.0),
"9015" : (1.0, 0.630757, 0.033105, 1.0),
"9016" : (0.012983, 1.0, 0.090842, 1.0),
"9017" : (0.059511, 0.768152, 0.913099, 1.0),
"9018" : (0.0, 0.226966, 1.0, 1.0),
"9019" : (0.40724, 0.012983, 1.0, 1.0),
"9020" : (0.686686, 0.000303, 1.0, 1.0),
}
# Duplicate Solid/Opaque
MATERIALS_OPAQUE["0"] = MATERIALS_OPAQUE["26"]
MATERIALS_OPAQUE["2"] = MATERIALS_OPAQUE["194"]
MATERIALS_OPAQUE["3"] = MATERIALS_OPAQUE["5"]
MATERIALS_OPAQUE["4"] = MATERIALS_OPAQUE["106"]
MATERIALS_OPAQUE["6"] = MATERIALS_OPAQUE["119"]
MATERIALS_OPAQUE["9"] = MATERIALS_OPAQUE["222"]
MATERIALS_OPAQUE["11"] = MATERIALS_OPAQUE["212"]
MATERIALS_OPAQUE["12"] = MATERIALS_OPAQUE["106"]
MATERIALS_OPAQUE["13"] = MATERIALS_OPAQUE["191"]
MATERIALS_OPAQUE["19"] = MATERIALS_OPAQUE["191"]
MATERIALS_OPAQUE["22"] = MATERIALS_OPAQUE["221"]
MATERIALS_OPAQUE["25"] = MATERIALS_OPAQUE["192"]
MATERIALS_OPAQUE["27"] = MATERIALS_OPAQUE["199"]
MATERIALS_OPAQUE["29"] = MATERIALS_OPAQUE["151"]
MATERIALS_OPAQUE["36"] = MATERIALS_OPAQUE["283"]
MATERIALS_OPAQUE["39"] = MATERIALS_OPAQUE["194"]
MATERIALS_OPAQUE["45"] = MATERIALS_OPAQUE["212"]
MATERIALS_OPAQUE["100"] = MATERIALS_OPAQUE["283"]
MATERIALS_OPAQUE["101"] = MATERIALS_OPAQUE["106"]
MATERIALS_OPAQUE["103"] = MATERIALS_OPAQUE["194"]
MATERIALS_OPAQUE["104"] = MATERIALS_OPAQUE["268"]
MATERIALS_OPAQUE["105"] = MATERIALS_OPAQUE["191"]
MATERIALS_OPAQUE["110"] = MATERIALS_OPAQUE["23"]
MATERIALS_OPAQUE["112"] = MATERIALS_OPAQUE["23"]
MATERIALS_OPAQUE["115"] = MATERIALS_OPAQUE["119"]
MATERIALS_OPAQUE["116"] = MATERIALS_OPAQUE["107"]
MATERIALS_OPAQUE["118"] = MATERIALS_OPAQUE["212"]
MATERIALS_OPAQUE["326"] = MATERIALS_OPAQUE["120"]
MATERIALS_OPAQUE["125"] = MATERIALS_OPAQUE["283"]
MATERIALS_OPAQUE["128"] = MATERIALS_OPAQUE["38"]
MATERIALS_OPAQUE["133"] = MATERIALS_OPAQUE["106"]
MATERIALS_OPAQUE["134"] = MATERIALS_OPAQUE["119"]
MATERIALS_OPAQUE["136"] = MATERIALS_OPAQUE["135"]
MATERIALS_OPAQUE["153"] = MATERIALS_OPAQUE["138"]
MATERIALS_OPAQUE["180"] = MATERIALS_OPAQUE["191"]
MATERIALS_OPAQUE["195"] = MATERIALS_OPAQUE["23"]
MATERIALS_OPAQUE["196"] = MATERIALS_OPAQUE["23"]
MATERIALS_OPAQUE["198"] = MATERIALS_OPAQUE["124"]
MATERIALS_OPAQUE["216"] = MATERIALS_OPAQUE["154"]
MATERIALS_OPAQUE["217"] = MATERIALS_OPAQUE["138"]
MATERIALS_OPAQUE["218"] = MATERIALS_OPAQUE["124"]
MATERIALS_OPAQUE["219"] = MATERIALS_OPAQUE["268"]
MATERIALS_OPAQUE["223"] = MATERIALS_OPAQUE["222"]
MATERIALS_OPAQUE["232"] = MATERIALS_OPAQUE["212"]
MATERIALS_OPAQUE["233"] = MATERIALS_OPAQUE["37"]
MATERIALS_OPAQUE["295"] = MATERIALS_OPAQUE["222"]
MATERIALS_OPAQUE["312"] = MATERIALS_OPAQUE["138"]
MATERIALS_OPAQUE["321"] = MATERIALS_OPAQUE["102"]
MATERIALS_OPAQUE["322"] = MATERIALS_OPAQUE["212"]
MATERIALS_OPAQUE["323"] = MATERIALS_OPAQUE["208"]
MATERIALS_OPAQUE["324"] = MATERIALS_OPAQUE["124"]
MATERIALS_OPAQUE["325"] = MATERIALS_OPAQUE["222"]
# Transparent
MATERIALS_TRANSPARENT = {
"20" : (0.930111, 0.672443, 0.250158, 1.0),
"40" : (0.854993, 0.854993, 0.854993, 1.0),
"41" : srgb2lin((0.674509, 0.0, 0.0, 1.0)),
"42" : srgb2lin((0.244106, 0.720966, 0.772058, 1.0)),
"43" : srgb2lin((0.031372, 0.285668, 0.643137, 1.0)),
"44" : srgb2lin((0.858, 0.771375, 0.0, 1.0)),
"47" : srgb2lin((0.986, 0.336526, 0.120035, 1.0)),
"48" : srgb2lin((0.0, 0.391, 0.0, 1.0)),
"49" : srgb2lin((0.697, 1.0, 0.0, 1.0)),
"111" : srgb2lin((0.741177, 0.670588, 0.639216, 1.0)),
"113" : srgb2lin((0.754717, 0.060520, 0.541647, 1.0)),
"126" : srgb2lin((0.267974, 0.196078, 0.627451, 1.0)),
"143" : srgb2lin((0.325985, 0.551358, 0.821, 1.0)),
"182" : srgb2lin((0.913726, 0.524575, 0.0156863, 1.0)),
"311" : srgb2lin((0.454640, 0.788235, 0.0980392, 1.0)),
}
# Duplicate Transparent
MATERIALS_TRANSPARENT["157"] = MATERIALS_TRANSPARENT["44"]
MATERIALS_TRANSPARENT["230"] = MATERIALS_TRANSPARENT["113"]
MATERIALS_TRANSPARENT["231"] = MATERIALS_TRANSPARENT["182"]
MATERIALS_TRANSPARENT["234"] = MATERIALS_TRANSPARENT["44"]
MATERIALS_TRANSPARENT["284"] = MATERIALS_TRANSPARENT["126"]
MATERIALS_TRANSPARENT["285"] = MATERIALS_TRANSPARENT["111"]
MATERIALS_TRANSPARENT["293"] = MATERIALS_TRANSPARENT["43"]
# Glow
MATERIALS_GLOW = {
"50" : (0.401978, 0.401978, 0.401978, 1.0),
"329" : MATERIALS_OPAQUE["329"],
"294" : MATERIALS_OPAQUE["294"],
"9013" : MATERIALS_OPAQUE["9013"],
"9014" : MATERIALS_OPAQUE["9014"],
"9015" : MATERIALS_OPAQUE["9015"],
"9016" : MATERIALS_OPAQUE["9016"],
"9017" : MATERIALS_OPAQUE["9017"],
"9018" : MATERIALS_OPAQUE["9018"],
"9019" : MATERIALS_OPAQUE["9019"],
"9020" : MATERIALS_OPAQUE["9020"],
}
# Duplicate Glow
MATERIALS_GLOW["9000"] = MATERIALS_GLOW["9020"]
MATERIALS_GLOW["9002"] = MATERIALS_GLOW["9016"]
MATERIALS_GLOW["9004"] = MATERIALS_GLOW["9018"]
MATERIALS_GLOW["9008"] = MATERIALS_GLOW["9013"]
MATERIALS_GLOW["9009"] = MATERIALS_GLOW["9014"]
MATERIALS_GLOW["9010"] = MATERIALS_GLOW["9016"]
MATERIALS_GLOW["9011"] = MATERIALS_GLOW["9017"]
MATERIALS_GLOW["9012"] = MATERIALS_GLOW["9019"]
MATERIALS_GLOW["9021"] = MATERIALS_GLOW["329"]
MATERIALS_GLOW["9022"] = MATERIALS_GLOW["50"]
MATERIALS_GLOW["9023"] = MATERIALS_GLOW["9013"]
MATERIALS_GLOW["9024"] = MATERIALS_GLOW["9014"]
MATERIALS_GLOW["9025"] = MATERIALS_GLOW["9016"]
MATERIALS_GLOW["9026"] = MATERIALS_GLOW["9018"]
MATERIALS_GLOW["9027"] = MATERIALS_GLOW["329"]
# Metallic
MATERIALS_METALLIC = {
("131", "150", "179", "298", "315") : (0.262251, 0.296138, 0.296138, 1.0),
("139", "187", "300") : (0.174648, 0.066626, 0.029557, 1.0),
"148" : (0.06301, 0.051269, 0.043735, 1.0),
"149" : (0.006, 0.006, 0.006, 1.0),
"184" : (0.238095, 0.00907, 0.00907, 1.0),
("186", "200") : (0.081104, 0.252379, 0.045668, 1.0),
("145", "185") : (0.104617, 0.177888, 0.278894, 1.0),
("309", "183") : (0.617207, 0.617207, 0.617207, 1.0),
("297", "147", "189") : (0.401978, 0.212231, 0.027321, 1.0),
("310", "127", ) : (0.737911, 0.533276, 0.181164, 1.0),
}
CUSTOM_VARIATION = {
"1" : 1.3,
"21" : 1.4,
"23" : 1.25,
"24" : 1.5,
"26" : 0.4,
"28" : 0.8,
"37" : 0.8,
"135" : 0.85,
"141" : 0.7,
"199" : 0.7,
"192" : 0.75,
"212" : 1.25,
"222" : 1.05,
"226" : 1.75,
"283" : 1.15,
"308" : 0.85,
"323" : 1.4,
"326" : 1.75,
}
ICON_MATERIALS_OPAQUE = {
"1" : srgb2lin((0.7, 0.7, 0.7, 1.0)),
"26" : srgb2lin((0.01, 0.01, 0.01, 1.0)),
}
ICON_MATERIALS_TRANSPARENT = {
}
ICON_MATERIALS_GLOW = {
}
ICON_MATERIALS_METALLIC = {
}
ICON_RENDER_DISABLE_SUBDIV = {
}
dicts = (
MATERIALS_OPAQUE, MATERIALS_TRANSPARENT, MATERIALS_GLOW, MATERIALS_METALLIC,
ICON_MATERIALS_OPAQUE, ICON_MATERIALS_TRANSPARENT, ICON_MATERIALS_GLOW,
ICON_MATERIALS_METALLIC, CUSTOM_VARIATION,
)
for dictionary in dicts:
for keys, value in list(dictionary.items()):
if not type(keys) == str:
dictionary.pop(keys)
for key in keys:
dictionary[key] = value
def get_lutb_bake_mat(parent_op=None):
append_resources(parent_op)
return bpy.data.materials.get(LUTB_BAKE_MAT)
def get_lutb_transparent_mat(parent_op=None):
append_resources(parent_op)
return bpy.data.materials.get(LUTB_TRANSPARENT_MAT)
def get_lutb_force_white_mat(parent_op=None):
append_resources(parent_op)
return bpy.data.materials.get(LUTB_FORCE_WHITE_MAT)
def get_lutb_ir_opaque_mat(parent_op=None):
append_resources(parent_op)
return bpy.data.materials.get(LUTB_IR_OPAQUE_MAT)
def get_lutb_ir_transparent_mat(parent_op=None):
append_resources(parent_op)
return bpy.data.materials.get(LUTB_IR_TRANSPARENT_MAT)
def get_lutb_ir_metal_mat(parent_op=None):
append_resources(parent_op)
return bpy.data.materials.get(LUTB_IR_METAL_MAT)
def get_lutb_ir_scene(parent_op=None, copy=True):
append_resources(parent_op)
return bpy.data.scenes.get(LUTB_IR_SCENE).copy()
def append_resources(parent_op=None):
blend_file = Path(__file__).parent / "resources.blend"
for mat_name in (*LUTB_BAKE_MATS, *LUTB_IR_MATS):
if not mat_name in bpy.data.materials:
bpy.ops.wm.append(directory=str(blend_file / "Material"), filename=mat_name)
if not mat_name in bpy.data.materials and parent_op:
parent_op.report({"WARNING"},
f"Failed to append \"{mat_name}\" from \"{blend_file}\"."
)
scene_name = LUTB_IR_SCENE
if not scene_name in bpy.data.scenes:
bpy.ops.wm.append(directory=str(blend_file / "Scene"), filename=scene_name)
if not scene_name in bpy.data.scenes and parent_op:
parent_op.report({"WARNING"},
f"Failed to append \"{scene_name}\" from \"{blend_file}\"."
)

View File

@@ -0,0 +1,19 @@
def srgb2lin(color):
result = []
for srgb in color:
if srgb <= 0.0404482362771082:
lin = srgb / 12.92
else:
lin = pow(((srgb + 0.055) / 1.055), 2.4)
result.append(lin)
return result
def lin2srgb(color):
result = []
for lin in color:
if lin > 0.0031308:
srgb = 1.055 * (pow(lin, (1.0 / 2.4))) - 0.055
else:
srgb = 12.92 * lin
result.append(srgb)
return result

Binary file not shown.

688
lu_toolbox/process_model.py Normal file
View File

@@ -0,0 +1,688 @@
import bpy, bmesh
from bpy.props import *
from mathutils import Color, Matrix
from math import radians
import random
import numpy as np
from timeit import default_timer as timer
from .remove_hidden_faces import LUTB_OT_remove_hidden_faces
from .materials import *
from .divide_mesh import divide_mesh
IS_TRANSPARENT = "lu_toolbox_is_transparent"
LOD_SUFFIXES = ("LOD_0", "LOD_1", "LOD_2", "LOD_3")
class LUTB_OT_process_model(bpy.types.Operator):
"""Process LU model"""
bl_idname = "lutb.process_model"
bl_label = "Process Model"
@classmethod
def poll(cls, context):
return context.mode == "OBJECT"
def execute(self, context):
start = timer()
scene = context.scene
scene.render.engine = "CYCLES"
scene.cycles.device = "GPU" if scene.lutb_process_use_gpu else "CPU"
for obj in scene.collection.all_objects:
if not obj.type == "MESH":
continue
mat_names = {material.name.rsplit(".", 1)[0] for material in obj.data.materials}
if mat_names.issubset(MATERIALS_TRANSPARENT):
obj[IS_TRANSPARENT] = True
if scene.lutb_combine_objects:
self.combine_objects(context, scene.collection.children)
context.view_layer.update()
opaque_objects = []
transparent_objects = []
for collection in scene.collection.children:
if not collection.children:
self.report({"WARNING"},
f"Ignoring \"{collection.name}\": doesn't have any LOD collections"
)
continue
for lod_collection in collection.children:
if not lod_collection.name[-5:] in LOD_SUFFIXES:
self.report({"WARNING"},
f"Ignoring \"{lod_collection.name}\": not a LOD collection ({LOD_SUFFIXES})"
)
continue
for obj in lod_collection.all_objects:
if obj.type == "MESH":
if not obj.get(IS_TRANSPARENT):
opaque_objects.append(obj)
else:
transparent_objects.append(obj)
all_objects = opaque_objects + transparent_objects
if not all_objects:
return {"FINISHED"}
if not scene.lutb_keep_uvs:
self.clear_uvs(all_objects)
if scene.lutb_reset_orientation:
self.reset_orientation(all_objects)
if scene.lutb_apply_vertex_colors:
if scene.lutb_correct_colors:
self.correct_colors(context, all_objects)
if scene.lutb_use_color_variation:
self.apply_color_variation(context, scene.collection.children)
self.apply_vertex_colors(context, all_objects)
if scene.lutb_setup_bake_mat:
self.setup_bake_mat(context, all_objects)
if scene.lutb_remove_hidden_faces:
for obj in transparent_objects:
obj.hide_render = True
self.remove_hidden_faces(context, opaque_objects)
for obj in transparent_objects:
obj.hide_render = False
new_objects = self.split_objects(context, scene.collection.children)
all_objects += new_objects
if scene.lutb_setup_lod_data:
self.setup_lod_data(context, scene.collection.children)
bpy.ops.object.select_all(action="DESELECT")
for obj in all_objects:
obj.select_set(True)
context.view_layer.objects.active = all_objects[0]
end = timer()
print(f"finished process model in {end - start:.2f}s")
return {"FINISHED"}
def clear_uvs(self, objects):
for obj in objects:
for uv_layer in reversed(obj.data.uv_layers):
obj.data.uv_layers.remove(uv_layer)
def reset_orientation(self, objects):
for obj in objects:
obj.select_set(True)
bpy.ops.object.transform_apply()
obj.rotation_euler = (radians(-90), 0, 0)
bpy.ops.object.transform_apply()
obj.rotation_euler = (radians(90), 0, 0)
obj.select_set(False)
def combine_objects(self, context, collections):
scene = context.scene
for collection in collections:
if not collection.children:
continue
for lod_collection in collection.children:
if not lod_collection.name[-5:] in LOD_SUFFIXES:
continue
objs_opaque = []
for obj in lod_collection.all_objects:
if obj.type == "MESH" and not obj.get(IS_TRANSPARENT):
objs_opaque.append(obj)
if objs_opaque:
joined_opaque = self.join_objects(context, objs_opaque)
joined_opaque.name = lod_collection.name[:-6]
objs_transparent = []
for obj in lod_collection.all_objects:
if obj.type == "MESH" and obj.get(IS_TRANSPARENT):
objs_transparent.append(obj)
if scene.lutb_combine_transparent:
if objs_transparent:
joined_transparent = self.join_objects(context, objs_transparent)
joined_transparent.name = lod_collection.name[:-6]
joined_transparent[IS_TRANSPARENT] = True
def join_objects(self, context, objects):
bpy.ops.object.select_all(action="DESELECT")
for obj in objects:
obj.select_set(True)
joined = objects[0]
context.view_layer.objects.active = joined
bpy.ops.object.join()
bpy.ops.object.transform_apply()
return joined
def correct_colors(self, context, objects):
for obj in objects:
for material in obj.data.materials:
name = material.name.rsplit(".", 1)[0]
if color := MATERIALS_OPAQUE.get(name):
material.diffuse_color = color
elif color := MATERIALS_TRANSPARENT.get(name):
material.diffuse_color = color
def apply_color_variation(self, context, collections):
initial_state = random.getstate()
variation = context.scene.lutb_color_variation
for collection in collections:
for lod_collection in collection.children:
random.setstate(initial_state)
for obj in list(lod_collection.objects):
if obj.type == "MESH":
for material in obj.data.materials:
color = Color(material.diffuse_color[:3])
gamma = color.v ** (1 / 2.224)
custom_variation = CUSTOM_VARIATION.get(material.name.rsplit(".", 1)[0])
var = variation if custom_variation is None else variation * custom_variation
gamma += random.uniform(-var / 200, var / 200)
color.v = min(max(0, gamma), 1) ** 2.224
material.diffuse_color = (*color, 1.0)
def apply_vertex_colors(self, context, objects):
scene = context.scene
for obj in objects:
is_transparent = obj.get(IS_TRANSPARENT)
mesh = obj.data
n_loops = len(mesh.loops)
if not is_transparent:
if not (vc_lit := mesh.vertex_colors.get("Lit")):
vc_lit = mesh.vertex_colors.new(name="Lit")
lit_data = np.tile((0.0, 0.0, 0.0, 1.0), n_loops)
vc_lit.data.foreach_set("color", lit_data)
if not (vc_col := mesh.vertex_colors.get("Col")):
vc_col = mesh.vertex_colors.new(name="Col")
if len(mesh.materials) < 2:
if mesh.materials:
color = lin2srgb(mesh.materials[0].diffuse_color)
else:
color = (0.8, 0.8, 0.8, 1.0)
if is_transparent:
color[3] = scene.lutb_transparent_opacity / 100.0
color_data = np.tile(color, n_loops)
else:
colors = np.empty((len(mesh.materials), 4))
for i, material in enumerate(mesh.materials):
colors[i] = lin2srgb(material.diffuse_color)
if is_transparent:
colors[:, 3] = scene.lutb_transparent_opacity / 100.0
color_indices = np.zeros(len(mesh.loops), dtype=int)
for poly in mesh.polygons:
poly_loop_end = poly.loop_start + poly.loop_total
color_indices[poly.loop_start:poly_loop_end] = poly.material_index
color_data = colors[color_indices].flatten()
vc_col.data.foreach_set("color", color_data)
if not is_transparent:
if not (vc_alpha := mesh.vertex_colors.get("Alpha")):
vc_alpha = mesh.vertex_colors.new(name="Alpha")
alpha_data = np.tile((1.0, 1.0, 1.0, 1.0), n_loops)
vc_alpha.data.foreach_set("color", alpha_data)
if not (vc_glow := mesh.vertex_colors.get("Glow")):
vc_glow = mesh.vertex_colors.new(name="Glow")
mat_names = [mat.name.rsplit(".", 1)[0] for mat in mesh.materials]
if set(mat_names) & set(MATERIALS_GLOW):
colors = np.empty((len(mesh.materials), 4))
for i, (name, material) in enumerate(zip(mat_names, mesh.materials)):
color = MATERIALS_GLOW.get(name)
colors[i] = lin2srgb(color) if color else (0.0, 0.0, 0.0, 1.0)
color_indices = np.zeros(len(mesh.loops), dtype=int)
for poly in mesh.polygons:
poly_loop_end = poly.loop_start + poly.loop_total
color_indices[poly.loop_start:poly_loop_end] = poly.material_index
glow_data = colors[color_indices].flatten()
else:
glow_data = np.tile((0.0, 0.0, 0.0, 1.0), n_loops)
vc_glow.data.foreach_set("color", glow_data)
# no clue why setting .active doesn't work ...
mesh.vertex_colors.active_index = mesh.vertex_colors.keys().index("Col")
shading = context.area.spaces[0].shading
shading.type = "SOLID"
shading.light = "FLAT"
shading.color_type = "VERTEX"
shading.show_backface_culling = True
def setup_bake_mat(self, context, objects):
if not (bake_mat := context.scene.lutb_bake_mat):
bake_mat = context.scene.lutb_bake_mat = get_lutb_bake_mat(self)
mat_transparent = get_lutb_transparent_mat(self)
for obj in objects:
mesh = obj.data
mesh.materials.clear()
if obj.get(IS_TRANSPARENT):
mesh.materials.append(mat_transparent)
else:
mesh.materials.append(bake_mat)
def remove_hidden_faces(self, context, objects):
scene = context.scene
for obj in objects:
if obj.get(IS_TRANSPARENT):
continue
context.view_layer.objects.active = obj
bpy.ops.object.select_all(action="DESELECT")
obj.select_set(True)
bpy.ops.lutb.remove_hidden_faces(
autoremove=scene.lutb_hsr_autoremove,
vc_pre_pass=scene.lutb_hsr_vc_pre_pass,
vc_pre_pass_samples=scene.lutb_hsr_vc_pre_pass_samples,
ignore_lights=scene.lutb_hsr_ignore_lights,
tris_to_quads=scene.lutb_hsr_tris_to_quads,
pixels_between_verts=scene.lutb_hsr_pixels_between_verts,
samples=scene.lutb_hsr_samples,
use_ground_plane=scene.lutb_hsr_use_ground_plane,
)
def split_objects(self, context, collections):
new_objects = []
for collection in collections:
for lod_collection in collection.children:
for obj in list(lod_collection.objects):
if obj.type == "MESH":
new_objects += divide_mesh(context, obj)
return new_objects
def setup_lod_data(self, context, collections):
scene = context.scene
for collection in collections:
if not collection.children:
continue
scene_node = bpy.data.objects.new(f"SceneNode_{collection.name}", None)
collection.objects.link(scene_node)
if scene.lutb_correct_orientation:
rotation = Matrix.Rotation(radians(90), 4, "X")
scene_node.matrix_world = rotation @ scene_node.matrix_world
ni_nodes = {}
used_lods = set()
for lod_collection in collection.children:
if (suffix := lod_collection.name[-5:]) in LOD_SUFFIXES:
used_lods.add(suffix)
for lod_collection in list(collection.children):
suffix = lod_collection.name[-5:]
if not suffix in LOD_SUFFIXES:
continue
for obj in list(lod_collection.all_objects):
is_transparent = bool(obj.get(IS_TRANSPARENT))
shader_prefix = "01" if is_transparent else scene.lutb_shader_opaque
type_prefix = "Alpha" if is_transparent else "Opaque"
obj_name = obj.name.rsplit(".", 1)[0]
name = f"S{shader_prefix}_{type_prefix}_{obj_name}"[:60]
obj.name = name
if (node := ni_nodes.get(name)):
node_obj, node_lods = node
else:
node_obj = bpy.data.objects.new(name, None)
node_obj["type"] = "NiLODNode"
matrix_before = node_obj.matrix_world.copy()
node_obj.parent = scene_node
node_obj.matrix_world = matrix_before
collection.objects.link(node_obj)
node_lods = {}
node = (node_obj, node_lods)
ni_nodes[name] = node
if not (lod_obj := node_lods.get(suffix)):
lod_obj = bpy.data.objects.new(suffix, None)
lod_obj.parent = node_obj
collection.objects.link(lod_obj)
# DYNAMIC LOD HELL
if len(used_lods) == 1:
lod_obj["near_extent"] = scene.lutb_lod0
lod_obj["far_extent"] = scene.lutb_cull
elif suffix == LOD_SUFFIXES[0]:
lod_obj["near_extent"] = scene.lutb_lod0
if used_lods in [
{suffix, LOD_SUFFIXES[2]},
{suffix, LOD_SUFFIXES[2], LOD_SUFFIXES[3]}]:
lod_obj["far_extent"] = scene.lutb_lod2
elif used_lods == {suffix, LOD_SUFFIXES[3]}:
lod_obj["far_extent"] = scene.lutb_lod3
else:
lod_obj["far_extent"] = scene.lutb_lod1
elif suffix == LOD_SUFFIXES[1]:
if used_lods == {LOD_SUFFIXES[0], suffix}:
lod_obj["near_extent"] = scene.lutb_lod1
lod_obj["far_extent"] = scene.lutb_cull
elif used_lods in [
{suffix, LOD_SUFFIXES[2]},
{suffix, LOD_SUFFIXES[2], LOD_SUFFIXES[3]}]:
lod_obj["near_extent"] = scene.lutb_lod0
lod_obj["far_extent"] = scene.lutb_lod2
elif used_lods == {LOD_SUFFIXES[0], suffix, LOD_SUFFIXES[3]}:
lod_obj["near_extent"] = scene.lutb_lod1
lod_obj["far_extent"] = scene.lutb_lod3
elif used_lods == {suffix, LOD_SUFFIXES[3]}:
lod_obj["near_extent"] = scene.lutb_lod0
lod_obj["far_extent"] = scene.lutb_lod3
elif used_lods in [
{LOD_SUFFIXES[0], suffix, LOD_SUFFIXES[2]},
{LOD_SUFFIXES[0], suffix, LOD_SUFFIXES[2], LOD_SUFFIXES[3]}]:
lod_obj["near_extent"] = scene.lutb_lod1
lod_obj["far_extent"] = scene.lutb_lod2
elif suffix == LOD_SUFFIXES[2]:
if used_lods in [
{LOD_SUFFIXES[0], suffix},
{LOD_SUFFIXES[1], suffix},
{LOD_SUFFIXES[0], LOD_SUFFIXES[1], suffix}]:
lod_obj["near_extent"] = scene.lutb_lod2
lod_obj["far_extent"] = scene.lutb_cull
elif used_lods == {suffix, LOD_SUFFIXES[3]}:
lod_obj["near_extent"] = scene.lutb_lod0
lod_obj["far_extent"] = scene.lutb_lod3
elif used_lods in [
{LOD_SUFFIXES[0], suffix, LOD_SUFFIXES[3]},
{LOD_SUFFIXES[1], suffix, LOD_SUFFIXES[3]},
{LOD_SUFFIXES[0], LOD_SUFFIXES[1], suffix, LOD_SUFFIXES[3]}]:
lod_obj["near_extent"] = scene.lutb_lod2
lod_obj["far_extent"] = scene.lutb_lod3
elif suffix == LOD_SUFFIXES[3]:
lod_obj["near_extent"] = scene.lutb_lod3
lod_obj["far_extent"] = scene.lutb_cull
node_lods[suffix] = lod_obj
obj.parent = lod_obj
class LUToolboxPanel:
bl_space_type = "VIEW_3D"
bl_region_type = "UI"
bl_category = "LU Toolbox"
class LUTB_PT_process_model(LUToolboxPanel, bpy.types.Panel):
bl_label = "Process Model"
def draw(self, context):
scene = context.scene
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
layout.operator("lutb.process_model")
layout.separator(factor=0.5)
layout.prop(scene, "lutb_process_use_gpu")
layout.prop(scene, "lutb_combine_objects")
col = layout.column()
col.prop(scene, "lutb_combine_transparent")
col.enabled = scene.lutb_combine_objects
layout.prop(scene, "lutb_keep_uvs")
layout.prop(scene, "lutb_reset_orientation")
class LUTB_PT_apply_vertex_colors(LUToolboxPanel, bpy.types.Panel):
bl_label = "Apply Vertex Colors"
bl_parent_id = "LUTB_PT_process_model"
bl_options = {"DEFAULT_CLOSED"}
def draw_header(self, context):
self.layout.prop(context.scene, "lutb_apply_vertex_colors", text="")
def draw(self, context):
scene = context.scene
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
layout.active = scene.lutb_apply_vertex_colors
layout.prop(scene, "lutb_correct_colors")
layout.prop(scene, "lutb_use_color_variation")
col = layout.column()
col.prop(scene, "lutb_color_variation")
col.enabled = scene.lutb_use_color_variation
layout.prop(scene, "lutb_transparent_opacity")
class LUTB_PT_setup_bake_mat(LUToolboxPanel, bpy.types.Panel):
bl_label = "Setup Bake Material"
bl_parent_id = "LUTB_PT_process_model"
bl_options = {"DEFAULT_CLOSED"}
def draw_header(self, context):
self.layout.prop(context.scene, "lutb_setup_bake_mat", text="")
def draw(self, context):
scene = context.scene
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
layout.active = scene.lutb_setup_bake_mat
layout.prop(scene, "lutb_bake_mat", text="")
class LUTB_PT_remove_hidden_faces(LUToolboxPanel, bpy.types.Panel):
bl_label = "Remove Hidden Faces"
bl_parent_id = "LUTB_PT_process_model"
bl_options = {"DEFAULT_CLOSED"}
def draw_header(self, context):
self.layout.prop(context.scene, "lutb_remove_hidden_faces", text="")
def draw(self, context):
scene = context.scene
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
layout.active = scene.lutb_remove_hidden_faces
layout.prop(scene, "lutb_hsr_autoremove")
layout.prop(scene, "lutb_hsr_vc_pre_pass")
row = layout.row()
row.prop(scene, "lutb_hsr_vc_pre_pass_samples", slider=True)
row.enabled = scene.lutb_hsr_vc_pre_pass
layout.prop(scene, "lutb_hsr_ignore_lights")
layout.prop(scene, "lutb_hsr_tris_to_quads")
layout.prop(scene, "lutb_hsr_use_ground_plane")
layout.prop(scene, "lutb_hsr_pixels_between_verts", slider=True)
layout.prop(scene, "lutb_hsr_samples", slider=True)
class LUTB_PT_setup_metadata(LUToolboxPanel, bpy.types.Panel):
bl_label = "Setup Metadata"
bl_parent_id = "LUTB_PT_process_model"
bl_options = {"DEFAULT_CLOSED"}
def draw_header(self, context):
self.layout.prop(context.scene, "lutb_setup_lod_data", text="")
def draw(self, context):
scene = context.scene
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
layout.active = scene.lutb_setup_lod_data
layout.prop(scene, "lutb_correct_orientation")
layout.prop(scene, "lutb_shader_opaque")
layout.prop(scene, "lutb_shader_glow")
layout.prop(scene, "lutb_shader_metal")
layout.prop(scene, "lutb_shader_superemissive")
layout.prop(scene, "lutb_lod0")
layout.prop(scene, "lutb_lod1")
layout.prop(scene, "lutb_lod2")
layout.prop(scene, "lutb_lod3")
layout.prop(scene, "lutb_cull")
def register():
bpy.utils.register_class(LUTB_OT_process_model)
bpy.utils.register_class(LUTB_PT_process_model)
bpy.utils.register_class(LUTB_PT_apply_vertex_colors)
bpy.utils.register_class(LUTB_PT_setup_bake_mat)
bpy.utils.register_class(LUTB_PT_remove_hidden_faces)
bpy.utils.register_class(LUTB_PT_setup_metadata)
bpy.types.Scene.lutb_process_use_gpu = BoolProperty(name="Use GPU", default=True)
bpy.types.Scene.lutb_combine_objects = BoolProperty(name="Combine Objects", default=True, description=""\
"Combine opaque bricks")
bpy.types.Scene.lutb_combine_transparent = BoolProperty(name="Combine Transparent", default=False, description=""\
"Combine transparent bricks")
bpy.types.Scene.lutb_keep_uvs = BoolProperty(name="Keep UVs", default=False, description=""\
"Keep the original mesh UVs. Disabling this results in a model with no UVs")
bpy.types.Scene.lutb_reset_orientation = BoolProperty(name="Reset Orientation", default=True, description=""\
"Reset the orientation so the model is properly upright for visual effects.")
bpy.types.Scene.lutb_correct_colors = BoolProperty(name="Correct Colors", default=False, description=""\
"Remap model colors to LU color palette. "\
"Note: Models imported with Toolbox import with the LU color palette natively")
bpy.types.Scene.lutb_use_color_variation = BoolProperty(name="Apply Color Variation", default=True, description=""\
"Randomly shift the brightness value of each brick")
bpy.types.Scene.lutb_color_variation = FloatProperty(name="Color Variation", subtype="PERCENTAGE", min=0.0, soft_max=15.0, max=100.0, default=5.0, description=""\
"Percentage of brightness value shift. Higher values result in more variation")
bpy.types.Scene.lutb_transparent_opacity = FloatProperty(name="Transparent Opacity", subtype="PERCENTAGE", min=0.0, max=100.0, default=58.82, description=""\
"Percentage of transparent brick opacity. "\
"This controls how see-through the models transparent bricks appear in LU. "\
"Lower values result in more transparency")
bpy.types.Scene.lutb_apply_vertex_colors = BoolProperty(name="Apply Vertex Colors", default=True, description=""\
"Apply vertex colors to the model")
bpy.types.Scene.lutb_setup_bake_mat = BoolProperty(name="Setup Bake Material", default=True, description=""\
"Apply new material to opaque bricks")
bpy.types.Scene.lutb_bake_mat = PointerProperty(name="Bake Material", type=bpy.types.Material, description=""\
"Choose the material that gets added to opaque bricks. "\
"If left blank, defaults to VertexColor material")
bpy.types.Scene.lutb_remove_hidden_faces = BoolProperty(name="Remove Hidden Faces", default=True,
description=LUTB_OT_remove_hidden_faces.__doc__)
bpy.types.Scene.lutb_hsr_autoremove = BoolProperty(name="Autoremove", default=True,
description=LUTB_OT_remove_hidden_faces.__annotations__["autoremove"].keywords["description"])
bpy.types.Scene.lutb_hsr_vc_pre_pass = BoolProperty(name="Vertex Color Pre-Pass", default=True,
description=LUTB_OT_remove_hidden_faces.__annotations__["vc_pre_pass"].keywords["description"])
bpy.types.Scene.lutb_hsr_vc_pre_pass_samples = IntProperty(name="Pre-Pass Samples", min=0, default=32, soft_max=64,
description=LUTB_OT_remove_hidden_faces.__annotations__["vc_pre_pass_samples"].keywords["description"])
bpy.types.Scene.lutb_hsr_ignore_lights = BoolProperty(name="Ignore Lights", default=True,
description=LUTB_OT_remove_hidden_faces.__annotations__["ignore_lights"].keywords["description"])
bpy.types.Scene.lutb_hsr_tris_to_quads = BoolProperty(name="Tris to Quads", default=True,
description=LUTB_OT_remove_hidden_faces.__annotations__["tris_to_quads"].keywords["description"])
bpy.types.Scene.lutb_hsr_pixels_between_verts = IntProperty(name="Pixels Between Vertices", min=0, default=5, soft_max=15,
description=LUTB_OT_remove_hidden_faces.__annotations__["pixels_between_verts"].keywords["description"])
bpy.types.Scene.lutb_hsr_samples = IntProperty(name="Samples", min=0, default=8, soft_max=32,
description=LUTB_OT_remove_hidden_faces.__annotations__["samples"].keywords["description"])
bpy.types.Scene.lutb_hsr_use_ground_plane = BoolProperty(name="Use Ground Plane", default=False,
description=LUTB_OT_remove_hidden_faces.__annotations__["use_ground_plane"].keywords["description"])
bpy.types.Scene.lutb_setup_lod_data = BoolProperty(name="Setup LOD Data", default=True)
bpy.types.Scene.lutb_correct_orientation = BoolProperty(name="Correct Orientation", default=True)
bpy.types.Scene.lutb_shader_opaque = StringProperty(name="Opaque Shader", default="01")
bpy.types.Scene.lutb_shader_glow = StringProperty(name="Glow Shader", default="72")
bpy.types.Scene.lutb_shader_metal = StringProperty(name="Metal Shader", default="88")
bpy.types.Scene.lutb_shader_superemissive = StringProperty(name="SuperEmissive Shader", default="19")
bpy.types.Scene.lutb_lod0 = FloatProperty(name="LOD 0", soft_min=0.0, default=0.0, soft_max=25.0)
bpy.types.Scene.lutb_lod1 = FloatProperty(name="LOD 1", soft_min=0.0, default=50.0, soft_max=100.0)
bpy.types.Scene.lutb_lod2 = FloatProperty(name="LOD 2", soft_min=0.0, default=100.0, soft_max=280.0)
bpy.types.Scene.lutb_lod3 = FloatProperty(name="LOD 3", soft_min=0.0, default=280.0, soft_max=500.0)
bpy.types.Scene.lutb_cull = FloatProperty(name="Cull", soft_min=1000.0, default=10000.0, soft_max=50000.0)
def unregister():
del bpy.types.Scene.lutb_process_use_gpu
del bpy.types.Scene.lutb_combine_objects
del bpy.types.Scene.lutb_combine_transparent
del bpy.types.Scene.lutb_keep_uvs
del bpy.types.Scene.lutb_reset_orientation
del bpy.types.Scene.lutb_correct_colors
del bpy.types.Scene.lutb_use_color_variation
del bpy.types.Scene.lutb_color_variation
del bpy.types.Scene.lutb_transparent_opacity
del bpy.types.Scene.lutb_apply_vertex_colors
del bpy.types.Scene.lutb_setup_bake_mat
del bpy.types.Scene.lutb_bake_mat
del bpy.types.Scene.lutb_remove_hidden_faces
del bpy.types.Scene.lutb_hsr_autoremove
del bpy.types.Scene.lutb_hsr_vc_pre_pass
del bpy.types.Scene.lutb_hsr_vc_pre_pass_samples
del bpy.types.Scene.lutb_hsr_ignore_lights
del bpy.types.Scene.lutb_hsr_tris_to_quads
del bpy.types.Scene.lutb_hsr_pixels_between_verts
del bpy.types.Scene.lutb_hsr_samples
del bpy.types.Scene.lutb_hsr_use_ground_plane
del bpy.types.Scene.lutb_setup_lod_data
del bpy.types.Scene.lutb_correct_orientation
del bpy.types.Scene.lutb_shader_opaque
del bpy.types.Scene.lutb_shader_glow
del bpy.types.Scene.lutb_shader_metal
del bpy.types.Scene.lutb_shader_superemissive
del bpy.types.Scene.lutb_lod0
del bpy.types.Scene.lutb_lod1
del bpy.types.Scene.lutb_lod2
del bpy.types.Scene.lutb_cull
bpy.utils.unregister_class(LUTB_PT_setup_metadata)
bpy.utils.unregister_class(LUTB_PT_remove_hidden_faces)
bpy.utils.unregister_class(LUTB_PT_setup_bake_mat)
bpy.utils.unregister_class(LUTB_PT_apply_vertex_colors)
bpy.utils.unregister_class(LUTB_PT_process_model)
bpy.utils.unregister_class(LUTB_OT_process_model)

View File

@@ -0,0 +1,385 @@
import bpy, bmesh
from mathutils import Vector, Matrix
from bpy.props import IntProperty, FloatProperty, BoolProperty
import math
import numpy as np
from timeit import default_timer as timer
LUTB_HSR_ID = "LUTB_HSR"
class LUTB_OT_remove_hidden_faces(bpy.types.Operator):
"""Remove hidden interior geometry from the model."""
bl_idname = "lutb.remove_hidden_faces"
bl_label = "Remove Hidden Faces"
autoremove : BoolProperty(default=True, description=""\
"Automatically remove hidden polygons. "\
"Disabling this results in hidden polygons being assigned to the objects Face Maps"
)
vc_pre_pass : BoolProperty(default=True, description=""\
"Use vertex color baking based pre-pass to quickly sort out faces that are"\
"definitely visible.")
vc_pre_pass_samples : IntProperty(min=1, default=32, description=""\
"Number of samples to render for vertex color pre-pass")
ignore_lights : BoolProperty(default=True, description=""\
"Hide all custom lights while processing."\
"Disable this if you want to manually affect the lighting.")
tris_to_quads : BoolProperty(default=True, description=""\
"Convert models triangles to quads for faster, more efficient HSR. "\
"Quads are then converted back to tris afterwards. "\
"Disabling this may result in slower HSR processing")
pixels_between_verts : IntProperty(min=0, default=5, description="")
samples : IntProperty(min=1, default=8, description=""\
"Number of samples to render for HSR")
threshold : FloatProperty(min=0, default=0.01, max=1)
use_ground_plane : BoolProperty(default=False, description=""\
"Add a ground plane that contributes occlusion to the model during HSR so that "\
"the underside of the model gets removed. Before enabling this option, make "\
"sure your model does not extend below the default ground plane in LDD")
@classmethod
def poll(cls, context):
return (
context.object
and context.object.type == "MESH"
and context.mode == "OBJECT"
and context.scene.render.engine == "CYCLES"
)
def execute(self, context):
start = timer()
scene = context.scene
target_obj = context.object
mesh = target_obj.data
loop_counts = np.empty(len(mesh.polygons), dtype=int)
mesh.polygons.foreach_get("loop_total", loop_counts)
if loop_counts.max() > 4:
self.report({"ERROR"}, "Mesh needs to consist of tris or quads only!")
return {"CANCELLED"}
ground_plane = None
if self.use_ground_plane:
ground_plane = self.add_ground_plane(context)
hidden_objects = []
for obj in list(scene.collection.all_objects):
if obj.hide_render:
continue
if obj in {target_obj, ground_plane}:
continue
if not self.ignore_lights and obj.type == "LIGHT":
continue
obj.hide_render = True
hidden_objects.append(obj)
scene_override = self.setup_scene_override(context)
if self.vc_pre_pass:
visible = self.compute_vc_pre_pass(context, scene_override)
bpy.ops.object.mode_set(mode="EDIT")
bpy.ops.mesh.select_all(action="DESELECT")
bpy.ops.object.mode_set(mode="OBJECT")
mesh.polygons.foreach_set("select", ~visible)
else:
bpy.ops.object.mode_set(mode="EDIT")
bpy.ops.mesh.select_all(action="SELECT")
bpy.ops.object.mode_set(mode="OBJECT")
if self.tris_to_quads:
bpy.ops.object.mode_set(mode="EDIT")
bpy.ops.mesh.tris_convert_to_quads()
bpy.ops.object.mode_set(mode="OBJECT")
select = np.empty(len(mesh.polygons), dtype=bool)
mesh.polygons.foreach_get("select", select)
face_indices = np.where(select)[0]
if len(face_indices) > 0:
image = self.bake_to_image(context, scene_override, mesh, face_indices)
hidden_indices = self.get_hidden_from_image(image, mesh, face_indices)
bpy.ops.object.mode_set(mode="EDIT")
context.tool_settings.mesh_select_mode = (False, False, True)
bpy.ops.mesh.select_all(action="DESELECT")
bpy.ops.object.mode_set(mode="OBJECT")
select = np.zeros(len(mesh.polygons), dtype=bool)
select[hidden_indices] = True
mesh.polygons.foreach_set("select", select)
if self.autoremove:
bpy.ops.object.mode_set(mode="EDIT")
bpy.ops.mesh.delete(type="FACE")
bpy.ops.mesh.select_all(action="SELECT")
bpy.ops.mesh.quads_convert_to_tris(quad_method="FIXED")
bpy.ops.object.mode_set(mode="OBJECT")
end = timer()
n = len(hidden_indices)
total = len(select)
operation = "removed" if self.autoremove else "found"
print(
f"hsr info: {operation} {n}/{total} hidden faces ({n / total:.2%}) "\
f"in {end - start:.2f}s"
)
else:
print("hsr info: found no hidden faces")
bpy.data.scenes.remove(scene_override)
for obj in hidden_objects:
obj.hide_render = False
if ground_plane:
bpy.data.objects.remove(ground_plane)
return {"FINISHED"}
def add_ground_plane(self, context):
bm = bmesh.new()
matrix = Matrix.Diagonal((1000, 1000, 100, 1)) @ Matrix.Translation((0, 0, -0.5))
bmesh.ops.create_cube(bm, size=1.0, matrix=matrix)
mesh = bpy.data.meshes.new(LUTB_HSR_ID)
obj = bpy.data.objects.new(LUTB_HSR_ID, mesh)
context.scene.collection.objects.link(obj)
bm.to_mesh(mesh)
if not (material := bpy.data.materials.get(LUTB_HSR_ID + "_GP")):
material = bpy.data.materials.new(LUTB_HSR_ID + "_GP")
material.use_nodes = True
nodes = material.node_tree.nodes
nodes.clear()
node_diffuse = nodes.new("ShaderNodeBsdfDiffuse")
node_diffuse.inputs["Color"].default_value = (0, 0, 0, 1)
node_output = nodes.new("ShaderNodeOutputMaterial")
material.node_tree.links.new(node_diffuse.outputs[0], node_output.inputs[0])
mesh.materials.append(material)
return obj
def setup_scene_override(self, context):
scene_override = context.scene.copy()
scene_override.world = get_overexposed_world()
cycles = scene_override.cycles
cycles.bake_type = "DIFFUSE"
cycles.use_denoising = False
cycles.use_fast_gi = False
cycles.sample_clamp_direct = 0.0
cycles.sample_clamp_indirect = 0.0
bake_settings = scene_override.render.bake
bake_settings.use_pass_direct = True
bake_settings.use_pass_indirect = True
bake_settings.use_pass_diffuse = True
bake_settings.margin = 0
bake_settings.use_clear = True
return scene_override
def compute_vc_pre_pass(self, context, scene):
start = timer()
obj = context.object
mesh = obj.data
material = bpy.data.materials.new(LUTB_HSR_ID)
original_materials = []
for i, material_slot in enumerate(obj.material_slots):
original_materials.append(material_slot.material)
obj.material_slots[i].material = material
vc = mesh.vertex_colors.new(name=LUTB_HSR_ID)
old_active_index = mesh.vertex_colors.active_index
mesh.vertex_colors.active_index = mesh.vertex_colors.keys().index(vc.name)
cycles = scene.cycles
cycles.samples = self.vc_pre_pass_samples
cycles.max_bounces = 12
cycles.diffuse_bounces = 12
scene.render.bake.target = "VERTEX_COLORS"
context_override = context.copy()
context_override["scene"] = scene
bpy.ops.object.bake(context_override)
for i, material in enumerate(original_materials):
obj.material_slots[i].material = material
vc_data = np.empty(len(mesh.loops) * 4)
vc.data.foreach_get("color", vc_data)
mesh.vertex_colors.remove(vc)
mesh.vertex_colors.active_index = old_active_index
loop_values = (vc_data.reshape(len(mesh.loops), 4)[:,:3].sum(1) / 3) > self.threshold
loop_starts = np.empty(len(mesh.polygons), dtype=int)
mesh.polygons.foreach_get("loop_start", loop_starts)
loop_totals = np.empty(len(mesh.polygons), dtype=int)
mesh.polygons.foreach_get("loop_total", loop_totals)
face_loop_values = np.zeros((len(mesh.polygons), 4), dtype=bool)
for i, (loop_start, loop_total) in enumerate(zip(loop_starts, loop_totals)):
face_loop_values[i,:loop_total] = loop_values[loop_start:loop_start + loop_total]
visible = face_loop_values.max(axis=1)
end = timer()
n = visible.sum()
total = len(mesh.polygons)
print(
f"hsr info: vc pre-pass sorted out {n}/{total} faces ({n / total:.2%}) "\
f"in {end - start:.2f}s"
)
return visible
def setup_uv_layer(self, context, mesh, face_indices, size, size_pixels):
uv_layer = mesh.uv_layers.new(name=LUTB_HSR_ID)
uv_layer.active = True
pbv_p_1 = self.pixels_between_verts + 1
offsets = np.array((
np.array((0, 0)) + np.array((-0.01, 0.00)) * pbv_p_1,
np.array((1, 0)) + np.array(( 1.00, 0.00)) * pbv_p_1,
np.array((1, 1)) + np.array(( 1.00, 1.01)) * pbv_p_1,
np.array((0, 1)) + np.array((-0.01, 1.01)) * pbv_p_1,
)) / size_pixels
size_inv = 1 / size
uv_data = np.zeros((len(mesh.loops), 2))
loop_starts = np.empty(len(mesh.polygons), dtype=int)
mesh.polygons.foreach_get("loop_start", loop_starts)
loop_starts = loop_starts[face_indices]
loop_totals = np.empty(len(mesh.polygons), dtype=int)
mesh.polygons.foreach_get("loop_total", loop_totals)
loop_totals = loop_totals[face_indices]
for i, (loop_start, loop_total) in enumerate(zip(loop_starts, loop_totals)):
target = np.array((i % size, i // size)) * size_inv
uv_data[loop_start:loop_start+loop_total] = target + offsets[:loop_total]
uv_layer.data.foreach_set("uv", uv_data.flatten())
return uv_layer
def bake_to_image(self, context, scene, mesh, face_indices):
obj = context.object
mesh = context.object.data
size = math.ceil(math.sqrt(len(face_indices)))
quadrant_size = 2 + self.pixels_between_verts
size_pixels = size * quadrant_size
uv_layer = self.setup_uv_layer(context, mesh, face_indices, size, size_pixels)
image = bpy.data.images.get(LUTB_HSR_ID)
if image and tuple(image.size) != (size_pixels, size_pixels):
bpy.data.images.remove(image)
image = None
if not image:
image = bpy.data.images.new(LUTB_HSR_ID, size_pixels, size_pixels)
material = get_overexposed_material(image)
original_materials = []
for i, material_slot in enumerate(obj.material_slots):
original_materials.append(material_slot.material)
obj.material_slots[i].material = material
cycles = scene.cycles
cycles.samples = self.samples
cycles.max_bounces = 8
cycles.diffuse_bounces = 8
scene.render.bake.target = "IMAGE_TEXTURES"
context_override = context.copy()
context_override["scene"] = scene
bpy.ops.object.bake(context_override)
for i, material in enumerate(original_materials):
obj.material_slots[i].material = material
mesh.uv_layers.remove(uv_layer)
return image
def get_hidden_from_image(self, image, mesh, face_indices):
face_count = len(face_indices)
size = math.ceil(math.sqrt(face_count))
quadrant_size = 2 + self.pixels_between_verts
size_pixels = size * quadrant_size
size_sq = size ** 2
size_pixels_sq = size_pixels ** 2
pixels = np.empty(size_pixels_sq * 4, dtype=np.float32)
image.pixels.foreach_get(pixels)
sum_per_face = pixels.copy()
sum_per_face = np.reshape(sum_per_face, (size_pixels_sq, 4))
sum_per_face = np.delete(sum_per_face, 3, 1)
sum_per_face = np.reshape(sum_per_face, (size_pixels_sq // quadrant_size, quadrant_size * 3))
sum_per_face = np.sum(sum_per_face, axis=1)
sum_per_face = np.reshape(sum_per_face, (size_pixels, size))
sum_per_face = np.swapaxes(sum_per_face, 0, 1)
sum_per_face = np.reshape(sum_per_face, (size_sq, quadrant_size))
sum_per_face = np.sum(sum_per_face, axis=1)
sum_per_face = np.reshape(sum_per_face, (size, size))
sum_per_face = np.swapaxes(sum_per_face, 0, 1)
sum_per_face = np.reshape(sum_per_face, (1, size_sq))[0][:face_count]
pixels_per_quad = quadrant_size ** 2
pixels_per_tri = (pixels_per_quad + quadrant_size) / 2
loop_totals = np.empty(len(mesh.polygons), dtype=int)
mesh.polygons.foreach_get("loop_total", loop_totals)
loops_per_face = loop_totals[face_indices]
pixels_per_face = np.array((pixels_per_tri, pixels_per_quad))[loops_per_face - 3]
average_per_face = sum_per_face / pixels_per_face / 3
indices = face_indices[np.where(average_per_face < self.threshold)[0]]
return indices
def get_overexposed_material(image):
material = bpy.data.materials.get(LUTB_HSR_ID)
if material and (not material.use_nodes or not "LUTB_TARGET" in material.node_tree.nodes):
bpy.data.materials.remove(material)
material = None
if not material:
material = bpy.data.materials.new(LUTB_HSR_ID)
material.use_nodes = True
nodes = material.node_tree.nodes
node_texture = nodes.new("ShaderNodeTexImage")
node_texture.name = "LUTB_TARGET"
nodes = material.node_tree.nodes
node_texture = nodes["LUTB_TARGET"]
node_texture.image = image
return material
def get_overexposed_world():
world = bpy.data.worlds.get(LUTB_HSR_ID)
if not world:
world = bpy.data.worlds.new(LUTB_HSR_ID)
world.use_nodes = True
nodes = world.node_tree.nodes
node_background = nodes["Background"]
node_background.inputs["Color"].default_value = (1, 1, 1, 1)
node_background.inputs["Strength"].default_value = 100000
return world
def register():
bpy.utils.register_class(LUTB_OT_remove_hidden_faces)
def unregister():
bpy.utils.unregister_class(LUTB_OT_remove_hidden_faces)