feat: finish virtools camera work
- add operator for applying camera aspect ratio to blender scene. - add camera aspect ratio preset in virtools camera panel. - update game camera operators. use virtools camera instead of directly modifying camera properties.
This commit is contained in:
@@ -1,34 +1,15 @@
|
|||||||
import bpy, mathutils
|
import bpy, mathutils
|
||||||
import typing, enum, math
|
import typing, enum, math
|
||||||
from . import UTIL_functions
|
from . import UTIL_functions, PROP_virtools_camera
|
||||||
|
|
||||||
# TODO:
|
#region Enum Defines
|
||||||
# This file should have fully refactor after we finish Virtools Camera import and export,
|
|
||||||
# because this module is highly rely on it. Current implementation is a compromise.
|
|
||||||
# There is a list of things to be done:
|
|
||||||
# - Remove BBP_OT_game_resolution operator, because Virtools Camera will have similar function in panel.
|
|
||||||
# - Update BBP_OT_game_cameraoperator with Virtools Camera.
|
|
||||||
|
|
||||||
#region Game Resolution
|
|
||||||
|
|
||||||
class ResolutionKind(enum.IntEnum):
|
class ResolutionKind(enum.IntEnum):
|
||||||
Normal = enum.auto()
|
Normal = enum.auto()
|
||||||
Extended = enum.auto()
|
WideScreen = enum.auto()
|
||||||
Widescreen = enum.auto()
|
|
||||||
Panoramic = enum.auto()
|
|
||||||
|
|
||||||
def to_resolution(self) -> tuple[int, int]:
|
|
||||||
match self:
|
|
||||||
case ResolutionKind.Normal: return (1024, 768)
|
|
||||||
case ResolutionKind.Extended: return (1280, 720)
|
|
||||||
case ResolutionKind.Widescreen: return (1400, 600)
|
|
||||||
case ResolutionKind.Panoramic: return (2000, 700)
|
|
||||||
|
|
||||||
_g_ResolutionKindDesc: dict[ResolutionKind, tuple[str, str]] = {
|
_g_ResolutionKindDesc: dict[ResolutionKind, tuple[str, str]] = {
|
||||||
ResolutionKind.Normal: ("Normal", "Aspect ratio: 4:3."),
|
ResolutionKind.Normal: ("Normal", "Vanilla Ballance Resolution"),
|
||||||
ResolutionKind.Extended: ("Extended", "Aspect ratio: 16:9."),
|
ResolutionKind.WideScreen: ("Wide Screen", "Ballance Resolution with Wide Screen Fix"),
|
||||||
ResolutionKind.Widescreen: ("Widescreen", "Aspect ratio: 7:3."),
|
|
||||||
ResolutionKind.Panoramic: ("Panoramic", "Aspect ratio: 20:7."),
|
|
||||||
}
|
}
|
||||||
_g_EnumHelper_ResolutionKind = UTIL_functions.EnumPropHelper(
|
_g_EnumHelper_ResolutionKind = UTIL_functions.EnumPropHelper(
|
||||||
ResolutionKind,
|
ResolutionKind,
|
||||||
@@ -39,45 +20,6 @@ _g_EnumHelper_ResolutionKind = UTIL_functions.EnumPropHelper(
|
|||||||
lambda _: ""
|
lambda _: ""
|
||||||
)
|
)
|
||||||
|
|
||||||
class BBP_OT_game_resolution(bpy.types.Operator):
|
|
||||||
"""Set Blender render resolution to Ballance game"""
|
|
||||||
bl_idname = "bbp.game_resolution"
|
|
||||||
bl_label = "Game Resolution"
|
|
||||||
bl_options = {'REGISTER', 'UNDO'}
|
|
||||||
bl_translation_context = 'BBP_OT_game_resolution'
|
|
||||||
|
|
||||||
resolution_kind: bpy.props.EnumProperty(
|
|
||||||
name = "Resolution Kind",
|
|
||||||
description = "The type of preset resolution.",
|
|
||||||
items = _g_EnumHelper_ResolutionKind.generate_items(),
|
|
||||||
default = _g_EnumHelper_ResolutionKind.to_selection(ResolutionKind.Normal),
|
|
||||||
translation_context = 'BBP_OT_game_resolution/property'
|
|
||||||
) # type: ignore
|
|
||||||
|
|
||||||
def invoke(self, context, event):
|
|
||||||
return self.execute(context)
|
|
||||||
|
|
||||||
def draw(self, context):
|
|
||||||
layout = self.layout
|
|
||||||
layout.use_property_split = True
|
|
||||||
layout.prop(self, 'resolution_kind')
|
|
||||||
|
|
||||||
def execute(self, context):
|
|
||||||
# fetch resolution
|
|
||||||
resolution_kind = _g_EnumHelper_ResolutionKind.get_selection(self.resolution_kind)
|
|
||||||
resolution = resolution_kind.to_resolution()
|
|
||||||
# setup resolution
|
|
||||||
render_settings = bpy.context.scene.render
|
|
||||||
render_settings.resolution_x = resolution[0]
|
|
||||||
render_settings.resolution_y = resolution[1]
|
|
||||||
return {'FINISHED'}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Game Camera
|
|
||||||
|
|
||||||
#region Enum Defines
|
|
||||||
|
|
||||||
class TargetKind(enum.IntEnum):
|
class TargetKind(enum.IntEnum):
|
||||||
Cursor = enum.auto()
|
Cursor = enum.auto()
|
||||||
ActiveObject = enum.auto()
|
ActiveObject = enum.auto()
|
||||||
@@ -281,6 +223,21 @@ class BBP_OT_game_camera(bpy.types.Operator):
|
|||||||
translation_context = 'BBP_OT_game_camera/property'
|
translation_context = 'BBP_OT_game_camera/property'
|
||||||
) # type: ignore
|
) # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
modify_resolution: bpy.props.BoolProperty(
|
||||||
|
name = 'Modify Resolution',
|
||||||
|
description = 'Whether modify the resolution of camera.',
|
||||||
|
default = False,
|
||||||
|
translation_context = 'BBP_OT_game_camera/property'
|
||||||
|
) # type: ignore
|
||||||
|
resolution_kind: bpy.props.EnumProperty(
|
||||||
|
name = "Resolution Kind",
|
||||||
|
description = "The type of preset resolution.",
|
||||||
|
items = _g_EnumHelper_ResolutionKind.generate_items(),
|
||||||
|
default = _g_EnumHelper_ResolutionKind.to_selection(ResolutionKind.Normal),
|
||||||
|
translation_context = 'BBP_OT_game_camera/property'
|
||||||
|
) # type: ignore
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
# find camera object
|
# find camera object
|
||||||
@@ -333,6 +290,12 @@ class BBP_OT_game_camera(bpy.types.Operator):
|
|||||||
layout.label(text='Perspective', text_ctxt='BBP_OT_game_camera/draw')
|
layout.label(text='Perspective', text_ctxt='BBP_OT_game_camera/draw')
|
||||||
layout.row().prop(self, 'perspective_kind', expand=True)
|
layout.row().prop(self, 'perspective_kind', expand=True)
|
||||||
|
|
||||||
|
# Show resolution kind
|
||||||
|
layout.separator()
|
||||||
|
layout.prop(self, 'modify_resolution', text='Resolution', text_ctxt='BBP_OT_game_camera/draw')
|
||||||
|
if self.modify_resolution:
|
||||||
|
layout.row().prop(self, 'resolution_kind', expand=True)
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
# fetch angle
|
# fetch angle
|
||||||
angle: float
|
angle: float
|
||||||
@@ -347,11 +310,12 @@ class BBP_OT_game_camera(bpy.types.Operator):
|
|||||||
camera_obj = typing.cast(bpy.types.Object, _find_camera_obj())
|
camera_obj = typing.cast(bpy.types.Object, _find_camera_obj())
|
||||||
target_kind = _g_EnumHelper_TargetKind.get_selection(self.target_kind)
|
target_kind = _g_EnumHelper_TargetKind.get_selection(self.target_kind)
|
||||||
perspective_kind = _g_EnumHelper_PerspectiveKind.get_selection(self.perspective_kind)
|
perspective_kind = _g_EnumHelper_PerspectiveKind.get_selection(self.perspective_kind)
|
||||||
|
resolution_kind = _g_EnumHelper_ResolutionKind.get_selection(self.resolution_kind)
|
||||||
|
|
||||||
# setup its transform and properties
|
# setup its transform and properties
|
||||||
glob_trans = _fetch_glob_translation(camera_obj, target_kind)
|
glob_trans = _fetch_glob_translation(camera_obj, target_kind)
|
||||||
_setup_camera_transform(camera_obj, angle, perspective_kind, glob_trans)
|
_setup_camera_transform(camera_obj, angle, perspective_kind, glob_trans)
|
||||||
_setup_camera_properties(camera_obj)
|
_setup_camera_properties(camera_obj, resolution_kind)
|
||||||
|
|
||||||
# return
|
# return
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
@@ -451,24 +415,39 @@ def _setup_camera_transform(camobj: bpy.types.Object, angle: float, perspective:
|
|||||||
glob_trans_mat = mathutils.Matrix.Translation(glob_trans)
|
glob_trans_mat = mathutils.Matrix.Translation(glob_trans)
|
||||||
camobj.matrix_world = glob_trans_mat @ trans_mat @ rot_mat
|
camobj.matrix_world = glob_trans_mat @ trans_mat @ rot_mat
|
||||||
|
|
||||||
def _setup_camera_properties(camobj: bpy.types.Object) -> None:
|
def _setup_camera_properties(camobj: bpy.types.Object, resolution_kind: ResolutionKind | None) -> None:
|
||||||
# fetch camera
|
# fetch camera and its raw data
|
||||||
camera = typing.cast(bpy.types.Camera, camobj.data)
|
camera = typing.cast(bpy.types.Camera, camobj.data)
|
||||||
|
rawdata = PROP_virtools_camera.get_raw_virtools_camera(camera)
|
||||||
|
|
||||||
# set clipping
|
# set clipping
|
||||||
camera.clip_start = 4
|
rawdata.mFrontPlane = 4
|
||||||
camera.clip_end = 1200
|
rawdata.mBackPlane = 1200
|
||||||
# set FOV
|
# set FOV and aspect ratio according to presented resolution kind
|
||||||
camera.lens_unit = 'FOV'
|
if resolution_kind is not None:
|
||||||
camera.angle = math.radians(58)
|
match resolution_kind:
|
||||||
|
case ResolutionKind.Normal:
|
||||||
|
rawdata.mFov = math.radians(58)
|
||||||
|
rawdata.mAspectRatio = (4, 3)
|
||||||
|
case ResolutionKind.WideScreen:
|
||||||
|
# prepare input arguments
|
||||||
|
aspect_ratio = (16, 9)
|
||||||
|
fov = math.radians(58)
|
||||||
|
# FOV correction reference:
|
||||||
|
# https://github.com/doyaGu/BallanceModLoaderPlus/blob/c4ab4386fd834af69a960c156fca97237b2fd4c5/src/RenderHook.cpp#L46
|
||||||
|
aspect = aspect_ratio[0] / aspect_ratio[1]
|
||||||
|
rawdata.mFov = math.atan2(math.tan(fov * 0.5) * 0.75 * aspect, 1.0) * 2.0
|
||||||
|
rawdata.mAspectRatio = aspect_ratio
|
||||||
|
|
||||||
#endregion
|
# rewrite it back
|
||||||
|
PROP_virtools_camera.set_raw_virtools_camera(camera, rawdata)
|
||||||
|
# and apply it into camera and blender scene
|
||||||
|
PROP_virtools_camera.apply_to_blender_camera(camera)
|
||||||
|
PROP_virtools_camera.apply_to_blender_scene_resolution(camera)
|
||||||
|
|
||||||
def register() -> None:
|
def register() -> None:
|
||||||
bpy.utils.register_class(BBP_OT_game_resolution)
|
|
||||||
bpy.utils.register_class(BBP_OT_game_camera)
|
bpy.utils.register_class(BBP_OT_game_camera)
|
||||||
|
|
||||||
def unregister() -> None:
|
def unregister() -> None:
|
||||||
bpy.utils.unregister_class(BBP_OT_game_camera)
|
bpy.utils.unregister_class(BBP_OT_game_camera)
|
||||||
bpy.utils.unregister_class(BBP_OT_game_resolution)
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import bpy
|
import bpy
|
||||||
import typing, math
|
import typing, math, enum
|
||||||
from . import UTIL_functions, UTIL_virtools_types
|
from . import UTIL_functions, UTIL_virtools_types
|
||||||
|
|
||||||
# Raw Data
|
|
||||||
|
|
||||||
class RawVirtoolsCamera():
|
class RawVirtoolsCamera():
|
||||||
# Class Member
|
# Class Member
|
||||||
|
|
||||||
@@ -54,10 +52,12 @@ class RawVirtoolsCamera():
|
|||||||
h = max(1, h)
|
h = max(1, h)
|
||||||
self.mAspectRatio = (w, h)
|
self.mAspectRatio = (w, h)
|
||||||
|
|
||||||
# Blender Property Group
|
#region Blender Enum Prop Helper
|
||||||
|
|
||||||
_g_Helper_CK_CAMERA_PROJECTION = UTIL_virtools_types.EnumPropHelper(UTIL_virtools_types.CK_CAMERA_PROJECTION)
|
_g_Helper_CK_CAMERA_PROJECTION = UTIL_virtools_types.EnumPropHelper(UTIL_virtools_types.CK_CAMERA_PROJECTION)
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
class BBP_PG_virtools_camera(bpy.types.PropertyGroup):
|
class BBP_PG_virtools_camera(bpy.types.PropertyGroup):
|
||||||
projection_type: bpy.props.EnumProperty(
|
projection_type: bpy.props.EnumProperty(
|
||||||
name = "Type",
|
name = "Type",
|
||||||
@@ -134,7 +134,7 @@ class BBP_PG_virtools_camera(bpy.types.PropertyGroup):
|
|||||||
translation_context = 'BBP_PG_virtools_camera/property'
|
translation_context = 'BBP_PG_virtools_camera/property'
|
||||||
) # type: ignore
|
) # type: ignore
|
||||||
|
|
||||||
# Getter Setter and Applyer
|
#region Getter Setter and Applyer
|
||||||
|
|
||||||
def get_virtools_camera(cam: bpy.types.Camera) -> BBP_PG_virtools_camera:
|
def get_virtools_camera(cam: bpy.types.Camera) -> BBP_PG_virtools_camera:
|
||||||
return cam.virtools_camera
|
return cam.virtools_camera
|
||||||
@@ -220,7 +220,50 @@ def apply_to_blender_scene_resolution(cam: bpy.types.Camera) -> None:
|
|||||||
render_settings.resolution_x = width
|
render_settings.resolution_x = width
|
||||||
render_settings.resolution_y = height
|
render_settings.resolution_y = height
|
||||||
|
|
||||||
# Operators
|
#endregion
|
||||||
|
|
||||||
|
#region Aspect Ratio Preset
|
||||||
|
|
||||||
|
class AspectRatioPresetType(enum.IntEnum):
|
||||||
|
Normal = enum.auto()
|
||||||
|
Extended = enum.auto()
|
||||||
|
Widescreen = enum.auto()
|
||||||
|
Panoramic = enum.auto()
|
||||||
|
|
||||||
|
def to_aspect_ratio(self) -> tuple[int, int]:
|
||||||
|
match self:
|
||||||
|
case AspectRatioPresetType.Normal: return (4, 3)
|
||||||
|
case AspectRatioPresetType.Extended: return (16, 9)
|
||||||
|
case AspectRatioPresetType.Widescreen: return (7, 3)
|
||||||
|
case AspectRatioPresetType.Panoramic: return (20, 7)
|
||||||
|
|
||||||
|
_g_AspectRatioPresetTypeDesc: dict[AspectRatioPresetType, tuple[str, str]] = {
|
||||||
|
AspectRatioPresetType.Normal: ("Normal", "Aspect ratio: 4:3."),
|
||||||
|
AspectRatioPresetType.Extended: ("Extended", "Aspect ratio: 16:9."),
|
||||||
|
AspectRatioPresetType.Widescreen: ("Widescreen", "Aspect ratio: 7:3."),
|
||||||
|
AspectRatioPresetType.Panoramic: ("Panoramic", "Aspect ratio: 20:7."),
|
||||||
|
}
|
||||||
|
|
||||||
|
_g_Helper_AspectRatioPresetType = UTIL_functions.EnumPropHelper(
|
||||||
|
AspectRatioPresetType,
|
||||||
|
lambda x: str(x.value),
|
||||||
|
lambda x: AspectRatioPresetType(int(x)),
|
||||||
|
lambda x: _g_AspectRatioPresetTypeDesc[x][0],
|
||||||
|
lambda x: _g_AspectRatioPresetTypeDesc[x][1],
|
||||||
|
lambda _: ""
|
||||||
|
)
|
||||||
|
|
||||||
|
def preset_virtools_camera_aspect_ratio(cam: bpy.types.Camera, preset_type: AspectRatioPresetType) -> None:
|
||||||
|
# get raw data from it
|
||||||
|
rawdata = get_raw_virtools_camera(cam)
|
||||||
|
# modify its aspect ratio
|
||||||
|
rawdata.mAspectRatio = preset_type.to_aspect_ratio()
|
||||||
|
# rewrite it.
|
||||||
|
set_raw_virtools_camera(cam, rawdata)
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Operators
|
||||||
|
|
||||||
class BBP_OT_apply_virtools_camera(bpy.types.Operator):
|
class BBP_OT_apply_virtools_camera(bpy.types.Operator):
|
||||||
"""Apply Virtools Camera to Blender Camera except Resolution."""
|
"""Apply Virtools Camera to Blender Camera except Resolution."""
|
||||||
@@ -254,7 +297,41 @@ class BBP_OT_apply_virtools_camera_resolution(bpy.types.Operator):
|
|||||||
apply_to_blender_scene_resolution(cam)
|
apply_to_blender_scene_resolution(cam)
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
# Display Panel
|
class BBP_OT_preset_virtools_camera_aspect_ratio(bpy.types.Operator):
|
||||||
|
"""Preset Virtools Camera Aspect Ratio with Virtools Presets."""
|
||||||
|
bl_idname = "bbp.preset_virtools_camera_aspect_ratio"
|
||||||
|
bl_label = "Preset Virtools Camera Aspect Ratio"
|
||||||
|
bl_options = {'UNDO'}
|
||||||
|
bl_translation_context = 'BBP_OT_preset_virtools_camera_aspect_ratio'
|
||||||
|
|
||||||
|
preset_type: bpy.props.EnumProperty(
|
||||||
|
name = "Preset",
|
||||||
|
description = "The preset which you want to apply.",
|
||||||
|
items = _g_Helper_AspectRatioPresetType.generate_items(),
|
||||||
|
translation_context = 'BBP_OT_preset_virtools_camera_aspect_ratio/property'
|
||||||
|
) # type: ignore
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return context.camera is not None
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
wm = context.window_manager
|
||||||
|
return wm.invoke_props_dialog(self)
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
self.layout.prop(self, "preset_type")
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
# get essential value
|
||||||
|
cam: bpy.types.Camera = context.camera
|
||||||
|
expected_preset: AspectRatioPresetType = _g_Helper_AspectRatioPresetType.get_selection(self.preset_type)
|
||||||
|
|
||||||
|
# apply preset to material
|
||||||
|
preset_virtools_camera_aspect_ratio(cam, expected_preset)
|
||||||
|
return {'FINISHED'}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
class BBP_PT_virtools_camera(bpy.types.Panel):
|
class BBP_PT_virtools_camera(bpy.types.Panel):
|
||||||
"""Show Virtools Camera Properties"""
|
"""Show Virtools Camera Properties"""
|
||||||
@@ -314,7 +391,9 @@ class BBP_PT_virtools_camera(bpy.types.Panel):
|
|||||||
|
|
||||||
# aspect ratio
|
# aspect ratio
|
||||||
layout.separator()
|
layout.separator()
|
||||||
layout.label(text='Aspect Ratio', text_ctxt='BBP_PT_virtools_camera/draw')
|
row = layout.row()
|
||||||
|
row.label(text='Aspect Ratio', text_ctxt='BBP_PT_virtools_camera/draw')
|
||||||
|
row.operator(BBP_OT_preset_virtools_camera_aspect_ratio.bl_idname, text='', icon = "PRESET")
|
||||||
sublayout = layout.row()
|
sublayout = layout.row()
|
||||||
sublayout.use_property_split = False
|
sublayout.use_property_split = False
|
||||||
sublayout.prop(props, 'aspect_ratio_w', text = '', expand = True)
|
sublayout.prop(props, 'aspect_ratio_w', text = '', expand = True)
|
||||||
@@ -326,6 +405,7 @@ def register() -> None:
|
|||||||
bpy.utils.register_class(BBP_PG_virtools_camera)
|
bpy.utils.register_class(BBP_PG_virtools_camera)
|
||||||
bpy.utils.register_class(BBP_OT_apply_virtools_camera)
|
bpy.utils.register_class(BBP_OT_apply_virtools_camera)
|
||||||
bpy.utils.register_class(BBP_OT_apply_virtools_camera_resolution)
|
bpy.utils.register_class(BBP_OT_apply_virtools_camera_resolution)
|
||||||
|
bpy.utils.register_class(BBP_OT_preset_virtools_camera_aspect_ratio)
|
||||||
bpy.utils.register_class(BBP_PT_virtools_camera)
|
bpy.utils.register_class(BBP_PT_virtools_camera)
|
||||||
|
|
||||||
# add into camera metadata
|
# add into camera metadata
|
||||||
@@ -336,6 +416,7 @@ def unregister() -> None:
|
|||||||
del bpy.types.Camera.virtools_camera
|
del bpy.types.Camera.virtools_camera
|
||||||
|
|
||||||
bpy.utils.unregister_class(BBP_PT_virtools_camera)
|
bpy.utils.unregister_class(BBP_PT_virtools_camera)
|
||||||
|
bpy.utils.unregister_class(BBP_OT_preset_virtools_camera_aspect_ratio)
|
||||||
bpy.utils.unregister_class(BBP_OT_apply_virtools_camera_resolution)
|
bpy.utils.unregister_class(BBP_OT_apply_virtools_camera_resolution)
|
||||||
bpy.utils.unregister_class(BBP_OT_apply_virtools_camera)
|
bpy.utils.unregister_class(BBP_OT_apply_virtools_camera)
|
||||||
bpy.utils.unregister_class(BBP_PG_virtools_camera)
|
bpy.utils.unregister_class(BBP_PG_virtools_camera)
|
||||||
|
|||||||
@@ -179,7 +179,6 @@ class BBP_MT_View3DMenu(bpy.types.Menu):
|
|||||||
layout.operator(OP_OBJECT_legacy_align.BBP_OT_legacy_align.bl_idname)
|
layout.operator(OP_OBJECT_legacy_align.BBP_OT_legacy_align.bl_idname)
|
||||||
layout.separator()
|
layout.separator()
|
||||||
layout.label(text='Camera', icon='CAMERA_DATA', text_ctxt='BBP_MT_View3DMenu/draw')
|
layout.label(text='Camera', icon='CAMERA_DATA', text_ctxt='BBP_MT_View3DMenu/draw')
|
||||||
layout.operator(OP_OBJECT_game_view.BBP_OT_game_resolution.bl_idname)
|
|
||||||
layout.operator(OP_OBJECT_game_view.BBP_OT_game_camera.bl_idname)
|
layout.operator(OP_OBJECT_game_view.BBP_OT_game_camera.bl_idname)
|
||||||
layout.separator()
|
layout.separator()
|
||||||
layout.label(text='Select', icon='SELECT_SET', text_ctxt='BBP_MT_View3DMenu/draw')
|
layout.label(text='Select', icon='SELECT_SET', text_ctxt='BBP_MT_View3DMenu/draw')
|
||||||
|
|||||||
Reference in New Issue
Block a user