BallanceBlenderHelper/bbp_ng/PROP_virtools_light.py
yyc12345 04aa879c22 feat: finish i18n extract work
- masically finish u18n extract work (most fields are extracted)
2025-01-12 15:15:29 +08:00

335 lines
13 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),
translation_context = 'BBP_PG_virtools_light/property'
) # 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(),
translation_context = 'BBP_PG_virtools_light/property'
) # 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,
translation_context = 'BBP_PG_virtools_light/property'
) # 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,
translation_context = 'BBP_PG_virtools_light/property'
) # 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,
translation_context = 'BBP_PG_virtools_light/property'
) # 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,
translation_context = 'BBP_PG_virtools_light/property'
) # 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,
translation_context = 'BBP_PG_virtools_light/property'
) # 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,
translation_context = 'BBP_PG_virtools_light/property'
) # 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,
translation_context = 'BBP_PG_virtools_light/property'
) # 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'}
bl_translation_context = 'BBP_OT_apply_virtools_light'
@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.
bl_translation_context = 'BBP_PT_virtools_light'
@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',
text_ctxt='BBP_PT_virtools_light/draw')
# draw data
layout.separator()
layout.label(text='Basics', text_ctxt='BBP_PT_virtools_light/draw')
# 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', text_ctxt='BBP_PT_virtools_light/draw')
layout.prop(props, 'constant_attenuation', text='Constant', text_ctxt='BBP_PT_virtools_light/draw')
layout.prop(props, 'linear_attenuation', text='Linear', text_ctxt='BBP_PT_virtools_light/draw')
layout.prop(props, 'quadratic_attenuation', text='Quadratic', text_ctxt='BBP_PT_virtools_light/draw')
# only spot light has spot cone properties.
if rawdata.mType == UTIL_virtools_types.VXLIGHT_TYPE.VX_LIGHTSPOT:
layout.separator()
layout.label(text='Spot Cone', text_ctxt='BBP_PT_virtools_light/draw')
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)