add conflict resolver

This commit is contained in:
yyc12345 2024-01-04 10:35:52 +08:00
parent 680f367a42
commit 2c006b4528
2 changed files with 269 additions and 196 deletions

View File

@ -21,7 +21,8 @@ class BBP_OT_import_virtools(bpy.types.Operator, UTIL_file_browser.ImportVirtool
def execute(self, context): def execute(self, context):
_import_virtools( _import_virtools(
self.general_get_filename(), 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.") self.report({'INFO'}, "Virtools File Importing Finished.")
return {'FINISHED'} return {'FINISHED'}
@ -34,7 +35,7 @@ class BBP_OT_import_virtools(bpy.types.Operator, UTIL_file_browser.ImportVirtool
layout.label(text = 'Virtools Params') layout.label(text = 'Virtools Params')
self.draw_virtools_params(layout.box()) 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 # create temp folder
with tempfile.TemporaryDirectory() as vt_temp_folder: with tempfile.TemporaryDirectory() as vt_temp_folder:
print(f'Virtools Engine Temp: {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: with ProgressReport(wm = bpy.context.window_manager) as progress:
# import textures # import textures
texture_cret_map: dict[bmap.BMTexture, bpy.types.Image] = _import_virtools_textures( texture_cret_map: dict[bmap.BMTexture, bpy.types.Image] = _import_virtools_textures(
reader, progress) reader, progress, resolver)
# import materials # import materials
material_cret_map: dict[bmap.BMMaterial, bpy.types.Material] = _import_virtools_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 # import meshes
mesh_cret_map: dict[bmap.BMMesh, bpy.types.Mesh] = _import_virtools_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 # import 3dobjects
obj3d_cret_map: dict[bmap.BM3dObject, bpy.types.Object] = _import_virtools_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 groups
_import_virtools_groups(reader, progress, obj3d_cret_map) _import_virtools_groups(reader, progress, obj3d_cret_map)
def _import_virtools_textures( def _import_virtools_textures(
reader: bmap.BMFileReader, reader: bmap.BMFileReader,
progress:ProgressReport progress: ProgressReport,
resolver: UTIL_ioport_shared.ConflictResolver
) -> dict[bmap.BMTexture, bpy.types.Image]: ) -> dict[bmap.BMTexture, bpy.types.Image]:
# create map # create map
texture_cret_map: dict[bmap.BMTexture, bpy.types.Image] = {} 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}') print(f'Texture Raw Data Temp: {rawdata_temp}')
for vttexture in reader.get_textures(): 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() texpath_to_load: str | None = vttexture.get_file_name()
# if no assoc file path (what? but it is real happended) # if no assoc file path (what? but it is real happended)
# this is invalid image, create a blank image instead # this is invalid image, create a blank image instead
if texpath_to_load is None: if texpath_to_load is None:
tex = bpy.data.images.new("", 1, 1) tex_cret = lambda: bpy.data.images.new("", 1, 1)
else: else:
# if this image is raw data, save it in external folder before loading # 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 # 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 # detect whether it is ballance texture and load
try_blc_tex: str | None = UTIL_ballance_texture.get_ballance_texture_filename(texpath_to_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 # 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: else:
# load as other textures # 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 # set texture cfg
rawtex: PROP_virtools_texture.RawVirtoolsTexture = PROP_virtools_texture.RawVirtoolsTexture() rawtex: PROP_virtools_texture.RawVirtoolsTexture = PROP_virtools_texture.RawVirtoolsTexture()
rawtex.mSaveOptions = vttexture.get_save_options() rawtex.mSaveOptions = vttexture.get_save_options()
rawtex.mVideoFormat = vttexture.get_video_format() rawtex.mVideoFormat = vttexture.get_video_format()
PROP_virtools_texture.set_raw_virtools_texture(tex, rawtex) PROP_virtools_texture.set_raw_virtools_texture(tex, rawtex)
# rename and insert it to map # insert it to map
tex.name = UTIL_virtools_types.virtools_name_regulator(vttexture.get_name())
texture_cret_map[vttexture] = tex texture_cret_map[vttexture] = tex
# inc steps # inc steps
@ -123,6 +132,7 @@ def _import_virtools_textures(
def _import_virtools_materials( def _import_virtools_materials(
reader: bmap.BMFileReader, reader: bmap.BMFileReader,
progress: ProgressReport, progress: ProgressReport,
resolver: UTIL_ioport_shared.ConflictResolver,
texture_cret_map: dict[bmap.BMTexture, bpy.types.Image] texture_cret_map: dict[bmap.BMTexture, bpy.types.Image]
) -> dict[bmap.BMMaterial, bpy.types.Material]: ) -> dict[bmap.BMMaterial, bpy.types.Material]:
# create map and prepare progress # create map and prepare progress
@ -130,6 +140,13 @@ def _import_virtools_materials(
progress.enter_substeps(reader.get_material_count(), "Loading Materials") progress.enter_substeps(reader.get_material_count(), "Loading Materials")
for vtmaterial in reader.get_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 # create new raw material
rawmtl: PROP_virtools_material.RawVirtoolsMaterial = PROP_virtools_material.RawVirtoolsMaterial() rawmtl: PROP_virtools_material.RawVirtoolsMaterial = PROP_virtools_material.RawVirtoolsMaterial()
@ -166,10 +183,6 @@ def _import_virtools_materials(
rawmtl.mAlphaFunc = vtmaterial.get_alpha_func() rawmtl.mAlphaFunc = vtmaterial.get_alpha_func()
rawmtl.mZFunc = vtmaterial.get_z_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.set_raw_virtools_material(mtl, rawmtl)
PROP_virtools_material.apply_to_blender_material(mtl) PROP_virtools_material.apply_to_blender_material(mtl)
@ -184,6 +197,7 @@ def _import_virtools_materials(
def _import_virtools_meshes( def _import_virtools_meshes(
reader: bmap.BMFileReader, reader: bmap.BMFileReader,
progress: ProgressReport, progress: ProgressReport,
resolver: UTIL_ioport_shared.ConflictResolver,
material_cret_map: dict[bmap.BMMaterial, bpy.types.Material] material_cret_map: dict[bmap.BMMaterial, bpy.types.Material]
) -> dict[bmap.BMMesh, bpy.types.Mesh]: ) -> dict[bmap.BMMesh, bpy.types.Mesh]:
# create map and prepare progress # create map and prepare progress
@ -192,10 +206,12 @@ def _import_virtools_meshes(
for vtmesh in reader.get_meshs(): for vtmesh in reader.get_meshs():
# create mesh # 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()) UTIL_virtools_types.virtools_name_regulator(vtmesh.get_name())
) )
# set mesh data if necessary
if init_mesh:
# open mesh writer # open mesh writer
with UTIL_blender_mesh.MeshWriter(mesh) as meshoper: with UTIL_blender_mesh.MeshWriter(mesh) as meshoper:
# construct data provider # construct data provider
@ -277,6 +293,7 @@ def _import_virtools_meshes(
def _import_virtools_3dobjects( def _import_virtools_3dobjects(
reader: bmap.BMFileReader, reader: bmap.BMFileReader,
progress: ProgressReport, progress: ProgressReport,
resolver: UTIL_ioport_shared.ConflictResolver,
mesh_cret_map: dict[bmap.BMMesh, bpy.types.Mesh] mesh_cret_map: dict[bmap.BMMesh, bpy.types.Mesh]
) -> dict[bmap.BM3dObject, bpy.types.Object]: ) -> dict[bmap.BM3dObject, bpy.types.Object]:
# create map and prepare progress # create map and prepare progress
@ -288,12 +305,17 @@ def _import_virtools_3dobjects(
blender_collection = blender_view_layer.active_layer_collection.collection blender_collection = blender_view_layer.active_layer_collection.collection
for vt3dobj in reader.get_3dobjects(): 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 # 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()), 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 # link to collection
blender_collection.objects.link(obj3d) blender_collection.objects.link(obj3d)
@ -305,8 +327,13 @@ def _import_virtools_3dobjects(
# set visibility # set visibility
obj3d.hide_set(not vt3dobj.get_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 obj3d_cret_map[vt3dobj] = obj3d
# step forward
progress.step() progress.step()
# leave progress and return # leave progress and return

View File

@ -1,5 +1,5 @@
import bpy import bpy
import enum import enum, typing
from . import UTIL_virtools_types, UTIL_functions from . import UTIL_virtools_types, UTIL_functions
from . import PROP_ptrprop_resolver from . import PROP_ptrprop_resolver
@ -12,11 +12,9 @@ from . import PROP_ptrprop_resolver
class ConflictStrategy(enum.IntEnum): class ConflictStrategy(enum.IntEnum):
Rename = enum.auto() Rename = enum.auto()
Current = enum.auto() Current = enum.auto()
Replace = enum.auto()
_g_ConflictStrategyDesc: dict[ConflictStrategy, tuple[str, str]] = { _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'),
ConflictStrategy.Replace: ('Replace', 'Replace the old one with new one'),
} }
_g_EnumHelper_ConflictStrategy: UTIL_functions.EnumPropHelper = UTIL_functions.EnumPropHelper( _g_EnumHelper_ConflictStrategy: UTIL_functions.EnumPropHelper = UTIL_functions.EnumPropHelper(
ConflictStrategy, ConflictStrategy,
@ -27,6 +25,103 @@ _g_EnumHelper_ConflictStrategy: UTIL_functions.EnumPropHelper = UTIL_functions.E
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():
"""
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(): class ImportParams():
texture_conflict_strategy: bpy.props.EnumProperty( texture_conflict_strategy: bpy.props.EnumProperty(
name = "Texture Name Conflict", name = "Texture Name Conflict",
@ -78,6 +173,14 @@ class ImportParams():
def general_get_object_conflict_strategy(self) -> ConflictStrategy: def general_get_object_conflict_strategy(self) -> ConflictStrategy:
return _g_EnumHelper_ConflictStrategy.get_selection(self.object_conflict_strategy) 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(): class ExportParams():
export_mode: bpy.props.EnumProperty( export_mode: bpy.props.EnumProperty(
name = "Export Mode", name = "Export Mode",
@ -128,60 +231,3 @@ class VirtoolsParams():
# get encoding, split it by `;` and strip blank chars. # get encoding, split it by `;` and strip blank chars.
encodings: str = self.vt_encodings encodings: str = self.vt_encodings
return tuple(map(lambda x: x.strip(), encodings.split(';'))) 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)