Skip to content

Commit

Permalink
feat: add light object support in importing virtools file.
Browse files Browse the repository at this point in the history
- fix all `def poll(self)` to `def poll(cls)` to let it fit class method name convention.
- fix wrong progress counter when importing virtools file.
- add light support when importing virtools file.
- add corresponding conflict strategy and resolver for light.
  • Loading branch information
yyc12345 committed Jan 3, 2025
1 parent 2f08455 commit cb893b7
Show file tree
Hide file tree
Showing 8 changed files with 130 additions and 27 deletions.
2 changes: 1 addition & 1 deletion bbp_ng/OP_EXPORT_bmfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class BBP_OT_export_bmfile(bpy.types.Operator, UTIL_file_browser.ExportBmxFile,
bl_options = {'PRESET'}

@classmethod
def poll(self, context):
def poll(cls, context):
return PROP_preferences.get_raw_preferences().has_valid_blc_tex_folder()

def execute(self, context):
Expand Down
2 changes: 1 addition & 1 deletion bbp_ng/OP_EXPORT_virtools.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class BBP_OT_export_virtools(bpy.types.Operator, UTIL_file_browser.ExportVirtool
bl_options = {'PRESET'}

@classmethod
def poll(self, context):
def poll(cls, context):
return (
PROP_preferences.get_raw_preferences().has_valid_blc_tex_folder()
and bmap.is_bmap_available())
Expand Down
2 changes: 1 addition & 1 deletion bbp_ng/OP_IMPORT_bmfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class BBP_OT_import_bmfile(bpy.types.Operator, UTIL_file_browser.ImportBmxFile,
bl_options = {'PRESET', 'UNDO'}

@classmethod
def poll(self, context):
def poll(cls, context):
return PROP_preferences.get_raw_preferences().has_valid_blc_tex_folder()

def execute(self, context):
Expand Down
83 changes: 70 additions & 13 deletions bbp_ng/OP_IMPORT_virtools.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
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_naming_convension
from . import PROP_virtools_group, PROP_virtools_material, PROP_virtools_mesh, PROP_virtools_texture, 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

class BBP_OT_import_virtools(bpy.types.Operator, UTIL_file_browser.ImportVirtoolsFile, UTIL_ioport_shared.ImportParams, UTIL_ioport_shared.VirtoolsParams, UTIL_ioport_shared.BallanceParams):
Expand All @@ -13,7 +13,7 @@ class BBP_OT_import_virtools(bpy.types.Operator, UTIL_file_browser.ImportVirtool
bl_options = {'PRESET', 'UNDO'}

@classmethod
def poll(self, context):
def poll(cls, context):
return (
PROP_preferences.get_raw_preferences().has_valid_blc_tex_folder()
and bmap.is_bmap_available())
Expand Down Expand Up @@ -59,6 +59,8 @@ def _import_virtools(file_name_: str, encodings_: tuple[str], resolver: UTIL_iop
# import 3dobjects
obj3d_cret_map: dict[bmap.BM3dObject, bpy.types.Object] = _import_virtools_3dobjects(
reader, progress, resolver, mesh_cret_map)
# import light
_import_virtools_lights(reader, progress, resolver)
# import groups
_import_virtools_groups(reader, progress, obj3d_cret_map)

Expand Down Expand Up @@ -200,7 +202,7 @@ def _import_virtools_meshes(
) -> dict[bmap.BMMesh, bpy.types.Mesh]:
# create map and prepare progress
mesh_cret_map: dict[bmap.BMMesh, bpy.types.Mesh] = {}
progress.enter_substeps(reader.get_material_count(), "Loading Meshes")
progress.enter_substeps(reader.get_mesh_count(), "Loading Meshes")

for vtmesh in reader.get_meshs():
# create mesh
Expand Down Expand Up @@ -296,11 +298,7 @@ def _import_virtools_3dobjects(
) -> dict[bmap.BM3dObject, bpy.types.Object]:
# create map and prepare progress
obj3d_cret_map: dict[bmap.BM3dObject, bpy.types.Object] = {}
progress.enter_substeps(reader.get_material_count(), "Loading 3dObjects")

# get some essential blender data
blender_view_layer = bpy.context.view_layer
blender_collection = blender_view_layer.active_layer_collection.collection
progress.enter_substeps(reader.get_3dobject_count(), "Loading 3dObjects")

for vt3dobj in reader.get_3dobjects():
# get virtools binding mesh data first
Expand All @@ -314,8 +312,8 @@ def _import_virtools_3dobjects(

# setup if necessary
if init_obj3d:
# link to collection
blender_collection.objects.link(obj3d)
# add into scene
UTIL_functions.add_into_scene(obj3d)

# set world matrix
vtmat: UTIL_virtools_types.VxMatrix = vt3dobj.get_world_matrix()
Expand All @@ -338,6 +336,61 @@ def _import_virtools_3dobjects(
progress.leave_substeps()
return obj3d_cret_map

def _import_virtools_lights(
reader: bmap.BMFileReader,
progress: ProgressReport,
resolver: UTIL_ioport_shared.ConflictResolver
) -> None:
# prepare progress
progress.enter_substeps(reader.get_target_light_count(), "Loading Lights")

# please note light is slightly different between virtools and blender.
# in virtools, light is the sub class of 3d entity.
# it means that virtools use class inheritance to implement light.
# however, in blender, light is the data property of object.
# comparing with normal mesh object, it just replace the data property of object to a light.
# so in blender, light is implemented as a data struct attached to object.
# thus we can reuse light data for multiple objects but virtools can not.
# in virtools, every light are individual objects.
for vtlight in reader.get_target_lights():
# create light data block and 3d object together
(light_3dobj, light, init_light) = resolver.create_light(
UTIL_virtools_types.virtools_name_regulator(vtlight.get_name())
)

if init_light:
# setup light data block
rawlight: PROP_virtools_light.RawVirtoolsLight = PROP_virtools_light.RawVirtoolsLight()
rawlight.mType = vtlight.get_type()
rawlight.mColor = vtlight.get_color()

rawlight.mConstantAttenuation = vtlight.get_constant_attenuation()
rawlight.mLinearAttenuation = vtlight.get_linear_attenuation()
rawlight.mQuadraticAttenuation = vtlight.get_quadratic_attenuation()

rawlight.mRange = vtlight.get_range()

rawlight.mHotSpot = vtlight.get_hot_spot()
rawlight.mFalloff = vtlight.get_falloff()
rawlight.mFalloffShape = vtlight.get_falloff_shape()

PROP_virtools_light.set_raw_virtools_light(light, rawlight)
PROP_virtools_light.apply_to_blender_light(light)

# setup light associated 3d object
# add into scene
UTIL_functions.add_into_scene(light_3dobj)
# set world matrix
# TODO: fix light direction
vtmat: UTIL_virtools_types.VxMatrix = vtlight.get_world_matrix()
UTIL_virtools_types.vxmatrix_conv_co(vtmat)
light_3dobj.matrix_world = UTIL_virtools_types.vxmatrix_to_blender(vtmat)
# set visibility
light_3dobj.hide_set(not vtlight.get_visibility())

# leave progress
progress.leave_substeps()

def _import_virtools_groups(
reader: bmap.BMFileReader,
progress: ProgressReport,
Expand All @@ -350,7 +403,7 @@ def _import_virtools_groups(
sector_count: int = 1

# prepare progress
progress.enter_substeps(reader.get_material_count(), "Loading Groups")
progress.enter_substeps(reader.get_group_count(), "Loading Groups")

for vtgroup in reader.get_groups():
# if this group do not have name, skip it
Expand All @@ -365,7 +418,7 @@ def _import_virtools_groups(
# creating map
for item in vtgroup.get_objects():
# get or create set
objgroups: set[str] = reverse_map.get(item, None)
objgroups: set[str] | None = reverse_map.get(item, None)
if objgroups is None:
objgroups = set()
reverse_map[item] = objgroups
Expand All @@ -385,7 +438,7 @@ def _import_virtools_groups(
progress.leave_substeps()

# now we can assign 3dobject group data by reverse map
progress.enter_substeps(reader.get_material_count(), "Applying Groups")
progress.enter_substeps(len(reverse_map), "Applying Groups")
for mapk, mapv in reverse_map.items():
# check object
assoc_obj = obj3d_cret_map.get(mapk, None)
Expand All @@ -396,6 +449,10 @@ def _import_virtools_groups(
gpoper.clear_groups()
gpoper.add_groups(mapv)

# step
progress.step()

# leave progress
progress.leave_substeps()


Expand Down
2 changes: 1 addition & 1 deletion bbp_ng/OP_UV_rail_uv.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class BBP_OT_rail_uv(bpy.types.Operator):
bl_options = {'UNDO'}

@classmethod
def poll(self, context):
def poll(cls, context):
return _check_rail_target()

def invoke(self, context, event):
Expand Down
6 changes: 3 additions & 3 deletions bbp_ng/PROP_virtools_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ class BBP_OT_add_virtools_group(bpy.types.Operator, SharedGroupNameInputProperti
bl_options = {'UNDO'}

@classmethod
def poll(self, context: bpy.types.Context):
def poll(cls, context: bpy.types.Context):
return context.object is not None

def invoke(self, context, event):
Expand All @@ -324,7 +324,7 @@ class BBP_OT_rm_virtools_group(bpy.types.Operator):
# Then pass it to helper.

@classmethod
def poll(self, context: bpy.types.Context):
def poll(cls, context: bpy.types.Context):
if context.object is None:
return False

Expand All @@ -351,7 +351,7 @@ class BBP_OT_clear_virtools_groups(bpy.types.Operator):
bl_options = {'UNDO'}

@classmethod
def poll(self, context: bpy.types.Context):
def poll(cls, context: bpy.types.Context):
return context.object is not None

def invoke(self, context, event):
Expand Down
10 changes: 6 additions & 4 deletions bbp_ng/UTIL_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ def draw(self, context: bpy.types.Context):

bpy.context.window_manager.popup_menu(draw, title = title, icon = icon)

def add_into_scene(obj: bpy.types.Object):
view_layer = bpy.context.view_layer
collection = view_layer.active_layer_collection.collection
collection.objects.link(obj)

def move_to_cursor(obj: bpy.types.Object):
# use obj.matrix_world to move, not obj.location because this bug:
# https://blender.stackexchange.com/questions/27667/incorrect-matrix-world-after-transformation
Expand All @@ -62,10 +67,7 @@ def move_to_cursor(obj: bpy.types.Object):
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):
view_layer = bpy.context.view_layer
collection = view_layer.active_layer_collection.collection
collection.objects.link(obj)

add_into_scene(obj)
move_to_cursor(obj)

def select_certain_objects(objs: tuple[bpy.types.Object, ...]) -> None:
Expand Down
50 changes: 47 additions & 3 deletions bbp_ng/UTIL_ioport_shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,27 +67,58 @@ class ConflictResolver():
"""

__mObjectStrategy: ConflictStrategy
__mLightStrategy: ConflictStrategy
__mMeshStrategy: ConflictStrategy
__mMaterialStrategy: ConflictStrategy
__mTextureStrategy: ConflictStrategy

def __init__(self, obj_strategy: ConflictStrategy, mesh_strategy: ConflictStrategy, mtl_strategy: ConflictStrategy, tex_strategy: ConflictStrategy):
def __init__(self,
obj_strategy: ConflictStrategy,
light_strategy: ConflictStrategy,
mesh_strategy: ConflictStrategy,
mtl_strategy: ConflictStrategy,
tex_strategy: ConflictStrategy):
self.__mObjectStrategy = obj_strategy
self.__mLightStrategy = light_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]:
def create_object(self, name: str, data: bpy.types.Mesh | None) -> 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)
`data` will only be applied when creating new object (no existing instance or strategy order rename).
Please note this function is only used to create mesh 3d object.
If you want to create light object, please use other functions provided by this class.
The 3d object and data block of light is created together.
"""
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_light(self, name: str) -> tuple[bpy.types.Object, bpy.types.Light, bool]:
"""
Create light data block and associated 3d object.
If conflict strategy is "Current", we try fetch 3d object with given name first,
then check whether it is light.
If no given name object or this object is not light, we create a new one,
otherwise return old one.
"""
if self.__mLightStrategy == ConflictStrategy.Current:
old_obj: bpy.types.Object | None = bpy.data.objects.get(name, None)
if old_obj is not None and old_obj.type == 'LIGHT':
return (old_obj, typing.cast(bpy.types.Light, old_obj.data), False)
# create new object.
# if object or light name is conflict, rename it directly without considering conflict strategy.
# create light with default point light type
new_light: bpy.types.Light = bpy.data.lights.new(name, 'POINT')
new_obj: bpy.types.Object = bpy.data.objects.new(name, new_light)
return (new_obj, new_light, 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)
Expand Down Expand Up @@ -144,6 +175,13 @@ class ImportParams():
default = _g_EnumHelper_ConflictStrategy.to_selection(ConflictStrategy.Rename),
) # type: ignore

light_conflict_strategy: bpy.props.EnumProperty(
name = "Light Name Conflict",
items = _g_EnumHelper_ConflictStrategy.generate_items(),
description = "Define how to process light name conflict",
default = _g_EnumHelper_ConflictStrategy.to_selection(ConflictStrategy.Rename),
) # type: ignore

object_conflict_strategy: bpy.props.EnumProperty(
name = "Object Name Conflict",
items = _g_EnumHelper_ConflictStrategy.generate_items(),
Expand All @@ -160,6 +198,8 @@ def draw_import_params(self, layout: bpy.types.UILayout) -> None:

body.label(text = 'Object Name Conflict')
body.prop(self, 'object_conflict_strategy', text = '')
body.label(text = 'Light Name Conflict')
body.prop(self, 'light_conflict_strategy', text = '')
body.label(text = 'Mesh Name Conflict')
body.prop(self, 'mesh_conflict_strategy', text = '')
body.label(text = 'Material Name Conflict')
Expand All @@ -176,12 +216,16 @@ def general_get_material_conflict_strategy(self) -> ConflictStrategy:
def general_get_mesh_conflict_strategy(self) -> ConflictStrategy:
return _g_EnumHelper_ConflictStrategy.get_selection(self.mesh_conflict_strategy)

def general_get_light_conflict_strategy(self) -> ConflictStrategy:
return _g_EnumHelper_ConflictStrategy.get_selection(self.light_conflict_strategy)

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_light_conflict_strategy(),
self.general_get_mesh_conflict_strategy(),
self.general_get_material_conflict_strategy(),
self.general_get_texture_conflict_strategy()
Expand Down

0 comments on commit cb893b7

Please sign in to comment.