light_field_tools: move to contrib: T63750
This commit is contained in:
parent
5b66741510
commit
0aacb34c82
|
@ -1,182 +0,0 @@
|
|||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
|
||||
bl_info = {
|
||||
"name": "Light Field Tools",
|
||||
"author": "Aurel Wildfellner",
|
||||
"description": "Tools to create a light field camera and projector",
|
||||
"version": (0, 3, 1),
|
||||
"blender": (2, 64, 0),
|
||||
"location": "View3D > Tool Shelf > Light Field Tools",
|
||||
"url": "http://www.jku.at/cg/",
|
||||
"wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/"
|
||||
"Scripts/Render/Light_Field_Tools",
|
||||
"category": "Render"
|
||||
}
|
||||
|
||||
|
||||
if "bpy" in locals():
|
||||
import importlib
|
||||
importlib.reload(light_field_tools)
|
||||
else:
|
||||
from . import light_field_tools
|
||||
|
||||
|
||||
import bpy
|
||||
from bpy.types import (
|
||||
AddonPreferences,
|
||||
PropertyGroup,
|
||||
)
|
||||
from bpy.props import (
|
||||
BoolProperty,
|
||||
FloatProperty,
|
||||
IntProperty,
|
||||
StringProperty,
|
||||
PointerProperty,
|
||||
)
|
||||
|
||||
|
||||
# global properties for the script, mainly for UI
|
||||
class LightFieldPropertyGroup(PropertyGroup):
|
||||
angle: FloatProperty(
|
||||
name="Angle",
|
||||
# 40 degrees
|
||||
default=0.69813170079,
|
||||
min=0,
|
||||
# 172 degrees
|
||||
max=3.001966313430247,
|
||||
precision=2,
|
||||
subtype='ANGLE',
|
||||
description="Field of view of camera and angle of beam for spotlights"
|
||||
)
|
||||
row_length: IntProperty(
|
||||
name="Row Length",
|
||||
default=1,
|
||||
min=1,
|
||||
description="The number of cameras/lights in one row"
|
||||
)
|
||||
create_handler: BoolProperty(
|
||||
name="Handler",
|
||||
default=True,
|
||||
description="Creates an empty object, to which the cameras and spotlights are parented to"
|
||||
)
|
||||
do_camera: BoolProperty(
|
||||
name="Create Camera",
|
||||
default=True,
|
||||
description="A light field camera is created"
|
||||
)
|
||||
animate_camera: BoolProperty(
|
||||
name="Animate Camera",
|
||||
default=True,
|
||||
description="Animates a single camera, so not multiple cameras get created"
|
||||
)
|
||||
do_projection: BoolProperty(
|
||||
name="Create Projector",
|
||||
default=False,
|
||||
description="A light field projector is created"
|
||||
)
|
||||
texture_path: StringProperty(
|
||||
name="Texture Path",
|
||||
description="From this path textures for the spotlights will be loaded",
|
||||
subtype='DIR_PATH'
|
||||
)
|
||||
light_intensity: FloatProperty(
|
||||
name="Light Intensity",
|
||||
default=2,
|
||||
min=0,
|
||||
precision=3,
|
||||
description="Total intensity of all lamps"
|
||||
)
|
||||
# blending of the spotlights
|
||||
spot_blend: FloatProperty(
|
||||
name="Blend",
|
||||
default=0,
|
||||
min=0,
|
||||
max=1,
|
||||
precision=3,
|
||||
description="Blending of the spotlights"
|
||||
)
|
||||
# spacing in pixels on the focal plane
|
||||
spacing: IntProperty(
|
||||
name="Spacing",
|
||||
default=10,
|
||||
min=0,
|
||||
description="The spacing in pixels between two cameras on the focal plane"
|
||||
)
|
||||
|
||||
|
||||
# Add-ons Preferences Update Panel
|
||||
|
||||
# Define Panel classes for updating
|
||||
panels = (
|
||||
light_field_tools.VIEW3D_PT_lightfield_tools,
|
||||
)
|
||||
|
||||
|
||||
def update_panel(self, context):
|
||||
message = "Light Field Tools: Updating Panel locations has failed"
|
||||
try:
|
||||
for panel in panels:
|
||||
if "bl_rna" in panel.__dict__:
|
||||
bpy.utils.unregister_class(panel)
|
||||
|
||||
for panel in panels:
|
||||
panel.bl_category = context.preferences.addons[__name__].preferences.category
|
||||
bpy.utils.register_class(panel)
|
||||
|
||||
except Exception as e:
|
||||
print("\n[{}]\n{}\n\nError:\n{}".format(__name__, message, e))
|
||||
pass
|
||||
|
||||
|
||||
class LFTPreferences(AddonPreferences):
|
||||
# this must match the addon name, use '__package__'
|
||||
# when defining this in a submodule of a python package.
|
||||
bl_idname = __name__
|
||||
|
||||
category: StringProperty(
|
||||
name="Tab Category",
|
||||
description="Choose a name for the category of the panel",
|
||||
default="Tools",
|
||||
update=update_panel
|
||||
)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
row = layout.row()
|
||||
col = row.column()
|
||||
col.label(text="Tab Category:")
|
||||
col.prop(self, "category", text="")
|
||||
|
||||
|
||||
def register():
|
||||
# register properties
|
||||
# bpy.utils.register_class(LightFieldPropertyGroup)
|
||||
bpy.utils.register_module(__name__)
|
||||
bpy.types.Scene.lightfield = PointerProperty(type=LightFieldPropertyGroup)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_module(__name__)
|
||||
del bpy.types.Scene.lightfield
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
|
@ -1,421 +0,0 @@
|
|||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
import bpy
|
||||
from bpy.types import (
|
||||
Operator,
|
||||
Panel,
|
||||
)
|
||||
import os
|
||||
from math import (
|
||||
degrees, tan,
|
||||
radians,
|
||||
)
|
||||
from mathutils import Vector
|
||||
|
||||
__bpydoc__ = """
|
||||
Light Field Tools
|
||||
|
||||
This script helps setting up rendering of lightfields. It
|
||||
also supports the projection of lightfields with textured
|
||||
spotlights.
|
||||
|
||||
Usage:
|
||||
A simple interface can be accessed in the tool shelf panel
|
||||
in 3D View ([T] Key).
|
||||
|
||||
A base mesh has to be provided, which will normally be a
|
||||
subdivided plane. The script will then create a camera rig
|
||||
and a light rig with adjustable properties. A sample camera
|
||||
and a spotlight will be created on each vertex of the
|
||||
basemesh object axis (maybe vertex normal in future
|
||||
versions).
|
||||
|
||||
Vertex order:
|
||||
The user has to provide the number of cameras or
|
||||
lights in one row in an unevenly spaced grid, the
|
||||
basemesh. Then the right vertex order can be
|
||||
computed as shown here.
|
||||
6-7-8
|
||||
| | |
|
||||
^ 3-4-5
|
||||
| | | |
|
||||
y 0-1-2
|
||||
x->
|
||||
|
||||
There is also a tool to create a basemesh, which is an
|
||||
evenly spaced grid. The row length parameter is taken to
|
||||
construct such a NxN grid. Someone would start out by adding
|
||||
a rectengular plane as the slice plane of the frustrum of
|
||||
the most middle camera of the light field rig. The spacing
|
||||
parameter then places the other cameras in a way, so they
|
||||
have an offset of n pixels from the other camera on this
|
||||
plane.
|
||||
|
||||
|
||||
Version history:
|
||||
v0.3.0 - Make compatible with 2.64
|
||||
v0.2.1 - Empty handler, multiple camera grid, r34843
|
||||
v0.2.0 - To be included in contrib, r34456
|
||||
v0.1.4 - To work with r34261
|
||||
v0.1.3 - Fixed base mesh creation for r29998
|
||||
v0.1.2 - Minor fixes, working with r29994
|
||||
v0.1.1 - Basemesh from focal plane.
|
||||
v0.1.0 - API updates, draft done.
|
||||
v0.0.4 - Texturing.
|
||||
v0.0.3 - Creates an array of non textured spotlights.
|
||||
v0.0.2 - Renders lightfields.
|
||||
v0.0.1 - Initial version.
|
||||
|
||||
TODO:
|
||||
* Restore view after primary camera is changed.
|
||||
* Apply object matrix to normals.
|
||||
* Align to normals, somehow,....
|
||||
* StringProperties with PATH tag, for proper ui.
|
||||
"""
|
||||
|
||||
|
||||
class OBJECT_OT_create_lightfield_rig(Operator):
|
||||
bl_idname = "object.create_lightfield_rig"
|
||||
bl_label = "Create a light field rig"
|
||||
bl_description = "Create a lightfield rig based on the active object/mesh"
|
||||
bl_options = {'REGISTER'}
|
||||
|
||||
layer0 = [True] + [False] * 19
|
||||
|
||||
numSamples = 0
|
||||
baseObject = None
|
||||
handler = None
|
||||
verts = []
|
||||
imagePaths = []
|
||||
|
||||
def arrangeVerts(self):
|
||||
"""Sorts the vertices as described in the usage part of the doc."""
|
||||
# FIXME get mesh with applied modifer stack
|
||||
scene = bpy.context.scene
|
||||
mesh = self.baseObject.data
|
||||
verts = []
|
||||
row_length = scene.lightfield.row_length
|
||||
matrix = self.baseObject.matrix_local.copy()
|
||||
for vert in mesh.vertices:
|
||||
# world/parent origin
|
||||
# ???, normal and co are in different spaces, sure you want this?
|
||||
co = matrix * vert.co
|
||||
normal = vert.normal
|
||||
verts.append([co, normal])
|
||||
|
||||
def key_x(v):
|
||||
return v[0][0]
|
||||
|
||||
def key_y(v):
|
||||
return v[0][1]
|
||||
|
||||
verts.sort(key=key_y)
|
||||
sorted_verts = []
|
||||
for i in range(0, len(verts), row_length):
|
||||
row = verts[i: i + row_length]
|
||||
row.sort(key=key_x)
|
||||
sorted_verts.extend(row)
|
||||
|
||||
return sorted_verts
|
||||
|
||||
def createCameraAnimated(self):
|
||||
scene = bpy.context.scene
|
||||
|
||||
bpy.ops.object.camera_add(align='WORLD')
|
||||
cam = bpy.context.active_object
|
||||
cam.name = "light_field_camera"
|
||||
|
||||
# set props
|
||||
cam.data.angle = scene.lightfield.angle
|
||||
|
||||
# display options of the camera
|
||||
cam.data.lens_unit = 'FOV'
|
||||
|
||||
# handler parent
|
||||
if scene.lightfield.create_handler:
|
||||
cam.parent = self.handler
|
||||
|
||||
# set as primary camera
|
||||
scene.camera = cam
|
||||
|
||||
# animate
|
||||
scene.frame_current = 0
|
||||
|
||||
for frame, vert in enumerate(self.verts):
|
||||
scene.frame_current = frame
|
||||
# translate
|
||||
cam.location = vert[0]
|
||||
# rotation
|
||||
cam.rotation_euler = self.baseObject.rotation_euler
|
||||
# insert LocRot keyframes
|
||||
cam.keyframe_insert('location')
|
||||
|
||||
# set anim render props
|
||||
scene.frame_current = 0
|
||||
scene.frame_start = 0
|
||||
scene.frame_end = self.numSamples - 1
|
||||
|
||||
def createCameraMultiple(self):
|
||||
scene = bpy.context.scene
|
||||
|
||||
for cam_idx, vert in enumerate(self.verts):
|
||||
# add and name camera
|
||||
bpy.ops.object.camera_add(align='WORLD')
|
||||
cam = bpy.context.active_object
|
||||
cam.name = "light_field_cam_" + str(cam_idx)
|
||||
|
||||
# translate
|
||||
cam.location = vert[0]
|
||||
# rotation
|
||||
cam.rotation_euler = self.baseObject.rotation_euler
|
||||
|
||||
# set camera props
|
||||
cam.data.angle = scene.lightfield.angle
|
||||
|
||||
# display options of the camera
|
||||
cam.data.display_size = 0.15
|
||||
cam.data.lens_unit = 'FOV'
|
||||
|
||||
# handler parent
|
||||
if scene.lightfield.create_handler:
|
||||
cam.parent = self.handler
|
||||
|
||||
def createCamera(self):
|
||||
if bpy.context.scene.lightfield.animate_camera:
|
||||
self.createCameraAnimated()
|
||||
else:
|
||||
self.createCameraMultiple()
|
||||
|
||||
def getImagePaths(self):
|
||||
path = bpy.context.scene.lightfield.texture_path
|
||||
if not os.path.isdir(path):
|
||||
return False
|
||||
files = os.listdir(path)
|
||||
if not len(files) == self.numSamples:
|
||||
return False
|
||||
files.sort()
|
||||
self.imagePaths = list(map(lambda f: os.path.join(path, f), files))
|
||||
return True
|
||||
|
||||
def createTexture(self, index):
|
||||
name = "light_field_spot_tex_" + str(index)
|
||||
tex = bpy.data.textures.new(name, type='IMAGE')
|
||||
|
||||
# load and set the image
|
||||
# FIXME width, height. not necessary to set in the past.
|
||||
img = bpy.data.images.new("lfe_str_" + str(index), width=5, height=5)
|
||||
img.filepath = self.imagePaths[index]
|
||||
img.source = 'FILE'
|
||||
tex.image = img
|
||||
|
||||
return tex
|
||||
|
||||
def createSpot(self, index, textured=False):
|
||||
scene = bpy.context.scene
|
||||
bpy.ops.object.light_add(
|
||||
type='SPOT')
|
||||
spot = bpy.context.active_object
|
||||
|
||||
# set object props
|
||||
spot.name = "light_field_spot_" + str(index)
|
||||
|
||||
# set constants
|
||||
spot.data.use_square = True
|
||||
spot.data.shadow_method = "RAY_SHADOW"
|
||||
# FIXME
|
||||
spot.data.distance = 10
|
||||
|
||||
# set spot props
|
||||
spot.data.energy = scene.lightfield.light_intensity / self.numSamples
|
||||
spot.data.spot_size = scene.lightfield.angle
|
||||
spot.data.spot_blend = scene.lightfield.spot_blend
|
||||
|
||||
# add texture
|
||||
if textured:
|
||||
spot.data.active_texture = self.createTexture(index)
|
||||
# texture mapping
|
||||
spot.data.texture_slots[0].texture_coords = 'VIEW'
|
||||
|
||||
# handler parent
|
||||
if scene.lightfield.create_handler:
|
||||
spot.parent = self.handler
|
||||
|
||||
return spot
|
||||
|
||||
def createLightfieldEmitter(self, textured=False):
|
||||
for i, vert in enumerate(self.verts):
|
||||
spot = self.createSpot(i, textured)
|
||||
spot.location = vert[0]
|
||||
spot.rotation_euler = self.baseObject.rotation_euler
|
||||
|
||||
def execute(self, context):
|
||||
scene = context.scene
|
||||
|
||||
obj = self.baseObject = context.active_object
|
||||
if not obj or obj.type != 'MESH':
|
||||
self.report({'ERROR'}, "No selected mesh object!")
|
||||
return {'CANCELLED'}
|
||||
|
||||
self.verts = self.arrangeVerts()
|
||||
self.numSamples = len(self.verts)
|
||||
|
||||
if scene.lightfield.create_handler:
|
||||
# create an empty
|
||||
bpy.ops.object.add(type='EMPTY')
|
||||
empty = bpy.context.active_object
|
||||
empty.location = self.baseObject.location
|
||||
empty.name = "light_field_handler"
|
||||
empty.rotation_euler = self.baseObject.rotation_euler
|
||||
self.handler = empty
|
||||
|
||||
if scene.lightfield.do_camera:
|
||||
self.createCamera()
|
||||
|
||||
if scene.lightfield.do_projection:
|
||||
if self.getImagePaths():
|
||||
self.createLightfieldEmitter(textured=True)
|
||||
else:
|
||||
self.createLightfieldEmitter(textured=False)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class OBJECT_OT_create_lightfield_basemesh(Operator):
|
||||
bl_idname = "object.create_lightfield_basemesh"
|
||||
bl_label = "Create a basemesh from the selected focal plane"
|
||||
bl_description = "Creates a basemesh from the selected focal plane"
|
||||
bl_options = {'REGISTER'}
|
||||
|
||||
objName = "lf_basemesh"
|
||||
|
||||
def getWidth(self, obj):
|
||||
mat = obj.matrix_local
|
||||
mesh = obj.data
|
||||
v0 = mat * mesh.vertices[mesh.edges[0].vertices[0]].co
|
||||
v1 = mat * mesh.vertices[mesh.edges[0].vertices[1]].co
|
||||
return (v0 - v1).length
|
||||
|
||||
def getCamVec(self, obj, angle):
|
||||
width = self.getWidth(obj)
|
||||
itmat = obj.matrix_local.inverted().transposed()
|
||||
normal = itmat * obj.data.polygons[0].normal.normalized()
|
||||
vl = (width / 2) * (1 / tan(radians(angle / 2)))
|
||||
return normal * vl
|
||||
|
||||
def addMeshObj(self, mesh):
|
||||
collection = bpy.context.collection
|
||||
scene = bpy.context.scene
|
||||
view_layer = bpy.context.view_layer
|
||||
|
||||
for o in scene.objects:
|
||||
o.select_set(False)
|
||||
|
||||
mesh.update()
|
||||
nobj = bpy.data.objects.new(self.objName, mesh)
|
||||
collection.objects.link(nobj)
|
||||
nobj.select_set(True)
|
||||
|
||||
if view_layer.objects.active is None or view_layer.objects.active.mode == 'OBJECT':
|
||||
view_layer.objects.active = nobj
|
||||
|
||||
def execute(self, context):
|
||||
scene = context.scene
|
||||
obj = context.active_object
|
||||
# check if active object is a mesh object
|
||||
if not obj or obj.type != 'MESH':
|
||||
self.report({'ERROR'}, "No selected mesh object!")
|
||||
return {'CANCELLED'}
|
||||
|
||||
# check if it has one single face
|
||||
if len(obj.data.polygons) != 1:
|
||||
self.report({'ERROR'}, "The selected mesh object has to have exactly one quad!")
|
||||
return {'CANCELLED'}
|
||||
|
||||
rl = scene.lightfield.row_length
|
||||
# use a degree angle here
|
||||
angle = degrees(scene.lightfield.angle)
|
||||
spacing = scene.lightfield.spacing
|
||||
# resolution of final renderings
|
||||
res = round(scene.render.resolution_x * (scene.render.resolution_percentage / 100.))
|
||||
width = self.getWidth(obj)
|
||||
|
||||
# the offset between n pixels on the focal plane
|
||||
fplane_offset = (width / res) * spacing
|
||||
|
||||
# vertices for the basemesh
|
||||
verts = []
|
||||
# the offset vector
|
||||
vec = self.getCamVec(obj, angle)
|
||||
# lower left coordinates of the grid
|
||||
sx = obj.location[0] - fplane_offset * int(rl / 2)
|
||||
sy = obj.location[1] - fplane_offset * int(rl / 2)
|
||||
z = obj.location[2]
|
||||
# position on the focal plane
|
||||
fplane_pos = Vector()
|
||||
for x in [sx + fplane_offset * i for i in range(rl)]:
|
||||
for y in [sy + fplane_offset * i for i in range(rl)]:
|
||||
fplane_pos.x = x
|
||||
fplane_pos.y = y
|
||||
fplane_pos.z = z
|
||||
# position of a vertex in a basemesh
|
||||
pos = fplane_pos + vec
|
||||
# pack coordinates flat into the vert list
|
||||
verts.append((pos.x, pos.y, pos.z))
|
||||
|
||||
# setup the basemesh and add verts
|
||||
mesh = bpy.data.meshes.new(self.objName)
|
||||
mesh.from_pydata(verts, [], [])
|
||||
self.addMeshObj(mesh)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class VIEW3D_PT_lightfield_tools(Panel):
|
||||
bl_space_type = "VIEW_3D"
|
||||
bl_region_type = "TOOLS"
|
||||
bl_context = "objectmode"
|
||||
bl_label = "Light Field Tools"
|
||||
bl_category = "Tools"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
scene = context.scene
|
||||
|
||||
col = layout.column()
|
||||
col.prop(scene.lightfield, "row_length")
|
||||
col.prop(scene.lightfield, "angle")
|
||||
|
||||
col.prop(scene.lightfield, "create_handler")
|
||||
|
||||
col.prop(scene.lightfield, "do_camera")
|
||||
col.prop(scene.lightfield, "animate_camera")
|
||||
col.prop(scene.lightfield, "do_projection")
|
||||
|
||||
col = layout.column(align=True)
|
||||
col.enabled = scene.lightfield.do_projection
|
||||
col.prop(scene.lightfield, "texture_path")
|
||||
col.prop(scene.lightfield, "light_intensity")
|
||||
col.prop(scene.lightfield, "spot_blend")
|
||||
|
||||
# create a basemesh
|
||||
col = layout.column(align=True)
|
||||
col.operator("object.create_lightfield_basemesh", text="Create Base Grid")
|
||||
col.prop(scene.lightfield, "spacing")
|
||||
|
||||
layout.operator("object.create_lightfield_rig", text="Create Rig")
|
Loading…
Reference in New Issue