Merge pull request #2030 from Naxela/master
Update lightmapper to version 0.4
This commit is contained in:
commit
67e2b0171b
|
@ -1,21 +1,20 @@
|
|||
import bpy
|
||||
|
||||
#from .. operators import build
|
||||
#from .. operators import clean
|
||||
|
||||
tlm_keymaps = []
|
||||
|
||||
def register():
|
||||
pass
|
||||
# winman = bpy.context.window_manager
|
||||
# keyman = winman.keyconfigs.addon.keymaps.new(name='Window', space_type='EMPTY', region_type="WINDOW")
|
||||
# keyman.keymap_items.new(build.TLM_BuildLightmaps.bl_idname, type='F6', value='PRESS')
|
||||
# keyman.keymap_items.new(clean.TLM_CleanLightmaps.bl_idname, type='F7', value='PRESS')
|
||||
# tlm_keymaps.append(keyman)
|
||||
|
||||
if not bpy.app.background:
|
||||
|
||||
winman = bpy.context.window_manager
|
||||
keyman = winman.keyconfigs.addon.keymaps.new(name='Window', space_type='EMPTY', region_type="WINDOW")
|
||||
|
||||
keyman.keymap_items.new('tlm.build_lightmaps', type='F6', value='PRESS')
|
||||
keyman.keymap_items.new('tlm.clean_lightmaps', type='F7', value='PRESS')
|
||||
tlm_keymaps.append(keyman)
|
||||
|
||||
def unregister():
|
||||
pass
|
||||
# winman = bpy.context.window_manager
|
||||
# for keyman in tlm_keymaps:
|
||||
# winman.keyconfigs.addon.keymaps.remove(keyman)
|
||||
# del tlm_keymaps[:]
|
||||
winman = bpy.context.window_manager
|
||||
for keyman in tlm_keymaps:
|
||||
winman.keyconfigs.addon.keymaps.remove(keyman)
|
||||
del tlm_keymaps[:]
|
27
blender/arm/lightmapper/network/client.py
Normal file
27
blender/arm/lightmapper/network/client.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
import socket, json, os
|
||||
|
||||
def connect_client(machine, port, blendpath, obj_num):
|
||||
|
||||
# Create a socket
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
|
||||
# Connect to the remote host and port
|
||||
sock.connect((machine, port))
|
||||
|
||||
#0: Blendpath,
|
||||
#1: For all designated objects, run from 0 to number; 0 indicates all
|
||||
|
||||
args = [blendpath, obj_num]
|
||||
|
||||
command = json.dumps({'call':1, 'command':1, 'args':args})
|
||||
|
||||
# Send a request to the host
|
||||
sock.send((command).encode())
|
||||
|
||||
# Get the host's response, no more than, say, 1,024 bytes
|
||||
response_data = sock.recv(1024)
|
||||
|
||||
print(response_data.decode())
|
||||
|
||||
# Terminate
|
||||
sock.close()
|
71
blender/arm/lightmapper/network/server.py
Normal file
71
blender/arm/lightmapper/network/server.py
Normal file
|
@ -0,0 +1,71 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import bpy, socket, json, subprocess, os, platform, subprocess, select
|
||||
|
||||
def startServer():
|
||||
|
||||
active = True
|
||||
baking = False
|
||||
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.bind(('localhost', 9898))
|
||||
sock.listen(1)
|
||||
|
||||
print("Server started")
|
||||
|
||||
while active:
|
||||
connection,address = sock.accept()
|
||||
|
||||
data = connection.recv(1024)
|
||||
|
||||
if data:
|
||||
|
||||
parsed_data = json.loads(data.decode())
|
||||
|
||||
if parsed_data["call"] == 0: #Ping
|
||||
|
||||
print("Pinged by: " + str(connection.getsockname()))
|
||||
connection.sendall(("Ping callback").encode())
|
||||
|
||||
elif parsed_data["call"] == 1: #Command
|
||||
|
||||
if parsed_data["command"] == 0: #Shutdown
|
||||
|
||||
print("Server shutdown")
|
||||
active = False
|
||||
|
||||
if parsed_data["command"] == 1: #Baking
|
||||
|
||||
print("Baking...")
|
||||
|
||||
args = parsed_data["args"]
|
||||
|
||||
blenderpath = bpy.app.binary_path
|
||||
|
||||
if not baking:
|
||||
|
||||
baking = True
|
||||
|
||||
pipe = subprocess.Popen([blenderpath, "-b", str(args[0]), "--python-expr", 'import bpy; import thelightmapper; thelightmapper.addon.utility.build.prepare_build(0, True);'], shell=True, stdout=subprocess.PIPE)
|
||||
|
||||
stdout = pipe.communicate()[0]
|
||||
|
||||
print("Baking finished...")
|
||||
|
||||
active = False
|
||||
|
||||
else:
|
||||
|
||||
print("Request denied, server busy...")
|
||||
|
||||
print("Data received: " + data.decode())
|
||||
|
||||
connection.send(('Callback from: ' + str(socket.gethostname())).encode())
|
||||
|
||||
connection.close()
|
||||
|
||||
print("Connection closed.")
|
||||
|
||||
sock.close()
|
||||
|
||||
print("Server closed.")
|
|
@ -1,6 +1,6 @@
|
|||
import bpy
|
||||
from bpy.utils import register_class, unregister_class
|
||||
from . import tlm, installopencv
|
||||
from . import tlm, installopencv, imagetools
|
||||
|
||||
classes = [
|
||||
tlm.TLM_BuildLightmaps,
|
||||
|
@ -9,7 +9,20 @@ classes = [
|
|||
tlm.TLM_EnableSelection,
|
||||
tlm.TLM_DisableSelection,
|
||||
tlm.TLM_RemoveLightmapUV,
|
||||
installopencv.TLM_Install_OpenCV
|
||||
tlm.TLM_SelectLightmapped,
|
||||
installopencv.TLM_Install_OpenCV,
|
||||
tlm.TLM_AtlasListNewItem,
|
||||
tlm.TLM_AtlastListDeleteItem,
|
||||
tlm.TLM_AtlasListMoveItem,
|
||||
tlm.TLM_PostAtlasListNewItem,
|
||||
tlm.TLM_PostAtlastListDeleteItem,
|
||||
tlm.TLM_PostAtlasListMoveItem,
|
||||
tlm.TLM_StartServer,
|
||||
tlm.TLM_BuildEnvironmentProbes,
|
||||
tlm.TLM_CleanBuildEnvironmentProbes,
|
||||
imagetools.TLM_ImageUpscale,
|
||||
imagetools.TLM_ImageDownscale
|
||||
|
||||
]
|
||||
|
||||
def register():
|
||||
|
|
25
blender/arm/lightmapper/operators/imagetools.py
Normal file
25
blender/arm/lightmapper/operators/imagetools.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
import bpy, os, time
|
||||
|
||||
class TLM_ImageUpscale(bpy.types.Operator):
|
||||
bl_idname = "tlm.image_upscale"
|
||||
bl_label = "Upscale image"
|
||||
bl_description = "Upscales the image to double resolution"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
|
||||
print("Upscale")
|
||||
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
class TLM_ImageDownscale(bpy.types.Operator):
|
||||
bl_idname = "tlm.image_downscale"
|
||||
bl_label = "Downscale image"
|
||||
bl_description = "Downscales the image to double resolution"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
|
||||
print("Downscale")
|
||||
|
||||
return {'RUNNING_MODAL'}
|
|
@ -19,6 +19,8 @@ class TLM_Install_OpenCV(bpy.types.Operator):
|
|||
scene = context.scene
|
||||
cycles = bpy.data.scenes[scene.name].cycles
|
||||
|
||||
print("Module OpenCV")
|
||||
|
||||
pythonbinpath = bpy.app.binary_path_python
|
||||
|
||||
if platform.system() == "Windows":
|
||||
|
@ -29,10 +31,12 @@ class TLM_Install_OpenCV(bpy.types.Operator):
|
|||
ensurepippath = os.path.join(pythonlibpath, "ensurepip")
|
||||
|
||||
cmda = [pythonbinpath, ensurepippath, "--upgrade", "--user"]
|
||||
pip = subprocess.run(cmda, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
pip = subprocess.run(cmda)
|
||||
cmdc = [pythonbinpath, "-m", "pip", "install", "--upgrade", "pip"]
|
||||
pipc = subprocess.run(cmdc)
|
||||
|
||||
if pip.returncode == 0:
|
||||
print("Successfully installed pip!\n")
|
||||
print("Sucessfully installed pip!\n")
|
||||
else:
|
||||
|
||||
try:
|
||||
|
@ -44,18 +48,27 @@ class TLM_Install_OpenCV(bpy.types.Operator):
|
|||
|
||||
if not module_pip:
|
||||
print("Failed to install pip!\n")
|
||||
ShowMessageBox("Failed to install pip - Please start Blender as administrator", "Restart", 'PREFERENCES')
|
||||
if platform.system() == "Windows":
|
||||
ShowMessageBox("Failed to install pip - Please start Blender as administrator", "Restart", 'PREFERENCES')
|
||||
else:
|
||||
ShowMessageBox("Failed to install pip - Try starting Blender with SUDO", "Restart", 'PREFERENCES')
|
||||
return{'FINISHED'}
|
||||
|
||||
cmdb = [pythonbinpath, "-m", "pip", "install", "opencv-python"]
|
||||
|
||||
opencv = subprocess.run(cmdb, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
#opencv = subprocess.run(cmdb, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
opencv = subprocess.run(cmdb)
|
||||
|
||||
if opencv.returncode == 0:
|
||||
print("Sucessfully installed OpenCV!\n")
|
||||
print("Successfully installed OpenCV!\n")
|
||||
else:
|
||||
print("Failed to install OpenCV!\n")
|
||||
ShowMessageBox("Failed to install opencv - Please start Blender as administrator", "Restart", 'PREFERENCES')
|
||||
|
||||
if platform.system() == "Windows":
|
||||
ShowMessageBox("Failed to install opencv - Please start Blender as administrator", "Restart", 'PREFERENCES')
|
||||
else:
|
||||
ShowMessageBox("Failed to install opencv - Try starting Blender with SUDO", "Restart", 'PREFERENCES')
|
||||
|
||||
return{'FINISHED'}
|
||||
|
||||
module_opencv = True
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import bpy, os, time, blf, webbrowser
|
||||
import bpy, os, time, blf, webbrowser, platform
|
||||
import math, subprocess, multiprocessing
|
||||
from .. utility import build
|
||||
from .. utility.cycles import cache
|
||||
from .. network import server
|
||||
|
||||
class TLM_BuildLightmaps(bpy.types.Operator):
|
||||
bl_idname = "tlm.build_lightmaps"
|
||||
|
@ -73,6 +75,45 @@ class TLM_CleanLightmaps(bpy.types.Operator):
|
|||
if image.name.endswith("_baked"):
|
||||
bpy.data.images.remove(image, do_unlink=True)
|
||||
|
||||
for obj in bpy.data.objects:
|
||||
if obj.type == "MESH":
|
||||
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
|
||||
if obj.TLM_ObjectProperties.tlm_postpack_object:
|
||||
|
||||
atlas = obj.TLM_ObjectProperties.tlm_postatlas_pointer
|
||||
atlas_resize = False
|
||||
|
||||
for atlasgroup in scene.TLM_PostAtlasList:
|
||||
if atlasgroup.name == atlas:
|
||||
atlas_resize = True
|
||||
|
||||
if atlas_resize:
|
||||
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
obj.select_set(True)
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
#print(x)
|
||||
|
||||
uv_layers = obj.data.uv_layers
|
||||
for i in range(0, len(uv_layers)):
|
||||
if uv_layers[i].name == 'UVMap_Lightmap':
|
||||
uv_layers.active_index = i
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("Lightmap shift A")
|
||||
break
|
||||
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
bpy.ops.mesh.select_all(action='SELECT')
|
||||
bpy.ops.uv.select_all(action='SELECT')
|
||||
bpy.ops.uv.pack_islands(rotate=False, margin=0.001)
|
||||
bpy.ops.uv.select_all(action='DESELECT')
|
||||
bpy.ops.mesh.select_all(action='DESELECT')
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
#print(obj.name + ": Active UV: " + obj.data.uv_layers[obj.data.uv_layers.active_index].name)
|
||||
print("Resized for obj: " + obj.name)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
class TLM_ExploreLightmaps(bpy.types.Operator):
|
||||
|
@ -92,12 +133,23 @@ class TLM_ExploreLightmaps(bpy.types.Operator):
|
|||
|
||||
filepath = bpy.data.filepath
|
||||
dirpath = os.path.join(os.path.dirname(bpy.data.filepath), scene.TLM_EngineProperties.tlm_lightmap_savedir)
|
||||
|
||||
if platform.system() != "Linux":
|
||||
|
||||
if os.path.isdir(dirpath):
|
||||
webbrowser.open('file://' + dirpath)
|
||||
if os.path.isdir(dirpath):
|
||||
webbrowser.open('file://' + dirpath)
|
||||
else:
|
||||
os.mkdir(dirpath)
|
||||
webbrowser.open('file://' + dirpath)
|
||||
else:
|
||||
os.mkdir(dirpath)
|
||||
webbrowser.open('file://' + dirpath)
|
||||
|
||||
if os.path.isdir(dirpath):
|
||||
os.system('xdg-open "%s"' % dirpath)
|
||||
#webbrowser.open('file://' + dirpath)
|
||||
else:
|
||||
os.mkdir(dirpath)
|
||||
os.system('xdg-open "%s"' % dirpath)
|
||||
#webbrowser.open('file://' + dirpath)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
@ -118,7 +170,13 @@ class TLM_EnableSelection(bpy.types.Operator):
|
|||
if scene.TLM_SceneProperties.tlm_override_object_settings:
|
||||
obj.TLM_ObjectProperties.tlm_mesh_lightmap_resolution = scene.TLM_SceneProperties.tlm_mesh_lightmap_resolution
|
||||
obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode = scene.TLM_SceneProperties.tlm_mesh_lightmap_unwrap_mode
|
||||
obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_margin = scene.TLM_SceneProperties.tlm_mesh_unwrap_margin
|
||||
obj.TLM_ObjectProperties.tlm_mesh_unwrap_margin = scene.TLM_SceneProperties.tlm_mesh_unwrap_margin
|
||||
obj.TLM_ObjectProperties.tlm_postpack_object = scene.TLM_SceneProperties.tlm_postpack_object
|
||||
|
||||
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA":
|
||||
obj.TLM_ObjectProperties.tlm_atlas_pointer = scene.TLM_SceneProperties.tlm_atlas_pointer
|
||||
|
||||
obj.TLM_ObjectProperties.tlm_postatlas_pointer = scene.TLM_SceneProperties.tlm_postatlas_pointer
|
||||
|
||||
return{'FINISHED'}
|
||||
|
||||
|
@ -153,4 +211,494 @@ class TLM_RemoveLightmapUV(bpy.types.Operator):
|
|||
if uvlayer.name == "UVMap_Lightmap":
|
||||
uv_layers.remove(uvlayer)
|
||||
|
||||
return{'FINISHED'}
|
||||
return{'FINISHED'}
|
||||
|
||||
class TLM_SelectLightmapped(bpy.types.Operator):
|
||||
"""Select all objects for lightmapping"""
|
||||
bl_idname = "tlm.select_lightmapped_objects"
|
||||
bl_label = "Select lightmap objects"
|
||||
bl_description = "Remove Lightmap UV for selection"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
for obj in bpy.data.objects:
|
||||
if obj.type == "MESH":
|
||||
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
|
||||
|
||||
obj.select_set(True)
|
||||
|
||||
return{'FINISHED'}
|
||||
|
||||
class TLM_AtlasListNewItem(bpy.types.Operator):
|
||||
# Add a new item to the list
|
||||
bl_idname = "tlm_atlaslist.new_item"
|
||||
bl_label = "Add a new item"
|
||||
bl_description = "Create a new AtlasGroup"
|
||||
|
||||
def execute(self, context):
|
||||
scene = context.scene
|
||||
scene.TLM_AtlasList.add()
|
||||
scene.TLM_AtlasListItem = len(scene.TLM_AtlasList) - 1
|
||||
|
||||
scene.TLM_AtlasList[len(scene.TLM_AtlasList) - 1].name = "AtlasGroup"
|
||||
|
||||
return{'FINISHED'}
|
||||
|
||||
class TLM_PostAtlasListNewItem(bpy.types.Operator):
|
||||
# Add a new item to the list
|
||||
bl_idname = "tlm_postatlaslist.new_item"
|
||||
bl_label = "Add a new item"
|
||||
bl_description = "Create a new AtlasGroup"
|
||||
bl_description = ""
|
||||
|
||||
def execute(self, context):
|
||||
scene = context.scene
|
||||
scene.TLM_PostAtlasList.add()
|
||||
scene.TLM_PostAtlasListItem = len(scene.TLM_PostAtlasList) - 1
|
||||
|
||||
scene.TLM_PostAtlasList[len(scene.TLM_PostAtlasList) - 1].name = "AtlasGroup"
|
||||
|
||||
return{'FINISHED'}
|
||||
|
||||
class TLM_AtlastListDeleteItem(bpy.types.Operator):
|
||||
# Delete the selected item from the list
|
||||
bl_idname = "tlm_atlaslist.delete_item"
|
||||
bl_label = "Deletes an item"
|
||||
bl_description = "Delete an AtlasGroup"
|
||||
|
||||
@classmethod
|
||||
def poll(self, context):
|
||||
""" Enable if there's something in the list """
|
||||
scene = context.scene
|
||||
return len(scene.TLM_AtlasList) > 0
|
||||
|
||||
def execute(self, context):
|
||||
scene = context.scene
|
||||
list = scene.TLM_AtlasList
|
||||
index = scene.TLM_AtlasListItem
|
||||
|
||||
for obj in bpy.data.objects:
|
||||
|
||||
atlasName = scene.TLM_AtlasList[index].name
|
||||
|
||||
if obj.TLM_ObjectProperties.tlm_atlas_pointer == atlasName:
|
||||
obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode = "SmartProject"
|
||||
|
||||
list.remove(index)
|
||||
|
||||
if index > 0:
|
||||
index = index - 1
|
||||
|
||||
scene.TLM_AtlasListItem = index
|
||||
return{'FINISHED'}
|
||||
|
||||
class TLM_PostAtlastListDeleteItem(bpy.types.Operator):
|
||||
# Delete the selected item from the list
|
||||
bl_idname = "tlm_postatlaslist.delete_item"
|
||||
bl_label = "Deletes an item"
|
||||
bl_description = "Delete an AtlasGroup"
|
||||
|
||||
@classmethod
|
||||
def poll(self, context):
|
||||
""" Enable if there's something in the list """
|
||||
scene = context.scene
|
||||
return len(scene.TLM_PostAtlasList) > 0
|
||||
|
||||
def execute(self, context):
|
||||
scene = context.scene
|
||||
list = scene.TLM_PostAtlasList
|
||||
index = scene.TLM_PostAtlasListItem
|
||||
|
||||
for obj in bpy.data.objects:
|
||||
|
||||
atlasName = scene.TLM_PostAtlasList[index].name
|
||||
|
||||
if obj.TLM_ObjectProperties.tlm_atlas_pointer == atlasName:
|
||||
obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode = "SmartProject"
|
||||
|
||||
list.remove(index)
|
||||
|
||||
if index > 0:
|
||||
index = index - 1
|
||||
|
||||
scene.TLM_PostAtlasListItem = index
|
||||
return{'FINISHED'}
|
||||
|
||||
class TLM_AtlasListMoveItem(bpy.types.Operator):
|
||||
# Move an item in the list
|
||||
bl_idname = "tlm_atlaslist.move_item"
|
||||
bl_label = "Move an item in the list"
|
||||
bl_description = "Move an item in the list"
|
||||
direction: bpy.props.EnumProperty(
|
||||
items=(
|
||||
('UP', 'Up', ""),
|
||||
('DOWN', 'Down', ""),))
|
||||
|
||||
def move_index(self):
|
||||
# Move index of an item render queue while clamping it
|
||||
scene = context.scene
|
||||
index = scene.TLM_AtlasListItem
|
||||
list_length = len(scene.TLM_AtlasList) - 1
|
||||
new_index = 0
|
||||
|
||||
if self.direction == 'UP':
|
||||
new_index = index - 1
|
||||
elif self.direction == 'DOWN':
|
||||
new_index = index + 1
|
||||
|
||||
new_index = max(0, min(new_index, list_length))
|
||||
scene.TLM_AtlasList.move(index, new_index)
|
||||
scene.TLM_AtlasListItem = new_index
|
||||
|
||||
def execute(self, context):
|
||||
scene = context.scene
|
||||
list = scene.TLM_AtlasList
|
||||
index = scene.TLM_AtlasListItem
|
||||
|
||||
if self.direction == 'DOWN':
|
||||
neighbor = index + 1
|
||||
self.move_index()
|
||||
|
||||
elif self.direction == 'UP':
|
||||
neighbor = index - 1
|
||||
self.move_index()
|
||||
else:
|
||||
return{'CANCELLED'}
|
||||
return{'FINISHED'}
|
||||
|
||||
class TLM_PostAtlasListMoveItem(bpy.types.Operator):
|
||||
# Move an item in the list
|
||||
bl_idname = "tlm_postatlaslist.move_item"
|
||||
bl_label = "Move an item in the list"
|
||||
bl_description = "Move an item in the list"
|
||||
direction: bpy.props.EnumProperty(
|
||||
items=(
|
||||
('UP', 'Up', ""),
|
||||
('DOWN', 'Down', ""),))
|
||||
|
||||
def move_index(self):
|
||||
# Move index of an item render queue while clamping it
|
||||
scene = context.scene
|
||||
index = scene.TLM_PostAtlasListItem
|
||||
list_length = len(scene.TLM_PostAtlasList) - 1
|
||||
new_index = 0
|
||||
|
||||
if self.direction == 'UP':
|
||||
new_index = index - 1
|
||||
elif self.direction == 'DOWN':
|
||||
new_index = index + 1
|
||||
|
||||
new_index = max(0, min(new_index, list_length))
|
||||
scene.TLM_PostAtlasList.move(index, new_index)
|
||||
scene.TLM_PostAtlasListItem = new_index
|
||||
|
||||
def execute(self, context):
|
||||
scene = context.scene
|
||||
list = scene.TLM_PostAtlasList
|
||||
index = scene.TLM_PostAtlasListItem
|
||||
|
||||
if self.direction == 'DOWN':
|
||||
neighbor = index + 1
|
||||
self.move_index()
|
||||
|
||||
elif self.direction == 'UP':
|
||||
neighbor = index - 1
|
||||
self.move_index()
|
||||
else:
|
||||
return{'CANCELLED'}
|
||||
return{'FINISHED'}
|
||||
|
||||
class TLM_StartServer(bpy.types.Operator):
|
||||
bl_idname = "tlm.start_server"
|
||||
bl_label = "Start Network Server"
|
||||
bl_description = "Start Network Server"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def modal(self, context, event):
|
||||
|
||||
#Add progress bar from 0.15
|
||||
|
||||
print("MODAL")
|
||||
|
||||
return {'PASS_THROUGH'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
|
||||
server.startServer()
|
||||
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
class TLM_BuildEnvironmentProbes(bpy.types.Operator):
|
||||
bl_idname = "tlm.build_environmentprobe"
|
||||
bl_label = "Build Environment Probes"
|
||||
bl_description = "Build all environment probes from reflection cubemaps"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
|
||||
for obj in bpy.data.objects:
|
||||
|
||||
if obj.type == "LIGHT_PROBE":
|
||||
if obj.data.type == "CUBEMAP":
|
||||
|
||||
cam_name = "EnvPCam_" + obj.name
|
||||
camera = bpy.data.cameras.new(cam_name)
|
||||
camobj_name = "EnvPCamera_" + obj.name
|
||||
cam_obj = bpy.data.objects.new(camobj_name, camera)
|
||||
bpy.context.collection.objects.link(cam_obj)
|
||||
cam_obj.location = obj.location
|
||||
camera.angle = math.radians(90)
|
||||
|
||||
prevResx = bpy.context.scene.render.resolution_x
|
||||
prevResy = bpy.context.scene.render.resolution_y
|
||||
prevCam = bpy.context.scene.camera
|
||||
prevEngine = bpy.context.scene.render.engine
|
||||
bpy.context.scene.camera = cam_obj
|
||||
|
||||
bpy.context.scene.render.engine = bpy.context.scene.TLM_SceneProperties.tlm_environment_probe_engine
|
||||
bpy.context.scene.render.resolution_x = int(bpy.context.scene.TLM_SceneProperties.tlm_environment_probe_resolution)
|
||||
bpy.context.scene.render.resolution_y = int(bpy.context.scene.TLM_SceneProperties.tlm_environment_probe_resolution)
|
||||
|
||||
savedir = os.path.dirname(bpy.data.filepath)
|
||||
directory = os.path.join(savedir, "Probes")
|
||||
|
||||
t = 90
|
||||
|
||||
inverted = bpy.context.scene.TLM_SceneProperties.tlm_invert_direction
|
||||
|
||||
if inverted:
|
||||
|
||||
positions = {
|
||||
"xp" : (math.radians(t), 0, math.radians(0)),
|
||||
"zp" : (math.radians(t), 0, math.radians(t)),
|
||||
"xm" : (math.radians(t), 0, math.radians(t*2)),
|
||||
"zm" : (math.radians(t), 0, math.radians(-t)),
|
||||
"yp" : (math.radians(t*2), 0, math.radians(t)),
|
||||
"ym" : (0, 0, math.radians(t))
|
||||
}
|
||||
|
||||
else:
|
||||
|
||||
positions = {
|
||||
"xp" : (math.radians(t), 0, math.radians(t*2)),
|
||||
"zp" : (math.radians(t), 0, math.radians(-t)),
|
||||
"xm" : (math.radians(t), 0, math.radians(0)),
|
||||
"zm" : (math.radians(t), 0, math.radians(t)),
|
||||
"yp" : (math.radians(t*2), 0, math.radians(-t)),
|
||||
"ym" : (0, 0, math.radians(-t))
|
||||
}
|
||||
|
||||
|
||||
|
||||
cam = cam_obj
|
||||
image_settings = bpy.context.scene.render.image_settings
|
||||
image_settings.file_format = "HDR"
|
||||
image_settings.color_depth = '32'
|
||||
|
||||
for val in positions:
|
||||
cam.rotation_euler = positions[val]
|
||||
|
||||
filename = os.path.join(directory, val) + "_" + camobj_name + ".hdr"
|
||||
bpy.data.scenes['Scene'].render.filepath = filename
|
||||
print("Writing out: " + val)
|
||||
bpy.ops.render.render(write_still=True)
|
||||
|
||||
cmft_path = bpy.path.abspath(os.path.join(os.path.dirname(bpy.data.filepath), bpy.context.scene.TLM_SceneProperties.tlm_cmft_path))
|
||||
|
||||
output_file_irr = camobj_name + ".hdr"
|
||||
|
||||
posx = directory + "/" + "xp_" + camobj_name + ".hdr"
|
||||
negx = directory + "/" + "xm_" + camobj_name + ".hdr"
|
||||
posy = directory + "/" + "yp_" + camobj_name + ".hdr"
|
||||
negy = directory + "/" + "ym_" + camobj_name + ".hdr"
|
||||
posz = directory + "/" + "zp_" + camobj_name + ".hdr"
|
||||
negz = directory + "/" + "zm_" + camobj_name + ".hdr"
|
||||
output = directory + "/" + camobj_name
|
||||
|
||||
if platform.system() == 'Windows':
|
||||
envpipe = [cmft_path,
|
||||
'--inputFacePosX', posx,
|
||||
'--inputFaceNegX', negx,
|
||||
'--inputFacePosY', posy,
|
||||
'--inputFaceNegY', negy,
|
||||
'--inputFacePosZ', posz,
|
||||
'--inputFaceNegZ', negz,
|
||||
'--output0', output,
|
||||
'--output0params',
|
||||
'hdr,rgbe,latlong']
|
||||
|
||||
else:
|
||||
envpipe = [cmft_path + '--inputFacePosX' + posx
|
||||
+ '--inputFaceNegX' + negx
|
||||
+ '--inputFacePosY' + posy
|
||||
+ '--inputFaceNegY' + negy
|
||||
+ '--inputFacePosZ' + posz
|
||||
+ '--inputFaceNegZ' + negz
|
||||
+ '--output0' + output
|
||||
+ '--output0params' + 'hdr,rgbe,latlong']
|
||||
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("Calling CMFT with:" + str(envpipe))
|
||||
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_create_spherical:
|
||||
subprocess.call(envpipe, shell=True)
|
||||
|
||||
input2 = output + ".hdr"
|
||||
output2 = directory + "/" + camobj_name
|
||||
|
||||
if platform.system() == 'Windows':
|
||||
envpipe2 = [cmft_path,
|
||||
'--input', input2,
|
||||
'--filter', 'shcoeffs',
|
||||
'--outputNum', '1',
|
||||
'--output0', output2]
|
||||
|
||||
else:
|
||||
envpipe2 = [cmft_path +
|
||||
'--input' + input2
|
||||
+ '-filter' + 'shcoeffs'
|
||||
+ '--outputNum' + '1'
|
||||
+ '--output0' + output2]
|
||||
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_write_sh:
|
||||
subprocess.call(envpipe2, shell=True)
|
||||
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_write_radiance:
|
||||
|
||||
use_opencl = 'false'
|
||||
cpu_count = 2
|
||||
|
||||
# 4096 = 256 face
|
||||
# 2048 = 128 face
|
||||
# 1024 = 64 face
|
||||
target_w = int(512)
|
||||
face_size = target_w / 8
|
||||
if target_w == 2048:
|
||||
mip_count = 9
|
||||
elif target_w == 1024:
|
||||
mip_count = 8
|
||||
else:
|
||||
mip_count = 7
|
||||
|
||||
output_file_rad = directory + "/" + camobj_name + "_rad.hdr"
|
||||
|
||||
if platform.system() == 'Windows':
|
||||
|
||||
envpipe3 = [
|
||||
cmft_path,
|
||||
'--input', input2,
|
||||
'--filter', 'radiance',
|
||||
'--dstFaceSize', str(face_size),
|
||||
'--srcFaceSize', str(face_size),
|
||||
'--excludeBase', 'false',
|
||||
# '--mipCount', str(mip_count),
|
||||
'--glossScale', '8',
|
||||
'--glossBias', '3',
|
||||
'--lightingModel', 'blinnbrdf',
|
||||
'--edgeFixup', 'none',
|
||||
'--numCpuProcessingThreads', str(cpu_count),
|
||||
'--useOpenCL', use_opencl,
|
||||
'--clVendor', 'anyGpuVendor',
|
||||
'--deviceType', 'gpu',
|
||||
'--deviceIndex', '0',
|
||||
'--generateMipChain', 'true',
|
||||
'--inputGammaNumerator', '1.0',
|
||||
'--inputGammaDenominator', '1.0',
|
||||
'--outputGammaNumerator', '1.0',
|
||||
'--outputGammaDenominator', '1.0',
|
||||
'--outputNum', '1',
|
||||
'--output0', output_file_rad,
|
||||
'--output0params', 'hdr,rgbe,latlong'
|
||||
]
|
||||
|
||||
subprocess.call(envpipe3)
|
||||
|
||||
else:
|
||||
|
||||
envpipe3 = cmft_path + \
|
||||
' --input "' + input2 + '"' + \
|
||||
' --filter radiance' + \
|
||||
' --dstFaceSize ' + str(face_size) + \
|
||||
' --srcFaceSize ' + str(face_size) + \
|
||||
' --excludeBase false' + \
|
||||
' --glossScale 8' + \
|
||||
' --glossBias 3' + \
|
||||
' --lightingModel blinnbrdf' + \
|
||||
' --edgeFixup none' + \
|
||||
' --numCpuProcessingThreads ' + str(cpu_count) + \
|
||||
' --useOpenCL ' + use_opencl + \
|
||||
' --clVendor anyGpuVendor' + \
|
||||
' --deviceType gpu' + \
|
||||
' --deviceIndex 0' + \
|
||||
' --generateMipChain true' + \
|
||||
' --inputGammaNumerator ' + '1.0' + \
|
||||
' --inputGammaDenominator 1.0' + \
|
||||
' --outputGammaNumerator 1.0' + \
|
||||
' --outputGammaDenominator 1.0' + \
|
||||
' --outputNum 1' + \
|
||||
' --output0 "' + output_file_rad + '"' + \
|
||||
' --output0params hdr,rgbe,latlong'
|
||||
|
||||
subprocess.call([envpipe3], shell=True)
|
||||
|
||||
for obj in bpy.data.objects:
|
||||
obj.select_set(False)
|
||||
|
||||
cam_obj.select_set(True)
|
||||
bpy.ops.object.delete()
|
||||
bpy.context.scene.render.resolution_x = prevResx
|
||||
bpy.context.scene.render.resolution_y = prevResy
|
||||
bpy.context.scene.camera = prevCam
|
||||
bpy.context.scene.render.engine = prevEngine
|
||||
|
||||
print("Finished building environment probes")
|
||||
|
||||
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
class TLM_CleanBuildEnvironmentProbes(bpy.types.Operator):
|
||||
bl_idname = "tlm.clean_environmentprobe"
|
||||
bl_label = "Clean Environment Probes"
|
||||
bl_description = "Clean Environment Probes"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
scene = context.scene
|
||||
|
||||
savedir = os.path.dirname(bpy.data.filepath)
|
||||
dirpath = os.path.join(savedir, "Probes")
|
||||
|
||||
if os.path.isdir(dirpath):
|
||||
for file in os.listdir(dirpath):
|
||||
os.remove(os.path.join(dirpath + "/" + file))
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
class TLM_MergeAdjacentActors(bpy.types.Operator):
|
||||
bl_idname = "tlm.merge_adjacent_actors"
|
||||
bl_label = "Merge adjacent actors"
|
||||
bl_description = "Merges the adjacent faces/vertices of selected objects"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
scene = context.scene
|
||||
|
||||
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
def TLM_DoubleResolution():
|
||||
pass
|
||||
|
||||
def TLM_HalfResolution():
|
||||
pass
|
||||
|
||||
def TLM_DivideLMGroups():
|
||||
pass
|
||||
|
||||
def TLM_LoadFromFolder():
|
||||
pass
|
|
@ -1,15 +1,20 @@
|
|||
import bpy
|
||||
from bpy.utils import register_class, unregister_class
|
||||
from . import scene, object
|
||||
from . renderer import cycles
|
||||
from . import scene, object, atlas
|
||||
from . renderer import cycles, luxcorerender
|
||||
from . denoiser import oidn, optix
|
||||
|
||||
classes = [
|
||||
scene.TLM_SceneProperties,
|
||||
object.TLM_ObjectProperties,
|
||||
cycles.TLM_CyclesSceneProperties,
|
||||
luxcorerender.TLM_LuxCoreSceneProperties,
|
||||
oidn.TLM_OIDNEngineProperties,
|
||||
optix.TLM_OptixEngineProperties
|
||||
optix.TLM_OptixEngineProperties,
|
||||
atlas.TLM_AtlasListItem,
|
||||
atlas.TLM_UL_AtlasList,
|
||||
atlas.TLM_PostAtlasListItem,
|
||||
atlas.TLM_UL_PostAtlasList
|
||||
]
|
||||
|
||||
def register():
|
||||
|
@ -19,8 +24,15 @@ def register():
|
|||
bpy.types.Scene.TLM_SceneProperties = bpy.props.PointerProperty(type=scene.TLM_SceneProperties)
|
||||
bpy.types.Object.TLM_ObjectProperties = bpy.props.PointerProperty(type=object.TLM_ObjectProperties)
|
||||
bpy.types.Scene.TLM_EngineProperties = bpy.props.PointerProperty(type=cycles.TLM_CyclesSceneProperties)
|
||||
bpy.types.Scene.TLM_Engine2Properties = bpy.props.PointerProperty(type=luxcorerender.TLM_LuxCoreSceneProperties)
|
||||
bpy.types.Scene.TLM_OIDNEngineProperties = bpy.props.PointerProperty(type=oidn.TLM_OIDNEngineProperties)
|
||||
bpy.types.Scene.TLM_OptixEngineProperties = bpy.props.PointerProperty(type=optix.TLM_OptixEngineProperties)
|
||||
bpy.types.Scene.TLM_AtlasListItem = bpy.props.IntProperty(name="Index for my_list", default=0)
|
||||
bpy.types.Scene.TLM_AtlasList = bpy.props.CollectionProperty(type=atlas.TLM_AtlasListItem)
|
||||
bpy.types.Scene.TLM_PostAtlasListItem = bpy.props.IntProperty(name="Index for my_list", default=0)
|
||||
bpy.types.Scene.TLM_PostAtlasList = bpy.props.CollectionProperty(type=atlas.TLM_PostAtlasListItem)
|
||||
|
||||
bpy.types.Material.TLM_ignore = bpy.props.BoolProperty(name="Skip material", description="Ignore material for lightmapped object", default=False)
|
||||
|
||||
def unregister():
|
||||
for cls in classes:
|
||||
|
@ -29,5 +41,10 @@ def unregister():
|
|||
del bpy.types.Scene.TLM_SceneProperties
|
||||
del bpy.types.Object.TLM_ObjectProperties
|
||||
del bpy.types.Scene.TLM_EngineProperties
|
||||
del bpy.types.Scene.TLM_Engine2Properties
|
||||
del bpy.types.Scene.TLM_OIDNEngineProperties
|
||||
del bpy.types.Scene.TLM_OptixEngineProperties
|
||||
del bpy.types.Scene.TLM_OptixEngineProperties
|
||||
del bpy.types.Scene.TLM_AtlasListItem
|
||||
del bpy.types.Scene.TLM_AtlasList
|
||||
del bpy.types.Scene.TLM_PostAtlasListItem
|
||||
del bpy.types.Scene.TLM_PostAtlasList
|
130
blender/arm/lightmapper/properties/atlas.py
Normal file
130
blender/arm/lightmapper/properties/atlas.py
Normal file
|
@ -0,0 +1,130 @@
|
|||
import bpy
|
||||
from bpy.props import *
|
||||
|
||||
class TLM_PostAtlasListItem(bpy.types.PropertyGroup):
|
||||
obj: PointerProperty(type=bpy.types.Object, description="The object to bake")
|
||||
tlm_atlas_lightmap_resolution : EnumProperty(
|
||||
items = [('32', '32', 'TODO'),
|
||||
('64', '64', 'TODO'),
|
||||
('128', '128', 'TODO'),
|
||||
('256', '256', 'TODO'),
|
||||
('512', '512', 'TODO'),
|
||||
('1024', '1024', 'TODO'),
|
||||
('2048', '2048', 'TODO'),
|
||||
('4096', '4096', 'TODO'),
|
||||
('8192', '8192', 'TODO')],
|
||||
name = "Atlas Lightmap Resolution",
|
||||
description="TODO",
|
||||
default='256')
|
||||
|
||||
tlm_atlas_repack_on_cleanup : BoolProperty(
|
||||
name="Repack on cleanup",
|
||||
description="Postpacking adjusts the UV's. Toggle to resize back to full scale on cleanup.",
|
||||
default=True)
|
||||
|
||||
tlm_atlas_dilation : BoolProperty(
|
||||
name="Dilation",
|
||||
description="Adds a blurred background layer that acts as a dilation map.",
|
||||
default=False)
|
||||
|
||||
tlm_atlas_unwrap_margin : FloatProperty(
|
||||
name="Unwrap Margin",
|
||||
default=0.1,
|
||||
min=0.0,
|
||||
max=1.0,
|
||||
subtype='FACTOR')
|
||||
|
||||
tlm_atlas_lightmap_unwrap_mode : EnumProperty(
|
||||
items = [('Lightmap', 'Lightmap', 'TODO'),
|
||||
('SmartProject', 'Smart Project', 'TODO'),
|
||||
('Xatlas', 'Xatlas', 'TODO')],
|
||||
name = "Unwrap Mode",
|
||||
description="TODO",
|
||||
default='SmartProject')
|
||||
|
||||
class TLM_UL_PostAtlasList(bpy.types.UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
|
||||
custom_icon = 'OBJECT_DATAMODE'
|
||||
|
||||
if self.layout_type in {'DEFAULT', 'COMPACT'}:
|
||||
|
||||
#In list object counter
|
||||
amount = 0
|
||||
|
||||
for obj in bpy.data.objects:
|
||||
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
|
||||
if obj.TLM_ObjectProperties.tlm_postpack_object:
|
||||
if obj.TLM_ObjectProperties.tlm_postatlas_pointer == item.name:
|
||||
amount = amount + 1
|
||||
|
||||
row = layout.row()
|
||||
row.prop(item, "name", text="", emboss=False, icon=custom_icon)
|
||||
col = row.column()
|
||||
col.label(text=item.tlm_atlas_lightmap_resolution)
|
||||
col = row.column()
|
||||
col.alignment = 'RIGHT'
|
||||
col.label(text=str(amount))
|
||||
|
||||
elif self.layout_type in {'GRID'}:
|
||||
layout.alignment = 'CENTER'
|
||||
layout.label(text="", icon = custom_icon)
|
||||
|
||||
|
||||
|
||||
|
||||
class TLM_AtlasListItem(bpy.types.PropertyGroup):
|
||||
obj: PointerProperty(type=bpy.types.Object, description="The object to bake")
|
||||
tlm_atlas_lightmap_resolution : EnumProperty(
|
||||
items = [('32', '32', 'TODO'),
|
||||
('64', '64', 'TODO'),
|
||||
('128', '128', 'TODO'),
|
||||
('256', '256', 'TODO'),
|
||||
('512', '512', 'TODO'),
|
||||
('1024', '1024', 'TODO'),
|
||||
('2048', '2048', 'TODO'),
|
||||
('4096', '4096', 'TODO'),
|
||||
('8192', '8192', 'TODO')],
|
||||
name = "Atlas Lightmap Resolution",
|
||||
description="TODO",
|
||||
default='256')
|
||||
|
||||
tlm_atlas_unwrap_margin : FloatProperty(
|
||||
name="Unwrap Margin",
|
||||
default=0.1,
|
||||
min=0.0,
|
||||
max=1.0,
|
||||
subtype='FACTOR')
|
||||
|
||||
tlm_atlas_lightmap_unwrap_mode : EnumProperty(
|
||||
items = [('Lightmap', 'Lightmap', 'TODO'),
|
||||
('SmartProject', 'Smart Project', 'TODO'),
|
||||
('Xatlas', 'Xatlas', 'TODO')],
|
||||
name = "Unwrap Mode",
|
||||
description="TODO",
|
||||
default='SmartProject')
|
||||
|
||||
class TLM_UL_AtlasList(bpy.types.UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
|
||||
custom_icon = 'OBJECT_DATAMODE'
|
||||
|
||||
if self.layout_type in {'DEFAULT', 'COMPACT'}:
|
||||
|
||||
amount = 0
|
||||
|
||||
for obj in bpy.data.objects:
|
||||
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
|
||||
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA":
|
||||
if obj.TLM_ObjectProperties.tlm_atlas_pointer == item.name:
|
||||
amount = amount + 1
|
||||
|
||||
row = layout.row()
|
||||
row.prop(item, "name", text="", emboss=False, icon=custom_icon)
|
||||
col = row.column()
|
||||
col.label(text=item.tlm_atlas_lightmap_resolution)
|
||||
col = row.column()
|
||||
col.alignment = 'RIGHT'
|
||||
col.label(text=str(amount))
|
||||
|
||||
elif self.layout_type in {'GRID'}:
|
||||
layout.alignment = 'CENTER'
|
||||
layout.label(text="", icon = custom_icon)
|
|
@ -1,4 +1,5 @@
|
|||
import bpy
|
||||
import bpy, os
|
||||
from ...utility import utility
|
||||
from bpy.props import *
|
||||
|
||||
class TLM_OIDNEngineProperties(bpy.types.PropertyGroup):
|
||||
|
|
10
blender/arm/lightmapper/properties/image.py
Normal file
10
blender/arm/lightmapper/properties/image.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
import bpy
|
||||
from bpy.props import *
|
||||
|
||||
class TLM_ObjectProperties(bpy.types.PropertyGroup):
|
||||
tlm_image_scale_method : EnumProperty(
|
||||
items = [('Native', 'Native', 'TODO'),
|
||||
('OpenCV', 'OpenCV', 'TODO')],
|
||||
name = "Scaling engine",
|
||||
description="TODO",
|
||||
default='Native')
|
|
@ -10,11 +10,31 @@ class TLM_ObjectProperties(bpy.types.PropertyGroup):
|
|||
description = "Atlas Lightmap Group",
|
||||
default = "")
|
||||
|
||||
tlm_postatlas_pointer : StringProperty(
|
||||
name = "Atlas Group",
|
||||
description = "Atlas Lightmap Group",
|
||||
default = "")
|
||||
|
||||
tlm_uvchannel_pointer : StringProperty(
|
||||
name = "UV Channel",
|
||||
description = "Select UV Channel to bake to",
|
||||
default = "")
|
||||
|
||||
tlm_uvchannel_pointer : BoolProperty(
|
||||
name="Enable Lightmapping",
|
||||
description="TODO",
|
||||
default=False)
|
||||
|
||||
tlm_mesh_lightmap_use : BoolProperty(
|
||||
name="Enable Lightmapping",
|
||||
description="TODO",
|
||||
default=False)
|
||||
|
||||
tlm_material_ignore : BoolProperty(
|
||||
name="Skip material",
|
||||
description="Ignore material for lightmapped object",
|
||||
default=False)
|
||||
|
||||
tlm_mesh_lightmap_resolution : EnumProperty(
|
||||
items = [('32', '32', 'TODO'),
|
||||
('64', '64', 'TODO'),
|
||||
|
@ -29,7 +49,15 @@ class TLM_ObjectProperties(bpy.types.PropertyGroup):
|
|||
description="TODO",
|
||||
default='256')
|
||||
|
||||
unwrap_modes = [('Lightmap', 'Lightmap', 'TODO'),('SmartProject', 'Smart Project', 'TODO'),('CopyExisting', 'Copy Existing', 'TODO'),('AtlasGroup', 'Atlas Group', 'TODO')]
|
||||
unwrap_modes = [('Lightmap', 'Lightmap', 'TODO'),
|
||||
('SmartProject', 'Smart Project', 'TODO'),
|
||||
('CopyExisting', 'Copy Existing', 'TODO'),
|
||||
('AtlasGroupA', 'Atlas Group (Prepack)', 'TODO')]
|
||||
|
||||
tlm_postpack_object : BoolProperty( #CHECK INSTEAD OF ATLASGROUPB
|
||||
name="Postpack object",
|
||||
description="Postpack object into an AtlasGroup",
|
||||
default=False)
|
||||
|
||||
if "blender_xatlas" in addon_keys:
|
||||
unwrap_modes.append(('Xatlas', 'Xatlas', 'TODO'))
|
||||
|
@ -117,5 +145,4 @@ class TLM_ObjectProperties(bpy.types.PropertyGroup):
|
|||
name="Median kernel",
|
||||
default=3,
|
||||
min=1,
|
||||
max=5)
|
||||
|
||||
max=5)
|
|
@ -84,4 +84,12 @@ class TLM_CyclesSceneProperties(bpy.types.PropertyGroup):
|
|||
('zero', 'Zero', 'Temporarily set to 0 during baking, and reapply after')],
|
||||
name = "Metallic handling",
|
||||
description="Set metallic handling mode to prevent black-baking.",
|
||||
default="ignore")
|
||||
default="ignore")
|
||||
|
||||
tlm_lighting_mode : EnumProperty(
|
||||
items = [('combined', 'Combined', 'Bake combined lighting'),
|
||||
('indirect', 'Indirect', 'Bake indirect lighting'),
|
||||
('ao', 'AO', 'Bake only Ambient Occlusion')],
|
||||
name = "Lighting mode",
|
||||
description="TODO.",
|
||||
default="combined")
|
|
@ -0,0 +1,11 @@
|
|||
import bpy
|
||||
from bpy.props import *
|
||||
|
||||
class TLM_LuxCoreSceneProperties(bpy.types.PropertyGroup):
|
||||
|
||||
#Luxcore specific here
|
||||
tlm_luxcore_dir : StringProperty(
|
||||
name="Luxcore Directory",
|
||||
description="Standalone path to your LuxCoreRender binary.",
|
||||
default="",
|
||||
subtype="FILE_PATH")
|
|
@ -5,9 +5,19 @@ class TLM_SceneProperties(bpy.types.PropertyGroup):
|
|||
|
||||
engines = [('Cycles', 'Cycles', 'Use Cycles for lightmapping')]
|
||||
|
||||
#engines.append(('LuxCoreRender', 'LuxCoreRender', 'Use LuxCoreRender for lightmapping'))
|
||||
engines.append(('LuxCoreRender', 'LuxCoreRender', 'Use LuxCoreRender for lightmapping'))
|
||||
#engines.append(('OctaneRender', 'Octane Render', 'Use Octane Render for lightmapping'))
|
||||
|
||||
tlm_atlas_pointer : StringProperty(
|
||||
name = "Atlas Group",
|
||||
description = "Atlas Lightmap Group",
|
||||
default = "")
|
||||
|
||||
tlm_postatlas_pointer : StringProperty(
|
||||
name = "Atlas Group",
|
||||
description = "Atlas Lightmap Group",
|
||||
default = "")
|
||||
|
||||
tlm_lightmap_engine : EnumProperty(
|
||||
items = engines,
|
||||
name = "Lightmap Engine",
|
||||
|
@ -102,8 +112,8 @@ class TLM_SceneProperties(bpy.types.PropertyGroup):
|
|||
|
||||
#FILTERING SETTINGS GROUP
|
||||
tlm_filtering_use : BoolProperty(
|
||||
name="Enable filtering",
|
||||
description="Enable filtering for lightmaps",
|
||||
name="Enable Filtering",
|
||||
description="Enable denoising for lightmaps",
|
||||
default=False)
|
||||
|
||||
tlm_filtering_engine : EnumProperty(
|
||||
|
@ -178,10 +188,30 @@ class TLM_SceneProperties(bpy.types.PropertyGroup):
|
|||
description="Enable encoding for lightmaps",
|
||||
default=False)
|
||||
|
||||
tlm_encoding_mode : EnumProperty(
|
||||
items = [('RGBM', 'RGBM', '8-bit HDR encoding. Good for compatibility, good for memory but has banding issues.'),
|
||||
tlm_encoding_device : EnumProperty(
|
||||
items = [('CPU', 'CPU', 'Todo'),
|
||||
('GPU', 'GPU', 'Todo.')],
|
||||
name = "Encoding Device",
|
||||
description="TODO",
|
||||
default='CPU')
|
||||
|
||||
encoding_modes_1 = [('RGBM', 'RGBM', '8-bit HDR encoding. Good for compatibility, good for memory but has banding issues.'),
|
||||
('RGBD', 'RGBD', '8-bit HDR encoding. Similar to RGBM.'),
|
||||
('HDR', 'HDR', '32-bit HDR encoding. Best quality, but high memory usage and not compatible with all devices.')]
|
||||
|
||||
encoding_modes_2 = [('RGBM', 'RGBM', '8-bit HDR encoding. Good for compatibility, good for memory but has banding issues.'),
|
||||
('RGBD', 'RGBD', '8-bit HDR encoding. Similar to RGBM.'),
|
||||
('LogLuv', 'LogLuv', '8-bit HDR encoding. Different.'),
|
||||
('HDR', 'HDR', '32-bit HDR encoding. Best quality, but high memory usage and not compatible with all devices.')],
|
||||
('HDR', 'HDR', '32-bit HDR encoding. Best quality, but high memory usage and not compatible with all devices.')]
|
||||
|
||||
tlm_encoding_mode_a : EnumProperty(
|
||||
items = encoding_modes_1,
|
||||
name = "Encoding Mode",
|
||||
description="TODO",
|
||||
default='HDR')
|
||||
|
||||
tlm_encoding_mode_b : EnumProperty(
|
||||
items = encoding_modes_2,
|
||||
name = "Encoding Mode",
|
||||
description="TODO",
|
||||
default='HDR')
|
||||
|
@ -191,10 +221,10 @@ class TLM_SceneProperties(bpy.types.PropertyGroup):
|
|||
description="Higher gives a larger HDR range, but also gives more banding.",
|
||||
default=6,
|
||||
min=1,
|
||||
max=10)
|
||||
max=255)
|
||||
|
||||
tlm_encoding_armory_setup : BoolProperty(
|
||||
name="Use Armory decoder",
|
||||
tlm_decoder_setup : BoolProperty(
|
||||
name="Use decoder",
|
||||
description="TODO",
|
||||
default=False)
|
||||
|
||||
|
@ -246,11 +276,17 @@ class TLM_SceneProperties(bpy.types.PropertyGroup):
|
|||
items = [('Lightmap', 'Lightmap', 'TODO'),
|
||||
('SmartProject', 'Smart Project', 'TODO'),
|
||||
('CopyExisting', 'Copy Existing', 'TODO'),
|
||||
('AtlasGroup', 'Atlas Group', 'TODO')],
|
||||
('AtlasGroupA', 'Atlas Group (Prepack)', 'TODO'),
|
||||
('Xatlas', 'Xatlas', 'TODO')],
|
||||
name = "Unwrap Mode",
|
||||
description="TODO",
|
||||
default='SmartProject')
|
||||
|
||||
tlm_postpack_object : BoolProperty( #CHECK INSTEAD OF ATLASGROUPB
|
||||
name="Postpack object",
|
||||
description="Postpack object into an AtlasGroup",
|
||||
default=False)
|
||||
|
||||
tlm_mesh_unwrap_margin : FloatProperty(
|
||||
name="Unwrap Margin",
|
||||
default=0.1,
|
||||
|
@ -263,6 +299,13 @@ class TLM_SceneProperties(bpy.types.PropertyGroup):
|
|||
description="Headless; Do not apply baked materials on finish.",
|
||||
default=False)
|
||||
|
||||
tlm_atlas_mode : EnumProperty(
|
||||
items = [('Prepack', 'Pre-packing', 'Todo.'),
|
||||
('Postpack', 'Post-packing', 'Todo.')],
|
||||
name = "Atlas mode",
|
||||
description="TODO",
|
||||
default='Prepack')
|
||||
|
||||
tlm_alert_sound : EnumProperty(
|
||||
items = [('dash', 'Dash', 'Dash alert'),
|
||||
('noot', 'Noot', 'Noot alert'),
|
||||
|
@ -270,4 +313,100 @@ class TLM_SceneProperties(bpy.types.PropertyGroup):
|
|||
('pingping', 'Ping', 'Ping alert')],
|
||||
name = "Alert sound",
|
||||
description="Alert sound when lightmap building finished.",
|
||||
default="gentle")
|
||||
default="gentle")
|
||||
|
||||
tlm_metallic_clamp : EnumProperty(
|
||||
items = [('ignore', 'Ignore', 'Ignore clamping'),
|
||||
('zero', 'Zero', 'Set zero'),
|
||||
('limit', 'Limit', 'Clamp to 0.9')],
|
||||
name = "Metallic clamping",
|
||||
description="TODO.",
|
||||
default="ignore")
|
||||
|
||||
tlm_verbose : BoolProperty(
|
||||
name="Verbose",
|
||||
description="Verbose console output",
|
||||
default=False)
|
||||
|
||||
tlm_compile_statistics : BoolProperty(
|
||||
name="Compile statistics",
|
||||
description="Compile lightbuild statistics",
|
||||
default=False)
|
||||
|
||||
tlm_override_bg_color : BoolProperty(
|
||||
name="Override background",
|
||||
description="Override background color, black by default.",
|
||||
default=False)
|
||||
|
||||
tlm_override_color : FloatVectorProperty(name="Color",
|
||||
description="Background color for baked maps",
|
||||
subtype='COLOR',
|
||||
default=[0.5,0.5,0.5])
|
||||
|
||||
tlm_reset_uv : BoolProperty(
|
||||
name="Remove Lightmap UV",
|
||||
description="Remove existing UV maps for lightmaps.",
|
||||
default=False)
|
||||
|
||||
tlm_network_render : BoolProperty(
|
||||
name="Enable network rendering",
|
||||
description="Enable network rendering (Unstable).",
|
||||
default=False)
|
||||
|
||||
tlm_network_paths : PointerProperty(
|
||||
name="Network file",
|
||||
description="Network instruction file",
|
||||
type=bpy.types.Text)
|
||||
|
||||
tlm_network_dir : StringProperty(
|
||||
name="Network directory",
|
||||
description="Use a path that is accessible to all your network render devices.",
|
||||
default="",
|
||||
subtype="FILE_PATH")
|
||||
|
||||
tlm_cmft_path : StringProperty(
|
||||
name="CMFT Path",
|
||||
description="The path to the CMFT binaries",
|
||||
default="",
|
||||
subtype="FILE_PATH")
|
||||
|
||||
tlm_create_spherical : BoolProperty(
|
||||
name="Create spherical texture",
|
||||
description="Merge cubemap to a 360 spherical texture.",
|
||||
default=False)
|
||||
|
||||
tlm_write_sh : BoolProperty(
|
||||
name="Calculate SH coefficients",
|
||||
description="Calculates spherical harmonics coefficients to a file.",
|
||||
default=False)
|
||||
|
||||
tlm_write_radiance : BoolProperty(
|
||||
name="Write radiance images",
|
||||
description="Writes out the radiance images.",
|
||||
default=False)
|
||||
|
||||
tlm_invert_direction : BoolProperty(
|
||||
name="Invert direction",
|
||||
description="Inverts the direction.",
|
||||
default=False)
|
||||
|
||||
tlm_environment_probe_resolution : EnumProperty(
|
||||
items = [('32', '32', 'TODO'),
|
||||
('64', '64', 'TODO'),
|
||||
('128', '128', 'TODO'),
|
||||
('256', '256', 'TODO'),
|
||||
('512', '512', 'TODO'),
|
||||
('1024', '1024', 'TODO'),
|
||||
('2048', '2048', 'TODO'),
|
||||
('4096', '4096', 'TODO'),
|
||||
('8192', '8192', 'TODO')],
|
||||
name = "Probe Resolution",
|
||||
description="TODO",
|
||||
default='256')
|
||||
|
||||
tlm_environment_probe_engine : EnumProperty(
|
||||
items = [('BLENDER_EEVEE', 'Eevee', 'TODO'),
|
||||
('CYCLES', 'Cycles', 'TODO')],
|
||||
name = "Probe Render Engine",
|
||||
description="TODO",
|
||||
default='BLENDER_EEVEE')
|
|
@ -1,15 +1,25 @@
|
|||
import bpy, os, importlib, subprocess, sys, threading, platform, aud
|
||||
from . import encoding
|
||||
import bpy, os, subprocess, sys, platform, aud, json, datetime, socket
|
||||
import threading
|
||||
from . import encoding, pack
|
||||
from . cycles import lightmap, prepare, nodes, cache
|
||||
from . denoiser import integrated, oidn, optix
|
||||
from . filtering import opencv
|
||||
from .. network import client
|
||||
from os import listdir
|
||||
from os.path import isfile, join
|
||||
from time import time, sleep
|
||||
from importlib import util
|
||||
|
||||
previous_settings = {}
|
||||
|
||||
def prepare_build(self=0, background_mode=False):
|
||||
postprocess_shutdown = False
|
||||
|
||||
def prepare_build(self=0, background_mode=False, shutdown_after_build=False):
|
||||
|
||||
if shutdown_after_build:
|
||||
postprocess_shutdown = True
|
||||
|
||||
print("Building lightmaps")
|
||||
|
||||
if bpy.context.scene.TLM_EngineProperties.tlm_bake_mode == "Foreground" or background_mode==True:
|
||||
|
||||
|
@ -19,20 +29,6 @@ def prepare_build(self=0, background_mode=False):
|
|||
scene = bpy.context.scene
|
||||
sceneProperties = scene.TLM_SceneProperties
|
||||
|
||||
#We dynamically load the renderer and denoiser, instead of loading something we don't use
|
||||
|
||||
if sceneProperties.tlm_lightmap_engine == "Cycles":
|
||||
|
||||
pass
|
||||
|
||||
if sceneProperties.tlm_lightmap_engine == "LuxCoreRender":
|
||||
|
||||
pass
|
||||
|
||||
if sceneProperties.tlm_lightmap_engine == "OctaneRender":
|
||||
|
||||
pass
|
||||
|
||||
#Timer start here bound to global
|
||||
|
||||
if check_save():
|
||||
|
@ -56,6 +52,12 @@ def prepare_build(self=0, background_mode=False):
|
|||
self.report({'INFO'}, "Error:Filtering - OpenCV not installed")
|
||||
return{'FINISHED'}
|
||||
|
||||
#TODO DO some resolution change
|
||||
#if checkAtlasSize():
|
||||
# print("Error: AtlasGroup overflow")
|
||||
# self.report({'INFO'}, "Error: AtlasGroup overflow - Too many objects")
|
||||
# return{'FINISHED'}
|
||||
|
||||
setMode()
|
||||
|
||||
dirpath = os.path.join(os.path.dirname(bpy.data.filepath), bpy.context.scene.TLM_EngineProperties.tlm_lightmap_savedir)
|
||||
|
@ -65,6 +67,17 @@ def prepare_build(self=0, background_mode=False):
|
|||
#Naming check
|
||||
naming_check()
|
||||
|
||||
# if sceneProperties.tlm_reset_uv or sceneProperties.tlm_atlas_mode == "Postpack":
|
||||
# for obj in bpy.data.objects:
|
||||
# if obj.type == "MESH":
|
||||
# uv_layers = obj.data.uv_layers
|
||||
|
||||
|
||||
|
||||
#for uvlayer in uv_layers:
|
||||
# if uvlayer.name == "UVMap_Lightmap":
|
||||
# uv_layers.remove(uvlayer)
|
||||
|
||||
## RENDER DEPENDENCY FROM HERE
|
||||
|
||||
if sceneProperties.tlm_lightmap_engine == "Cycles":
|
||||
|
@ -73,7 +86,7 @@ def prepare_build(self=0, background_mode=False):
|
|||
|
||||
if sceneProperties.tlm_lightmap_engine == "LuxCoreRender":
|
||||
|
||||
pass
|
||||
setup.init(self, previous_settings)
|
||||
|
||||
if sceneProperties.tlm_lightmap_engine == "OctaneRender":
|
||||
|
||||
|
@ -91,6 +104,8 @@ def prepare_build(self=0, background_mode=False):
|
|||
|
||||
filepath = bpy.data.filepath
|
||||
|
||||
bpy.ops.wm.save_as_mainfile(filepath=bpy.data.filepath)
|
||||
|
||||
start_time = time()
|
||||
|
||||
scene = bpy.context.scene
|
||||
|
@ -140,19 +155,113 @@ def prepare_build(self=0, background_mode=False):
|
|||
#Naming check
|
||||
naming_check()
|
||||
|
||||
pipe_open([sys.executable,"-b",filepath,"--python-expr",'import bpy; import thelightmapper; thelightmapper.addon.utility.build.prepare_build(0, True);'], finish_assemble)
|
||||
if scene.TLM_SceneProperties.tlm_network_render:
|
||||
|
||||
def finish_assemble():
|
||||
pass
|
||||
#bpy.ops.wm.revert_mainfile() We cannot use this, as Blender crashes...
|
||||
print("Background baking finished")
|
||||
print("NETWORK RENDERING")
|
||||
|
||||
if scene.TLM_SceneProperties.tlm_network_paths != None:
|
||||
HOST = bpy.data.texts[scene.TLM_SceneProperties.tlm_network_paths.name].lines[0].body # The server's hostname or IP address
|
||||
else:
|
||||
HOST = '127.0.0.1' # The server's hostname or IP address
|
||||
|
||||
PORT = 9898 # The port used by the server
|
||||
|
||||
client.connect_client(HOST, PORT, bpy.data.filepath, 0)
|
||||
|
||||
# with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
# s.connect((HOST, PORT))
|
||||
# message = {
|
||||
# "call" : 1,
|
||||
# "command" : 1,
|
||||
# "enquiry" : 0,
|
||||
# "args" : bpy.data.filepath
|
||||
# }
|
||||
|
||||
# s.sendall(json.dumps(message).encode())
|
||||
# data = s.recv(1024)
|
||||
# print(data.decode())
|
||||
|
||||
finish_assemble()
|
||||
|
||||
else:
|
||||
|
||||
bpy.app.driver_namespace["alpha"] = 0
|
||||
|
||||
bpy.app.driver_namespace["tlm_process"] = False
|
||||
|
||||
if os.path.exists(os.path.join(dirpath, "process.tlm")):
|
||||
os.remove(os.path.join(dirpath, "process.tlm"))
|
||||
|
||||
bpy.app.timers.register(distribute_building)
|
||||
|
||||
def distribute_building():
|
||||
|
||||
#CHECK IF THERE'S AN EXISTING SUBPROCESS
|
||||
|
||||
if not os.path.isfile(os.path.join(os.path.dirname(bpy.data.filepath), bpy.context.scene.TLM_EngineProperties.tlm_lightmap_savedir, "process.tlm")):
|
||||
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("No process file - Creating one...")
|
||||
|
||||
write_directory = os.path.join(os.path.dirname(bpy.data.filepath), bpy.context.scene.TLM_EngineProperties.tlm_lightmap_savedir)
|
||||
|
||||
blendPath = bpy.data.filepath
|
||||
|
||||
process_status = [blendPath,
|
||||
{'bake': 'all',
|
||||
'completed': False
|
||||
}]
|
||||
|
||||
with open(os.path.join(write_directory, "process.tlm"), 'w') as file:
|
||||
json.dump(process_status, file, indent=2)
|
||||
|
||||
bpy.app.driver_namespace["tlm_process"] = subprocess.Popen([sys.executable,"-b", blendPath,"--python-expr",'import bpy; import thelightmapper; thelightmapper.addon.utility.build.prepare_build(0, True);'], shell=False, stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL)
|
||||
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("Started process: " + str(bpy.app.driver_namespace["tlm_process"]) + " at " + str(datetime.datetime.now()))
|
||||
|
||||
else:
|
||||
|
||||
write_directory = os.path.join(os.path.dirname(bpy.data.filepath), bpy.context.scene.TLM_EngineProperties.tlm_lightmap_savedir)
|
||||
|
||||
process_status = json.loads(open(os.path.join(write_directory, "process.tlm")).read())
|
||||
|
||||
if process_status[1]["completed"]:
|
||||
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("Baking finished")
|
||||
|
||||
bpy.app.timers.unregister(distribute_building)
|
||||
|
||||
finish_assemble()
|
||||
|
||||
else:
|
||||
|
||||
#Open the json and check the status!
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("Baking in progress")
|
||||
|
||||
process_status = json.loads(open(os.path.join(write_directory, "process.tlm")).read())
|
||||
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print(process_status)
|
||||
|
||||
return 1.0
|
||||
|
||||
|
||||
def finish_assemble(self=0):
|
||||
|
||||
print("Finishing assembly")
|
||||
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("Background baking finished")
|
||||
|
||||
scene = bpy.context.scene
|
||||
sceneProperties = scene.TLM_SceneProperties
|
||||
|
||||
if sceneProperties.tlm_lightmap_engine == "Cycles":
|
||||
|
||||
prepare.init(previous_settings)
|
||||
prepare.init(self, previous_settings)
|
||||
|
||||
if sceneProperties.tlm_lightmap_engine == "LuxCoreRender":
|
||||
pass
|
||||
|
@ -162,20 +271,10 @@ def finish_assemble():
|
|||
|
||||
manage_build(True)
|
||||
|
||||
def pipe_open(args, callback):
|
||||
|
||||
def thread_process(args, callback):
|
||||
process = subprocess.Popen(args)
|
||||
process.wait()
|
||||
callback()
|
||||
return
|
||||
|
||||
thread = threading.Thread(target=thread_process, args=(args, callback))
|
||||
thread.start()
|
||||
return thread
|
||||
|
||||
def begin_build():
|
||||
|
||||
print("Beginning build")
|
||||
|
||||
dirpath = os.path.join(os.path.dirname(bpy.data.filepath), bpy.context.scene.TLM_EngineProperties.tlm_lightmap_savedir)
|
||||
|
||||
scene = bpy.context.scene
|
||||
|
@ -192,7 +291,6 @@ def begin_build():
|
|||
pass
|
||||
|
||||
#Denoiser
|
||||
|
||||
if sceneProperties.tlm_denoise_use:
|
||||
|
||||
if sceneProperties.tlm_denoise_engine == "Integrated":
|
||||
|
@ -205,7 +303,8 @@ def begin_build():
|
|||
if file.endswith("_baked.hdr"):
|
||||
baked_image_array.append(file)
|
||||
|
||||
print(baked_image_array)
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print(baked_image_array)
|
||||
|
||||
denoiser = integrated.TLM_Integrated_Denoise()
|
||||
|
||||
|
@ -267,22 +366,52 @@ def begin_build():
|
|||
|
||||
filter.init(dirpath, useDenoise)
|
||||
|
||||
if sceneProperties.tlm_encoding_use:
|
||||
#Encoding
|
||||
if sceneProperties.tlm_encoding_use and scene.TLM_EngineProperties.tlm_bake_mode != "Background":
|
||||
|
||||
if sceneProperties.tlm_encoding_mode == "HDR":
|
||||
if sceneProperties.tlm_encoding_device == "CPU":
|
||||
|
||||
if sceneProperties.tlm_format == "EXR":
|
||||
if sceneProperties.tlm_encoding_mode_a == "HDR":
|
||||
|
||||
print("EXR Format")
|
||||
if sceneProperties.tlm_format == "EXR":
|
||||
|
||||
ren = bpy.context.scene.render
|
||||
ren.image_settings.file_format = "OPEN_EXR"
|
||||
#ren.image_settings.exr_codec = "scene.TLM_SceneProperties.tlm_exr_codec"
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("EXR Format")
|
||||
|
||||
ren = bpy.context.scene.render
|
||||
ren.image_settings.file_format = "OPEN_EXR"
|
||||
#ren.image_settings.exr_codec = "scene.TLM_SceneProperties.tlm_exr_codec"
|
||||
|
||||
end = "_baked"
|
||||
|
||||
baked_image_array = []
|
||||
|
||||
if sceneProperties.tlm_denoise_use:
|
||||
|
||||
end = "_denoised"
|
||||
|
||||
if sceneProperties.tlm_filtering_use:
|
||||
|
||||
end = "_filtered"
|
||||
|
||||
#For each image in folder ending in denoised/filtered
|
||||
dirfiles = [f for f in listdir(dirpath) if isfile(join(dirpath, f))]
|
||||
|
||||
for file in dirfiles:
|
||||
if file.endswith(end + ".hdr"):
|
||||
|
||||
img = bpy.data.images.load(os.path.join(dirpath,file))
|
||||
img.save_render(img.filepath_raw[:-4] + ".exr")
|
||||
|
||||
if sceneProperties.tlm_encoding_mode_a == "RGBM":
|
||||
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("ENCODING RGBM")
|
||||
|
||||
dirfiles = [f for f in listdir(dirpath) if isfile(join(dirpath, f))]
|
||||
|
||||
end = "_baked"
|
||||
|
||||
baked_image_array = []
|
||||
|
||||
if sceneProperties.tlm_denoise_use:
|
||||
|
||||
end = "_denoised"
|
||||
|
@ -290,65 +419,155 @@ def begin_build():
|
|||
if sceneProperties.tlm_filtering_use:
|
||||
|
||||
end = "_filtered"
|
||||
|
||||
#For each image in folder ending in denoised/filtered
|
||||
dirfiles = [f for f in listdir(dirpath) if isfile(join(dirpath, f))]
|
||||
|
||||
for file in dirfiles:
|
||||
if file.endswith(end + ".hdr"):
|
||||
|
||||
img = bpy.data.images.load(os.path.join(dirpath,file))
|
||||
img.save_render(img.filepath_raw[:-4] + ".exr")
|
||||
img = bpy.data.images.load(os.path.join(dirpath, file), check_existing=False)
|
||||
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("Encoding:" + str(file))
|
||||
encoding.encodeImageRGBMCPU(img, sceneProperties.tlm_encoding_range, dirpath, 0)
|
||||
|
||||
if sceneProperties.tlm_encoding_mode == "LogLuv":
|
||||
if sceneProperties.tlm_encoding_mode_b == "RGBD":
|
||||
|
||||
dirfiles = [f for f in listdir(dirpath) if isfile(join(dirpath, f))]
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("ENCODING RGBD")
|
||||
|
||||
end = "_baked"
|
||||
dirfiles = [f for f in listdir(dirpath) if isfile(join(dirpath, f))]
|
||||
|
||||
if sceneProperties.tlm_denoise_use:
|
||||
end = "_baked"
|
||||
|
||||
end = "_denoised"
|
||||
if sceneProperties.tlm_denoise_use:
|
||||
|
||||
if sceneProperties.tlm_filtering_use:
|
||||
end = "_denoised"
|
||||
|
||||
end = "_filtered"
|
||||
if sceneProperties.tlm_filtering_use:
|
||||
|
||||
for file in dirfiles:
|
||||
if file.endswith(end + ".hdr"):
|
||||
end = "_filtered"
|
||||
|
||||
img = bpy.data.images.load(os.path.join(dirpath, file), check_existing=False)
|
||||
for file in dirfiles:
|
||||
if file.endswith(end + ".hdr"):
|
||||
|
||||
img = bpy.data.images.load(os.path.join(dirpath, file), check_existing=False)
|
||||
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("Encoding:" + str(file))
|
||||
encoding.encodeImageRGBDCPU(img, sceneProperties.tlm_encoding_range, dirpath, 0)
|
||||
|
||||
else:
|
||||
|
||||
if sceneProperties.tlm_encoding_mode_b == "HDR":
|
||||
|
||||
if sceneProperties.tlm_format == "EXR":
|
||||
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("EXR Format")
|
||||
|
||||
ren = bpy.context.scene.render
|
||||
ren.image_settings.file_format = "OPEN_EXR"
|
||||
#ren.image_settings.exr_codec = "scene.TLM_SceneProperties.tlm_exr_codec"
|
||||
|
||||
end = "_baked"
|
||||
|
||||
baked_image_array = []
|
||||
|
||||
if sceneProperties.tlm_denoise_use:
|
||||
|
||||
end = "_denoised"
|
||||
|
||||
if sceneProperties.tlm_filtering_use:
|
||||
|
||||
end = "_filtered"
|
||||
|
||||
encoding.encodeLogLuv(img, dirpath, 0)
|
||||
#For each image in folder ending in denoised/filtered
|
||||
dirfiles = [f for f in listdir(dirpath) if isfile(join(dirpath, f))]
|
||||
|
||||
if sceneProperties.tlm_encoding_mode == "RGBM":
|
||||
for file in dirfiles:
|
||||
if file.endswith(end + ".hdr"):
|
||||
|
||||
print("ENCODING RGBM")
|
||||
img = bpy.data.images.load(os.path.join(dirpath,file))
|
||||
img.save_render(img.filepath_raw[:-4] + ".exr")
|
||||
|
||||
dirfiles = [f for f in listdir(dirpath) if isfile(join(dirpath, f))]
|
||||
if sceneProperties.tlm_encoding_mode_b == "LogLuv":
|
||||
|
||||
end = "_baked"
|
||||
dirfiles = [f for f in listdir(dirpath) if isfile(join(dirpath, f))]
|
||||
|
||||
if sceneProperties.tlm_denoise_use:
|
||||
end = "_baked"
|
||||
|
||||
end = "_denoised"
|
||||
if sceneProperties.tlm_denoise_use:
|
||||
|
||||
if sceneProperties.tlm_filtering_use:
|
||||
end = "_denoised"
|
||||
|
||||
end = "_filtered"
|
||||
if sceneProperties.tlm_filtering_use:
|
||||
|
||||
for file in dirfiles:
|
||||
if file.endswith(end + ".hdr"):
|
||||
end = "_filtered"
|
||||
|
||||
img = bpy.data.images.load(os.path.join(dirpath, file), check_existing=False)
|
||||
|
||||
print("Encoding:" + str(file))
|
||||
encoding.encodeImageRGBM(img, sceneProperties.tlm_encoding_range, dirpath, 0)
|
||||
for file in dirfiles:
|
||||
if file.endswith(end + ".hdr"):
|
||||
|
||||
img = bpy.data.images.load(os.path.join(dirpath, file), check_existing=False)
|
||||
|
||||
encoding.encodeLogLuvGPU(img, dirpath, 0)
|
||||
|
||||
if sceneProperties.tlm_encoding_mode_b == "RGBM":
|
||||
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("ENCODING RGBM")
|
||||
|
||||
dirfiles = [f for f in listdir(dirpath) if isfile(join(dirpath, f))]
|
||||
|
||||
end = "_baked"
|
||||
|
||||
if sceneProperties.tlm_denoise_use:
|
||||
|
||||
end = "_denoised"
|
||||
|
||||
if sceneProperties.tlm_filtering_use:
|
||||
|
||||
end = "_filtered"
|
||||
|
||||
for file in dirfiles:
|
||||
if file.endswith(end + ".hdr"):
|
||||
|
||||
img = bpy.data.images.load(os.path.join(dirpath, file), check_existing=False)
|
||||
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("Encoding:" + str(file))
|
||||
encoding.encodeImageRGBMGPU(img, sceneProperties.tlm_encoding_range, dirpath, 0)
|
||||
|
||||
if sceneProperties.tlm_encoding_mode_b == "RGBD":
|
||||
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("ENCODING RGBD")
|
||||
|
||||
dirfiles = [f for f in listdir(dirpath) if isfile(join(dirpath, f))]
|
||||
|
||||
end = "_baked"
|
||||
|
||||
if sceneProperties.tlm_denoise_use:
|
||||
|
||||
end = "_denoised"
|
||||
|
||||
if sceneProperties.tlm_filtering_use:
|
||||
|
||||
end = "_filtered"
|
||||
|
||||
for file in dirfiles:
|
||||
if file.endswith(end + ".hdr"):
|
||||
|
||||
img = bpy.data.images.load(os.path.join(dirpath, file), check_existing=False)
|
||||
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("Encoding:" + str(file))
|
||||
encoding.encodeImageRGBDGPU(img, sceneProperties.tlm_encoding_range, dirpath, 0)
|
||||
|
||||
manage_build()
|
||||
|
||||
def manage_build(background_pass=False):
|
||||
|
||||
print("Managing build")
|
||||
|
||||
scene = bpy.context.scene
|
||||
sceneProperties = scene.TLM_SceneProperties
|
||||
|
||||
|
@ -371,25 +590,108 @@ def manage_build(background_pass=False):
|
|||
|
||||
formatEnc = ".hdr"
|
||||
|
||||
if sceneProperties.tlm_encoding_use:
|
||||
if sceneProperties.tlm_encoding_use and scene.TLM_EngineProperties.tlm_bake_mode != "Background":
|
||||
|
||||
if sceneProperties.tlm_encoding_mode == "HDR":
|
||||
if sceneProperties.tlm_encoding_device == "CPU":
|
||||
|
||||
if sceneProperties.tlm_format == "EXR":
|
||||
print("CPU Encoding")
|
||||
|
||||
formatEnc = ".exr"
|
||||
if sceneProperties.tlm_encoding_mode_a == "HDR":
|
||||
|
||||
if sceneProperties.tlm_encoding_mode == "LogLuv":
|
||||
if sceneProperties.tlm_format == "EXR":
|
||||
|
||||
formatEnc = "_encoded.png"
|
||||
formatEnc = ".exr"
|
||||
|
||||
if sceneProperties.tlm_encoding_mode == "RGBM":
|
||||
if sceneProperties.tlm_encoding_mode_a == "RGBM":
|
||||
|
||||
formatEnc = "_encoded.png"
|
||||
formatEnc = "_encoded.png"
|
||||
|
||||
if sceneProperties.tlm_encoding_mode_a == "RGBD":
|
||||
|
||||
formatEnc = "_encoded.png"
|
||||
|
||||
else:
|
||||
|
||||
print("GPU Encoding")
|
||||
|
||||
if sceneProperties.tlm_encoding_mode_b == "HDR":
|
||||
|
||||
if sceneProperties.tlm_format == "EXR":
|
||||
|
||||
formatEnc = ".exr"
|
||||
|
||||
if sceneProperties.tlm_encoding_mode_b == "LogLuv":
|
||||
|
||||
formatEnc = "_encoded.png"
|
||||
|
||||
if sceneProperties.tlm_encoding_mode_b == "RGBM":
|
||||
|
||||
formatEnc = "_encoded.png"
|
||||
|
||||
if sceneProperties.tlm_encoding_mode_b == "RGBD":
|
||||
|
||||
formatEnc = "_encoded.png"
|
||||
|
||||
if not background_pass:
|
||||
nodes.exchangeLightmapsToPostfix("_baked", end, formatEnc)
|
||||
|
||||
if scene.TLM_EngineProperties.tlm_setting_supersample == "2x":
|
||||
supersampling_scale = 2
|
||||
elif scene.TLM_EngineProperties.tlm_setting_supersample == "4x":
|
||||
supersampling_scale = 4
|
||||
else:
|
||||
supersampling_scale = 1
|
||||
|
||||
pack.postpack()
|
||||
|
||||
for image in bpy.data.images:
|
||||
if image.users < 1:
|
||||
bpy.data.images.remove(image)
|
||||
|
||||
if scene.TLM_SceneProperties.tlm_headless:
|
||||
|
||||
filepath = bpy.data.filepath
|
||||
dirpath = os.path.join(os.path.dirname(bpy.data.filepath), scene.TLM_EngineProperties.tlm_lightmap_savedir)
|
||||
|
||||
for obj in bpy.data.objects:
|
||||
if obj.type == "MESH":
|
||||
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
|
||||
cache.backup_material_restore(obj)
|
||||
|
||||
for obj in bpy.data.objects:
|
||||
if obj.type == "MESH":
|
||||
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
|
||||
cache.backup_material_rename(obj)
|
||||
|
||||
for mat in bpy.data.materials:
|
||||
if mat.users < 1:
|
||||
bpy.data.materials.remove(mat)
|
||||
|
||||
for mat in bpy.data.materials:
|
||||
if mat.name.startswith("."):
|
||||
if "_Original" in mat.name:
|
||||
bpy.data.materials.remove(mat)
|
||||
|
||||
for obj in bpy.data.objects:
|
||||
|
||||
if obj.type == "MESH":
|
||||
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
|
||||
img_name = obj.name + '_baked'
|
||||
Lightmapimage = bpy.data.images[img_name]
|
||||
obj["Lightmap"] = Lightmapimage.filepath_raw
|
||||
|
||||
for image in bpy.data.images:
|
||||
if image.name.endswith("_baked"):
|
||||
bpy.data.images.remove(image, do_unlink=True)
|
||||
|
||||
total_time = sec_to_hours((time() - start_time))
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print(total_time)
|
||||
|
||||
bpy.context.scene["TLM_Buildstat"] = total_time
|
||||
|
||||
reset_settings(previous_settings["settings"])
|
||||
|
||||
if sceneProperties.tlm_lightmap_engine == "LuxCoreRender":
|
||||
|
||||
pass
|
||||
|
@ -400,66 +702,6 @@ def manage_build(background_pass=False):
|
|||
|
||||
if bpy.context.scene.TLM_EngineProperties.tlm_bake_mode == "Background":
|
||||
pass
|
||||
#bpy.ops.wm.save_as_mainfile(filepath=bpy.data.filepath + "baked") #Crashes Blender
|
||||
|
||||
if scene.TLM_EngineProperties.tlm_setting_supersample == "2x":
|
||||
supersampling_scale = 2
|
||||
elif scene.TLM_EngineProperties.tlm_setting_supersample == "4x":
|
||||
supersampling_scale = 4
|
||||
else:
|
||||
supersampling_scale = 1
|
||||
|
||||
# for image in bpy.data.images:
|
||||
# if image.name.endswith("_baked"):
|
||||
# resolution = image.size[0]
|
||||
# rescale = resolution / supersampling_scale
|
||||
# image.scale(rescale, rescale)
|
||||
# image.save()
|
||||
|
||||
for image in bpy.data.images:
|
||||
if image.users < 1:
|
||||
bpy.data.images.remove(image)
|
||||
|
||||
if scene.TLM_SceneProperties.tlm_headless:
|
||||
|
||||
filepath = bpy.data.filepath
|
||||
dirpath = os.path.join(os.path.dirname(bpy.data.filepath), scene.TLM_EngineProperties.tlm_lightmap_savedir)
|
||||
|
||||
for obj in bpy.data.objects:
|
||||
if obj.type == "MESH":
|
||||
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
|
||||
cache.backup_material_restore(obj)
|
||||
|
||||
for obj in bpy.data.objects:
|
||||
if obj.type == "MESH":
|
||||
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
|
||||
cache.backup_material_rename(obj)
|
||||
|
||||
for mat in bpy.data.materials:
|
||||
if mat.users < 1:
|
||||
bpy.data.materials.remove(mat)
|
||||
|
||||
for mat in bpy.data.materials:
|
||||
if mat.name.startswith("."):
|
||||
if "_Original" in mat.name:
|
||||
bpy.data.materials.remove(mat)
|
||||
|
||||
for obj in bpy.data.objects:
|
||||
|
||||
if obj.type == "MESH":
|
||||
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
|
||||
img_name = obj.name + '_baked'
|
||||
Lightmapimage = bpy.data.images[img_name]
|
||||
obj["Lightmap"] = Lightmapimage.filepath_raw
|
||||
|
||||
for image in bpy.data.images:
|
||||
if image.name.endswith("_baked"):
|
||||
bpy.data.images.remove(image, do_unlink=True)
|
||||
|
||||
total_time = sec_to_hours((time() - start_time))
|
||||
print(total_time)
|
||||
|
||||
reset_settings(previous_settings["settings"])
|
||||
|
||||
if scene.TLM_SceneProperties.tlm_alert_on_finish:
|
||||
|
||||
|
@ -481,6 +723,29 @@ def manage_build(background_pass=False):
|
|||
sound = aud.Sound.file(sound_path)
|
||||
device.play(sound)
|
||||
|
||||
print("Lightmap building finished")
|
||||
|
||||
if bpy.app.background:
|
||||
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("Writing background process report")
|
||||
|
||||
write_directory = os.path.join(os.path.dirname(bpy.data.filepath), bpy.context.scene.TLM_EngineProperties.tlm_lightmap_savedir)
|
||||
|
||||
if os.path.exists(os.path.join(write_directory, "process.tlm")):
|
||||
|
||||
process_status = json.loads(open(os.path.join(write_directory, "process.tlm")).read())
|
||||
|
||||
process_status[1]["completed"] = True
|
||||
|
||||
with open(os.path.join(write_directory, "process.tlm"), 'w') as file:
|
||||
json.dump(process_status, file, indent=2)
|
||||
|
||||
if postprocess_shutdown:
|
||||
sys.exit()
|
||||
|
||||
#TODO - SET BELOW TO UTILITY
|
||||
|
||||
def reset_settings(prev_settings):
|
||||
scene = bpy.context.scene
|
||||
cycles = scene.cycles
|
||||
|
@ -546,7 +811,7 @@ def naming_check():
|
|||
|
||||
def opencv_check():
|
||||
|
||||
cv2 = importlib.util.find_spec("cv2")
|
||||
cv2 = util.find_spec("cv2")
|
||||
|
||||
if cv2 is not None:
|
||||
return 0
|
||||
|
@ -589,7 +854,8 @@ def check_materials():
|
|||
mat = slot.material
|
||||
|
||||
if mat is None:
|
||||
print("MatNone")
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("MatNone")
|
||||
mat = bpy.data.materials.new(name="Material")
|
||||
mat.use_nodes = True
|
||||
slot.material = mat
|
||||
|
@ -609,4 +875,41 @@ def sec_to_hours(seconds):
|
|||
def setMode():
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
#TODO Make some checks that returns to previous selection
|
||||
#TODO Make some checks that returns to previous selection
|
||||
|
||||
def checkAtlasSize():
|
||||
|
||||
overflow = False
|
||||
|
||||
scene = bpy.context.scene
|
||||
|
||||
if scene.TLM_EngineProperties.tlm_setting_supersample == "2x":
|
||||
supersampling_scale = 2
|
||||
elif scene.TLM_EngineProperties.tlm_setting_supersample == "4x":
|
||||
supersampling_scale = 4
|
||||
else:
|
||||
supersampling_scale = 1
|
||||
|
||||
for atlas in bpy.context.scene.TLM_PostAtlasList:
|
||||
|
||||
atlas_resolution = int(int(atlas.tlm_atlas_lightmap_resolution) / int(scene.TLM_EngineProperties.tlm_resolution_scale) * int(supersampling_scale))
|
||||
|
||||
utilized = 0
|
||||
atlasUsedArea = 0
|
||||
|
||||
for obj in bpy.data.objects:
|
||||
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
|
||||
if obj.TLM_ObjectProperties.tlm_postpack_object:
|
||||
if obj.TLM_ObjectProperties.tlm_postatlas_pointer == atlas.name:
|
||||
|
||||
atlasUsedArea += int(obj.TLM_ObjectProperties.tlm_mesh_lightmap_resolution) ** 2
|
||||
|
||||
utilized = atlasUsedArea / (int(atlas_resolution) ** 2)
|
||||
if (utilized * 100) > 100:
|
||||
overflow = True
|
||||
print("Overflow for: " + str(atlas.name))
|
||||
|
||||
if overflow == True:
|
||||
return True
|
||||
else:
|
||||
return False
|
|
@ -13,28 +13,35 @@ def backup_material_cache(slot, path):
|
|||
bpy.ops.wm.save_as_mainfile(filepath=path, copy=True)
|
||||
|
||||
def backup_material_cache_restore(slot, path):
|
||||
print("Restore cache")
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("Restore cache")
|
||||
|
||||
def backup_material_rename(obj):
|
||||
if "TLM_PrevMatArray" in obj:
|
||||
print("Has PrevMat B")
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("Renaming material for: " + obj.name)
|
||||
|
||||
for slot in obj.material_slots:
|
||||
|
||||
if slot.material is not None:
|
||||
if slot.material.name.endswith("_Original"):
|
||||
newname = slot.material.name[1:-9]
|
||||
if newname in bpy.data.materials:
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("Removing material: " + bpy.data.materials[newname].name)
|
||||
bpy.data.materials.remove(bpy.data.materials[newname])
|
||||
slot.material.name = newname
|
||||
|
||||
del obj["TLM_PrevMatArray"]
|
||||
|
||||
def backup_material_restore(obj):
|
||||
print("RESTORE")
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("Restoring material for: " + obj.name)
|
||||
|
||||
if "TLM_PrevMatArray" in obj:
|
||||
|
||||
print("Has PrevMat A")
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("Material restore array found: " + str(obj["TLM_PrevMatArray"]))
|
||||
#Running through the slots
|
||||
prevMatArray = obj["TLM_PrevMatArray"]
|
||||
slotsLength = len(prevMatArray)
|
||||
|
@ -43,12 +50,17 @@ def backup_material_restore(obj):
|
|||
for idx, slot in enumerate(obj.material_slots): #For each slot, we get the index
|
||||
#We only need the index, corresponds to the array index
|
||||
try:
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("Attempting to set material")
|
||||
originalMaterial = prevMatArray[idx]
|
||||
except IndexError:
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("Material restore failed - Resetting")
|
||||
originalMaterial = ""
|
||||
|
||||
if slot.material is not None:
|
||||
slot.material.user_clear()
|
||||
#slot.material.user_clear() Seems to be bad; See: https://developer.blender.org/T49837
|
||||
bpy.data.materials.remove(slot.material)
|
||||
|
||||
if "." + originalMaterial + "_Original" in bpy.data.materials:
|
||||
slot.material = bpy.data.materials["." + originalMaterial + "_Original"]
|
||||
|
|
|
@ -32,9 +32,20 @@ def bake():
|
|||
obj.hide_render = False
|
||||
scene.render.bake.use_clear = False
|
||||
|
||||
print("Baking " + str(currentIterNum) + "/" + str(iterNum) + " (" + str(round(currentIterNum/iterNum*100, 2)) + "%) : " + obj.name)
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("Baking " + str(currentIterNum) + "/" + str(iterNum) + " (" + str(round(currentIterNum/iterNum*100, 2)) + "%) : " + obj.name)
|
||||
|
||||
bpy.ops.object.bake(type="DIFFUSE", pass_filter={"DIRECT","INDIRECT"}, margin=scene.TLM_EngineProperties.tlm_dilation_margin, use_clear=False)
|
||||
if scene.TLM_EngineProperties.tlm_lighting_mode == "combined" or scene.TLM_EngineProperties.tlm_lighting_mode == "combinedAO":
|
||||
bpy.ops.object.bake(type="DIFFUSE", pass_filter={"DIRECT","INDIRECT"}, margin=scene.TLM_EngineProperties.tlm_dilation_margin, use_clear=False)
|
||||
elif scene.TLM_EngineProperties.tlm_lighting_mode == "indirect" or scene.TLM_EngineProperties.tlm_lighting_mode == "indirectAO":
|
||||
bpy.ops.object.bake(type="DIFFUSE", pass_filter={"INDIRECT"}, margin=scene.TLM_EngineProperties.tlm_dilation_margin, use_clear=False)
|
||||
elif scene.TLM_EngineProperties.tlm_lighting_mode == "ao":
|
||||
bpy.ops.object.bake(type="AO", margin=scene.TLM_EngineProperties.tlm_dilation_margin, use_clear=False)
|
||||
elif scene.TLM_EngineProperties.tlm_lighting_mode == "complete":
|
||||
bpy.ops.object.bake(type="COMBINED", margin=scene.TLM_EngineProperties.tlm_dilation_margin, use_clear=False)
|
||||
else:
|
||||
bpy.ops.object.bake(type="DIFFUSE", pass_filter={"DIRECT","INDIRECT"}, margin=scene.TLM_EngineProperties.tlm_dilation_margin, use_clear=False)
|
||||
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
currentIterNum = currentIterNum + 1
|
||||
|
||||
|
@ -46,5 +57,6 @@ def bake():
|
|||
filepath_ext = ".hdr"
|
||||
image.filepath_raw = bakemap_path + filepath_ext
|
||||
image.file_format = "HDR"
|
||||
print("Saving to: " + image.filepath_raw)
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("Saving to: " + image.filepath_raw)
|
||||
image.save()
|
|
@ -49,6 +49,21 @@ def apply_materials():
|
|||
old = slot.material
|
||||
slot.material = bpy.data.materials[old.name.split('_' + obj.name)[0]]
|
||||
|
||||
if(scene.TLM_SceneProperties.tlm_decoder_setup):
|
||||
|
||||
tlm_rgbm = bpy.data.node_groups.get('RGBM Decode')
|
||||
tlm_rgbd = bpy.data.node_groups.get('RGBD Decode')
|
||||
tlm_logluv = bpy.data.node_groups.get('LogLuv Decode')
|
||||
|
||||
if tlm_rgbm == None:
|
||||
load_library('RGBM Decode')
|
||||
|
||||
if tlm_rgbd == None:
|
||||
load_library('RGBD Decode')
|
||||
|
||||
if tlm_logluv == None:
|
||||
load_library('LogLuv Decode')
|
||||
|
||||
if(scene.TLM_EngineProperties.tlm_exposure_multiplier > 0):
|
||||
tlm_exposure = bpy.data.node_groups.get("Exposure")
|
||||
|
||||
|
@ -56,88 +71,174 @@ def apply_materials():
|
|||
load_library("Exposure")
|
||||
|
||||
#Apply materials
|
||||
print(obj.name)
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print(obj.name)
|
||||
for slot in obj.material_slots:
|
||||
mat = slot.material
|
||||
print(slot.material)
|
||||
|
||||
node_tree = mat.node_tree
|
||||
nodes = mat.node_tree.nodes
|
||||
|
||||
foundBakedNode = False
|
||||
|
||||
#Find nodes
|
||||
for node in nodes:
|
||||
if node.name == "Baked Image":
|
||||
lightmapNode = node
|
||||
lightmapNode.location = -800, 300
|
||||
lightmapNode.name = "TLM_Lightmap"
|
||||
foundBakedNode = True
|
||||
|
||||
img_name = obj.name + '_baked'
|
||||
mat = slot.material
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print(slot.material)
|
||||
|
||||
if not foundBakedNode:
|
||||
lightmapNode = node_tree.nodes.new(type="ShaderNodeTexImage")
|
||||
lightmapNode.location = -300, 300
|
||||
lightmapNode.image = bpy.data.images[img_name]
|
||||
lightmapNode.name = "TLM_Lightmap"
|
||||
lightmapNode.interpolation = "Smart"
|
||||
if not mat.TLM_ignore:
|
||||
|
||||
#Find output node
|
||||
outputNode = nodes[0]
|
||||
if(outputNode.type != "OUTPUT_MATERIAL"):
|
||||
for node in node_tree.nodes:
|
||||
if node.type == "OUTPUT_MATERIAL":
|
||||
outputNode = node
|
||||
break
|
||||
node_tree = mat.node_tree
|
||||
nodes = mat.node_tree.nodes
|
||||
|
||||
#Find mainnode
|
||||
mainNode = outputNode.inputs[0].links[0].from_node
|
||||
foundBakedNode = False
|
||||
|
||||
#Add all nodes first
|
||||
#Add lightmap multipliction texture
|
||||
mixNode = node_tree.nodes.new(type="ShaderNodeMixRGB")
|
||||
mixNode.name = "Lightmap_Multiplication"
|
||||
mixNode.location = -300, 300
|
||||
mixNode.blend_type = 'MULTIPLY'
|
||||
mixNode.inputs[0].default_value = 1.0
|
||||
#Find nodes
|
||||
for node in nodes:
|
||||
if node.name == "Baked Image":
|
||||
lightmapNode = node
|
||||
lightmapNode.location = -800, 300
|
||||
lightmapNode.name = "TLM_Lightmap"
|
||||
foundBakedNode = True
|
||||
|
||||
img_name = obj.name + '_baked'
|
||||
|
||||
UVLightmap = node_tree.nodes.new(type="ShaderNodeUVMap")
|
||||
UVLightmap.uv_map = "UVMap_Lightmap"
|
||||
UVLightmap.name = "Lightmap_UV"
|
||||
UVLightmap.location = -1000, 300
|
||||
if not foundBakedNode:
|
||||
lightmapNode = node_tree.nodes.new(type="ShaderNodeTexImage")
|
||||
lightmapNode.location = -300, 300
|
||||
lightmapNode.name = "TLM_Lightmap"
|
||||
lightmapNode.interpolation = "Smart"
|
||||
|
||||
if(scene.TLM_EngineProperties.tlm_exposure_multiplier > 0):
|
||||
ExposureNode = node_tree.nodes.new(type="ShaderNodeGroup")
|
||||
ExposureNode.node_tree = bpy.data.node_groups["Exposure"]
|
||||
ExposureNode.inputs[1].default_value = scene.TLM_EngineProperties.tlm_exposure_multiplier
|
||||
ExposureNode.location = -500, 300
|
||||
ExposureNode.name = "Lightmap_Exposure"
|
||||
if (obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA" and obj.TLM_ObjectProperties.tlm_atlas_pointer != ""):
|
||||
lightmapNode.image = bpy.data.images[obj.TLM_ObjectProperties.tlm_atlas_pointer + "_baked"]
|
||||
else:
|
||||
lightmapNode.image = bpy.data.images[img_name]
|
||||
|
||||
#Add Basecolor node
|
||||
if len(mainNode.inputs[0].links) == 0:
|
||||
baseColorValue = mainNode.inputs[0].default_value
|
||||
baseColorNode = node_tree.nodes.new(type="ShaderNodeRGB")
|
||||
baseColorNode.outputs[0].default_value = baseColorValue
|
||||
baseColorNode.location = ((mainNode.location[0] - 500, mainNode.location[1] - 300))
|
||||
baseColorNode.name = "Lightmap_BasecolorNode_A"
|
||||
else:
|
||||
baseColorNode = mainNode.inputs[0].links[0].from_node
|
||||
baseColorNode.name = "LM_P"
|
||||
#Find output node
|
||||
outputNode = nodes[0]
|
||||
if(outputNode.type != "OUTPUT_MATERIAL"):
|
||||
for node in node_tree.nodes:
|
||||
if node.type == "OUTPUT_MATERIAL":
|
||||
outputNode = node
|
||||
break
|
||||
|
||||
#Linking
|
||||
if(scene.TLM_EngineProperties.tlm_exposure_multiplier > 0):
|
||||
mat.node_tree.links.new(lightmapNode.outputs[0], ExposureNode.inputs[0]) #Connect lightmap node to mixnode
|
||||
mat.node_tree.links.new(ExposureNode.outputs[0], mixNode.inputs[1]) #Connect lightmap node to mixnode
|
||||
else:
|
||||
mat.node_tree.links.new(lightmapNode.outputs[0], mixNode.inputs[1]) #Connect lightmap node to mixnode
|
||||
mat.node_tree.links.new(baseColorNode.outputs[0], mixNode.inputs[2]) #Connect basecolor to pbr node
|
||||
mat.node_tree.links.new(mixNode.outputs[0], mainNode.inputs[0]) #Connect mixnode to pbr node
|
||||
mat.node_tree.links.new(UVLightmap.outputs[0], lightmapNode.inputs[0]) #Connect uvnode to lightmapnode
|
||||
#Find mainnode
|
||||
mainNode = outputNode.inputs[0].links[0].from_node
|
||||
|
||||
#Clamp metallic
|
||||
|
||||
# if scene.TLM_SceneProperties.tlm_metallic_clamp != "ignore":
|
||||
# if mainNode.type == "BSDF_PRINCIPLED":
|
||||
|
||||
# if len(mainNode.inputs[4].links) == 0:
|
||||
|
||||
# if scene.TLM_SceneProperties.tlm_metallic_clamp == "zero":
|
||||
# mainNode.inputs[4].default_value = 0.0
|
||||
# else:
|
||||
# mainNode.inputs[4].default_value = 0.99
|
||||
|
||||
# else:
|
||||
|
||||
# pass
|
||||
|
||||
#Add all nodes first
|
||||
#Add lightmap multipliction texture
|
||||
mixNode = node_tree.nodes.new(type="ShaderNodeMixRGB")
|
||||
mixNode.name = "Lightmap_Multiplication"
|
||||
mixNode.location = -300, 300
|
||||
if scene.TLM_EngineProperties.tlm_lighting_mode == "indirect" or scene.TLM_EngineProperties.tlm_lighting_mode == "indirectAO":
|
||||
mixNode.blend_type = 'ADD'
|
||||
else:
|
||||
mixNode.blend_type = 'MULTIPLY'
|
||||
mixNode.inputs[0].default_value = 1.0
|
||||
|
||||
UVLightmap = node_tree.nodes.new(type="ShaderNodeUVMap")
|
||||
UVLightmap.uv_map = "UVMap_Lightmap"
|
||||
UVLightmap.name = "Lightmap_UV"
|
||||
UVLightmap.location = -1000, 300
|
||||
|
||||
if(scene.TLM_SceneProperties.tlm_decoder_setup):
|
||||
if scene.TLM_SceneProperties.tlm_encoding_device == "CPU":
|
||||
if scene.TLM_SceneProperties.tlm_encoding_mode_a == 'RGBM':
|
||||
DecodeNode = node_tree.nodes.new(type="ShaderNodeGroup")
|
||||
DecodeNode.node_tree = bpy.data.node_groups["RGBM Decode"]
|
||||
DecodeNode.location = -400, 300
|
||||
DecodeNode.name = "Lightmap_RGBM_Decode"
|
||||
decoding = True
|
||||
if scene.TLM_SceneProperties.tlm_encoding_mode_b == "RGBD":
|
||||
DecodeNode = node_tree.nodes.new(type="ShaderNodeGroup")
|
||||
DecodeNode.node_tree = bpy.data.node_groups["RGBD Decode"]
|
||||
DecodeNode.location = -400, 300
|
||||
DecodeNode.name = "Lightmap_RGBD_Decode"
|
||||
decoding = True
|
||||
else:
|
||||
if scene.TLM_SceneProperties.tlm_encoding_mode_b == 'RGBM':
|
||||
DecodeNode = node_tree.nodes.new(type="ShaderNodeGroup")
|
||||
DecodeNode.node_tree = bpy.data.node_groups["RGBM Decode"]
|
||||
DecodeNode.location = -400, 300
|
||||
DecodeNode.name = "Lightmap_RGBM_Decode"
|
||||
decoding = True
|
||||
if scene.TLM_SceneProperties.tlm_encoding_mode_b == "RGBD":
|
||||
DecodeNode = node_tree.nodes.new(type="ShaderNodeGroup")
|
||||
DecodeNode.node_tree = bpy.data.node_groups["RGBD Decode"]
|
||||
DecodeNode.location = -400, 300
|
||||
DecodeNode.name = "Lightmap_RGBD_Decode"
|
||||
decoding = True
|
||||
if scene.TLM_SceneProperties.tlm_encoding_mode_b == "LogLuv":
|
||||
DecodeNode = node_tree.nodes.new(type="ShaderNodeGroup")
|
||||
DecodeNode.node_tree = bpy.data.node_groups["LogLuv Decode"]
|
||||
DecodeNode.location = -400, 300
|
||||
DecodeNode.name = "Lightmap_LogLuv_Decode"
|
||||
decoding = True
|
||||
|
||||
if(scene.TLM_EngineProperties.tlm_exposure_multiplier > 0):
|
||||
ExposureNode = node_tree.nodes.new(type="ShaderNodeGroup")
|
||||
ExposureNode.node_tree = bpy.data.node_groups["Exposure"]
|
||||
ExposureNode.inputs[1].default_value = scene.TLM_EngineProperties.tlm_exposure_multiplier
|
||||
ExposureNode.location = -500, 300
|
||||
ExposureNode.name = "Lightmap_Exposure"
|
||||
|
||||
#Add Basecolor node
|
||||
if len(mainNode.inputs[0].links) == 0:
|
||||
baseColorValue = mainNode.inputs[0].default_value
|
||||
baseColorNode = node_tree.nodes.new(type="ShaderNodeRGB")
|
||||
baseColorNode.outputs[0].default_value = baseColorValue
|
||||
baseColorNode.location = ((mainNode.location[0] - 500, mainNode.location[1] - 300))
|
||||
baseColorNode.name = "Lightmap_BasecolorNode_A"
|
||||
else:
|
||||
baseColorNode = mainNode.inputs[0].links[0].from_node
|
||||
baseColorNode.name = "LM_P"
|
||||
|
||||
#Linking
|
||||
|
||||
if decoding and scene.TLM_SceneProperties.tlm_encoding_use:
|
||||
|
||||
if(scene.TLM_EngineProperties.tlm_exposure_multiplier > 0):
|
||||
|
||||
mat.node_tree.links.new(lightmapNode.outputs[0], DecodeNode.inputs[0]) #Connect lightmap node to mixnode
|
||||
mat.node_tree.links.new(lightmapNode.outputs[1], DecodeNode.inputs[1]) #Connect lightmap node to mixnode
|
||||
|
||||
mat.node_tree.links.new(DecodeNode.outputs[0], mixNode.inputs[1]) #Connect lightmap node to mixnode
|
||||
mat.node_tree.links.new(ExposureNode.outputs[0], mixNode.inputs[1]) #Connect lightmap node to mixnode
|
||||
|
||||
else:
|
||||
mat.node_tree.links.new(lightmapNode.outputs[0], DecodeNode.inputs[0]) #Connect lightmap node to mixnode
|
||||
mat.node_tree.links.new(lightmapNode.outputs[1], DecodeNode.inputs[1]) #Connect lightmap node to mixnode
|
||||
|
||||
mat.node_tree.links.new(DecodeNode.outputs[0], mixNode.inputs[1]) #Connect lightmap node to mixnode
|
||||
|
||||
mat.node_tree.links.new(baseColorNode.outputs[0], mixNode.inputs[2]) #Connect basecolor to pbr node
|
||||
mat.node_tree.links.new(mixNode.outputs[0], mainNode.inputs[0]) #Connect mixnode to pbr node
|
||||
mat.node_tree.links.new(UVLightmap.outputs[0], lightmapNode.inputs[0]) #Connect uvnode to lightmapnode
|
||||
|
||||
else:
|
||||
|
||||
if(scene.TLM_EngineProperties.tlm_exposure_multiplier > 0):
|
||||
mat.node_tree.links.new(lightmapNode.outputs[0], ExposureNode.inputs[0]) #Connect lightmap node to mixnode
|
||||
mat.node_tree.links.new(ExposureNode.outputs[0], mixNode.inputs[1]) #Connect lightmap node to mixnode
|
||||
else:
|
||||
mat.node_tree.links.new(lightmapNode.outputs[0], mixNode.inputs[1]) #Connect lightmap node to mixnode
|
||||
mat.node_tree.links.new(baseColorNode.outputs[0], mixNode.inputs[2]) #Connect basecolor to pbr node
|
||||
mat.node_tree.links.new(mixNode.outputs[0], mainNode.inputs[0]) #Connect mixnode to pbr node
|
||||
mat.node_tree.links.new(UVLightmap.outputs[0], lightmapNode.inputs[0]) #Connect uvnode to lightmapnode
|
||||
|
||||
def exchangeLightmapsToPostfix(ext_postfix, new_postfix, formatHDR=".hdr"):
|
||||
|
||||
print(ext_postfix, new_postfix, formatHDR)
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print(ext_postfix, new_postfix, formatHDR)
|
||||
|
||||
for obj in bpy.data.objects:
|
||||
if obj.type == "MESH":
|
||||
|
@ -151,7 +252,8 @@ def exchangeLightmapsToPostfix(ext_postfix, new_postfix, formatHDR=".hdr"):
|
|||
if node.name == "Baked Image" or node.name == "TLM_Lightmap":
|
||||
img_name = node.image.filepath_raw
|
||||
cutLen = len(ext_postfix + formatHDR)
|
||||
print("Len:" + str(len(ext_postfix + formatHDR)) + "|" + ext_postfix + ".." + formatHDR)
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("Len:" + str(len(ext_postfix + formatHDR)) + "|" + ext_postfix + ".." + formatHDR)
|
||||
|
||||
#Simple way to sort out objects with multiple materials
|
||||
if formatHDR == ".hdr" or formatHDR == ".exr":
|
||||
|
@ -172,7 +274,7 @@ def load_library(asset_name):
|
|||
if bpy.data.filepath.endswith('tlm_data.blend'): # Prevent load in library itself
|
||||
return
|
||||
|
||||
data_path = os.path.abspath(os.path.join(scriptDir, '..', '..', 'Assets/tlm_data.blend'))
|
||||
data_path = os.path.abspath(os.path.join(scriptDir, '..', '..', 'assets/tlm_data.blend'))
|
||||
data_names = [asset_name]
|
||||
|
||||
# Import
|
||||
|
|
|
@ -64,9 +64,83 @@ def configure_meshes(self):
|
|||
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
|
||||
for slot in obj.material_slots:
|
||||
if "." + slot.name + '_Original' in bpy.data.materials:
|
||||
print("The material: " + slot.name + " shifted to " + "." + slot.name + '_Original')
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("The material: " + slot.name + " shifted to " + "." + slot.name + '_Original')
|
||||
slot.material = bpy.data.materials["." + slot.name + '_Original']
|
||||
|
||||
|
||||
#ATLAS
|
||||
for atlasgroup in scene.TLM_AtlasList:
|
||||
|
||||
atlas = atlasgroup.name
|
||||
atlas_items = []
|
||||
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
|
||||
for obj in bpy.data.objects:
|
||||
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA":
|
||||
|
||||
uv_layers = obj.data.uv_layers
|
||||
if not "UVMap_Lightmap" in uv_layers:
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("UVMap made A")
|
||||
uvmap = uv_layers.new(name="UVMap_Lightmap")
|
||||
uv_layers.active_index = len(uv_layers) - 1
|
||||
else:
|
||||
print("Existing found...skipping")
|
||||
for i in range(0, len(uv_layers)):
|
||||
if uv_layers[i].name == 'UVMap_Lightmap':
|
||||
uv_layers.active_index = i
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("Lightmap shift A")
|
||||
break
|
||||
|
||||
atlas_items.append(obj)
|
||||
obj.select_set(True)
|
||||
|
||||
if scene.TLM_SceneProperties.tlm_apply_on_unwrap:
|
||||
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
|
||||
|
||||
if atlasgroup.tlm_atlas_lightmap_unwrap_mode == "SmartProject":
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("Smart Project A for: " + str(atlas_items))
|
||||
for obj in atlas_items:
|
||||
print(obj.name + ": Active UV: " + obj.data.uv_layers[obj.data.uv_layers.active_index].name)
|
||||
|
||||
if len(atlas_items) > 0:
|
||||
bpy.context.view_layer.objects.active = atlas_items[0]
|
||||
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
bpy.ops.mesh.select_all(action='SELECT')
|
||||
bpy.ops.uv.smart_project(angle_limit=45.0, island_margin=atlasgroup.tlm_atlas_unwrap_margin, user_area_weight=1.0, use_aspect=True, stretch_to_bounds=False)
|
||||
bpy.ops.mesh.select_all(action='DESELECT')
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
elif atlasgroup.tlm_atlas_lightmap_unwrap_mode == "Lightmap":
|
||||
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
bpy.ops.uv.lightmap_pack('EXEC_SCREEN', PREF_CONTEXT='ALL_FACES', PREF_MARGIN_DIV=atlasgroup.tlm_atlas_unwrap_margin)
|
||||
bpy.ops.mesh.select_all(action='DESELECT')
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
elif atlasgroup.tlm_atlas_lightmap_unwrap_mode == "Xatlas":
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("Temporary skip: COPYING SMART PROJECT")
|
||||
|
||||
for obj in atlas_items:
|
||||
obj.select_set(True)
|
||||
if len(atlas_items) > 0:
|
||||
bpy.context.view_layer.objects.active = atlas_items[0]
|
||||
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
|
||||
Unwrap_Lightmap_Group_Xatlas_2_headless_call(obj)
|
||||
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
else:
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("Copied Existing A")
|
||||
|
||||
for obj in bpy.data.objects:
|
||||
if obj.type == "MESH":
|
||||
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
|
||||
|
@ -96,10 +170,11 @@ def configure_meshes(self):
|
|||
preprocess_material(obj, scene)
|
||||
|
||||
#UV Layer management here
|
||||
if not obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroup":
|
||||
if not obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA":
|
||||
uv_layers = obj.data.uv_layers
|
||||
if not "UVMap_Lightmap" in uv_layers:
|
||||
print("UVMap made B")
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("UVMap made B")
|
||||
uvmap = uv_layers.new(name="UVMap_Lightmap")
|
||||
uv_layers.active_index = len(uv_layers) - 1
|
||||
|
||||
|
@ -111,7 +186,8 @@ def configure_meshes(self):
|
|||
|
||||
#If smart project
|
||||
elif obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "SmartProject":
|
||||
print("Smart Project B")
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("Smart Project B")
|
||||
if scene.TLM_SceneProperties.tlm_apply_on_unwrap:
|
||||
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
|
@ -132,22 +208,24 @@ def configure_meshes(self):
|
|||
#bpy.ops.object.setup_unwrap()
|
||||
Unwrap_Lightmap_Group_Xatlas_2_headless_call(obj)
|
||||
|
||||
elif obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroup":
|
||||
elif obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA":
|
||||
|
||||
print("ATLAS GROUP: " + obj.TLM_ObjectProperties.tlm_atlas_pointer)
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("ATLAS GROUP: " + obj.TLM_ObjectProperties.tlm_atlas_pointer)
|
||||
|
||||
else: #if copy existing
|
||||
|
||||
print("Copied Existing B")
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("Copied Existing B")
|
||||
|
||||
#Here we copy an existing map
|
||||
pass
|
||||
else:
|
||||
print("Existing found...skipping")
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("Existing found...skipping")
|
||||
for i in range(0, len(uv_layers)):
|
||||
if uv_layers[i].name == 'UVMap_Lightmap':
|
||||
uv_layers.active_index = i
|
||||
print("Lightmap shift B")
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("Lightmap shift B")
|
||||
break
|
||||
|
||||
#Sort out nodes
|
||||
|
@ -178,29 +256,32 @@ def configure_meshes(self):
|
|||
mainNode = find_node_by_type(nodetree.nodes, Node_Types.diffuse)[0]
|
||||
else:
|
||||
self.report({'INFO'}, "No supported nodes. Continuing anyway.")
|
||||
pass
|
||||
|
||||
if mainNode.type == 'GROUP':
|
||||
if mainNode.node_tree != "Armory PBR":
|
||||
print("The material group is not supported!")
|
||||
pass
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("The material group is not supported!")
|
||||
|
||||
if (mainNode.type == "BSDF_PRINCIPLED"):
|
||||
print("BSDF_Principled")
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("BSDF_Principled")
|
||||
if scene.TLM_EngineProperties.tlm_directional_mode == "None":
|
||||
print("Directional mode")
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("Directional mode")
|
||||
if not len(mainNode.inputs[19].links) == 0:
|
||||
print("NOT LEN 0")
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("NOT LEN 0")
|
||||
ninput = mainNode.inputs[19].links[0]
|
||||
noutput = mainNode.inputs[19].links[0].from_node
|
||||
nodetree.links.remove(noutput.outputs[0].links[0])
|
||||
|
||||
#Clamp metallic
|
||||
# if(mainNode.inputs[4].default_value == 1 and scene.TLM_SceneProperties.tlm_clamp_metallic):
|
||||
# mainNode.inputs[4].default_value = 0.99
|
||||
if(mainNode.inputs[4].default_value == 1):
|
||||
mainNode.inputs[4].default_value = 0.0
|
||||
|
||||
if (mainNode.type == "BSDF_DIFFUSE"):
|
||||
print("BSDF_Diffuse")
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("BSDF_Diffuse")
|
||||
|
||||
for slot in obj.material_slots:
|
||||
|
||||
|
@ -290,44 +371,95 @@ def preprocess_material(obj, scene):
|
|||
else:
|
||||
supersampling_scale = 1
|
||||
|
||||
res = int(obj.TLM_ObjectProperties.tlm_mesh_lightmap_resolution) / int(scene.TLM_EngineProperties.tlm_resolution_scale) * int(supersampling_scale)
|
||||
|
||||
#If image not in bpy.data.images or if size changed, make a new image
|
||||
if img_name not in bpy.data.images or bpy.data.images[img_name].size[0] != res or bpy.data.images[img_name].size[1] != res:
|
||||
img = bpy.data.images.new(img_name, res, res, alpha=True, float_buffer=True)
|
||||
if (obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA" and obj.TLM_ObjectProperties.tlm_atlas_pointer != ""):
|
||||
|
||||
num_pixels = len(img.pixels)
|
||||
result_pixel = list(img.pixels)
|
||||
atlas_image_name = obj.TLM_ObjectProperties.tlm_atlas_pointer + "_baked"
|
||||
|
||||
for i in range(0,num_pixels,4):
|
||||
# result_pixel[i+0] = scene.TLM_SceneProperties.tlm_default_color[0]
|
||||
# result_pixel[i+1] = scene.TLM_SceneProperties.tlm_default_color[1]
|
||||
# result_pixel[i+2] = scene.TLM_SceneProperties.tlm_default_color[2]
|
||||
result_pixel[i+0] = 0.0
|
||||
result_pixel[i+1] = 0.0
|
||||
result_pixel[i+2] = 0.0
|
||||
result_pixel[i+3] = 1.0
|
||||
res = int(scene.TLM_AtlasList[obj.TLM_ObjectProperties.tlm_atlas_pointer].tlm_atlas_lightmap_resolution) / int(scene.TLM_EngineProperties.tlm_resolution_scale) * int(supersampling_scale)
|
||||
|
||||
img.pixels = result_pixel
|
||||
#If image not in bpy.data.images or if size changed, make a new image
|
||||
if atlas_image_name not in bpy.data.images or bpy.data.images[atlas_image_name].size[0] != res or bpy.data.images[atlas_image_name].size[1] != res:
|
||||
img = bpy.data.images.new(img_name, res, res, alpha=True, float_buffer=True)
|
||||
|
||||
img.name = img_name
|
||||
else:
|
||||
img = bpy.data.images[img_name]
|
||||
num_pixels = len(img.pixels)
|
||||
result_pixel = list(img.pixels)
|
||||
|
||||
for slot in obj.material_slots:
|
||||
mat = slot.material
|
||||
mat.use_nodes = True
|
||||
nodes = mat.node_tree.nodes
|
||||
for i in range(0,num_pixels,4):
|
||||
|
||||
if "Baked Image" in nodes:
|
||||
img_node = nodes["Baked Image"]
|
||||
if scene.TLM_SceneProperties.tlm_override_bg_color:
|
||||
result_pixel[i+0] = scene.TLM_SceneProperties.tlm_override_color[0]
|
||||
result_pixel[i+1] = scene.TLM_SceneProperties.tlm_override_color[1]
|
||||
result_pixel[i+2] = scene.TLM_SceneProperties.tlm_override_color[2]
|
||||
else:
|
||||
result_pixel[i+0] = 0.0
|
||||
result_pixel[i+1] = 0.0
|
||||
result_pixel[i+2] = 0.0
|
||||
result_pixel[i+3] = 1.0
|
||||
|
||||
img.pixels = result_pixel
|
||||
|
||||
img.name = atlas_image_name
|
||||
else:
|
||||
img_node = nodes.new('ShaderNodeTexImage')
|
||||
img_node.name = 'Baked Image'
|
||||
img_node.location = (100, 100)
|
||||
img_node.image = img
|
||||
img_node.select = True
|
||||
nodes.active = img_node
|
||||
img = bpy.data.images[atlas_image_name]
|
||||
|
||||
for slot in obj.material_slots:
|
||||
mat = slot.material
|
||||
mat.use_nodes = True
|
||||
nodes = mat.node_tree.nodes
|
||||
|
||||
if "Baked Image" in nodes:
|
||||
img_node = nodes["Baked Image"]
|
||||
else:
|
||||
img_node = nodes.new('ShaderNodeTexImage')
|
||||
img_node.name = 'Baked Image'
|
||||
img_node.location = (100, 100)
|
||||
img_node.image = img
|
||||
img_node.select = True
|
||||
nodes.active = img_node
|
||||
|
||||
else:
|
||||
|
||||
res = int(obj.TLM_ObjectProperties.tlm_mesh_lightmap_resolution) / int(scene.TLM_EngineProperties.tlm_resolution_scale) * int(supersampling_scale)
|
||||
|
||||
#If image not in bpy.data.images or if size changed, make a new image
|
||||
if img_name not in bpy.data.images or bpy.data.images[img_name].size[0] != res or bpy.data.images[img_name].size[1] != res:
|
||||
img = bpy.data.images.new(img_name, res, res, alpha=True, float_buffer=True)
|
||||
|
||||
num_pixels = len(img.pixels)
|
||||
result_pixel = list(img.pixels)
|
||||
|
||||
for i in range(0,num_pixels,4):
|
||||
if scene.TLM_SceneProperties.tlm_override_bg_color:
|
||||
result_pixel[i+0] = scene.TLM_SceneProperties.tlm_override_color[0]
|
||||
result_pixel[i+1] = scene.TLM_SceneProperties.tlm_override_color[1]
|
||||
result_pixel[i+2] = scene.TLM_SceneProperties.tlm_override_color[2]
|
||||
else:
|
||||
result_pixel[i+0] = 0.0
|
||||
result_pixel[i+1] = 0.0
|
||||
result_pixel[i+2] = 0.0
|
||||
result_pixel[i+3] = 1.0
|
||||
|
||||
img.pixels = result_pixel
|
||||
|
||||
img.name = img_name
|
||||
else:
|
||||
img = bpy.data.images[img_name]
|
||||
|
||||
for slot in obj.material_slots:
|
||||
mat = slot.material
|
||||
mat.use_nodes = True
|
||||
nodes = mat.node_tree.nodes
|
||||
|
||||
if "Baked Image" in nodes:
|
||||
img_node = nodes["Baked Image"]
|
||||
else:
|
||||
img_node = nodes.new('ShaderNodeTexImage')
|
||||
img_node.name = 'Baked Image'
|
||||
img_node.location = (100, 100)
|
||||
img_node.image = img
|
||||
img_node.select = True
|
||||
nodes.active = img_node
|
||||
|
||||
def set_settings():
|
||||
|
||||
|
@ -337,6 +469,13 @@ def set_settings():
|
|||
sceneProperties = scene.TLM_SceneProperties
|
||||
engineProperties = scene.TLM_EngineProperties
|
||||
cycles.device = scene.TLM_EngineProperties.tlm_mode
|
||||
|
||||
if cycles.device == "GPU":
|
||||
scene.render.tile_x = 256
|
||||
scene.render.tile_y = 256
|
||||
else:
|
||||
scene.render.tile_x = 32
|
||||
scene.render.tile_y = 32
|
||||
|
||||
if engineProperties.tlm_quality == "0":
|
||||
cycles.samples = 32
|
||||
|
|
|
@ -36,7 +36,8 @@ class TLM_Integrated_Denoise:
|
|||
|
||||
for image in self.image_array:
|
||||
|
||||
print("Image...: " + image)
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("Image...: " + image)
|
||||
|
||||
img = bpy.data.images.load(self.image_output_destination + "/" + image)
|
||||
|
||||
|
|
|
@ -27,22 +27,22 @@ class TLM_OIDN_Denoise:
|
|||
|
||||
file = os.path.basename(os.path.realpath(oidnPath))
|
||||
filename, file_extension = os.path.splitext(file)
|
||||
|
||||
if(file_extension == ".exe"):
|
||||
|
||||
#if file exists oidnDenoise or denoise
|
||||
|
||||
pass
|
||||
|
||||
else:
|
||||
|
||||
#if file exists oidnDenoise or denoise
|
||||
|
||||
self.oidnProperties.tlm_oidn_path = os.path.join(self.oidnProperties.tlm_oidn_path,"oidnDenoise.exe")
|
||||
|
||||
|
||||
if platform.system() == 'Windows':
|
||||
|
||||
if(file_extension == ".exe"):
|
||||
|
||||
pass
|
||||
|
||||
else:
|
||||
|
||||
self.oidnProperties.tlm_oidn_path = os.path.join(self.oidnProperties.tlm_oidn_path,"oidnDenoise.exe")
|
||||
|
||||
else:
|
||||
|
||||
print("Please provide OIDN path")
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("Please provide OIDN path")
|
||||
|
||||
def denoise(self):
|
||||
|
||||
|
@ -71,9 +71,10 @@ class TLM_OIDN_Denoise:
|
|||
self.save_pfm(fileWritePFM, image_output_array)
|
||||
|
||||
#Denoise
|
||||
print("Loaded image: " + str(loaded_image))
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("Loaded image: " + str(loaded_image))
|
||||
|
||||
verbose = self.oidnProperties.tlm_oidn_verbose
|
||||
verbose = bpy.context.scene.TLM_SceneProperties.tlm_verbose
|
||||
affinity = self.oidnProperties.tlm_oidn_affinity
|
||||
|
||||
if verbose:
|
||||
|
@ -98,6 +99,9 @@ class TLM_OIDN_Denoise:
|
|||
pipePath = [oidnPath + ' -f ' + ' RTLightmap ' + ' -hdr ' + image_output_denoise_destination + ' -o ' + image_output_denoise_result_destination + ' -verbose ' + v]
|
||||
else:
|
||||
oidnPath = bpy.path.abspath(self.oidnProperties.tlm_oidn_path)
|
||||
oidnPath = oidnPath.replace(' ', '\\ ')
|
||||
image_output_denoise_destination = image_output_denoise_destination.replace(' ', '\\ ')
|
||||
image_output_denoise_result_destination = image_output_denoise_result_destination.replace(' ', '\\ ')
|
||||
pipePath = [oidnPath + ' -f ' + ' RTLightmap ' + ' -hdr ' + image_output_denoise_destination + ' -o ' + image_output_denoise_result_destination + ' -verbose ' + v]
|
||||
|
||||
if not verbose:
|
||||
|
@ -107,6 +111,9 @@ class TLM_OIDN_Denoise:
|
|||
|
||||
denoisePipe.communicate()[0]
|
||||
|
||||
if platform.system() != 'Windows':
|
||||
image_output_denoise_result_destination = image_output_denoise_result_destination.replace('\\', '')
|
||||
|
||||
with open(image_output_denoise_result_destination, "rb") as f:
|
||||
denoise_data, scale = self.load_pfm(f)
|
||||
|
||||
|
@ -197,4 +204,4 @@ class TLM_OIDN_Denoise:
|
|||
|
||||
image.tofile(file)
|
||||
|
||||
#print("PFM export took %.3f s" % (time() - start))
|
||||
#print("PFM export took %.3f s" % (time() - start))
|
||||
|
|
|
@ -40,12 +40,13 @@ class TLM_Optix_Denoise:
|
|||
self.optixProperties.tlm_optix_path = os.path.join(self.optixProperties.tlm_optix_path,"Denoiser.exe")
|
||||
|
||||
else:
|
||||
|
||||
print("Please provide Optix path")
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("Please provide Optix path")
|
||||
|
||||
def denoise(self):
|
||||
|
||||
print("Optix: Denoising")
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("Optix: Denoising")
|
||||
for image in self.image_array:
|
||||
|
||||
if image not in self.denoised_array:
|
||||
|
@ -58,9 +59,11 @@ class TLM_Optix_Denoise:
|
|||
optixPath = bpy.path.abspath(self.optixProperties.tlm_optix_path)
|
||||
pipePath = [optixPath, '-i', image_path, '-o', denoise_output_destination]
|
||||
elif platform.system() == 'Darwin':
|
||||
print("Mac for Optix is still unsupported")
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("Mac for Optix is still unsupported")
|
||||
else:
|
||||
print("Linux for Optix is still unsupported")
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("Linux for Optix is still unsupported")
|
||||
|
||||
if self.optixProperties.tlm_optix_verbose:
|
||||
denoisePipe = subprocess.Popen(pipePath, shell=True)
|
||||
|
|
|
@ -4,7 +4,7 @@ from . import utility
|
|||
from fractions import Fraction
|
||||
from gpu_extras.batch import batch_for_shader
|
||||
|
||||
def encodeLogLuv(image, outDir, quality):
|
||||
def encodeLogLuvGPU(image, outDir, quality):
|
||||
input_image = bpy.data.images[image.name]
|
||||
image_name = input_image.name
|
||||
|
||||
|
@ -90,7 +90,8 @@ def encodeLogLuv(image, outDir, quality):
|
|||
image_name = image_name[:-4]
|
||||
|
||||
target_image = bpy.data.images.get(image_name + '_encoded')
|
||||
print(image_name + '_encoded')
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print(image_name + '_encoded')
|
||||
if not target_image:
|
||||
target_image = bpy.data.images.new(
|
||||
name = image_name + '_encoded',
|
||||
|
@ -130,7 +131,8 @@ def encodeLogLuv(image, outDir, quality):
|
|||
input_image = target_image
|
||||
|
||||
#Save LogLuv
|
||||
print(input_image.name)
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print(input_image.name)
|
||||
input_image.filepath_raw = outDir + "/" + input_image.name + ".png"
|
||||
#input_image.filepath_raw = outDir + "_encoded.png"
|
||||
input_image.file_format = "PNG"
|
||||
|
@ -141,7 +143,196 @@ def encodeLogLuv(image, outDir, quality):
|
|||
#Todo - Find a way to save
|
||||
#bpy.ops.image.save_all_modified()
|
||||
|
||||
def encodeImageRGBM(image, maxRange, outDir, quality):
|
||||
def encodeImageRGBDGPU(image, maxRange, outDir, quality):
|
||||
input_image = bpy.data.images[image.name]
|
||||
image_name = input_image.name
|
||||
|
||||
offscreen = gpu.types.GPUOffScreen(input_image.size[0], input_image.size[1])
|
||||
|
||||
image = input_image
|
||||
|
||||
vertex_shader = '''
|
||||
|
||||
uniform mat4 ModelViewProjectionMatrix;
|
||||
|
||||
in vec2 texCoord;
|
||||
in vec2 pos;
|
||||
out vec2 texCoord_interp;
|
||||
|
||||
void main()
|
||||
{
|
||||
//gl_Position = ModelViewProjectionMatrix * vec4(pos.xy, 0.0f, 1.0f);
|
||||
//gl_Position.z = 1.0;
|
||||
gl_Position = vec4(pos.xy, 100, 100);
|
||||
texCoord_interp = texCoord;
|
||||
}
|
||||
|
||||
'''
|
||||
fragment_shader = '''
|
||||
in vec2 texCoord_interp;
|
||||
out vec4 fragColor;
|
||||
|
||||
uniform sampler2D image;
|
||||
|
||||
//Code from here: https://github.com/BabylonJS/Babylon.js/blob/master/src/Shaders/ShadersInclude/helperFunctions.fx
|
||||
|
||||
const float PI = 3.1415926535897932384626433832795;
|
||||
const float HALF_MIN = 5.96046448e-08; // Smallest positive half.
|
||||
|
||||
const float LinearEncodePowerApprox = 2.2;
|
||||
const float GammaEncodePowerApprox = 1.0 / LinearEncodePowerApprox;
|
||||
const vec3 LuminanceEncodeApprox = vec3(0.2126, 0.7152, 0.0722);
|
||||
|
||||
const float Epsilon = 0.0000001;
|
||||
#define saturate(x) clamp(x, 0.0, 1.0)
|
||||
|
||||
float maxEps(float x) {
|
||||
return max(x, Epsilon);
|
||||
}
|
||||
|
||||
float toLinearSpace(float color)
|
||||
{
|
||||
return pow(color, LinearEncodePowerApprox);
|
||||
}
|
||||
|
||||
vec3 toLinearSpace(vec3 color)
|
||||
{
|
||||
return pow(color, vec3(LinearEncodePowerApprox));
|
||||
}
|
||||
|
||||
vec4 toLinearSpace(vec4 color)
|
||||
{
|
||||
return vec4(pow(color.rgb, vec3(LinearEncodePowerApprox)), color.a);
|
||||
}
|
||||
|
||||
vec3 toGammaSpace(vec3 color)
|
||||
{
|
||||
return pow(color, vec3(GammaEncodePowerApprox));
|
||||
}
|
||||
|
||||
vec4 toGammaSpace(vec4 color)
|
||||
{
|
||||
return vec4(pow(color.rgb, vec3(GammaEncodePowerApprox)), color.a);
|
||||
}
|
||||
|
||||
float toGammaSpace(float color)
|
||||
{
|
||||
return pow(color, GammaEncodePowerApprox);
|
||||
}
|
||||
|
||||
float square(float value)
|
||||
{
|
||||
return value * value;
|
||||
}
|
||||
|
||||
// Check if configurable value is needed.
|
||||
const float rgbdMaxRange = 255.0;
|
||||
|
||||
vec4 toRGBD(vec3 color) {
|
||||
float maxRGB = maxEps(max(color.r, max(color.g, color.b)));
|
||||
float D = max(rgbdMaxRange / maxRGB, 1.);
|
||||
D = clamp(floor(D) / 255.0, 0., 1.);
|
||||
vec3 rgb = color.rgb * D;
|
||||
|
||||
// Helps with png quantization.
|
||||
rgb = toGammaSpace(rgb);
|
||||
|
||||
return vec4(rgb, D);
|
||||
}
|
||||
|
||||
vec3 fromRGBD(vec4 rgbd) {
|
||||
// Helps with png quantization.
|
||||
rgbd.rgb = toLinearSpace(rgbd.rgb);
|
||||
|
||||
// return rgbd.rgb * ((rgbdMaxRange / 255.0) / rgbd.a);
|
||||
|
||||
return rgbd.rgb / rgbd.a;
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
|
||||
fragColor = toRGBD(texture(image, texCoord_interp).rgb);
|
||||
|
||||
}
|
||||
|
||||
'''
|
||||
|
||||
x_screen = 0
|
||||
off_x = -100
|
||||
off_y = -100
|
||||
y_screen_flip = 0
|
||||
sx = 200
|
||||
sy = 200
|
||||
|
||||
vertices = (
|
||||
(x_screen + off_x, y_screen_flip - off_y),
|
||||
(x_screen + off_x, y_screen_flip - sy - off_y),
|
||||
(x_screen + off_x + sx, y_screen_flip - sy - off_y),
|
||||
(x_screen + off_x + sx, y_screen_flip - off_x))
|
||||
|
||||
if input_image.colorspace_settings.name != 'Linear':
|
||||
input_image.colorspace_settings.name = 'Linear'
|
||||
|
||||
# Removing .exr or .hdr prefix
|
||||
if image_name[-4:] == '.exr' or image_name[-4:] == '.hdr':
|
||||
image_name = image_name[:-4]
|
||||
|
||||
target_image = bpy.data.images.get(image_name + '_encoded')
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print(image_name + '_encoded')
|
||||
if not target_image:
|
||||
target_image = bpy.data.images.new(
|
||||
name = image_name + '_encoded',
|
||||
width = input_image.size[0],
|
||||
height = input_image.size[1],
|
||||
alpha = True,
|
||||
float_buffer = False
|
||||
)
|
||||
|
||||
shader = gpu.types.GPUShader(vertex_shader, fragment_shader)
|
||||
batch = batch_for_shader(
|
||||
shader, 'TRI_FAN',
|
||||
{
|
||||
"pos": vertices,
|
||||
"texCoord": ((0, 1), (0, 0), (1, 0), (1, 1)),
|
||||
},
|
||||
)
|
||||
|
||||
if image.gl_load():
|
||||
raise Exception()
|
||||
|
||||
with offscreen.bind():
|
||||
bgl.glActiveTexture(bgl.GL_TEXTURE0)
|
||||
bgl.glBindTexture(bgl.GL_TEXTURE_2D, image.bindcode)
|
||||
|
||||
shader.bind()
|
||||
shader.uniform_int("image", 0)
|
||||
batch.draw(shader)
|
||||
|
||||
buffer = bgl.Buffer(bgl.GL_BYTE, input_image.size[0] * input_image.size[1] * 4)
|
||||
bgl.glReadBuffer(bgl.GL_BACK)
|
||||
bgl.glReadPixels(0, 0, input_image.size[0], input_image.size[1], bgl.GL_RGBA, bgl.GL_UNSIGNED_BYTE, buffer)
|
||||
|
||||
offscreen.free()
|
||||
|
||||
target_image.pixels = [v / 255 for v in buffer]
|
||||
input_image = target_image
|
||||
|
||||
#Save LogLuv
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print(input_image.name)
|
||||
input_image.filepath_raw = outDir + "/" + input_image.name + ".png"
|
||||
#input_image.filepath_raw = outDir + "_encoded.png"
|
||||
input_image.file_format = "PNG"
|
||||
bpy.context.scene.render.image_settings.quality = quality
|
||||
#input_image.save_render(filepath = input_image.filepath_raw, scene = bpy.context.scene)
|
||||
input_image.save()
|
||||
|
||||
#Todo - Find a way to save
|
||||
#bpy.ops.image.save_all_modified()
|
||||
|
||||
def encodeImageRGBMCPU(image, maxRange, outDir, quality):
|
||||
input_image = bpy.data.images[image.name]
|
||||
image_name = input_image.name
|
||||
|
||||
|
@ -153,7 +344,8 @@ def encodeImageRGBM(image, maxRange, outDir, quality):
|
|||
image_name = image_name[:-4]
|
||||
|
||||
target_image = bpy.data.images.get(image_name + '_encoded')
|
||||
print(image_name + '_encoded')
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print(image_name + '_encoded')
|
||||
if not target_image:
|
||||
target_image = bpy.data.images.new(
|
||||
name = image_name + '_encoded',
|
||||
|
@ -178,7 +370,8 @@ def encodeImageRGBM(image, maxRange, outDir, quality):
|
|||
input_image = target_image
|
||||
|
||||
#Save RGBM
|
||||
print(input_image.name)
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print(input_image.name)
|
||||
input_image.filepath_raw = outDir + "/" + input_image.name + ".png"
|
||||
input_image.file_format = "PNG"
|
||||
bpy.context.scene.render.image_settings.quality = quality
|
||||
|
@ -193,13 +386,16 @@ def encodeImageRGBM(image, maxRange, outDir, quality):
|
|||
#input_image.save()
|
||||
|
||||
def saturate(num, floats=True):
|
||||
if num < 0:
|
||||
if num <= 0:
|
||||
num = 0
|
||||
elif num > (1 if floats else 255):
|
||||
num = (1 if floats else 255)
|
||||
return num
|
||||
|
||||
def encodeImageRGBD(image, maxRange, outDir, quality):
|
||||
def maxEps(x):
|
||||
return max(x, 1e-6)
|
||||
|
||||
def encodeImageRGBDCPU(image, maxRange, outDir, quality):
|
||||
input_image = bpy.data.images[image.name]
|
||||
image_name = input_image.name
|
||||
|
||||
|
@ -223,23 +419,63 @@ def encodeImageRGBD(image, maxRange, outDir, quality):
|
|||
num_pixels = len(input_image.pixels)
|
||||
result_pixel = list(input_image.pixels)
|
||||
|
||||
rgbdMaxRange = 255.0
|
||||
|
||||
for i in range(0,num_pixels,4):
|
||||
|
||||
m = utility.saturate(max(result_pixel[i], result_pixel[i+1], result_pixel[i+2], 1e-6))
|
||||
d = max(maxRange / m, 1)
|
||||
d = utility.saturate(math.floor(d) / 255 )
|
||||
maxRGB = maxEps(max(result_pixel[i], result_pixel[i+1], result_pixel[i+2]))
|
||||
D = max(rgbdMaxRange/maxRGB, 1.0)
|
||||
D = np.clip((math.floor(D) / 255.0), 0.0, 1.0)
|
||||
|
||||
result_pixel[i] = result_pixel[i] * d * 255 / maxRange
|
||||
result_pixel[i+1] = result_pixel[i+1] * d * 255 / maxRange
|
||||
result_pixel[i+2] = result_pixel[i+2] * d * 255 / maxRange
|
||||
result_pixel[i+3] = d
|
||||
result_pixel[i] = math.pow(result_pixel[i] * D, 1/2.2)
|
||||
result_pixel[i+1] = math.pow(result_pixel[i+1] * D, 1/2.2)
|
||||
result_pixel[i+2] = math.pow(result_pixel[i+2] * D, 1/2.2)
|
||||
result_pixel[i+3] = D
|
||||
|
||||
|
||||
# for i in range(0,num_pixels,4):
|
||||
|
||||
# m = saturate(max(result_pixel[i], result_pixel[i+1], result_pixel[i+2], 1e-6))
|
||||
# d = max(maxRange / m, 1)
|
||||
# #d = saturate(math.floor(d) / 255.0)
|
||||
# d = np.clip((math.floor(d) / 255.0), 0.0, 1.0)
|
||||
|
||||
# #TODO TO GAMMA SPACE
|
||||
|
||||
# result_pixel[i] = math.pow(result_pixel[i] * d * 255 / maxRange, 1/2.2)
|
||||
# result_pixel[i+1] = math.pow(result_pixel[i+1] * d * 255 / maxRange, 1/2.2)
|
||||
# result_pixel[i+2] = math.pow(result_pixel[i+2] * d * 255 / maxRange, 1/2.2)
|
||||
# result_pixel[i+3] = d
|
||||
|
||||
target_image.pixels = result_pixel
|
||||
|
||||
input_image = target_image
|
||||
|
||||
#Save RGBD
|
||||
input_image.filepath_raw = outDir + "_encoded.png"
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print(input_image.name)
|
||||
input_image.filepath_raw = outDir + "/" + input_image.name + ".png"
|
||||
input_image.file_format = "PNG"
|
||||
bpy.context.scene.render.image_settings.quality = quality
|
||||
input_image.save_render(filepath = input_image.filepath_raw, scene = bpy.context.scene)
|
||||
input_image.save()
|
||||
|
||||
# const float rgbdMaxRange = 255.0;
|
||||
|
||||
# vec4 toRGBD(vec3 color) {
|
||||
# float maxRGB = maxEps(max(color.r, max(color.g, color.b)));
|
||||
# float D = max(rgbdMaxRange / maxRGB, 1.);
|
||||
# D = clamp(floor(D) / 255.0, 0., 1.);
|
||||
# vec3 rgb = color.rgb * D;
|
||||
|
||||
# // Helps with png quantization.
|
||||
# rgb = toGammaSpace(rgb);
|
||||
|
||||
# return vec4(rgb, D);
|
||||
# }
|
||||
|
||||
# const float Epsilon = 0.0000001;
|
||||
# #define saturate(x) clamp(x, 0.0, 1.0)
|
||||
|
||||
# float maxEps(float x) {
|
||||
# return max(x, Epsilon);
|
||||
# }
|
|
@ -10,7 +10,8 @@ class TLM_CV_Filtering:
|
|||
|
||||
scene = bpy.context.scene
|
||||
|
||||
print("Beginning filtering for files: ")
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("Beginning filtering for files: ")
|
||||
|
||||
if denoise:
|
||||
file_ending = "_denoised.hdr"
|
||||
|
@ -22,7 +23,8 @@ class TLM_CV_Filtering:
|
|||
cv2 = importlib.util.find_spec("cv2")
|
||||
|
||||
if cv2 is None:
|
||||
print("CV2 not found - Ignoring filtering")
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("CV2 not found - Ignoring filtering")
|
||||
return 0
|
||||
else:
|
||||
cv2 = importlib.__import__("cv2")
|
||||
|
@ -43,13 +45,31 @@ class TLM_CV_Filtering:
|
|||
|
||||
opencv_process_image = cv2.imread(file_input, -1)
|
||||
|
||||
print("Filtering: " + os.path.basename(file_input))
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("Filtering: " + os.path.basename(file_input))
|
||||
|
||||
obj_name = os.path.basename(file_input).split("_")[0]
|
||||
|
||||
if bpy.data.objects[obj_name].TLM_ObjectProperties.tlm_mesh_filter_override:
|
||||
#SEAM TESTING# #####################
|
||||
|
||||
print("OVERRIDE!")
|
||||
# obj = bpy.data.objects[obj_name]
|
||||
|
||||
# bpy.context.view_layer.objects.active = obj
|
||||
# bpy.ops.object.mode_set(mode='EDIT')
|
||||
# bpy.ops.uv.export_layout(filepath=os.path.join(lightmap_dir,obj_name), export_all=True, mode='PNG', opacity=0.0)
|
||||
# bpy.ops.object.mode_set(mode='OBJECT')
|
||||
# print("Exported")
|
||||
|
||||
#SEAM TESTING# #####################
|
||||
|
||||
if obj_name in bpy.data.objects:
|
||||
override = bpy.data.objects[obj_name].TLM_ObjectProperties.tlm_mesh_filter_override
|
||||
elif obj_name in scene.TLM_AtlasList:
|
||||
override = False
|
||||
else:
|
||||
override = False
|
||||
|
||||
if override:
|
||||
|
||||
print(os.path.join(lightmap_dir, file))
|
||||
|
||||
|
@ -101,7 +121,8 @@ class TLM_CV_Filtering:
|
|||
|
||||
cv2.imwrite(filter_file_output, opencv_bl_result)
|
||||
|
||||
print("Written to: " + filter_file_output)
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("Written to: " + filter_file_output)
|
||||
|
||||
else:
|
||||
|
||||
|
@ -153,8 +174,5 @@ class TLM_CV_Filtering:
|
|||
|
||||
cv2.imwrite(filter_file_output, opencv_bl_result)
|
||||
|
||||
print("Written to: " + filter_file_output)
|
||||
|
||||
# if file.endswith(file_ending):
|
||||
# print()
|
||||
# baked_image_array.append(file)
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("Written to: " + filter_file_output)
|
303
blender/arm/lightmapper/utility/pack.py
Normal file
303
blender/arm/lightmapper/utility/pack.py
Normal file
|
@ -0,0 +1,303 @@
|
|||
import bpy, os, sys, math, mathutils, importlib
|
||||
import numpy as np
|
||||
from . rectpack import newPacker, PackingMode, PackingBin
|
||||
|
||||
def postpack():
|
||||
|
||||
cv_installed = False
|
||||
|
||||
cv2 = importlib.util.find_spec("cv2")
|
||||
|
||||
if cv2 is None:
|
||||
print("CV2 not found - Ignoring postpacking")
|
||||
return 0
|
||||
else:
|
||||
cv2 = importlib.__import__("cv2")
|
||||
cv_installed = True
|
||||
|
||||
if cv_installed:
|
||||
|
||||
lightmap_directory = os.path.join(os.path.dirname(bpy.data.filepath), bpy.context.scene.TLM_EngineProperties.tlm_lightmap_savedir)
|
||||
|
||||
packedAtlas = {}
|
||||
|
||||
#TODO - TEST WITH ONLY 1 ATLAS AT FIRST (1 Atlas for each, but only 1 bin (no overflow))
|
||||
#PackedAtlas = Packer
|
||||
#Each atlas has bins
|
||||
#Each bins has rects
|
||||
#Each rect corresponds to a pack_object
|
||||
|
||||
scene = bpy.context.scene
|
||||
|
||||
sceneProperties = scene.TLM_SceneProperties
|
||||
|
||||
end = "_baked"
|
||||
|
||||
if sceneProperties.tlm_denoise_use:
|
||||
|
||||
end = "_denoised"
|
||||
|
||||
if sceneProperties.tlm_filtering_use:
|
||||
|
||||
end = "_filtered"
|
||||
|
||||
formatEnc = ".hdr"
|
||||
|
||||
image_channel_depth = cv2.IMREAD_ANYDEPTH
|
||||
linear_straight = False
|
||||
|
||||
if sceneProperties.tlm_encoding_use and scene.TLM_EngineProperties.tlm_bake_mode != "Background":
|
||||
|
||||
if sceneProperties.tlm_encoding_device == "CPU":
|
||||
|
||||
if sceneProperties.tlm_encoding_mode_a == "HDR":
|
||||
|
||||
if sceneProperties.tlm_format == "EXR":
|
||||
|
||||
formatEnc = ".exr"
|
||||
|
||||
if sceneProperties.tlm_encoding_mode_a == "RGBM":
|
||||
|
||||
formatEnc = "_encoded.png"
|
||||
image_channel_depth = cv2.IMREAD_UNCHANGED
|
||||
|
||||
else:
|
||||
|
||||
if sceneProperties.tlm_encoding_mode_b == "HDR":
|
||||
|
||||
if sceneProperties.tlm_format == "EXR":
|
||||
|
||||
formatEnc = ".exr"
|
||||
|
||||
if sceneProperties.tlm_encoding_mode_b == "LogLuv":
|
||||
|
||||
formatEnc = "_encoded.png"
|
||||
image_channel_depth = cv2.IMREAD_UNCHANGED
|
||||
linear_straight = True
|
||||
|
||||
if sceneProperties.tlm_encoding_mode_b == "RGBM":
|
||||
|
||||
formatEnc = "_encoded.png"
|
||||
image_channel_depth = cv2.IMREAD_UNCHANGED
|
||||
|
||||
if sceneProperties.tlm_encoding_mode_b == "RGBD":
|
||||
|
||||
formatEnc = "_encoded.png"
|
||||
image_channel_depth = cv2.IMREAD_UNCHANGED
|
||||
|
||||
packer = {}
|
||||
|
||||
for atlas in bpy.context.scene.TLM_PostAtlasList: #For each atlas
|
||||
|
||||
packer[atlas.name] = newPacker(PackingMode.Offline, PackingBin.BFF, rotation=False)
|
||||
|
||||
if scene.TLM_EngineProperties.tlm_setting_supersample == "2x":
|
||||
supersampling_scale = 2
|
||||
elif scene.TLM_EngineProperties.tlm_setting_supersample == "4x":
|
||||
supersampling_scale = 4
|
||||
else:
|
||||
supersampling_scale = 1
|
||||
|
||||
atlas_resolution = int(int(atlas.tlm_atlas_lightmap_resolution) / int(scene.TLM_EngineProperties.tlm_resolution_scale) * int(supersampling_scale))
|
||||
|
||||
packer[atlas.name].add_bin(atlas_resolution, atlas_resolution, 1)
|
||||
|
||||
#AtlasList same name prevention?
|
||||
rect = []
|
||||
|
||||
#For each object that targets the atlas
|
||||
for obj in bpy.data.objects:
|
||||
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
|
||||
if obj.TLM_ObjectProperties.tlm_postpack_object:
|
||||
if obj.TLM_ObjectProperties.tlm_postatlas_pointer == atlas.name:
|
||||
|
||||
res = int(int(obj.TLM_ObjectProperties.tlm_mesh_lightmap_resolution) / int(scene.TLM_EngineProperties.tlm_resolution_scale) * int(supersampling_scale))
|
||||
|
||||
rect.append((res, res, obj.name))
|
||||
|
||||
for r in rect:
|
||||
packer[atlas.name].add_rect(*r)
|
||||
|
||||
packedAtlas[atlas.name] = np.zeros((atlas_resolution,atlas_resolution, 3), dtype="float32")
|
||||
|
||||
#Continue here...overwrite value if using 8-bit encoding
|
||||
if sceneProperties.tlm_encoding_use:
|
||||
if sceneProperties.tlm_encoding_device == "CPU":
|
||||
if sceneProperties.tlm_encoding_mode_a == "RGBM":
|
||||
packedAtlas[atlas.name] = np.zeros((atlas_resolution,atlas_resolution, 4), dtype=np.uint8)
|
||||
if sceneProperties.tlm_encoding_mode_a == "RGBD":
|
||||
packedAtlas[atlas.name] = np.zeros((atlas_resolution,atlas_resolution, 4), dtype=np.uint8)
|
||||
|
||||
if sceneProperties.tlm_encoding_device == "GPU":
|
||||
if sceneProperties.tlm_encoding_mode_b == "RGBM":
|
||||
packedAtlas[atlas.name] = np.zeros((atlas_resolution,atlas_resolution, 4), dtype=np.uint8)
|
||||
if sceneProperties.tlm_encoding_mode_b == "RGBD":
|
||||
packedAtlas[atlas.name] = np.zeros((atlas_resolution,atlas_resolution, 4), dtype=np.uint8)
|
||||
if sceneProperties.tlm_encoding_mode_b == "LogLuv":
|
||||
packedAtlas[atlas.name] = np.zeros((atlas_resolution,atlas_resolution, 4), dtype=np.uint8)
|
||||
|
||||
packer[atlas.name].pack()
|
||||
|
||||
for idy, rect in enumerate(packer[atlas.name].rect_list()):
|
||||
|
||||
aob = rect[5]
|
||||
|
||||
src = cv2.imread(os.path.join(lightmap_directory, aob + end + formatEnc), image_channel_depth) #"_baked.hdr"
|
||||
|
||||
print("Obj name is: " + aob)
|
||||
|
||||
x,y,w,h = rect[1],rect[2],rect[3],rect[4]
|
||||
|
||||
print(src.shape)
|
||||
print(packedAtlas[atlas.name].shape)
|
||||
|
||||
packedAtlas[atlas.name][y:h+y, x:w+x] = src
|
||||
|
||||
obj = bpy.data.objects[aob]
|
||||
|
||||
for idx, layer in enumerate(obj.data.uv_layers):
|
||||
if layer.name == "UVMap_Lightmap":
|
||||
obj.data.uv_layers.active_index = idx
|
||||
|
||||
print("UVLayer set to: " + str(obj.data.uv_layers.active_index))
|
||||
|
||||
for uv_verts in obj.data.uv_layers.active.data:
|
||||
|
||||
#SET UV LAYER TO
|
||||
|
||||
atlasRes = atlas_resolution
|
||||
texRes = rect[3]
|
||||
x,y,w,z = x,y,texRes,texRes
|
||||
|
||||
ratio = atlasRes/texRes
|
||||
|
||||
if x == 0:
|
||||
x_offset = 0
|
||||
else:
|
||||
x_offset = 1/(atlasRes/x)
|
||||
|
||||
if y == 0:
|
||||
y_offset = 0
|
||||
else:
|
||||
y_offset = 1/(atlasRes/y)
|
||||
|
||||
vertex_x = (uv_verts.uv[0] * 1/(ratio)) + x_offset
|
||||
vertex_y = (1 - ((uv_verts.uv[1] * 1/(ratio)) + y_offset))
|
||||
|
||||
uv_verts.uv[0] = vertex_x
|
||||
uv_verts.uv[1] = vertex_y
|
||||
|
||||
scaleUV(obj.data.uv_layers.active, (1, -1), getBoundsCenter(obj.data.uv_layers.active))
|
||||
print(getCenter(obj.data.uv_layers.active))
|
||||
|
||||
cv2.imwrite(os.path.join(lightmap_directory, atlas.name + end + formatEnc), packedAtlas[atlas.name])
|
||||
print("Written: " + str(os.path.join(lightmap_directory, atlas.name + end + formatEnc)))
|
||||
|
||||
#Change the material for each material, slot
|
||||
for obj in bpy.data.objects:
|
||||
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
|
||||
if obj.TLM_ObjectProperties.tlm_postpack_object:
|
||||
if obj.TLM_ObjectProperties.tlm_postatlas_pointer == atlas.name:
|
||||
for slot in obj.material_slots:
|
||||
nodetree = slot.material.node_tree
|
||||
|
||||
for node in nodetree.nodes:
|
||||
|
||||
if node.name == "TLM_Lightmap":
|
||||
|
||||
existing_image = node.image
|
||||
|
||||
atlasImage = bpy.data.images.load(os.path.join(lightmap_directory, atlas.name + end + formatEnc), check_existing=True)
|
||||
|
||||
if linear_straight:
|
||||
if atlasImage.colorspace_settings.name != 'Linear':
|
||||
atlasImage.colorspace_settings.name = 'Linear'
|
||||
|
||||
node.image = atlasImage
|
||||
|
||||
os.remove(os.path.join(lightmap_directory, obj.name + end + formatEnc))
|
||||
existing_image.user_clear()
|
||||
|
||||
#Add dilation map here...
|
||||
for obj in bpy.data.objects:
|
||||
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
|
||||
if obj.TLM_ObjectProperties.tlm_postpack_object:
|
||||
if obj.TLM_ObjectProperties.tlm_postatlas_pointer == atlas.name:
|
||||
if atlas.tlm_atlas_dilation:
|
||||
for slot in obj.material_slots:
|
||||
nodetree = slot.material.node_tree
|
||||
|
||||
for node in nodetree.nodes:
|
||||
|
||||
if node.name == "TLM_Lightmap":
|
||||
|
||||
existing_image = node.image
|
||||
|
||||
atlasImage = bpy.data.images.load(os.path.join(lightmap_directory, atlas.name + end + formatEnc), check_existing=True)
|
||||
|
||||
img = cv2.imread(atlasImage.filepath_raw, image_channel_depth)
|
||||
|
||||
kernel = np.ones((5,5), dtype="float32")
|
||||
|
||||
img_dilation = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)
|
||||
|
||||
cv2.imshow('Dilation', img_dilation)
|
||||
cv2.waitKey(0)
|
||||
|
||||
print("TODO: Adding dilation for: " + obj.name)
|
||||
#TODO MASKING OPTION!
|
||||
|
||||
|
||||
|
||||
else:
|
||||
|
||||
print("OpenCV not installed. Skipping postpacking process.")
|
||||
|
||||
def getCenter(uv_layer):
|
||||
|
||||
total_x, total_y = 0,0
|
||||
len = 0
|
||||
|
||||
for uv_verts in uv_layer.data:
|
||||
total_x += uv_verts.uv[0]
|
||||
total_y += uv_verts.uv[1]
|
||||
|
||||
len += 1
|
||||
|
||||
center_x = total_x / len
|
||||
center_y = total_y / len
|
||||
|
||||
return (center_x, center_y)
|
||||
|
||||
def getBoundsCenter(uv_layer):
|
||||
|
||||
min_x = getCenter(uv_layer)[0]
|
||||
max_x = getCenter(uv_layer)[0]
|
||||
min_y = getCenter(uv_layer)[1]
|
||||
max_y = getCenter(uv_layer)[1]
|
||||
|
||||
len = 0
|
||||
|
||||
for uv_verts in uv_layer.data:
|
||||
|
||||
if uv_verts.uv[0] < min_x:
|
||||
min_x = uv_verts.uv[0]
|
||||
if uv_verts.uv[0] > max_x:
|
||||
max_x = uv_verts.uv[0]
|
||||
if uv_verts.uv[1] < min_y:
|
||||
min_y = uv_verts.uv[1]
|
||||
if uv_verts.uv[1] > max_y:
|
||||
max_y = uv_verts.uv[1]
|
||||
|
||||
center_x = (max_x - min_x) / 2 + min_x
|
||||
center_y = (max_y - min_y) / 2 + min_y
|
||||
|
||||
return (center_x, center_y)
|
||||
|
||||
|
||||
def scale2D(v, s, p):
|
||||
return (p[0] + s[0]*(v[0] - p[0]), p[1] + s[1]*(v[1] - p[1]))
|
||||
|
||||
def scaleUV( uvMap, scale, pivot ):
|
||||
for uvIndex in range( len(uvMap.data) ):
|
||||
uvMap.data[uvIndex].uv = scale2D(uvMap.data[uvIndex].uv, scale, pivot)
|
23
blender/arm/lightmapper/utility/rectpack/__init__.py
Normal file
23
blender/arm/lightmapper/utility/rectpack/__init__.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
from .guillotine import GuillotineBssfSas, GuillotineBssfLas, \
|
||||
GuillotineBssfSlas, GuillotineBssfLlas, GuillotineBssfMaxas, \
|
||||
GuillotineBssfMinas, GuillotineBlsfSas, GuillotineBlsfLas, \
|
||||
GuillotineBlsfSlas, GuillotineBlsfLlas, GuillotineBlsfMaxas, \
|
||||
GuillotineBlsfMinas, GuillotineBafSas, GuillotineBafLas, \
|
||||
GuillotineBafSlas, GuillotineBafLlas, GuillotineBafMaxas, \
|
||||
GuillotineBafMinas
|
||||
|
||||
from .maxrects import MaxRectsBl, MaxRectsBssf, MaxRectsBaf, MaxRectsBlsf
|
||||
|
||||
from .skyline import SkylineMwf, SkylineMwfl, SkylineBl, \
|
||||
SkylineBlWm, SkylineMwfWm, SkylineMwflWm
|
||||
|
||||
from .packer import SORT_AREA, SORT_PERI, SORT_DIFF, SORT_SSIDE, \
|
||||
SORT_LSIDE, SORT_RATIO, SORT_NONE
|
||||
|
||||
from .packer import PackerBNF, PackerBFF, PackerBBF, PackerOnlineBNF, \
|
||||
PackerOnlineBFF, PackerOnlineBBF, PackerGlobal, newPacker, \
|
||||
PackingMode, PackingBin, float2dec
|
||||
|
||||
|
||||
|
||||
|
148
blender/arm/lightmapper/utility/rectpack/enclose.py
Normal file
148
blender/arm/lightmapper/utility/rectpack/enclose.py
Normal file
|
@ -0,0 +1,148 @@
|
|||
import heapq # heapq.heappush, heapq.heappop
|
||||
from .packer import newPacker, PackingMode, PackingBin, SORT_LSIDE
|
||||
from .skyline import SkylineBlWm
|
||||
|
||||
|
||||
|
||||
class Enclose(object):
|
||||
|
||||
def __init__(self, rectangles=[], max_width=None, max_height=None, rotation=True):
|
||||
"""
|
||||
Arguments:
|
||||
rectangles (list): Rectangle to be enveloped
|
||||
[(width1, height1), (width2, height2), ...]
|
||||
max_width (number|None): Enveloping rectangle max allowed width.
|
||||
max_height (number|None): Enveloping rectangle max allowed height.
|
||||
rotation (boolean): Enable/Disable rectangle rotation.
|
||||
"""
|
||||
# Enclosing rectangle max width
|
||||
self._max_width = max_width
|
||||
|
||||
# Encloseing rectangle max height
|
||||
self._max_height = max_height
|
||||
|
||||
# Enable or disable rectangle rotation
|
||||
self._rotation = rotation
|
||||
|
||||
# Default packing algorithm
|
||||
self._pack_algo = SkylineBlWm
|
||||
|
||||
# rectangles to enclose [(width, height), (width, height, ...)]
|
||||
self._rectangles = []
|
||||
for r in rectangles:
|
||||
self.add_rect(*r)
|
||||
|
||||
def _container_candidates(self):
|
||||
"""Generate container candidate list
|
||||
|
||||
Returns:
|
||||
tuple list: [(width1, height1), (width2, height2), ...]
|
||||
"""
|
||||
if not self._rectangles:
|
||||
return []
|
||||
|
||||
if self._rotation:
|
||||
sides = sorted(side for rect in self._rectangles for side in rect)
|
||||
max_height = sum(max(r[0], r[1]) for r in self._rectangles)
|
||||
min_width = max(min(r[0], r[1]) for r in self._rectangles)
|
||||
max_width = max_height
|
||||
else:
|
||||
sides = sorted(r[0] for r in self._rectangles)
|
||||
max_height = sum(r[1] for r in self._rectangles)
|
||||
min_width = max(r[0] for r in self._rectangles)
|
||||
max_width = sum(sides)
|
||||
|
||||
if self._max_width and self._max_width < max_width:
|
||||
max_width = self._max_width
|
||||
|
||||
if self._max_height and self._max_height < max_height:
|
||||
max_height = self._max_height
|
||||
|
||||
assert(max_width>min_width)
|
||||
|
||||
# Generate initial container widths
|
||||
candidates = [max_width, min_width]
|
||||
|
||||
width = 0
|
||||
for s in reversed(sides):
|
||||
width += s
|
||||
candidates.append(width)
|
||||
|
||||
width = 0
|
||||
for s in sides:
|
||||
width += s
|
||||
candidates.append(width)
|
||||
|
||||
candidates.append(max_width)
|
||||
candidates.append(min_width)
|
||||
|
||||
# Remove duplicates and widths too big or small
|
||||
seen = set()
|
||||
seen_add = seen.add
|
||||
candidates = [x for x in candidates if not(x in seen or seen_add(x))]
|
||||
candidates = [x for x in candidates if not(x>max_width or x<min_width)]
|
||||
|
||||
# Remove candidates too small to fit all the rectangles
|
||||
min_area = sum(r[0]*r[1] for r in self._rectangles)
|
||||
return [(c, max_height) for c in candidates if c*max_height>=min_area]
|
||||
|
||||
def _refine_candidate(self, width, height):
|
||||
"""
|
||||
Use bottom-left packing algorithm to find a lower height for the
|
||||
container.
|
||||
|
||||
Arguments:
|
||||
width
|
||||
height
|
||||
|
||||
Returns:
|
||||
tuple (width, height, PackingAlgorithm):
|
||||
"""
|
||||
packer = newPacker(PackingMode.Offline, PackingBin.BFF,
|
||||
pack_algo=self._pack_algo, sort_algo=SORT_LSIDE,
|
||||
rotation=self._rotation)
|
||||
packer.add_bin(width, height)
|
||||
|
||||
for r in self._rectangles:
|
||||
packer.add_rect(*r)
|
||||
|
||||
packer.pack()
|
||||
|
||||
# Check all rectangles where packed
|
||||
if len(packer[0]) != len(self._rectangles):
|
||||
return None
|
||||
|
||||
# Find highest rectangle
|
||||
new_height = max(packer[0], key=lambda x: x.top).top
|
||||
return(width, new_height, packer)
|
||||
|
||||
def generate(self):
|
||||
|
||||
# Generate initial containers
|
||||
candidates = self._container_candidates()
|
||||
if not candidates:
|
||||
return None
|
||||
|
||||
# Refine candidates and return the one with the smaller area
|
||||
containers = [self._refine_candidate(*c) for c in candidates]
|
||||
containers = [c for c in containers if c]
|
||||
if not containers:
|
||||
return None
|
||||
|
||||
width, height, packer = min(containers, key=lambda x: x[0]*x[1])
|
||||
|
||||
packer.width = width
|
||||
packer.height = height
|
||||
return packer
|
||||
|
||||
def add_rect(self, width, height):
|
||||
"""
|
||||
Add anoter rectangle to be enclosed
|
||||
|
||||
Arguments:
|
||||
width (number): Rectangle width
|
||||
height (number): Rectangle height
|
||||
"""
|
||||
self._rectangles.append((width, height))
|
||||
|
||||
|
344
blender/arm/lightmapper/utility/rectpack/geometry.py
Normal file
344
blender/arm/lightmapper/utility/rectpack/geometry.py
Normal file
|
@ -0,0 +1,344 @@
|
|||
from math import sqrt
|
||||
|
||||
|
||||
|
||||
class Point(object):
|
||||
|
||||
__slots__ = ('x', 'y')
|
||||
|
||||
def __init__(self, x, y):
|
||||
self.x = x
|
||||
self.y = y
|
||||
|
||||
def __eq__(self, other):
|
||||
return (self.x == other.x and self.y == other.y)
|
||||
|
||||
def __repr__(self):
|
||||
return "P({}, {})".format(self.x, self.y)
|
||||
|
||||
def distance(self, point):
|
||||
"""
|
||||
Calculate distance to another point
|
||||
"""
|
||||
return sqrt((self.x-point.x)**2+(self.y-point.y)**2)
|
||||
|
||||
def distance_squared(self, point):
|
||||
return (self.x-point.x)**2+(self.y-point.y)**2
|
||||
|
||||
|
||||
class Segment(object):
|
||||
|
||||
__slots__ = ('start', 'end')
|
||||
|
||||
def __init__(self, start, end):
|
||||
"""
|
||||
Arguments:
|
||||
start (Point): Segment start point
|
||||
end (Point): Segment end point
|
||||
"""
|
||||
assert(isinstance(start, Point) and isinstance(end, Point))
|
||||
self.start = start
|
||||
self.end = end
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, self.__class__):
|
||||
None
|
||||
return self.start==other.start and self.end==other.end
|
||||
|
||||
def __repr__(self):
|
||||
return "S({}, {})".format(self.start, self.end)
|
||||
|
||||
@property
|
||||
def length_squared(self):
|
||||
"""Faster than length and useful for some comparisons"""
|
||||
return self.start.distance_squared(self.end)
|
||||
|
||||
@property
|
||||
def length(self):
|
||||
return self.start.distance(self.end)
|
||||
|
||||
@property
|
||||
def top(self):
|
||||
return max(self.start.y, self.end.y)
|
||||
|
||||
@property
|
||||
def bottom(self):
|
||||
return min(self.start.y, self.end.y)
|
||||
|
||||
@property
|
||||
def right(self):
|
||||
return max(self.start.x, self.end.x)
|
||||
|
||||
@property
|
||||
def left(self):
|
||||
return min(self.start.x, self.end.x)
|
||||
|
||||
|
||||
class HSegment(Segment):
|
||||
"""Horizontal Segment"""
|
||||
|
||||
def __init__(self, start, length):
|
||||
"""
|
||||
Create an Horizontal segment given its left most end point and its
|
||||
length.
|
||||
|
||||
Arguments:
|
||||
- start (Point): Starting Point
|
||||
- length (number): segment length
|
||||
"""
|
||||
assert(isinstance(start, Point) and not isinstance(length, Point))
|
||||
super(HSegment, self).__init__(start, Point(start.x+length, start.y))
|
||||
|
||||
@property
|
||||
def length(self):
|
||||
return self.end.x-self.start.x
|
||||
|
||||
|
||||
class VSegment(Segment):
|
||||
"""Vertical Segment"""
|
||||
|
||||
def __init__(self, start, length):
|
||||
"""
|
||||
Create a Vertical segment given its bottom most end point and its
|
||||
length.
|
||||
|
||||
Arguments:
|
||||
- start (Point): Starting Point
|
||||
- length (number): segment length
|
||||
"""
|
||||
assert(isinstance(start, Point) and not isinstance(length, Point))
|
||||
super(VSegment, self).__init__(start, Point(start.x, start.y+length))
|
||||
|
||||
@property
|
||||
def length(self):
|
||||
return self.end.y-self.start.y
|
||||
|
||||
|
||||
|
||||
class Rectangle(object):
|
||||
"""Basic rectangle primitive class.
|
||||
x, y-> Lower right corner coordinates
|
||||
width -
|
||||
height -
|
||||
"""
|
||||
__slots__ = ('width', 'height', 'x', 'y', 'rid')
|
||||
|
||||
def __init__(self, x, y, width, height, rid = None):
|
||||
"""
|
||||
Args:
|
||||
x (int, float):
|
||||
y (int, float):
|
||||
width (int, float):
|
||||
height (int, float):
|
||||
rid (int):
|
||||
"""
|
||||
assert(height >=0 and width >=0)
|
||||
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.rid = rid
|
||||
|
||||
@property
|
||||
def bottom(self):
|
||||
"""
|
||||
Rectangle bottom edge y coordinate
|
||||
"""
|
||||
return self.y
|
||||
|
||||
@property
|
||||
def top(self):
|
||||
"""
|
||||
Rectangle top edge y coordiante
|
||||
"""
|
||||
return self.y+self.height
|
||||
|
||||
@property
|
||||
def left(self):
|
||||
"""
|
||||
Rectangle left ednge x coordinate
|
||||
"""
|
||||
return self.x
|
||||
|
||||
@property
|
||||
def right(self):
|
||||
"""
|
||||
Rectangle right edge x coordinate
|
||||
"""
|
||||
return self.x+self.width
|
||||
|
||||
@property
|
||||
def corner_top_l(self):
|
||||
return Point(self.left, self.top)
|
||||
|
||||
@property
|
||||
def corner_top_r(self):
|
||||
return Point(self.right, self.top)
|
||||
|
||||
@property
|
||||
def corner_bot_r(self):
|
||||
return Point(self.right, self.bottom)
|
||||
|
||||
@property
|
||||
def corner_bot_l(self):
|
||||
return Point(self.left, self.bottom)
|
||||
|
||||
def __lt__(self, other):
|
||||
"""
|
||||
Compare rectangles by area (used for sorting)
|
||||
"""
|
||||
return self.area() < other.area()
|
||||
|
||||
def __eq__(self, other):
|
||||
"""
|
||||
Equal rectangles have same area.
|
||||
"""
|
||||
if not isinstance(other, self.__class__):
|
||||
return False
|
||||
|
||||
return (self.width == other.width and \
|
||||
self.height == other.height and \
|
||||
self.x == other.x and \
|
||||
self.y == other.y)
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.x, self.y, self.width, self.height))
|
||||
|
||||
def __iter__(self):
|
||||
"""
|
||||
Iterate through rectangle corners
|
||||
"""
|
||||
yield self.corner_top_l
|
||||
yield self.corner_top_r
|
||||
yield self.corner_bot_r
|
||||
yield self.corner_bot_l
|
||||
|
||||
def __repr__(self):
|
||||
return "R({}, {}, {}, {})".format(self.x, self.y, self.width, self.height)
|
||||
|
||||
def area(self):
|
||||
"""
|
||||
Rectangle area
|
||||
"""
|
||||
return self.width * self.height
|
||||
|
||||
def move(self, x, y):
|
||||
"""
|
||||
Move Rectangle to x,y coordinates
|
||||
|
||||
Arguments:
|
||||
x (int, float): X coordinate
|
||||
y (int, float): Y coordinate
|
||||
"""
|
||||
self.x = x
|
||||
self.y = y
|
||||
|
||||
def contains(self, rect):
|
||||
"""
|
||||
Tests if another rectangle is contained by this one
|
||||
|
||||
Arguments:
|
||||
rect (Rectangle): The other rectangle
|
||||
|
||||
Returns:
|
||||
bool: True if it is container, False otherwise
|
||||
"""
|
||||
return (rect.y >= self.y and \
|
||||
rect.x >= self.x and \
|
||||
rect.y+rect.height <= self.y+self.height and \
|
||||
rect.x+rect.width <= self.x+self.width)
|
||||
|
||||
def intersects(self, rect, edges=False):
|
||||
"""
|
||||
Detect intersections between this and another Rectangle.
|
||||
|
||||
Parameters:
|
||||
rect (Rectangle): The other rectangle.
|
||||
edges (bool): True to consider rectangles touching by their
|
||||
edges or corners to be intersecting.
|
||||
(Should have been named include_touching)
|
||||
|
||||
Returns:
|
||||
bool: True if the rectangles intersect, False otherwise
|
||||
"""
|
||||
if edges:
|
||||
if (self.bottom > rect.top or self.top < rect.bottom or\
|
||||
self.left > rect.right or self.right < rect.left):
|
||||
return False
|
||||
else:
|
||||
if (self.bottom >= rect.top or self.top <= rect.bottom or
|
||||
self.left >= rect.right or self.right <= rect.left):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def intersection(self, rect, edges=False):
|
||||
"""
|
||||
Returns the rectangle resulting of the intersection between this and another
|
||||
rectangle. If the rectangles are only touching by their edges, and the
|
||||
argument 'edges' is True the rectangle returned will have an area of 0.
|
||||
Returns None if there is no intersection.
|
||||
|
||||
Arguments:
|
||||
rect (Rectangle): The other rectangle.
|
||||
edges (bool): If True Rectangles touching by their edges are
|
||||
considered to be intersection. In this case a rectangle of
|
||||
0 height or/and width will be returned.
|
||||
|
||||
Returns:
|
||||
Rectangle: Intersection.
|
||||
None: There was no intersection.
|
||||
"""
|
||||
if not self.intersects(rect, edges=edges):
|
||||
return None
|
||||
|
||||
bottom = max(self.bottom, rect.bottom)
|
||||
left = max(self.left, rect.left)
|
||||
top = min(self.top, rect.top)
|
||||
right = min(self.right, rect.right)
|
||||
|
||||
return Rectangle(left, bottom, right-left, top-bottom)
|
||||
|
||||
def join(self, other):
|
||||
"""
|
||||
Try to join a rectangle to this one, if the result is also a rectangle
|
||||
and the operation is successful and this rectangle is modified to the union.
|
||||
|
||||
Arguments:
|
||||
other (Rectangle): Rectangle to join
|
||||
|
||||
Returns:
|
||||
bool: True when successfully joined, False otherwise
|
||||
"""
|
||||
if self.contains(other):
|
||||
return True
|
||||
|
||||
if other.contains(self):
|
||||
self.x = other.x
|
||||
self.y = other.y
|
||||
self.width = other.width
|
||||
self.height = other.height
|
||||
return True
|
||||
|
||||
if not self.intersects(other, edges=True):
|
||||
return False
|
||||
|
||||
# Other rectangle is Up/Down from this
|
||||
if self.left == other.left and self.width == other.width:
|
||||
y_min = min(self.bottom, other.bottom)
|
||||
y_max = max(self.top, other.top)
|
||||
self.y = y_min
|
||||
self.height = y_max-y_min
|
||||
return True
|
||||
|
||||
# Other rectangle is Right/Left from this
|
||||
if self.bottom == other.bottom and self.height == other.height:
|
||||
x_min = min(self.left, other.left)
|
||||
x_max = max(self.right, other.right)
|
||||
self.x = x_min
|
||||
self.width = x_max-x_min
|
||||
return True
|
||||
|
||||
return False
|
||||
|
368
blender/arm/lightmapper/utility/rectpack/guillotine.py
Normal file
368
blender/arm/lightmapper/utility/rectpack/guillotine.py
Normal file
|
@ -0,0 +1,368 @@
|
|||
from .pack_algo import PackingAlgorithm
|
||||
from .geometry import Rectangle
|
||||
import itertools
|
||||
import operator
|
||||
|
||||
|
||||
class Guillotine(PackingAlgorithm):
|
||||
"""Implementation of several variants of Guillotine packing algorithm
|
||||
|
||||
For a more detailed explanation of the algorithm used, see:
|
||||
Jukka Jylanki - A Thousand Ways to Pack the Bin (February 27, 2010)
|
||||
"""
|
||||
def __init__(self, width, height, rot=True, merge=True, *args, **kwargs):
|
||||
"""
|
||||
Arguments:
|
||||
width (int, float):
|
||||
height (int, float):
|
||||
merge (bool): Optional keyword argument
|
||||
"""
|
||||
self._merge = merge
|
||||
super(Guillotine, self).__init__(width, height, rot, *args, **kwargs)
|
||||
|
||||
|
||||
def _add_section(self, section):
|
||||
"""Adds a new section to the free section list, but before that and if
|
||||
section merge is enabled, tries to join the rectangle with all existing
|
||||
sections, if successful the resulting section is again merged with the
|
||||
remaining sections until the operation fails. The result is then
|
||||
appended to the list.
|
||||
|
||||
Arguments:
|
||||
section (Rectangle): New free section.
|
||||
"""
|
||||
section.rid = 0
|
||||
plen = 0
|
||||
|
||||
while self._merge and self._sections and plen != len(self._sections):
|
||||
plen = len(self._sections)
|
||||
self._sections = [s for s in self._sections if not section.join(s)]
|
||||
self._sections.append(section)
|
||||
|
||||
|
||||
def _split_horizontal(self, section, width, height):
|
||||
"""For an horizontal split the rectangle is placed in the lower
|
||||
left corner of the section (section's xy coordinates), the top
|
||||
most side of the rectangle and its horizontal continuation,
|
||||
marks the line of division for the split.
|
||||
+-----------------+
|
||||
| |
|
||||
| |
|
||||
| |
|
||||
| |
|
||||
+-------+---------+
|
||||
|#######| |
|
||||
|#######| |
|
||||
|#######| |
|
||||
+-------+---------+
|
||||
If the rectangle width is equal to the the section width, only one
|
||||
section is created over the rectangle. If the rectangle height is
|
||||
equal to the section height, only one section to the right of the
|
||||
rectangle is created. If both width and height are equal, no sections
|
||||
are created.
|
||||
"""
|
||||
# First remove the section we are splitting so it doesn't
|
||||
# interfere when later we try to merge the resulting split
|
||||
# rectangles, with the rest of free sections.
|
||||
#self._sections.remove(section)
|
||||
|
||||
# Creates two new empty sections, and returns the new rectangle.
|
||||
if height < section.height:
|
||||
self._add_section(Rectangle(section.x, section.y+height,
|
||||
section.width, section.height-height))
|
||||
|
||||
if width < section.width:
|
||||
self._add_section(Rectangle(section.x+width, section.y,
|
||||
section.width-width, height))
|
||||
|
||||
|
||||
def _split_vertical(self, section, width, height):
|
||||
"""For a vertical split the rectangle is placed in the lower
|
||||
left corner of the section (section's xy coordinates), the
|
||||
right most side of the rectangle and its vertical continuation,
|
||||
marks the line of division for the split.
|
||||
+-------+---------+
|
||||
| | |
|
||||
| | |
|
||||
| | |
|
||||
| | |
|
||||
+-------+ |
|
||||
|#######| |
|
||||
|#######| |
|
||||
|#######| |
|
||||
+-------+---------+
|
||||
If the rectangle width is equal to the the section width, only one
|
||||
section is created over the rectangle. If the rectangle height is
|
||||
equal to the section height, only one section to the right of the
|
||||
rectangle is created. If both width and height are equal, no sections
|
||||
are created.
|
||||
"""
|
||||
# When a section is split, depending on the rectangle size
|
||||
# two, one, or no new sections will be created.
|
||||
if height < section.height:
|
||||
self._add_section(Rectangle(section.x, section.y+height,
|
||||
width, section.height-height))
|
||||
|
||||
if width < section.width:
|
||||
self._add_section(Rectangle(section.x+width, section.y,
|
||||
section.width-width, section.height))
|
||||
|
||||
|
||||
def _split(self, section, width, height):
|
||||
"""
|
||||
Selects the best split for a section, given a rectangle of dimmensions
|
||||
width and height, then calls _split_vertical or _split_horizontal,
|
||||
to do the dirty work.
|
||||
|
||||
Arguments:
|
||||
section (Rectangle): Section to split
|
||||
width (int, float): Rectangle width
|
||||
height (int, float): Rectangle height
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def _section_fitness(self, section, width, height):
|
||||
"""The subclass for each one of the Guillotine selection methods,
|
||||
BAF, BLSF.... will override this method, this is here only
|
||||
to asure a valid value return if the worst happens.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def _select_fittest_section(self, w, h):
|
||||
"""Calls _section_fitness for each of the sections in free section
|
||||
list. Returns the section with the minimal fitness value, all the rest
|
||||
is boilerplate to make the fitness comparison, to rotatate the rectangles,
|
||||
and to take into account when _section_fitness returns None because
|
||||
the rectangle couldn't be placed.
|
||||
|
||||
Arguments:
|
||||
w (int, float): Rectangle width
|
||||
h (int, float): Rectangle height
|
||||
|
||||
Returns:
|
||||
(section, was_rotated): Returns the tuple
|
||||
section (Rectangle): Section with best fitness
|
||||
was_rotated (bool): The rectangle was rotated
|
||||
"""
|
||||
fitn = ((self._section_fitness(s, w, h), s, False) for s in self._sections
|
||||
if self._section_fitness(s, w, h) is not None)
|
||||
fitr = ((self._section_fitness(s, h, w), s, True) for s in self._sections
|
||||
if self._section_fitness(s, h, w) is not None)
|
||||
|
||||
if not self.rot:
|
||||
fitr = []
|
||||
|
||||
fit = itertools.chain(fitn, fitr)
|
||||
|
||||
try:
|
||||
_, sec, rot = min(fit, key=operator.itemgetter(0))
|
||||
except ValueError:
|
||||
return None, None
|
||||
|
||||
return sec, rot
|
||||
|
||||
|
||||
def add_rect(self, width, height, rid=None):
|
||||
"""
|
||||
Add rectangle of widthxheight dimensions.
|
||||
|
||||
Arguments:
|
||||
width (int, float): Rectangle width
|
||||
height (int, float): Rectangle height
|
||||
rid: Optional rectangle user id
|
||||
|
||||
Returns:
|
||||
Rectangle: Rectangle with placemente coordinates
|
||||
None: If the rectangle couldn be placed.
|
||||
"""
|
||||
assert(width > 0 and height >0)
|
||||
|
||||
# Obtain the best section to place the rectangle.
|
||||
section, rotated = self._select_fittest_section(width, height)
|
||||
if not section:
|
||||
return None
|
||||
|
||||
if rotated:
|
||||
width, height = height, width
|
||||
|
||||
# Remove section, split and store results
|
||||
self._sections.remove(section)
|
||||
self._split(section, width, height)
|
||||
|
||||
# Store rectangle in the selected position
|
||||
rect = Rectangle(section.x, section.y, width, height, rid)
|
||||
self.rectangles.append(rect)
|
||||
return rect
|
||||
|
||||
def fitness(self, width, height):
|
||||
"""
|
||||
In guillotine algorithm case, returns the min of the fitness of all
|
||||
free sections, for the given dimension, both normal and rotated
|
||||
(if rotation enabled.)
|
||||
"""
|
||||
assert(width > 0 and height > 0)
|
||||
|
||||
# Get best fitness section.
|
||||
section, rotated = self._select_fittest_section(width, height)
|
||||
if not section:
|
||||
return None
|
||||
|
||||
# Return fitness of returned section, with correct dimmensions if the
|
||||
# the rectangle was rotated.
|
||||
if rotated:
|
||||
return self._section_fitness(section, height, width)
|
||||
else:
|
||||
return self._section_fitness(section, width, height)
|
||||
|
||||
def reset(self):
|
||||
super(Guillotine, self).reset()
|
||||
self._sections = []
|
||||
self._add_section(Rectangle(0, 0, self.width, self.height))
|
||||
|
||||
|
||||
|
||||
class GuillotineBaf(Guillotine):
|
||||
"""Implements Best Area Fit (BAF) section selection criteria for
|
||||
Guillotine algorithm.
|
||||
"""
|
||||
def _section_fitness(self, section, width, height):
|
||||
if width > section.width or height > section.height:
|
||||
return None
|
||||
return section.area()-width*height
|
||||
|
||||
|
||||
class GuillotineBlsf(Guillotine):
|
||||
"""Implements Best Long Side Fit (BLSF) section selection criteria for
|
||||
Guillotine algorithm.
|
||||
"""
|
||||
def _section_fitness(self, section, width, height):
|
||||
if width > section.width or height > section.height:
|
||||
return None
|
||||
return max(section.width-width, section.height-height)
|
||||
|
||||
|
||||
class GuillotineBssf(Guillotine):
|
||||
"""Implements Best Short Side Fit (BSSF) section selection criteria for
|
||||
Guillotine algorithm.
|
||||
"""
|
||||
def _section_fitness(self, section, width, height):
|
||||
if width > section.width or height > section.height:
|
||||
return None
|
||||
return min(section.width-width, section.height-height)
|
||||
|
||||
|
||||
class GuillotineSas(Guillotine):
|
||||
"""Implements Short Axis Split (SAS) selection rule for Guillotine
|
||||
algorithm.
|
||||
"""
|
||||
def _split(self, section, width, height):
|
||||
if section.width < section.height:
|
||||
return self._split_horizontal(section, width, height)
|
||||
else:
|
||||
return self._split_vertical(section, width, height)
|
||||
|
||||
|
||||
|
||||
class GuillotineLas(Guillotine):
|
||||
"""Implements Long Axis Split (LAS) selection rule for Guillotine
|
||||
algorithm.
|
||||
"""
|
||||
def _split(self, section, width, height):
|
||||
if section.width >= section.height:
|
||||
return self._split_horizontal(section, width, height)
|
||||
else:
|
||||
return self._split_vertical(section, width, height)
|
||||
|
||||
|
||||
|
||||
class GuillotineSlas(Guillotine):
|
||||
"""Implements Short Leftover Axis Split (SLAS) selection rule for
|
||||
Guillotine algorithm.
|
||||
"""
|
||||
def _split(self, section, width, height):
|
||||
if section.width-width < section.height-height:
|
||||
return self._split_horizontal(section, width, height)
|
||||
else:
|
||||
return self._split_vertical(section, width, height)
|
||||
|
||||
|
||||
|
||||
class GuillotineLlas(Guillotine):
|
||||
"""Implements Long Leftover Axis Split (LLAS) selection rule for
|
||||
Guillotine algorithm.
|
||||
"""
|
||||
def _split(self, section, width, height):
|
||||
if section.width-width >= section.height-height:
|
||||
return self._split_horizontal(section, width, height)
|
||||
else:
|
||||
return self._split_vertical(section, width, height)
|
||||
|
||||
|
||||
|
||||
class GuillotineMaxas(Guillotine):
|
||||
"""Implements Max Area Axis Split (MAXAS) selection rule for Guillotine
|
||||
algorithm. Maximize the larger area == minimize the smaller area.
|
||||
Tries to make the rectangles more even-sized.
|
||||
"""
|
||||
def _split(self, section, width, height):
|
||||
if width*(section.height-height) <= height*(section.width-width):
|
||||
return self._split_horizontal(section, width, height)
|
||||
else:
|
||||
return self._split_vertical(section, width, height)
|
||||
|
||||
|
||||
|
||||
class GuillotineMinas(Guillotine):
|
||||
"""Implements Min Area Axis Split (MINAS) selection rule for Guillotine
|
||||
algorithm.
|
||||
"""
|
||||
def _split(self, section, width, height):
|
||||
if width*(section.height-height) >= height*(section.width-width):
|
||||
return self._split_horizontal(section, width, height)
|
||||
else:
|
||||
return self._split_vertical(section, width, height)
|
||||
|
||||
|
||||
|
||||
# Guillotine algorithms GUILLOTINE-RECT-SPLIT, Selecting one
|
||||
# Axis split, and one selection criteria.
|
||||
class GuillotineBssfSas(GuillotineBssf, GuillotineSas):
|
||||
pass
|
||||
class GuillotineBssfLas(GuillotineBssf, GuillotineLas):
|
||||
pass
|
||||
class GuillotineBssfSlas(GuillotineBssf, GuillotineSlas):
|
||||
pass
|
||||
class GuillotineBssfLlas(GuillotineBssf, GuillotineLlas):
|
||||
pass
|
||||
class GuillotineBssfMaxas(GuillotineBssf, GuillotineMaxas):
|
||||
pass
|
||||
class GuillotineBssfMinas(GuillotineBssf, GuillotineMinas):
|
||||
pass
|
||||
class GuillotineBlsfSas(GuillotineBlsf, GuillotineSas):
|
||||
pass
|
||||
class GuillotineBlsfLas(GuillotineBlsf, GuillotineLas):
|
||||
pass
|
||||
class GuillotineBlsfSlas(GuillotineBlsf, GuillotineSlas):
|
||||
pass
|
||||
class GuillotineBlsfLlas(GuillotineBlsf, GuillotineLlas):
|
||||
pass
|
||||
class GuillotineBlsfMaxas(GuillotineBlsf, GuillotineMaxas):
|
||||
pass
|
||||
class GuillotineBlsfMinas(GuillotineBlsf, GuillotineMinas):
|
||||
pass
|
||||
class GuillotineBafSas(GuillotineBaf, GuillotineSas):
|
||||
pass
|
||||
class GuillotineBafLas(GuillotineBaf, GuillotineLas):
|
||||
pass
|
||||
class GuillotineBafSlas(GuillotineBaf, GuillotineSlas):
|
||||
pass
|
||||
class GuillotineBafLlas(GuillotineBaf, GuillotineLlas):
|
||||
pass
|
||||
class GuillotineBafMaxas(GuillotineBaf, GuillotineMaxas):
|
||||
pass
|
||||
class GuillotineBafMinas(GuillotineBaf, GuillotineMinas):
|
||||
pass
|
||||
|
||||
|
||||
|
244
blender/arm/lightmapper/utility/rectpack/maxrects.py
Normal file
244
blender/arm/lightmapper/utility/rectpack/maxrects.py
Normal file
|
@ -0,0 +1,244 @@
|
|||
from .pack_algo import PackingAlgorithm
|
||||
from .geometry import Rectangle
|
||||
import itertools
|
||||
import collections
|
||||
import operator
|
||||
|
||||
|
||||
first_item = operator.itemgetter(0)
|
||||
|
||||
|
||||
|
||||
class MaxRects(PackingAlgorithm):
|
||||
|
||||
def __init__(self, width, height, rot=True, *args, **kwargs):
|
||||
super(MaxRects, self).__init__(width, height, rot, *args, **kwargs)
|
||||
|
||||
def _rect_fitness(self, max_rect, width, height):
|
||||
"""
|
||||
Arguments:
|
||||
max_rect (Rectangle): Destination max_rect
|
||||
width (int, float): Rectangle width
|
||||
height (int, float): Rectangle height
|
||||
|
||||
Returns:
|
||||
None: Rectangle couldn't be placed into max_rect
|
||||
integer, float: fitness value
|
||||
"""
|
||||
if width <= max_rect.width and height <= max_rect.height:
|
||||
return 0
|
||||
else:
|
||||
return None
|
||||
|
||||
def _select_position(self, w, h):
|
||||
"""
|
||||
Find max_rect with best fitness for placing a rectangle
|
||||
of dimentsions w*h
|
||||
|
||||
Arguments:
|
||||
w (int, float): Rectangle width
|
||||
h (int, float): Rectangle height
|
||||
|
||||
Returns:
|
||||
(rect, max_rect)
|
||||
rect (Rectangle): Placed rectangle or None if was unable.
|
||||
max_rect (Rectangle): Maximal rectangle were rect was placed
|
||||
"""
|
||||
if not self._max_rects:
|
||||
return None, None
|
||||
|
||||
# Normal rectangle
|
||||
fitn = ((self._rect_fitness(m, w, h), w, h, m) for m in self._max_rects
|
||||
if self._rect_fitness(m, w, h) is not None)
|
||||
|
||||
# Rotated rectangle
|
||||
fitr = ((self._rect_fitness(m, h, w), h, w, m) for m in self._max_rects
|
||||
if self._rect_fitness(m, h, w) is not None)
|
||||
|
||||
if not self.rot:
|
||||
fitr = []
|
||||
|
||||
fit = itertools.chain(fitn, fitr)
|
||||
|
||||
try:
|
||||
_, w, h, m = min(fit, key=first_item)
|
||||
except ValueError:
|
||||
return None, None
|
||||
|
||||
return Rectangle(m.x, m.y, w, h), m
|
||||
|
||||
def _generate_splits(self, m, r):
|
||||
"""
|
||||
When a rectangle is placed inside a maximal rectangle, it stops being one
|
||||
and up to 4 new maximal rectangles may appear depending on the placement.
|
||||
_generate_splits calculates them.
|
||||
|
||||
Arguments:
|
||||
m (Rectangle): max_rect rectangle
|
||||
r (Rectangle): rectangle placed
|
||||
|
||||
Returns:
|
||||
list : list containing new maximal rectangles or an empty list
|
||||
"""
|
||||
new_rects = []
|
||||
|
||||
if r.left > m.left:
|
||||
new_rects.append(Rectangle(m.left, m.bottom, r.left-m.left, m.height))
|
||||
if r.right < m.right:
|
||||
new_rects.append(Rectangle(r.right, m.bottom, m.right-r.right, m.height))
|
||||
if r.top < m.top:
|
||||
new_rects.append(Rectangle(m.left, r.top, m.width, m.top-r.top))
|
||||
if r.bottom > m.bottom:
|
||||
new_rects.append(Rectangle(m.left, m.bottom, m.width, r.bottom-m.bottom))
|
||||
|
||||
return new_rects
|
||||
|
||||
def _split(self, rect):
|
||||
"""
|
||||
Split all max_rects intersecting the rectangle rect into up to
|
||||
4 new max_rects.
|
||||
|
||||
Arguments:
|
||||
rect (Rectangle): Rectangle
|
||||
|
||||
Returns:
|
||||
split (Rectangle list): List of rectangles resulting from the split
|
||||
"""
|
||||
max_rects = collections.deque()
|
||||
|
||||
for r in self._max_rects:
|
||||
if r.intersects(rect):
|
||||
max_rects.extend(self._generate_splits(r, rect))
|
||||
else:
|
||||
max_rects.append(r)
|
||||
|
||||
# Add newly generated max_rects
|
||||
self._max_rects = list(max_rects)
|
||||
|
||||
def _remove_duplicates(self):
|
||||
"""
|
||||
Remove every maximal rectangle contained by another one.
|
||||
"""
|
||||
contained = set()
|
||||
for m1, m2 in itertools.combinations(self._max_rects, 2):
|
||||
if m1.contains(m2):
|
||||
contained.add(m2)
|
||||
elif m2.contains(m1):
|
||||
contained.add(m1)
|
||||
|
||||
# Remove from max_rects
|
||||
self._max_rects = [m for m in self._max_rects if m not in contained]
|
||||
|
||||
def fitness(self, width, height):
|
||||
"""
|
||||
Metric used to rate how much space is wasted if a rectangle is placed.
|
||||
Returns a value greater or equal to zero, the smaller the value the more
|
||||
'fit' is the rectangle. If the rectangle can't be placed, returns None.
|
||||
|
||||
Arguments:
|
||||
width (int, float): Rectangle width
|
||||
height (int, float): Rectangle height
|
||||
|
||||
Returns:
|
||||
int, float: Rectangle fitness
|
||||
None: Rectangle can't be placed
|
||||
"""
|
||||
assert(width > 0 and height > 0)
|
||||
|
||||
rect, max_rect = self._select_position(width, height)
|
||||
if rect is None:
|
||||
return None
|
||||
|
||||
# Return fitness
|
||||
return self._rect_fitness(max_rect, rect.width, rect.height)
|
||||
|
||||
def add_rect(self, width, height, rid=None):
|
||||
"""
|
||||
Add rectangle of widthxheight dimensions.
|
||||
|
||||
Arguments:
|
||||
width (int, float): Rectangle width
|
||||
height (int, float): Rectangle height
|
||||
rid: Optional rectangle user id
|
||||
|
||||
Returns:
|
||||
Rectangle: Rectangle with placemente coordinates
|
||||
None: If the rectangle couldn be placed.
|
||||
"""
|
||||
assert(width > 0 and height >0)
|
||||
|
||||
# Search best position and orientation
|
||||
rect, _ = self._select_position(width, height)
|
||||
if not rect:
|
||||
return None
|
||||
|
||||
# Subdivide all the max rectangles intersecting with the selected
|
||||
# rectangle.
|
||||
self._split(rect)
|
||||
|
||||
# Remove any max_rect contained by another
|
||||
self._remove_duplicates()
|
||||
|
||||
# Store and return rectangle position.
|
||||
rect.rid = rid
|
||||
self.rectangles.append(rect)
|
||||
return rect
|
||||
|
||||
def reset(self):
|
||||
super(MaxRects, self).reset()
|
||||
self._max_rects = [Rectangle(0, 0, self.width, self.height)]
|
||||
|
||||
|
||||
|
||||
|
||||
class MaxRectsBl(MaxRects):
|
||||
|
||||
def _select_position(self, w, h):
|
||||
"""
|
||||
Select the position where the y coordinate of the top of the rectangle
|
||||
is lower, if there are severtal pick the one with the smallest x
|
||||
coordinate
|
||||
"""
|
||||
fitn = ((m.y+h, m.x, w, h, m) for m in self._max_rects
|
||||
if self._rect_fitness(m, w, h) is not None)
|
||||
fitr = ((m.y+w, m.x, h, w, m) for m in self._max_rects
|
||||
if self._rect_fitness(m, h, w) is not None)
|
||||
|
||||
if not self.rot:
|
||||
fitr = []
|
||||
|
||||
fit = itertools.chain(fitn, fitr)
|
||||
|
||||
try:
|
||||
_, _, w, h, m = min(fit, key=first_item)
|
||||
except ValueError:
|
||||
return None, None
|
||||
|
||||
return Rectangle(m.x, m.y, w, h), m
|
||||
|
||||
|
||||
class MaxRectsBssf(MaxRects):
|
||||
"""Best Sort Side Fit minimize short leftover side"""
|
||||
def _rect_fitness(self, max_rect, width, height):
|
||||
if width > max_rect.width or height > max_rect.height:
|
||||
return None
|
||||
|
||||
return min(max_rect.width-width, max_rect.height-height)
|
||||
|
||||
class MaxRectsBaf(MaxRects):
|
||||
"""Best Area Fit pick maximal rectangle with smallest area
|
||||
where the rectangle can be placed"""
|
||||
def _rect_fitness(self, max_rect, width, height):
|
||||
if width > max_rect.width or height > max_rect.height:
|
||||
return None
|
||||
|
||||
return (max_rect.width*max_rect.height)-(width*height)
|
||||
|
||||
|
||||
class MaxRectsBlsf(MaxRects):
|
||||
"""Best Long Side Fit minimize long leftover side"""
|
||||
def _rect_fitness(self, max_rect, width, height):
|
||||
if width > max_rect.width or height > max_rect.height:
|
||||
return None
|
||||
|
||||
return max(max_rect.width-width, max_rect.height-height)
|
140
blender/arm/lightmapper/utility/rectpack/pack_algo.py
Normal file
140
blender/arm/lightmapper/utility/rectpack/pack_algo.py
Normal file
|
@ -0,0 +1,140 @@
|
|||
from .geometry import Rectangle
|
||||
|
||||
|
||||
class PackingAlgorithm(object):
|
||||
"""PackingAlgorithm base class"""
|
||||
|
||||
def __init__(self, width, height, rot=True, bid=None, *args, **kwargs):
|
||||
"""
|
||||
Initialize packing algorithm
|
||||
|
||||
Arguments:
|
||||
width (int, float): Packing surface width
|
||||
height (int, float): Packing surface height
|
||||
rot (bool): Rectangle rotation enabled or disabled
|
||||
bid (string|int|...): Packing surface identification
|
||||
"""
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.rot = rot
|
||||
self.rectangles = []
|
||||
self.bid = bid
|
||||
self._surface = Rectangle(0, 0, width, height)
|
||||
self.reset()
|
||||
|
||||
def __len__(self):
|
||||
return len(self.rectangles)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.rectangles)
|
||||
|
||||
def _fits_surface(self, width, height):
|
||||
"""
|
||||
Test surface is big enough to place a rectangle
|
||||
|
||||
Arguments:
|
||||
width (int, float): Rectangle width
|
||||
height (int, float): Rectangle height
|
||||
|
||||
Returns:
|
||||
boolean: True if it could be placed, False otherwise
|
||||
"""
|
||||
assert(width > 0 and height > 0)
|
||||
if self.rot and (width > self.width or height > self.height):
|
||||
width, height = height, width
|
||||
|
||||
if width > self.width or height > self.height:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def __getitem__(self, key):
|
||||
"""
|
||||
Return rectangle in selected position.
|
||||
"""
|
||||
return self.rectangles[key]
|
||||
|
||||
def used_area(self):
|
||||
"""
|
||||
Total area of rectangles placed
|
||||
|
||||
Returns:
|
||||
int, float: Area
|
||||
"""
|
||||
return sum(r.area() for r in self)
|
||||
|
||||
def fitness(self, width, height, rot = False):
|
||||
"""
|
||||
Metric used to rate how much space is wasted if a rectangle is placed.
|
||||
Returns a value greater or equal to zero, the smaller the value the more
|
||||
'fit' is the rectangle. If the rectangle can't be placed, returns None.
|
||||
|
||||
Arguments:
|
||||
width (int, float): Rectangle width
|
||||
height (int, float): Rectangle height
|
||||
rot (bool): Enable rectangle rotation
|
||||
|
||||
Returns:
|
||||
int, float: Rectangle fitness
|
||||
None: Rectangle can't be placed
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def add_rect(self, width, height, rid=None):
|
||||
"""
|
||||
Add rectangle of widthxheight dimensions.
|
||||
|
||||
Arguments:
|
||||
width (int, float): Rectangle width
|
||||
height (int, float): Rectangle height
|
||||
rid: Optional rectangle user id
|
||||
|
||||
Returns:
|
||||
Rectangle: Rectangle with placemente coordinates
|
||||
None: If the rectangle couldn be placed.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def rect_list(self):
|
||||
"""
|
||||
Returns a list with all rectangles placed into the surface.
|
||||
|
||||
Returns:
|
||||
List: Format [(x, y, width, height, rid), ...]
|
||||
"""
|
||||
rectangle_list = []
|
||||
for r in self:
|
||||
rectangle_list.append((r.x, r.y, r.width, r.height, r.rid))
|
||||
|
||||
return rectangle_list
|
||||
|
||||
def validate_packing(self):
|
||||
"""
|
||||
Check for collisions between rectangles, also check all are placed
|
||||
inside surface.
|
||||
"""
|
||||
surface = Rectangle(0, 0, self.width, self.height)
|
||||
|
||||
for r in self:
|
||||
if not surface.contains(r):
|
||||
raise Exception("Rectangle placed outside surface")
|
||||
|
||||
|
||||
rectangles = [r for r in self]
|
||||
if len(rectangles) <= 1:
|
||||
return
|
||||
|
||||
for r1 in range(0, len(rectangles)-2):
|
||||
for r2 in range(r1+1, len(rectangles)-1):
|
||||
if rectangles[r1].intersects(rectangles[r2]):
|
||||
raise Exception("Rectangle collision detected")
|
||||
|
||||
def is_empty(self):
|
||||
# Returns true if there is no rectangles placed.
|
||||
return not bool(len(self))
|
||||
|
||||
def reset(self):
|
||||
self.rectangles = [] # List of placed Rectangles.
|
||||
|
||||
|
||||
|
580
blender/arm/lightmapper/utility/rectpack/packer.py
Normal file
580
blender/arm/lightmapper/utility/rectpack/packer.py
Normal file
|
@ -0,0 +1,580 @@
|
|||
from .maxrects import MaxRectsBssf
|
||||
|
||||
import operator
|
||||
import itertools
|
||||
import collections
|
||||
|
||||
import decimal
|
||||
|
||||
# Float to Decimal helper
|
||||
def float2dec(ft, decimal_digits):
|
||||
"""
|
||||
Convert float (or int) to Decimal (rounding up) with the
|
||||
requested number of decimal digits.
|
||||
|
||||
Arguments:
|
||||
ft (float, int): Number to convert
|
||||
decimal (int): Number of digits after decimal point
|
||||
|
||||
Return:
|
||||
Decimal: Number converted to decima
|
||||
"""
|
||||
with decimal.localcontext() as ctx:
|
||||
ctx.rounding = decimal.ROUND_UP
|
||||
places = decimal.Decimal(10)**(-decimal_digits)
|
||||
return decimal.Decimal.from_float(float(ft)).quantize(places)
|
||||
|
||||
|
||||
# Sorting algos for rectangle lists
|
||||
SORT_AREA = lambda rectlist: sorted(rectlist, reverse=True,
|
||||
key=lambda r: r[0]*r[1]) # Sort by area
|
||||
|
||||
SORT_PERI = lambda rectlist: sorted(rectlist, reverse=True,
|
||||
key=lambda r: r[0]+r[1]) # Sort by perimeter
|
||||
|
||||
SORT_DIFF = lambda rectlist: sorted(rectlist, reverse=True,
|
||||
key=lambda r: abs(r[0]-r[1])) # Sort by Diff
|
||||
|
||||
SORT_SSIDE = lambda rectlist: sorted(rectlist, reverse=True,
|
||||
key=lambda r: (min(r[0], r[1]), max(r[0], r[1]))) # Sort by short side
|
||||
|
||||
SORT_LSIDE = lambda rectlist: sorted(rectlist, reverse=True,
|
||||
key=lambda r: (max(r[0], r[1]), min(r[0], r[1]))) # Sort by long side
|
||||
|
||||
SORT_RATIO = lambda rectlist: sorted(rectlist, reverse=True,
|
||||
key=lambda r: r[0]/r[1]) # Sort by side ratio
|
||||
|
||||
SORT_NONE = lambda rectlist: list(rectlist) # Unsorted
|
||||
|
||||
|
||||
|
||||
class BinFactory(object):
|
||||
|
||||
def __init__(self, width, height, count, pack_algo, *args, **kwargs):
|
||||
self._width = width
|
||||
self._height = height
|
||||
self._count = count
|
||||
|
||||
self._pack_algo = pack_algo
|
||||
self._algo_kwargs = kwargs
|
||||
self._algo_args = args
|
||||
self._ref_bin = None # Reference bin used to calculate fitness
|
||||
|
||||
self._bid = kwargs.get("bid", None)
|
||||
|
||||
def _create_bin(self):
|
||||
return self._pack_algo(self._width, self._height, *self._algo_args, **self._algo_kwargs)
|
||||
|
||||
def is_empty(self):
|
||||
return self._count<1
|
||||
|
||||
def fitness(self, width, height):
|
||||
if not self._ref_bin:
|
||||
self._ref_bin = self._create_bin()
|
||||
|
||||
return self._ref_bin.fitness(width, height)
|
||||
|
||||
def fits_inside(self, width, height):
|
||||
# Determine if rectangle widthxheight will fit into empty bin
|
||||
if not self._ref_bin:
|
||||
self._ref_bin = self._create_bin()
|
||||
|
||||
return self._ref_bin._fits_surface(width, height)
|
||||
|
||||
def new_bin(self):
|
||||
if self._count > 0:
|
||||
self._count -= 1
|
||||
return self._create_bin()
|
||||
else:
|
||||
return None
|
||||
|
||||
def __eq__(self, other):
|
||||
return self._width*self._height == other._width*other._height
|
||||
|
||||
def __lt__(self, other):
|
||||
return self._width*self._height < other._width*other._height
|
||||
|
||||
def __str__(self):
|
||||
return "Bin: {} {} {}".format(self._width, self._height, self._count)
|
||||
|
||||
|
||||
|
||||
class PackerBNFMixin(object):
|
||||
"""
|
||||
BNF (Bin Next Fit): Only one open bin at a time. If the rectangle
|
||||
doesn't fit, close the current bin and go to the next.
|
||||
"""
|
||||
|
||||
def add_rect(self, width, height, rid=None):
|
||||
while True:
|
||||
# if there are no open bins, try to open a new one
|
||||
if len(self._open_bins)==0:
|
||||
# can we find an unopened bin that will hold this rect?
|
||||
new_bin = self._new_open_bin(width, height, rid=rid)
|
||||
if new_bin is None:
|
||||
return None
|
||||
|
||||
# we have at least one open bin, so check if it can hold this rect
|
||||
rect = self._open_bins[0].add_rect(width, height, rid=rid)
|
||||
if rect is not None:
|
||||
return rect
|
||||
|
||||
# since the rect doesn't fit, close this bin and try again
|
||||
closed_bin = self._open_bins.popleft()
|
||||
self._closed_bins.append(closed_bin)
|
||||
|
||||
|
||||
class PackerBFFMixin(object):
|
||||
"""
|
||||
BFF (Bin First Fit): Pack rectangle in first bin it fits
|
||||
"""
|
||||
|
||||
def add_rect(self, width, height, rid=None):
|
||||
# see if this rect will fit in any of the open bins
|
||||
for b in self._open_bins:
|
||||
rect = b.add_rect(width, height, rid=rid)
|
||||
if rect is not None:
|
||||
return rect
|
||||
|
||||
while True:
|
||||
# can we find an unopened bin that will hold this rect?
|
||||
new_bin = self._new_open_bin(width, height, rid=rid)
|
||||
if new_bin is None:
|
||||
return None
|
||||
|
||||
# _new_open_bin may return a bin that's too small,
|
||||
# so we have to double-check
|
||||
rect = new_bin.add_rect(width, height, rid=rid)
|
||||
if rect is not None:
|
||||
return rect
|
||||
|
||||
|
||||
class PackerBBFMixin(object):
|
||||
"""
|
||||
BBF (Bin Best Fit): Pack rectangle in bin that gives best fitness
|
||||
"""
|
||||
|
||||
# only create this getter once
|
||||
first_item = operator.itemgetter(0)
|
||||
|
||||
def add_rect(self, width, height, rid=None):
|
||||
|
||||
# Try packing into open bins
|
||||
fit = ((b.fitness(width, height), b) for b in self._open_bins)
|
||||
fit = (b for b in fit if b[0] is not None)
|
||||
try:
|
||||
_, best_bin = min(fit, key=self.first_item)
|
||||
best_bin.add_rect(width, height, rid)
|
||||
return True
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# Try packing into one of the empty bins
|
||||
while True:
|
||||
# can we find an unopened bin that will hold this rect?
|
||||
new_bin = self._new_open_bin(width, height, rid=rid)
|
||||
if new_bin is None:
|
||||
return False
|
||||
|
||||
# _new_open_bin may return a bin that's too small,
|
||||
# so we have to double-check
|
||||
if new_bin.add_rect(width, height, rid):
|
||||
return True
|
||||
|
||||
|
||||
|
||||
class PackerOnline(object):
|
||||
"""
|
||||
Rectangles are packed as soon are they are added
|
||||
"""
|
||||
|
||||
def __init__(self, pack_algo=MaxRectsBssf, rotation=True):
|
||||
"""
|
||||
Arguments:
|
||||
pack_algo (PackingAlgorithm): What packing algo to use
|
||||
rotation (bool): Enable/Disable rectangle rotation
|
||||
"""
|
||||
self._rotation = rotation
|
||||
self._pack_algo = pack_algo
|
||||
self.reset()
|
||||
|
||||
def __iter__(self):
|
||||
return itertools.chain(self._closed_bins, self._open_bins)
|
||||
|
||||
def __len__(self):
|
||||
return len(self._closed_bins)+len(self._open_bins)
|
||||
|
||||
def __getitem__(self, key):
|
||||
"""
|
||||
Return bin in selected position. (excluding empty bins)
|
||||
"""
|
||||
if not isinstance(key, int):
|
||||
raise TypeError("Indices must be integers")
|
||||
|
||||
size = len(self) # avoid recalulations
|
||||
|
||||
if key < 0:
|
||||
key += size
|
||||
|
||||
if not 0 <= key < size:
|
||||
raise IndexError("Index out of range")
|
||||
|
||||
if key < len(self._closed_bins):
|
||||
return self._closed_bins[key]
|
||||
else:
|
||||
return self._open_bins[key-len(self._closed_bins)]
|
||||
|
||||
def _new_open_bin(self, width=None, height=None, rid=None):
|
||||
"""
|
||||
Extract the next empty bin and append it to open bins
|
||||
|
||||
Returns:
|
||||
PackingAlgorithm: Initialized empty packing bin.
|
||||
None: No bin big enough for the rectangle was found
|
||||
"""
|
||||
factories_to_delete = set() #
|
||||
new_bin = None
|
||||
|
||||
for key, binfac in self._empty_bins.items():
|
||||
|
||||
# Only return the new bin if the rect fits.
|
||||
# (If width or height is None, caller doesn't know the size.)
|
||||
if not binfac.fits_inside(width, height):
|
||||
continue
|
||||
|
||||
# Create bin and add to open_bins
|
||||
new_bin = binfac.new_bin()
|
||||
if new_bin is None:
|
||||
continue
|
||||
self._open_bins.append(new_bin)
|
||||
|
||||
# If the factory was depleted mark for deletion
|
||||
if binfac.is_empty():
|
||||
factories_to_delete.add(key)
|
||||
|
||||
break
|
||||
|
||||
# Delete marked factories
|
||||
for f in factories_to_delete:
|
||||
del self._empty_bins[f]
|
||||
|
||||
return new_bin
|
||||
|
||||
def add_bin(self, width, height, count=1, **kwargs):
|
||||
# accept the same parameters as PackingAlgorithm objects
|
||||
kwargs['rot'] = self._rotation
|
||||
bin_factory = BinFactory(width, height, count, self._pack_algo, **kwargs)
|
||||
self._empty_bins[next(self._bin_count)] = bin_factory
|
||||
|
||||
def rect_list(self):
|
||||
rectangles = []
|
||||
bin_count = 0
|
||||
|
||||
for abin in self:
|
||||
for rect in abin:
|
||||
rectangles.append((bin_count, rect.x, rect.y, rect.width, rect.height, rect.rid))
|
||||
bin_count += 1
|
||||
|
||||
return rectangles
|
||||
|
||||
def bin_list(self):
|
||||
"""
|
||||
Return a list of the dimmensions of the bins in use, that is closed
|
||||
or open containing at least one rectangle
|
||||
"""
|
||||
return [(b.width, b.height) for b in self]
|
||||
|
||||
def validate_packing(self):
|
||||
for b in self:
|
||||
b.validate_packing()
|
||||
|
||||
def reset(self):
|
||||
# Bins fully packed and closed.
|
||||
self._closed_bins = collections.deque()
|
||||
|
||||
# Bins ready to pack rectangles
|
||||
self._open_bins = collections.deque()
|
||||
|
||||
# User provided bins not in current use
|
||||
self._empty_bins = collections.OrderedDict() # O(1) deletion of arbitrary elem
|
||||
self._bin_count = itertools.count()
|
||||
|
||||
|
||||
class Packer(PackerOnline):
|
||||
"""
|
||||
Rectangles aren't packed untils pack() is called
|
||||
"""
|
||||
|
||||
def __init__(self, pack_algo=MaxRectsBssf, sort_algo=SORT_NONE,
|
||||
rotation=True):
|
||||
"""
|
||||
"""
|
||||
super(Packer, self).__init__(pack_algo=pack_algo, rotation=rotation)
|
||||
|
||||
self._sort_algo = sort_algo
|
||||
|
||||
# User provided bins and Rectangles
|
||||
self._avail_bins = collections.deque()
|
||||
self._avail_rect = collections.deque()
|
||||
|
||||
# Aux vars used during packing
|
||||
self._sorted_rect = []
|
||||
|
||||
def add_bin(self, width, height, count=1, **kwargs):
|
||||
self._avail_bins.append((width, height, count, kwargs))
|
||||
|
||||
def add_rect(self, width, height, rid=None):
|
||||
self._avail_rect.append((width, height, rid))
|
||||
|
||||
def _is_everything_ready(self):
|
||||
return self._avail_rect and self._avail_bins
|
||||
|
||||
def pack(self):
|
||||
|
||||
self.reset()
|
||||
|
||||
if not self._is_everything_ready():
|
||||
# maybe we should throw an error here?
|
||||
return
|
||||
|
||||
# Add available bins to packer
|
||||
for b in self._avail_bins:
|
||||
width, height, count, extra_kwargs = b
|
||||
super(Packer, self).add_bin(width, height, count, **extra_kwargs)
|
||||
|
||||
# If enabled sort rectangles
|
||||
self._sorted_rect = self._sort_algo(self._avail_rect)
|
||||
|
||||
# Start packing
|
||||
for r in self._sorted_rect:
|
||||
super(Packer, self).add_rect(*r)
|
||||
|
||||
|
||||
|
||||
class PackerBNF(Packer, PackerBNFMixin):
|
||||
"""
|
||||
BNF (Bin Next Fit): Only one open bin, if rectangle doesn't fit
|
||||
go to next bin and close current one.
|
||||
"""
|
||||
pass
|
||||
|
||||
class PackerBFF(Packer, PackerBFFMixin):
|
||||
"""
|
||||
BFF (Bin First Fit): Pack rectangle in first bin it fits
|
||||
"""
|
||||
pass
|
||||
|
||||
class PackerBBF(Packer, PackerBBFMixin):
|
||||
"""
|
||||
BBF (Bin Best Fit): Pack rectangle in bin that gives best fitness
|
||||
"""
|
||||
pass
|
||||
|
||||
class PackerOnlineBNF(PackerOnline, PackerBNFMixin):
|
||||
"""
|
||||
BNF Bin Next Fit Online variant
|
||||
"""
|
||||
pass
|
||||
|
||||
class PackerOnlineBFF(PackerOnline, PackerBFFMixin):
|
||||
"""
|
||||
BFF Bin First Fit Online variant
|
||||
"""
|
||||
pass
|
||||
|
||||
class PackerOnlineBBF(PackerOnline, PackerBBFMixin):
|
||||
"""
|
||||
BBF Bin Best Fit Online variant
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class PackerGlobal(Packer, PackerBNFMixin):
|
||||
"""
|
||||
GLOBAL: For each bin pack the rectangle with the best fitness.
|
||||
"""
|
||||
first_item = operator.itemgetter(0)
|
||||
|
||||
def __init__(self, pack_algo=MaxRectsBssf, rotation=True):
|
||||
"""
|
||||
"""
|
||||
super(PackerGlobal, self).__init__(pack_algo=pack_algo,
|
||||
sort_algo=SORT_NONE, rotation=rotation)
|
||||
|
||||
def _find_best_fit(self, pbin):
|
||||
"""
|
||||
Return best fitness rectangle from rectangles packing _sorted_rect list
|
||||
|
||||
Arguments:
|
||||
pbin (PackingAlgorithm): Packing bin
|
||||
|
||||
Returns:
|
||||
key of the rectangle with best fitness
|
||||
"""
|
||||
fit = ((pbin.fitness(r[0], r[1]), k) for k, r in self._sorted_rect.items())
|
||||
fit = (f for f in fit if f[0] is not None)
|
||||
try:
|
||||
_, rect = min(fit, key=self.first_item)
|
||||
return rect
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
|
||||
def _new_open_bin(self, remaining_rect):
|
||||
"""
|
||||
Extract the next bin where at least one of the rectangles in
|
||||
rem
|
||||
|
||||
Arguments:
|
||||
remaining_rect (dict): rectangles not placed yet
|
||||
|
||||
Returns:
|
||||
PackingAlgorithm: Initialized empty packing bin.
|
||||
None: No bin big enough for the rectangle was found
|
||||
"""
|
||||
factories_to_delete = set() #
|
||||
new_bin = None
|
||||
|
||||
for key, binfac in self._empty_bins.items():
|
||||
|
||||
# Only return the new bin if at least one of the remaining
|
||||
# rectangles fit inside.
|
||||
a_rectangle_fits = False
|
||||
for _, rect in remaining_rect.items():
|
||||
if binfac.fits_inside(rect[0], rect[1]):
|
||||
a_rectangle_fits = True
|
||||
break
|
||||
|
||||
if not a_rectangle_fits:
|
||||
factories_to_delete.add(key)
|
||||
continue
|
||||
|
||||
# Create bin and add to open_bins
|
||||
new_bin = binfac.new_bin()
|
||||
if new_bin is None:
|
||||
continue
|
||||
self._open_bins.append(new_bin)
|
||||
|
||||
# If the factory was depleted mark for deletion
|
||||
if binfac.is_empty():
|
||||
factories_to_delete.add(key)
|
||||
|
||||
break
|
||||
|
||||
# Delete marked factories
|
||||
for f in factories_to_delete:
|
||||
del self._empty_bins[f]
|
||||
|
||||
return new_bin
|
||||
|
||||
def pack(self):
|
||||
|
||||
self.reset()
|
||||
|
||||
if not self._is_everything_ready():
|
||||
return
|
||||
|
||||
# Add available bins to packer
|
||||
for b in self._avail_bins:
|
||||
width, height, count, extra_kwargs = b
|
||||
super(Packer, self).add_bin(width, height, count, **extra_kwargs)
|
||||
|
||||
# Store rectangles into dict for fast deletion
|
||||
self._sorted_rect = collections.OrderedDict(
|
||||
enumerate(self._sort_algo(self._avail_rect)))
|
||||
|
||||
# For each bin pack the rectangles with lowest fitness until it is filled or
|
||||
# the rectangles exhausted, then open the next bin where at least one rectangle
|
||||
# will fit and repeat the process until there aren't more rectangles or bins
|
||||
# available.
|
||||
while len(self._sorted_rect) > 0:
|
||||
|
||||
# Find one bin where at least one of the remaining rectangles fit
|
||||
pbin = self._new_open_bin(self._sorted_rect)
|
||||
if pbin is None:
|
||||
break
|
||||
|
||||
# Pack as many rectangles as possible into the open bin
|
||||
while True:
|
||||
|
||||
# Find 'fittest' rectangle
|
||||
best_rect_key = self._find_best_fit(pbin)
|
||||
if best_rect_key is None:
|
||||
closed_bin = self._open_bins.popleft()
|
||||
self._closed_bins.append(closed_bin)
|
||||
break # None of the remaining rectangles can be packed in this bin
|
||||
|
||||
best_rect = self._sorted_rect[best_rect_key]
|
||||
del self._sorted_rect[best_rect_key]
|
||||
|
||||
PackerBNFMixin.add_rect(self, *best_rect)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Packer factory
|
||||
class Enum(tuple):
|
||||
__getattr__ = tuple.index
|
||||
|
||||
PackingMode = Enum(["Online", "Offline"])
|
||||
PackingBin = Enum(["BNF", "BFF", "BBF", "Global"])
|
||||
|
||||
|
||||
def newPacker(mode=PackingMode.Offline,
|
||||
bin_algo=PackingBin.BBF,
|
||||
pack_algo=MaxRectsBssf,
|
||||
sort_algo=SORT_AREA,
|
||||
rotation=True):
|
||||
"""
|
||||
Packer factory helper function
|
||||
|
||||
Arguments:
|
||||
mode (PackingMode): Packing mode
|
||||
Online: Rectangles are packed as soon are they are added
|
||||
Offline: Rectangles aren't packed untils pack() is called
|
||||
bin_algo (PackingBin): Bin selection heuristic
|
||||
pack_algo (PackingAlgorithm): Algorithm used
|
||||
rotation (boolean): Enable or disable rectangle rotation.
|
||||
|
||||
Returns:
|
||||
Packer: Initialized packer instance.
|
||||
"""
|
||||
packer_class = None
|
||||
|
||||
# Online Mode
|
||||
if mode == PackingMode.Online:
|
||||
sort_algo=None
|
||||
if bin_algo == PackingBin.BNF:
|
||||
packer_class = PackerOnlineBNF
|
||||
elif bin_algo == PackingBin.BFF:
|
||||
packer_class = PackerOnlineBFF
|
||||
elif bin_algo == PackingBin.BBF:
|
||||
packer_class = PackerOnlineBBF
|
||||
else:
|
||||
raise AttributeError("Unsupported bin selection heuristic")
|
||||
|
||||
# Offline Mode
|
||||
elif mode == PackingMode.Offline:
|
||||
if bin_algo == PackingBin.BNF:
|
||||
packer_class = PackerBNF
|
||||
elif bin_algo == PackingBin.BFF:
|
||||
packer_class = PackerBFF
|
||||
elif bin_algo == PackingBin.BBF:
|
||||
packer_class = PackerBBF
|
||||
elif bin_algo == PackingBin.Global:
|
||||
packer_class = PackerGlobal
|
||||
sort_algo=None
|
||||
else:
|
||||
raise AttributeError("Unsupported bin selection heuristic")
|
||||
|
||||
else:
|
||||
raise AttributeError("Unknown packing mode.")
|
||||
|
||||
if sort_algo:
|
||||
return packer_class(pack_algo=pack_algo, sort_algo=sort_algo,
|
||||
rotation=rotation)
|
||||
else:
|
||||
return packer_class(pack_algo=pack_algo, rotation=rotation)
|
||||
|
||||
|
303
blender/arm/lightmapper/utility/rectpack/skyline.py
Normal file
303
blender/arm/lightmapper/utility/rectpack/skyline.py
Normal file
|
@ -0,0 +1,303 @@
|
|||
import collections
|
||||
import itertools
|
||||
import operator
|
||||
import heapq
|
||||
import copy
|
||||
from .pack_algo import PackingAlgorithm
|
||||
from .geometry import Point as P
|
||||
from .geometry import HSegment, Rectangle
|
||||
from .waste import WasteManager
|
||||
|
||||
|
||||
class Skyline(PackingAlgorithm):
|
||||
""" Class implementing Skyline algorithm as described by
|
||||
Jukka Jylanki - A Thousand Ways to Pack the Bin (February 27, 2010)
|
||||
|
||||
_skyline: stores all the segments at the top of the skyline.
|
||||
_waste: Handles all wasted sections.
|
||||
"""
|
||||
|
||||
def __init__(self, width, height, rot=True, *args, **kwargs):
|
||||
"""
|
||||
_skyline is the list used to store all the skyline segments, each
|
||||
one is a list with the format [x, y, width] where x is the x
|
||||
coordinate of the left most point of the segment, y the y coordinate
|
||||
of the segment, and width the length of the segment. The initial
|
||||
segment is allways [0, 0, surface_width]
|
||||
|
||||
Arguments:
|
||||
width (int, float):
|
||||
height (int, float):
|
||||
rot (bool): Enable or disable rectangle rotation
|
||||
"""
|
||||
self._waste_management = False
|
||||
self._waste = WasteManager(rot=rot)
|
||||
super(Skyline, self).__init__(width, height, rot, merge=False, *args, **kwargs)
|
||||
|
||||
def _placement_points_generator(self, skyline, width):
|
||||
"""Returns a generator for the x coordinates of all the placement
|
||||
points on the skyline for a given rectangle.
|
||||
|
||||
WARNING: In some cases could be duplicated points, but it is faster
|
||||
to compute them twice than to remove them.
|
||||
|
||||
Arguments:
|
||||
skyline (list): Skyline HSegment list
|
||||
width (int, float): Rectangle width
|
||||
|
||||
Returns:
|
||||
generator
|
||||
"""
|
||||
skyline_r = skyline[-1].right
|
||||
skyline_l = skyline[0].left
|
||||
|
||||
# Placements using skyline segment left point
|
||||
ppointsl = (s.left for s in skyline if s.left+width <= skyline_r)
|
||||
|
||||
# Placements using skyline segment right point
|
||||
ppointsr = (s.right-width for s in skyline if s.right-width >= skyline_l)
|
||||
|
||||
# Merge positions
|
||||
return heapq.merge(ppointsl, ppointsr)
|
||||
|
||||
def _generate_placements(self, width, height):
|
||||
"""
|
||||
Generate a list with
|
||||
|
||||
Arguments:
|
||||
skyline (list): SkylineHSegment list
|
||||
width (number):
|
||||
|
||||
Returns:
|
||||
tuple (Rectangle, fitness):
|
||||
Rectangle: Rectangle in valid position
|
||||
left_skyline: Index for the skyline under the rectangle left edge.
|
||||
right_skyline: Index for the skyline under the rectangle right edte.
|
||||
"""
|
||||
skyline = self._skyline
|
||||
|
||||
points = collections.deque()
|
||||
|
||||
left_index = right_index = 0 # Left and right side skyline index
|
||||
support_height = skyline[0].top
|
||||
support_index = 0
|
||||
|
||||
placements = self._placement_points_generator(skyline, width)
|
||||
for p in placements:
|
||||
|
||||
# If Rectangle's right side changed segment, find new support
|
||||
if p+width > skyline[right_index].right:
|
||||
for right_index in range(right_index+1, len(skyline)):
|
||||
if skyline[right_index].top >= support_height:
|
||||
support_index = right_index
|
||||
support_height = skyline[right_index].top
|
||||
if p+width <= skyline[right_index].right:
|
||||
break
|
||||
|
||||
# If left side changed segment.
|
||||
if p >= skyline[left_index].right:
|
||||
left_index +=1
|
||||
|
||||
# Find new support if the previous one was shifted out.
|
||||
if support_index < left_index:
|
||||
support_index = left_index
|
||||
support_height = skyline[left_index].top
|
||||
for i in range(left_index, right_index+1):
|
||||
if skyline[i].top >= support_height:
|
||||
support_index = i
|
||||
support_height = skyline[i].top
|
||||
|
||||
# Add point if there is enought room at the top
|
||||
if support_height+height <= self.height:
|
||||
points.append((Rectangle(p, support_height, width, height),\
|
||||
left_index, right_index))
|
||||
|
||||
return points
|
||||
|
||||
def _merge_skyline(self, skylineq, segment):
|
||||
"""
|
||||
Arguments:
|
||||
skylineq (collections.deque):
|
||||
segment (HSegment):
|
||||
"""
|
||||
if len(skylineq) == 0:
|
||||
skylineq.append(segment)
|
||||
return
|
||||
|
||||
if skylineq[-1].top == segment.top:
|
||||
s = skylineq[-1]
|
||||
skylineq[-1] = HSegment(s.start, s.length+segment.length)
|
||||
else:
|
||||
skylineq.append(segment)
|
||||
|
||||
def _add_skyline(self, rect):
|
||||
"""
|
||||
Arguments:
|
||||
seg (Rectangle):
|
||||
"""
|
||||
skylineq = collections.deque([]) # Skyline after adding new one
|
||||
|
||||
for sky in self._skyline:
|
||||
if sky.right <= rect.left or sky.left >= rect.right:
|
||||
self._merge_skyline(skylineq, sky)
|
||||
continue
|
||||
|
||||
if sky.left < rect.left and sky.right > rect.left:
|
||||
# Skyline section partially under segment left
|
||||
self._merge_skyline(skylineq,
|
||||
HSegment(sky.start, rect.left-sky.left))
|
||||
sky = HSegment(P(rect.left, sky.top), sky.right-rect.left)
|
||||
|
||||
if sky.left < rect.right:
|
||||
if sky.left == rect.left:
|
||||
self._merge_skyline(skylineq,
|
||||
HSegment(P(rect.left, rect.top), rect.width))
|
||||
# Skyline section partially under segment right
|
||||
if sky.right > rect.right:
|
||||
self._merge_skyline(skylineq,
|
||||
HSegment(P(rect.right, sky.top), sky.right-rect.right))
|
||||
sky = HSegment(sky.start, rect.right-sky.left)
|
||||
|
||||
if sky.left >= rect.left and sky.right <= rect.right:
|
||||
# Skyline section fully under segment, account for wasted space
|
||||
if self._waste_management and sky.top < rect.bottom:
|
||||
self._waste.add_waste(sky.left, sky.top,
|
||||
sky.length, rect.bottom - sky.top)
|
||||
else:
|
||||
# Segment
|
||||
self._merge_skyline(skylineq, sky)
|
||||
|
||||
# Aaaaand ..... Done
|
||||
self._skyline = list(skylineq)
|
||||
|
||||
def _rect_fitness(self, rect, left_index, right_index):
|
||||
return rect.top
|
||||
|
||||
def _select_position(self, width, height):
|
||||
"""
|
||||
Search for the placement with the bes fitness for the rectangle.
|
||||
|
||||
Returns:
|
||||
tuple (Rectangle, fitness) - Rectangle placed in the fittest position
|
||||
None - Rectangle couldn't be placed
|
||||
"""
|
||||
positions = self._generate_placements(width, height)
|
||||
if self.rot and width != height:
|
||||
positions += self._generate_placements(height, width)
|
||||
if not positions:
|
||||
return None, None
|
||||
return min(((p[0], self._rect_fitness(*p))for p in positions),
|
||||
key=operator.itemgetter(1))
|
||||
|
||||
def fitness(self, width, height):
|
||||
"""Search for the best fitness
|
||||
"""
|
||||
assert(width > 0 and height >0)
|
||||
if width > max(self.width, self.height) or\
|
||||
height > max(self.height, self.width):
|
||||
return None
|
||||
|
||||
# If there is room in wasted space, FREE PACKING!!
|
||||
if self._waste_management:
|
||||
if self._waste.fitness(width, height) is not None:
|
||||
return 0
|
||||
|
||||
# Get best fitness segment, for normal rectangle, and for
|
||||
# rotated rectangle if rotation is enabled.
|
||||
rect, fitness = self._select_position(width, height)
|
||||
return fitness
|
||||
|
||||
def add_rect(self, width, height, rid=None):
|
||||
"""
|
||||
Add new rectangle
|
||||
"""
|
||||
assert(width > 0 and height > 0)
|
||||
if width > max(self.width, self.height) or\
|
||||
height > max(self.height, self.width):
|
||||
return None
|
||||
|
||||
rect = None
|
||||
# If Waste managment is enabled, first try to place the rectangle there
|
||||
if self._waste_management:
|
||||
rect = self._waste.add_rect(width, height, rid)
|
||||
|
||||
# Get best possible rectangle position
|
||||
if not rect:
|
||||
rect, _ = self._select_position(width, height)
|
||||
if rect:
|
||||
self._add_skyline(rect)
|
||||
|
||||
if rect is None:
|
||||
return None
|
||||
|
||||
# Store rectangle, and recalculate skyline
|
||||
rect.rid = rid
|
||||
self.rectangles.append(rect)
|
||||
return rect
|
||||
|
||||
def reset(self):
|
||||
super(Skyline, self).reset()
|
||||
self._skyline = [HSegment(P(0, 0), self.width)]
|
||||
self._waste.reset()
|
||||
|
||||
|
||||
|
||||
|
||||
class SkylineWMixin(Skyline):
|
||||
"""Waste managment mixin"""
|
||||
def __init__(self, width, height, *args, **kwargs):
|
||||
super(SkylineWMixin, self).__init__(width, height, *args, **kwargs)
|
||||
self._waste_management = True
|
||||
|
||||
|
||||
class SkylineMwf(Skyline):
|
||||
"""Implements Min Waste fit heuristic, minimizing the area wasted under the
|
||||
rectangle.
|
||||
"""
|
||||
def _rect_fitness(self, rect, left_index, right_index):
|
||||
waste = 0
|
||||
for seg in self._skyline[left_index:right_index+1]:
|
||||
waste +=\
|
||||
(min(rect.right, seg.right)-max(rect.left, seg.left)) *\
|
||||
(rect.bottom-seg.top)
|
||||
|
||||
return waste
|
||||
|
||||
def _rect_fitnes2s(self, rect, left_index, right_index):
|
||||
waste = ((min(rect.right, seg.right)-max(rect.left, seg.left)) for seg in self._skyline[left_index:right_index+1])
|
||||
return sum(waste)
|
||||
|
||||
class SkylineMwfl(Skyline):
|
||||
"""Implements Min Waste fit with low profile heuritic, minimizing the area
|
||||
wasted below the rectangle, at the same time it tries to keep the height
|
||||
minimal.
|
||||
"""
|
||||
def _rect_fitness(self, rect, left_index, right_index):
|
||||
waste = 0
|
||||
for seg in self._skyline[left_index:right_index+1]:
|
||||
waste +=\
|
||||
(min(rect.right, seg.right)-max(rect.left, seg.left)) *\
|
||||
(rect.bottom-seg.top)
|
||||
|
||||
return waste*self.width*self.height+rect.top
|
||||
|
||||
|
||||
class SkylineBl(Skyline):
|
||||
"""Implements Bottom Left heuristic, the best fit option is that which
|
||||
results in which the top side of the rectangle lies at the bottom-most
|
||||
position.
|
||||
"""
|
||||
def _rect_fitness(self, rect, left_index, right_index):
|
||||
return rect.top
|
||||
|
||||
|
||||
|
||||
|
||||
class SkylineBlWm(SkylineBl, SkylineWMixin):
|
||||
pass
|
||||
|
||||
class SkylineMwfWm(SkylineMwf, SkylineWMixin):
|
||||
pass
|
||||
|
||||
class SkylineMwflWm(SkylineMwfl, SkylineWMixin):
|
||||
pass
|
23
blender/arm/lightmapper/utility/rectpack/waste.py
Normal file
23
blender/arm/lightmapper/utility/rectpack/waste.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
from .guillotine import GuillotineBafMinas
|
||||
from .geometry import Rectangle
|
||||
|
||||
|
||||
|
||||
class WasteManager(GuillotineBafMinas):
|
||||
|
||||
def __init__(self, rot=True, merge=True, *args, **kwargs):
|
||||
super(WasteManager, self).__init__(1, 1, rot=rot, merge=merge, *args, **kwargs)
|
||||
|
||||
def add_waste(self, x, y, width, height):
|
||||
"""Add new waste section"""
|
||||
self._add_section(Rectangle(x, y, width, height))
|
||||
|
||||
def _fits_surface(self, width, height):
|
||||
raise NotImplementedError
|
||||
|
||||
def validate_packing(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def reset(self):
|
||||
super(WasteManager, self).reset()
|
||||
self._sections = []
|
|
@ -97,7 +97,8 @@ def get_file_size(filepath):
|
|||
size = os.path.getsize(path)
|
||||
size /= 1024
|
||||
except:
|
||||
print("error getting file path for " + filepath)
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("error getting file path for " + filepath)
|
||||
|
||||
return (size)
|
||||
|
||||
|
@ -144,7 +145,8 @@ def clean_empty_materials(self):
|
|||
for slot in obj.material_slots:
|
||||
mat = slot.material
|
||||
if mat is None:
|
||||
print("Removed Empty Materials from " + obj.name)
|
||||
if bpy.context.scene.TLM_SceneProperties.tlm_verbose:
|
||||
print("Removed Empty Materials from " + obj.name)
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
obj.select_set(True)
|
||||
bpy.ops.object.material_slot_remove()
|
||||
|
|
|
@ -60,6 +60,9 @@ class ARM_PT_ObjectPropsPanel(bpy.types.Panel):
|
|||
# Lightmapping props
|
||||
if obj.type == "MESH":
|
||||
row = layout.row(align=True)
|
||||
scene = bpy.context.scene
|
||||
if scene == None:
|
||||
return
|
||||
row.prop(obj.TLM_ObjectProperties, "tlm_mesh_lightmap_use")
|
||||
|
||||
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
|
||||
|
@ -69,8 +72,34 @@ class ARM_PT_ObjectPropsPanel(bpy.types.Panel):
|
|||
row = layout.row()
|
||||
row.prop(obj.TLM_ObjectProperties, "tlm_mesh_lightmap_unwrap_mode")
|
||||
row = layout.row()
|
||||
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroup":
|
||||
pass
|
||||
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA":
|
||||
|
||||
if scene.TLM_AtlasListItem >= 0 and len(scene.TLM_AtlasList) > 0:
|
||||
row = layout.row()
|
||||
item = scene.TLM_AtlasList[scene.TLM_AtlasListItem]
|
||||
row.prop_search(obj.TLM_ObjectProperties, "tlm_atlas_pointer", scene, "TLM_AtlasList", text='Atlas Group')
|
||||
row = layout.row()
|
||||
else:
|
||||
row = layout.label(text="Add Atlas Groups from the scene lightmapping settings.")
|
||||
row = layout.row()
|
||||
|
||||
else:
|
||||
row = layout.row()
|
||||
row.prop(obj.TLM_ObjectProperties, "tlm_postpack_object")
|
||||
row = layout.row()
|
||||
|
||||
|
||||
if obj.TLM_ObjectProperties.tlm_postpack_object and obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode != "AtlasGroupA":
|
||||
if scene.TLM_PostAtlasListItem >= 0 and len(scene.TLM_PostAtlasList) > 0:
|
||||
row = layout.row()
|
||||
item = scene.TLM_PostAtlasList[scene.TLM_PostAtlasListItem]
|
||||
row.prop_search(obj.TLM_ObjectProperties, "tlm_postatlas_pointer", scene, "TLM_PostAtlasList", text='Atlas Group')
|
||||
row = layout.row()
|
||||
|
||||
else:
|
||||
row = layout.label(text="Add Atlas Groups from the scene lightmapping settings.")
|
||||
row = layout.row()
|
||||
|
||||
row.prop(obj.TLM_ObjectProperties, "tlm_mesh_unwrap_margin")
|
||||
row = layout.row()
|
||||
row.prop(obj.TLM_ObjectProperties, "tlm_mesh_filter_override")
|
||||
|
@ -1428,6 +1457,29 @@ class ARM_PT_BakePanel(bpy.types.Panel):
|
|||
row = layout.row(align=True)
|
||||
row.prop(sceneProperties, "tlm_alert_on_finish")
|
||||
|
||||
if sceneProperties.tlm_alert_on_finish:
|
||||
row = layout.row(align=True)
|
||||
row.prop(sceneProperties, "tlm_alert_sound")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.prop(sceneProperties, "tlm_verbose")
|
||||
#row = layout.row(align=True)
|
||||
#row.prop(sceneProperties, "tlm_compile_statistics")
|
||||
row = layout.row(align=True)
|
||||
row.prop(sceneProperties, "tlm_override_bg_color")
|
||||
if sceneProperties.tlm_override_bg_color:
|
||||
row = layout.row(align=True)
|
||||
row.prop(sceneProperties, "tlm_override_color")
|
||||
row = layout.row(align=True)
|
||||
row.prop(sceneProperties, "tlm_reset_uv")
|
||||
|
||||
row = layout.row(align=True)
|
||||
try:
|
||||
if bpy.context.scene["TLM_Buildstat"] is not None:
|
||||
row.label(text="Last build completed in: " + str(bpy.context.scene["TLM_Buildstat"][0]))
|
||||
except:
|
||||
pass
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="Cycles Settings")
|
||||
|
||||
|
@ -1439,10 +1491,20 @@ class ARM_PT_BakePanel(bpy.types.Panel):
|
|||
row.prop(engineProperties, "tlm_resolution_scale")
|
||||
row = layout.row(align=True)
|
||||
row.prop(engineProperties, "tlm_bake_mode")
|
||||
row = layout.row(align=True)
|
||||
row.prop(engineProperties, "tlm_lighting_mode")
|
||||
|
||||
if scene.TLM_EngineProperties.tlm_bake_mode == "Background":
|
||||
row = layout.row(align=True)
|
||||
row.label(text="Warning! Background mode is currently unstable", icon_value=2)
|
||||
row = layout.row(align=True)
|
||||
row.prop(sceneProperties, "tlm_network_render")
|
||||
if sceneProperties.tlm_network_render:
|
||||
row = layout.row(align=True)
|
||||
row.prop(sceneProperties, "tlm_network_paths")
|
||||
#row = layout.row(align=True)
|
||||
#row.prop(sceneProperties, "tlm_network_dir")
|
||||
row = layout.row(align=True)
|
||||
row = layout.row(align=True)
|
||||
row.prop(engineProperties, "tlm_caching_mode")
|
||||
row = layout.row(align=True)
|
||||
|
@ -1455,6 +1517,8 @@ class ARM_PT_BakePanel(bpy.types.Panel):
|
|||
row.prop(engineProperties, "tlm_exposure_multiplier")
|
||||
row = layout.row(align=True)
|
||||
row.prop(engineProperties, "tlm_setting_supersample")
|
||||
row = layout.row(align=True)
|
||||
row.prop(sceneProperties, "tlm_metallic_clamp")
|
||||
|
||||
elif sceneProperties.tlm_lightmap_engine == "LuxCoreRender":
|
||||
|
||||
|
@ -1516,8 +1580,6 @@ class ARM_PT_BakePanel(bpy.types.Panel):
|
|||
row.prop(denoiseProperties, "tlm_optix_verbose")
|
||||
row = layout.row(align=True)
|
||||
row.prop(denoiseProperties, "tlm_optix_maxmem")
|
||||
row = layout.row(align=True)
|
||||
row.prop(denoiseProperties, "tlm_denoise_ao")
|
||||
|
||||
|
||||
##################
|
||||
|
@ -1581,52 +1643,44 @@ class ARM_PT_BakePanel(bpy.types.Panel):
|
|||
|
||||
if sceneProperties.tlm_encoding_use:
|
||||
|
||||
row.prop(sceneProperties, "tlm_encoding_mode", expand=True)
|
||||
if sceneProperties.tlm_encoding_mode == "RGBM" or sceneProperties.tlm_encoding_mode == "RGBD":
|
||||
|
||||
if scene.TLM_EngineProperties.tlm_bake_mode == "Background":
|
||||
row.label(text="Encoding options disabled in background mode")
|
||||
row = layout.row(align=True)
|
||||
row.prop(sceneProperties, "tlm_encoding_range")
|
||||
if sceneProperties.tlm_encoding_mode == "LogLuv":
|
||||
pass
|
||||
if sceneProperties.tlm_encoding_mode == "HDR":
|
||||
row = layout.row(align=True)
|
||||
row.prop(sceneProperties, "tlm_format")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.label(text="Encoding Settings")
|
||||
row.prop(sceneProperties, "tlm_encoding_device", expand=True)
|
||||
row = layout.row(align=True)
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("tlm.enable_selection")
|
||||
row = layout.row(align=True)
|
||||
row.operator("tlm.disable_selection")
|
||||
row = layout.row(align=True)
|
||||
row.prop(sceneProperties, "tlm_override_object_settings")
|
||||
if sceneProperties.tlm_encoding_device == "CPU":
|
||||
row.prop(sceneProperties, "tlm_encoding_mode_a", expand=True)
|
||||
else:
|
||||
row.prop(sceneProperties, "tlm_encoding_mode_b", expand=True)
|
||||
|
||||
if sceneProperties.tlm_override_object_settings:
|
||||
if sceneProperties.tlm_encoding_device == "CPU":
|
||||
if sceneProperties.tlm_encoding_mode_a == "RGBM":
|
||||
row = layout.row(align=True)
|
||||
row.prop(sceneProperties, "tlm_encoding_range")
|
||||
row = layout.row(align=True)
|
||||
row.prop(sceneProperties, "tlm_decoder_setup")
|
||||
if sceneProperties.tlm_encoding_mode_a == "RGBD":
|
||||
pass
|
||||
if sceneProperties.tlm_encoding_mode_a == "HDR":
|
||||
row = layout.row(align=True)
|
||||
row.prop(sceneProperties, "tlm_format")
|
||||
else:
|
||||
|
||||
row = layout.row(align=True)
|
||||
row = layout.row()
|
||||
row.prop(sceneProperties, "tlm_mesh_lightmap_unwrap_mode")
|
||||
row = layout.row()
|
||||
if sceneProperties.tlm_encoding_mode_b == "RGBM":
|
||||
row = layout.row(align=True)
|
||||
row.prop(sceneProperties, "tlm_encoding_range")
|
||||
row = layout.row(align=True)
|
||||
row.prop(sceneProperties, "tlm_decoder_setup")
|
||||
|
||||
if sceneProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroup":
|
||||
|
||||
if scene.TLM_AtlasList_index >= 0 and len(scene.TLM_AtlasList) > 0:
|
||||
row = layout.row()
|
||||
item = scene.TLM_AtlasList[scene.TLM_AtlasList_index]
|
||||
row.prop_search(sceneProperties, "tlm_atlas_pointer", scene, "TLM_AtlasList", text='Atlas Group')
|
||||
else:
|
||||
row = layout.label(text="Add Atlas Groups from the scene lightmapping settings.")
|
||||
|
||||
else:
|
||||
|
||||
row.prop(sceneProperties, "tlm_mesh_lightmap_resolution")
|
||||
row = layout.row()
|
||||
row.prop(sceneProperties, "tlm_mesh_unwrap_margin")
|
||||
|
||||
row = layout.row(align=True)
|
||||
row.operator("tlm.remove_uv_selection")
|
||||
row = layout.row(align=True)
|
||||
if sceneProperties.tlm_encoding_mode_b == "LogLuv" and sceneProperties.tlm_encoding_device == "GPU":
|
||||
row = layout.row(align=True)
|
||||
row.prop(sceneProperties, "tlm_decoder_setup")
|
||||
if sceneProperties.tlm_encoding_mode_b == "HDR":
|
||||
row = layout.row(align=True)
|
||||
row.prop(sceneProperties, "tlm_format")
|
||||
|
||||
##################
|
||||
#SELECTION OPERATORS!
|
||||
|
@ -1649,17 +1703,32 @@ class ARM_PT_BakePanel(bpy.types.Panel):
|
|||
row.prop(sceneProperties, "tlm_mesh_lightmap_unwrap_mode")
|
||||
row = layout.row()
|
||||
|
||||
if sceneProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroup":
|
||||
if sceneProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA":
|
||||
|
||||
if scene.TLM_AtlasList_index >= 0 and len(scene.TLM_AtlasList) > 0:
|
||||
if scene.TLM_AtlasListItem >= 0 and len(scene.TLM_AtlasList) > 0:
|
||||
row = layout.row()
|
||||
item = scene.TLM_AtlasList[scene.TLM_AtlasList_index]
|
||||
item = scene.TLM_AtlasList[scene.TLM_AtlasListItem]
|
||||
row.prop_search(sceneProperties, "tlm_atlas_pointer", scene, "TLM_AtlasList", text='Atlas Group')
|
||||
else:
|
||||
row = layout.label(text="Add Atlas Groups from the scene lightmapping settings.")
|
||||
|
||||
else:
|
||||
row = layout.row()
|
||||
row.prop(sceneProperties, "tlm_postpack_object")
|
||||
row = layout.row()
|
||||
|
||||
if sceneProperties.tlm_postpack_object and sceneProperties.tlm_mesh_lightmap_unwrap_mode != "AtlasGroupA":
|
||||
if scene.TLM_PostAtlasListItem >= 0 and len(scene.TLM_PostAtlasList) > 0:
|
||||
row = layout.row()
|
||||
item = scene.TLM_PostAtlasList[scene.TLM_PostAtlasListItem]
|
||||
row.prop_search(sceneProperties, "tlm_postatlas_pointer", scene, "TLM_PostAtlasList", text='Atlas Group')
|
||||
row = layout.row()
|
||||
|
||||
else:
|
||||
row = layout.label(text="Add Atlas Groups from the scene lightmapping settings.")
|
||||
row = layout.row()
|
||||
|
||||
if sceneProperties.tlm_mesh_lightmap_unwrap_mode != "AtlasGroupA":
|
||||
row.prop(sceneProperties, "tlm_mesh_lightmap_resolution")
|
||||
row = layout.row()
|
||||
row.prop(sceneProperties, "tlm_mesh_unwrap_margin")
|
||||
|
@ -1667,6 +1736,112 @@ class ARM_PT_BakePanel(bpy.types.Panel):
|
|||
row = layout.row(align=True)
|
||||
row.operator("tlm.remove_uv_selection")
|
||||
row = layout.row(align=True)
|
||||
row.operator("tlm.select_lightmapped_objects")
|
||||
row = layout.row(align=True)
|
||||
|
||||
##################
|
||||
#Additional settings
|
||||
row = layout.row(align=True)
|
||||
row.label(text="Additional options")
|
||||
sceneProperties = scene.TLM_SceneProperties
|
||||
atlasListItem = scene.TLM_AtlasListItem
|
||||
atlasList = scene.TLM_AtlasList
|
||||
postatlasListItem = scene.TLM_PostAtlasListItem
|
||||
postatlasList = scene.TLM_PostAtlasList
|
||||
|
||||
layout.label(text="Atlas Groups")
|
||||
row = layout.row()
|
||||
row.prop(sceneProperties, "tlm_atlas_mode", expand=True)
|
||||
|
||||
if sceneProperties.tlm_atlas_mode == "Prepack":
|
||||
|
||||
rows = 2
|
||||
if len(atlasList) > 1:
|
||||
rows = 4
|
||||
row = layout.row()
|
||||
row.template_list("TLM_UL_AtlasList", "Atlas List", scene, "TLM_AtlasList", scene, "TLM_AtlasListItem", rows=rows)
|
||||
col = row.column(align=True)
|
||||
col.operator("tlm_atlaslist.new_item", icon='ADD', text="")
|
||||
col.operator("tlm_atlaslist.delete_item", icon='REMOVE', text="")
|
||||
#col.menu("ARM_MT_BakeListSpecials", icon='DOWNARROW_HLT', text="")
|
||||
|
||||
# if len(scene.TLM_AtlasList) > 1:
|
||||
# col.separator()
|
||||
# op = col.operator("arm_bakelist.move_item", icon='TRIA_UP', text="")
|
||||
# op.direction = 'UP'
|
||||
# op = col.operator("arm_bakelist.move_item", icon='TRIA_DOWN', text="")
|
||||
# op.direction = 'DOWN'
|
||||
|
||||
if atlasListItem >= 0 and len(atlasList) > 0:
|
||||
item = atlasList[atlasListItem]
|
||||
#layout.prop_search(item, "obj", bpy.data, "objects", text="Object")
|
||||
#layout.prop(item, "res_x")
|
||||
layout.prop(item, "tlm_atlas_lightmap_unwrap_mode")
|
||||
layout.prop(item, "tlm_atlas_lightmap_resolution")
|
||||
layout.prop(item, "tlm_atlas_unwrap_margin")
|
||||
|
||||
amount = 0
|
||||
|
||||
for obj in bpy.data.objects:
|
||||
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
|
||||
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_unwrap_mode == "AtlasGroupA":
|
||||
if obj.TLM_ObjectProperties.tlm_atlas_pointer == item.name:
|
||||
amount = amount + 1
|
||||
|
||||
layout.label(text="Objects: " + str(amount))
|
||||
|
||||
# layout.use_property_split = True
|
||||
# layout.use_property_decorate = False
|
||||
# layout.label(text="Enable for selection")
|
||||
# layout.label(text="Disable for selection")
|
||||
# layout.label(text="Something...")
|
||||
|
||||
else:
|
||||
|
||||
layout.label(text="Postpacking is unstable.")
|
||||
rows = 2
|
||||
if len(atlasList) > 1:
|
||||
rows = 4
|
||||
row = layout.row()
|
||||
row.template_list("TLM_UL_PostAtlasList", "PostList", scene, "TLM_PostAtlasList", scene, "TLM_PostAtlasListItem", rows=rows)
|
||||
col = row.column(align=True)
|
||||
col.operator("tlm_postatlaslist.new_item", icon='ADD', text="")
|
||||
col.operator("tlm_postatlaslist.delete_item", icon='REMOVE', text="")
|
||||
|
||||
if postatlasListItem >= 0 and len(postatlasList) > 0:
|
||||
item = postatlasList[postatlasListItem]
|
||||
layout.prop(item, "tlm_atlas_lightmap_resolution")
|
||||
|
||||
#Below list object counter
|
||||
amount = 0
|
||||
utilized = 0
|
||||
atlasUsedArea = 0
|
||||
atlasSize = item.tlm_atlas_lightmap_resolution
|
||||
|
||||
for obj in bpy.data.objects:
|
||||
if obj.TLM_ObjectProperties.tlm_mesh_lightmap_use:
|
||||
if obj.TLM_ObjectProperties.tlm_postpack_object:
|
||||
if obj.TLM_ObjectProperties.tlm_postatlas_pointer == item.name:
|
||||
amount = amount + 1
|
||||
|
||||
atlasUsedArea += int(obj.TLM_ObjectProperties.tlm_mesh_lightmap_resolution) ** 2
|
||||
|
||||
row = layout.row()
|
||||
row.prop(item, "tlm_atlas_repack_on_cleanup")
|
||||
|
||||
#TODO SET A CHECK FOR THIS! ADD A CV2 CHECK TO UTILITY!
|
||||
cv2 = True
|
||||
|
||||
if cv2:
|
||||
row = layout.row()
|
||||
row.prop(item, "tlm_atlas_dilation")
|
||||
layout.label(text="Objects: " + str(amount))
|
||||
|
||||
utilized = atlasUsedArea / (int(atlasSize) ** 2)
|
||||
layout.label(text="Utilized: " + str(utilized * 100) + "%")
|
||||
|
||||
if (utilized * 100) > 100:
|
||||
layout.label(text="Warning! Overflow not yet supported")
|
||||
|
||||
class ArmGenLodButton(bpy.types.Operator):
|
||||
'''Automatically generate LoD levels'''
|
||||
|
|
Loading…
Reference in a new issue