yyc12345
2f08455518
- add virtools light feature for blender light type and add all essential operators, functions and structs. - remove PyBMap from repository. order builder fetch it on their own. - update gitignore.
322 lines
12 KiB
Python
322 lines
12 KiB
Python
import bpy, mathutils
|
|
from bpy.types import Context
|
|
import typing, math
|
|
from . import UTIL_functions, UTIL_virtools_types
|
|
|
|
# Raw Data
|
|
|
|
class RawVirtoolsLight():
|
|
# Class member
|
|
|
|
mType: UTIL_virtools_types.VXLIGHT_TYPE
|
|
mColor: UTIL_virtools_types.VxColor
|
|
|
|
mConstantAttenuation: float
|
|
mLinearAttenuation: float
|
|
mQuadraticAttenuation: float
|
|
|
|
mRange: float
|
|
|
|
mHotSpot: float
|
|
mFalloff: float
|
|
mFalloffShape: float
|
|
|
|
# Class member default value
|
|
|
|
cDefaultType: typing.ClassVar[UTIL_virtools_types.VXLIGHT_TYPE] = UTIL_virtools_types.VXLIGHT_TYPE.VX_LIGHTPOINT
|
|
cDefaultColor: typing.ClassVar[UTIL_virtools_types.VxColor] = UTIL_virtools_types.VxColor(1.0, 1.0, 1.0, 1.0)
|
|
|
|
cDefaultConstantAttenuation: typing.ClassVar[float] = 1.0
|
|
cDefaultLinearAttenuation: typing.ClassVar[float] = 0.0
|
|
cDefaultQuadraticAttenuation: typing.ClassVar[float] = 0.0
|
|
|
|
cDefaultRange: typing.ClassVar[float] = 100.0
|
|
|
|
cDefaultHotSpot: typing.ClassVar[float] = math.radians(40)
|
|
cDefaultFalloff: typing.ClassVar[float] = math.radians(45)
|
|
cDefaultFalloffShape: typing.ClassVar[float] = 1.0
|
|
|
|
def __init__(self, **kwargs):
|
|
# assign default value for each component
|
|
self.mType = kwargs.get('mType', RawVirtoolsLight.cDefaultType)
|
|
self.mColor = kwargs.get('mColor', RawVirtoolsLight.cDefaultColor).clone()
|
|
|
|
self.mConstantAttenuation = kwargs.get('mConstantAttenuation', RawVirtoolsLight.cDefaultConstantAttenuation)
|
|
self.mLinearAttenuation = kwargs.get('mLinearAttenuation', RawVirtoolsLight.cDefaultLinearAttenuation)
|
|
self.mQuadraticAttenuation = kwargs.get('mQuadraticAttenuation', RawVirtoolsLight.cDefaultQuadraticAttenuation)
|
|
|
|
self.mRange = kwargs.get('mRange', RawVirtoolsLight.cDefaultRange)
|
|
|
|
self.mHotSpot = kwargs.get('mHotSpot', RawVirtoolsLight.cDefaultHotSpot)
|
|
self.mFalloff = kwargs.get('mFalloff', RawVirtoolsLight.cDefaultFalloff)
|
|
self.mFalloffShape = kwargs.get('mFalloffShape', RawVirtoolsLight.cDefaultFalloffShape)
|
|
|
|
def regulate(self) -> None:
|
|
# regulate color and reset its alpha value
|
|
self.mColor.regulate()
|
|
self.mColor.a = 1.0
|
|
# regulate range
|
|
self.mRange = UTIL_functions.clamp_float(self.mRange, 0.0, 200.0)
|
|
|
|
# regulate attenuation
|
|
self.mConstantAttenuation = UTIL_functions.clamp_float(self.mConstantAttenuation, 0.0, 10.0)
|
|
self.mLinearAttenuation = UTIL_functions.clamp_float(self.mLinearAttenuation, 0.0, 10.0)
|
|
self.mQuadraticAttenuation = UTIL_functions.clamp_float(self.mQuadraticAttenuation, 0.0, 10.0)
|
|
|
|
# regulate spot cone
|
|
self.mHotSpot = UTIL_functions.clamp_float(self.mHotSpot, 0.0, math.radians(180))
|
|
self.mFalloff = UTIL_functions.clamp_float(self.mFalloff, 0.0, math.radians(180))
|
|
self.mFalloffShape = UTIL_functions.clamp_float(self.mFalloffShape, 0.0, 10.0)
|
|
# regulate spot cone size order
|
|
if self.mFalloff < self.mHotSpot:
|
|
self.mFalloff = self.mHotSpot
|
|
|
|
# Blender Property Group
|
|
|
|
_g_Helper_VXLIGHT_TYPE: UTIL_virtools_types.EnumPropHelper = UTIL_virtools_types.EnumPropHelper(UTIL_virtools_types.VXLIGHT_TYPE)
|
|
|
|
class BBP_PG_virtools_light(bpy.types.PropertyGroup):
|
|
light_type: bpy.props.EnumProperty(
|
|
name = "Type",
|
|
description = "The type of this light",
|
|
items = _g_Helper_VXLIGHT_TYPE.generate_items(),
|
|
default = _g_Helper_VXLIGHT_TYPE.to_selection(RawVirtoolsLight.cDefaultType)
|
|
) # type: ignore
|
|
|
|
light_color: bpy.props.FloatVectorProperty(
|
|
name = "Color",
|
|
description = "Defines the red, green and blue components of the light.",
|
|
subtype = 'COLOR',
|
|
min = 0.0,
|
|
max = 1.0,
|
|
size = 3,
|
|
default = RawVirtoolsLight.cDefaultColor.to_const_rgb()
|
|
) # type: ignore
|
|
|
|
constant_attenuation: bpy.props.FloatProperty(
|
|
name = "Constant Attenuation",
|
|
description = "Defines the constant attenuation factor.",
|
|
min = 0.0,
|
|
max = 10.0,
|
|
step = 10,
|
|
default = RawVirtoolsLight.cDefaultConstantAttenuation
|
|
) # type: ignore
|
|
|
|
linear_attenuation: bpy.props.FloatProperty(
|
|
name = "Linear Attenuation",
|
|
description = "Defines the linear attenuation factor.",
|
|
min = 0.0,
|
|
max = 10.0,
|
|
step = 10,
|
|
default = RawVirtoolsLight.cDefaultLinearAttenuation
|
|
) # type: ignore
|
|
|
|
quadratic_attenuation: bpy.props.FloatProperty(
|
|
name = "Quadratic Attenuation",
|
|
description = "Defines the quadratic attenuation factor.",
|
|
min = 0.0,
|
|
max = 10.0,
|
|
step = 10,
|
|
default = RawVirtoolsLight.cDefaultQuadraticAttenuation
|
|
) # type: ignore
|
|
|
|
light_range: bpy.props.FloatProperty(
|
|
name = "Range",
|
|
description = "Defines the radius of the lighting area.",
|
|
min = 0.0,
|
|
max = 200.0,
|
|
step = 100,
|
|
default = RawVirtoolsLight.cDefaultRange
|
|
) # type: ignore
|
|
|
|
hot_spot: bpy.props.FloatProperty(
|
|
name = "Hot Spot",
|
|
description = "Sets the value of the hot spot of the light.",
|
|
min = 0.0,
|
|
max = math.radians(180),
|
|
subtype = 'ANGLE',
|
|
default = RawVirtoolsLight.cDefaultHotSpot
|
|
) # type: ignore
|
|
|
|
falloff: bpy.props.FloatProperty(
|
|
name = "Fall Off",
|
|
description = "Sets the light fall off rate.",
|
|
min = 0.0,
|
|
max = math.radians(180),
|
|
subtype = 'ANGLE',
|
|
default = RawVirtoolsLight.cDefaultFalloff
|
|
) # type: ignore
|
|
|
|
falloff_shape: bpy.props.FloatProperty(
|
|
name = "Fall Off Shape",
|
|
description = "Sets the value of the light fall off shape.",
|
|
min = 0.0,
|
|
max = 10.0,
|
|
step = 10,
|
|
default = RawVirtoolsLight.cDefaultFalloffShape
|
|
) # type: ignore
|
|
|
|
# Getter Setter and Applyer
|
|
|
|
def get_virtools_light(lit: bpy.types.Light) -> BBP_PG_virtools_light:
|
|
return lit.virtools_light
|
|
|
|
def get_raw_virtools_light(lit: bpy.types.Light) -> RawVirtoolsLight:
|
|
props: BBP_PG_virtools_light = get_virtools_light(lit)
|
|
rawdata: RawVirtoolsLight = RawVirtoolsLight()
|
|
|
|
rawdata.mType = _g_Helper_VXLIGHT_TYPE.get_selection(props.light_type)
|
|
rawdata.mColor.from_const_rgb(props.light_color)
|
|
|
|
rawdata.mConstantAttenuation = props.constant_attenuation
|
|
rawdata.mLinearAttenuation = props.linear_attenuation
|
|
rawdata.mQuadraticAttenuation = props.quadratic_attenuation
|
|
|
|
rawdata.mRange = props.light_range
|
|
|
|
rawdata.mHotSpot = props.hot_spot
|
|
rawdata.mFalloff = props.falloff
|
|
rawdata.mFalloffShape = props.falloff_shape
|
|
|
|
rawdata.regulate()
|
|
return rawdata
|
|
|
|
def set_raw_virtools_light(lit: bpy.types.Light, rawdata: RawVirtoolsLight) -> None:
|
|
props: BBP_PG_virtools_light = get_virtools_light(lit)
|
|
|
|
props.light_type = _g_Helper_VXLIGHT_TYPE.to_selection(rawdata.mType)
|
|
props.light_color = rawdata.mColor.to_const_rgb()
|
|
|
|
props.constant_attenuation = rawdata.mConstantAttenuation
|
|
props.linear_attenuation = rawdata.mLinearAttenuation
|
|
props.quadratic_attenuation = rawdata.mQuadraticAttenuation
|
|
|
|
props.light_range = rawdata.mRange
|
|
|
|
props.hot_spot = rawdata.mHotSpot
|
|
props.falloff = rawdata.mFalloff
|
|
props.falloff_shape = rawdata.mFalloffShape
|
|
|
|
def apply_to_blender_light(lit: bpy.types.Light) -> None:
|
|
# get raw data first
|
|
rawdata: RawVirtoolsLight = get_raw_virtools_light(lit)
|
|
|
|
# set light type and color
|
|
match(rawdata.mType):
|
|
case UTIL_virtools_types.VXLIGHT_TYPE.VX_LIGHTPOINT:
|
|
lit.type = 'POINT'
|
|
case UTIL_virtools_types.VXLIGHT_TYPE.VX_LIGHTSPOT:
|
|
lit.type = 'SPOT'
|
|
case UTIL_virtools_types.VXLIGHT_TYPE.VX_LIGHTDIREC:
|
|
lit.type = 'SUN'
|
|
lit.color = rawdata.mColor.to_const_rgb()
|
|
|
|
# MARK:
|
|
# After set light type, we must re-fetch light object,
|
|
# because it seems that the object hold by this variable
|
|
# is not the object after light type changes.
|
|
#
|
|
# If I do not do this, function will throw exception
|
|
# like `'PointLight' object has no attribute 'spot_size'`.
|
|
lit = bpy.data.lights[lit.name]
|
|
match(rawdata.mType):
|
|
case UTIL_virtools_types.VXLIGHT_TYPE.VX_LIGHTPOINT:
|
|
point_lit: bpy.types.PointLight = typing.cast(bpy.types.PointLight, lit)
|
|
point_lit.shadow_soft_size = rawdata.mRange
|
|
case UTIL_virtools_types.VXLIGHT_TYPE.VX_LIGHTSPOT:
|
|
spot_lit: bpy.types.SpotLight = typing.cast(bpy.types.SpotLight, lit)
|
|
spot_lit.shadow_soft_size = rawdata.mRange
|
|
spot_lit.spot_size = rawdata.mFalloff
|
|
if rawdata.mFalloff == 0: spot_lit.spot_blend = 0.0
|
|
else: spot_lit.spot_blend = 1.0 - rawdata.mHotSpot / rawdata.mFalloff
|
|
case UTIL_virtools_types.VXLIGHT_TYPE.VX_LIGHTDIREC:
|
|
pass
|
|
|
|
# Operators
|
|
|
|
class BBP_OT_apply_virtools_light(bpy.types.Operator):
|
|
"""Apply Virtools Light to Blender Light."""
|
|
bl_idname = "bbp.apply_virtools_light"
|
|
bl_label = "Apply to Blender Light"
|
|
bl_options = {'UNDO'}
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.light is not None
|
|
|
|
def execute(self, context):
|
|
lit: bpy.types.Light = context.light
|
|
apply_to_blender_light(lit)
|
|
return {'FINISHED'}
|
|
|
|
# Display Panel
|
|
|
|
class BBP_PT_virtools_light(bpy.types.Panel):
|
|
"""Show Virtools Light Properties"""
|
|
bl_label = "Virtools Light"
|
|
bl_idname = "BBP_PT_virtools_light"
|
|
bl_space_type = 'PROPERTIES'
|
|
bl_region_type = 'WINDOW'
|
|
bl_context = "data" # idk why blender use `data` as the light tab same as mesh.
|
|
|
|
@classmethod
|
|
def poll(cls, context):
|
|
return context.light is not None
|
|
|
|
def draw(self, context):
|
|
# get layout and target
|
|
layout = self.layout
|
|
layout.use_property_split = True
|
|
lit: bpy.types.Light = context.light
|
|
props: BBP_PG_virtools_light = get_virtools_light(lit)
|
|
rawdata: RawVirtoolsLight = get_raw_virtools_light(lit)
|
|
|
|
# draw operator
|
|
layout.operator(BBP_OT_apply_virtools_light.bl_idname, text = 'Apply', icon = 'NODETREE')
|
|
|
|
# draw data
|
|
layout.separator()
|
|
layout.label(text = 'Basics')
|
|
# all lights has type and color property
|
|
sublayout = layout.row()
|
|
sublayout.use_property_split = False
|
|
sublayout.prop(props, 'light_type', expand = True)
|
|
layout.prop(props, 'light_color')
|
|
# all light has range property exception directional light
|
|
if rawdata.mType != UTIL_virtools_types.VXLIGHT_TYPE.VX_LIGHTDIREC:
|
|
layout.prop(props, 'light_range')
|
|
|
|
# all light has attenuation exception directional light
|
|
if rawdata.mType != UTIL_virtools_types.VXLIGHT_TYPE.VX_LIGHTDIREC:
|
|
layout.separator()
|
|
layout.label(text = 'Attenuation')
|
|
layout.prop(props, 'constant_attenuation', text = 'Constant')
|
|
layout.prop(props, 'linear_attenuation', text = 'Linear')
|
|
layout.prop(props, 'quadratic_attenuation', text = 'Quadratic')
|
|
|
|
# only spot light has spot cone properties.
|
|
if rawdata.mType == UTIL_virtools_types.VXLIGHT_TYPE.VX_LIGHTSPOT:
|
|
layout.separator()
|
|
layout.label(text = 'Spot Cone')
|
|
layout.prop(props, 'hot_spot')
|
|
layout.prop(props, 'falloff')
|
|
layout.prop(props, 'falloff_shape')
|
|
|
|
# Register
|
|
|
|
def register() -> None:
|
|
bpy.utils.register_class(BBP_PG_virtools_light)
|
|
bpy.utils.register_class(BBP_OT_apply_virtools_light)
|
|
bpy.utils.register_class(BBP_PT_virtools_light)
|
|
|
|
# add into light metadata
|
|
bpy.types.Light.virtools_light = bpy.props.PointerProperty(type = BBP_PG_virtools_light)
|
|
|
|
def unregister() -> None:
|
|
# remove from metadata
|
|
del bpy.types.Light.virtools_light
|
|
|
|
bpy.utils.unregister_class(BBP_PT_virtools_light)
|
|
bpy.utils.unregister_class(BBP_OT_apply_virtools_light)
|
|
bpy.utils.unregister_class(BBP_PG_virtools_light)
|