add conflict resolver
This commit is contained in:
		| @ -21,7 +21,8 @@ class BBP_OT_import_virtools(bpy.types.Operator, UTIL_file_browser.ImportVirtool | ||||
|     def execute(self, context): | ||||
|         _import_virtools( | ||||
|             self.general_get_filename(), | ||||
|             self.general_get_vt_encodings() | ||||
|             self.general_get_vt_encodings(), | ||||
|             self.general_get_conflict_resolver() | ||||
|         ) | ||||
|         self.report({'INFO'}, "Virtools File Importing Finished.") | ||||
|         return {'FINISHED'} | ||||
| @ -34,7 +35,7 @@ class BBP_OT_import_virtools(bpy.types.Operator, UTIL_file_browser.ImportVirtool | ||||
|         layout.label(text = 'Virtools Params') | ||||
|         self.draw_virtools_params(layout.box()) | ||||
|  | ||||
| def _import_virtools(file_name_: str, encodings_: tuple[str]) -> None: | ||||
| def _import_virtools(file_name_: str, encodings_: tuple[str], resolver: UTIL_ioport_shared.ConflictResolver) -> None: | ||||
|     # create temp folder | ||||
|     with tempfile.TemporaryDirectory() as vt_temp_folder: | ||||
|         print(f'Virtools Engine Temp: {vt_temp_folder}') | ||||
| @ -50,22 +51,23 @@ def _import_virtools(file_name_: str, encodings_: tuple[str]) -> None: | ||||
|             with ProgressReport(wm = bpy.context.window_manager) as progress: | ||||
|                 # import textures | ||||
|                 texture_cret_map: dict[bmap.BMTexture, bpy.types.Image] = _import_virtools_textures( | ||||
|                     reader, progress) | ||||
|                     reader, progress, resolver) | ||||
|                 # import materials | ||||
|                 material_cret_map: dict[bmap.BMMaterial, bpy.types.Material] = _import_virtools_materials( | ||||
|                     reader, progress, texture_cret_map) | ||||
|                     reader, progress, resolver, texture_cret_map) | ||||
|                 # import meshes | ||||
|                 mesh_cret_map: dict[bmap.BMMesh, bpy.types.Mesh] = _import_virtools_meshes( | ||||
|                     reader, progress, material_cret_map) | ||||
|                     reader, progress, resolver, material_cret_map) | ||||
|                 # import 3dobjects | ||||
|                 obj3d_cret_map: dict[bmap.BM3dObject, bpy.types.Object] = _import_virtools_3dobjects( | ||||
|                     reader, progress, mesh_cret_map) | ||||
|                     reader, progress, resolver, mesh_cret_map) | ||||
|                 # import groups | ||||
|                 _import_virtools_groups(reader, progress, obj3d_cret_map) | ||||
|  | ||||
| def _import_virtools_textures( | ||||
|         reader: bmap.BMFileReader,  | ||||
|         progress:ProgressReport | ||||
|         progress: ProgressReport, | ||||
|         resolver: UTIL_ioport_shared.ConflictResolver | ||||
|         ) -> dict[bmap.BMTexture, bpy.types.Image]: | ||||
|     # create map | ||||
|     texture_cret_map: dict[bmap.BMTexture, bpy.types.Image] = {} | ||||
| @ -76,13 +78,13 @@ def _import_virtools_textures( | ||||
|         print(f'Texture Raw Data Temp: {rawdata_temp}') | ||||
|  | ||||
|         for vttexture in reader.get_textures(): | ||||
|             tex: bpy.types.Image | ||||
|             tex_cret: typing.Callable[[], bpy.types.Image] | ||||
|             texpath_to_load: str | None = vttexture.get_file_name() | ||||
|  | ||||
|             # if no assoc file path (what? but it is real happended) | ||||
|             # this is invalid image, create a blank image instead | ||||
|             if texpath_to_load is None: | ||||
|                 tex = bpy.data.images.new("", 1, 1) | ||||
|                 tex_cret = lambda: bpy.data.images.new("", 1, 1) | ||||
|             else: | ||||
|                 # if this image is raw data, save it in external folder before loading | ||||
|                 # the attribute of raw data saving is the file path is not absolute path | ||||
| @ -96,21 +98,28 @@ def _import_virtools_textures( | ||||
|                 # detect whether it is ballance texture and load | ||||
|                 try_blc_tex: str | None = UTIL_ballance_texture.get_ballance_texture_filename(texpath_to_load) | ||||
|                  | ||||
|                 if try_blc_tex: | ||||
|                 if try_blc_tex is not None: | ||||
|                     # load as ballance texture | ||||
|                     tex = UTIL_ballance_texture.load_ballance_texture(try_blc_tex) | ||||
|                     tex_cret = lambda: UTIL_ballance_texture.load_ballance_texture(typing.cast(str, try_blc_tex)) | ||||
|                 else: | ||||
|                     # load as other textures | ||||
|                     tex = UTIL_ballance_texture.load_other_texture(texpath_to_load) | ||||
|                     tex_cret = lambda: UTIL_ballance_texture.load_other_texture(typing.cast(str, texpath_to_load)) | ||||
|  | ||||
|             # create real texture by tex cret fct | ||||
|             (tex, init_tex) = resolver.create_texture( | ||||
|                 UTIL_virtools_types.virtools_name_regulator(vttexture.get_name()),  | ||||
|                 tex_cret | ||||
|             ) | ||||
|              | ||||
|             # init tex if needed | ||||
|             if init_tex: | ||||
|                 # set texture cfg | ||||
|                 rawtex: PROP_virtools_texture.RawVirtoolsTexture = PROP_virtools_texture.RawVirtoolsTexture() | ||||
|                 rawtex.mSaveOptions = vttexture.get_save_options() | ||||
|                 rawtex.mVideoFormat = vttexture.get_video_format() | ||||
|                 PROP_virtools_texture.set_raw_virtools_texture(tex, rawtex) | ||||
|  | ||||
|             # rename and insert it to map | ||||
|             tex.name = UTIL_virtools_types.virtools_name_regulator(vttexture.get_name()) | ||||
|             # insert it to map | ||||
|             texture_cret_map[vttexture] = tex | ||||
|  | ||||
|             # inc steps | ||||
| @ -123,6 +132,7 @@ def _import_virtools_textures( | ||||
| def _import_virtools_materials( | ||||
|         reader: bmap.BMFileReader,  | ||||
|         progress: ProgressReport, | ||||
|         resolver: UTIL_ioport_shared.ConflictResolver, | ||||
|         texture_cret_map: dict[bmap.BMTexture, bpy.types.Image] | ||||
|         ) -> dict[bmap.BMMaterial, bpy.types.Material]: | ||||
|     # create map and prepare progress | ||||
| @ -130,6 +140,13 @@ def _import_virtools_materials( | ||||
|     progress.enter_substeps(reader.get_material_count(), "Loading Materials") | ||||
|  | ||||
|     for vtmaterial in reader.get_materials(): | ||||
|         # create mtl | ||||
|         (mtl, init_mtl) = resolver.create_material( | ||||
|             UTIL_virtools_types.virtools_name_regulator(vtmaterial.get_name()) | ||||
|         ) | ||||
|  | ||||
|         # apply it if necessary | ||||
|         if init_mtl: | ||||
|             # create new raw material | ||||
|             rawmtl: PROP_virtools_material.RawVirtoolsMaterial = PROP_virtools_material.RawVirtoolsMaterial() | ||||
|              | ||||
| @ -166,10 +183,6 @@ def _import_virtools_materials( | ||||
|             rawmtl.mAlphaFunc = vtmaterial.get_alpha_func() | ||||
|             rawmtl.mZFunc = vtmaterial.get_z_func() | ||||
|  | ||||
|         # create mtl and apply it | ||||
|         mtl: bpy.types.Material = bpy.data.materials.new( | ||||
|             UTIL_virtools_types.virtools_name_regulator(vtmaterial.get_name()) | ||||
|         ) | ||||
|             PROP_virtools_material.set_raw_virtools_material(mtl, rawmtl) | ||||
|             PROP_virtools_material.apply_to_blender_material(mtl) | ||||
|  | ||||
| @ -184,6 +197,7 @@ def _import_virtools_materials( | ||||
| def _import_virtools_meshes( | ||||
|         reader: bmap.BMFileReader,  | ||||
|         progress: ProgressReport, | ||||
|         resolver: UTIL_ioport_shared.ConflictResolver,  | ||||
|         material_cret_map: dict[bmap.BMMaterial, bpy.types.Material] | ||||
|         ) -> dict[bmap.BMMesh, bpy.types.Mesh]: | ||||
|     # create map and prepare progress | ||||
| @ -192,10 +206,12 @@ def _import_virtools_meshes( | ||||
|  | ||||
|     for vtmesh in reader.get_meshs(): | ||||
|         # create mesh | ||||
|         mesh: bpy.types.Mesh = bpy.data.meshes.new( | ||||
|         (mesh, init_mesh) = resolver.create_mesh( | ||||
|             UTIL_virtools_types.virtools_name_regulator(vtmesh.get_name()) | ||||
|         ) | ||||
|  | ||||
|         # set mesh data if necessary | ||||
|         if init_mesh: | ||||
|             # open mesh writer | ||||
|             with UTIL_blender_mesh.MeshWriter(mesh) as meshoper: | ||||
|                 # construct data provider | ||||
| @ -277,6 +293,7 @@ def _import_virtools_meshes( | ||||
| def _import_virtools_3dobjects( | ||||
|         reader: bmap.BMFileReader,  | ||||
|         progress: ProgressReport, | ||||
|         resolver: UTIL_ioport_shared.ConflictResolver,  | ||||
|         mesh_cret_map: dict[bmap.BMMesh, bpy.types.Mesh] | ||||
|         ) -> dict[bmap.BM3dObject, bpy.types.Object]: | ||||
|     # create map and prepare progress | ||||
| @ -288,12 +305,17 @@ def _import_virtools_3dobjects( | ||||
|     blender_collection = blender_view_layer.active_layer_collection.collection | ||||
|  | ||||
|     for vt3dobj in reader.get_3dobjects(): | ||||
|         # get virtools binding mesh data first | ||||
|         vt3dobj_data: bmap.BMMesh | None = vt3dobj.get_current_mesh() | ||||
|  | ||||
|         # create 3d object with mesh | ||||
|         obj3d: bpy.types.Object = bpy.data.objects.new( | ||||
|         (obj3d, init_obj3d) = resolver.create_object( | ||||
|             UTIL_virtools_types.virtools_name_regulator(vt3dobj.get_name()), | ||||
|             mesh_cret_map.get(vt3dobj.get_current_mesh(), None) | ||||
|             None if vt3dobj_data is None else mesh_cret_map.get(vt3dobj_data, None) | ||||
|         ) | ||||
|  | ||||
|         # setup if necessary | ||||
|         if init_obj3d: | ||||
|             # link to collection | ||||
|             blender_collection.objects.link(obj3d) | ||||
|  | ||||
| @ -305,8 +327,13 @@ def _import_virtools_3dobjects( | ||||
|             # set visibility | ||||
|             obj3d.hide_set(not vt3dobj.get_visibility()) | ||||
|  | ||||
|         # add into map and step | ||||
|             # add into map | ||||
|             # NOTE: the return value only provided to group setter | ||||
|             # and group setter should only set group data to new created 3d objects | ||||
|             # thus we only insert pair when this 3d obj is new created. | ||||
|             obj3d_cret_map[vt3dobj] = obj3d | ||||
|  | ||||
|         # step forward | ||||
|         progress.step() | ||||
|  | ||||
|     # leave progress and return | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| import bpy | ||||
| import enum | ||||
| import enum, typing | ||||
| from . import UTIL_virtools_types, UTIL_functions | ||||
| from . import PROP_ptrprop_resolver | ||||
|  | ||||
| @ -12,11 +12,9 @@ from . import PROP_ptrprop_resolver | ||||
| class ConflictStrategy(enum.IntEnum): | ||||
|     Rename = enum.auto() | ||||
|     Current = enum.auto() | ||||
|     Replace = enum.auto() | ||||
| _g_ConflictStrategyDesc: dict[ConflictStrategy, tuple[str, str]] = { | ||||
|     ConflictStrategy.Rename: ('Rename', 'Rename the new one'), | ||||
|     ConflictStrategy.Current: ('Use Current', 'Use current one'), | ||||
|     ConflictStrategy.Replace: ('Replace', 'Replace the old one with new one'), | ||||
| } | ||||
| _g_EnumHelper_ConflictStrategy: UTIL_functions.EnumPropHelper = UTIL_functions.EnumPropHelper( | ||||
|     ConflictStrategy, | ||||
| @ -27,6 +25,103 @@ _g_EnumHelper_ConflictStrategy: UTIL_functions.EnumPropHelper = UTIL_functions.E | ||||
|     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(): | ||||
|     """ | ||||
|     This class frequently used when importing objects. | ||||
|     This class accept 4 conflict strategies for object, mesh, material and texture, | ||||
|     and provide 4 general creation functions to handle these strategies. | ||||
|     Each general creation functions will return an instance and a bool indicating whether this instance need be initialized. | ||||
|     """ | ||||
|      | ||||
|     __mObjectStrategy: ConflictStrategy | ||||
|     __mMeshStrategy: ConflictStrategy | ||||
|     __mMaterialStrategy: ConflictStrategy | ||||
|     __mTextureStrategy: ConflictStrategy | ||||
|  | ||||
|     def __init__(self, obj_strategy: ConflictStrategy, mesh_strategy: ConflictStrategy, mtl_strategy: ConflictStrategy, tex_strategy: ConflictStrategy): | ||||
|         self.__mObjectStrategy = obj_strategy | ||||
|         self.__mMeshStrategy = mesh_strategy | ||||
|         self.__mMaterialStrategy = mtl_strategy | ||||
|         self.__mTextureStrategy = tex_strategy | ||||
|  | ||||
|     def create_object(self, name: str, data: bpy.types.Mesh) -> tuple[bpy.types.Object, bool]: | ||||
|         """ | ||||
|         Create object according to conflict strategy. | ||||
|         `data` will only be applied when creating new object (no existing instance or strategy order rename) | ||||
|         """ | ||||
|         if self.__mObjectStrategy == ConflictStrategy.Current: | ||||
|             old: bpy.types.Object | None = bpy.data.objects.get(name, None) | ||||
|             if old is not None: | ||||
|                 return (old, False) | ||||
|         return (bpy.data.objects.new(name, data), True) | ||||
|      | ||||
|     def create_mesh(self, name: str) -> tuple[bpy.types.Mesh, bool]: | ||||
|         if self.__mMeshStrategy == ConflictStrategy.Current: | ||||
|             old: bpy.types.Mesh | None = bpy.data.meshes.get(name, None) | ||||
|             if old is not None: | ||||
|                 return (old, False) | ||||
|         return (bpy.data.meshes.new(name), True) | ||||
|      | ||||
|     def create_material(self, name: str) -> tuple[bpy.types.Material, bool]: | ||||
|         if self.__mMaterialStrategy == ConflictStrategy.Current: | ||||
|             old: bpy.types.Material | None = bpy.data.materials.get(name, None) | ||||
|             if old is not None: | ||||
|                 return (old, False) | ||||
|         return (bpy.data.materials.new(name), True) | ||||
|      | ||||
|     def create_texture(self, name: str, fct_cret: typing.Callable[[], bpy.types.Image]) -> tuple[bpy.types.Image, bool]: | ||||
|         """ | ||||
|         Create texture according to conflict strategy. | ||||
|         If the strategy order current, it will return current existing instance. | ||||
|         If no existing instance or strategy order rename, it will call `fct_cret` to create new texture. | ||||
|  | ||||
|         Because texture do not have a general creation function, we frequently create it by other modules provided texture functions. | ||||
|         So `fct_cret` is the real creation function. And it will not be executed if no creation happended. | ||||
|         """ | ||||
|         if self.__mTextureStrategy == ConflictStrategy.Current: | ||||
|             old: bpy.types.Image | None = bpy.data.images.get(name, None) | ||||
|             if old is not None: | ||||
|                 return (old, False) | ||||
|         # create texture, set name, and return | ||||
|         tex: bpy.types.Image = fct_cret() | ||||
|         tex.name = name | ||||
|         return (tex, True) | ||||
|  | ||||
| #endregion | ||||
|  | ||||
| class ImportParams(): | ||||
|     texture_conflict_strategy: bpy.props.EnumProperty( | ||||
|         name = "Texture Name Conflict", | ||||
| @ -78,6 +173,14 @@ class ImportParams(): | ||||
|     def general_get_object_conflict_strategy(self) -> ConflictStrategy: | ||||
|         return _g_EnumHelper_ConflictStrategy.get_selection(self.object_conflict_strategy) | ||||
|  | ||||
|     def general_get_conflict_resolver(self) -> ConflictResolver: | ||||
|         return ConflictResolver( | ||||
|             self.general_get_object_conflict_strategy(), | ||||
|             self.general_get_mesh_conflict_strategy(), | ||||
|             self.general_get_material_conflict_strategy(), | ||||
|             self.general_get_texture_conflict_strategy() | ||||
|         ) | ||||
|  | ||||
| class ExportParams(): | ||||
|     export_mode: bpy.props.EnumProperty( | ||||
|         name = "Export Mode", | ||||
| @ -128,60 +231,3 @@ class VirtoolsParams(): | ||||
|         # get encoding, split it by `;` and strip blank chars. | ||||
|         encodings: str = self.vt_encodings | ||||
|         return tuple(map(lambda x: x.strip(), encodings.split(';'))) | ||||
|  | ||||
| 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(): | ||||
|     """ | ||||
|     This class frequently used when importing objects. | ||||
|     This class accept 4 conflict strategies for object, mesh, material and texture, | ||||
|     and provide 4 general creation functions to handle these strategies. | ||||
|     Each general creation functions will return an instance and a bool indicating whether this instance need be initialized. | ||||
|  | ||||
|     This class also provide 3 static common creation functions without considering conflict. | ||||
|     They just a redirect calling to `bpy.data.xxx.new()`. | ||||
|     No static texture (Image) creation function because texture is not created from `bpy.data.images`. | ||||
|     """ | ||||
|      | ||||
|     @staticmethod | ||||
|     def create_object(name: str, data: bpy.types.Mesh) -> bpy.types.Object: | ||||
|         return bpy.data.objects.new(name, data) | ||||
|      | ||||
|     @staticmethod | ||||
|     def create_mesh(name: str) -> bpy.types.Mesh: | ||||
|         return bpy.data.meshes.new(name) | ||||
|      | ||||
|     @staticmethod | ||||
|     def create_material(name: str) -> bpy.types.Material: | ||||
|         return bpy.data.materials.new(name) | ||||
|      | ||||
|      | ||||
|  | ||||
		Reference in New Issue
	
	Block a user