1 Commits

Author SHA1 Message Date
f29e4e9478 feat: support macos arm64 arch 2025-08-04 11:17:46 +08:00
160 changed files with 5684 additions and 10973 deletions

11
.gitattributes vendored
View File

@ -1,6 +1,7 @@
# All PNG image are binary # all png are binary
*.png binary *.png binary
# Element placeholder mesh should be save as binary # our generated mesh should be save as binary
*.ph binary *.bin binary
# Raw json data should be binary, although i edit it manually # the raw json data should be binary
assets/jsons/*.json binary # although i edit it manually
bbp_ng/raw_jsons/*.json binary

4
.gitignore vendored
View File

@ -1,3 +1,3 @@
## ===== Personal ===== ## ===== Personal =====
# Disable VSCode # Disable distribution build folder
.vscode/ redist/

13
bbp_ng/.gitignore vendored
View File

@ -1,15 +1,20 @@
## ===== Personal ===== ## ===== Personal =====
# Do not include PyBMap in this repository. # Do not include PyBMap in this repository.
# Order user build and fetch it manually. # Order build fetch it manually.
PyBMap/ PyBMap/
# Disable generated assets but keep the directory hierarchy. # Disable generated icons and jsons but keep the directory hierarchy.
icons/* icons/*
!icons/.gitkeep !icons/.gitkeep
jsons/* jsons/*
!jsons/.gitkeep !jsons/.gitkeep
meshes/*
!meshes/.gitkeep # Do not include intermidiate translation template (POT)
# NOTE: it seems that Python default gitignore (written following) disable POT file in default.
# so, as it wish, I also remove it from git.
i18n/*
!i18n/blender.pot
!i18n/*.po
## ===== Python ===== ## ===== Python =====
# Byte-compiled / optimized / DLL files # Byte-compiled / optimized / DLL files

View File

@ -5,7 +5,7 @@ from . import UTIL_functions, UTIL_translation, UTIL_bme
#region BME Adder #region BME Adder
_g_EnumHelper_BmeStructType = UTIL_bme.EnumPropHelper() _g_EnumHelper_BmeStructType: UTIL_bme.EnumPropHelper = UTIL_bme.EnumPropHelper()
class BBP_PG_bme_adder_cfgs(bpy.types.PropertyGroup): class BBP_PG_bme_adder_cfgs(bpy.types.PropertyGroup):
prop_int: bpy.props.IntProperty( prop_int: bpy.props.IntProperty(
@ -37,9 +37,8 @@ class BBP_OT_add_bme_struct(bpy.types.Operator):
bl_options = {'REGISTER', 'UNDO'} bl_options = {'REGISTER', 'UNDO'}
bl_translation_context = 'BBP_OT_add_bme_struct' bl_translation_context = 'BBP_OT_add_bme_struct'
# YYC MARK: ## There is a compromise due to the shitty Blender design.
# ===== 20231217 ===== #
# There is a compromise due to the shitty Blender design.
# The passed `self` of Blender Property update function is not the instance of operator, # The passed `self` of Blender Property update function is not the instance of operator,
# but a simple OperatorProperties. # but a simple OperatorProperties.
# It mean that I can not visit the full operator, only what I can do is visit existing # It mean that I can not visit the full operator, only what I can do is visit existing
@ -53,28 +52,24 @@ class BBP_OT_add_bme_struct(bpy.types.Operator):
# Otherwise do nothing. # Otherwise do nothing.
# #
# Reference: https://docs.blender.org/api/current/bpy.props.html#update-example # Reference: https://docs.blender.org/api/current/bpy.props.html#update-example
#
# ===== 20250131 ===== ## Compromise used "outdated" flag.
# There is a fatal performance bug when I adding BME operator list into 3D View sidebar panels (N Menu). outdated_flag: bpy.props.BoolProperty(
# It will cause calling my Panel's `draw` function infinityly of Panel in each render tick, # TR: Property not showen should not have name and desc.
# which calls `BBP_OT_add_bme_struct.draw_blc_menu` directly, # name = "Outdated Type",
# eat too much CPU and GPU resources and make the whole Blender be laggy. # description = "Internal flag.",
# options = {'HIDDEN', 'SKIP_SAVE'},
# After some research, I found that if I comment the parameter `update` of the member `bme_struct_type`, default = False
# everything will be resolved. ) # type: ignore
# It even doesn't work that do nothing in update function.
# So I realize that sidebar panel may not be compatible with update function.
# After reading the note written above, I decide to remove the whole feature of this ugly implementation,
# so that I need to remove the ability that changing BME prototype type in left-bottom window.
#
# After talking with the requestor of this feature, ZZQ,
# he agree with my decision and I think this change will not broke any experience of BBP.
## A BME struct cfgs descriptor cache list ## A BME struct cfgs descriptor cache list
# Not only the descriptor self, also the cfg associated index in bme_struct_cfgs # Not only the descriptor self, also the cfg associated index in bme_struct_cfgs
bme_struct_cfg_index_cache: list[tuple[UTIL_bme.PrototypeShowcaseCfgDescriptor, int]] bme_struct_cfg_index_cache: list[tuple[UTIL_bme.PrototypeShowcaseCfgDescriptor, int]]
def __build_bme_struct_cfg_index_cache(self) -> None: def __internal_update_bme_struct_type(self) -> None:
# if not outdated, skip
if not self.outdated_flag: return
# get available cfg entires # get available cfg entires
cfgs: typing.Iterator[UTIL_bme.PrototypeShowcaseCfgDescriptor] cfgs: typing.Iterator[UTIL_bme.PrototypeShowcaseCfgDescriptor]
cfgs = _g_EnumHelper_BmeStructType.get_bme_showcase_cfgs( cfgs = _g_EnumHelper_BmeStructType.get_bme_showcase_cfgs(
@ -130,10 +125,21 @@ class BBP_OT_add_bme_struct(bpy.types.Operator):
for i in range(6): for i in range(6):
op_cfgs_visitor[cfg_index + i].prop_bool = default_values[i] op_cfgs_visitor[cfg_index + i].prop_bool = default_values[i]
# reset outdated flag
self.outdated_flag = False
# the updator for default side value
def bme_struct_type_updated(self, context):
# update outdated flag
self.outdated_flag = True
# blender required
return None
bme_struct_type: bpy.props.EnumProperty( bme_struct_type: bpy.props.EnumProperty(
name = "Type", name = "Type",
description = "The type of BME structure.", description = "The type of BME structure.",
items = _g_EnumHelper_BmeStructType.generate_items(), items = _g_EnumHelper_BmeStructType.generate_items(),
update = bme_struct_type_updated,
translation_context = 'BBP_OT_add_bme_struct/property' translation_context = 'BBP_OT_add_bme_struct/property'
) # type: ignore ) # type: ignore
@ -174,16 +180,19 @@ class BBP_OT_add_bme_struct(bpy.types.Operator):
self.extra_translation = (0.0, 0.0, 0.0) self.extra_translation = (0.0, 0.0, 0.0)
self.extra_rotation = (0.0, 0.0, 0.0) self.extra_rotation = (0.0, 0.0, 0.0)
self.extra_scale = (1.0, 1.0, 1.0) self.extra_scale = (1.0, 1.0, 1.0)
# create internal list # create internal list
self.bme_struct_cfg_index_cache = [] self.bme_struct_cfg_index_cache = []
# call internal builder to load prototype data inside it # trigger default bme struct type updator
self.__build_bme_struct_cfg_index_cache() self.bme_struct_type_updated(context)
# call internal updator
self.__internal_update_bme_struct_type()
# run execute() function # run execute() function
return self.execute(context) return self.execute(context)
def execute(self, context): def execute(self, context):
# call internal updator
self.__internal_update_bme_struct_type()
# create cfg visitor # create cfg visitor
op_cfgs_visitor: UTIL_functions.CollectionVisitor[BBP_PG_bme_adder_cfgs] op_cfgs_visitor: UTIL_functions.CollectionVisitor[BBP_PG_bme_adder_cfgs]
op_cfgs_visitor = UTIL_functions.CollectionVisitor(self.bme_struct_cfgs) op_cfgs_visitor = UTIL_functions.CollectionVisitor(self.bme_struct_cfgs)
@ -222,8 +231,13 @@ class BBP_OT_add_bme_struct(bpy.types.Operator):
return {'FINISHED'} return {'FINISHED'}
def draw(self, context): def draw(self, context):
# call internal updator
self.__internal_update_bme_struct_type()
# start drawing # start drawing
layout: bpy.types.UILayout = self.layout layout: bpy.types.UILayout = self.layout
# show type
layout.prop(self, 'bme_struct_type')
# create cfg visitor # create cfg visitor
op_cfgs_visitor: UTIL_functions.CollectionVisitor[BBP_PG_bme_adder_cfgs] op_cfgs_visitor: UTIL_functions.CollectionVisitor[BBP_PG_bme_adder_cfgs]
@ -286,7 +300,7 @@ class BBP_OT_add_bme_struct(bpy.types.Operator):
cls.bl_idname, cls.bl_idname,
text = _g_EnumHelper_BmeStructType.get_bme_showcase_title(ident), text = _g_EnumHelper_BmeStructType.get_bme_showcase_title(ident),
icon_value = _g_EnumHelper_BmeStructType.get_bme_showcase_icon(ident), icon_value = _g_EnumHelper_BmeStructType.get_bme_showcase_icon(ident),
text_ctxt = UTIL_translation.build_prototype_showcase_context(ident), text_ctxt = UTIL_translation.build_prototype_showcase_context(ident)
) )
# and assign its init type value # and assign its init type value
cop.bme_struct_type = _g_EnumHelper_BmeStructType.to_selection(ident) cop.bme_struct_type = _g_EnumHelper_BmeStructType.to_selection(ident)

View File

@ -1,6 +1,6 @@
import bpy, mathutils import bpy, mathutils
import math, typing import math, typing
from . import UTIL_functions, UTIL_icons_manager, UTIL_naming_convention from . import UTIL_functions, UTIL_icons_manager, UTIL_naming_convension
from . import PROP_ballance_element, PROP_virtools_group, PROP_ballance_map_info from . import PROP_ballance_element, PROP_virtools_group, PROP_ballance_map_info
#region Param Help Classes #region Param Help Classes
@ -41,33 +41,33 @@ class ComponentCountParam():
#region Help Classes & Functions #region Help Classes & Functions
def _get_component_info(comp_type: PROP_ballance_element.BallanceElementType, comp_sector: int) -> UTIL_naming_convention.BallanceObjectInfo: def _get_component_info(comp_type: PROP_ballance_element.BallanceElementType, comp_sector: int) -> UTIL_naming_convension.BallanceObjectInfo:
match(comp_type): match(comp_type):
# process for 2 special unique components # process for 2 special unique components
case PROP_ballance_element.BallanceElementType.PS_FourFlames: case PROP_ballance_element.BallanceElementType.PS_FourFlames:
return UTIL_naming_convention.BallanceObjectInfo.create_from_others(UTIL_naming_convention.BallanceObjectType.LEVEL_START) return UTIL_naming_convension.BallanceObjectInfo.create_from_others(UTIL_naming_convension.BallanceObjectType.LEVEL_START)
case PROP_ballance_element.BallanceElementType.PE_Balloon: case PROP_ballance_element.BallanceElementType.PE_Balloon:
return UTIL_naming_convention.BallanceObjectInfo.create_from_others(UTIL_naming_convention.BallanceObjectType.LEVEL_END) return UTIL_naming_convension.BallanceObjectInfo.create_from_others(UTIL_naming_convension.BallanceObjectType.LEVEL_END)
# process naming convention required special components # process naming convention required special components
case PROP_ballance_element.BallanceElementType.PC_TwoFlames: case PROP_ballance_element.BallanceElementType.PC_TwoFlames:
return UTIL_naming_convention.BallanceObjectInfo.create_from_checkpoint(comp_sector) return UTIL_naming_convension.BallanceObjectInfo.create_from_checkpoint(comp_sector)
case PROP_ballance_element.BallanceElementType.PR_Resetpoint: case PROP_ballance_element.BallanceElementType.PR_Resetpoint:
return UTIL_naming_convention.BallanceObjectInfo.create_from_resetpoint(comp_sector) return UTIL_naming_convension.BallanceObjectInfo.create_from_resetpoint(comp_sector)
# process for other components # process for other components
case _: case _:
return UTIL_naming_convention.BallanceObjectInfo.create_from_component( return UTIL_naming_convension.BallanceObjectInfo.create_from_component(
PROP_ballance_element.get_ballance_element_name(comp_type), PROP_ballance_element.get_ballance_element_name(comp_type),
comp_sector comp_sector
) )
def _set_component_by_info(obj: bpy.types.Object, info: UTIL_naming_convention.BallanceObjectInfo) -> None: 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 component name and grouping it into virtools group at the same time
# set name first # set name first
if not UTIL_naming_convention.YYCToolchainConvention.set_to_object(obj, info, None): if not UTIL_naming_convension.YYCToolchainConvention.set_to_object(obj, info, None):
raise UTIL_functions.BBPException('impossible fail to set component name.') raise UTIL_functions.BBPException('impossible fail to set component name.')
# set vt group next # set vt group next
if not UTIL_naming_convention.VirtoolsGroupConvention.set_to_object(obj, info, None): if not UTIL_naming_convension.VirtoolsGroupConvention.set_to_object(obj, info, None):
raise UTIL_functions.BBPException('impossible fail to set component virtools groups.') 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: def _check_component_existance(comp_type: PROP_ballance_element.BallanceElementType, comp_sector: int) -> str | None:
@ -85,10 +85,10 @@ def _check_component_existance(comp_type: PROP_ballance_element.BallanceElementT
return None # return, do not check return None # return, do not check
# get info # get info
comp_info: UTIL_naming_convention.BallanceObjectInfo = _get_component_info(comp_type, comp_sector) comp_info: UTIL_naming_convension.BallanceObjectInfo = _get_component_info(comp_type, comp_sector)
# get expected name # get expected name
expect_name: str | None = UTIL_naming_convention.YYCToolchainConvention.set_to_name(comp_info, None) expect_name: str | None = UTIL_naming_convension.YYCToolchainConvention.set_to_name(comp_info, None)
if expect_name is None: if expect_name is None:
raise UTIL_functions.BBPException('impossible fail to get component name.') raise UTIL_functions.BBPException('impossible fail to get component name.')
@ -129,7 +129,7 @@ class _GeneralComponentCreator():
@return The created component instance. @return The created component instance.
""" """
# get element info first # get element info first
ele_info: UTIL_naming_convention.BallanceObjectInfo = _get_component_info(comp_type, comp_sector) ele_info: UTIL_naming_convension.BallanceObjectInfo = _get_component_info(comp_type, comp_sector)
# create blc element context # create blc element context
with PROP_ballance_element.BallanceElementsHelper(bpy.context.scene) as creator: with PROP_ballance_element.BallanceElementsHelper(bpy.context.scene) as creator:
@ -152,12 +152,12 @@ class _GeneralComponentCreator():
enlarged_sector: int enlarged_sector: int
# check component type to get enlarged value # check component type to get enlarged value
match(ele_info.mBasicType): match(ele_info.mBasicType):
case UTIL_naming_convention.BallanceObjectType.COMPONENT: case UTIL_naming_convension.BallanceObjectType.COMPONENT:
enlarged_sector = comp_sector enlarged_sector = comp_sector
case UTIL_naming_convention.BallanceObjectType.CHECKPOINT: case UTIL_naming_convension.BallanceObjectType.CHECKPOINT:
# checkpoint 1 means that there is sector 2, so we plus 1 for it. # checkpoint 1 means that there is sector 2, so we plus 1 for it.
enlarged_sector = comp_sector + 1 enlarged_sector = comp_sector + 1
case UTIL_naming_convention.BallanceObjectType.RESETPOINT: case UTIL_naming_convension.BallanceObjectType.RESETPOINT:
enlarged_sector = comp_sector enlarged_sector = comp_sector
case _: case _:
# this component is not a sector based component # this component is not a sector based component
@ -176,7 +176,7 @@ class _GeneralComponentCreator():
#endregion #endregion
#region Normal Component Adder #region Noemal Component Adder
# element enum prop helper # element enum prop helper
@ -184,7 +184,7 @@ def _get_component_icon_by_name(elename: str):
icon: int | None = UTIL_icons_manager.get_component_icon(elename) icon: int | None = UTIL_icons_manager.get_component_icon(elename)
if icon is None: return UTIL_icons_manager.get_empty_icon() if icon is None: return UTIL_icons_manager.get_empty_icon()
else: return icon else: return icon
_g_EnumHelper_Component = UTIL_functions.EnumPropHelper( _g_EnumHelper_Component: UTIL_functions.EnumPropHelper = UTIL_functions.EnumPropHelper(
PROP_ballance_element.BallanceElementType, PROP_ballance_element.BallanceElementType,
lambda x: str(x.value), lambda x: str(x.value),
lambda x: PROP_ballance_element.BallanceElementType(int(x)), lambda x: PROP_ballance_element.BallanceElementType(int(x)),
@ -217,7 +217,7 @@ class BBP_OT_add_component(bpy.types.Operator, ComponentSectorParam):
layout.prop(self, "component_type") layout.prop(self, "component_type")
# only show sector for non-PE/PS component # only show sector for non-PE/PS component
eletype = _g_EnumHelper_Component.get_selection(self.component_type) 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: if eletype != PROP_ballance_element.BallanceElementType.PS_FourFlames and eletype != PROP_ballance_element.BallanceElementType.PE_Balloon:
self.draw_component_sector_params(layout) self.draw_component_sector_params(layout)
@ -574,17 +574,14 @@ class BBP_OT_add_sector_component_pair(bpy.types.Operator, ComponentSectorParam)
# get type and sector data first # get type and sector data first
(checkp_ty, checkp_sector) = self.__get_checkpoint() (checkp_ty, checkp_sector) = self.__get_checkpoint()
(resetp_ty, resetp_sector) = self.__get_resetpoint() (resetp_ty, resetp_sector) = self.__get_resetpoint()
# calc resetpoint offset and checkpoint rotation # calc resetpoint offset
# resetpoint need a extra offset between checkpoint but it is different in FourFlams and TwoFlams. # resetpoint need a extra offset between checkpoint
# 4 flames startpoint need a extra 90 degree rotation to correspond with ballance asset library (and the direction of resetpoint). # but it is different in FourFlams and TwoFlams
resetp_offset: float resetp_offset: float
checkp_degree: float
if checkp_ty == PROP_ballance_element.BallanceElementType.PS_FourFlames: if checkp_ty == PROP_ballance_element.BallanceElementType.PS_FourFlames:
resetp_offset = 3.65 resetp_offset = 3.25
checkp_degree = 90
else: else:
resetp_offset = 3.3258 resetp_offset = 2.0
checkp_degree = 0
# add elements # add elements
# create checkpoint # create checkpoint
@ -593,7 +590,7 @@ class BBP_OT_add_sector_component_pair(bpy.types.Operator, ComponentSectorParam)
checkp_ty, checkp_ty,
checkp_sector, checkp_sector,
1, # only create one 1, # only create one
lambda _: mathutils.Matrix.Rotation(math.radians(checkp_degree), 4, 'Z') lambda _: mathutils.Matrix.Identity(4)
) )
# create resetpoint # create resetpoint
creator.create_component( creator.create_component(

View File

@ -1,8 +1,8 @@
import bpy, mathutils import bpy, mathutils
from bpy_extras.wm_utils.progress_report import ProgressReport from bpy_extras.wm_utils.progress_report import ProgressReport
import tempfile, os, typing import tempfile, os, typing
from . import PROP_preferences, UTIL_ioport_shared, UTIL_naming_convention from . import PROP_preferences, UTIL_ioport_shared
from . import UTIL_virtools_types, UTIL_functions, UTIL_file_browser, UTIL_blender_mesh, UTIL_ballance_texture from . import UTIL_virtools_types, UTIL_functions, UTIL_file_browser, UTIL_blender_mesh, UTIL_ballance_texture, UTIL_naming_convension
from . import PROP_virtools_group, PROP_virtools_material, PROP_virtools_mesh, PROP_virtools_texture, PROP_virtools_light from . import PROP_virtools_group, PROP_virtools_material, PROP_virtools_mesh, PROP_virtools_texture, PROP_virtools_light
from .PyBMap import bmap_wrapper as bmap from .PyBMap import bmap_wrapper as bmap
@ -190,7 +190,7 @@ def _export_virtools_groups(
# So we create all needed sector group in here to make sure exported virtools file can be read by Ballancde correctly. # So we create all needed sector group in here to make sure exported virtools file can be read by Ballancde correctly.
if successive_sector: if successive_sector:
for i in range(successive_sector_count): for i in range(successive_sector_count):
gp_name: str = UTIL_naming_convention.build_name_from_sector_index(i + 1) gp_name: str = UTIL_naming_convension.build_name_from_sector_index(i + 1)
vtgroup: bmap.BMGroup | None = group_cret_map.get(gp_name, None) vtgroup: bmap.BMGroup | None = group_cret_map.get(gp_name, None)
if vtgroup is None: if vtgroup is None:
vtgroup = writer.create_group() vtgroup = writer.create_group()

View File

@ -1,8 +1,8 @@
import bpy, mathutils import bpy, mathutils
from bpy_extras.wm_utils.progress_report import ProgressReport from bpy_extras.wm_utils.progress_report import ProgressReport
import tempfile, os, typing import tempfile, os, typing
from . import PROP_preferences, UTIL_ioport_shared, UTIL_naming_convention from . import PROP_preferences, UTIL_ioport_shared
from . import UTIL_virtools_types, UTIL_functions, UTIL_file_browser, UTIL_blender_mesh, UTIL_ballance_texture from . import UTIL_virtools_types, UTIL_functions, UTIL_file_browser, UTIL_blender_mesh, UTIL_ballance_texture, UTIL_naming_convension
from . import PROP_virtools_group, PROP_virtools_material, PROP_virtools_mesh, PROP_virtools_texture, PROP_virtools_light, PROP_ballance_map_info from . import PROP_virtools_group, PROP_virtools_material, PROP_virtools_mesh, PROP_virtools_texture, PROP_virtools_light, PROP_ballance_map_info
from .PyBMap import bmap_wrapper as bmap from .PyBMap import bmap_wrapper as bmap
@ -432,7 +432,7 @@ def _import_virtools_groups(
if group_name is None: continue if group_name is None: continue
# try extracting sector info # try extracting sector info
potential_sector_count: int | None = UTIL_naming_convention.extract_sector_from_name(group_name) potential_sector_count: int | None = UTIL_naming_convension.extract_sector_from_name(group_name)
if potential_sector_count is not None: if potential_sector_count is not None:
sector_count = max(sector_count, potential_sector_count) sector_count = max(sector_count, potential_sector_count)

View File

@ -2,12 +2,12 @@ import bpy
from . import UTIL_functions from . import UTIL_functions
from . import PROP_virtools_material, PROP_preferences from . import PROP_virtools_material, PROP_preferences
class BBP_OT_fix_all_materials(bpy.types.Operator): class BBP_OT_fix_all_material(bpy.types.Operator):
"""Fix All Materials by Its Referred Ballance Texture Name.""" """Fix All Materials by Its Referred Ballance Texture Name."""
bl_idname = "bbp.fix_all_materials" bl_idname = "bbp.fix_all_material"
bl_label = "Fix All Materials" bl_label = "Fix All Materials"
bl_options = {'UNDO'} bl_options = {'UNDO'}
bl_translation_context = 'BBP_OT_fix_all_materials' bl_translation_context = 'BBP_OT_fix_all_material'
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
@ -31,12 +31,12 @@ class BBP_OT_fix_all_materials(bpy.types.Operator):
# report and return # report and return
tr_text: str = bpy.app.translations.pgettext_rpt( tr_text: str = bpy.app.translations.pgettext_rpt(
'Fix {0}/{1} materials.', 'BBP_OT_fix_all_materials/draw') 'Fix {0}/{1} materials.', 'BBP_OT_fix_all_material/draw')
self.report({'INFO'}, tr_text.format(counter_suc, counter_all)) self.report({'INFO'}, tr_text.format(counter_suc, counter_all))
return {'FINISHED'} return {'FINISHED'}
def register() -> None: def register() -> None:
bpy.utils.register_class(BBP_OT_fix_all_materials) bpy.utils.register_class(BBP_OT_fix_all_material)
def unregister() -> None: def unregister() -> None:
bpy.utils.unregister_class(BBP_OT_fix_all_materials) bpy.utils.unregister_class(BBP_OT_fix_all_material)

View File

@ -9,35 +9,19 @@ class AlignMode(enum.IntEnum):
BBoxCenter = enum.auto() BBoxCenter = enum.auto()
AxisCenter = enum.auto() AxisCenter = enum.auto()
Max = enum.auto() Max = enum.auto()
_g_AlignModeDesc: dict[AlignMode, tuple[str, str, str]] = { _g_AlignModeDesc: dict[AlignMode, tuple[str, str]] = {
AlignMode.Min: ("Min", "The min value in specified axis.", "REMOVE"), AlignMode.Min: ("Min", "The min value in specified axis."),
AlignMode.BBoxCenter: ("Center (Bounding Box)", "The bounding box center in specified axis.", "SHADING_BBOX"), AlignMode.BBoxCenter: ("Center (Bounding Box)", "The bounding box center in specified axis."),
AlignMode.AxisCenter: ("Center (Axis)", "The object's source point in specified axis.", "OBJECT_ORIGIN"), AlignMode.AxisCenter: ("Center (Axis)", "The object's source point in specified axis."),
AlignMode.Max: ("Max", "The max value in specified axis.", "ADD"), AlignMode.Max: ("Max", "The max value in specified axis."),
} }
_g_EnumHelper_AlignMode = UTIL_functions.EnumPropHelper( _g_EnumHelper_AlignMode: UTIL_functions.EnumPropHelper = UTIL_functions.EnumPropHelper(
AlignMode, AlignMode,
lambda x: str(x.value), lambda x: str(x.value),
lambda x: AlignMode(int(x)), lambda x: AlignMode(int(x)),
lambda x: _g_AlignModeDesc[x][0], lambda x: _g_AlignModeDesc[x][0],
lambda x: _g_AlignModeDesc[x][1], lambda x: _g_AlignModeDesc[x][1],
lambda x: _g_AlignModeDesc[x][2] lambda _: ''
)
class CurrentInstance(enum.IntEnum):
ActiveObject = enum.auto()
Cursor = enum.auto()
_g_CurrentInstanceDesc: dict[CurrentInstance, tuple[str, str, str]] = {
CurrentInstance.ActiveObject: ("Active Object", "Use Active Object as Current Object", "OBJECT_DATA"),
CurrentInstance.Cursor: ("3D Cursor", "Use 3D Cursor as Current Object", "CURSOR"),
}
_g_EnumHelper_CurrentInstance = UTIL_functions.EnumPropHelper(
CurrentInstance,
lambda x: str(x.value),
lambda x: CurrentInstance(int(x)),
lambda x: _g_CurrentInstanceDesc[x][0],
lambda x: _g_CurrentInstanceDesc[x][1],
lambda x: _g_CurrentInstanceDesc[x][2]
) )
#endregion #endregion
@ -71,23 +55,14 @@ class BBP_PG_legacy_align_history(bpy.types.PropertyGroup):
default = False, default = False,
translation_context = 'BBP_PG_legacy_align_history/property' translation_context = 'BBP_PG_legacy_align_history/property'
) # type: ignore ) # type: ignore
current_instance: bpy.props.EnumProperty(
name = "Current Instance",
description = "Decide which instance should be used as Current Object",
items = _g_EnumHelper_CurrentInstance.generate_items(),
default = _g_EnumHelper_CurrentInstance.to_selection(CurrentInstance.ActiveObject),
translation_context = 'BBP_PG_legacy_align_history/property'
) # type: ignore
current_align_mode: bpy.props.EnumProperty( current_align_mode: bpy.props.EnumProperty(
name = "Current Object", name = "Current Object (Active Object)",
description = "The align mode applied to Current Object",
items = _g_EnumHelper_AlignMode.generate_items(), items = _g_EnumHelper_AlignMode.generate_items(),
default = _g_EnumHelper_AlignMode.to_selection(AlignMode.AxisCenter), default = _g_EnumHelper_AlignMode.to_selection(AlignMode.AxisCenter),
translation_context = 'BBP_PG_legacy_align_history/property' translation_context = 'BBP_PG_legacy_align_history/property'
) # type: ignore ) # type: ignore
target_align_mode: bpy.props.EnumProperty( target_align_mode: bpy.props.EnumProperty(
name = "Target Objects", name = "Target Objects (Selected Objects)",
description = "The align mode applied to Target Objects (selected objects except active object if Current Instance is active object)",
items = _g_EnumHelper_AlignMode.generate_items(), items = _g_EnumHelper_AlignMode.generate_items(),
default = _g_EnumHelper_AlignMode.to_selection(AlignMode.AxisCenter), default = _g_EnumHelper_AlignMode.to_selection(AlignMode.AxisCenter),
translation_context = 'BBP_PG_legacy_align_history/property' translation_context = 'BBP_PG_legacy_align_history/property'
@ -173,7 +148,7 @@ class BBP_OT_legacy_align(bpy.types.Operator):
def execute(self, context): def execute(self, context):
# get processed objects # get processed objects
(current_obj, current_cursor, target_objs) = _prepare_objects() (current_obj, target_objs) = _prepare_objects()
# YYC MARK: # YYC MARK:
# This statement is VERY IMPORTANT. # This statement is VERY IMPORTANT.
# If this statement is not presented, Blender will return identity matrix # If this statement is not presented, Blender will return identity matrix
@ -187,8 +162,7 @@ class BBP_OT_legacy_align(bpy.types.Operator):
histories = UTIL_functions.CollectionVisitor(self.align_history) histories = UTIL_functions.CollectionVisitor(self.align_history)
for entry in histories: for entry in histories:
_align_objects( _align_objects(
_g_EnumHelper_CurrentInstance.get_selection(entry.current_instance), current_obj, target_objs,
current_obj, current_cursor, target_objs,
entry.align_x, entry.align_y, entry.align_z, entry.align_x, entry.align_y, entry.align_z,
_g_EnumHelper_AlignMode.get_selection(entry.current_align_mode), _g_EnumHelper_AlignMode.get_selection(entry.current_align_mode),
_g_EnumHelper_AlignMode.get_selection(entry.target_align_mode) _g_EnumHelper_AlignMode.get_selection(entry.target_align_mode)
@ -211,19 +185,8 @@ class BBP_OT_legacy_align(bpy.types.Operator):
row.prop(entry, "align_y", toggle = 1) row.prop(entry, "align_y", toggle = 1)
row.prop(entry, "align_z", toggle = 1) row.prop(entry, "align_z", toggle = 1)
# show current instance # show mode
col.separator() col.separator()
col.label(text='Current Instance', text_ctxt='BBP_OT_legacy_align/draw')
# it should be shown in horizon so we create a new sublayout
row = col.row()
row.prop(entry, 'current_instance', expand=True)
# show instance and mode
col.separator()
# only show current object mode if current instance is active object,
# because there is no mode for 3d cursor.
current_instnce = _g_EnumHelper_CurrentInstance.get_selection(entry.current_instance)
if current_instnce == CurrentInstance.ActiveObject:
col.label(text='Current Object (Active Object)', text_ctxt='BBP_OT_legacy_align/draw') col.label(text='Current Object (Active Object)', text_ctxt='BBP_OT_legacy_align/draw')
col.prop(entry, "current_align_mode", expand = True) col.prop(entry, "current_align_mode", expand = True)
col.label(text='Target Objects (Selected Objects)', text_ctxt='BBP_OT_legacy_align/draw') col.label(text='Target Objects (Selected Objects)', text_ctxt='BBP_OT_legacy_align/draw')
@ -243,66 +206,44 @@ class BBP_OT_legacy_align(bpy.types.Operator):
#region Core Functions #region Core Functions
def _check_align_requirement() -> bool: def _check_align_requirement() -> bool:
# If we are not in object mode, do not do legacy align # if we are not in object mode, do not do legacy align
if not UTIL_functions.is_in_object_mode(): if not UTIL_functions.is_in_object_mode():
return False return False
# YYC MARK: # check current obj
# We still need to check active object (as current object)
# although we can choose align with active object or 3d cursor.
# Because we can not make any promise that user will
# select Active Object or 3D Cursor as current object before executing this operator.
if bpy.context.active_object is None: if bpy.context.active_object is None:
return False return False
# check target obj with filter of current obj
length = len(bpy.context.selected_objects)
if bpy.context.active_object in bpy.context.selected_objects:
length -= 1
return length != 0
# YYC MARK: def _prepare_objects() -> tuple[bpy.types.Object, set[bpy.types.Object]]:
# Roughly check selected objects. # get current object
# We do not need exclude active object from selected objects, current_obj: bpy.types.Object = bpy.context.active_object
# because active object may be moved when 3D Cursor is current object.
if len(bpy.context.selected_objects) == 0:
return False
return True # get target objects
target_objs: set[bpy.types.Object] = set(bpy.context.selected_objects)
def _prepare_objects() -> tuple[bpy.types.Object, mathutils.Vector, list[bpy.types.Object]]: # remove active one
# Fetch current object if current_obj in target_objs:
current_obj = typing.cast(bpy.types.Object, bpy.context.active_object) target_objs.remove(current_obj)
# Fetch 3d cursor location
current_cursor: mathutils.Vector = bpy.context.scene.cursor.location
# YYC MARK:
# Fetch target objects and do NOT remove active object from it.
# because active object will be moved when current instance is 3D Cursor.
target_objs: list[bpy.types.Object] = bpy.context.selected_objects[:]
# return value # return value
return (current_obj, current_cursor, target_objs) return (current_obj, target_objs)
def _align_objects( def _align_objects(
current_instance: CurrentInstance, current_obj: bpy.types.Object, target_objs: set[bpy.types.Object],
current_obj: bpy.types.Object, current_cursor: mathutils.Vector, target_objs: list[bpy.types.Object],
align_x: bool, align_y: bool, align_z: bool, current_mode: AlignMode, target_mode: AlignMode) -> None: align_x: bool, align_y: bool, align_z: bool, current_mode: AlignMode, target_mode: AlignMode) -> None:
# if no align, skip # if no align, skip
if not (align_x or align_y or align_z): if not (align_x or align_y or align_z):
return return
# calc current object data # calc current object data
current_obj_ref: mathutils.Vector current_obj_ref: mathutils.Vector = _get_object_ref_point(current_obj, current_mode)
match current_instance:
case CurrentInstance.ActiveObject:
current_obj_ref = _get_object_ref_point(current_obj, current_mode)
case CurrentInstance.Cursor:
current_obj_ref = current_cursor
# process each target obj # process each target obj
for target_obj in target_objs: for target_obj in target_objs:
# YYC MARK:
# If we use active object as current instance, we need exclude it from target objects,
# because there is no pre-exclude considering the scenario that 3D Cursor is current instance.
if current_instance == CurrentInstance.ActiveObject and current_obj == target_obj:
continue
# calc target object data # calc target object data
target_obj_ref: mathutils.Vector = _get_object_ref_point(target_obj, target_mode) target_obj_ref: mathutils.Vector = _get_object_ref_point(target_obj, target_mode)
# build translation transform # build translation transform
@ -315,21 +256,21 @@ def _align_objects(
# apply translation transform to left side (add into original matrix) # apply translation transform to left side (add into original matrix)
target_obj.matrix_world = target_obj_translation_matrix @ target_obj.matrix_world target_obj.matrix_world = target_obj_translation_matrix @ target_obj.matrix_world
bpy.context.scene.update_tag
def _get_object_ref_point(obj: bpy.types.Object, mode: AlignMode) -> mathutils.Vector: def _get_object_ref_point(obj: bpy.types.Object, mode: AlignMode) -> mathutils.Vector:
ref_pos = mathutils.Vector((0, 0, 0)) ref_pos: mathutils.Vector = mathutils.Vector((0, 0, 0))
# calc bounding box data # calc bounding box data
corners: tuple[mathutils.Vector, ...] = tuple(obj.matrix_world @ mathutils.Vector(corner) for corner in obj.bound_box) corners: tuple[mathutils.Vector] = tuple(obj.matrix_world @ mathutils.Vector(corner) for corner in obj.bound_box)
bbox_min_corner = mathutils.Vector(( bbox_min_corner: mathutils.Vector = mathutils.Vector((0, 0, 0))
min((vec.x for vec in corners)), bbox_min_corner.x = min((vec.x for vec in corners))
min((vec.y for vec in corners)), bbox_min_corner.y = min((vec.y for vec in corners))
min((vec.z for vec in corners)), bbox_min_corner.z = min((vec.z for vec in corners))
)) bbox_max_corner: mathutils.Vector = mathutils.Vector((0, 0, 0))
bbox_max_corner = mathutils.Vector(( bbox_max_corner.x = max((vec.x for vec in corners))
max((vec.x for vec in corners)), bbox_max_corner.y = max((vec.y for vec in corners))
max((vec.y for vec in corners)), bbox_max_corner.z = max((vec.z for vec in corners))
max((vec.z for vec in corners)),
))
# return value by given align mode # return value by given align mode
match(mode): match(mode):

View File

@ -1,6 +1,6 @@
import bpy import bpy
import typing import typing
from . import UTIL_functions, UTIL_icons_manager, UTIL_naming_convention from . import UTIL_naming_convension, UTIL_functions, UTIL_icons_manager
class BBP_OT_regulate_objects_name(bpy.types.Operator): class BBP_OT_regulate_objects_name(bpy.types.Operator):
"""Regulate Objects Name by Virtools Group and Naming Convention""" """Regulate Objects Name by Virtools Group and Naming Convention"""
@ -15,8 +15,8 @@ class BBP_OT_regulate_objects_name(bpy.types.Operator):
def execute(self, context): def execute(self, context):
_rename_core( _rename_core(
UTIL_naming_convention.VirtoolsGroupConvention.parse_from_object, UTIL_naming_convension.VirtoolsGroupConvention.parse_from_object,
UTIL_naming_convention.YYCToolchainConvention.set_to_object UTIL_naming_convension.YYCToolchainConvention.set_to_object
) )
return {'FINISHED'} return {'FINISHED'}
@ -33,8 +33,8 @@ class BBP_OT_auto_grouping(bpy.types.Operator):
def execute(self, context): def execute(self, context):
_rename_core( _rename_core(
UTIL_naming_convention.YYCToolchainConvention.parse_from_object, UTIL_naming_convension.YYCToolchainConvention.parse_from_object,
UTIL_naming_convention.VirtoolsGroupConvention.set_to_object UTIL_naming_convension.VirtoolsGroupConvention.set_to_object
) )
return {'FINISHED'} return {'FINISHED'}
@ -51,26 +51,26 @@ class BBP_OT_convert_to_imengyu(bpy.types.Operator):
def execute(self, context): def execute(self, context):
_rename_core( _rename_core(
UTIL_naming_convention.YYCToolchainConvention.parse_from_object, UTIL_naming_convension.YYCToolchainConvention.parse_from_object,
UTIL_naming_convention.ImengyuConvention.set_to_object UTIL_naming_convension.ImengyuConvention.set_to_object
) )
return {'FINISHED'} return {'FINISHED'}
def _rename_core( def _rename_core(
fct_get_info: typing.Callable[[bpy.types.Object, UTIL_naming_convention.RenameErrorReporter], UTIL_naming_convention.BallanceObjectInfo | None], fct_get_info: typing.Callable[[bpy.types.Object, UTIL_naming_convension.RenameErrorReporter], UTIL_naming_convension.BallanceObjectInfo | None],
ftc_set_info: typing.Callable[[bpy.types.Object, UTIL_naming_convention.BallanceObjectInfo, UTIL_naming_convention.RenameErrorReporter], bool] ftc_set_info: typing.Callable[[bpy.types.Object, UTIL_naming_convension.BallanceObjectInfo, UTIL_naming_convension.RenameErrorReporter], bool]
) -> None: ) -> None:
# get selected objects. allow nested collection # get selected objects. allow nested collection
selected_objects: typing.Iterable[bpy.types.Object] = bpy.context.view_layer.active_layer_collection.collection.all_objects selected_objects: typing.Iterable[bpy.types.Object] = bpy.context.view_layer.active_layer_collection.collection.all_objects
# create reporter # create reporter
with UTIL_naming_convention.RenameErrorReporter() as reporter: with UTIL_naming_convension.RenameErrorReporter() as reporter:
# iterate objects # iterate objects
for obj in selected_objects: for obj in selected_objects:
reporter.enter_object(obj) reporter.enter_object(obj)
# try get info # try get info
info: UTIL_naming_convention.BallanceObjectInfo | None = fct_get_info(obj, reporter) info: UTIL_naming_convension.BallanceObjectInfo | None = fct_get_info(obj, reporter)
if info is not None: if info is not None:
# if info is valid, try assign it # if info is valid, try assign it
if not ftc_set_info(obj, info, reporter): if not ftc_set_info(obj, info, reporter):

View File

@ -18,7 +18,7 @@ _g_SelectModeDesc: dict[SelectMode, tuple[str, str, str]] = {
SelectMode.Difference: ('Invert', 'Inverts the selection.', 'SELECT_DIFFERENCE'), SelectMode.Difference: ('Invert', 'Inverts the selection.', 'SELECT_DIFFERENCE'),
SelectMode.Intersect: ('Intersect', 'Selects items that intersect with the existing selection.', 'SELECT_INTERSECT') SelectMode.Intersect: ('Intersect', 'Selects items that intersect with the existing selection.', 'SELECT_INTERSECT')
} }
_g_EnumHelper_SelectMode = UTIL_functions.EnumPropHelper( _g_EnumHelper_SelectMode: UTIL_functions.EnumPropHelper = UTIL_functions.EnumPropHelper(
SelectMode, SelectMode,
lambda x: str(x.value), lambda x: str(x.value),
lambda x: SelectMode(int(x)), lambda x: SelectMode(int(x)),

View File

@ -124,7 +124,7 @@ def _load_element(mesh: bpy.types.Mesh, element_type: BallanceElementType) -> No
element_filename: str = os.path.join( element_filename: str = os.path.join(
os.path.dirname(__file__), os.path.dirname(__file__),
"meshes", "meshes",
element_name + '.ph' element_name + '.bin'
) )
# open file and read # open file and read

View File

@ -1,6 +1,6 @@
import bpy import bpy
import os, typing import os, typing
from . import UTIL_naming_convention from . import UTIL_naming_convension
class RawPreferences(): class RawPreferences():
cBallanceTextureFolder: typing.ClassVar[str] = "" cBallanceTextureFolder: typing.ClassVar[str] = ""

View File

@ -205,7 +205,7 @@ class VirtoolsGroupsPreset(enum.Enum):
Shadow = "Shadow" Shadow = "Shadow"
_g_VtGrpPresetValues: tuple[str, ...] = tuple(map(lambda x: x.value, VirtoolsGroupsPreset)) _g_VtGrpPresetValues: tuple[str] = tuple(map(lambda x: x.value, VirtoolsGroupsPreset))
## Some of group names are not matched with icon name ## Some of group names are not matched with icon name
# So we create a convertion map to convert them. # So we create a convertion map to convert them.
@ -236,7 +236,7 @@ def _get_group_icon_by_name(gp_name: str) -> int:
if value is not None: return value if value is not None: return value
else: return UTIL_icons_manager.get_empty_icon() else: return UTIL_icons_manager.get_empty_icon()
# blender group name prop helper # blender group name prop helper
_g_EnumHelper_Group = UTIL_functions.EnumPropHelper( _g_EnumHelper_Group: UTIL_functions.EnumPropHelper = UTIL_functions.EnumPropHelper(
VirtoolsGroupsPreset, VirtoolsGroupsPreset,
lambda x: x.value, # member is string self lambda x: x.value, # member is string self
lambda x: VirtoolsGroupsPreset(x), # convert directly because it is StrEnum. lambda x: VirtoolsGroupsPreset(x), # convert directly because it is StrEnum.

View File

@ -73,7 +73,7 @@ class RawVirtoolsLight():
# Blender Property Group # Blender Property Group
_g_Helper_VXLIGHT_TYPE = UTIL_virtools_types.EnumPropHelper(UTIL_virtools_types.VXLIGHT_TYPE) _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): class BBP_PG_virtools_light(bpy.types.PropertyGroup):
light_type: bpy.props.EnumProperty( light_type: bpy.props.EnumProperty(

View File

@ -114,13 +114,13 @@ class RawVirtoolsMaterial():
#region Blender Enum Prop Helper (Virtools type specified) #region Blender Enum Prop Helper (Virtools type specified)
_g_Helper_VXTEXTURE_BLENDMODE = UTIL_virtools_types.EnumPropHelper(UTIL_virtools_types.VXTEXTURE_BLENDMODE) _g_Helper_VXTEXTURE_BLENDMODE: UTIL_virtools_types.EnumPropHelper = UTIL_virtools_types.EnumPropHelper(UTIL_virtools_types.VXTEXTURE_BLENDMODE)
_g_Helper_VXTEXTURE_FILTERMODE = UTIL_virtools_types.EnumPropHelper(UTIL_virtools_types.VXTEXTURE_FILTERMODE) _g_Helper_VXTEXTURE_FILTERMODE: UTIL_virtools_types.EnumPropHelper = UTIL_virtools_types.EnumPropHelper(UTIL_virtools_types.VXTEXTURE_FILTERMODE)
_g_Helper_VXTEXTURE_ADDRESSMODE = UTIL_virtools_types.EnumPropHelper(UTIL_virtools_types.VXTEXTURE_ADDRESSMODE) _g_Helper_VXTEXTURE_ADDRESSMODE: UTIL_virtools_types.EnumPropHelper = UTIL_virtools_types.EnumPropHelper(UTIL_virtools_types.VXTEXTURE_ADDRESSMODE)
_g_Helper_VXBLEND_MODE = UTIL_virtools_types.EnumPropHelper(UTIL_virtools_types.VXBLEND_MODE) _g_Helper_VXBLEND_MODE: UTIL_virtools_types.EnumPropHelper = UTIL_virtools_types.EnumPropHelper(UTIL_virtools_types.VXBLEND_MODE)
_g_Helper_VXFILL_MODE = UTIL_virtools_types.EnumPropHelper(UTIL_virtools_types.VXFILL_MODE) _g_Helper_VXFILL_MODE: UTIL_virtools_types.EnumPropHelper = UTIL_virtools_types.EnumPropHelper(UTIL_virtools_types.VXFILL_MODE)
_g_Helper_VXSHADE_MODE = UTIL_virtools_types.EnumPropHelper(UTIL_virtools_types.VXSHADE_MODE) _g_Helper_VXSHADE_MODE: UTIL_virtools_types.EnumPropHelper = UTIL_virtools_types.EnumPropHelper(UTIL_virtools_types.VXSHADE_MODE)
_g_Helper_VXCMPFUNC = UTIL_virtools_types.EnumPropHelper(UTIL_virtools_types.VXCMPFUNC) _g_Helper_VXCMPFUNC: UTIL_virtools_types.EnumPropHelper = UTIL_virtools_types.EnumPropHelper(UTIL_virtools_types.VXCMPFUNC)
#endregion #endregion
@ -558,7 +558,7 @@ def preset_virtools_material(mtl: bpy.types.Material, preset_type: MaterialPrese
set_raw_virtools_material(mtl, preset_data) set_raw_virtools_material(mtl, preset_data)
# create preset enum blender helper # create preset enum blender helper
_g_Helper_MtlPreset = UTIL_functions.EnumPropHelper( _g_Helper_MtlPreset: UTIL_functions.EnumPropHelper = UTIL_functions.EnumPropHelper(
MaterialPresetType, MaterialPresetType,
lambda x: str(x.value), lambda x: str(x.value),
lambda x: MaterialPresetType(int(x)), lambda x: MaterialPresetType(int(x)),
@ -572,13 +572,13 @@ _g_Helper_MtlPreset = UTIL_functions.EnumPropHelper(
#region Fix Material #region Fix Material
def fix_material(mtl: bpy.types.Material) -> bool: def fix_material(mtl: bpy.types.Material) -> bool:
""" """!
Fix single Blender material. Fix single Blender material.
The implementation of this function is copied from `BallanceVirtoolsHelper/bvh/features/mapping/bmfile_fix_texture.cpp` @remark The implementation of this function is copied from BallanceVirtoolsHelper/bvh/features/mapping/bmfile_fix_texture.cpp
:param mtl: The blender material need to be processed. @param mtl[in] The blender material need to be processed.
:return: True if we do a fix, otherwise return False. @return True if we do a fix, otherwise return False.
""" """
# prepare return value first # prepare return value first
ret: bool = False ret: bool = False

View File

@ -15,7 +15,7 @@ class RawVirtoolsMesh():
self.mLitMode = kwargs.get('mLitMode', RawVirtoolsMesh.cDefaultLitMode) self.mLitMode = kwargs.get('mLitMode', RawVirtoolsMesh.cDefaultLitMode)
# blender enum prop helper defines # blender enum prop helper defines
_g_Helper_VXMESH_LITMODE = UTIL_virtools_types.EnumPropHelper(UTIL_virtools_types.VXMESH_LITMODE) _g_Helper_VXMESH_LITMODE: UTIL_virtools_types.EnumPropHelper = UTIL_virtools_types.EnumPropHelper(UTIL_virtools_types.VXMESH_LITMODE)
# Blender Property Group # Blender Property Group

View File

@ -20,8 +20,8 @@ class RawVirtoolsTexture():
self.mVideoFormat = kwargs.get('mVideoFormat', RawVirtoolsTexture.cDefaultVideoFormat) self.mVideoFormat = kwargs.get('mVideoFormat', RawVirtoolsTexture.cDefaultVideoFormat)
# blender enum prop helper defines # blender enum prop helper defines
_g_Helper_CK_TEXTURE_SAVEOPTIONS = UTIL_virtools_types.EnumPropHelper(UTIL_virtools_types.CK_TEXTURE_SAVEOPTIONS) _g_Helper_CK_TEXTURE_SAVEOPTIONS: UTIL_virtools_types.EnumPropHelper = UTIL_virtools_types.EnumPropHelper(UTIL_virtools_types.CK_TEXTURE_SAVEOPTIONS)
_g_Helper_VX_PIXELFORMAT = UTIL_virtools_types.EnumPropHelper(UTIL_virtools_types.VX_PIXELFORMAT) _g_Helper_VX_PIXELFORMAT: UTIL_virtools_types.EnumPropHelper = UTIL_virtools_types.EnumPropHelper(UTIL_virtools_types.VX_PIXELFORMAT)
class BBP_PG_virtools_texture(bpy.types.PropertyGroup): class BBP_PG_virtools_texture(bpy.types.PropertyGroup):
@ -64,10 +64,12 @@ def set_raw_virtools_texture(img: bpy.types.Image, rawdata: RawVirtoolsTexture)
#region Virtools Texture Drawer #region Virtools Texture Drawer
# YYC MARK: """!
# Because Image do not have its unique properties window, @remark
# so we only can draw Virtools Texture properties in other window. Because Image do not have its unique properties window
# We provide various functions to help draw properties. so we only can draw virtools texture properties in other window
we provide various function to help draw property.
"""
def draw_virtools_texture(img: bpy.types.Image, layout: bpy.types.UILayout): def draw_virtools_texture(img: bpy.types.Image, layout: bpy.types.UILayout):
props: BBP_PG_virtools_texture = get_virtools_texture(img) props: BBP_PG_virtools_texture = get_virtools_texture(img)

View File

@ -1,7 +1,7 @@
import bpy, mathutils import bpy, mathutils
import os, json, enum, typing, math import os, json, enum, typing, math
from . import PROP_virtools_group, PROP_bme_material, UTIL_naming_convention from . import PROP_virtools_group, PROP_bme_material
from . import UTIL_functions, UTIL_icons_manager, UTIL_blender_mesh, UTIL_virtools_types from . import UTIL_functions, UTIL_icons_manager, UTIL_blender_mesh, UTIL_virtools_types, UTIL_naming_convension
## NOTE: Outside caller should use BME struct's unique indetifier to visit each prototype ## NOTE: Outside caller should use BME struct's unique indetifier to visit each prototype
# and drive this class' functions to work. # and drive this class' functions to work.
@ -187,14 +187,15 @@ class PrototypeShowcaseCfgDescriptor():
def get_default(self) -> typing.Any: def get_default(self) -> typing.Any:
return _eval_showcase_cfgs_default(self.__mRawCfg[TOKEN_SHOWCASE_CFGS_DEFAULT]) return _eval_showcase_cfgs_default(self.__mRawCfg[TOKEN_SHOWCASE_CFGS_DEFAULT])
class EnumPropHelper(UTIL_functions.EnumPropHelper[str]): class EnumPropHelper(UTIL_functions.EnumPropHelper):
""" """
The BME specialized Blender EnumProperty helper. The BME specialized Blender EnumProperty helper.
""" """
def __init__(self): def __init__(self):
# init parent class # init parent class
super().__init__( UTIL_functions.EnumPropHelper.__init__(
self,
self.get_bme_identifiers(), self.get_bme_identifiers(),
lambda x: x, lambda x: x,
lambda x: x, lambda x: x,
@ -270,23 +271,23 @@ def create_bme_struct_wrapper(ident: str, cfgs: dict[str, typing.Any]) -> bpy.ty
# create object and assign prop # create object and assign prop
# get obj info first # get obj info first
obj_info: UTIL_naming_convention.BallanceObjectInfo obj_info: UTIL_naming_convension.BallanceObjectInfo
match(PrototypeShowcaseTypes(proto[TOKEN_SHOWCASE][TOKEN_SHOWCASE_TYPE])): match(PrototypeShowcaseTypes(proto[TOKEN_SHOWCASE][TOKEN_SHOWCASE_TYPE])):
case PrototypeShowcaseTypes.No: case PrototypeShowcaseTypes.No:
obj_info = UTIL_naming_convention.BallanceObjectInfo.create_from_others(UTIL_naming_convention.BallanceObjectType.DECORATION) obj_info = UTIL_naming_convension.BallanceObjectInfo.create_from_others(UTIL_naming_convension.BallanceObjectType.DECORATION)
case PrototypeShowcaseTypes.Floor: case PrototypeShowcaseTypes.Floor:
obj_info = UTIL_naming_convention.BallanceObjectInfo.create_from_others(UTIL_naming_convention.BallanceObjectType.FLOOR) obj_info = UTIL_naming_convension.BallanceObjectInfo.create_from_others(UTIL_naming_convension.BallanceObjectType.FLOOR)
case PrototypeShowcaseTypes.Rail: case PrototypeShowcaseTypes.Rail:
obj_info = UTIL_naming_convention.BallanceObjectInfo.create_from_others(UTIL_naming_convention.BallanceObjectType.RAIL) obj_info = UTIL_naming_convension.BallanceObjectInfo.create_from_others(UTIL_naming_convension.BallanceObjectType.RAIL)
case PrototypeShowcaseTypes.Wood: case PrototypeShowcaseTypes.Wood:
obj_info = UTIL_naming_convention.BallanceObjectInfo.create_from_others(UTIL_naming_convention.BallanceObjectType.WOOD) obj_info = UTIL_naming_convension.BallanceObjectInfo.create_from_others(UTIL_naming_convension.BallanceObjectType.WOOD)
# then get object name # then get object name
obj_name: str | None = UTIL_naming_convention.YYCToolchainConvention.set_to_name(obj_info, None) obj_name: str | None = UTIL_naming_convension.YYCToolchainConvention.set_to_name(obj_info, None)
if obj_name is None: raise UTIL_functions.BBPException('impossible null name') if obj_name is None: raise UTIL_functions.BBPException('impossible null name')
# create object by name # create object by name
obj: bpy.types.Object = bpy.data.objects.new(obj_name, mesh) obj: bpy.types.Object = bpy.data.objects.new(obj_name, mesh)
# assign virtools groups # assign virtools groups
UTIL_naming_convention.VirtoolsGroupConvention.set_to_object(obj, obj_info, None) UTIL_naming_convension.VirtoolsGroupConvention.set_to_object(obj, obj_info, None)
# return object # return object
return obj return obj

View File

@ -2,17 +2,19 @@ import bpy, mathutils
import math, typing, enum, sys import math, typing, enum, sys
class BBPException(Exception): class BBPException(Exception):
""" The exception thrown by Ballance Blender Plugin""" """
The exception thrown by Ballance Blender Plugin
"""
pass pass
def clamp_float(v: float, min_val: float, max_val: float) -> float: def clamp_float(v: float, min_val: float, max_val: float) -> float:
""" """!
Clamp a float value @brief Clamp a float value
:param v: The value need to be clamp. @param v[in] The value need to be clamp.
:param min_val: The allowed minium value (inclusive). @param min_val[in] The allowed minium value, including self.
:param max_val: The allowed maxium value (inclusive). @param max_val[in] The allowed maxium value, including self.
:return: Clamped value. @return Clamped value.
""" """
if (max_val < min_val): raise BBPException("Invalid range of clamp_float().") if (max_val < min_val): raise BBPException("Invalid range of clamp_float().")
@ -21,13 +23,13 @@ def clamp_float(v: float, min_val: float, max_val: float) -> float:
else: return v else: return v
def clamp_int(v: int, min_val: int, max_val: int) -> int: def clamp_int(v: int, min_val: int, max_val: int) -> int:
""" """!
Clamp a int value @brief Clamp a int value
:param v: The value need to be clamp. @param v[in] The value need to be clamp.
:param min_val: The allowed minium value (inclusive). @param min_val[in] The allowed minium value, including self.
:param max_val: The allowed maxium value (inclusive). @param max_val[in] The allowed maxium value, including self.
:return: Clamped value. @return Clamped value.
""" """
if (max_val < min_val): raise BBPException("Invalid range of clamp_int().") if (max_val < min_val): raise BBPException("Invalid range of clamp_int().")
@ -39,9 +41,9 @@ def message_box(message: tuple[str, ...], title: str, icon: str):
""" """
Show a message box in Blender. Non-block mode. Show a message box in Blender. Non-block mode.
:param message: The text this message box displayed. Each item in this param will show as a single line. @param message[in] The text this message box displayed. Each item in this param will show as a single line.
:param title: Message box title text. @param title[in] Message box title text.
:param icon: The icon this message box displayed. @param icon[in] The icon this message box displayed.
""" """
def draw(self, context: bpy.types.Context): def draw(self, context: bpy.types.Context):
layout = self.layout layout = self.layout
@ -51,48 +53,24 @@ def message_box(message: tuple[str, ...], title: str, icon: str):
bpy.context.window_manager.popup_menu(draw, title = title, icon = icon) bpy.context.window_manager.popup_menu(draw, title = title, icon = icon)
def add_into_scene(obj: bpy.types.Object): def add_into_scene(obj: bpy.types.Object):
"""
Add given object into active scene.
:param obj: The 3d object to be added.
"""
view_layer = bpy.context.view_layer view_layer = bpy.context.view_layer
collection = view_layer.active_layer_collection.collection collection = view_layer.active_layer_collection.collection
collection.objects.link(obj) collection.objects.link(obj)
def move_to_cursor(obj: bpy.types.Object): def move_to_cursor(obj: bpy.types.Object):
""" # use obj.matrix_world to move, not obj.location because this bug:
Move given object to the position of cursor.
:param obj: The 3d object to be moved.
"""
# YYC MARK:
# Use `obj.matrix_world` to move, not `obj.location`, because this bug:
# https://blender.stackexchange.com/questions/27667/incorrect-matrix-world-after-transformation # https://blender.stackexchange.com/questions/27667/incorrect-matrix-world-after-transformation
# The update of `matrix_world` after setting `location` is not immediately. # the update of matrix_world after setting location is not immediately.
# And it is inviable that calling `update()` function for `view_layer` to update these fields, # and calling update() function for view_layer for the translation of each object is not suit for too much objects.
# because it involve too much objects and cost too much time.
# obj.location = bpy.context.scene.cursor.location # obj.location = bpy.context.scene.cursor.location
obj.matrix_world = obj.matrix_world @ mathutils.Matrix.Translation(bpy.context.scene.cursor.location - obj.location) obj.matrix_world = obj.matrix_world @ mathutils.Matrix.Translation(bpy.context.scene.cursor.location - obj.location)
def add_into_scene_and_move_to_cursor(obj: bpy.types.Object): def add_into_scene_and_move_to_cursor(obj: bpy.types.Object):
"""
Add given object into active scene and move it to cursor position.
This function is just a simple combination of previous functions.
:param obj: The 3d object to be processed.
"""
add_into_scene(obj) add_into_scene(obj)
move_to_cursor(obj) move_to_cursor(obj)
def select_certain_objects(objs: tuple[bpy.types.Object, ...]) -> None: def select_certain_objects(objs: tuple[bpy.types.Object, ...]) -> None:
"""
Deselect all objects and then select given 3d objects.
:param objs: The tuple of 3d objects to be selected.
"""
# deselect all objects first # deselect all objects first
bpy.ops.object.select_all(action = 'DESELECT') bpy.ops.object.select_all(action = 'DESELECT')
# if no objects, return # if no objects, return
@ -105,11 +83,6 @@ def select_certain_objects(objs: tuple[bpy.types.Object, ...]) -> None:
bpy.context.view_layer.objects.active = objs[0] bpy.context.view_layer.objects.active = objs[0]
def is_in_object_mode() -> bool: def is_in_object_mode() -> bool:
"""
Check whether we are in Blender Object Mode.
:return: True if we are in object mode which suit for exporting something.
"""
# get active object from context # get active object from context
obj = bpy.context.active_object obj = bpy.context.active_object
@ -119,59 +92,51 @@ def is_in_object_mode() -> bool:
# simply check active object mode # simply check active object mode
return obj.mode == 'OBJECT' return obj.mode == 'OBJECT'
#region Blender Enum Property Helper class EnumPropHelper():
_TRawEnum = typing.TypeVar('_TRawEnum')
_TFctToStr = typing.Callable[[_TRawEnum], str]
_TFctFromStr = typing.Callable[[str], _TRawEnum]
_TFctName = typing.Callable[[_TRawEnum], str]
_TFctDesc = typing.Callable[[_TRawEnum], str]
_TFctIcon = typing.Callable[[_TRawEnum], str | int]
class EnumPropHelper(typing.Generic[_TRawEnum]):
""" """
These class contain all functions related to EnumProperty, including generating `items`, These class contain all functions related to EnumProperty, including generating `items`,
parsing data from EnumProperty string value and getting EnumProperty acceptable string format from data. parsing data from EnumProperty string value and getting EnumProperty acceptable string format from data.
""" """
# YYC MARK: # define some type hint
# I don't know why I can have subscripting for a `typing.Callable` object. _TFctToStr = typing.Callable[[typing.Any], str]
# It was not introduced in any document and I just know it from AI. _TFctFromStr = typing.Callable[[str], typing.Any]
# If I am not doing this, the type hint will crash into Unknown type. _TFctName = typing.Callable[[typing.Any], str]
# But it works now I don't want to touch it anymore. _TFctDesc = typing.Callable[[typing.Any], str]
_TFctIcon = typing.Callable[[typing.Any], str | int]
__mCollections: typing.Iterable[_TRawEnum] # define class member
__mFctToStr: _TFctToStr[_TRawEnum]
__mFctFromStr: _TFctFromStr[_TRawEnum]
__mFctName: _TFctName[_TRawEnum]
__mFctDesc: _TFctDesc[_TRawEnum]
__mFctIcon: _TFctIcon[_TRawEnum]
def __init__(self, collections: typing.Iterable[_TRawEnum], __mCollections: typing.Iterable[typing.Any]
fct_to_str: _TFctToStr[_TRawEnum], fct_from_str: _TFctFromStr[_TRawEnum], __mFctToStr: _TFctToStr
fct_name: _TFctName[_TRawEnum], fct_desc: _TFctDesc[_TRawEnum], __mFctFromStr: _TFctFromStr
fct_icon: _TFctIcon[_TRawEnum]): __mFctName: _TFctName
__mFctDesc: _TFctDesc
__mFctIcon: _TFctIcon
def __init__(
self,
collections_: typing.Iterable[typing.Any],
fct_to_str: _TFctToStr,
fct_from_str: _TFctFromStr,
fct_name: _TFctName,
fct_desc: _TFctDesc,
fct_icon: _TFctIcon):
""" """
Initialize an EnumProperty helper. Initialize a EnumProperty helper.
:param collections: The collection containing all available enum property entries. @param collections_ [in] The collection all available enum property entries contained.
It can be `enum.Enum` or a simple list/tuple. It can be enum.Enum or a simple list/tuple/dict.
:param fct_to_str: A function pointer converting data collection member to its string format. @param fct_to_str [in] A function pointer converting data collection member to its string format.
You must make sure that each members built name is unique in collection! For enum.IntEnum, it can be simply `lambda x: str(x.value)`
For `enum.IntEnum`, it can be simple `lambda x: str(x.value)` @param fct_from_str [in] A function pointer getting data collection member from its string format.
:param fct_from_str: A function pointer getting data collection member from its string format. For enum.IntEnum, it can be simply `lambda x: TEnum(int(x))`
This class promise that given string must can be parsed. @param fct_name [in] A function pointer converting data collection member to its display name.
For `enum.IntEnum`, it can be simple `lambda x: TEnum(int(x))` @param fct_desc [in] Same as `fct_name` but return description instead. Return empty string, not None if no description.
:param fct_name: A function pointer converting data collection member to its display name which shown in Blender. @param fct_icon [in] Same as `fct_name` but return the used icon instead. Return empty string if no icon.
:param fct_desc: Same as `fct_name` but return description instead which shown in Blender
If no description, return empty string, not None.
:param fct_icon: Same as `fct_name` but return the used icon instead which shown in Blender.
It can be a Blender builtin icon string, or any loaded icon integer ID.
If no icon, return empty string.
""" """
# assign member # assign member
self.__mCollections = collections self.__mCollections = collections_
self.__mFctToStr = fct_to_str self.__mFctToStr = fct_to_str
self.__mFctFromStr = fct_from_str self.__mFctFromStr = fct_from_str
self.__mFctName = fct_name self.__mFctName = fct_name
@ -194,22 +159,20 @@ class EnumPropHelper(typing.Generic[_TRawEnum]):
) for idx, member in enumerate(self.__mCollections) ) for idx, member in enumerate(self.__mCollections)
) )
def get_selection(self, prop: str) -> _TRawEnum: def get_selection(self, prop: str) -> typing.Any:
""" """
Return collection member from given Blender EnumProp string data. Return collection member from given Blender EnumProp string data.
""" """
# call from_str fct ptr # call from_str fct ptr
return self.__mFctFromStr(prop) return self.__mFctFromStr(prop)
def to_selection(self, val: _TRawEnum) -> str: def to_selection(self, val: typing.Any) -> str:
""" """
Parse collection member to Blender EnumProp acceptable string format. Parse collection member to Blender EnumProp acceptable string format.
""" """
# call to_str fct ptr # call to_str fct ptr
return self.__mFctToStr(val) return self.__mFctToStr(val)
#endregion
#region Blender Collection Visitor #region Blender Collection Visitor
_TPropertyGroup = typing.TypeVar('_TPropertyGroup', bound = bpy.types.PropertyGroup) _TPropertyGroup = typing.TypeVar('_TPropertyGroup', bound = bpy.types.PropertyGroup)
@ -227,33 +190,30 @@ class CollectionVisitor(typing.Generic[_TPropertyGroup]):
self.__mSrcProp = src_prop self.__mSrcProp = src_prop
def add(self) -> _TPropertyGroup: def add(self) -> _TPropertyGroup:
""" """!
Adds a new item to the collection. @brief Adds a new item to the collection.
@return The instance of newly created item.
:return: The instance of newly created item.
""" """
return self.__mSrcProp.add() return self.__mSrcProp.add()
def remove(self, index: int) -> None: def remove(self, index: int) -> None:
""" """!
Removes the item at the specified index from the collection. @brief Removes the item at the specified index from the collection.
@param[in] index The index of the item to remove.
:param index: The index of the item to remove.
""" """
self.__mSrcProp.remove(index) self.__mSrcProp.remove(index)
def move(self, from_index: int, to_index: int) -> None: def move(self, from_index: int, to_index: int) -> None:
""" """!
Moves an item from one index to another within the collection. @brief Moves an item from one index to another within the collection.
@param[in] from_index The current index of the item to move.
:param from_index: The current index of the item to move. @param[in] to_index The target index where the item should be moved.
:param to_index: The target index where the item should be moved.
""" """
self.__mSrcProp.move(from_index, to_index) self.__mSrcProp.move(from_index, to_index)
def clear(self) -> None: def clear(self) -> None:
""" """!
Clears all items from the collection. @brief Clears all items from the collection.
""" """
self.__mSrcProp.clear() self.__mSrcProp.clear()
@ -278,8 +238,8 @@ _TMutexObject = typing.TypeVar('_TMutexObject')
class TinyMutex(typing.Generic[_TMutexObject]): class TinyMutex(typing.Generic[_TMutexObject]):
""" """
In this plugin, some classes have "with" context feature. In this plugin, some class have "with" context feature.
However, in some cases, it is essential to block any futher visiting if some "with" context are operating on some object. However, it is essential to block any futher visiting if some "with" context are operating on some object.
This is the reason why this tiny mutex is designed. This is the reason why this tiny mutex is designed.
Please note this class is not a real MUTEX. Please note this class is not a real MUTEX.
@ -293,35 +253,17 @@ class TinyMutex(typing.Generic[_TMutexObject]):
self.__mProtectedObjects = set() self.__mProtectedObjects = set()
def lock(self, obj: _TMutexObject) -> None: def lock(self, obj: _TMutexObject) -> None:
"""
Lock given object.
:raise BBPException: Raised if given object has been locked.
:param obj: The resource to be locked.
"""
if obj in self.__mProtectedObjects: if obj in self.__mProtectedObjects:
raise BBPException('It is not allowed that operate multiple "with" contexts on a single object.') raise BBPException('It is not allowed that operate multiple "with" contexts on a single object.')
self.__mProtectedObjects.add(obj) self.__mProtectedObjects.add(obj)
def try_lock(self, obj: _TMutexObject) -> bool: def try_lock(self, obj: _TMutexObject) -> bool:
"""
Try lock given object.
:param obj: The resource to be locked.
:return: True if we successfully lock it, otherwise false.
"""
if obj in self.__mProtectedObjects: if obj in self.__mProtectedObjects:
return False return False
self.__mProtectedObjects.add(obj) self.__mProtectedObjects.add(obj)
return True return True
def unlock(self, obj: _TMutexObject) -> None: def unlock(self, obj: _TMutexObject) -> None:
"""
Unlock given object.
:raise BBPException: Raised if given object is not locked.
:param obj: The resource to be unlocked.
"""
if obj not in self.__mProtectedObjects: if obj not in self.__mProtectedObjects:
raise BBPException('It is not allowed that unlock an non-existent object.') raise BBPException('It is not allowed that unlock an non-existent object.')
self.__mProtectedObjects.remove(obj) self.__mProtectedObjects.remove(obj)

View File

@ -3,14 +3,12 @@ import enum, typing
from . import UTIL_virtools_types, UTIL_functions from . import UTIL_virtools_types, UTIL_functions
from . import PROP_ptrprop_resolver, PROP_ballance_map_info from . import PROP_ptrprop_resolver, PROP_ballance_map_info
# INTENT: ## Intent
# Some importer or exporter may share same properties. # Some importer or exporter may share same properties.
# So we create some shared class and user just need inherit them # So we create some shared class and user just need inherit them
# and call general getter to get user selected data. # and call general getter to get user selected data.
# Also provide draw function thus caller do not need draw the params themselves. # Also provide draw function thus caller do not need draw the params themselves.
#region Import Params
class ConflictStrategy(enum.IntEnum): class ConflictStrategy(enum.IntEnum):
Rename = enum.auto() Rename = enum.auto()
Current = enum.auto() Current = enum.auto()
@ -18,7 +16,7 @@ _g_ConflictStrategyDesc: dict[ConflictStrategy, tuple[str, str]] = {
ConflictStrategy.Rename: ('Rename', 'Rename the new one'), ConflictStrategy.Rename: ('Rename', 'Rename the new one'),
ConflictStrategy.Current: ('Use Current', 'Use current one'), ConflictStrategy.Current: ('Use Current', 'Use current one'),
} }
_g_EnumHelper_ConflictStrategy = UTIL_functions.EnumPropHelper( _g_EnumHelper_ConflictStrategy: UTIL_functions.EnumPropHelper = UTIL_functions.EnumPropHelper(
ConflictStrategy, ConflictStrategy,
lambda x: str(x.value), lambda x: str(x.value),
lambda x: ConflictStrategy(int(x)), lambda x: ConflictStrategy(int(x)),
@ -27,6 +25,39 @@ _g_EnumHelper_ConflictStrategy = UTIL_functions.EnumPropHelper(
lambda _: '' lambda _: ''
) )
#region Assist Classes
class ExportEditModeBackup():
"""
The class which save Edit Mode when exporting and restore it after exporting.
Because edit mode is not allowed when exporting.
Support `with` statement.
```
with ExportEditModeBackup():
# do some exporting work
blabla()
# restore automatically when exiting "with"
```
"""
mInEditMode: bool
def __init__(self):
if bpy.context.object and bpy.context.object.mode == "EDIT":
# set and toggle it. otherwise exporting will failed.
self.mInEditMode = True
bpy.ops.object.editmode_toggle()
else:
self.mInEditMode = False
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
if self.mInEditMode:
bpy.ops.object.editmode_toggle()
self.mInEditMode = False
class ConflictResolver(): class ConflictResolver():
""" """
This class frequently used when importing objects. This class frequently used when importing objects.
@ -120,6 +151,8 @@ class ConflictResolver():
tex.name = name tex.name = name
return (tex, True) return (tex, True)
#endregion
class ImportParams(): class ImportParams():
texture_conflict_strategy: bpy.props.EnumProperty( texture_conflict_strategy: bpy.props.EnumProperty(
name = "Texture Name Conflict", name = "Texture Name Conflict",
@ -206,65 +239,13 @@ class ImportParams():
self.general_get_texture_conflict_strategy() self.general_get_texture_conflict_strategy()
) )
#endregion
#region Export Params
class ExportEditModeBackup():
"""
The class which save Edit Mode when exporting and restore it after exporting.
Because edit mode is not allowed when exporting.
Support `with` statement.
```
with ExportEditModeBackup():
# do some exporting work
blabla()
# restore automatically when exiting "with"
```
"""
mInEditMode: bool
def __init__(self):
if bpy.context.object and bpy.context.object.mode == "EDIT":
# set and toggle it. otherwise exporting will failed.
self.mInEditMode = True
bpy.ops.object.editmode_toggle()
else:
self.mInEditMode = False
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
if self.mInEditMode:
bpy.ops.object.editmode_toggle()
self.mInEditMode = False
class ExportMode(enum.IntEnum):
BldColl = enum.auto()
BldObj = enum.auto()
BldSelObjs = enum.auto()
_g_ExportModeDesc: dict[ExportMode, tuple[str, str, str]] = {
ExportMode.BldColl: ('Collection', 'Export a collection', 'OUTLINER_COLLECTION'),
ExportMode.BldObj: ('Object', 'Export an object', 'OBJECT_DATA'),
ExportMode.BldSelObjs: ('Selected Objects', 'Export selected objects', 'SELECT_SET'),
}
_g_EnumHelper_ExportMode = UTIL_functions.EnumPropHelper(
ExportMode,
lambda x: str(x.value),
lambda x: ExportMode(int(x)),
lambda x: _g_ExportModeDesc[x][0],
lambda x: _g_ExportModeDesc[x][1],
lambda x: _g_ExportModeDesc[x][2]
)
class ExportParams(): class ExportParams():
export_mode: bpy.props.EnumProperty( export_mode: bpy.props.EnumProperty(
name = "Export Mode", name = "Export Mode",
description = "Define which 3D objects should be exported", items = (
items = _g_EnumHelper_ExportMode.generate_items(), ('COLLECTION', "Collection", "Export a collection", 'OUTLINER_COLLECTION', 0),
default = _g_EnumHelper_ExportMode.to_selection(ExportMode.BldColl), ('OBJECT', "Object", "Export an object", 'OBJECT_DATA', 1),
),
translation_context = 'BBP/UTIL_ioport_shared.ExportParams/property' translation_context = 'BBP/UTIL_ioport_shared.ExportParams/property'
) # type: ignore ) # type: ignore
@ -281,40 +262,29 @@ class ExportParams():
horizon_body.prop(self, "export_mode", expand=True) horizon_body.prop(self, "export_mode", expand=True)
# draw picker # draw picker
export_mode = _g_EnumHelper_ExportMode.get_selection(self.export_mode)
ptrprops = PROP_ptrprop_resolver.PropsVisitor(context.scene) ptrprops = PROP_ptrprop_resolver.PropsVisitor(context.scene)
match export_mode: if self.export_mode == 'COLLECTION':
case ExportMode.BldColl:
ptrprops.draw_export_collection(body) ptrprops.draw_export_collection(body)
case ExportMode.BldObj: elif self.export_mode == 'OBJECT':
ptrprops.draw_export_object(body) ptrprops.draw_export_object(body)
case ExportMode.BldSelObjs:
pass # Draw nothing
def general_get_export_objects(self, context: bpy.types.Context) -> tuple[bpy.types.Object, ...] | None: def general_get_export_objects(self, context: bpy.types.Context) -> tuple[bpy.types.Object] | None:
""" """
Return resolved exported objects or None if no selection. Return resolved exported objects or None if no selection.
""" """
export_mode = _g_EnumHelper_ExportMode.get_selection(self.export_mode)
ptrprops = PROP_ptrprop_resolver.PropsVisitor(context.scene) ptrprops = PROP_ptrprop_resolver.PropsVisitor(context.scene)
match export_mode: if self.export_mode == 'COLLECTION':
case ExportMode.BldColl:
col: bpy.types.Collection = ptrprops.get_export_collection() col: bpy.types.Collection = ptrprops.get_export_collection()
if col is None: return None if col is None: return None
else: return tuple(col.all_objects) else:
case ExportMode.BldObj: return tuple(col.all_objects)
else:
obj: bpy.types.Object = ptrprops.get_export_object() obj: bpy.types.Object = ptrprops.get_export_object()
if obj is None: return None if obj is None: return None
else: return (obj, ) else: return (obj, )
case ExportMode.BldSelObjs:
return tuple(context.selected_objects)
#endregion
#region Virtools Params
# define global tex save opt blender enum prop helper # define global tex save opt blender enum prop helper
_g_EnumHelper_CK_TEXTURE_SAVEOPTIONS = UTIL_virtools_types.EnumPropHelper(UTIL_virtools_types.CK_TEXTURE_SAVEOPTIONS) _g_EnumHelper_CK_TEXTURE_SAVEOPTIONS: UTIL_virtools_types.EnumPropHelper = UTIL_virtools_types.EnumPropHelper(UTIL_virtools_types.CK_TEXTURE_SAVEOPTIONS)
class VirtoolsParams(): class VirtoolsParams():
texture_save_opt: bpy.props.EnumProperty( texture_save_opt: bpy.props.EnumProperty(
@ -379,10 +349,6 @@ class VirtoolsParams():
def general_get_compress_level(self) -> int: def general_get_compress_level(self) -> int:
return self.compress_level return self.compress_level
#endregion
#region Ballance Params
class BallanceParams(): class BallanceParams():
successive_sector: bpy.props.BoolProperty( successive_sector: bpy.props.BoolProperty(
name="Successive Sector", name="Successive Sector",
@ -421,5 +387,3 @@ class BallanceParams():
map_info: PROP_ballance_map_info.RawBallanceMapInfo map_info: PROP_ballance_map_info.RawBallanceMapInfo
map_info = PROP_ballance_map_info.get_raw_ballance_map_info(bpy.context.scene) map_info = PROP_ballance_map_info.get_raw_ballance_map_info(bpy.context.scene)
return map_info.mSectorCount return map_info.mSectorCount
#endregion

View File

@ -72,7 +72,7 @@ class RenameErrorReporter():
print('============') print('============')
print(bpy.app.translations.pgettext_rpt( print(bpy.app.translations.pgettext_rpt(
'Rename Report', 'Rename Report',
'BBP/UTIL_naming_convention.RenameErrorReporter' 'BBP/UTIL_naming_convension.RenameErrorReporter'
)) ))
print('------------') print('------------')
# return self as context # return self as context
@ -82,7 +82,7 @@ class RenameErrorReporter():
# print console report tail # print console report tail
print('------------') print('------------')
tr_text: str = bpy.app.translations.pgettext_rpt( tr_text: str = bpy.app.translations.pgettext_rpt(
'All / Failed - {0} / {1}', 'BBP/UTIL_naming_convention.RenameErrorReporter') 'All / Failed - {0} / {1}', 'BBP/UTIL_naming_convension.RenameErrorReporter')
print(tr_text.format(self.mAllObjCounter, self.mFailedObjCounter)) print(tr_text.format(self.mAllObjCounter, self.mFailedObjCounter))
print('============') print('============')
# reset variables # reset variables
@ -107,10 +107,10 @@ class RenameErrorReporter():
tr_text: str tr_text: str
new_name: str = obj.name new_name: str = obj.name
if self.mOldName == new_name: if self.mOldName == new_name:
tr_text = bpy.app.translations.pgettext_rpt('For object "{0}"', 'BBP/UTIL_naming_convention.RenameErrorReporter') tr_text = bpy.app.translations.pgettext_rpt('For object "{0}"', 'BBP/UTIL_naming_convension.RenameErrorReporter')
print(tr_text.format(new_name)) print(tr_text.format(new_name))
else: else:
tr_text = bpy.app.translations.pgettext_rpt('For object "{0}" (Old name: "{1}")', 'BBP/UTIL_naming_convention.RenameErrorReporter') tr_text = bpy.app.translations.pgettext_rpt('For object "{0}" (Old name: "{1}")', 'BBP/UTIL_naming_convension.RenameErrorReporter')
print(tr_text.format(new_name, self.mOldName)) print(tr_text.format(new_name, self.mOldName))
# output error list with indent # output error list with indent
@ -125,11 +125,11 @@ class RenameErrorReporter():
def __errtype_to_string(err_v: _RenameErrorType) -> str: def __errtype_to_string(err_v: _RenameErrorType) -> str:
match(err_v): match(err_v):
case _RenameErrorType.ERROR: case _RenameErrorType.ERROR:
return bpy.app.translations.pgettext_rpt('ERROR', 'BBP/UTIL_naming_convention.RenameErrorReporter') return bpy.app.translations.pgettext_rpt('ERROR', 'BBP/UTIL_naming_convension.RenameErrorReporter')
case _RenameErrorType.WARNING: case _RenameErrorType.WARNING:
return bpy.app.translations.pgettext_rpt('WARN', 'BBP/UTIL_naming_convention.RenameErrorReporter') return bpy.app.translations.pgettext_rpt('WARN', 'BBP/UTIL_naming_convension.RenameErrorReporter')
case _RenameErrorType.INFO: case _RenameErrorType.INFO:
return bpy.app.translations.pgettext_rpt('INFO', 'BBP/UTIL_naming_convention.RenameErrorReporter') return bpy.app.translations.pgettext_rpt('INFO', 'BBP/UTIL_naming_convension.RenameErrorReporter')
case _: raise UTIL_functions.BBPException("Unknown error type.") case _: raise UTIL_functions.BBPException("Unknown error type.")
@staticmethod @staticmethod
def __erritem_to_string(item: _RenameErrorItem) -> str: def __erritem_to_string(item: _RenameErrorItem) -> str:
@ -292,7 +292,7 @@ class VirtoolsGroupConvention():
) )
tr_text: str = bpy.app.translations.pgettext_rpt( tr_text: str = bpy.app.translations.pgettext_rpt(
"PC_Checkpoints or PR_Resetpoints detected. But couldn't get sector from name.", 'BBP/UTIL_naming_convention.VirtoolsGroupConvention') "PC_Checkpoints or PR_Resetpoints detected. But couldn't get sector from name.", 'BBP/UTIL_naming_convension.VirtoolsGroupConvention')
if reporter: reporter.add_error(tr_text) if reporter: reporter.add_error(tr_text)
return None return None
@ -337,12 +337,12 @@ class VirtoolsGroupConvention():
return VirtoolsGroupConvention.__get_pcpr_from_name(obj.name, reporter) return VirtoolsGroupConvention.__get_pcpr_from_name(obj.name, reporter)
case _: case _:
tr_text = bpy.app.translations.pgettext_rpt( tr_text = bpy.app.translations.pgettext_rpt(
"The match of Unique Component lost.", 'BBP/UTIL_naming_convention.VirtoolsGroupConvention') "The match of Unique Component lost.", 'BBP/UTIL_naming_convension.VirtoolsGroupConvention')
if reporter: reporter.add_error(tr_text) if reporter: reporter.add_error(tr_text)
return None return None
elif len(inter_gps) != 0: elif len(inter_gps) != 0:
tr_text = bpy.app.translations.pgettext_rpt( tr_text = bpy.app.translations.pgettext_rpt(
"A Multi-grouping Unique Component.", 'BBP/UTIL_naming_convention.VirtoolsGroupConvention') "A Multi-grouping Unique Component.", 'BBP/UTIL_naming_convension.VirtoolsGroupConvention')
if reporter: reporter.add_error(tr_text) if reporter: reporter.add_error(tr_text)
return None return None
@ -356,7 +356,7 @@ class VirtoolsGroupConvention():
if gotten_sector is None: if gotten_sector is None:
# fail to get sector # fail to get sector
tr_text = bpy.app.translations.pgettext_rpt( tr_text = bpy.app.translations.pgettext_rpt(
"Component detected. But couldn't get sector from CKGroup data.", 'BBP/UTIL_naming_convention.VirtoolsGroupConvention') "Component detected. But couldn't get sector from CKGroup data.", 'BBP/UTIL_naming_convension.VirtoolsGroupConvention')
if reporter: reporter.add_error(tr_text) if reporter: reporter.add_error(tr_text)
return None return None
return BallanceObjectInfo.create_from_component( return BallanceObjectInfo.create_from_component(
@ -366,7 +366,7 @@ class VirtoolsGroupConvention():
elif len(inter_gps) != 0: elif len(inter_gps) != 0:
# must be a weird grouping, report it # must be a weird grouping, report it
tr_text = bpy.app.translations.pgettext_rpt( tr_text = bpy.app.translations.pgettext_rpt(
"A Multi-grouping Component.", 'BBP/UTIL_naming_convention.VirtoolsGroupConvention') "A Multi-grouping Component.", 'BBP/UTIL_naming_convension.VirtoolsGroupConvention')
if reporter: reporter.add_error(tr_text) if reporter: reporter.add_error(tr_text)
return None return None
@ -384,7 +384,7 @@ class VirtoolsGroupConvention():
return BallanceObjectInfo.create_from_others(BallanceObjectType.WOOD) return BallanceObjectInfo.create_from_others(BallanceObjectType.WOOD)
else: else:
tr_text = bpy.app.translations.pgettext_rpt( tr_text = bpy.app.translations.pgettext_rpt(
"Can't distinguish object between Floors and Rails. Suppose it is Floors.", 'BBP/UTIL_naming_convention.VirtoolsGroupConvention') "Can't distinguish object between Floors and Rails. Suppose it is Floors.", 'BBP/UTIL_naming_convension.VirtoolsGroupConvention')
if reporter: reporter.add_warning(tr_text) if reporter: reporter.add_warning(tr_text)
return BallanceObjectInfo.create_from_others(BallanceObjectType.FLOOR) return BallanceObjectInfo.create_from_others(BallanceObjectType.FLOOR)
elif gp.contain_group('Phys_FloorStopper'): elif gp.contain_group('Phys_FloorStopper'):
@ -394,7 +394,7 @@ class VirtoolsGroupConvention():
# no matched # no matched
tr_text = bpy.app.translations.pgettext_rpt( tr_text = bpy.app.translations.pgettext_rpt(
"Group match lost.", 'BBP/UTIL_naming_convention.VirtoolsGroupConvention') "Group match lost.", 'BBP/UTIL_naming_convension.VirtoolsGroupConvention')
if reporter: reporter.add_error(tr_text) if reporter: reporter.add_error(tr_text)
return None return None
@ -445,7 +445,7 @@ class VirtoolsGroupConvention():
case _: case _:
tr_text: str = bpy.app.translations.pgettext_rpt( tr_text: str = bpy.app.translations.pgettext_rpt(
"No matched info.", 'BBP/UTIL_naming_convention.VirtoolsGroupConvention') "No matched info.", 'BBP/UTIL_naming_convension.VirtoolsGroupConvention')
if reporter: reporter.add_error(tr_text) if reporter: reporter.add_error(tr_text)
return False return False
@ -499,7 +499,7 @@ class YYCToolchainConvention():
return BallanceObjectInfo.create_from_others(BallanceObjectType.SKYLAYER) return BallanceObjectInfo.create_from_others(BallanceObjectType.SKYLAYER)
tr_text: str = bpy.app.translations.pgettext_rpt( tr_text: str = bpy.app.translations.pgettext_rpt(
"Name match lost.", 'BBP/UTIL_naming_convention.YYCToolchainConvention') "Name match lost.", 'BBP/UTIL_naming_convension.YYCToolchainConvention')
if reporter: reporter.add_error(tr_text) if reporter: reporter.add_error(tr_text)
return None return None
@ -543,7 +543,7 @@ class YYCToolchainConvention():
case _: case _:
tr_text: str = bpy.app.translations.pgettext_rpt( tr_text: str = bpy.app.translations.pgettext_rpt(
"No matched info.", 'BBP/UTIL_naming_convention.YYCToolchainConvention') "No matched info.", 'BBP/UTIL_naming_convension.YYCToolchainConvention')
if reporter: reporter.add_error(tr_text) if reporter: reporter.add_error(tr_text)
return None return None
@ -607,7 +607,7 @@ class ImengyuConvention():
return BallanceObjectInfo.create_from_others(BallanceObjectType.SKYLAYER) return BallanceObjectInfo.create_from_others(BallanceObjectType.SKYLAYER)
tr_text: str = bpy.app.translations.pgettext_rpt( tr_text: str = bpy.app.translations.pgettext_rpt(
"Name match lost.", 'BBP/UTIL_naming_convention.ImengyuConvention') "Name match lost.", 'BBP/UTIL_naming_convension.ImengyuConvention')
if reporter: reporter.add_error(tr_text) if reporter: reporter.add_error(tr_text)
return None return None
@ -653,7 +653,7 @@ class ImengyuConvention():
case _: case _:
tr_text: str = bpy.app.translations.pgettext_rpt( tr_text: str = bpy.app.translations.pgettext_rpt(
"No matched info.", 'BBP/UTIL_naming_convention.ImengyuConvention') "No matched info.", 'BBP/UTIL_naming_convension.ImengyuConvention')
if reporter: reporter.add_error(tr_text) if reporter: reporter.add_error(tr_text)
return None return None

View File

@ -1,6 +1,6 @@
import bpy, bmesh, mathutils, math import bpy, bmesh, mathutils, math
import typing import typing
from . import UTIL_functions, UTIL_naming_convention from . import UTIL_functions, UTIL_naming_convension
from . import PROP_bme_material from . import PROP_bme_material
#region BMesh Operations Helper #region BMesh Operations Helper
@ -132,16 +132,16 @@ def rail_creator_wrapper(fct_poly_cret: typing.Callable[[bmesh.types.BMesh], Non
# create object and assoc with it # create object and assoc with it
# create info first # create info first
rail_info: UTIL_naming_convention.BallanceObjectInfo = UTIL_naming_convention.BallanceObjectInfo.create_from_others( rail_info: UTIL_naming_convension.BallanceObjectInfo = UTIL_naming_convension.BallanceObjectInfo.create_from_others(
UTIL_naming_convention.BallanceObjectType.RAIL UTIL_naming_convension.BallanceObjectType.RAIL
) )
# then get object name # then get object name
rail_name: str | None = UTIL_naming_convention.YYCToolchainConvention.set_to_name(rail_info, None) rail_name: str | None = UTIL_naming_convension.YYCToolchainConvention.set_to_name(rail_info, None)
if rail_name is None: raise UTIL_functions.BBPException('impossible null name') if rail_name is None: raise UTIL_functions.BBPException('impossible null name')
# create object by name # create object by name
obj: bpy.types.Object = bpy.data.objects.new(rail_name, mesh) obj: bpy.types.Object = bpy.data.objects.new(rail_name, mesh)
# assign virtools groups # assign virtools groups
UTIL_naming_convention.VirtoolsGroupConvention.set_to_object(obj, rail_info, None) UTIL_naming_convension.VirtoolsGroupConvention.set_to_object(obj, rail_info, None)
# move to cursor # move to cursor
UTIL_functions.add_into_scene_and_move_to_cursor(obj) UTIL_functions.add_into_scene_and_move_to_cursor(obj)

View File

@ -235,28 +235,21 @@ _g_Annotation: dict[type, dict[int, EnumAnnotation]] = {
} }
} }
_TRawEnum = typing.TypeVar('_TRawEnum', bound = enum.Enum) class EnumPropHelper(UTIL_functions.EnumPropHelper):
class EnumPropHelper(UTIL_functions.EnumPropHelper[_TRawEnum]):
""" """
Virtools type specified Blender EnumProp helper. Virtools type specified Blender EnumProp helper.
""" """
__mAnnotationDict: dict[int, EnumAnnotation] __mAnnotationDict: dict[int, EnumAnnotation]
__mEnumTy: type[_TRawEnum] __mEnumTy: type[enum.Enum]
def __init__(self, ty: type[_TRawEnum]): def __init__(self, ty: type[enum.Enum]):
# set enum type and annotation ref first # set enum type and annotation ref first
self.__mEnumTy = ty self.__mEnumTy = ty
self.__mAnnotationDict = _g_Annotation[ty] self.__mAnnotationDict = _g_Annotation[ty]
# init parent data
# YYC MARK: UTIL_functions.EnumPropHelper.__init__(
# It seems that Pylance has bad generic analyse ability in there. self,
# It can not deduce the correct generic type in lambda. self.__mEnumTy, # enum.Enum it self is iterable
# I gave up.
# Init parent data
super().__init__(
self.__mEnumTy, # enum.Enum its self is iterable
lambda x: str(x.value), # convert enum.Enum's value to string lambda x: str(x.value), # convert enum.Enum's value to string
lambda x: self.__mEnumTy(int(x)), # use stored enum type and int() to get enum member lambda x: self.__mEnumTy(int(x)), # use stored enum type and int() to get enum member
lambda x: self.__mAnnotationDict[x.value].mDisplayName, lambda x: self.__mAnnotationDict[x.value].mDisplayName,
@ -272,8 +265,8 @@ def virtools_name_regulator(name: str | None) -> str:
if name: return name if name: return name
else: return bpy.app.translations.pgettext_data('annoymous', 'BME/UTIL_virtools_types.virtools_name_regulator()') else: return bpy.app.translations.pgettext_data('annoymous', 'BME/UTIL_virtools_types.virtools_name_regulator()')
# YYC MARK: ## Default Encoding for PyBMap
# There are default encodings for PyBMap. We support Western European and Simplified Chinese in default. # Use semicolon split each encodings. Support Western European and Simplified Chinese in default.
# Since LibCmo 0.2, the encoding name of LibCmo become universal encoding which is platfoorm independent. # Since LibCmo 0.2, the encoding name of LibCmo become universal encoding which is platfoorm independent.
# So no need set it according to different platform. # So no need set it according to different platform.
# Use universal encoding name (like Python). # Use universal encoding name (like Python).

View File

@ -1,8 +1,8 @@
#region Import and Reload #region Reload and Import
# import core lib # import core lib
import bpy import bpy
import typing, enum import typing, collections
# reload if needed # reload if needed
# TODO: finish reload feature if needed. # TODO: finish reload feature if needed.
@ -10,6 +10,8 @@ import typing, enum
if "bpy" in locals(): if "bpy" in locals():
import importlib import importlib
#endregion
# we must load icons manager first # we must load icons manager first
# and register it # and register it
from . import UTIL_icons_manager from . import UTIL_icons_manager
@ -21,147 +23,13 @@ from . import PROP_preferences, PROP_ptrprop_resolver, PROP_virtools_material, P
from . import PROP_ballance_element, PROP_bme_material, PROP_ballance_map_info from . import PROP_ballance_element, PROP_bme_material, PROP_ballance_map_info
from . import OP_IMPORT_bmfile, OP_EXPORT_bmfile, OP_IMPORT_virtools, OP_EXPORT_virtools from . import OP_IMPORT_bmfile, OP_EXPORT_bmfile, OP_IMPORT_virtools, OP_EXPORT_virtools
from . import OP_UV_flatten_uv, OP_UV_rail_uv from . import OP_UV_flatten_uv, OP_UV_rail_uv
from . import OP_MTL_fix_materials from . import OP_MTL_fix_material
from . import OP_ADDS_component, OP_ADDS_bme, OP_ADDS_rail from . import OP_ADDS_component, OP_ADDS_bme, OP_ADDS_rail
from . import OP_OBJECT_legacy_align, OP_OBJECT_virtools_group, OP_OBJECT_snoop_group_then_to_mesh, OP_OBJECT_naming_convention from . import OP_OBJECT_legacy_align, OP_OBJECT_virtools_group, OP_OBJECT_snoop_group_then_to_mesh, OP_OBJECT_naming_convention
#endregion #region Menu
#region Menu and Sidebar Panel # ===== Menu Defines =====
#region Ballance Adder Menu and Panel
class DrawTarget(enum.IntEnum):
BldMenu = enum.auto()
BldPanel = enum.auto()
def reuse_create_layout(layout: bpy.types.UILayout, target: DrawTarget) -> bpy.types.UILayout:
# If we are draw for Panel, we need use Grid to use space enough.
match target:
case DrawTarget.BldMenu:
return layout
case DrawTarget.BldPanel:
return layout.grid_flow(even_columns=True, even_rows=True)
def reuse_draw_add_bme(layout: bpy.types.UILayout, target: DrawTarget):
# Draw operators.
OP_ADDS_bme.BBP_OT_add_bme_struct.draw_blc_menu(reuse_create_layout(layout, target))
def reuse_draw_add_rail(layout: bpy.types.UILayout, target: DrawTarget):
layout.label(text="Sections", icon='MESH_CIRCLE', text_ctxt='BBP/__init__.reuse_draw_add_rail()')
sublayout = reuse_create_layout(layout, target)
sublayout.operator(OP_ADDS_rail.BBP_OT_add_rail_section.bl_idname)
sublayout.operator(OP_ADDS_rail.BBP_OT_add_transition_section.bl_idname)
layout.separator()
layout.label(text="Straight Rails", icon='IPO_CONSTANT', text_ctxt='BBP/__init__.reuse_draw_add_rail()')
sublayout = reuse_create_layout(layout, target)
sublayout.operator(OP_ADDS_rail.BBP_OT_add_straight_rail.bl_idname)
sublayout.operator(OP_ADDS_rail.BBP_OT_add_transition_rail.bl_idname)
sublayout.operator(OP_ADDS_rail.BBP_OT_add_side_rail.bl_idname)
layout.separator()
layout.label(text="Curve Rails", icon='MOD_SCREW', text_ctxt='BBP/__init__.reuse_draw_add_rail()')
sublayout = reuse_create_layout(layout, target)
sublayout.operator(OP_ADDS_rail.BBP_OT_add_arc_rail.bl_idname)
sublayout.operator(OP_ADDS_rail.BBP_OT_add_spiral_rail.bl_idname)
sublayout.operator(OP_ADDS_rail.BBP_OT_add_side_spiral_rail.bl_idname)
def reuse_draw_add_component(layout: bpy.types.UILayout, target: DrawTarget):
# We only use Grid for basic components
layout.label(text="Basic Components", text_ctxt='BBP/__init__.reuse_draw_add_component()')
OP_ADDS_component.BBP_OT_add_component.draw_blc_menu(reuse_create_layout(layout, target))
layout.separator()
layout.label(text="Nong Components", text_ctxt='BBP/__init__.reuse_draw_add_component()')
sublayout = reuse_create_layout(layout, target)
OP_ADDS_component.BBP_OT_add_nong_extra_point.draw_blc_menu(sublayout)
OP_ADDS_component.BBP_OT_add_nong_ventilator.draw_blc_menu(sublayout)
layout.separator()
layout.label(text="Series Components", text_ctxt='BBP/__init__.reuse_draw_add_component()')
sublayout = reuse_create_layout(layout, target)
OP_ADDS_component.BBP_OT_add_tilting_block_series.draw_blc_menu(sublayout)
OP_ADDS_component.BBP_OT_add_swing_series.draw_blc_menu(sublayout)
OP_ADDS_component.BBP_OT_add_ventilator_series.draw_blc_menu(sublayout)
layout.separator()
layout.label(text="Components Pair", text_ctxt='BBP/__init__.reuse_draw_add_component()')
sublayout = reuse_create_layout(layout, target)
OP_ADDS_component.BBP_OT_add_sector_component_pair.draw_blc_menu(sublayout)
class BBP_MT_AddBmeMenu(bpy.types.Menu):
"""Add Ballance Floor"""
bl_idname = "BBP_MT_AddBmeMenu"
bl_label = "Floors"
bl_translation_context = 'BBP_MT_AddBmeMenu'
def draw(self, context):
reuse_draw_add_bme(self.layout, DrawTarget.BldMenu)
class BBP_MT_AddRailMenu(bpy.types.Menu):
"""Add Ballance Rail"""
bl_idname = "BBP_MT_AddRailMenu"
bl_label = "Rails"
bl_translation_context = 'BBP_MT_AddRailMenu'
def draw(self, context):
reuse_draw_add_rail(self.layout, DrawTarget.BldMenu)
class BBP_MT_AddComponentMenu(bpy.types.Menu):
"""Add Ballance Component"""
bl_idname = "BBP_MT_AddComponentsMenu"
bl_label = "Components"
bl_translation_context = 'BBP_MT_AddComponentsMenu'
def draw(self, context):
reuse_draw_add_component(self.layout, DrawTarget.BldMenu)
class BBP_PT_SidebarAddBmePanel(bpy.types.Panel):
"""Add Ballance Floor"""
bl_label = "Floors"
bl_idname = "BBP_PT_SidebarAddBmePanel"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_context = "objectmode"
bl_category = 'Ballance'
bl_options = {'DEFAULT_CLOSED'}
bl_translation_context = 'BBP_PT_SidebarAddBmePanel'
def draw(self, context):
reuse_draw_add_bme(self.layout, DrawTarget.BldPanel)
class BBP_PT_SidebarAddRailPanel(bpy.types.Panel):
"""Add Ballance Rail"""
bl_label = "Rails"
bl_idname = "BBP_PT_SidebarAddRailPanel"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_context = "objectmode"
bl_category = 'Ballance'
bl_options = {'DEFAULT_CLOSED'}
bl_translation_context = 'BBP_PT_SidebarAddRailPanel'
def draw(self, context):
reuse_draw_add_rail(self.layout, DrawTarget.BldPanel)
class BBP_PT_SidebarAddComponentPanel(bpy.types.Panel):
"""Add Ballance Component"""
bl_label = "Components"
bl_idname = "BBP_PT_SidebarAddComponentPanel"
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_context = "objectmode"
bl_category = 'Ballance'
bl_options = {'DEFAULT_CLOSED'}
bl_translation_context = 'BBP_PT_SidebarAddComponentPanel'
def draw(self, context):
reuse_draw_add_component(self.layout, DrawTarget.BldPanel)
#endregion
#region Other Menu
class BBP_MT_View3DMenu(bpy.types.Menu): class BBP_MT_View3DMenu(bpy.types.Menu):
"""Ballance 3D related operators""" """Ballance 3D related operators"""
@ -182,13 +50,73 @@ class BBP_MT_View3DMenu(bpy.types.Menu):
layout.operator(OP_OBJECT_virtools_group.BBP_OT_select_object_by_virtools_group.bl_idname) layout.operator(OP_OBJECT_virtools_group.BBP_OT_select_object_by_virtools_group.bl_idname)
layout.separator() layout.separator()
layout.label(text='Material', icon='MATERIAL', text_ctxt='BBP_MT_View3DMenu/draw') layout.label(text='Material', icon='MATERIAL', text_ctxt='BBP_MT_View3DMenu/draw')
layout.operator(OP_MTL_fix_materials.BBP_OT_fix_all_materials.bl_idname) layout.operator(OP_MTL_fix_material.BBP_OT_fix_all_material.bl_idname)
#endregion class BBP_MT_AddBmeMenu(bpy.types.Menu):
"""Add Ballance Floor"""
bl_idname = "BBP_MT_AddBmeMenu"
bl_label = "Floors"
bl_translation_context = 'BBP_MT_AddBmeMenu'
#region Menu Drawer def draw(self, context):
layout = self.layout
OP_ADDS_bme.BBP_OT_add_bme_struct.draw_blc_menu(layout)
TFctMenuDrawer = typing.Callable[[typing.Any, typing.Any], None] class BBP_MT_AddRailMenu(bpy.types.Menu):
"""Add Ballance Rail"""
bl_idname = "BBP_MT_AddRailMenu"
bl_label = "Rails"
bl_translation_context = 'BBP_MT_AddRailMenu'
def draw(self, context):
layout = self.layout
layout.label(text="Sections", icon='MESH_CIRCLE', text_ctxt='BBP_MT_AddRailMenu/draw')
layout.operator(OP_ADDS_rail.BBP_OT_add_rail_section.bl_idname)
layout.operator(OP_ADDS_rail.BBP_OT_add_transition_section.bl_idname)
layout.separator()
layout.label(text="Straight Rails", icon='IPO_CONSTANT', text_ctxt='BBP_MT_AddRailMenu/draw')
layout.operator(OP_ADDS_rail.BBP_OT_add_straight_rail.bl_idname)
layout.operator(OP_ADDS_rail.BBP_OT_add_transition_rail.bl_idname)
layout.operator(OP_ADDS_rail.BBP_OT_add_side_rail.bl_idname)
layout.separator()
layout.label(text="Curve Rails", icon='MOD_SCREW', text_ctxt='BBP_MT_AddRailMenu/draw')
layout.operator(OP_ADDS_rail.BBP_OT_add_arc_rail.bl_idname)
layout.operator(OP_ADDS_rail.BBP_OT_add_spiral_rail.bl_idname)
layout.operator(OP_ADDS_rail.BBP_OT_add_side_spiral_rail.bl_idname)
class BBP_MT_AddComponentsMenu(bpy.types.Menu):
"""Add Ballance Component"""
bl_idname = "BBP_MT_AddComponentsMenu"
bl_label = "Components"
bl_translation_context = 'BBP_MT_AddComponentsMenu'
def draw(self, context):
layout = self.layout
layout.label(text="Basic Components", text_ctxt='BBP_MT_AddComponentsMenu/draw')
OP_ADDS_component.BBP_OT_add_component.draw_blc_menu(layout)
layout.separator()
layout.label(text="Nong Components", text_ctxt='BBP_MT_AddComponentsMenu/draw')
OP_ADDS_component.BBP_OT_add_nong_extra_point.draw_blc_menu(layout)
OP_ADDS_component.BBP_OT_add_nong_ventilator.draw_blc_menu(layout)
layout.separator()
layout.label(text="Series Components", text_ctxt='BBP_MT_AddComponentsMenu/draw')
OP_ADDS_component.BBP_OT_add_tilting_block_series.draw_blc_menu(layout)
OP_ADDS_component.BBP_OT_add_swing_series.draw_blc_menu(layout)
OP_ADDS_component.BBP_OT_add_ventilator_series.draw_blc_menu(layout)
layout.separator()
layout.label(text="Components Pair", text_ctxt='BBP_MT_AddComponentsMenu/draw')
OP_ADDS_component.BBP_OT_add_sector_component_pair.draw_blc_menu(layout)
# ===== Menu Drawer =====
MenuDrawer_t = typing.Callable[[typing.Any, typing.Any], None]
def menu_drawer_import(self, context) -> None: def menu_drawer_import(self, context) -> None:
layout: bpy.types.UILayout = self.layout layout: bpy.types.UILayout = self.layout
@ -226,17 +154,16 @@ def menu_drawer_add(self, context) -> None:
layout.label(text="Ballance", text_ctxt='BBP/__init__.menu_drawer_add()') layout.label(text="Ballance", text_ctxt='BBP/__init__.menu_drawer_add()')
layout.menu(BBP_MT_AddBmeMenu.bl_idname, icon='MESH_CUBE') layout.menu(BBP_MT_AddBmeMenu.bl_idname, icon='MESH_CUBE')
layout.menu(BBP_MT_AddRailMenu.bl_idname, icon='MESH_CIRCLE') layout.menu(BBP_MT_AddRailMenu.bl_idname, icon='MESH_CIRCLE')
layout.menu(BBP_MT_AddComponentMenu.bl_idname, icon='MESH_ICOSPHERE') layout.menu(BBP_MT_AddComponentsMenu.bl_idname, icon='MESH_ICOSPHERE')
def menu_drawer_grouping(self, context) -> None: def menu_drawer_grouping(self, context) -> None:
layout: bpy.types.UILayout = self.layout layout: bpy.types.UILayout = self.layout
layout.separator() layout.separator()
# YYC MARK: # NOTE: because outline context may change operator context
# Because outline context change operator context into EXEC_*,
# so it will cause no popup window when click operator in outline. # so it will cause no popup window when click operator in outline.
# Thus we create a sub layout and set its operator context as 'INVOKE_DEFAULT', # thus we create a sub layout and set its operator context as 'INVOKE_DEFAULT'
# so that all operators can pop up normally. # thus, all operators can pop up normally.
col = layout.column() col = layout.column()
col.operator_context = 'INVOKE_DEFAULT' col.operator_context = 'INVOKE_DEFAULT'
@ -261,8 +188,7 @@ def menu_drawer_naming_convention(self, context) -> None:
layout: bpy.types.UILayout = self.layout layout: bpy.types.UILayout = self.layout
layout.separator() layout.separator()
# YYC MARK: # same reason in `menu_drawer_grouping()``
# Same reason for changing operator context introduced in `menu_drawer_grouping()`
col = layout.column() col = layout.column()
col.operator_context = 'INVOKE_DEFAULT' col.operator_context = 'INVOKE_DEFAULT'
@ -273,26 +199,20 @@ def menu_drawer_naming_convention(self, context) -> None:
#endregion #endregion
#endregion
#region Register and Unregister. #region Register and Unregister.
g_BldClasses: tuple[typing.Any, ...] = ( g_BldClasses: tuple[typing.Any, ...] = (
BBP_MT_View3DMenu, BBP_MT_View3DMenu,
BBP_MT_AddBmeMenu, BBP_MT_AddBmeMenu,
BBP_MT_AddRailMenu, BBP_MT_AddRailMenu,
BBP_MT_AddComponentMenu, BBP_MT_AddComponentsMenu
BBP_PT_SidebarAddBmePanel,
BBP_PT_SidebarAddRailPanel,
BBP_PT_SidebarAddComponentPanel,
) )
class MenuEntry(): class MenuEntry():
mContainerMenu: bpy.types.Menu mContainerMenu: bpy.types.Menu
mIsAppend: bool mIsAppend: bool
mMenuDrawer: TFctMenuDrawer mMenuDrawer: MenuDrawer_t
def __init__(self, cont: bpy.types.Menu, is_append: bool, menu_func: TFctMenuDrawer): def __init__(self, cont: bpy.types.Menu, is_append: bool, menu_func: MenuDrawer_t):
self.mContainerMenu = cont self.mContainerMenu = cont
self.mIsAppend = is_append self.mIsAppend = is_append
self.mMenuDrawer = menu_func self.mMenuDrawer = menu_func
@ -305,7 +225,7 @@ g_BldMenus: tuple[MenuEntry, ...] = (
MenuEntry(bpy.types.VIEW3D_MT_object_context_menu, True, menu_drawer_snoop_then_conv), MenuEntry(bpy.types.VIEW3D_MT_object_context_menu, True, menu_drawer_snoop_then_conv),
# Register this twice (for 2 menus respectively) # register double (for 2 menus)
MenuEntry(bpy.types.VIEW3D_MT_object_context_menu, True, menu_drawer_grouping), MenuEntry(bpy.types.VIEW3D_MT_object_context_menu, True, menu_drawer_grouping),
MenuEntry(bpy.types.OUTLINER_MT_object, True, menu_drawer_grouping), MenuEntry(bpy.types.OUTLINER_MT_object, True, menu_drawer_grouping),
@ -336,7 +256,7 @@ def register() -> None:
OP_UV_rail_uv.register() OP_UV_rail_uv.register()
OP_UV_flatten_uv.register() OP_UV_flatten_uv.register()
OP_MTL_fix_materials.register() OP_MTL_fix_material.register()
OP_ADDS_component.register() OP_ADDS_component.register()
OP_ADDS_bme.register() OP_ADDS_bme.register()
@ -377,7 +297,7 @@ def unregister() -> None:
OP_ADDS_bme.unregister() OP_ADDS_bme.unregister()
OP_ADDS_component.unregister() OP_ADDS_component.unregister()
OP_MTL_fix_materials.unregister() OP_MTL_fix_material.unregister()
OP_UV_flatten_uv.unregister() OP_UV_flatten_uv.unregister()
OP_UV_rail_uv.unregister() OP_UV_rail_uv.unregister()

View File

@ -1,4 +1,4 @@
# Full context are copied from https://docs.blender.org/manual/en/latest/advanced/extensions/getting_started.html # Full context are copied from https://docs.blender.org/manual/en/dev/extensions/getting_started.html
# Please note any update of this manifest # Please note any update of this manifest
schema_version = "1.0.0" schema_version = "1.0.0"
@ -6,7 +6,7 @@ schema_version = "1.0.0"
# Example of manifest file for a Blender extension # Example of manifest file for a Blender extension
# Change the values according to your extension # Change the values according to your extension
id = "bbp_ng" id = "bbp_ng"
version = "4.3.0" version = "4.2.1"
name = "Ballance Blender Plugin" name = "Ballance Blender Plugin"
tagline = "The specialized add-on served for creating game map of Ballance" tagline = "The specialized add-on served for creating game map of Ballance"
maintainer = "yyc12345 <yyc12321@outlook.com>" maintainer = "yyc12345 <yyc12321@outlook.com>"
@ -20,7 +20,7 @@ website = "https://github.com/yyc12345/BallanceBlenderHelper"
# https://docs.blender.org/manual/en/dev/advanced/extensions/tags.html # https://docs.blender.org/manual/en/dev/advanced/extensions/tags.html
tags = ["Object", "Mesh", "UV", "Import-Export"] tags = ["Object", "Mesh", "UV", "Import-Export"]
blender_version_min = "4.5.0" blender_version_min = "4.2.0"
# # Optional: Blender version that the extension does not support, earlier versions are supported. # # Optional: Blender version that the extension does not support, earlier versions are supported.
# # This can be omitted and defined later on the extensions platform if an issue is found. # # This can be omitted and defined later on the extensions platform if an issue is found.
# blender_version_max = "5.1.0" # blender_version_max = "5.1.0"
@ -37,7 +37,7 @@ license = [
# ] # ]
# Optional list of supported platforms. If omitted, the extension will be available in all operating systems. # Optional list of supported platforms. If omitted, the extension will be available in all operating systems.
platforms = ["windows-x64", "linux-x64"] platforms = ["windows-x64", "linux-x64", "macos-arm64"]
# Supported platforms: "windows-x64", "macos-arm64", "linux-x64", "windows-arm64", "macos-x64" # Supported platforms: "windows-x64", "macos-arm64", "linux-x64", "windows-arm64", "macos-x64"
# Optional: bundle 3rd party Python modules. # Optional: bundle 3rd party Python modules.
@ -71,8 +71,12 @@ files = "Import/export Virtools file from/to disk"
[build] [build]
paths_exclude_pattern = [ paths_exclude_pattern = [
"__pycache__/", # Python runtime cache "__pycache__/", # Python runtime cache
".gitignore", # Git Ignore File
".style.yapf", # Python code style ".style.yapf", # Python code style
"*.gitkeep", # Git directory keeper "*.gitkeep", # Git directory keeper
".gitignore", # Git Ignore File
"*.md", # Useless document. "*.md", # Useless document.
"/raw_jsons", # Raw JSONs.
"/raw_icons", # Raw Icons.
"/tools", # Assistant tools.
"/i18n", # GNU gettext Translation.
] ]

View File

View File

Before

Width:  |  Height:  |  Size: 785 B

After

Width:  |  Height:  |  Size: 785 B

View File

@ -0,0 +1,7 @@
# Raw Icons
This folder contain all images used by this Blender plugin.
This folder should not be distributed in production because all of these files are in original size. It is pretty need too much time to load them in blender.
So we keep these high quality images here and provide a tools in `tools` folder. Builder should run script to generate thumbnails in `icons` folder.
Then this Blender plugin can work normally.

View File

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

View File

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 108 KiB

View File

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

View File

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 108 KiB

View File

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

View File

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

View File

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View File

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

View File

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

View File

Before

Width:  |  Height:  |  Size: 663 B

After

Width:  |  Height:  |  Size: 663 B

View File

Before

Width:  |  Height:  |  Size: 745 B

After

Width:  |  Height:  |  Size: 745 B

View File

Before

Width:  |  Height:  |  Size: 945 B

After

Width:  |  Height:  |  Size: 945 B

View File

Before

Width:  |  Height:  |  Size: 818 B

After

Width:  |  Height:  |  Size: 818 B

View File

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

Before

Width:  |  Height:  |  Size: 984 B

After

Width:  |  Height:  |  Size: 984 B

Some files were not shown because too many files have changed in this diff Show More