diff --git a/bbp_ng/OP_ADDS_component.py b/bbp_ng/OP_ADDS_component.py index 6b46ea2..bc710e4 100644 --- a/bbp_ng/OP_ADDS_component.py +++ b/bbp_ng/OP_ADDS_component.py @@ -1,7 +1,7 @@ import bpy, mathutils import math, typing from . import UTIL_functions, UTIL_icons_manager, UTIL_naming_convension -from . import PROP_ballance_element, PROP_virtools_group +from . import PROP_ballance_element, PROP_virtools_group, PROP_ballance_map_info #region Param Help Classes @@ -128,6 +128,7 @@ class _GeneralComponentCreator(): """ # get element info first ele_info: UTIL_naming_convension.BallanceObjectInfo = _get_component_info(comp_type, comp_sector) + # create blc element context with PROP_ballance_element.BallanceElementsHelper(bpy.context.scene) as creator: # object creation counter @@ -143,6 +144,27 @@ class _GeneralComponentCreator(): # put into created object list self.__mObjList.append(obj) + # enlarge scene sector field for non-PS (start point) PE (end point) component + # read from scene and create var for enlarged sector count + map_info: PROP_ballance_map_info.RawBallanceMapInfo = PROP_ballance_map_info.get_raw_ballance_map_info(bpy.context.scene) + enlarged_sector: int + # check component type to get enlarged value + match(ele_info.mBasicType): + case UTIL_naming_convension.BallanceObjectType.COMPONENT: + enlarged_sector = comp_sector + case UTIL_naming_convension.BallanceObjectType.CHECKPOINT: + # checkpoint 1 means that there is sector 2, so we plus 1 for it. + enlarged_sector = comp_sector + 1 + case UTIL_naming_convension.BallanceObjectType.RESETPOINT: + enlarged_sector = comp_sector + case _: + # this component is not a sector based component + # so we do not change it (use original value) + enlarged_sector = map_info.mSectorCount + # enlarge it + map_info.mSectorCount = max(map_info.mSectorCount, enlarged_sector) + PROP_ballance_map_info.set_raw_ballance_map_info(bpy.context.scene, map_info) + def finish_component(self) -> None: """ Finish up component creation. diff --git a/bbp_ng/OP_EXPORT_virtools.py b/bbp_ng/OP_EXPORT_virtools.py index f181e06..e7d45df 100644 --- a/bbp_ng/OP_EXPORT_virtools.py +++ b/bbp_ng/OP_EXPORT_virtools.py @@ -1,9 +1,9 @@ import bpy from bpy_extras.wm_utils.progress_report import ProgressReport -import tempfile, os, typing, re +import tempfile, os, typing from . import PROP_preferences, UTIL_ioport_shared -from . import UTIL_virtools_types, UTIL_functions, UTIL_file_browser, UTIL_blender_mesh, UTIL_ballance_texture, UTIL_icons_manager -from . import PROP_virtools_group, PROP_virtools_material, PROP_virtools_mesh, PROP_virtools_texture +from . import UTIL_virtools_types, UTIL_functions, UTIL_file_browser, UTIL_blender_mesh, UTIL_ballance_texture, UTIL_icons_manager, UTIL_naming_convension +from . import PROP_virtools_group, PROP_virtools_material, PROP_virtools_mesh, PROP_virtools_texture, PROP_ballance_map_info from .PyBMap import bmap_wrapper as bmap # define global tex save opt blender enum prop helper @@ -84,6 +84,11 @@ class BBP_OT_export_virtools(bpy.types.Operator, UTIL_file_browser.ExportVirtool if self.use_compress: box.prop(self, 'compress_level') + # show sector info to notice user + layout.separator() + map_info: PROP_ballance_map_info.RawBallanceMapInfo = PROP_ballance_map_info.get_raw_ballance_map_info(bpy.context.scene) + layout.label(text = f'Map Sectors: {map_info.mSectorCount}') + _TObj3dPair = tuple[bpy.types.Object, bmap.BM3dObject] _TMeshPair = tuple[bpy.types.Object, bpy.types.Mesh, bmap.BMMesh] _TMaterialPair = tuple[bpy.types.Material, bmap.BMMaterial] @@ -170,8 +175,22 @@ def _export_virtools_groups( # start saving progress.enter_substeps(len(obj3d_crets), "Saving Groups") - # create group exporting helper - group_cret_guard: VirtoolsGroupCreationGuard = VirtoolsGroupCreationGuard(writer) + # create sector group first + # This step is designed for ensure that the created sector group is successive. + # + # Due to the design of Ballance, Ballance rely on checking the existance of Sector_XX to get how many sectors this map have. + # Thus if there are no component in a sector, it still need to create a empty Sector_XX group, otherwise the game will crash + # or be ended at a accident sector. + # + # So we create all needed sector group in here to make sure exported virtools file can be read by Ballancde correctly. + map_info: PROP_ballance_map_info.RawBallanceMapInfo = PROP_ballance_map_info.get_raw_ballance_map_info(bpy.context.scene) + for i in range(map_info.mSectorCount): + gp_name: str = UTIL_naming_convension.build_name_from_sector_index(i + 1) + vtgroup: bmap.BMGroup | None = group_cret_map.get(gp_name, None) + if vtgroup is None: + vtgroup = writer.create_group() + vtgroup.set_name(gp_name) + group_cret_map[gp_name] = vtgroup for obj3d, vtobj3d in obj3d_crets: # open group visitor @@ -180,8 +199,8 @@ def _export_virtools_groups( # get group or create new group from guard vtgroup: bmap.BMGroup | None = group_cret_map.get(gp_name, None) if vtgroup is None: - # note: no need to set name, guard has set it. - vtgroup = group_cret_guard.create_group(gp_name) + vtgroup = writer.create_group() + vtgroup.set_name(gp_name) group_cret_map[gp_name] = vtgroup # group this object @@ -471,73 +490,6 @@ def _save_virtools_document( progress.step() progress.leave_substeps() -class VirtoolsGroupCreationGuard(): - """ - This class is designed for ensure that the created sector group is successive. - - Due to the design of Ballance, Ballance rely on checking the existance of Sector_XX to get how many sectors this map have. - Thus if there are no component in a sector, it still need to create a empty Sector_XX group, otherwise the game will crash - or be ended at a accident sector. - - This class hook the operation of Virtools group creation and check all Sector group creation. - Create essential group to make Sector_XX group successive. - Thus all group creation in this module should be passed by this class. - """ - cRegexGroupSector: typing.ClassVar[re.Pattern] = re.compile('^Sector_(0[1-8]|[1-9][0-9]{1,2}|9)$') - - @staticmethod - def __get_group_index(group_name: str) -> int | None: - """ - Return the sector index of group name if it is. Otherwise None. - """ - regex_result = VirtoolsGroupCreationGuard.cRegexGroupSector.match(group_name) - if regex_result is not None: - return int(regex_result.group(1)) - else: - return None - - @staticmethod - def __get_group_name(group_index: int) -> str: - """ - Output Sector group name by given sector index - """ - if group_index == 9: - return 'Sector_9' - else: - return f'Sector_{group_index:0>2d}' - - __mWriter: bmap.BMFileWriter - __mSectors: list[bmap.BMGroup] - - def __init__(self, assoc_writer: bmap.BMFileWriter): - self.__mWriter = assoc_writer - self.__mSectors = [] - - def create_group(self, group_name: str) -> bmap.BMGroup: - """ - The hooked group creation function. - - Please note the passed group name argument is just for name checking. - This function will set group name for you, not like BMFileWriter.create_group() operated. - """ - # check whether it is sector group - # note: the return sector index is 1 based, not 0 - sector_idx: int | None = VirtoolsGroupCreationGuard.__get_group_index(group_name) - # if it is regular group, return normal creation - if sector_idx is None: - gp: bmap.BMGroup = self.__mWriter.create_group() - gp.set_name(group_name) - return gp - - # get from sector cahce list - # enlarge sector cache list if it is not fulfilled given sector index - while sector_idx > len(self.__mSectors): - gp: bmap.BMGroup = self.__mWriter.create_group() - self.__mSectors.append(gp) - gp.set_name(self.__get_group_name(len(self.__mSectors))) - # return ordered sector from sector caches - return self.__mSectors[sector_idx - 1] - def register() -> None: bpy.utils.register_class(BBP_OT_export_virtools) diff --git a/bbp_ng/OP_IMPORT_virtools.py b/bbp_ng/OP_IMPORT_virtools.py index d1fbb6f..670bd9f 100644 --- a/bbp_ng/OP_IMPORT_virtools.py +++ b/bbp_ng/OP_IMPORT_virtools.py @@ -2,8 +2,8 @@ import bpy from bpy_extras.wm_utils.progress_report import ProgressReport import tempfile, os, typing 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 PROP_virtools_group, PROP_virtools_material, PROP_virtools_mesh, PROP_virtools_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_ballance_map_info from .PyBMap import bmap_wrapper as bmap class BBP_OT_import_virtools(bpy.types.Operator, UTIL_file_browser.ImportVirtoolsFile, UTIL_ioport_shared.ImportParams, UTIL_ioport_shared.VirtoolsParams): @@ -344,10 +344,12 @@ def _import_virtools_groups( reader: bmap.BMFileReader, progress: ProgressReport, obj3d_cret_map: dict[bmap.BM3dObject, bpy.types.Object] - ) -> dict[bmap.BM3dObject, bpy.types.Object]: + ) -> None: # we need iterate all groups to construct a reversed map # to indicate which groups should this 3dobject be grouped into. reverse_map: dict[bmap.BM3dObject, set[str]] = {} + # sector counter to record the maximum sector we have processed. + sector_count: int = 1 # prepare progress progress.enter_substeps(reader.get_material_count(), "Loading Groups") @@ -357,6 +359,12 @@ def _import_virtools_groups( group_name: str | None = vtgroup.get_name() if group_name is None: continue + # try extracting sector info + potential_sector_count: int | None = UTIL_naming_convension.extract_sector_from_name(group_name) + if potential_sector_count is not None: + sector_count = max(sector_count, potential_sector_count) + + # creating map for item in vtgroup.get_objects(): # get or create set objgroups: set[str] = reverse_map.get(item, None) @@ -370,6 +378,11 @@ def _import_virtools_groups( # step progress.step() + # assign to ballance map info according to gotten sector count + map_info: PROP_ballance_map_info.RawBallanceMapInfo = PROP_ballance_map_info.get_raw_ballance_map_info(bpy.context.scene) + map_info.mSectorCount = max(map_info.mSectorCount, sector_count) + PROP_ballance_map_info.set_raw_ballance_map_info(bpy.context.scene, map_info) + # leave progress progress.leave_substeps() diff --git a/bbp_ng/PROP_ballance_map_info.py b/bbp_ng/PROP_ballance_map_info.py new file mode 100644 index 0000000..9deec6a --- /dev/null +++ b/bbp_ng/PROP_ballance_map_info.py @@ -0,0 +1,81 @@ +import bpy +import typing +from . import UTIL_functions + +class RawBallanceMapInfo(): + cSectorCount: typing.ClassVar[int] = 1 + + mSectorCount: int + + def __init__(self, **kwargs): + self.mSectorCount = kwargs.get("mSectorCount", RawBallanceMapInfo.cSectorCount) + + def regulate(self): + self.mSectorCount = UTIL_functions.clamp_int(self.mSectorCount, 1, 999) + +#region Prop Decl & Getter Setter + +class BBP_PG_ballance_map_info(bpy.types.PropertyGroup): + sector_count: bpy.props.IntProperty( + name = "Sector", + description = "The sector count of this Ballance map which is used in exporting map and may be changed when importing map.", + default = 1, + max = 999, min = 1, + soft_max = 8, soft_min = 1, + step = 1 + ) # type: ignore + +def get_ballance_map_info(scene: bpy.types.Scene) -> BBP_PG_ballance_map_info: + return scene.ballance_map_info + +def get_raw_ballance_map_info(scene: bpy.types.Scene) -> RawBallanceMapInfo: + props: BBP_PG_ballance_map_info = get_ballance_map_info(scene) + rawdata: RawBallanceMapInfo = RawBallanceMapInfo() + + rawdata.mSectorCount = props.sector_count + + rawdata.regulate() + return rawdata + +def set_raw_ballance_map_info(scene: bpy.types.Scene, rawdata: RawBallanceMapInfo) -> None: + props: BBP_PG_ballance_map_info = get_ballance_map_info(scene) + + props.sector_count = rawdata.mSectorCount + +#endregion + +class BBP_PT_ballance_map_info(bpy.types.Panel): + """Show Ballance Map Infos.""" + bl_label = "Ballance Map" + bl_idname = "BBP_PT_ballance_map_info" + bl_space_type = 'PROPERTIES' + bl_region_type = 'WINDOW' + bl_context = "scene" + + @classmethod + def poll(cls, context): + return context.scene is not None + + def draw(self, context): + layout: bpy.types.UILayout = self.layout + target: bpy.types.Scene = context.scene + props: BBP_PG_ballance_map_info = get_ballance_map_info(target) + + # show map sector count numberbox + layout.prop(props, 'sector_count') + +def register() -> None: + # register + bpy.utils.register_class(BBP_PG_ballance_map_info) + bpy.utils.register_class(BBP_PT_ballance_map_info) + + # add into scene metadata + bpy.types.Scene.ballance_map_info = bpy.props.PointerProperty(type = BBP_PG_ballance_map_info) + +def unregister() -> None: + # del from scene metadata + del bpy.types.Scene.ballance_map_info + + # unregister + bpy.utils.unregister_class(BBP_PG_ballance_map_info) + bpy.utils.unregister_class(BBP_PT_ballance_map_info) diff --git a/bbp_ng/PROP_virtools_group.py b/bbp_ng/PROP_virtools_group.py index bdc7823..c183d62 100644 --- a/bbp_ng/PROP_virtools_group.py +++ b/bbp_ng/PROP_virtools_group.py @@ -8,7 +8,7 @@ class BBP_PG_virtools_group(bpy.types.PropertyGroup): group_name: bpy.props.StringProperty( name = "Group Name", default = "" - ) + ) # type: ignore def get_virtools_groups(obj: bpy.types.Object) -> bpy.types.CollectionProperty: return obj.virtools_groups diff --git a/bbp_ng/UTIL_naming_convension.py b/bbp_ng/UTIL_naming_convension.py index dea9b5d..ed7e674 100644 --- a/bbp_ng/UTIL_naming_convension.py +++ b/bbp_ng/UTIL_naming_convension.py @@ -184,6 +184,42 @@ class BallanceObjectInfo(): #endregion +#region Sector Extractor + +_g_RegexBlcSectorGroup: re.Pattern = re.compile('^Sector_(0[1-8]|[1-9][0-9]{1,2}|9)$') + +def extract_sector_from_name(group_name: str) -> int | None: + """ + A convenient function to extract sector index from given group name. + This function also supports 999 sector plugin. + + Not only in this module, but also in outside modules, this function is vary used to extract sector index info. + + Function return the index extracted, or None if given group name is not a valid sector group. + The valid sector index is range from 1 to 999 (inclusive) + """ + regex_result = _g_RegexBlcSectorGroup.match(group_name) + if regex_result is not None: + return int(regex_result.group(1)) + else: + return None + +def build_name_from_sector_index(sector_index: int) -> str: + """ + A convenient function to build Ballance recognizable sector group name. + This function also supports 999 sector plugin. + + This function also is used in this module or other modules outside. + + Function return a sector name string. It basically the reverse operation of `extract_sector_from_name`. + """ + if sector_index == 9: + return 'Sector_9' + else: + return f'Sector_{sector_index:0>2d}' + +#endregion + #region Naming Convention Declaration _g_BlcNormalComponents: set[str] = set(( @@ -227,7 +263,6 @@ _g_BlcWood: set[str] = set(( )) class VirtoolsGroupConvention(): - cRegexGroupSector: typing.ClassVar[re.Pattern] = re.compile('^Sector_(0[1-8]|[1-9][0-9]{1,2}|9)$') cRegexComponent: typing.ClassVar[re.Pattern] = re.compile('^(' + '|'.join(_g_BlcNormalComponents) + ')_(0[1-9]|[1-9][0-9])_.*$') cRegexPC: typing.ClassVar[re.Pattern] = re.compile('^PC_TwoFlames_(0[1-7])$') cRegexPR: typing.ClassVar[re.Pattern] = re.compile('^PR_Resetpoint_(0[1-8])$') @@ -255,9 +290,9 @@ class VirtoolsGroupConvention(): counter: int = 0 last_matched_sector: int = 0 for i in gps: - regex_result = VirtoolsGroupConvention.cRegexGroupSector.match(i) + regex_result: int | None = extract_sector_from_name(i) if regex_result is not None: - last_matched_sector = int(regex_result.group(1)) + last_matched_sector = regex_result counter += 1 if counter != 1: return None @@ -377,12 +412,8 @@ class VirtoolsGroupConvention(): # group into component type # use typing.cast() to force linter accept it because None is impossible gp.add_group(typing.cast(str, info.mComponentType)) - # group to sector - if info.mSector == 9: - gp.add_group('Sector_9') - else: - gp.add_group(f'Sector_{info.mSector:0>2d}') + gp.add_group(build_name_from_sector_index(typing.cast(int, info.mSector))) case _: if reporter is not None: diff --git a/bbp_ng/__init__.py b/bbp_ng/__init__.py index e6f5c0e..8da9d63 100644 --- a/bbp_ng/__init__.py +++ b/bbp_ng/__init__.py @@ -29,7 +29,8 @@ from . import UTIL_icons_manager UTIL_icons_manager.register() # then load other modules -from . import PROP_preferences, PROP_ptrprop_resolver, PROP_virtools_material, PROP_virtools_texture, PROP_virtools_mesh, PROP_virtools_group, PROP_ballance_element, PROP_bme_material +from . import PROP_preferences, PROP_ptrprop_resolver, PROP_virtools_material, PROP_virtools_texture, PROP_virtools_mesh, PROP_virtools_group +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_UV_flatten_uv, OP_UV_rail_uv from . import OP_MTL_fix_material @@ -217,6 +218,7 @@ def register() -> None: PROP_virtools_group.register() PROP_ballance_element.register() PROP_bme_material.register() + PROP_ballance_map_info.register() OP_IMPORT_bmfile.register() OP_EXPORT_bmfile.register() @@ -275,6 +277,7 @@ def unregister() -> None: OP_EXPORT_bmfile.unregister() OP_IMPORT_bmfile.unregister() + PROP_ballance_map_info.unregister() PROP_bme_material.unregister() PROP_ballance_element.unregister() PROP_virtools_group.unregister()