BallanceBlenderHelper/bbp_ng/OP_ADDS_component.py

423 lines
17 KiB
Python
Raw Normal View History

2023-12-09 17:42:03 +08:00
import bpy, mathutils
import math, typing
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
#region Help Classes & Functions
2023-12-06 17:16:31 +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
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
)
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-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)
#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):
"""Add Component"""
2023-12-06 17:16:31 +08:00
bl_idname = "bbp.add_component"
bl_label = "Add Component"
2023-12-06 17:16:31 +08:00
bl_options = {'UNDO'}
component_type: bpy.props.EnumProperty(
2023-12-06 17:16:31 +08:00
name = "Type",
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
# show type
layout.prop(self, "component_type")
2023-12-06 17:16:31 +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)
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)
# 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())
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)