2023-12-09 17:42:03 +08:00
import bpy , mathutils
import math , typing
2023-12-07 21:28:23 +08:00
from . import UTIL_functions , UTIL_icons_manager , UTIL_naming_convension
from . import PROP_ballance_element , PROP_virtools_group
2023-12-06 17:16:31 +08:00
2023-12-09 17:42:03 +08:00
#region Param Help Classes
class ComponentSectorParam ():
component_sector : bpy . props . IntProperty (
name = "Sector" ,
description = "Define which sector the object will be grouped in" ,
min = 1 , max = 999 ,
soft_min = 1 , soft_max = 8 ,
default = 1 ,
)
def general_get_component_sector ( self ) -> int :
return self . component_sector
def draw_component_sector_params ( self , layout : bpy . types . UILayout ) -> None :
layout . prop ( self , 'component_sector' )
class ComponentCountParam ():
component_count : bpy . props . IntProperty (
name = "Count" ,
description = "The count of components you want to generate" ,
min = 1 , max = 64 ,
soft_min = 1 , soft_max = 32 ,
default = 1 ,
)
def general_get_component_count ( self ) -> int :
return self . component_count
def draw_component_count_params ( self , layout : bpy . types . UILayout ) -> None :
layout . prop ( self , 'component_count' )
#endregion
2023-12-07 21:28:23 +08:00
#region Help Classes & Functions
2023-12-06 17:16:31 +08:00
2023-12-07 21:28:23 +08:00
def _get_component_info ( comp_type : PROP_ballance_element . BallanceElementType , comp_sector : int ) -> UTIL_naming_convension . BallanceObjectInfo :
match ( comp_type ):
2023-12-09 17:42:03 +08:00
# process for 2 special unique components
2023-12-07 21:28:23 +08:00
case PROP_ballance_element . BallanceElementType . PS_FourFlames :
return UTIL_naming_convension . BallanceObjectInfo . create_from_others ( UTIL_naming_convension . BallanceObjectType . LEVEL_START )
case PROP_ballance_element . BallanceElementType . PE_Balloon :
return UTIL_naming_convension . BallanceObjectInfo . create_from_others ( UTIL_naming_convension . BallanceObjectType . LEVEL_END )
# process naming convention required special components
case PROP_ballance_element . BallanceElementType . PC_TwoFlames :
return UTIL_naming_convension . BallanceObjectInfo . create_from_checkpoint ( comp_sector )
case PROP_ballance_element . BallanceElementType . PR_Resetpoint :
return UTIL_naming_convension . BallanceObjectInfo . create_from_resetpoint ( comp_sector )
# process for other components
case _ :
2023-12-09 17:42:03 +08:00
return UTIL_naming_convension . BallanceObjectInfo . create_from_component (
PROP_ballance_element . get_ballance_element_name ( comp_type ),
comp_sector
)
2023-12-07 21:28:23 +08:00
def _set_component_by_info ( obj : bpy . types . Object , info : UTIL_naming_convension . BallanceObjectInfo ) -> None :
# set component name and grouping it into virtools group at the same time
# set name first
if not UTIL_naming_convension . YYCToolchainConvention . set_to_object ( obj , info , None ):
raise UTIL_functions . BBPException ( 'impossible fail to set component name.' )
# set vt group next
if not UTIL_naming_convension . VirtoolsGroupConvention . set_to_object ( obj , info , None ):
raise UTIL_functions . BBPException ( 'impossible fail to set component virtools groups.' )
def _check_component_existance ( comp_type : PROP_ballance_element . BallanceElementType , comp_sector : int ) -> str | None :
"""
Check the existance of 4 special components name, PS, PE, PC, PR
These 4 components will have special name.
@return Return name if selected component is one of PS, PE, PC, PR and there already is a name conflict, otherwise None.
"""
# check component type requirements
match ( comp_type ):
case PROP_ballance_element . BallanceElementType . PS_FourFlames | PROP_ballance_element . BallanceElementType . PE_Balloon | PROP_ballance_element . BallanceElementType . PC_TwoFlames | PROP_ballance_element . BallanceElementType . PR_Resetpoint :
pass # exit match and start check
case _ :
return None # return, do not check
# get info
comp_info : UTIL_naming_convension . BallanceObjectInfo = _get_component_info ( comp_type , comp_sector )
# get expected name
expect_name : str | None = UTIL_naming_convension . YYCToolchainConvention . set_to_name ( comp_info , None )
if expect_name is None :
raise UTIL_functions . BBPException ( 'impossible fail to get component name.' )
# check expected name
if expect_name in bpy . data . objects : return expect_name
else : return None
2023-12-15 21:57:50 +08:00
def _general_create_component (
comp_type : PROP_ballance_element . BallanceElementType ,
comp_sector : int ,
comp_count : int ,
comp_offset : typing . Callable [[ int ], mathutils . Matrix ]
) -> None :
2023-12-09 17:42:03 +08:00
"""
General component creation function.
2023-12-07 21:28:23 +08:00
2023-12-09 17:42:03 +08:00
@param comp_type[in] The component type created.
@param comp_sector[in] The sector param which passed to other functions. For non-sector component, pass any number.
@param comp_count[in] The count of created component. For single component creation, please pass 1.
@param comp_offset[in] The function pointer which receive 1 argument indicating the index of object which we want to get its offset.
You can pass `lambda _: mathutils.Matrix.Identity(4)` to get zero offset for every items.
You can pass `lambda _: mathutils.Matrix( xxx )` to get same offset for every items.
You can pass `lambda i: mathutils.Matrix( func(i) )` to get index based offset for each items.
The offset is the offset to the origin point, not the previous object.
"""
# get element info first
ele_info : UTIL_naming_convension . BallanceObjectInfo = _get_component_info ( comp_type , comp_sector )
# create blc element context
with PROP_ballance_element . BallanceElementsHelper ( bpy . context . scene ) as creator :
# object creation counter
for i in range ( comp_count ):
# get mesh from element context, and create with empty name first. we assign name later.
obj : bpy . types . Object = bpy . data . objects . new ( '' , creator . get_element ( comp_type ))
# assign virtools group, object name by we gotten element info.
_set_component_by_info ( obj , ele_info )
# add into scene and move to cursor
UTIL_functions . add_into_scene_and_move_to_cursor ( obj )
# move with extra offset by calling offset getter
obj . matrix_world = obj . matrix_world @ comp_offset ( i )
2023-12-07 21:28:23 +08:00
#endregion
2023-12-06 17:16:31 +08:00
2023-12-09 17:42:03 +08:00
#region Noemal Component Adder
2023-12-15 21:57:50 +08:00
# element enum prop helper
def _get_component_icon_by_name ( elename : str ):
icon : int | None = UTIL_icons_manager . get_component_icon ( elename )
if icon is None : return UTIL_icons_manager . get_empty_icon ()
else : return icon
_g_EnumHelper_Component : UTIL_functions . EnumPropHelper = UTIL_functions . EnumPropHelper (
PROP_ballance_element . BallanceElementType ,
lambda x : x . name ,
lambda x : '' ,
lambda x : _get_component_icon_by_name ( PROP_ballance_element . get_ballance_element_name ( x )),
)
2023-12-09 17:42:03 +08:00
class BBP_OT_add_component ( bpy . types . Operator , ComponentSectorParam ):
2023-12-07 21:28:23 +08:00
"""Add Component"""
2023-12-06 17:16:31 +08:00
bl_idname = "bbp.add_component"
2023-12-07 21:28:23 +08:00
bl_label = "Add Component"
2023-12-06 17:16:31 +08:00
bl_options = { 'UNDO' }
2023-12-07 21:28:23 +08:00
component_type : bpy . props . EnumProperty (
2023-12-06 17:16:31 +08:00
name = "Type" ,
2023-12-07 21:28:23 +08:00
description = "This component type" ,
2023-12-15 21:57:50 +08:00
items = _g_EnumHelper_Component . generate_items (),
2023-12-06 17:16:31 +08:00
)
def invoke ( self , context , event ):
wm = context . window_manager
return wm . invoke_props_dialog ( self )
def draw ( self , context ):
layout = self . layout
2023-12-07 21:28:23 +08:00
# show type
layout . prop ( self , "component_type" )
2023-12-06 17:16:31 +08:00
2023-12-07 21:28:23 +08:00
# only show sector for non-PE/PS component
2023-12-15 21:57:50 +08:00
eletype : PROP_ballance_element . BallanceElementType = _g_EnumHelper_Component . get_selection ( self . component_type )
2023-12-07 21:28:23 +08:00
if eletype != PROP_ballance_element . BallanceElementType . PS_FourFlames and eletype != PROP_ballance_element . BallanceElementType . PE_Balloon :
2023-12-09 17:42:03 +08:00
self . draw_component_sector_params ( layout )
2023-12-07 21:28:23 +08:00
# check for some special components and show warning
2023-12-15 21:57:50 +08:00
elename : str | None = _check_component_existance ( _g_EnumHelper_Component . get_selection ( self . component_type ), self . general_get_component_sector ())
2023-12-07 21:28:23 +08:00
if elename is not None :
layout . label ( text = f 'Warning: { elename } already exist.' )
2023-12-06 17:16:31 +08:00
def execute ( self , context ):
2023-12-09 17:42:03 +08:00
# call general creator
_general_create_component (
2023-12-15 21:57:50 +08:00
_g_EnumHelper_Component . get_selection ( self . component_type ),
2023-12-09 17:42:03 +08:00
self . general_get_component_sector (),
1 , # only create one
lambda _ : mathutils . Matrix . Identity ( 4 )
)
2023-12-06 17:16:31 +08:00
return { 'FINISHED' }
2023-12-15 21:57:50 +08:00
@staticmethod
def draw_blc_menu ( layout : bpy . types . UILayout ):
2023-12-06 17:16:31 +08:00
for item in PROP_ballance_element . BallanceElementType :
2023-12-09 17:42:03 +08:00
item_name : str = PROP_ballance_element . get_ballance_element_name ( item )
2023-12-06 17:16:31 +08:00
cop = layout . operator (
2023-12-15 21:57:50 +08:00
BBP_OT_add_component . bl_idname , text = item_name ,
2023-12-14 18:03:03 +08:00
icon_value = UTIL_icons_manager . get_component_icon ( item_name )
2023-12-09 17:42:03 +08:00
)
2023-12-15 21:57:50 +08:00
cop . component_type = _g_EnumHelper_Component . to_selection ( item )
2023-12-06 17:16:31 +08:00
2023-12-09 17:42:03 +08:00
#endregion
#region Nong Comp Adder
class BBP_OT_add_nong_extra_point ( bpy . types . Operator , ComponentSectorParam , ComponentCountParam ):
"""Add Nong Extra Point"""
bl_idname = "bbp.add_nong_extra_point"
bl_label = "Nong Extra Point"
bl_options = { 'REGISTER' , 'UNDO' }
def draw ( self , context ):
layout = self . layout
self . draw_component_sector_params ( layout )
self . draw_component_count_params ( layout )
def execute ( self , context ):
# create objects and rotate it by a certain degree calculated by its index
# calc percent first
percent : float = 1.0 / self . general_get_component_count ()
# create elements
_general_create_component (
PROP_ballance_element . BallanceElementType . P_Extra_Point ,
self . general_get_component_sector (),
self . general_get_component_count (),
lambda i : mathutils . Matrix . Rotation ( percent * i * math . pi * 2 , 4 , 'Z' )
)
return { 'FINISHED' }
2023-12-15 21:57:50 +08:00
@staticmethod
def draw_blc_menu ( layout : bpy . types . UILayout ):
2023-12-09 17:42:03 +08:00
layout . operator (
BBP_OT_add_nong_extra_point . bl_idname ,
2023-12-14 18:03:03 +08:00
icon_value = UTIL_icons_manager . get_component_icon (
2023-12-09 17:42:03 +08:00
PROP_ballance_element . get_ballance_element_name ( PROP_ballance_element . BallanceElementType . P_Extra_Point )
)
)
#endregion
#region Series Comp Adder
class BBP_OT_add_tilting_block_series ( bpy . types . Operator , ComponentSectorParam , ComponentCountParam ):
"""Add Tilting Block Series"""
bl_idname = "bbp.add_tilting_block_series"
bl_label = "Tilting Block Series"
bl_options = { 'REGISTER' , 'UNDO' }
component_span : bpy . props . FloatProperty (
name = "Span" ,
description = "The distance between each titling blocks" ,
min = 0.0 , max = 100.0 ,
soft_min = 0.0 , soft_max = 12.0 ,
default = 6.0022 ,
)
def draw ( self , context ):
layout = self . layout
self . draw_component_sector_params ( layout )
self . draw_component_count_params ( layout )
layout . prop ( self , 'component_span' )
def execute ( self , context ):
# create objects and move it by delta
# get span first
span : float = self . component_span
# create elements
_general_create_component (
PROP_ballance_element . BallanceElementType . P_Modul_41 ,
self . general_get_component_sector (),
self . general_get_component_count (),
lambda i : mathutils . Matrix . Translation ( mathutils . Vector (( span * i , 0.0 , 0.0 ))) # move with extra delta in x axis
)
return { 'FINISHED' }
2023-12-15 21:57:50 +08:00
@staticmethod
def draw_blc_menu ( layout : bpy . types . UILayout ):
2023-12-09 17:42:03 +08:00
layout . operator (
BBP_OT_add_tilting_block_series . bl_idname ,
2023-12-14 18:03:03 +08:00
icon_value = UTIL_icons_manager . get_component_icon (
2023-12-09 17:42:03 +08:00
PROP_ballance_element . get_ballance_element_name ( PROP_ballance_element . BallanceElementType . P_Modul_41 )
)
)
class BBP_OT_add_ventilator_series ( bpy . types . Operator , ComponentSectorParam , ComponentCountParam ):
"""Add Ventilator Series"""
bl_idname = "bbp.add_ventilator_series"
bl_label = "Ventilator Series"
bl_options = { 'REGISTER' , 'UNDO' }
component_translation : bpy . props . FloatVectorProperty (
name = "Delta Vector" ,
description = "The translation between each ventilators. You can use this property to implement vertical or horizontal ventilator series. Set all factors to zero can get Nong ventilator." ,
size = 3 , subtype = 'TRANSLATION' ,
min = 0.0 , max = 100.0 ,
soft_min = 0.0 , soft_max = 50.0 ,
default = ( 0.0 , 0.0 , 15.0 ),
)
def draw ( self , context ):
layout = self . layout
self . draw_component_sector_params ( layout )
self . draw_component_count_params ( layout )
layout . prop ( self , 'component_translation' )
def execute ( self , context ):
# create objects and move it by delta
# get translation first
translation : mathutils . Vector = mathutils . Vector ( self . component_translation )
# create elements
_general_create_component (
PROP_ballance_element . BallanceElementType . P_Modul_18 ,
self . general_get_component_sector (),
self . general_get_component_count (),
lambda i : mathutils . Matrix . Translation ( i * translation ) # move with extra translation
)
return { 'FINISHED' }
2023-12-15 21:57:50 +08:00
@staticmethod
def draw_blc_menu ( layout : bpy . types . UILayout ):
2023-12-09 17:42:03 +08:00
layout . operator (
BBP_OT_add_ventilator_series . bl_idname ,
2023-12-14 18:03:03 +08:00
icon_value = UTIL_icons_manager . get_component_icon (
2023-12-09 17:42:03 +08:00
PROP_ballance_element . get_ballance_element_name ( PROP_ballance_element . BallanceElementType . P_Modul_18 )
)
)
#endregion
#region Comp Pair Adder
class BBP_OT_add_sector_component_pair ( bpy . types . Operator , ComponentSectorParam ):
"""Add Sector Pair, both check point and reset point."""
bl_idname = "bbp.add_sector_component_pair"
bl_label = "Sector Pair"
bl_options = { 'UNDO' }
def __get_checkpoint ( self ) -> tuple [ PROP_ballance_element . BallanceElementType , int ]:
if self . general_get_component_sector () == 1 :
return ( PROP_ballance_element . BallanceElementType . PS_FourFlames , 1 )
else :
# the sector of two flames should be `sector - 1` because first one was occupied by FourFlams
return ( PROP_ballance_element . BallanceElementType . PC_TwoFlames , self . general_get_component_sector () - 1 )
def __get_resetpoint ( self ) -> tuple [ PROP_ballance_element . BallanceElementType , int ]:
# resetpoint's sector is just sector it self.
return ( PROP_ballance_element . BallanceElementType . PR_Resetpoint , self . general_get_component_sector ())
def invoke ( self , context , event ):
wm = context . window_manager
return wm . invoke_props_dialog ( self )
def draw ( self , context ):
layout = self . layout
self . draw_component_sector_params ( layout )
# check checkpoint and resetpoint name conflict and show warnings
( checkp_ty , checkp_sector ) = self . __get_checkpoint ()
elename : str | None = _check_component_existance ( checkp_ty , checkp_sector )
if elename is not None :
layout . label ( text = f 'Warning: { elename } already exist.' )
( resetp_ty , resetp_sector ) = self . __get_resetpoint ()
elename = _check_component_existance ( resetp_ty , resetp_sector )
if elename is not None :
layout . label ( text = f 'Warning: { elename } already exist.' )
def execute ( self , context ):
# create checkpoint and resetpoint individually in element context
# get type and sector data first
( checkp_ty , checkp_sector ) = self . __get_checkpoint ()
( resetp_ty , resetp_sector ) = self . __get_resetpoint ()
# calc resetpoint offset
# resetpoint need a extra offset between checkpoint
# but it is different in FourFlams and TwoFlams
resetp_offset : float
if checkp_ty == PROP_ballance_element . BallanceElementType . PS_FourFlames :
resetp_offset = 3.25
else :
resetp_offset = 2.0
# add elements
# create checkpoint
_general_create_component (
checkp_ty ,
checkp_sector ,
1 , # only create one
lambda _ : mathutils . Matrix . Identity ( 4 )
)
# create resetpoint
_general_create_component (
resetp_ty ,
resetp_sector ,
1 , # only create one
lambda _ : mathutils . Matrix . Translation ( mathutils . Vector (( 0.0 , 0.0 , resetp_offset ))) # apply resetpoint offset
)
return { 'FINISHED' }
2023-12-15 21:57:50 +08:00
@staticmethod
def draw_blc_menu ( layout : bpy . types . UILayout ):
2023-12-09 17:42:03 +08:00
layout . operator (
BBP_OT_add_sector_component_pair . bl_idname ,
2023-12-14 18:03:03 +08:00
icon_value = UTIL_icons_manager . get_component_icon (
2023-12-09 17:42:03 +08:00
PROP_ballance_element . get_ballance_element_name ( PROP_ballance_element . BallanceElementType . PR_Resetpoint )
)
)
#endregion
2023-12-06 17:16:31 +08:00
def register ():
bpy . utils . register_class ( BBP_OT_add_component )
2023-12-09 17:42:03 +08:00
bpy . utils . register_class ( BBP_OT_add_nong_extra_point )
bpy . utils . register_class ( BBP_OT_add_tilting_block_series )
bpy . utils . register_class ( BBP_OT_add_ventilator_series )
bpy . utils . register_class ( BBP_OT_add_sector_component_pair )
2023-12-06 17:16:31 +08:00
def unregister ():
2023-12-09 17:42:03 +08:00
bpy . utils . unregister_class ( BBP_OT_add_sector_component_pair )
bpy . utils . unregister_class ( BBP_OT_add_ventilator_series )
bpy . utils . unregister_class ( BBP_OT_add_tilting_block_series )
bpy . utils . unregister_class ( BBP_OT_add_nong_extra_point )
2023-12-06 17:16:31 +08:00
bpy . utils . unregister_class ( BBP_OT_add_component )