Files
blender-portable-repo/scripts/addons/cc_blender_tools-2_3_3/rlx.py
T
2026-03-17 15:25:32 -06:00

856 lines
33 KiB
Python

# Copyright (C) 2021 Victor Soupday
# This file is part of CC/iC Blender Tools <https://github.com/soupday/cc_blender_tools>
#
# CC/iC Blender Tools is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# CC/iC Blender Tools 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 CC/iC Blender Tools. If not, see <https://www.gnu.org/licenses/>.
import bpy, struct, json, os
from mathutils import Vector, Matrix, Color, Quaternion
from enum import IntEnum
from . import utils, rigutils, nodeutils, imageutils
class RLXCodes(IntEnum):
RLX_ID_LIGHT = 0xCC01
RLX_ID_CAMERA = 0xCC02
RECTANGULAR_AS_AREA = False
TUBE_AS_AREA = True
ENERGY_SCALE = 35 * 0.7
SUN_SCALE = 2 * 0.7
class BinaryData():
data: bytearray = None
offset: int = 0
def __init__(self, data: bytearray = None, start_offset = 0,
file_path: str = None, file = None):
if data:
self.data = data
elif file_path:
with open(file_path, 'rb') as read_file:
self.data = bytearray(read_file.read())
elif file:
self.data = bytearray(file.read())
self.offset = start_offset
def json(self):
size = self.int()
data = self.bytes(size)
text = data.decode("utf-8")
obj = json.loads(text)
return obj
def float(self):
value = struct.unpack_from("!f", self.data, self.offset)[0]
self.offset += 4
return value
def int(self):
value = struct.unpack_from("!I", self.data, self.offset)[0]
self.offset += 4
return value
def bool(self):
value = struct.unpack_from("!?", self.data, self.offset)[0]
self.offset += 1
return value
def string(self):
length = self.int()
data = self.bytes(length)
value = data.decode(encoding="utf-8")
return value
def time(self):
time_code = self.int()
return float(time_code) / 6000.0
def vector(self):
x = self.float()
y = self.float()
z = self.float()
value = Vector((x, y, z))
return value
def quaternion(self):
x = self.float()
y = self.float()
z = self.float()
w = self.float()
value = Quaternion((w, x, y, z))
return value
def color(self):
r = self.float()
g = self.float()
b = self.float()
value = Color((r, g, b))
return value
def bytes(self, size):
sub_data = self.data[self.offset:self.offset+size]
self.offset += size
return sub_data
def block(self):
size = self.int()
data = self.bytes(size)
return BinaryData(data=data)
def eof(self):
return self.offset >= len(self.data)
def import_rlx(file_path):
data_folder, data_file = os.path.split(file_path)
data = BinaryData(file_path=file_path)
rlx_code = data.int()
utils.log_info(f"RLX Code: {rlx_code}")
if rlx_code == RLXCodes.RLX_ID_LIGHT:
return import_rlx_light(data, data_folder)
elif rlx_code == RLXCodes.RLX_ID_CAMERA:
return import_rlx_camera(data, data_folder)
return None
def remap_file(file_path, data_folder):
if file_path and data_folder:
orig_folder, orig_file = os.path.split(file_path)
file_path = os.path.join(data_folder, orig_file)
return file_path
def prep_rlx_actions(obj, name, motion_id, reuse_existing=False, timestamp=False, motion_prefix=None):
if not motion_id:
motion_id = "DataLink"
if timestamp:
motion_id += f"_{utils.datetimes()}"
f_prefix = rigutils.get_formatted_prefix(motion_prefix)
# generate names
T = utils.get_slot_type_for(obj.data)
ob_name = f"{f_prefix}{name}|O|{motion_id}"
data_name = f"{f_prefix}{name}|{T[0]}|{motion_id}"
# find existing actions
ob_action = utils.safe_get_action(obj)
data_action = utils.safe_get_action(obj.data)
# reuse existing by name if nothing on the object
if reuse_existing and not ob_action and ob_name in bpy.data.actions:
ob_action = bpy.data.actions[ob_name]
if reuse_existing and not data_action and data_name in bpy.data.actions:
data_action = bpy.data.actions[data_name]
# clear existing actions or create new ones
if ob_action:
utils.clear_action(ob_action)
ob_action.name = ob_name
else:
ob_action = bpy.data.actions.new(ob_name)
# clear or add action for object data animation
if data_action and data_action != ob_action:
utils.clear_action(data_action)
data_action.name = data_name
elif utils.B440():
data_action = ob_action
else:
data_action = bpy.data.actions.new(data_name)
if utils.B440():
# add slots to Blender 4.4 actions
ob_slot = ob_action.slots.new("OBJECT", ob_name)
data_slot = data_action.slots.new(T, data_name)
else:
ob_slot = None
data_slot = None
# set the actions
utils.safe_set_action(obj, ob_action, slot=ob_slot)
utils.safe_set_action(obj.data, data_action, slot=data_slot)
return ob_action, data_action, ob_slot, data_slot
def import_rlx_light(data: BinaryData, data_folder):
light_data = data.json()
# make the light
link_id = light_data["link_id"]
light = find_link_id(link_id)
light = decode_rlx_light(light_data, light)
# static properties
name: str = light_data["name"]
type: str = light_data["type"]
inverse_square: bool = light_data["inverse_square"]
transmission: bool = light_data["transmission"]
is_tube: bool = light_data["is_tube"]
tube_length: float = light_data["tube_length"] / 100
tube_radius: float = light_data["tube_radius"] / 100
tube_soft_radius: float = light_data["tube_soft_radius"] / 100
is_rectangle: bool = light_data["is_rectangle"]
rect: tuple = (light_data["rect"][0] / 100, light_data["rect"][1] / 100)
cast_shadow: bool = light_data["cast_shadow"]
num_frames = light_data["frame_count"]
light_type = get_light_type(type, is_rectangle, is_tube)
cookie = remap_file(light_data.get("cookie"), data_folder)
ies = remap_file(light_data.get("ies"), data_folder)
build_light_nodes(light, cookie, ies)
# now read in the frames and create an action for the light...
frames = data.block()
loc_cache = frame_cache(num_frames, 3)
rot_cache = frame_rotation_cache(light, num_frames)
sca_cache = frame_cache(num_frames, 3)
color_cache = frame_cache(num_frames, 3)
energy_cache = frame_cache(num_frames)
cutoff_distance_cache = frame_cache(num_frames)
spot_blend_cache = frame_cache(num_frames)
spot_size_cache = frame_cache(num_frames)
visible_cache = frame_cache(num_frames)
render_cache = frame_cache(num_frames)
start = None
while not frames.eof():
time = frames.time()
frame = frames.int() + 1
if start is None:
start = frame
active = frames.bool()
loc = frames.vector() / 100
rot = frames.quaternion()
sca = frames.vector()
color = frames.color()
multiplier = frames.float()
range = frames.float() / 100
angle = frames.float() * 0.01745329
falloff = frames.float() / 100
attenuation = frames.float() / 100
darkness = frames.float()
cutoff_distance = range
store_frame(light, loc_cache, frame, start, loc)
store_frame(light, rot_cache, frame, start, rot)
store_frame(light, sca_cache, frame, start, sca)
store_frame(light, color_cache, frame, start, color)
store_frame(light, cutoff_distance_cache, frame, start, cutoff_distance)
if light_type == "SUN":
energy = SUN_SCALE * multiplier
store_frame(light, energy_cache, frame, start, energy)
elif light_type == "SPOT":
energy = ENERGY_SCALE * multiplier
spot_blend = (falloff + attenuation) / 2
spot_size = angle
store_frame(light, energy_cache, frame, start, energy)
store_frame(light, spot_blend_cache, frame, start, spot_blend)
store_frame(light, spot_size_cache, frame, start, spot_size)
elif light_type == "AREA":
energy = ENERGY_SCALE * multiplier
store_frame(light, energy_cache, frame, start, energy)
elif light_type == "POINT":
energy = ENERGY_SCALE * multiplier
store_frame(light, energy_cache, frame, start, energy)
store_frame(light, visible_cache, frame, start, 0.0 if active else 1.0)
store_frame(light, render_cache, frame, start, 0.0 if active else 1.0)
ob_action, light_action, ob_slot, light_slot = prep_rlx_actions(light, name, "Export",
reuse_existing=False,
timestamp=True)
add_cache_fcurves(ob_action, light.path_from_id("location"), loc_cache, num_frames, "Location", slot=ob_slot)
add_cache_rotation_fcurves(light, ob_action, rot_cache, num_frames, slot=ob_slot)
add_cache_fcurves(ob_action, light.path_from_id("scale"), sca_cache, num_frames, "Scale", slot=ob_slot)
add_cache_fcurves(light_action, light.path_from_id("hide_viewport"), visible_cache, num_frames, "Hide Viewport", slot=ob_slot, interpolation="CONSTANT")
add_cache_fcurves(light_action, light.path_from_id("hide_render"), render_cache, num_frames, "Hide Render", slot=ob_slot, interpolation="CONSTANT")
add_cache_fcurves(light_action, light.data.path_from_id("color"), color_cache, num_frames, "Color", slot=light_slot)
add_cache_fcurves(light_action, light.data.path_from_id("energy"), energy_cache, num_frames, "Energy", slot=light_slot)
add_cache_fcurves(light_action, light.data.path_from_id("cutoff_distance"), cutoff_distance_cache, num_frames, "Cutoff Distance", slot=light_slot)
if light_type == "SPOT":
add_cache_fcurves(light_action, light.data.path_from_id("spot_blend"), spot_blend_cache, num_frames, "Spot Blend", slot=light_slot)
add_cache_fcurves(light_action, light.data.path_from_id("spot_size"), spot_size_cache, num_frames, "Spot Size", slot=light_slot)
def import_rlx_camera(data: BinaryData, data_folder):
camera_data = data.json()
# make the camera
link_id = camera_data["link_id"]
camera = find_link_id(link_id)
camera = decode_rlx_camera(camera_data, camera)
# static properties
link_id = camera_data["link_id"]
name: str = camera_data["name"]
fit = camera_data["fit"]
width = camera_data["width"] # mm
height = camera_data["height"] # mm
far_clip = camera_data["far_clip"] / 100
near_clip = camera_data["near_clip"] / 100
pivot_pos = utils.array_to_vector(camera_data["pos"]) / 100
dof_weight = camera_data["dof_weight"]
dof_decay = camera_data["dof_decay"]
# now read in the frames and create an action for the light...
num_frames = camera_data["frame_count"]
frames = data.block()
loc_cache = frame_cache(num_frames, 3)
rot_cache = frame_rotation_cache(camera, num_frames)
sca_cache = frame_cache(num_frames, 3)
lens_cache = frame_cache(num_frames)
dof_cache = frame_cache(num_frames)
focus_distance_cache = frame_cache(num_frames)
f_stop_cache = frame_cache(num_frames)
active_cache = []
start = None
while not frames.eof():
time = frames.time()
frame = frames.int() + 1
if start is None:
start = frame
loc = frames.vector() / 100
rot = frames.quaternion()
sca = frames.vector()
focal_length = frames.float() # mm
dof_enable = frames.bool()
dof_focus = frames.float() / 100
dof_range = frames.float() / 100
dof_far_blur = frames.float()
dof_near_blur = frames.float()
dof_far_transition = frames.float() / 100
dof_near_transition = frames.float() / 100
dof_min_blend_distance = frames.float()
fov = frames.float()
active = frames.bool()
store_frame(camera, loc_cache, frame, start, loc)
store_frame(camera, rot_cache, frame, start, rot)
store_frame(camera, sca_cache, frame, start, sca)
store_frame(camera, lens_cache, frame, start, focal_length)
store_frame(camera, dof_cache, frame, start, 1.0 if dof_enable else 0.0)
store_frame(camera, focus_distance_cache, frame, start, dof_focus)
blur = (dof_far_blur + dof_near_blur) / 2
transition = (1 / blur) * (dof_range + dof_far_transition + dof_near_transition) / 16
f_stop = transition
store_frame(camera, f_stop_cache, frame, start, f_stop)
active_cache.append((frame, time, active))
ob_action, cam_action, ob_slot, cam_slot = prep_rlx_actions(camera, name, "Export",
reuse_existing=False,
timestamp=True)
add_cache_fcurves(ob_action, "location", loc_cache, num_frames, "Location", slot=ob_slot)
add_cache_rotation_fcurves(camera, ob_action, rot_cache, num_frames, slot=ob_slot)
add_cache_fcurves(ob_action, "scale", sca_cache, num_frames, "Scale", slot=ob_slot)
add_cache_fcurves(cam_action, "lens", lens_cache, num_frames, "Camera", slot=cam_slot)
add_cache_fcurves(cam_action, "dof.use_dof", dof_cache, num_frames, "DOF", slot=cam_slot)
add_cache_fcurves(cam_action, "dof.focus_distance", focus_distance_cache, num_frames, "DOF", slot=cam_slot)
add_cache_fcurves(cam_action, "dof.aperture_fstop", f_stop_cache, num_frames, "DOF", slot=cam_slot)
add_camera_markers(camera, active_cache, num_frames, start)
def frame_rotation_cache(obj, frames):
if obj.rotation_mode == "QUATERNION":
indices = 4
defaults = [1,0,0,0]
elif obj.rotation_mode == "AXIS_ANGLE":
indices = 4
defaults = [0,0,1,0]
else: # transform_object.rotation_mode in [ "XYZ", "XZY", "YXZ", "YZX", "ZXY", "ZYX" ]:
indices = 3
defaults = [0,0,0]
cache = []
for i in range(0, indices):
data = [0, defaults[i]] * frames
for j in range(0, frames):
data[j * 2] = j
cache.append(data)
return cache
def frame_cache(frames, indices=1, default_value=0.0):
cache = []
for i in range(0, indices):
data = [0, default_value] * frames
for j in range(0, frames):
data[j * 2] = j
cache.append(data)
return cache
def store_frame(obj, cache, frame, start, value):
T = type(value)
index = (frame - start) * 2
if T is Quaternion:
if obj.rotation_mode == "QUATERNION":
l = len(value)
for i in range(0, l):
curve = cache[i]
curve[index] = frame
curve[index + 1] = value[i]
elif obj.rotation_mode == "AXIS_ANGLE":
# convert quaternion to angle axis
v,a = value.to_axis_angle()
l = len(v)
for i in range(0, l):
curve = cache[i]
curve[index] = frame
curve[index + 1] = v[i]
curve = cache[3]
curve[index] = frame
curve[index + 1] = a
else:
euler = value.to_euler(obj.rotation_mode)
l = len(euler)
for i in range(0, l):
curve = cache[i]
curve[index] = frame
curve[index + 1] = euler[i]
elif T is Vector or T is Color:
l = len(value)
for i in range(0, l):
curve = cache[i]
curve[index] = frame
curve[index + 1] = value[i]
else:
curve = cache[0]
curve[index] = frame
curve[index + 1] = value
def add_cache_rotation_fcurves(obj, action: bpy.types.Action, cache, num_frames, slot=None):
if obj.rotation_mode == "QUATERNION":
data_path = obj.path_from_id("rotation_quaternion")
group_name = "Rotation Quaternion"
elif obj.rotation_mode == "AXIS_ANGLE":
data_path = obj.path_from_id("rotation_axis_angle")
group_name = "Rotation Axis-Angle"
else: # Euler
data_path = obj.path_from_id("rotation_euler")
group_name = "Rotation Euler"
add_cache_fcurves(action, data_path, cache, num_frames, group_name=group_name, slot=slot)
def add_cache_fcurves(action: bpy.types.Action, data_path, cache, num_frames, group_name=None, slot=None, interpolation="LINEAR"):
channels = utils.get_action_channels(action, slot)
num_curves = len(cache)
if channels:
fcurve: bpy.types.FCurve = None
if group_name not in channels.groups:
channels.groups.new(group_name)
for i in range(0, num_curves):
fcurve = channels.fcurves.new(data_path, index=i)
fcurve.auto_smoothing = "NONE"
fcurve.group = channels.groups[group_name]
reduced = reduce_cache(cache[i], interpolation)
num_reduced = int(len(reduced) / 2)
fcurve.keyframe_points.add(num_reduced)
fcurve.keyframe_points.foreach_set('co', reduced)
if interpolation != "BEZIER":
for keyframe in fcurve.keyframe_points:
keyframe.interpolation = interpolation
else:
L = len(fcurve.keyframe_points)
for i, keyframe in enumerate(fcurve.keyframe_points):
prev = fcurve.keyframe_points[i-1] if i > 0 else keyframe
next = fcurve.keyframe_points[i+1] if i < L-1 else keyframe
keyframe.handle_left_type = "AUTO"
keyframe.handle_left[0] = keyframe.co.x - 0.5
keyframe.handle_left[1] = (keyframe.co.y + prev.co.y) * 0.5
keyframe.handle_right_type = "AUTO"
keyframe.handle_right[0] = keyframe.co.x + 0.5
keyframe.handle_right[1] = (keyframe.co.y + next.co.y) * 0.5
def reduce_cache(cache, interpolation):
if not cache or len(cache) <= 4:
return cache
reduced = []
L = len(cache)
Lm2 = L - 2
# add first frame
reduced.append(cache[0])
reduced.append(cache[1])
use_next = interpolation != "CONSTANT"
for i in range(2, L, 2):
frame = cache[i]
value = cache[i+1]
value_last = cache[i-1]
value_next = cache[i+3] if i < Lm2 else value
last_changed = abs(value - value_last) > 0.0001
next_changed = abs(value - value_next) > 0.0001
if last_changed or (use_next and next_changed):
reduced.append(frame)
reduced.append(value)
# add last frame
#reduced.append(cache[-2])
#reduced.append(cache[-1])
return reduced
def add_camera_markers(camera, cache, num_frames, start):
scene = bpy.context.scene
frames = len(cache)
# wipe all camera markers for this camera in this frame range
to_remove = []
for marker in scene.timeline_markers:
if marker.frame >= start and marker.frame < start + num_frames:
if marker.camera == camera:
to_remove.append(marker)
for marker in to_remove:
scene.timeline_markers.remove(marker)
# add markers for camera only when camera first activates
last_active = False
for i, (frame, time, active) in enumerate(cache):
if active and not last_active:
marker = scene.timeline_markers.new(f"RLCam_F{frame}")
marker.frame = frame
marker.camera = camera
last_active = active
def decode_rlx_light(light_data, light: bpy.types.Object=None, container=None):
# static properties
link_id = light_data["link_id"]
name: str = light_data["name"]
type: str = light_data["type"]
inverse_square: bool = light_data["inverse_square"]
transmission: bool = light_data["transmission"]
is_tube: bool = light_data["is_tube"]
tube_length: float = light_data["tube_length"] / 100
tube_radius: float = light_data["tube_radius"] / 100
tube_soft_radius: float = light_data["tube_soft_radius"] / 100
is_rectangle: bool = light_data["is_rectangle"]
rect: tuple = (light_data["rect"][0] / 100, light_data["rect"][1] / 100)
cast_shadow: bool = light_data["cast_shadow"]
# animateable properties
active = light_data["active"]
loc = utils.array_to_vector(light_data["loc"]) / 100
rot = utils.array_to_quaternion(light_data["rot"])
sca = utils.array_to_vector(light_data["sca"])
color = utils.array_to_color(light_data["color"])
multiplier = light_data["multiplier"]
range = light_data["range"] / 100
angle = light_data["angle"] * 0.01745329
falloff = light_data["falloff"] / 100
attenuation = light_data["attenuation"] / 100
darkness = light_data["darkness"]
light_type = get_light_type(type, is_rectangle, is_tube)
ob_action = utils.safe_get_action(light) if light else None
light_action = utils.safe_get_action(light.data) if light else None
if light and (light.type != "LIGHT" or light.data.type != light_type):
utils.delete_light_object(light)
light = None
if not light:
if light_type == "AREA":
light = add_area_light(light_data["name"], container)
elif light_type == "POINT":
light = add_point_light(light_data["name"], container)
elif light_type == "SUN":
light = add_dir_light(light_data["name"], container)
else:
light = add_spot_light(light_data["name"], container)
utils.set_rl_link_id(light, link_id)
utils.safe_set_action(light, ob_action)
utils.safe_set_action(light.data, light_action)
light.location = loc
utils.set_transform_rotation(light, rot)
light.scale = sca
light.data.color = color
if light_type == "SUN":
light.data.energy = SUN_SCALE * multiplier
elif light_type == "SPOT":
light.data.energy = ENERGY_SCALE * multiplier
light.data.use_custom_distance = True
light.data.cutoff_distance = range
light.data.spot_blend = (falloff*attenuation + attenuation) / 2
light.data.spot_size = angle
if utils.B410():
try:
light.data.use_soft_falloff = True
except: ...
if is_rectangle:
light.data.shadow_soft_size = (rect[0] + rect[1]) / 3
elif is_tube:
light.data.shadow_soft_size = (tube_radius + tube_length) / 3
elif light_type == "AREA":
light.data.energy = ENERGY_SCALE * multiplier
light.data.use_custom_distance = True
light.data.cutoff_distance = range
if is_rectangle:
light.data.shape = "RECTANGLE"
light.data.size = rect[0]
light.data.size_y = rect[1]
elif is_tube:
light.data.shape = "ELLIPSE"
light.data.size = 10 * max(0.01, tube_length)
light.data.size_y = tube_radius
elif light_type == "POINT":
light.data.energy = ENERGY_SCALE * 2.0 * multiplier
light.data.use_custom_distance = True
light.data.cutoff_distance = range
light.data.use_shadow = cast_shadow
if cast_shadow:
if utils.B420():
light.data.use_shadow_jitter = True
else:
if light_type != "SUN":
light.data.shadow_buffer_clip_start = 0.0025
light.data.shadow_buffer_bias = 1.0
light.data.use_contact_shadow = True
light.data.contact_shadow_distance = 0.1
light.data.contact_shadow_bias = 0.03
light.data.contact_shadow_thickness = 0.001
light.hide_viewport = not active
light.hide_render = not active
return light
def apply_light_pose(light, loc, rot, sca, color, active, multiplier, range, angle, falloff, attenuation, darkness):
light.location = loc
utils.set_transform_rotation(light, rot)
light.scale = sca
light.data.color = color
if not active:
multiplier = 0.0
if light.data.type == "SUN":
light.data.energy = 2 * multiplier
elif light.data.type == "SPOT":
light.data.energy = ENERGY_SCALE * multiplier
light.data.cutoff_distance = range / 100
light.data.spot_blend = (attenuation * falloff + attenuation) / 200
light.data.spot_size = angle * 0.01745329
elif light.data.type == "AREA":
light.data.energy = ENERGY_SCALE * multiplier
light.data.cutoff_distance = range / 100
elif light.data.type == "POINT":
light.data.energy = ENERGY_SCALE * 2.0 * multiplier
light.data.cutoff_distance = range / 100
def decode_rlx_camera(camera_data, camera):
# static properties
link_id = camera_data["link_id"]
name: str = camera_data["name"]
fit = camera_data["fit"]
width = camera_data["width"] # mm
height = camera_data["height"] # mm
far_clip = camera_data["far_clip"] / 100
near_clip = camera_data["near_clip"] / 100
pivot_pos = utils.array_to_vector(camera_data["pos"]) / 100
dof_weight = camera_data["dof_weight"]
dof_decay = camera_data["dof_decay"]
# animateable properties
fov = camera_data["fov"]
focal_length = camera_data["focal_length"] # mm
loc = utils.array_to_vector(camera_data["loc"]) / 100
rot = utils.array_to_quaternion(camera_data["rot"])
sca = utils.array_to_vector(camera_data["sca"])
dof_enable = camera_data["dof_enable"]
dof_focus = camera_data["dof_focus"] / 100
dof_range = camera_data["dof_range"] / 100
dof_far_blur = camera_data["dof_far_blur"] # 0.1 - 1.8
dof_near_blur = camera_data["dof_near_blur"] # 0.1 - 1.8
dof_far_transition = camera_data["dof_far_transition"] / 100
dof_near_transition = camera_data["dof_near_transition"] / 100
dof_min_blend_distance = camera_data["dof_min_blend_distance"] # 0.0 - 1.0
active = camera_data["active"]
ob_action = utils.safe_get_action(camera) if camera else None
cam_action = utils.safe_get_action(camera.data) if camera else None
if camera and camera.type != "CAMERA":
utils.delete_object(camera)
camera = None
if not camera:
camera = add_camera(name)
utils.set_rl_link_id(camera, link_id)
utils.safe_set_action(camera, ob_action)
utils.safe_set_action(camera.data, cam_action)
camera.location = loc
utils.set_transform_rotation(camera, rot)
camera.scale = sca
camera.data.lens = focal_length
camera.data.sensor_fit = fit
camera.data.sensor_width = width
camera.data.sensor_height = height
camera.data.clip_start = near_clip
camera.data.clip_end = far_clip
# depth of field
camera.data.dof.use_dof = dof_enable
camera.data.dof.focus_distance = dof_focus
# not much we can do about blur as DOF blur is a global scene setting in Blender (and only for Eevee)
# bpy.data.scenes["Scene"].eevee.bokeh_max_size
# TODO maybe blur can be incorporated into f_stop
# TODO maybe dof_range too (perfect focus range)
blur = (dof_far_blur + dof_near_blur) / 2
# transition range can be interpreted as the f-stop
transition = (1 / blur) * (dof_range + dof_far_transition + dof_near_transition) / 16
f_stop = transition
camera.data.dof.aperture_fstop = f_stop
return camera
def apply_camera_pose(camera, loc, rot, sca, focal_length,
dof_enable, dof_focus, dof_range,
dof_far_blur, dof_near_blur,
dof_far_transition, dof_near_transition, dof_min_blend_distance):
camera.location = loc
utils.set_transform_rotation(camera, rot)
camera.scale = sca
camera.data.lens = focal_length
# depth of field
camera.data.dof.use_dof = dof_enable
camera.data.dof.focus_distance = dof_focus / 100
# not much we can do about blur as DOF blur is a global scene setting in Blender (and only for Eevee)
# bpy.data.scenes["Scene"].eevee.bokeh_max_size
# TODO maybe blur can be incorporated into f_stop
# TODO maybe dof_range too (perfect focus range)
blur = (dof_far_blur + dof_near_blur) / 2
# transition range can be interpreted as the f-stop
transition = (1 / blur) * (dof_range + dof_far_transition + dof_near_transition) / 1600
f_stop = transition
camera.data.dof.aperture_fstop = f_stop
def get_light_type(rl_type, is_rectangle, is_tube):
shape = "RECTANGLE" if is_rectangle else "TUBE" if is_tube else "NONE"
if rl_type == "DIR":
light_type = "SUN"
else:
light_type = rl_type
if TUBE_AS_AREA and shape == "TUBE":
light_type = "AREA"
if RECTANGULAR_AS_AREA and shape == "RECTANGLE":
light_type = "AREA"
# area lights reproduce linear falloff (none inverse_square) lights best
#if light_type == "SPOT" or light_type == "POINT":
# if (shape == "TUBE" or shape == "NONE") and not inverse_square:
# light_type = "AREA"
return light_type
def find_link_id(link_id: str):
for obj in bpy.data.objects:
obj_link_id = utils.get_rl_link_id(obj)
if obj_link_id == link_id:
return obj
return None
def add_camera(name, container=None):
bpy.ops.object.camera_add()
camera = utils.get_active_object()
camera.name = name
camera.data.name = name
utils.set_ccic_id(camera)
if container:
camera.parent = container
camera.matrix_parent_inverse = container.matrix_world.inverted()
return camera
def add_spot_light(name, container=None):
bpy.ops.object.light_add(type="SPOT")
light = utils.get_active_object()
light.name = name
light.data.name = name
utils.set_ccic_id(light)
if container:
light.parent = container
light.matrix_parent_inverse = container.matrix_world.inverted()
return light
def add_area_light(name, container=None):
bpy.ops.object.light_add(type="AREA")
light = utils.get_active_object()
light.name = name
light.data.name = name
utils.set_ccic_id(light)
if container:
light.parent = container
light.matrix_parent_inverse = container.matrix_world.inverted()
return light
def add_point_light(name, container=None):
bpy.ops.object.light_add(type="POINT")
light = utils.get_active_object()
light.name = name
light.data.name = name
utils.set_ccic_id(light)
if container:
light.parent = container
light.matrix_parent_inverse = container.matrix_world.inverted()
return light
def add_dir_light(name, container=None):
bpy.ops.object.light_add(type="SUN")
light = utils.get_active_object()
light.name = name
light.data.name = name
utils.set_ccic_id(light)
if container:
light.parent = container
light.matrix_parent_inverse = container.matrix_world.inverted()
return light
def add_light_container():
container = None
for obj in bpy.data.objects:
if obj.type == "EMPTY" and "Lighting" in obj.name and utils.has_ccic_id(obj):
container = obj
if not container:
bpy.ops.object.empty_add(type="PLAIN_AXES", radius=0.01)
container = utils.get_active_object()
container.name = "Lighting"
utils.set_ccic_id(container)
children = utils.get_child_objects(container)
for child in children:
if utils.has_ccic_id(child) and child.type == "LIGHT":
utils.delete_object_tree(child)
return container
def build_light_nodes(light, cookie, ies):
if light and (cookie or ies):
light.data.use_nodes = True
nodes: bpy.types.Nodes = light.data.node_tree.nodes
links = light.data.node_tree.links
nodes.clear()
emission_node: bpy.types.ShaderNodeEmission = nodes.new("ShaderNodeEmission")
output_node: bpy.types.ShaderNodeOutputLight = nodes.new("ShaderNodeOutputLight")
nodeutils.link_nodes(links, emission_node, "Emission", output_node, "Surface")
emission_node.location = Vector((40, 380))
output_node.location = Vector((320, 300))
if ies:
ies_node: bpy.types.ShaderNodeTexIES = nodes.new("ShaderNodeTexIES")
ies_node.mode = "EXTERNAL"
ies_node.filepath = ies
nodeutils.set_node_input_value(ies_node, "Strength", 0.01)
nodeutils.link_nodes(links, ies_node, "Fac", emission_node, "Strength")
ies_node.location = Vector((-220, 200))
if cookie:
cookie_node: bpy.types.ShaderNodeTexImage = nodes.new("ShaderNodeTexImage")
cookie_node.image = imageutils.load_image(cookie, "sRGB")
nodeutils.link_nodes(links, cookie_node, "Color", emission_node, "Color")
cookie_node.location = Vector((-320, 520))