diff --git a/bbp_ng/OP_EXPORT_virtools.py b/bbp_ng/OP_EXPORT_virtools.py index b21db81..c6d9738 100644 --- a/bbp_ng/OP_EXPORT_virtools.py +++ b/bbp_ng/OP_EXPORT_virtools.py @@ -159,28 +159,33 @@ def _prepare_virtools_3dobjects( # iterate exported object list for obj3d in export_objects: - # only accept mesh object and light object + # only accept mesh-like object, camera and light object # all of other objects will be discard. - match(obj3d.type): - case 'MESH': - # mesh object - if obj3d not in obj3d_cret_set: - # add into set - obj3d_cret_set.add(obj3d) - # create virtools instance - vtobj3d: bmap.BM3dObject = writer.create_3dobject() - # add into result list - obj3d_crets.append((obj3d, vtobj3d)) - case 'LIGHT': - # light object - if obj3d not in light_cret_set: - # add into set - light_cret_set.add(obj3d) - # create virtools instance - vtlight: bmap.BMTargetLight = writer.create_target_light() - # add into result list - light_crets.append((obj3d, typing.cast(bpy.types.Light, obj3d.data), vtlight)) - + if UTIL_blender_mesh.TemporaryMesh.has_geometry(obj3d): + # mesh-like object + if obj3d not in obj3d_cret_set: + # add into set + obj3d_cret_set.add(obj3d) + # create virtools instance + vtobj3d: bmap.BM3dObject = writer.create_3dobject() + # add into result list + obj3d_crets.append((obj3d, vtobj3d)) + else: + match(obj3d.type): + case 'CAMERA': + # camera object + # TODO + pass + case 'LIGHT': + # light object + if obj3d not in light_cret_set: + # add into set + light_cret_set.add(obj3d) + # create virtools instance + vtlight: bmap.BMTargetLight = writer.create_target_light() + # add into result list + light_crets.append((obj3d, typing.cast(bpy.types.Light, obj3d.data), vtlight)) + # step progress no matter whether create new one progress.step() diff --git a/bbp_ng/PROP_virtools_group.py b/bbp_ng/PROP_virtools_group.py index 86fd0d2..52f167f 100644 --- a/bbp_ng/PROP_virtools_group.py +++ b/bbp_ng/PROP_virtools_group.py @@ -1,6 +1,6 @@ import bpy import typing, enum -from . import UTIL_functions, UTIL_icons_manager +from . import UTIL_functions, UTIL_icons_manager, UTIL_blender_mesh #region Virtools Groups Define & Help Class @@ -387,9 +387,9 @@ class BBP_PT_virtools_groups(bpy.types.Panel): target = typing.cast(bpy.types.Object, context.active_object) # notify on non-mesh object - if target.type != 'MESH': + if not UTIL_blender_mesh.TemporaryMesh.has_geometry(target): layout.label( - text='Virtools Group is invalid on non-mesh object!', icon='ERROR', + text='Virtools Group is invalid on non-mesh-like object!', icon='ERROR', text_ctxt='BBP_PT_virtools_groups/draw') # draw main body diff --git a/bbp_ng/PROP_virtools_mesh.py b/bbp_ng/PROP_virtools_mesh.py index 4c133af..6f1601f 100644 --- a/bbp_ng/PROP_virtools_mesh.py +++ b/bbp_ng/PROP_virtools_mesh.py @@ -1,6 +1,6 @@ import bpy import typing, enum -from . import UTIL_functions, UTIL_virtools_types +from . import UTIL_functions, UTIL_blender_mesh, UTIL_virtools_types # Raw Data @@ -30,19 +30,21 @@ class BBP_PG_virtools_mesh(bpy.types.PropertyGroup): # Getter Setter -def get_virtools_mesh(mesh: bpy.types.Mesh) -> BBP_PG_virtools_mesh: - return mesh.virtools_mesh +CanToMesh = bpy.types.Mesh | bpy.types.Curve | bpy.types.SurfaceCurve | bpy.types.TextCurve | bpy.types.MetaBall -def get_raw_virtools_mesh(mesh: bpy.types.Mesh) -> RawVirtoolsMesh: - props: BBP_PG_virtools_mesh = get_virtools_mesh(mesh) +def get_virtools_mesh(meshlike: CanToMesh) -> BBP_PG_virtools_mesh: + return meshlike.virtools_mesh + +def get_raw_virtools_mesh(meshlike: CanToMesh) -> RawVirtoolsMesh: + props: BBP_PG_virtools_mesh = get_virtools_mesh(meshlike) rawdata: RawVirtoolsMesh = RawVirtoolsMesh() rawdata.mLitMode = _g_Helper_VXMESH_LITMODE.get_selection(props.lit_mode) return rawdata -def set_raw_virtools_mesh(mesh: bpy.types.Mesh, rawdata: RawVirtoolsMesh) -> None: - props: BBP_PG_virtools_mesh = get_virtools_mesh(mesh) +def set_raw_virtools_mesh(meshlike: CanToMesh, rawdata: RawVirtoolsMesh) -> None: + props: BBP_PG_virtools_mesh = get_virtools_mesh(meshlike) props.lit_mode = _g_Helper_VXMESH_LITMODE.to_selection(rawdata.mLitMode) @@ -59,12 +61,22 @@ class BBP_PT_virtools_mesh(bpy.types.Panel): @classmethod def poll(cls, context): - return context.mesh is not None + if context.mesh is not None: return True + if context.curve is not None: return True + if context.meta_ball is not None: return True + return False def draw(self, context): - # get layout and target + # get layout layout = self.layout - props: BBP_PG_virtools_mesh = get_virtools_mesh(context.mesh) + # get target + datablock: typing.Any + if context.mesh is not None: datablock = context.mesh + elif context.curve is not None: datablock = context.curve + elif context.meta_ball is not None: datablock = context.meta_ball + else: datablock = None + # get mesh properties + props: BBP_PG_virtools_mesh = get_virtools_mesh(datablock) # draw data layout.prop(props, 'lit_mode') @@ -75,11 +87,21 @@ def register() -> None: bpy.utils.register_class(BBP_PG_virtools_mesh) bpy.utils.register_class(BBP_PT_virtools_mesh) - # add into mesh metadata + # Add metadata into mesh-like data block. + # according to TemporaryMesh, we need add it into: + # mesh, curve, surface, font, and metaball. bpy.types.Mesh.virtools_mesh = bpy.props.PointerProperty(type = BBP_PG_virtools_mesh) + bpy.types.Curve.virtools_mesh = bpy.props.PointerProperty(type = BBP_PG_virtools_mesh) + bpy.types.SurfaceCurve.virtools_mesh = bpy.props.PointerProperty(type = BBP_PG_virtools_mesh) + bpy.types.TextCurve.virtools_mesh = bpy.props.PointerProperty(type = BBP_PG_virtools_mesh) + bpy.types.MetaBall.virtools_mesh = bpy.props.PointerProperty(type = BBP_PG_virtools_mesh) def unregister() -> None: # remove from metadata + del bpy.types.MetaBall.virtools_mesh + del bpy.types.TextCurve.virtools_mesh + del bpy.types.SurfaceCurve.virtools_mesh + del bpy.types.Curve.virtools_mesh del bpy.types.Mesh.virtools_mesh bpy.utils.unregister_class(BBP_PT_virtools_mesh) diff --git a/bbp_ng/UTIL_blender_mesh.py b/bbp_ng/UTIL_blender_mesh.py index 860b98d..74a2514 100644 --- a/bbp_ng/UTIL_blender_mesh.py +++ b/bbp_ng/UTIL_blender_mesh.py @@ -105,22 +105,37 @@ def _nest_custom_split_normal(nml_array: array.array) -> typing.Iterator[UTIL_vi class TemporaryMesh(): """ Create a temporary mesh for convenient exporting. - When exporting mesh, we need triangulate it first. - We create a temporary mesh to hold the triangulated mesh result. + When exporting mesh, we need evaluate it first, then triangulate it. + We create a temporary mesh to hold the evaluated and triangulated mesh result. So that original object will not be affected and keep its original geometry. - Please note passed bpy.types.Object must be Mesh Object. + Please note passed bpy.types.Object must be an object which can be converted into mesh. + You can use this class provided static method to check it. """ + + __cAllowedObjectType: typing.ClassVar[set[str]] = { + 'MESH', 'CURVE', 'SURFACE', 'FONT', 'META' + } + + @staticmethod + def has_geometry(obj: bpy.types.Object): + """ + Check whether given Blender object has geometry. + If it has, it can safely utilize this class for visiting mesh. + """ + return obj.type in TemporaryMesh.__cAllowedObjectType __mBindingObject: bpy.types.Object + __mEvaluatedObject: bpy.types.Object __mTempMesh: bpy.types.Mesh def __init__(self, binding_obj: bpy.types.Object): + depsgraph = bpy.context.evaluated_depsgraph_get() self.__mBindingObject = binding_obj - self.__mTempMesh = None + self.__mEvaluatedObject = self.__mBindingObject.evaluated_get(depsgraph) + self.__mTempMesh = self.__mEvaluatedObject.to_mesh() - if self.__mBindingObject.data is None: + if self.__mTempMesh is None: raise UTIL_functions.BBPException('try getting mesh from an object without mesh.') - self.__mTempMesh = self.__mBindingObject.to_mesh() def __enter__(self): return self @@ -129,6 +144,7 @@ class TemporaryMesh(): self.dispose() def is_valid(self) -> bool: + if self.__mBindingObject is None: return False if self.__mBindingObject is None: return False if self.__mTempMesh is None: return False return True @@ -136,7 +152,8 @@ class TemporaryMesh(): def dispose(self) -> None: if self.is_valid(): self.__mTempMesh = None - self.__mBindingObject.to_mesh_clear() + self.__mEvaluatedObject.to_mesh_clear() + self.__mEvaluatedObject = None self.__mBindingObject = None def get_temp_mesh(self) -> bpy.types.Mesh: