From 814fd8cf58eaf6e125e3e398623a5f70f07337c8 Mon Sep 17 00:00:00 2001 From: Candoran2 <45334438+Candoran2@users.noreply.github.com> Date: Wed, 5 Jan 2022 17:59:22 +0100 Subject: [PATCH 01/49] Added extra warning for too many bones per partition and move warning up. --- .../nif_export/geometry/mesh/__init__.py | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/io_scene_niftools/modules/nif_export/geometry/mesh/__init__.py b/io_scene_niftools/modules/nif_export/geometry/mesh/__init__.py index de0edd0c1..1924f33be 100644 --- a/io_scene_niftools/modules/nif_export/geometry/mesh/__init__.py +++ b/io_scene_niftools/modules/nif_export/geometry/mesh/__init__.py @@ -460,6 +460,24 @@ def export_tri_shapes(self, b_obj, n_parent, n_root, trishape_name=None): if NifData.data.version >= 0x04020100 and NifOp.props.skin_partition: NifLog.info("Creating skin partition") + + # warn on bad config settings + if game == 'OBLIVION': + if NifOp.props.pad_bones: + NifLog.warn("Using padbones on Oblivion export. Disable the pad bones option to get higher quality skin partitions.") + if game in ('OBLIVION', 'FALLOUT_3'): + if NifOp.props.max_bones_per_partition < 18: + NifLog.warn("Using less than 18 bones per partition on Oblivion/Fallout 3 export." + "Set it to 18 to get higher quality skin partitions.") + elif NifOp.props.max_bones_per_partition > 18: + NifLog.warn("Using more than 18 bones per partition on Oblivion/Fallout 3 export." + "This may cause issues in-game.") + if game == 'SKYRIM': + if NifOp.props.max_bones_per_partition < 24: + NifLog.warn("Using less than 24 bones per partition on Skyrim export." + "Set it to 24 to get higher quality skin partitions.") + # Skyrim Special Edition has a limit of 80 bones per partition, but export is not yet supported + part_order = [getattr(NifFormat.BSDismemberBodyPartType, face_map.name, None) for face_map in b_obj.face_maps] part_order = [body_part for body_part in part_order if body_part is not None] # override pyffi trishape.update_skin_partition with custom one (that allows ordering) @@ -475,18 +493,6 @@ def export_tri_shapes(self, b_obj, n_parent, n_root, trishape_name=None): maximize_bone_sharing=(game in ('FALLOUT_3', 'SKYRIM')), part_sort_order=part_order) - # warn on bad config settings - if game == 'OBLIVION': - if NifOp.props.pad_bones: - NifLog.warn("Using padbones on Oblivion export. Disable the pad bones option to get higher quality skin partitions.") - if game in ('OBLIVION', 'FALLOUT_3'): - if NifOp.props.max_bones_per_partition < 18: - NifLog.warn("Using less than 18 bones per partition on Oblivion/Fallout 3 export." - "Set it to 18 to get higher quality skin partitions.") - if game == 'SKYRIM': - if NifOp.props.max_bones_per_partition < 24: - NifLog.warn("Using less than 24 bones per partition on Skyrim export." - "Set it to 24 to get higher quality skin partitions.") if lostweight > NifOp.props.epsilon: NifLog.warn(f"Lost {lostweight:f} in vertex weights while creating a skin partition for Blender object '{b_obj.name}' (nif block '{trishape.name}')") From a68aee754c2e93ddadfb4f0a5701f4fddcd67ae9 Mon Sep 17 00:00:00 2001 From: Candoran2 <45334438+Candoran2@users.noreply.github.com> Date: Wed, 5 Jan 2022 23:26:16 +0100 Subject: [PATCH 02/49] Fixed issue #496 by allowing for multiple triangles with the same vertices used. --- .../modules/nif_import/geometry/vertex/groups.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/io_scene_niftools/modules/nif_import/geometry/vertex/groups.py b/io_scene_niftools/modules/nif_import/geometry/vertex/groups.py index e7ae96399..3665ee06c 100644 --- a/io_scene_niftools/modules/nif_import/geometry/vertex/groups.py +++ b/io_scene_niftools/modules/nif_import/geometry/vertex/groups.py @@ -164,8 +164,14 @@ def import_skin(ni_block, b_obj): v_group.add([vert], w, 'REPLACE') # import body parts as face maps - # get faces (triangles) as map of tuples to index - tri_map = {frozenset(polygon.vertices): polygon.index for polygon in b_obj.data.polygons} + # get faces (triangles) as map of unordered vertices to list of indices + tri_map = {} + for polygon in b_obj.data.polygons: + vertices = frozenset(polygon.vertices) + if vertices in tri_map: + tri_map[vertices].append(polygon.index) + else: + tri_map[vertices] = [polygon.index] if isinstance(skininst, NifFormat.BSDismemberSkinInstance): skinpart = ni_block.get_skin_partition() for bodypart, skinpartblock in zip(skininst.partitions, skinpart.skin_partition_blocks): @@ -178,4 +184,5 @@ def import_skin(ni_block, b_obj): f_group = b_obj.face_maps.new(name=group_name) # add the triangles to the face map - f_group.add([tri_map[frozenset(vertices)] for vertices in skinpartblock.get_mapped_triangles()]) + for vertices in skinpartblock.get_mapped_triangles(): + f_group.add(tri_map[frozenset(vertices)]) From 0aa5ba3f9a8152723a1ede11e1cdc83f128956e7 Mon Sep 17 00:00:00 2001 From: HENDRIX-ZT2 Date: Mon, 28 Mar 2022 18:17:35 +0200 Subject: [PATCH 03/49] Import animation by default --- io_scene_niftools/operators/nif_import_op.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/io_scene_niftools/operators/nif_import_op.py b/io_scene_niftools/operators/nif_import_op.py index 61ebe1e3c..27894b945 100644 --- a/io_scene_niftools/operators/nif_import_op.py +++ b/io_scene_niftools/operators/nif_import_op.py @@ -65,7 +65,7 @@ class NifImportOperator(Operator, ImportHelper, CommonScale, CommonDevOperator, animation: bpy.props.BoolProperty( name="Animation", description="Import animation", - default=False) + default=True) # Merge skeleton roots. merge_skeleton_roots: bpy.props.BoolProperty( From 3dbe8a75785b492aab47588ca8e1865ba240606d Mon Sep 17 00:00:00 2001 From: HENDRIX-ZT2 Date: Mon, 28 Mar 2022 18:27:34 +0200 Subject: [PATCH 04/49] Add timing debug for keys --- io_scene_niftools/modules/nif_import/animation/transform.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/io_scene_niftools/modules/nif_import/animation/transform.py b/io_scene_niftools/modules/nif_import/animation/transform.py index ce62370e0..3520ff256 100644 --- a/io_scene_niftools/modules/nif_import/animation/transform.py +++ b/io_scene_niftools/modules/nif_import/animation/transform.py @@ -39,6 +39,7 @@ import bpy import mathutils +import time from functools import singledispatch from bisect import bisect_left @@ -277,6 +278,7 @@ def import_keyframe_controller(self, n_kfc, b_armature, b_target, b_action_name) b_action = self.create_action(b_target, f"{b_action_name}_{b_target.name}") bone_name = None + start_time = time.time() if eulers: NifLog.debug('Rotation keys..(euler)') fcurves = self.create_fcurves(b_action, "rotation_euler", range(3), flags, bone_name) @@ -307,6 +309,7 @@ def import_keyframe_controller(self, n_kfc, b_armature, b_target, b_action_name) for t, val in scales: key = (val, val, val) self.add_key(fcurves, t, key, interp_scale) + NifLog.debug(f'Keys for {b_target.name} imported in {time.time()-start_time:0.3f} seconds') return b_action def import_transforms(self, n_block, b_obj, bone_name=None): From ebb59e7969efc7dc2e768266484e0f68e16da5e7 Mon Sep 17 00:00:00 2001 From: HENDRIX-ZT2 Date: Mon, 28 Mar 2022 19:11:20 +0200 Subject: [PATCH 05/49] Speed up keys import --- .../modules/nif_import/animation/__init__.py | 19 ++++++ .../modules/nif_import/animation/transform.py | 66 +++++++++++-------- 2 files changed, 59 insertions(+), 26 deletions(-) diff --git a/io_scene_niftools/modules/nif_import/animation/__init__.py b/io_scene_niftools/modules/nif_import/animation/__init__.py index 9f0af6d9a..659c58778 100644 --- a/io_scene_niftools/modules/nif_import/animation/__init__.py +++ b/io_scene_niftools/modules/nif_import/animation/__init__.py @@ -141,6 +141,25 @@ def set_extrapolation(extend_type, fcurves): for fcurve in fcurves: fcurve.extrapolation = 'CONSTANT' + def add_keys(self, fcurves, times, keys, interp): + """ + Add a key (len=n) to a set of fcurves (len=n) at the given frame. Set the key's interpolation to interp. + """ + samples = [round(t * self.fps) for t in times] + assert len(samples) == len(keys) + # get interpolation enum representation + ipo = bpy.types.Keyframe.bl_rna.properties['interpolation'].enum_items[interp].value + interpolations = [ipo for _ in range(len(samples))] + # import the keys + for fcurve, fcu_keys in zip(fcurves, zip(*keys)): + # add new points + fcurve.keyframe_points.add(count=len(fcu_keys)) + # populate points with keys for this curve + fcurve.keyframe_points.foreach_set("co", [x for co in zip(samples, fcu_keys) for x in co]) + fcurve.keyframe_points.foreach_set("interpolation", interpolations) + # update + fcurve.update() + def add_key(self, fcurves, t, key, interp): """ Add a key (len=n) to a set of fcurves (len=n) at the given frame. Set the key's interpolation to interp. diff --git a/io_scene_niftools/modules/nif_import/animation/transform.py b/io_scene_niftools/modules/nif_import/animation/transform.py index 3520ff256..882b037ea 100644 --- a/io_scene_niftools/modules/nif_import/animation/transform.py +++ b/io_scene_niftools/modules/nif_import/animation/transform.py @@ -280,38 +280,52 @@ def import_keyframe_controller(self, n_kfc, b_armature, b_target, b_action_name) start_time = time.time() if eulers: - NifLog.debug('Rotation keys..(euler)') - fcurves = self.create_fcurves(b_action, "rotation_euler", range(3), flags, bone_name) - for t, val in eulers: - key = mathutils.Euler(val) - if bone_name: - key = math.import_keymat(n_bind_rot_inv, key.to_matrix().to_4x4()).to_euler() - self.add_key(fcurves, t, key, interp_rot) + self.import_eulers(b_action, bone_name, eulers, flags, interp_rot, n_bind_rot_inv) elif rotations: - NifLog.debug('Rotation keys...(quaternions)') - fcurves = self.create_fcurves(b_action, "rotation_quaternion", range(4), flags, bone_name) - for t, val in rotations: - key = mathutils.Quaternion([val.w, val.x, val.y, val.z]) - if bone_name: - key = math.import_keymat(n_bind_rot_inv, key.to_matrix().to_4x4()).to_quaternion() - self.add_key(fcurves, t, key, interp_rot) + self.import_rotations(b_action, bone_name, flags, interp_rot, n_bind_rot_inv, rotations) if translations: - NifLog.debug('Translation keys...') - fcurves = self.create_fcurves(b_action, "location", range(3), flags, bone_name) - for t, val in translations: - key = mathutils.Vector([val.x, val.y, val.z]) - if bone_name: - key = math.import_keymat(n_bind_rot_inv, mathutils.Matrix.Translation(key - n_bind_trans)).to_translation() - self.add_key(fcurves, t, key, interp_loc) + self.import_translations(b_action, bone_name, flags, interp_loc, n_bind_rot_inv, n_bind_trans, translations) if scales: - NifLog.debug('Scale keys...') - fcurves = self.create_fcurves(b_action, "scale", range(3), flags, bone_name) - for t, val in scales: - key = (val, val, val) - self.add_key(fcurves, t, key, interp_scale) + self.import_scale(b_action, bone_name, flags, interp_scale, scales) NifLog.debug(f'Keys for {b_target.name} imported in {time.time()-start_time:0.3f} seconds') return b_action + def import_scale(self, b_action, bone_name, flags, interp_scale, scales): + NifLog.debug('Scale keys...') + times = [t for t, val in scales] + keys = [(val, val, val) for t, val in scales] + fcurves = self.create_fcurves(b_action, "scale", range(3), flags, bone_name) + self.add_keys(fcurves, times, keys, interp_scale) + + def import_translations(self, b_action, bone_name, flags, interp_loc, n_bind_rot_inv, n_bind_trans, translations): + NifLog.debug('Translation keys...') + times = [t for t, val in translations] + keys = [mathutils.Vector([val.x, val.y, val.z]) for t, val in translations] + if bone_name: + keys = [ + math.import_keymat(n_bind_rot_inv, mathutils.Matrix.Translation(key - n_bind_trans)).to_translation() + for key in keys] + fcurves = self.create_fcurves(b_action, "location", range(3), flags, bone_name) + self.add_keys(fcurves, times, keys, interp_loc) + + def import_rotations(self, b_action, bone_name, flags, interp_rot, n_bind_rot_inv, rotations): + NifLog.debug('Rotation keys...(quaternions)') + times = [t for t, val in rotations] + keys = [mathutils.Quaternion([val.w, val.x, val.y, val.z]) for t, val in rotations] + if bone_name: + keys = [math.import_keymat(n_bind_rot_inv, key.to_matrix().to_4x4()).to_quaternion() for key in keys] + fcurves = self.create_fcurves(b_action, "rotation_quaternion", range(4), flags, bone_name) + self.add_keys(fcurves, times, keys, interp_rot) + + def import_eulers(self, b_action, bone_name, eulers, flags, interp_rot, n_bind_rot_inv): + NifLog.debug('Rotation keys..(euler)') + times = [t for t, val in eulers] + keys = [mathutils.Euler(val) for t, val in eulers] + if bone_name: + keys = [math.import_keymat(n_bind_rot_inv, key.to_matrix().to_4x4()).to_euler() for key in keys] + fcurves = self.create_fcurves(b_action, "rotation_euler", range(3), flags, bone_name) + self.add_keys(fcurves, times, keys, interp_rot) + def import_transforms(self, n_block, b_obj, bone_name=None): """Loads an animation attached to a nif block.""" # find keyframe controller From 35b1b7902540c890a090ef06c797cf2f29454027 Mon Sep 17 00:00:00 2001 From: HENDRIX-ZT2 Date: Mon, 28 Mar 2022 19:30:46 +0200 Subject: [PATCH 06/49] Speed up keys import --- .../modules/nif_import/animation/transform.py | 116 ++++++++---------- 1 file changed, 52 insertions(+), 64 deletions(-) diff --git a/io_scene_niftools/modules/nif_import/animation/transform.py b/io_scene_niftools/modules/nif_import/animation/transform.py index 882b037ea..363c235c1 100644 --- a/io_scene_niftools/modules/nif_import/animation/transform.py +++ b/io_scene_niftools/modules/nif_import/animation/transform.py @@ -188,11 +188,21 @@ def import_keyframe_controller(self, n_kfc, b_armature, b_target, b_action_name) return NifLog.debug(f'Importing keyframe controller for {b_target.name}') - translations = [] - scales = [] - rotations = [] - eulers = [] n_kfd = None + # fallout, Loki - we set extrapolation according to the root NiControllerSequence.cycle_type + flags = None + + # create or get the action + if b_armature and isinstance(b_target, bpy.types.PoseBone): + # action on armature, one per armature + b_action = self.create_action(b_armature, b_action_name) + if b_target.name in self.bind_data: + n_bind_rot_inv, n_bind_trans = self.bind_data[b_target.name] + bone_name = b_target.name + else: + # one action per object + b_action = self.create_action(b_target, f"{b_action_name}_{b_target.name}") + bone_name = None # transform controllers (dartgun.nif) if isinstance(n_kfc, NifFormat.NiTransformController): @@ -200,6 +210,8 @@ def import_keyframe_controller(self, n_kfc, b_armature, b_target, b_action_name) n_kfd = n_kfc.interpolator.data # B-spline curve import elif isinstance(n_kfc, NifFormat.NiBSplineInterpolator): + # Bsplines are Bezier curves + interp_rot = interp_loc = interp_scale = "BEZIER" # used by WLP2 (tiger.kf), but only for non-LocRotScale data # eg. bone stretching - see controlledblock.get_variable_1() # do not support this for now, no good representation in Blender @@ -208,24 +220,21 @@ def import_keyframe_controller(self, n_kfc, b_armature, b_target, b_action_name) # keys = list(kfc._getCompKeys(kfc.offset, 1, kfc.bias, kfc.multiplier)) return times = list(n_kfc.get_times()) - # just do these temp steps to avoid generating empty fcurves down the line - trans_temp = [mathutils.Vector(tup) for tup in n_kfc.get_translations()] - if trans_temp: - translations = zip(times, trans_temp) - rot_temp = [mathutils.Quaternion(tup) for tup in n_kfc.get_rotations()] - if rot_temp: - rotations = zip(times, rot_temp) - scale_temp = list(n_kfc.get_scales()) - if scale_temp: - scales = zip(times, scale_temp) - # Bsplines are Bezier curves - interp_rot = interp_loc = interp_scale = "BEZIER" + keys = list(n_kfc.get_translations()) + self.import_translations(b_action, bone_name, times, keys, flags, interp_loc, n_bind_rot_inv, n_bind_trans) + keys = list(n_kfc.get_rotations()) + self.import_quats(b_action, bone_name, times, keys, flags, interp_rot, n_bind_rot_inv) + keys = list(n_kfc.get_scales()) + self.import_scales(b_action, bone_name, times, keys, flags, interp_scale) elif isinstance(n_kfc, NifFormat.NiMultiTargetTransformController): # not sure what this is used for return else: # ZT2 & Fallout n_kfd = n_kfc.data + # ZT2 - get extrapolation for every kfc + if isinstance(n_kfc, NifFormat.NiKeyframeController): + flags = n_kfc.flags if isinstance(n_kfd, NifFormat.NiKeyframeData): interp_rot = self.get_b_interp_from_n_interp(n_kfd.rotation_type) interp_loc = self.get_b_interp_from_n_interp(n_kfd.translations.interpolation) @@ -248,59 +257,36 @@ def import_keyframe_controller(self, n_kfc, b_armature, b_target, b_action_name) x_r = interpolate(times_all, times_x, [key.value for key in n_kfd.xyz_rotations[0].keys]) y_r = interpolate(times_all, times_y, [key.value for key in n_kfd.xyz_rotations[1].keys]) z_r = interpolate(times_all, times_z, [key.value for key in n_kfd.xyz_rotations[2].keys]) - eulers = zip(times_all, zip(x_r, y_r, z_r)) + self.import_eulers(b_action, bone_name, times_all, zip(x_r, y_r, z_r), flags, interp_rot, n_bind_rot_inv) else: b_target.rotation_mode = "QUATERNION" - rotations = [(key.time, key.value) for key in n_kfd.quaternion_keys] + times = [key.time for key in n_kfd.quaternion_keys] + keys = [key.value for key in n_kfd.quaternion_keys] + self.import_quats(b_action, bone_name, times, keys, flags, interp_rot, n_bind_rot_inv) - if n_kfd.scales.keys: - scales = [(key.time, key.value) for key in n_kfd.scales.keys] + times = [key.time for key in n_kfd.scales.keys] + keys = [key.value for key in n_kfd.scales.keys] + self.import_scales(b_action, bone_name, times, keys, flags, interp_scale) + times = [key.time for key in n_kfd.translations.keys] + keys = [key.value for key in n_kfd.translations.keys] + self.import_translations(b_action, bone_name, times, keys, flags, interp_loc, n_bind_rot_inv, n_bind_trans) - if n_kfd.translations.keys: - translations = [(key.time, key.value) for key in n_kfd.translations.keys] - - # ZT2 - get extrapolation for every kfc - if isinstance(n_kfc, NifFormat.NiKeyframeController): - flags = n_kfc.flags - # fallout, Loki - we set extrapolation according to the root NiControllerSequence.cycle_type - else: - flags = None - - # create or get the action - if b_armature and isinstance(b_target, bpy.types.PoseBone): - # action on armature, one per armature - b_action = self.create_action(b_armature, b_action_name) - if b_target.name in self.bind_data: - n_bind_rot_inv, n_bind_trans = self.bind_data[b_target.name] - bone_name = b_target.name - else: - # one action per object - b_action = self.create_action(b_target, f"{b_action_name}_{b_target.name}") - bone_name = None - - start_time = time.time() - if eulers: - self.import_eulers(b_action, bone_name, eulers, flags, interp_rot, n_bind_rot_inv) - elif rotations: - self.import_rotations(b_action, bone_name, flags, interp_rot, n_bind_rot_inv, rotations) - if translations: - self.import_translations(b_action, bone_name, flags, interp_loc, n_bind_rot_inv, n_bind_trans, translations) - if scales: - self.import_scale(b_action, bone_name, flags, interp_scale, scales) - NifLog.debug(f'Keys for {b_target.name} imported in {time.time()-start_time:0.3f} seconds') return b_action - def import_scale(self, b_action, bone_name, flags, interp_scale, scales): + def import_scales(self, b_action, bone_name, times, keys, flags, interp_scale): + if not keys: + return NifLog.debug('Scale keys...') - times = [t for t, val in scales] - keys = [(val, val, val) for t, val in scales] + # make 3D + keys = [(val, val, val) for t, val in keys] fcurves = self.create_fcurves(b_action, "scale", range(3), flags, bone_name) self.add_keys(fcurves, times, keys, interp_scale) - def import_translations(self, b_action, bone_name, flags, interp_loc, n_bind_rot_inv, n_bind_trans, translations): + def import_translations(self, b_action, bone_name, times, keys, flags, interp_loc, n_bind_rot_inv, n_bind_trans): + if not keys: + return NifLog.debug('Translation keys...') - times = [t for t, val in translations] - keys = [mathutils.Vector([val.x, val.y, val.z]) for t, val in translations] + keys = [mathutils.Vector([val.x, val.y, val.z]) for val in keys] if bone_name: keys = [ math.import_keymat(n_bind_rot_inv, mathutils.Matrix.Translation(key - n_bind_trans)).to_translation() @@ -308,19 +294,21 @@ def import_translations(self, b_action, bone_name, flags, interp_loc, n_bind_rot fcurves = self.create_fcurves(b_action, "location", range(3), flags, bone_name) self.add_keys(fcurves, times, keys, interp_loc) - def import_rotations(self, b_action, bone_name, flags, interp_rot, n_bind_rot_inv, rotations): + def import_quats(self, b_action, bone_name, times, keys, flags, interp_rot, n_bind_rot_inv): + if not keys: + return NifLog.debug('Rotation keys...(quaternions)') - times = [t for t, val in rotations] - keys = [mathutils.Quaternion([val.w, val.x, val.y, val.z]) for t, val in rotations] + keys = [mathutils.Quaternion([val.w, val.x, val.y, val.z]) for val in keys] if bone_name: keys = [math.import_keymat(n_bind_rot_inv, key.to_matrix().to_4x4()).to_quaternion() for key in keys] fcurves = self.create_fcurves(b_action, "rotation_quaternion", range(4), flags, bone_name) self.add_keys(fcurves, times, keys, interp_rot) - def import_eulers(self, b_action, bone_name, eulers, flags, interp_rot, n_bind_rot_inv): + def import_eulers(self, b_action, bone_name, times, keys, flags, interp_rot, n_bind_rot_inv): + if not keys: + return NifLog.debug('Rotation keys..(euler)') - times = [t for t, val in eulers] - keys = [mathutils.Euler(val) for t, val in eulers] + keys = [mathutils.Euler(val) for val in keys] if bone_name: keys = [math.import_keymat(n_bind_rot_inv, key.to_matrix().to_4x4()).to_euler() for key in keys] fcurves = self.create_fcurves(b_action, "rotation_euler", range(3), flags, bone_name) From 9997f62a3d4d63ab156e96d53b30c301b21c25a5 Mon Sep 17 00:00:00 2001 From: HENDRIX-ZT2 Date: Wed, 30 Mar 2022 15:03:02 +0200 Subject: [PATCH 07/49] Refactor keys import pull out get_keys_values clean up euler import --- .../modules/nif_import/animation/transform.py | 65 +++++++++---------- 1 file changed, 31 insertions(+), 34 deletions(-) diff --git a/io_scene_niftools/modules/nif_import/animation/transform.py b/io_scene_niftools/modules/nif_import/animation/transform.py index 363c235c1..7f6d10730 100644 --- a/io_scene_niftools/modules/nif_import/animation/transform.py +++ b/io_scene_niftools/modules/nif_import/animation/transform.py @@ -211,7 +211,7 @@ def import_keyframe_controller(self, n_kfc, b_armature, b_target, b_action_name) # B-spline curve import elif isinstance(n_kfc, NifFormat.NiBSplineInterpolator): # Bsplines are Bezier curves - interp_rot = interp_loc = interp_scale = "BEZIER" + interp = "BEZIER" # used by WLP2 (tiger.kf), but only for non-LocRotScale data # eg. bone stretching - see controlledblock.get_variable_1() # do not support this for now, no good representation in Blender @@ -221,11 +221,11 @@ def import_keyframe_controller(self, n_kfc, b_armature, b_target, b_action_name) return times = list(n_kfc.get_times()) keys = list(n_kfc.get_translations()) - self.import_translations(b_action, bone_name, times, keys, flags, interp_loc, n_bind_rot_inv, n_bind_trans) + self.import_translations(b_action, bone_name, times, keys, flags, interp, n_bind_rot_inv, n_bind_trans) keys = list(n_kfc.get_rotations()) - self.import_quats(b_action, bone_name, times, keys, flags, interp_rot, n_bind_rot_inv) + self.import_quats(b_action, bone_name, times, keys, flags, interp, n_bind_rot_inv) keys = list(n_kfc.get_scales()) - self.import_scales(b_action, bone_name, times, keys, flags, interp_scale) + self.import_scales(b_action, bone_name, times, keys, flags, interp) elif isinstance(n_kfc, NifFormat.NiMultiTargetTransformController): # not sure what this is used for return @@ -236,49 +236,46 @@ def import_keyframe_controller(self, n_kfc, b_armature, b_target, b_action_name) if isinstance(n_kfc, NifFormat.NiKeyframeController): flags = n_kfc.flags if isinstance(n_kfd, NifFormat.NiKeyframeData): - interp_rot = self.get_b_interp_from_n_interp(n_kfd.rotation_type) - interp_loc = self.get_b_interp_from_n_interp(n_kfd.translations.interpolation) - interp_scale = self.get_b_interp_from_n_interp(n_kfd.scales.interpolation) if n_kfd.rotation_type == 4: b_target.rotation_mode = "XYZ" - # uses xyz rotation - if n_kfd.xyz_rotations[0].keys: - # euler keys need not be sampled at the same time in KFs - # but we need complete key sets to do the space conversion - # so perform linear interpolation to import all keys properly + # euler keys need not be sampled at the same time in KFs + # but we need complete key sets to do the space conversion + # so perform linear interpolation to import all keys properly - # get all the keys' times - times_x = [key.time for key in n_kfd.xyz_rotations[0].keys] - times_y = [key.time for key in n_kfd.xyz_rotations[1].keys] - times_z = [key.time for key in n_kfd.xyz_rotations[2].keys] - # the unique time stamps we have to sample all curves at - times_all = sorted(set(times_x + times_y + times_z)) - # the actual resampling - x_r = interpolate(times_all, times_x, [key.value for key in n_kfd.xyz_rotations[0].keys]) - y_r = interpolate(times_all, times_y, [key.value for key in n_kfd.xyz_rotations[1].keys]) - z_r = interpolate(times_all, times_z, [key.value for key in n_kfd.xyz_rotations[2].keys]) - self.import_eulers(b_action, bone_name, times_all, zip(x_r, y_r, z_r), flags, interp_rot, n_bind_rot_inv) + # get all the times and keys for each coordinate + times_keys = [self.get_keys_values(euler.keys) for euler in n_kfd.xyz_rotations] + # the unique time stamps we have to sample all curves at + times_all = sorted(set(times_keys[0][0] + times_keys[1][0] + times_keys[2][0])) + # todo - this assumes that all three channels are keyframed, but it seems like this need not be the case + # resample each coordinate for all times + keys_res = [interpolate(times_all, times, keys) for times, keys in times_keys] + # for eulers, the actual rotation type is apparently stored per channel + interp = self.get_b_interp_from_n_interp(n_kfd.xyz_rotations[0].rotation_type) + self.import_eulers(b_action, bone_name, times_all, zip(*keys_res), flags, interp, n_bind_rot_inv) else: b_target.rotation_mode = "QUATERNION" - times = [key.time for key in n_kfd.quaternion_keys] - keys = [key.value for key in n_kfd.quaternion_keys] - self.import_quats(b_action, bone_name, times, keys, flags, interp_rot, n_bind_rot_inv) - - times = [key.time for key in n_kfd.scales.keys] - keys = [key.value for key in n_kfd.scales.keys] - self.import_scales(b_action, bone_name, times, keys, flags, interp_scale) - times = [key.time for key in n_kfd.translations.keys] - keys = [key.value for key in n_kfd.translations.keys] - self.import_translations(b_action, bone_name, times, keys, flags, interp_loc, n_bind_rot_inv, n_bind_trans) + times, keys = self.get_keys_values(n_kfd.quaternion_keys) + interp = self.get_b_interp_from_n_interp(n_kfd.rotation_type) + self.import_quats(b_action, bone_name, times, keys, flags, interp, n_bind_rot_inv) + times, keys = self.get_keys_values(n_kfd.scales.keys) + interp = self.get_b_interp_from_n_interp(n_kfd.scales.interpolation) + self.import_scales(b_action, bone_name, times, keys, flags, interp) + times, keys = self.get_keys_values(n_kfd.translations.keys) + interp = self.get_b_interp_from_n_interp(n_kfd.translations.interpolation) + self.import_translations(b_action, bone_name, times, keys, flags, interp, n_bind_rot_inv, n_bind_trans) return b_action + @staticmethod + def get_keys_values(items): + return [key.time for key in items], [key.value for key in items] + def import_scales(self, b_action, bone_name, times, keys, flags, interp_scale): if not keys: return NifLog.debug('Scale keys...') # make 3D - keys = [(val, val, val) for t, val in keys] + keys = [(val, val, val) for val in keys] fcurves = self.create_fcurves(b_action, "scale", range(3), flags, bone_name) self.add_keys(fcurves, times, keys, interp_scale) From 14daae851ea02259af23df5d8169807a5851f5c7 Mon Sep 17 00:00:00 2001 From: HENDRIX-ZT2 Date: Wed, 30 Mar 2022 15:07:29 +0200 Subject: [PATCH 08/49] Remove removed attribute for blender 3.1 --- io_scene_niftools/modules/nif_export/collision/havok.py | 3 ++- io_scene_niftools/modules/nif_import/collision/havok.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/io_scene_niftools/modules/nif_export/collision/havok.py b/io_scene_niftools/modules/nif_export/collision/havok.py index aeaca69de..e35d902f1 100644 --- a/io_scene_niftools/modules/nif_export/collision/havok.py +++ b/io_scene_niftools/modules/nif_export/collision/havok.py @@ -173,7 +173,8 @@ def export_bhk_rigid_body(self, b_obj, n_col_obj): n_r_body.restitution = b_r_body.restitution n_r_body.max_linear_velocity = b_obj.nifcollision.max_linear_velocity n_r_body.max_angular_velocity = b_obj.nifcollision.max_angular_velocity - n_r_body.penetration_depth = b_obj.collision.permeability + # no longer available in blender 3.1 + # n_r_body.penetration_depth = b_obj.collision.permeability n_r_body.motion_system = b_obj.nifcollision.motion_system n_r_body.deactivator_type = b_obj.nifcollision.deactivator_type n_r_body.solver_deactivation = b_obj.nifcollision.solver_deactivation diff --git a/io_scene_niftools/modules/nif_import/collision/havok.py b/io_scene_niftools/modules/nif_import/collision/havok.py index c3f1b3947..4d67cd52d 100644 --- a/io_scene_niftools/modules/nif_import/collision/havok.py +++ b/io_scene_niftools/modules/nif_import/collision/havok.py @@ -197,7 +197,8 @@ def _import_bhk_rigid_body(self, bhkshape, collision_objs): b_r_body.deactivate_angular_velocity = mathutils.Vector([ang_vel.w, ang_vel.x, ang_vel.y, ang_vel.z]).magnitude # Custom Niftools properties - b_col_obj.collision.permeability = bhkshape.penetration_depth + # no longer available in blender 3.1 + # b_col_obj.collision.permeability = bhkshape.penetration_depth b_col_obj.nifcollision.deactivator_type = NifFormat.DeactivatorType._enumkeys[bhkshape.deactivator_type] b_col_obj.nifcollision.solver_deactivation = NifFormat.SolverDeactivation._enumkeys[bhkshape.solver_deactivation] b_col_obj.nifcollision.max_linear_velocity = bhkshape.max_linear_velocity From d67e66b0b91d080947c30c8c1649e754ab6cf0be Mon Sep 17 00:00:00 2001 From: HENDRIX-ZT2 Date: Wed, 30 Mar 2022 15:08:55 +0200 Subject: [PATCH 09/49] Fix anim import --- io_scene_niftools/modules/nif_import/animation/transform.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/io_scene_niftools/modules/nif_import/animation/transform.py b/io_scene_niftools/modules/nif_import/animation/transform.py index 7f6d10730..3aefa10b9 100644 --- a/io_scene_niftools/modules/nif_import/animation/transform.py +++ b/io_scene_niftools/modules/nif_import/animation/transform.py @@ -250,7 +250,7 @@ def import_keyframe_controller(self, n_kfc, b_armature, b_target, b_action_name) # resample each coordinate for all times keys_res = [interpolate(times_all, times, keys) for times, keys in times_keys] # for eulers, the actual rotation type is apparently stored per channel - interp = self.get_b_interp_from_n_interp(n_kfd.xyz_rotations[0].rotation_type) + interp = self.get_b_interp_from_n_interp(n_kfd.xyz_rotations[0].interpolation) self.import_eulers(b_action, bone_name, times_all, zip(*keys_res), flags, interp, n_bind_rot_inv) else: b_target.rotation_mode = "QUATERNION" From 1671410b3fe44a77c86cff75581412725d3fdad8 Mon Sep 17 00:00:00 2001 From: HENDRIX-ZT2 Date: Wed, 30 Mar 2022 15:12:05 +0200 Subject: [PATCH 10/49] Fix anim import --- io_scene_niftools/modules/nif_import/animation/transform.py | 1 + 1 file changed, 1 insertion(+) diff --git a/io_scene_niftools/modules/nif_import/animation/transform.py b/io_scene_niftools/modules/nif_import/animation/transform.py index 3aefa10b9..50fb7d36d 100644 --- a/io_scene_niftools/modules/nif_import/animation/transform.py +++ b/io_scene_niftools/modules/nif_import/animation/transform.py @@ -191,6 +191,7 @@ def import_keyframe_controller(self, n_kfc, b_armature, b_target, b_action_name) n_kfd = None # fallout, Loki - we set extrapolation according to the root NiControllerSequence.cycle_type flags = None + n_bind_rot_inv = n_bind_trans = None # create or get the action if b_armature and isinstance(b_target, bpy.types.PoseBone): From fd08980dcabb4bdd2b9961557c87943e020564ef Mon Sep 17 00:00:00 2001 From: HENDRIX-ZT2 Date: Wed, 30 Mar 2022 15:19:29 +0200 Subject: [PATCH 11/49] Improve comments --- .../modules/nif_import/animation/transform.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/io_scene_niftools/modules/nif_import/animation/transform.py b/io_scene_niftools/modules/nif_import/animation/transform.py index 50fb7d36d..43739ea0a 100644 --- a/io_scene_niftools/modules/nif_import/animation/transform.py +++ b/io_scene_niftools/modules/nif_import/animation/transform.py @@ -213,10 +213,10 @@ def import_keyframe_controller(self, n_kfc, b_armature, b_target, b_action_name) elif isinstance(n_kfc, NifFormat.NiBSplineInterpolator): # Bsplines are Bezier curves interp = "BEZIER" - # used by WLP2 (tiger.kf), but only for non-LocRotScale data - # eg. bone stretching - see controlledblock.get_variable_1() - # do not support this for now, no good representation in Blender if isinstance(n_kfc, NifFormat.NiBSplineCompFloatInterpolator): + # used by WLP2 (tiger.kf), but only for non-LocRotScale data + # eg. bone stretching - see controlledblock.get_variable_1() + # do not support this for now, no good representation in Blender # pyffi lacks support for this, but the following gets float keys # keys = list(kfc._getCompKeys(kfc.offset, 1, kfc.bias, kfc.multiplier)) return @@ -250,7 +250,7 @@ def import_keyframe_controller(self, n_kfc, b_armature, b_target, b_action_name) # todo - this assumes that all three channels are keyframed, but it seems like this need not be the case # resample each coordinate for all times keys_res = [interpolate(times_all, times, keys) for times, keys in times_keys] - # for eulers, the actual rotation type is apparently stored per channel + # for eulers, the actual interpolation type is apparently stored per channel interp = self.get_b_interp_from_n_interp(n_kfd.xyz_rotations[0].interpolation) self.import_eulers(b_action, bone_name, times_all, zip(*keys_res), flags, interp, n_bind_rot_inv) else: @@ -269,6 +269,7 @@ def import_keyframe_controller(self, n_kfc, b_armature, b_target, b_action_name) @staticmethod def get_keys_values(items): + """Returns list of times and keys for an array 'items' with key elements having 'time' and 'value' attributes""" return [key.time for key in items], [key.value for key in items] def import_scales(self, b_action, bone_name, times, keys, flags, interp_scale): From 56d208d2fa945452c5aef3ed509274f096217f3d Mon Sep 17 00:00:00 2001 From: HENDRIX-ZT2 Date: Wed, 30 Mar 2022 15:24:08 +0200 Subject: [PATCH 12/49] Improve parameters --- .../modules/nif_import/animation/transform.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/io_scene_niftools/modules/nif_import/animation/transform.py b/io_scene_niftools/modules/nif_import/animation/transform.py index 43739ea0a..61b775d7b 100644 --- a/io_scene_niftools/modules/nif_import/animation/transform.py +++ b/io_scene_niftools/modules/nif_import/animation/transform.py @@ -272,16 +272,16 @@ def get_keys_values(items): """Returns list of times and keys for an array 'items' with key elements having 'time' and 'value' attributes""" return [key.time for key in items], [key.value for key in items] - def import_scales(self, b_action, bone_name, times, keys, flags, interp_scale): + def import_scales(self, b_action, bone_name, times, keys, flags, interp): if not keys: return NifLog.debug('Scale keys...') # make 3D keys = [(val, val, val) for val in keys] fcurves = self.create_fcurves(b_action, "scale", range(3), flags, bone_name) - self.add_keys(fcurves, times, keys, interp_scale) + self.add_keys(fcurves, times, keys, interp) - def import_translations(self, b_action, bone_name, times, keys, flags, interp_loc, n_bind_rot_inv, n_bind_trans): + def import_translations(self, b_action, bone_name, times, keys, flags, interp, n_bind_rot_inv, n_bind_trans): if not keys: return NifLog.debug('Translation keys...') @@ -291,9 +291,9 @@ def import_translations(self, b_action, bone_name, times, keys, flags, interp_lo math.import_keymat(n_bind_rot_inv, mathutils.Matrix.Translation(key - n_bind_trans)).to_translation() for key in keys] fcurves = self.create_fcurves(b_action, "location", range(3), flags, bone_name) - self.add_keys(fcurves, times, keys, interp_loc) + self.add_keys(fcurves, times, keys, interp) - def import_quats(self, b_action, bone_name, times, keys, flags, interp_rot, n_bind_rot_inv): + def import_quats(self, b_action, bone_name, times, keys, flags, interp, n_bind_rot_inv): if not keys: return NifLog.debug('Rotation keys...(quaternions)') @@ -301,9 +301,9 @@ def import_quats(self, b_action, bone_name, times, keys, flags, interp_rot, n_bi if bone_name: keys = [math.import_keymat(n_bind_rot_inv, key.to_matrix().to_4x4()).to_quaternion() for key in keys] fcurves = self.create_fcurves(b_action, "rotation_quaternion", range(4), flags, bone_name) - self.add_keys(fcurves, times, keys, interp_rot) + self.add_keys(fcurves, times, keys, interp) - def import_eulers(self, b_action, bone_name, times, keys, flags, interp_rot, n_bind_rot_inv): + def import_eulers(self, b_action, bone_name, times, keys, flags, interp, n_bind_rot_inv): if not keys: return NifLog.debug('Rotation keys..(euler)') @@ -311,7 +311,7 @@ def import_eulers(self, b_action, bone_name, times, keys, flags, interp_rot, n_b if bone_name: keys = [math.import_keymat(n_bind_rot_inv, key.to_matrix().to_4x4()).to_euler() for key in keys] fcurves = self.create_fcurves(b_action, "rotation_euler", range(3), flags, bone_name) - self.add_keys(fcurves, times, keys, interp_rot) + self.add_keys(fcurves, times, keys, interp) def import_transforms(self, n_block, b_obj, bone_name=None): """Loads an animation attached to a nif block.""" From 5508e6ee2651c6752859845ead99afe9a6a1ea3b Mon Sep 17 00:00:00 2001 From: HENDRIX-ZT2 Date: Wed, 30 Mar 2022 15:50:49 +0200 Subject: [PATCH 13/49] Refactor and join code paths for keys import pull out maths and type handling into dict --- .../modules/nif_import/animation/transform.py | 106 +++++++++++------- 1 file changed, 63 insertions(+), 43 deletions(-) diff --git a/io_scene_niftools/modules/nif_import/animation/transform.py b/io_scene_niftools/modules/nif_import/animation/transform.py index 61b775d7b..7da2794ce 100644 --- a/io_scene_niftools/modules/nif_import/animation/transform.py +++ b/io_scene_niftools/modules/nif_import/animation/transform.py @@ -51,6 +51,51 @@ from io_scene_niftools.utils.blocks import safe_decode from io_scene_niftools.utils.logging import NifLog +QUAT = "rotation_quaternion" +EULER = "rotation_euler" +LOC = "location" +SCALE = "scale" + + +def as_b_quat(n_val): + return mathutils.Quaternion([n_val.w, n_val.x, n_val.y, n_val.z]) + + +def as_b_loc(n_val): + return mathutils.Vector([n_val.x, n_val.y, n_val.z]) + + +def as_b_scale(n_val): + return n_val, n_val, n_val + + +def as_b_euler(n_val): + return mathutils.Euler(n_val) + + +def correct_loc(key, n_bind_rot_inv, n_bind_trans): + return math.import_keymat(n_bind_rot_inv, mathutils.Matrix.Translation(key - n_bind_trans)).to_translation() + + +def correct_quat(key, n_bind_rot_inv, n_bind_trans): + return math.import_keymat(n_bind_rot_inv, key.to_matrix().to_4x4()).to_quaternion() + + +def correct_euler(key, n_bind_rot_inv, n_bind_trans): + return math.import_keymat(n_bind_rot_inv, key.to_matrix().to_4x4()).to_euler() + + +def correct_scale(key, n_bind_rot_inv, n_bind_trans): + return key + + +key_lut = { + QUAT: (as_b_quat, correct_quat, 4), + EULER: (as_b_euler, correct_euler, 3), + LOC: (as_b_loc, correct_loc, 3), + SCALE: (as_b_scale, correct_scale, 3), +} + def interpolate(x_out, x_in, y_in): """ @@ -222,11 +267,11 @@ def import_keyframe_controller(self, n_kfc, b_armature, b_target, b_action_name) return times = list(n_kfc.get_times()) keys = list(n_kfc.get_translations()) - self.import_translations(b_action, bone_name, times, keys, flags, interp, n_bind_rot_inv, n_bind_trans) + self.import_keys(LOC, b_action, bone_name, times, keys, flags, interp, n_bind_rot_inv, n_bind_trans) keys = list(n_kfc.get_rotations()) - self.import_quats(b_action, bone_name, times, keys, flags, interp, n_bind_rot_inv) + self.import_keys(QUAT, b_action, bone_name, times, keys, flags, interp, n_bind_rot_inv, n_bind_trans) keys = list(n_kfc.get_scales()) - self.import_scales(b_action, bone_name, times, keys, flags, interp) + self.import_keys(SCALE, b_action, bone_name, times, keys, flags, interp, n_bind_rot_inv, n_bind_trans) elif isinstance(n_kfc, NifFormat.NiMultiTargetTransformController): # not sure what this is used for return @@ -252,18 +297,19 @@ def import_keyframe_controller(self, n_kfc, b_armature, b_target, b_action_name) keys_res = [interpolate(times_all, times, keys) for times, keys in times_keys] # for eulers, the actual interpolation type is apparently stored per channel interp = self.get_b_interp_from_n_interp(n_kfd.xyz_rotations[0].interpolation) - self.import_eulers(b_action, bone_name, times_all, zip(*keys_res), flags, interp, n_bind_rot_inv) + self.import_keys(EULER, b_action, bone_name, times_all, zip(*keys_res), flags, interp, n_bind_rot_inv, n_bind_trans) else: b_target.rotation_mode = "QUATERNION" times, keys = self.get_keys_values(n_kfd.quaternion_keys) interp = self.get_b_interp_from_n_interp(n_kfd.rotation_type) - self.import_quats(b_action, bone_name, times, keys, flags, interp, n_bind_rot_inv) + self.import_keys(QUAT, b_action, bone_name, times, keys, flags, interp, n_bind_rot_inv, n_bind_trans) times, keys = self.get_keys_values(n_kfd.scales.keys) interp = self.get_b_interp_from_n_interp(n_kfd.scales.interpolation) - self.import_scales(b_action, bone_name, times, keys, flags, interp) + self.import_keys(SCALE, b_action, bone_name, times, keys, flags, interp, n_bind_rot_inv, n_bind_trans) + times, keys = self.get_keys_values(n_kfd.translations.keys) interp = self.get_b_interp_from_n_interp(n_kfd.translations.interpolation) - self.import_translations(b_action, bone_name, times, keys, flags, interp, n_bind_rot_inv, n_bind_trans) + self.import_keys(LOC, b_action, bone_name, times, keys, flags, interp, n_bind_rot_inv, n_bind_trans) return b_action @@ -272,45 +318,19 @@ def get_keys_values(items): """Returns list of times and keys for an array 'items' with key elements having 'time' and 'value' attributes""" return [key.time for key in items], [key.value for key in items] - def import_scales(self, b_action, bone_name, times, keys, flags, interp): - if not keys: - return - NifLog.debug('Scale keys...') - # make 3D - keys = [(val, val, val) for val in keys] - fcurves = self.create_fcurves(b_action, "scale", range(3), flags, bone_name) - self.add_keys(fcurves, times, keys, interp) - - def import_translations(self, b_action, bone_name, times, keys, flags, interp, n_bind_rot_inv, n_bind_trans): - if not keys: - return - NifLog.debug('Translation keys...') - keys = [mathutils.Vector([val.x, val.y, val.z]) for val in keys] - if bone_name: - keys = [ - math.import_keymat(n_bind_rot_inv, mathutils.Matrix.Translation(key - n_bind_trans)).to_translation() - for key in keys] - fcurves = self.create_fcurves(b_action, "location", range(3), flags, bone_name) - self.add_keys(fcurves, times, keys, interp) - - def import_quats(self, b_action, bone_name, times, keys, flags, interp, n_bind_rot_inv): - if not keys: - return - NifLog.debug('Rotation keys...(quaternions)') - keys = [mathutils.Quaternion([val.w, val.x, val.y, val.z]) for val in keys] - if bone_name: - keys = [math.import_keymat(n_bind_rot_inv, key.to_matrix().to_4x4()).to_quaternion() for key in keys] - fcurves = self.create_fcurves(b_action, "rotation_quaternion", range(4), flags, bone_name) - self.add_keys(fcurves, times, keys, interp) - - def import_eulers(self, b_action, bone_name, times, keys, flags, interp, n_bind_rot_inv): + def import_keys(self, key_type, b_action, bone_name, times, keys, flags, interp, n_bind_rot_inv, n_bind_trans): + """Imports key frames according to the specified key_type""" if not keys: return - NifLog.debug('Rotation keys..(euler)') - keys = [mathutils.Euler(val) for val in keys] + # look up conventions by key type + key_func, key_corrector, key_dim = key_lut[key_type] + NifLog.debug(f'{key_type} keys...') + # convert nif keys to proper key type for blender + keys = [key_func(val) for val in keys] + # correct for bone space if target is an armature bone if bone_name: - keys = [math.import_keymat(n_bind_rot_inv, key.to_matrix().to_4x4()).to_euler() for key in keys] - fcurves = self.create_fcurves(b_action, "rotation_euler", range(3), flags, bone_name) + keys = [key_corrector(key, n_bind_rot_inv, n_bind_trans) for key in keys] + fcurves = self.create_fcurves(b_action, key_type, range(key_dim), flags, bone_name) self.add_keys(fcurves, times, keys, interp) def import_transforms(self, n_block, b_obj, bone_name=None): From c696772375dd601db19d3851a12a306f140e389c Mon Sep 17 00:00:00 2001 From: HENDRIX-ZT2 Date: Wed, 30 Mar 2022 15:52:25 +0200 Subject: [PATCH 14/49] Add todo for permeability --- io_scene_niftools/modules/nif_export/collision/havok.py | 2 +- io_scene_niftools/modules/nif_import/collision/havok.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/io_scene_niftools/modules/nif_export/collision/havok.py b/io_scene_niftools/modules/nif_export/collision/havok.py index e35d902f1..249b7edcc 100644 --- a/io_scene_niftools/modules/nif_export/collision/havok.py +++ b/io_scene_niftools/modules/nif_export/collision/havok.py @@ -173,7 +173,7 @@ def export_bhk_rigid_body(self, b_obj, n_col_obj): n_r_body.restitution = b_r_body.restitution n_r_body.max_linear_velocity = b_obj.nifcollision.max_linear_velocity n_r_body.max_angular_velocity = b_obj.nifcollision.max_angular_velocity - # no longer available in blender 3.1 + # todo - no longer available in blender 3.1, add to nifcollision # n_r_body.penetration_depth = b_obj.collision.permeability n_r_body.motion_system = b_obj.nifcollision.motion_system n_r_body.deactivator_type = b_obj.nifcollision.deactivator_type diff --git a/io_scene_niftools/modules/nif_import/collision/havok.py b/io_scene_niftools/modules/nif_import/collision/havok.py index 4d67cd52d..4de4e2f7e 100644 --- a/io_scene_niftools/modules/nif_import/collision/havok.py +++ b/io_scene_niftools/modules/nif_import/collision/havok.py @@ -197,7 +197,7 @@ def _import_bhk_rigid_body(self, bhkshape, collision_objs): b_r_body.deactivate_angular_velocity = mathutils.Vector([ang_vel.w, ang_vel.x, ang_vel.y, ang_vel.z]).magnitude # Custom Niftools properties - # no longer available in blender 3.1 + # todo - no longer available in blender 3.1, add to nifcollision # b_col_obj.collision.permeability = bhkshape.penetration_depth b_col_obj.nifcollision.deactivator_type = NifFormat.DeactivatorType._enumkeys[bhkshape.deactivator_type] b_col_obj.nifcollision.solver_deactivation = NifFormat.SolverDeactivation._enumkeys[bhkshape.solver_deactivation] From 936f780971c46da9fc7e31a9c64040720c9f1d64 Mon Sep 17 00:00:00 2001 From: HENDRIX-ZT2 Date: Wed, 30 Mar 2022 16:58:06 +0200 Subject: [PATCH 15/49] Cleanup --- io_scene_niftools/modules/nif_import/animation/material.py | 2 +- io_scene_niftools/modules/nif_import/animation/morph.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/io_scene_niftools/modules/nif_import/animation/material.py b/io_scene_niftools/modules/nif_import/animation/material.py index 60ec804a5..486cc80ff 100644 --- a/io_scene_niftools/modules/nif_import/animation/material.py +++ b/io_scene_niftools/modules/nif_import/animation/material.py @@ -111,7 +111,7 @@ def import_material_uv_controller(self, b_material, n_geom): # so we have to repeat the import for each used tex slot for i, texture_slot in enumerate(b_material.texture_slots): if texture_slot: - fcurves = self.create_fcurves(b_mat_action, f"texture_slots[{i}]." + data_path, (array_ind,), n_ctrl.flags) + fcurves = self.create_fcurves(b_mat_action, f"texture_slots[{i}].{data_path}", (array_ind,), n_ctrl.flags) for key in n_uvgroup.keys: if "offset" in data_path: self.add_key(fcurves, key.time, (-key.value,), interp) diff --git a/io_scene_niftools/modules/nif_import/animation/morph.py b/io_scene_niftools/modules/nif_import/animation/morph.py index b61a138d1..2a003a70e 100644 --- a/io_scene_niftools/modules/nif_import/animation/morph.py +++ b/io_scene_niftools/modules/nif_import/animation/morph.py @@ -102,7 +102,7 @@ def import_morph_controller(self, n_node, b_obj): continue # get the interpolation mode - interp = self.get_b_interp_from_n_interp( morph_data.interpolation) + interp = self.get_b_interp_from_n_interp(morph_data.interpolation) fcu = self.create_fcurves(shape_action, "value", (0,), flags=n_morphCtrl.flags, keyname=shape_key.name) # set keyframes From d00f471b922c2142a292f18cf5754534cf294dd6 Mon Sep 17 00:00:00 2001 From: HENDRIX-ZT2 Date: Wed, 30 Mar 2022 17:06:04 +0200 Subject: [PATCH 16/49] Move key lut, share data type constants --- io_scene_niftools/modules/nif_export/animation/transform.py | 3 ++- io_scene_niftools/modules/nif_import/animation/__init__.py | 4 +++- io_scene_niftools/modules/nif_import/animation/transform.py | 6 +----- io_scene_niftools/utils/consts.py | 6 ++++++ 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/io_scene_niftools/modules/nif_export/animation/transform.py b/io_scene_niftools/modules/nif_export/animation/transform.py index 964660061..3ae9ee05c 100644 --- a/io_scene_niftools/modules/nif_export/animation/transform.py +++ b/io_scene_niftools/modules/nif_export/animation/transform.py @@ -46,6 +46,7 @@ from io_scene_niftools.modules.nif_export.block_registry import block_store from io_scene_niftools.utils import math, consts from io_scene_niftools.utils.logging import NifError, NifLog +from io_scene_niftools.utils.consts import QUAT, EULER, LOC, SCALE class TransformAnimation(Animation): @@ -166,7 +167,7 @@ def export_transforms(self, parent_block, b_obj, b_action, bone=None): # matrix_local = matrix_parent_inverse * matrix_basis bind_matrix = b_obj.matrix_parent_inverse exp_fcurves = [fcu for fcu in b_action.fcurves if - fcu.data_path in ("rotation_quaternion", "rotation_euler", "location", "scale")] + fcu.data_path in (QUAT, EULER, LOC, SCALE)] else: # bone isn't keyframed in this action, nothing to do here diff --git a/io_scene_niftools/modules/nif_import/animation/__init__.py b/io_scene_niftools/modules/nif_import/animation/__init__.py index 659c58778..0b8ea7027 100644 --- a/io_scene_niftools/modules/nif_import/animation/__init__.py +++ b/io_scene_niftools/modules/nif_import/animation/__init__.py @@ -41,6 +41,7 @@ from pyffi.formats.nif import NifFormat from io_scene_niftools.utils.logging import NifLog +from io_scene_niftools.utils.consts import QUAT, EULER, LOC, SCALE class Animation: @@ -103,7 +104,7 @@ def create_fcurves(self, action, dtype, drange, flags=None, bonename=None, keyna ] else: # Object animation (non-skeletal) is lumped into the "LocRotScale" action_group - if dtype in ("rotation_euler", "rotation_quaternion", "location", "scale"): + if dtype in (QUAT, EULER, LOC, SCALE): action_group = "LocRotScale" # Non-transformaing animations (eg. visibility or material anims) use no action groups else: @@ -231,3 +232,4 @@ def set_frames_per_second(self, roots): self.fps = fps bpy.context.scene.render.fps = fps bpy.context.scene.frame_set(0) + diff --git a/io_scene_niftools/modules/nif_import/animation/transform.py b/io_scene_niftools/modules/nif_import/animation/transform.py index 7da2794ce..b270af267 100644 --- a/io_scene_niftools/modules/nif_import/animation/transform.py +++ b/io_scene_niftools/modules/nif_import/animation/transform.py @@ -50,11 +50,7 @@ from io_scene_niftools.utils import math from io_scene_niftools.utils.blocks import safe_decode from io_scene_niftools.utils.logging import NifLog - -QUAT = "rotation_quaternion" -EULER = "rotation_euler" -LOC = "location" -SCALE = "scale" +from io_scene_niftools.utils.consts import QUAT, EULER, LOC, SCALE def as_b_quat(n_val): diff --git a/io_scene_niftools/utils/consts.py b/io_scene_niftools/utils/consts.py index c4dd53c42..22ec76837 100644 --- a/io_scene_niftools/utils/consts.py +++ b/io_scene_niftools/utils/consts.py @@ -87,3 +87,9 @@ class EmptyObject: TEX_SLOTS.DECAL_2 = "Decal 2" TEX_SLOTS.SPECULAR = "Specular" TEX_SLOTS.NORMAL = "Normal" + +# fcurve data types for blender +QUAT = "rotation_quaternion" +EULER = "rotation_euler" +LOC = "location" +SCALE = "scale" From b5d02a853af86f44e2a6bcc2463071ad1d80b36d Mon Sep 17 00:00:00 2001 From: HENDRIX-ZT2 Date: Wed, 30 Mar 2022 17:49:17 +0200 Subject: [PATCH 17/49] Refactor + fix import_visibility --- .../modules/nif_import/animation/__init__.py | 10 ++++++++-- .../modules/nif_import/animation/object.py | 11 +++++------ .../modules/nif_import/animation/transform.py | 8 +------- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/io_scene_niftools/modules/nif_import/animation/__init__.py b/io_scene_niftools/modules/nif_import/animation/__init__.py index 0b8ea7027..003e66569 100644 --- a/io_scene_niftools/modules/nif_import/animation/__init__.py +++ b/io_scene_niftools/modules/nif_import/animation/__init__.py @@ -55,6 +55,11 @@ def __init__(self): # and still be able to access existing actions from this run self.actions = {} + @staticmethod + def get_keys_values(items): + """Returns list of times and keys for an array 'items' with key elements having 'time' and 'value' attributes""" + return [key.time for key in items], [key.value for key in items] + @staticmethod def show_pose_markers(): """Helper function to ensure that pose markers are shown""" @@ -142,9 +147,9 @@ def set_extrapolation(extend_type, fcurves): for fcurve in fcurves: fcurve.extrapolation = 'CONSTANT' - def add_keys(self, fcurves, times, keys, interp): + def add_keys(self, b_action, key_type, key_range, flags, bone_name, times, keys, interp): """ - Add a key (len=n) to a set of fcurves (len=n) at the given frame. Set the key's interpolation to interp. + Create needed fcurves and add a list of keys to an action. """ samples = [round(t * self.fps) for t in times] assert len(samples) == len(keys) @@ -152,6 +157,7 @@ def add_keys(self, fcurves, times, keys, interp): ipo = bpy.types.Keyframe.bl_rna.properties['interpolation'].enum_items[interp].value interpolations = [ipo for _ in range(len(samples))] # import the keys + fcurves = self.create_fcurves(b_action, key_type, key_range, flags, bone_name) for fcurve, fcu_keys in zip(fcurves, zip(*keys)): # add new points fcurve.keyframe_points.add(count=len(fcu_keys)) diff --git a/io_scene_niftools/modules/nif_import/animation/object.py b/io_scene_niftools/modules/nif_import/animation/object.py index bafd94c5d..07ba463cd 100644 --- a/io_scene_niftools/modules/nif_import/animation/object.py +++ b/io_scene_niftools/modules/nif_import/animation/object.py @@ -53,9 +53,8 @@ def import_visibility(self, n_node, b_obj): if not n_vis_ctrl: return NifLog.info("Importing vis controller") - - b_obj_action = self.create_action(b_obj, b_obj.name + "-Anim") - - fcurves = self.create_fcurves(b_obj_action, "hide", (0,), n_vis_ctrl.flags) - for key in n_vis_ctrl.data.keys: - self.add_key(fcurves, key.time, (key.value,), "CONSTANT") + b_obj_action = self.create_action(b_obj, f"{b_obj.name}-Anim") + times, keys = self.get_keys_values(n_vis_ctrl.data.keys) + # flat key - make it zippable + keys = [[key] for key in keys] + self.add_keys(b_obj_action, "hide_viewport", (0,), n_vis_ctrl.flags, None, times, keys, "CONSTANT") diff --git a/io_scene_niftools/modules/nif_import/animation/transform.py b/io_scene_niftools/modules/nif_import/animation/transform.py index b270af267..7c313b63c 100644 --- a/io_scene_niftools/modules/nif_import/animation/transform.py +++ b/io_scene_niftools/modules/nif_import/animation/transform.py @@ -309,11 +309,6 @@ def import_keyframe_controller(self, n_kfc, b_armature, b_target, b_action_name) return b_action - @staticmethod - def get_keys_values(items): - """Returns list of times and keys for an array 'items' with key elements having 'time' and 'value' attributes""" - return [key.time for key in items], [key.value for key in items] - def import_keys(self, key_type, b_action, bone_name, times, keys, flags, interp, n_bind_rot_inv, n_bind_trans): """Imports key frames according to the specified key_type""" if not keys: @@ -326,8 +321,7 @@ def import_keys(self, key_type, b_action, bone_name, times, keys, flags, interp, # correct for bone space if target is an armature bone if bone_name: keys = [key_corrector(key, n_bind_rot_inv, n_bind_trans) for key in keys] - fcurves = self.create_fcurves(b_action, key_type, range(key_dim), flags, bone_name) - self.add_keys(fcurves, times, keys, interp) + self.add_keys(b_action, key_type, range(key_dim), flags, bone_name, times, keys, interp) def import_transforms(self, n_block, b_obj, bone_name=None): """Loads an animation attached to a nif block.""" From d057831b6ea814bd580e89b1c612d79a73260e1a Mon Sep 17 00:00:00 2001 From: HENDRIX-ZT2 Date: Wed, 30 Mar 2022 18:04:41 +0200 Subject: [PATCH 18/49] Refactor morph + vis ctrl import --- .../modules/nif_import/animation/__init__.py | 21 ++++++++++++------- .../modules/nif_import/animation/morph.py | 12 ++++------- .../modules/nif_import/animation/object.py | 4 +--- .../modules/nif_import/animation/transform.py | 2 +- 4 files changed, 19 insertions(+), 20 deletions(-) diff --git a/io_scene_niftools/modules/nif_import/animation/__init__.py b/io_scene_niftools/modules/nif_import/animation/__init__.py index 003e66569..2b02c0a26 100644 --- a/io_scene_niftools/modules/nif_import/animation/__init__.py +++ b/io_scene_niftools/modules/nif_import/animation/__init__.py @@ -95,17 +95,17 @@ def create_action(self, b_obj, action_name): b_obj.animation_data.action = b_action return b_action - def create_fcurves(self, action, dtype, drange, flags=None, bonename=None, keyname=None): + def create_fcurves(self, action, dtype, drange, flags, bone_name, key_name): """ Create fcurves in action for desired conditions. """ # armature pose bone animation - if bonename: + if bone_name: fcurves = [ - action.fcurves.new(data_path=f'pose.bones["{bonename}"].{dtype}', index=i, action_group=bonename) + action.fcurves.new(data_path=f'pose.bones["{bone_name}"].{dtype}', index=i, action_group=bone_name) for i in drange] # shapekey pose bone animation - elif keyname: + elif key_name: fcurves = [ - action.fcurves.new(data_path=f'key_blocks["{keyname}"].{dtype}', index=0,) + action.fcurves.new(data_path=f'key_blocks["{key_name}"].{dtype}', index=0,) ] else: # Object animation (non-skeletal) is lumped into the "LocRotScale" action_group @@ -147,7 +147,7 @@ def set_extrapolation(extend_type, fcurves): for fcurve in fcurves: fcurve.extrapolation = 'CONSTANT' - def add_keys(self, b_action, key_type, key_range, flags, bone_name, times, keys, interp): + def add_keys(self, b_action, key_type, key_range, flags, times, keys, interp, bone_name=None, key_name=None): """ Create needed fcurves and add a list of keys to an action. """ @@ -157,8 +157,13 @@ def add_keys(self, b_action, key_type, key_range, flags, bone_name, times, keys, ipo = bpy.types.Keyframe.bl_rna.properties['interpolation'].enum_items[interp].value interpolations = [ipo for _ in range(len(samples))] # import the keys - fcurves = self.create_fcurves(b_action, key_type, key_range, flags, bone_name) - for fcurve, fcu_keys in zip(fcurves, zip(*keys)): + fcurves = self.create_fcurves(b_action, key_type, key_range, flags, bone_name, key_name) + if len(key_range) == 1: + # flat key - make it zippable + key_per_fcurve = [keys] + else: + key_per_fcurve = zip(*keys) + for fcurve, fcu_keys in zip(fcurves, key_per_fcurve): # add new points fcurve.keyframe_points.add(count=len(fcu_keys)) # populate points with keys for this curve diff --git a/io_scene_niftools/modules/nif_import/animation/morph.py b/io_scene_niftools/modules/nif_import/animation/morph.py index 2a003a70e..f123ac3fa 100644 --- a/io_scene_niftools/modules/nif_import/animation/morph.py +++ b/io_scene_niftools/modules/nif_import/animation/morph.py @@ -98,16 +98,13 @@ def import_morph_controller(self, n_node, b_obj): elif n_morphCtrl.interpolator_weights: morph_data = n_morphCtrl.interpolator_weights[idxMorph].interpolator.data.data except KeyError: - NifLog.info(f"Unsupported interpolator \"{type(n_morphCtrl.interpolator_weights['idxMorph'].interpolator)}\"") + NifLog.info(f"Unsupported interpolator '{type(n_morphCtrl.interpolator_weights['idxMorph'].interpolator)}'") continue # get the interpolation mode interp = self.get_b_interp_from_n_interp(morph_data.interpolation) - fcu = self.create_fcurves(shape_action, "value", (0,), flags=n_morphCtrl.flags, keyname=shape_key.name) - - # set keyframes - for key in morph_data.keys: - self.add_key(fcu, key.time, (key.value,), interp) + times, keys = self.get_keys_values(morph_data.keys) + self.add_keys(shape_action, "value", (0,), n_morphCtrl.flags, times, keys, interp, key_name=shape_key.name) def import_egm_morphs(self, b_obj): """Import all EGM morphs as shape keys for blender object.""" @@ -129,8 +126,7 @@ def import_egm_morphs(self, b_obj): # convert tuples into vector here so we can simply add in morph_mesh() for b_v_index, (bv, mv) in enumerate(zip(base_verts, morph_verts)): b_mesh.vertices[b_v_index].co = bv + mathutils.Vector(mv) - # TODO [animation] unused variable is it required - shape_key = b_obj.shape_key_add(name=key_name, from_mix=False) + b_obj.shape_key_add(name=key_name, from_mix=False) def morph_mesh(self, b_mesh, baseverts, morphverts): """Transform a mesh to be in the shape given by morphverts.""" diff --git a/io_scene_niftools/modules/nif_import/animation/object.py b/io_scene_niftools/modules/nif_import/animation/object.py index 07ba463cd..8ce5d21be 100644 --- a/io_scene_niftools/modules/nif_import/animation/object.py +++ b/io_scene_niftools/modules/nif_import/animation/object.py @@ -55,6 +55,4 @@ def import_visibility(self, n_node, b_obj): NifLog.info("Importing vis controller") b_obj_action = self.create_action(b_obj, f"{b_obj.name}-Anim") times, keys = self.get_keys_values(n_vis_ctrl.data.keys) - # flat key - make it zippable - keys = [[key] for key in keys] - self.add_keys(b_obj_action, "hide_viewport", (0,), n_vis_ctrl.flags, None, times, keys, "CONSTANT") + self.add_keys(b_obj_action, "hide_viewport", (0,), n_vis_ctrl.flags, times, keys, "CONSTANT") diff --git a/io_scene_niftools/modules/nif_import/animation/transform.py b/io_scene_niftools/modules/nif_import/animation/transform.py index 7c313b63c..8d8208fb2 100644 --- a/io_scene_niftools/modules/nif_import/animation/transform.py +++ b/io_scene_niftools/modules/nif_import/animation/transform.py @@ -321,7 +321,7 @@ def import_keys(self, key_type, b_action, bone_name, times, keys, flags, interp, # correct for bone space if target is an armature bone if bone_name: keys = [key_corrector(key, n_bind_rot_inv, n_bind_trans) for key in keys] - self.add_keys(b_action, key_type, range(key_dim), flags, bone_name, times, keys, interp) + self.add_keys(b_action, key_type, range(key_dim), flags, times, keys, interp, bone_name=bone_name) def import_transforms(self, n_block, b_obj, bone_name=None): """Loads an animation attached to a nif block.""" From fd9eadf3b4d8b8c4765eec86e6e4b91bbbdf13e6 Mon Sep 17 00:00:00 2001 From: HENDRIX-ZT2 Date: Wed, 30 Mar 2022 18:23:37 +0200 Subject: [PATCH 19/49] Refactor mat anim import --- .../modules/nif_import/animation/material.py | 41 ++++++++----------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/io_scene_niftools/modules/nif_import/animation/material.py b/io_scene_niftools/modules/nif_import/animation/material.py index 486cc80ff..2c6b84569 100644 --- a/io_scene_niftools/modules/nif_import/animation/material.py +++ b/io_scene_niftools/modules/nif_import/animation/material.py @@ -63,35 +63,31 @@ def import_material_controllers(self, n_geom, b_material): def import_material_alpha_controller(self, b_material, n_material): # find alpha controller - n_alphactrl = math.find_controller(n_material, NifFormat.NiAlphaController) - if not n_alphactrl: + n_ctrl = math.find_controller(n_material, NifFormat.NiAlphaController) + if not n_ctrl: return NifLog.info("Importing alpha controller") b_mat_action = self.create_action(b_material, "MaterialAction") - fcurves = self.create_fcurves(b_mat_action, "niftools.emissive_alpha", range(3), n_alphactrl.flags) - interp = self.get_b_interp_from_n_interp(n_alphactrl.data.data.interpolation) - for key in n_alphactrl.data.data.keys: - self.add_key(fcurves, key.time, (key.value, key.value, key.value), interp) + interp = self.get_b_interp_from_n_interp(n_ctrl.data.data.interpolation) + times, keys = self.get_keys_values(n_ctrl.data.data.keys) + # key needs to be RGB due to current representation in blender + keys = [(v, v, v) for v in keys] + self.add_keys(b_mat_action, "niftools.emissive_alpha", range(3), n_ctrl.flags, times, keys, interp) def import_material_color_controller(self, b_material, n_material, b_channel, n_target_color): # find material color controller with matching target color - for ctrl in n_material.get_controllers(): - if isinstance(ctrl, NifFormat.NiMaterialColorController): - if ctrl.get_target_color() == n_target_color: - n_matcolor_ctrl = ctrl + for n_ctrl in n_material.get_controllers(): + if isinstance(n_ctrl, NifFormat.NiMaterialColorController): + if n_ctrl.get_target_color() == n_target_color: break else: return NifLog.info(f"Importing material color controller for target color {n_target_color} into blender channel {b_channel}") - - # import data as curves b_mat_action = self.create_action(b_material, "MaterialAction") - - fcurves = self.create_fcurves(b_mat_action, b_channel, range(3), n_matcolor_ctrl.flags) - interp = self.get_b_interp_from_n_interp(n_matcolor_ctrl.data.data.interpolation) - for key in n_matcolor_ctrl.data.data.keys: - self.add_key(fcurves, key.time, key.value.as_list(), interp) + interp = self.get_b_interp_from_n_interp(n_ctrl.data.data.interpolation) + times, keys = self.get_keys_values(n_ctrl.data.data.keys) + self.add_keys(b_mat_action, b_channel, range(3), n_ctrl.flags, times, keys, interp) def import_material_uv_controller(self, b_material, n_geom): """Import UV controller data.""" @@ -111,10 +107,9 @@ def import_material_uv_controller(self, b_material, n_geom): # so we have to repeat the import for each used tex slot for i, texture_slot in enumerate(b_material.texture_slots): if texture_slot: - fcurves = self.create_fcurves(b_mat_action, f"texture_slots[{i}].{data_path}", (array_ind,), n_ctrl.flags) - for key in n_uvgroup.keys: - if "offset" in data_path: - self.add_key(fcurves, key.time, (-key.value,), interp) - else: - self.add_key(fcurves, key.time, (key.value,), interp) + times, keys = self.get_keys_values(n_uvgroup.keys) + # UV V coordinate is inverted + if "offset" in data_path and array_ind == 1: + keys = [-key for key in keys] + self.add_keys(b_mat_action, f"texture_slots[{i}].{data_path}", (array_ind,), n_ctrl.flags, times, keys, interp) From dcc661c3b159f7ee2e4d17cd56bfcc7ee0ec8164 Mon Sep 17 00:00:00 2001 From: HENDRIX-ZT2 Date: Wed, 30 Mar 2022 20:12:54 +0200 Subject: [PATCH 20/49] Upgrade UV offset import --- .../modules/nif_import/animation/material.py | 44 ++++++++++++++----- .../property/nodes_wrapper/__init__.py | 2 +- 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/io_scene_niftools/modules/nif_import/animation/material.py b/io_scene_niftools/modules/nif_import/animation/material.py index 2c6b84569..7dec12963 100644 --- a/io_scene_niftools/modules/nif_import/animation/material.py +++ b/io_scene_niftools/modules/nif_import/animation/material.py @@ -43,6 +43,7 @@ from io_scene_niftools.utils import math from io_scene_niftools.utils.singleton import NifOp from io_scene_niftools.utils.logging import NifLog +from io_scene_niftools.utils.consts import LOC, SCALE class MaterialAnimation(Animation): @@ -97,19 +98,42 @@ def import_material_uv_controller(self, b_material, n_geom): return NifLog.info("Importing UV controller") + if not any(n_uvgroup.keys for n_uvgroup in n_ctrl.data.uv_groups): + return + b_mat_action = self.create_action(b_material, "MaterialAction") - dtypes = ("offset", 0), ("offset", 1), ("scale", 0), ("scale", 1) + tree = b_material.node_tree + transform = tree.nodes.new('ShaderNodeMapping') + uv_index = 0 + uv_name = f"TexCoordIndex{uv_index}" + uv_node = tree.nodes.get(uv_name) + # get previous links + used_links = [] + for link in tree.links: + # todo - think about a better method, maybe by type + if link.from_node == uv_node: + # print("found") + used_links.append(link) + + # link the node between previous uv node and texture node + for link in used_links: + from_socket = link.from_socket + to_socket = link.to_socket + tree.links.remove(link) + tree.links.new(from_socket, transform.inputs[0]) + tree.links.new(transform.outputs[0], to_socket) + + # loc U, loc V, scale U, scale V + dtypes = (1, 0), (1, 1), (3, 0), (3, 1) for n_uvgroup, (data_path, array_ind) in zip(n_ctrl.data.uv_groups, dtypes): if n_uvgroup.keys: interp = self.get_b_interp_from_n_interp(n_uvgroup.interpolation) - # in blender, UV offset is stored per n_texture slot - # so we have to repeat the import for each used tex slot - for i, texture_slot in enumerate(b_material.texture_slots): - if texture_slot: - times, keys = self.get_keys_values(n_uvgroup.keys) - # UV V coordinate is inverted - if "offset" in data_path and array_ind == 1: - keys = [-key for key in keys] - self.add_keys(b_mat_action, f"texture_slots[{i}].{data_path}", (array_ind,), n_ctrl.flags, times, keys, interp) + times, keys = self.get_keys_values(n_uvgroup.keys) + # UV V coordinate is inverted in blender + if 1 == data_path and array_ind == 1: + keys = [-key for key in keys] + # todo - this does not register as keyframed visually, but animates the value + # bpy.data.materials["Material"].node_tree.nodes["Mapping.001"].inputs[1].default_value[0] + self.add_keys(b_mat_action, f'node_tree.nodes["{transform.name}"].inputs[{data_path}].default_value', (array_ind,), n_ctrl.flags, times, keys, interp) diff --git a/io_scene_niftools/modules/nif_import/property/nodes_wrapper/__init__.py b/io_scene_niftools/modules/nif_import/property/nodes_wrapper/__init__.py index 1e10ac2ec..e5f9382ce 100644 --- a/io_scene_niftools/modules/nif_import/property/nodes_wrapper/__init__.py +++ b/io_scene_niftools/modules/nif_import/property/nodes_wrapper/__init__.py @@ -76,7 +76,7 @@ def set_uv_map(self, b_texture_node, uv_index=0, reflective=False): self.tree.links.new(uv.outputs[6], b_texture_node.inputs[0]) # use supplied UV maps for everything else, if present else: - uv_name = "TexCoordIndex" + str(uv_index) + uv_name = f"TexCoordIndex{uv_index}" existing_node = self.tree.nodes.get(uv_name) if not existing_node: uv = self.tree.nodes.new('ShaderNodeUVMap') From b30338fb38c813d4afd1a2b0e25899d8848107b9 Mon Sep 17 00:00:00 2001 From: HENDRIX-ZT2 Date: Wed, 30 Mar 2022 20:32:57 +0200 Subject: [PATCH 21/49] Upgrade UV offset import --- .../modules/nif_import/animation/material.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/io_scene_niftools/modules/nif_import/animation/material.py b/io_scene_niftools/modules/nif_import/animation/material.py index 7dec12963..3e33d65e9 100644 --- a/io_scene_niftools/modules/nif_import/animation/material.py +++ b/io_scene_niftools/modules/nif_import/animation/material.py @@ -91,7 +91,7 @@ def import_material_color_controller(self, b_material, n_material, b_channel, n_ self.add_keys(b_mat_action, b_channel, range(3), n_ctrl.flags, times, keys, interp) def import_material_uv_controller(self, b_material, n_geom): - """Import UV controller data.""" + """Import UV controller data as a mapping node with animated values.""" # search for the block n_ctrl = math.find_controller(n_geom, NifFormat.NiUVController) if not n_ctrl: @@ -105,15 +105,11 @@ def import_material_uv_controller(self, b_material, n_geom): tree = b_material.node_tree transform = tree.nodes.new('ShaderNodeMapping') - uv_index = 0 - uv_name = f"TexCoordIndex{uv_index}" - uv_node = tree.nodes.get(uv_name) # get previous links used_links = [] for link in tree.links: - # todo - think about a better method, maybe by type - if link.from_node == uv_node: - # print("found") + # get uv nodes + if link.from_node.type == "UVMAP": used_links.append(link) # link the node between previous uv node and texture node From f3bc4fe5c929b78bdca181be494fb65bcd55be8f Mon Sep 17 00:00:00 2001 From: HENDRIX-ZT2 Date: Wed, 30 Mar 2022 20:58:59 +0200 Subject: [PATCH 22/49] Upgrade UV offset import --- .../modules/nif_import/animation/material.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/io_scene_niftools/modules/nif_import/animation/material.py b/io_scene_niftools/modules/nif_import/animation/material.py index 3e33d65e9..7180a8dd3 100644 --- a/io_scene_niftools/modules/nif_import/animation/material.py +++ b/io_scene_niftools/modules/nif_import/animation/material.py @@ -101,7 +101,7 @@ def import_material_uv_controller(self, b_material, n_geom): if not any(n_uvgroup.keys for n_uvgroup in n_ctrl.data.uv_groups): return - b_mat_action = self.create_action(b_material, "MaterialAction") + b_mat_action = self.create_action(b_material.node_tree, "MaterialAction") tree = b_material.node_tree transform = tree.nodes.new('ShaderNodeMapping') @@ -121,15 +121,15 @@ def import_material_uv_controller(self, b_material, n_geom): tree.links.new(transform.outputs[0], to_socket) # loc U, loc V, scale U, scale V - dtypes = (1, 0), (1, 1), (3, 0), (3, 1) + LOC_DP = 1 + SCALE_DP = 3 + dtypes = (LOC_DP, 0), (LOC_DP, 1), (SCALE_DP, 0), (SCALE_DP, 1) for n_uvgroup, (data_path, array_ind) in zip(n_ctrl.data.uv_groups, dtypes): if n_uvgroup.keys: interp = self.get_b_interp_from_n_interp(n_uvgroup.interpolation) times, keys = self.get_keys_values(n_uvgroup.keys) # UV V coordinate is inverted in blender - if 1 == data_path and array_ind == 1: + if 1 == LOC_DP and array_ind == 1: keys = [-key for key in keys] - # todo - this does not register as keyframed visually, but animates the value - # bpy.data.materials["Material"].node_tree.nodes["Mapping.001"].inputs[1].default_value[0] - self.add_keys(b_mat_action, f'node_tree.nodes["{transform.name}"].inputs[{data_path}].default_value', (array_ind,), n_ctrl.flags, times, keys, interp) + self.add_keys(b_mat_action, f'nodes["{transform.name}"].inputs[{data_path}].default_value', (array_ind,), n_ctrl.flags, times, keys, interp) From 133a9e5d22c1e9af4bba1932a37ba6287f108db9 Mon Sep 17 00:00:00 2001 From: HENDRIX-ZT2 Date: Wed, 30 Mar 2022 21:00:17 +0200 Subject: [PATCH 23/49] Upgrade UV offset import --- io_scene_niftools/modules/nif_import/animation/material.py | 1 - 1 file changed, 1 deletion(-) diff --git a/io_scene_niftools/modules/nif_import/animation/material.py b/io_scene_niftools/modules/nif_import/animation/material.py index 7180a8dd3..e61b044f1 100644 --- a/io_scene_niftools/modules/nif_import/animation/material.py +++ b/io_scene_niftools/modules/nif_import/animation/material.py @@ -43,7 +43,6 @@ from io_scene_niftools.utils import math from io_scene_niftools.utils.singleton import NifOp from io_scene_niftools.utils.logging import NifLog -from io_scene_niftools.utils.consts import LOC, SCALE class MaterialAnimation(Animation): From af4a25fe5c47f42f2917e036a5c0c078a5df2f14 Mon Sep 17 00:00:00 2001 From: HENDRIX-ZT2 Date: Thu, 31 Mar 2022 14:33:16 +0200 Subject: [PATCH 24/49] Remove uv anim comments from BFB plugin --- .../property/nodes_wrapper/__init__.py | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/io_scene_niftools/modules/nif_import/property/nodes_wrapper/__init__.py b/io_scene_niftools/modules/nif_import/property/nodes_wrapper/__init__.py index e5f9382ce..01b9efc07 100644 --- a/io_scene_niftools/modules/nif_import/property/nodes_wrapper/__init__.py +++ b/io_scene_niftools/modules/nif_import/property/nodes_wrapper/__init__.py @@ -85,25 +85,6 @@ def set_uv_map(self, b_texture_node, uv_index=0, reflective=False): else: uv = existing_node self.tree.links.new(uv.outputs[0], b_texture_node.inputs[0]) - # todo [texture/anim] if present in nifs, support it and move to anim sys - # if tex_transform or tex_anim: - # transform = tree.nodes.new('ShaderNodeMapping') - # # todo [texture] negate V coordinate - # if tex_transform: - # matrix_4x4 = mathutils.Matrix(tex_transform) - # transform.scale = matrix_4x4.to_scale() - # transform.rotation = matrix_4x4.to_euler() - # transform.translation = matrix_4x4.to_translation() - # transform.name = "TextureTransform" + str(i) - # if tex_anim: - # for j, dtype in enumerate(("offsetu", "offsetv")): - # for key in tex_anim[dtype]: - # transform.translation[j] = key[1] - # # note that since we are dealing with UV coordinates, V has to be negated - # if j == 1: transform.translation[j] *= -1 - # transform.keyframe_insert("translation", index=j, frame=int(key[0] * fps)) - # tree.links.new(uv.outputs[0], transform.inputs[0]) - # tree.links.new(transform.outputs[0], tex.inputs[0]) def global_uv_offset_scale(self, x_scale, y_scale, x_offset, y_offset, clamp_x, clamp_y): # get all uv nodes (by name, since we are importing they have the predefined name From 0f5f263101bd2bd9ed739b59faca8dad6ebabf8c Mon Sep 17 00:00:00 2001 From: HENDRIX-ZT2 Date: Thu, 31 Mar 2022 15:07:32 +0200 Subject: [PATCH 25/49] Fix decoding handling - closes #500 --- .../modules/nif_import/property/geometry/mesh.py | 5 +++-- io_scene_niftools/utils/blocks.py | 13 ++++++++----- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/io_scene_niftools/modules/nif_import/property/geometry/mesh.py b/io_scene_niftools/modules/nif_import/property/geometry/mesh.py index 2f5bf9d02..dfc2008e3 100644 --- a/io_scene_niftools/modules/nif_import/property/geometry/mesh.py +++ b/io_scene_niftools/modules/nif_import/property/geometry/mesh.py @@ -47,6 +47,7 @@ from io_scene_niftools.modules.nif_import.property.shader.bsshaderlightingproperty import BSShaderLightingPropertyProcessor from io_scene_niftools.modules.nif_import.property.shader.bsshaderproperty import BSShaderPropertyProcessor from io_scene_niftools.utils.logging import NifLog +from io_scene_niftools.utils.blocks import safe_decode class MeshPropertyProcessor: @@ -78,7 +79,7 @@ def process_property_list(self, n_block, b_obj): # just to avoid duped materials, a first pass, make sure a named material is created or retrieved for prop in props: if prop.name: - name = prop.name.decode() + name = safe_decode(prop.name) if name and name in bpy.data.materials: b_mat = bpy.data.materials[name] NifLog.debug(f"Retrieved already imported material {b_mat.name} from name {name}") @@ -88,7 +89,7 @@ def process_property_list(self, n_block, b_obj): break else: # bs shaders often have no name, so generate one from mesh name - name = n_block.name.decode() + "_nt_mat" + name = safe_decode(n_block.name) + "_nt_mat" b_mat = bpy.data.materials.new(name) NifLog.debug(f"Created material {name} to store properties in {b_mat.name}") diff --git a/io_scene_niftools/utils/blocks.py b/io_scene_niftools/utils/blocks.py index 64533df58..6d95d60aa 100644 --- a/io_scene_niftools/utils/blocks.py +++ b/io_scene_niftools/utils/blocks.py @@ -1,7 +1,10 @@ """ Nif Utilities, stores common code that is used across the code base""" -def safe_decode(b: bytes) -> str: - try: - return b.decode() - except UnicodeDecodeError: - return b.decode("shift-jis", errors="surrogateescape") \ No newline at end of file + +def safe_decode(b: bytes, encodings=('ascii', 'utf8', 'latin1', 'shift-jis')) -> str: + for encoding in encodings: + try: + return b.decode(encoding) + except UnicodeDecodeError: + pass + return b.decode("ascii", errors="surrogateescape") From 16fd32c7209b2af47a81b937f2796ccd2dd1eeab Mon Sep 17 00:00:00 2001 From: HENDRIX-ZT2 Date: Thu, 31 Mar 2022 19:48:50 +0200 Subject: [PATCH 26/49] Fix shape keys export, add doc - closes #180 shape keys are only present on non-evaluated meshes! --- docs/user/features/geometry/geometry.rst | 12 +++++ .../modules/nif_export/animation/morph.py | 5 +- .../nif_export/geometry/mesh/__init__.py | 48 ++++++++++--------- .../modules/nif_import/animation/morph.py | 27 ++++++----- 4 files changed, 54 insertions(+), 38 deletions(-) diff --git a/docs/user/features/geometry/geometry.rst b/docs/user/features/geometry/geometry.rst index ff1813f96..cb55b20dd 100644 --- a/docs/user/features/geometry/geometry.rst +++ b/docs/user/features/geometry/geometry.rst @@ -94,3 +94,15 @@ Vertex Color & Alpha * `This image should clarify per-face vertex colouring `_ * On export, the scripts will create extra vertices for different vertex colors per face. + + +.. _geometry-shapekeys: + +Shape Key Animations +-------------------- + +**Example:** + +#. :ref:`Create a mesh-object `. +#. Add relative shape keys to your mesh. +#. Keyframe each shape key's value so that the key influences the shape of the mesh at the desired time. \ No newline at end of file diff --git a/io_scene_niftools/modules/nif_export/animation/morph.py b/io_scene_niftools/modules/nif_export/animation/morph.py index a1e2680a4..6078bdb5f 100644 --- a/io_scene_niftools/modules/nif_export/animation/morph.py +++ b/io_scene_niftools/modules/nif_export/animation/morph.py @@ -55,10 +55,11 @@ def __init__(self): EGMData.data = None def export_morph(self, b_mesh, n_trishape, vertmap): - # shape b_key morphing + NifLog.debug(f"Checking {b_mesh.name} for shape keys") + # shape keys are only present on non-evaluated meshes! b_key = b_mesh.shape_keys if b_key and len(b_key.key_blocks) > 1: - + NifLog.debug(f"{b_mesh.name} has shape keys") # yes, there is a b_key object attached # export as egm, or as morph_data? if b_key.key_blocks[1].name.startswith("EGM"): diff --git a/io_scene_niftools/modules/nif_export/geometry/mesh/__init__.py b/io_scene_niftools/modules/nif_export/geometry/mesh/__init__.py index 1924f33be..8d0d753cd 100644 --- a/io_scene_niftools/modules/nif_export/geometry/mesh/__init__.py +++ b/io_scene_niftools/modules/nif_export/geometry/mesh/__init__.py @@ -78,21 +78,22 @@ def export_tri_shapes(self, b_obj, n_parent, n_root, trishape_name=None): assert (b_obj.type == 'MESH') - # get mesh from b_obj - b_mesh = self.get_triangulated_mesh(b_obj) - b_mesh.calc_normals_split() + # get mesh from b_obj, and evaluate the mesh with modifiers applied, too + b_mesh = b_obj.data + eval_mesh = self.get_triangulated_mesh(b_obj) + eval_mesh.calc_normals_split() # getVertsFromGroup fails if the mesh has no vertices # (this happens when checking for fallout 3 body parts) # so quickly catch this (rare!) case - if not b_mesh.vertices: + if not eval_mesh.vertices: # do not export anything NifLog.warn(f"{b_obj} has no vertices, skipped.") return # get the mesh's materials, this updates the mesh material list if not isinstance(n_parent, NifFormat.RootCollisionNode): - mesh_materials = b_mesh.materials + mesh_materials = eval_mesh.materials else: # ignore materials on collision trishapes mesh_materials = [] @@ -102,9 +103,9 @@ def export_tri_shapes(self, b_obj, n_parent, n_root, trishape_name=None): mesh_materials = [None] # vertex color check - mesh_hasvcol = b_mesh.vertex_colors + mesh_hasvcol = eval_mesh.vertex_colors # list of body part (name, index, vertices) in this mesh - polygon_parts = self.get_polygon_parts(b_obj, b_mesh) + polygon_parts = self.get_polygon_parts(b_obj, eval_mesh) game = bpy.context.scene.niftools_scene.game # Non-textured materials, vertex colors are used to color the mesh @@ -188,9 +189,9 @@ def export_tri_shapes(self, b_obj, n_parent, n_root, trishape_name=None): # The following algorithm extracts all unique quads(vert, uv-vert, normal, vcol), # produce lists of vertices, uv-vertices, normals, vertex colors, and face indices. - mesh_uv_layers = b_mesh.uv_layers + mesh_uv_layers = eval_mesh.uv_layers vertquad_list = [] # (vertex, uv coordinate, normal, vertex color) list - vertex_map = [None for _ in range(len(b_mesh.vertices))] # blender vertex -> nif vertices + vertex_map = [None for _ in range(len(eval_mesh.vertices))] # blender vertex -> nif vertices vertex_positions = [] normals = [] vertex_colors = [] @@ -200,21 +201,21 @@ def export_tri_shapes(self, b_obj, n_parent, n_root, trishape_name=None): bodypartfacemap = [] polygons_without_bodypart = [] - if b_mesh.polygons: + if eval_mesh.polygons: if mesh_uv_layers: # if we have uv coordinates double check that we have uv data - if not b_mesh.uv_layer_stencil: - NifLog.warn(f"No UV map for texture associated with selected mesh '{b_mesh.name}'.") + if not eval_mesh.uv_layer_stencil: + NifLog.warn(f"No UV map for texture associated with selected mesh '{eval_mesh.name}'.") use_tangents = False if mesh_uv_layers and mesh_hasnormals: if game in ('OBLIVION', 'FALLOUT_3', 'SKYRIM') or (game in self.texture_helper.USED_EXTRA_SHADER_TEXTURES): use_tangents = True - b_mesh.calc_tangents(uvmap=mesh_uv_layers[0].name) + eval_mesh.calc_tangents(uvmap=mesh_uv_layers[0].name) tangents = [] bitangent_signs = [] - for poly in b_mesh.polygons: + for poly in eval_mesh.polygons: # does the face belong to this trishape? if b_mat is not None and poly.material_index != materialIndex: @@ -230,25 +231,25 @@ def export_tri_shapes(self, b_obj, n_parent, n_root, trishape_name=None): f_index = [-1] * f_numverts for i, loop_index in enumerate(poly.loop_indices): - fv_index = b_mesh.loops[loop_index].vertex_index - vertex = b_mesh.vertices[fv_index] + fv_index = eval_mesh.loops[loop_index].vertex_index + vertex = eval_mesh.vertices[fv_index] vertex_index = vertex.index fv = vertex.co # smooth = vertex normal, non-smooth = face normal) if mesh_hasnormals: if poly.use_smooth: - fn = b_mesh.loops[loop_index].normal + fn = eval_mesh.loops[loop_index].normal else: fn = poly.normal else: fn = None - fuv = [uv_layer.data[loop_index].uv for uv_layer in b_mesh.uv_layers] + fuv = [uv_layer.data[loop_index].uv for uv_layer in eval_mesh.uv_layers] # TODO [geomotry][mesh] Need to map b_verts -> n_verts if mesh_hasvcol: - f_col = list(b_mesh.vertex_colors[0].data[loop_index].color) + f_col = list(eval_mesh.vertex_colors[0].data[loop_index].color) else: f_col = None @@ -282,8 +283,8 @@ def export_tri_shapes(self, b_obj, n_parent, n_root, trishape_name=None): if mesh_hasnormals: normals.append(vertquad[2]) if use_tangents: - tangents.append(b_mesh.loops[loop_index].tangent) - bitangent_signs.append([b_mesh.loops[loop_index].bitangent_sign]) + tangents.append(eval_mesh.loops[loop_index].tangent) + bitangent_signs.append([eval_mesh.loops[loop_index].bitangent_sign]) if mesh_hasvcol: vertex_colors.append(vertquad[3]) if mesh_uv_layers: @@ -312,7 +313,7 @@ def export_tri_shapes(self, b_obj, n_parent, n_root, trishape_name=None): # check that there are no missing body part polygons if polygons_without_bodypart: - self.select_unassigned_polygons(b_mesh, b_obj, polygons_without_bodypart) + self.select_unassigned_polygons(eval_mesh, b_obj, polygons_without_bodypart) if len(triangles) > 65535: raise NifError("Too many polygons. Decimate your mesh and try again.") @@ -402,7 +403,7 @@ def export_tri_shapes(self, b_obj, n_parent, n_root, trishape_name=None): b_list_weight = [] b_vert_group = b_obj.vertex_groups[bone_group] - for b_vert in b_mesh.vertices: + for b_vert in eval_mesh.vertices: if len(b_vert.groups) == 0: # check vert has weight_groups unweighted_vertices.append(b_vert.index) continue @@ -504,6 +505,7 @@ def export_tri_shapes(self, b_obj, n_parent, n_root, trishape_name=None): tridata.consistency_flags = b_obj.niftools.consistency_flags # export EGM or NiGeomMorpherController animation + # shape keys are only present on the raw, unevaluated mesh self.morph_anim.export_morph(b_mesh, trishape, vertex_map) return trishape diff --git a/io_scene_niftools/modules/nif_import/animation/morph.py b/io_scene_niftools/modules/nif_import/animation/morph.py index f123ac3fa..f9f4f2add 100644 --- a/io_scene_niftools/modules/nif_import/animation/morph.py +++ b/io_scene_niftools/modules/nif_import/animation/morph.py @@ -64,41 +64,42 @@ def import_morph_controller(self, n_node, b_obj): morphData = n_morphCtrl.data if morphData.num_morphs: # get name for base key - keyname = morphData.morphs[0].frame_name.decode() + morph_data = morphData.morphs[0] + keyname = morph_data.frame_name.decode() if not keyname: keyname = 'Base' - # insert base key at frame 1, using relative keys + # insert base key, using relative keys sk_basis = b_obj.shape_key_add(name=keyname) # get base vectors and import all morphs - baseverts = morphData.morphs[0].vectors + baseverts = morph_data.vectors - shape_action = self.create_action(b_obj.data.shape_keys, b_obj.name + "-Morphs") + shape_action = self.create_action(b_obj.data.shape_keys, f"{b_obj.name}-Morphs") - for idxMorph in range(1, morphData.num_morphs): + for morph_i in range(1, morphData.num_morphs): + morph_data = morphData.morphs[morph_i] # get name for key - keyname = morphData.morphs[idxMorph].frame_name.decode() + keyname = morph_data.frame_name.decode() if not keyname: - keyname = f'Key {idxMorph}' + keyname = f'Key {morph_i}' NifLog.info(f"Inserting key '{keyname}'") # get vectors - morph_verts = morphData.morphs[idxMorph].vectors + morph_verts = morph_data.vectors self.morph_mesh(b_mesh, baseverts, morph_verts) shape_key = b_obj.shape_key_add(name=keyname, from_mix=False) - # first find the keys + # find the keys # older versions store keys in the morphData - morph_data = morphData.morphs[idxMorph] # newer versions store keys in the controller if not morph_data.keys: try: if n_morphCtrl.interpolators: - morph_data = n_morphCtrl.interpolators[idxMorph].data.data + morph_data = n_morphCtrl.interpolators[morph_i].data.data elif n_morphCtrl.interpolator_weights: - morph_data = n_morphCtrl.interpolator_weights[idxMorph].interpolator.data.data + morph_data = n_morphCtrl.interpolator_weights[morph_i].interpolator.data.data except KeyError: - NifLog.info(f"Unsupported interpolator '{type(n_morphCtrl.interpolator_weights['idxMorph'].interpolator)}'") + NifLog.info(f"Unsupported interpolator '{type(n_morphCtrl.interpolator_weights[morph_i].interpolator)}'") continue # get the interpolation mode From d20ca21532bfc9f8dd314dc9e714c04796f739c6 Mon Sep 17 00:00:00 2001 From: HENDRIX-ZT2 Date: Sat, 2 Apr 2022 10:39:04 +0200 Subject: [PATCH 27/49] Refactor export_children() --- .../modules/nif_export/object/__init__.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/io_scene_niftools/modules/nif_export/object/__init__.py b/io_scene_niftools/modules/nif_export/object/__init__.py index 06c586691..4e4f63527 100644 --- a/io_scene_niftools/modules/nif_export/object/__init__.py +++ b/io_scene_niftools/modules/nif_export/object/__init__.py @@ -97,7 +97,7 @@ def get_export_objects(self, only_selected=True): def export_root_node(self, root_objects, filebase): """ Exports a nif's root node; use blender root if there is only one, else create a meta root """ # TODO [collsion] detect root collision -> root collision node (can be mesh or empty) - # self.nif_export.collisionhelper.export_collision(b_obj, n_parent) + # self.export_collision(b_obj, n_parent) # return None # done; stop here self.n_root = None # there is only one root object so that will be our final root @@ -221,17 +221,12 @@ def export_node(self, b_obj, n_parent): def export_children(self, b_parent, n_parent): """Export all children of blender object b_parent as children of n_parent.""" - # loop over all obj's children for b_child in b_parent.children: - temp_parent = n_parent # special case: objects parented to armature bones - find the nif parent bone if b_parent.type == 'ARMATURE' and b_child.parent_bone != "": - parent_bone = b_parent.data.bones[b_child.parent_bone] - assert (parent_bone in block_store.block_to_obj.values()) - for temp_parent, obj in block_store.block_to_obj.items(): - if obj == parent_bone: - break - self.export_node(b_child, temp_parent) + b_parent_bone = b_parent.data.bones[b_child.parent_bone] + n_parent = block_store.block_to_obj[b_parent_bone] + self.export_node(b_child, n_parent) def export_collision(self, b_obj, n_parent): """Main function for adding collision object b_obj to a node. @@ -259,7 +254,7 @@ def export_collision(self, b_obj, n_parent): else: # all nodes failed so add new one node = types.create_ninode(b_obj) - node.name = 'collisiondummy{:d}'.format(n_parent.num_children) + node.name = f'collisiondummy{n_parent.num_children}' if b_obj.niftools.flags != 0: node_flag_hex = hex(b_obj.niftools.flags) else: @@ -273,4 +268,4 @@ def export_collision(self, b_obj, n_parent): else: NifLog.warn(f"Collisions not supported for game '{bpy.context.scene.niftools_scene.game}', skipped collision object '{b_obj.name}'") - return True \ No newline at end of file + return True From 425f9daa2a2a6f06840de18682e8f03b9bdf8562 Mon Sep 17 00:00:00 2001 From: HENDRIX-ZT2 Date: Sat, 2 Apr 2022 11:33:19 +0200 Subject: [PATCH 28/49] Move root node type to scene as root node can be virtual in blender --- .../modules/nif_export/object/__init__.py | 2 +- .../modules/nif_import/property/object/__init__.py | 4 +--- io_scene_niftools/properties/object.py | 9 --------- io_scene_niftools/properties/scene.py | 11 ++++++++++- io_scene_niftools/ui/object.py | 1 - io_scene_niftools/ui/scene.py | 1 + 6 files changed, 13 insertions(+), 15 deletions(-) diff --git a/io_scene_niftools/modules/nif_export/object/__init__.py b/io_scene_niftools/modules/nif_export/object/__init__.py index 4e4f63527..8ace84651 100644 --- a/io_scene_niftools/modules/nif_export/object/__init__.py +++ b/io_scene_niftools/modules/nif_export/object/__init__.py @@ -115,7 +115,7 @@ def export_root_node(self, root_objects, filebase): # TODO [object] How dow we know we are selecting the right node in the case of multi-root? # making root block a fade node - root_type = b_obj.niftools.rootnode + root_type = bpy.context.scene.niftools_scene.rootnode if bpy.context.scene.niftools_scene.game in ('FALLOUT_3', 'SKYRIM') and root_type == 'BSFadeNode': NifLog.info("Making root block a BSFadeNode") fade_root_block = NifFormat.BSFadeNode().deepcopy(self.n_root) diff --git a/io_scene_niftools/modules/nif_import/property/object/__init__.py b/io_scene_niftools/modules/nif_import/property/object/__init__.py index 21b486f1c..b76d23eee 100644 --- a/io_scene_niftools/modules/nif_import/property/object/__init__.py +++ b/io_scene_niftools/modules/nif_import/property/object/__init__.py @@ -50,9 +50,7 @@ def import_extra_datas(self, root_block, b_obj): """ Only to be called on nif and blender root objects! """ # store type of root node if isinstance(root_block, NifFormat.BSFadeNode): - b_obj.niftools.rootnode = 'BSFadeNode' - else: - b_obj.niftools.rootnode = 'NiNode' + bpy.context.scene.niftools_scene.rootnode = 'BSFadeNode' # store its flags b_obj.niftools.flags = root_block.flags # store extra datas diff --git a/io_scene_niftools/properties/object.py b/io_scene_niftools/properties/object.py index a35b09a17..78c165b1f 100644 --- a/io_scene_niftools/properties/object.py +++ b/io_scene_niftools/properties/object.py @@ -99,15 +99,6 @@ class ExtraDataStore(PropertyGroup): class ObjectProperty(PropertyGroup): - rootnode: EnumProperty( - name='Nif Root Node', - description='Type of property used to display meshes', - items=( - ('NiNode', 'NiNode', "", 0), - ('BSFadeNode', 'BSFadeNode', "", 1)), - default='NiNode', - ) - prn_location: EnumProperty( name='Weapon Location', description='Attachment point of weapon, for Skyrim, FO3 & Oblivion', diff --git a/io_scene_niftools/properties/scene.py b/io_scene_niftools/properties/scene.py index 3b0c5fbdd..a0ccfd095 100644 --- a/io_scene_niftools/properties/scene.py +++ b/io_scene_niftools/properties/scene.py @@ -39,7 +39,7 @@ import bpy -from bpy.props import PointerProperty, IntProperty +from bpy.props import PointerProperty, IntProperty, EnumProperty from bpy.types import PropertyGroup from pyffi.formats.nif import NifFormat @@ -94,6 +94,15 @@ class Scene(PropertyGroup): default='NONE', update=update_version_from_game) + rootnode: EnumProperty( + name='Root Node', + description='Type of property used to display meshes', + items=( + ('NiNode', 'NiNode', "", 0), + ('BSFadeNode', 'BSFadeNode', "", 1)), + default='NiNode', + ) + # Map game enum to nif version. VERSION = { _game_to_enum(game): versions[-1] diff --git a/io_scene_niftools/ui/object.py b/io_scene_niftools/ui/object.py index ef57f7184..72a6aab77 100644 --- a/io_scene_niftools/ui/object.py +++ b/io_scene_niftools/ui/object.py @@ -61,7 +61,6 @@ def draw(self, context): layout = self.layout row = layout.column() - row.prop(nif_obj_props, "rootnode") row.prop(nif_obj_props, "prn_location") row.prop(nif_obj_props, "upb") row.prop(nif_obj_props, "bsxflags") diff --git a/io_scene_niftools/ui/scene.py b/io_scene_niftools/ui/scene.py index 16f1503cb..094f02685 100644 --- a/io_scene_niftools/ui/scene.py +++ b/io_scene_niftools/ui/scene.py @@ -65,6 +65,7 @@ def draw(self, context): layout = self.layout row = layout.column() row.prop(nif_scene_props, "game") + row.prop(nif_scene_props, "rootnode") class SceneVersionInfoPanel(SceneButtonsPanel, Panel): From 33d0a35fdbdff0812574d7fa7db52d897755b728 Mon Sep 17 00:00:00 2001 From: HENDRIX-ZT2 Date: Sat, 2 Apr 2022 14:12:20 +0200 Subject: [PATCH 29/49] Refactor root node --- .../modules/nif_export/object/__init__.py | 45 ++++++++----------- io_scene_niftools/modules/nif_export/types.py | 25 ++++++----- 2 files changed, 32 insertions(+), 38 deletions(-) diff --git a/io_scene_niftools/modules/nif_export/object/__init__.py b/io_scene_niftools/modules/nif_export/object/__init__.py index 8ace84651..ea8b4d3c6 100644 --- a/io_scene_niftools/modules/nif_export/object/__init__.py +++ b/io_scene_niftools/modules/nif_export/object/__init__.py @@ -100,28 +100,20 @@ def export_root_node(self, root_objects, filebase): # self.export_collision(b_obj, n_parent) # return None # done; stop here self.n_root = None + node_type = bpy.context.scene.niftools_scene.rootnode # there is only one root object so that will be our final root if len(root_objects) == 1: b_obj = root_objects[0] - self.export_node(b_obj, None) + self.export_node(b_obj, None, n_node_type=node_type) # there is more than one root object so we create a meta root else: - NifLog.info("Created meta root because blender scene had multiple root objects") - self.n_root = types.create_ninode() + NifLog.info(f"Created meta root because blender scene had {len(root_objects)} root objects") + self.n_root = types.create_ninode(n_node_type=node_type) self.n_root.name = "Scene Root" for b_obj in root_objects: self.export_node(b_obj, self.n_root) - # TODO [object] How dow we know we are selecting the right node in the case of multi-root? - # making root block a fade node - root_type = bpy.context.scene.niftools_scene.rootnode - if bpy.context.scene.niftools_scene.game in ('FALLOUT_3', 'SKYRIM') and root_type == 'BSFadeNode': - NifLog.info("Making root block a BSFadeNode") - fade_root_block = NifFormat.BSFadeNode().deepcopy(self.n_root) - fade_root_block.replace_global_node(self.n_root, fade_root_block) - self.n_root = fade_root_block - # various extra datas object_property = ObjectDataProperty() object_property.export_bsxflags_upb(self.n_root, root_objects) @@ -147,7 +139,7 @@ def set_node_flags(self, b_obj, n_node): else: n_node.flags = 0x000C # morrowind - def export_node(self, b_obj, n_parent): + def export_node(self, b_obj, n_parent, n_node_type=None): """Export a mesh/armature/empty object b_obj as child of n_parent. Export also all children of b_obj. @@ -190,7 +182,7 @@ def export_node(self, b_obj, n_parent): b_action = False # -> everything else (empty/armature) is a (more or less regular) node - node = types.create_ninode(b_obj) + node = types.create_ninode(b_obj, n_node_type=n_node_type) # set parenting here so that it can be accessed if not self.n_root: self.n_root = node @@ -213,21 +205,22 @@ def export_node(self, b_obj, n_parent): # if it is an armature, export the bones as ninode children of this ninode elif b_obj.type == 'ARMATURE': self.armaturehelper.export_bones(b_obj, node) - - # export all children of this b_obj as children of this NiNode - self.export_children(b_obj, node) + # special case: objects parented to armature bones + for b_child in b_obj.children: + # find and attach to the right bone + if b_child.parent_bone: + b_obj_bone = b_obj.data.bones[b_child.parent_bone] + self.export_node(b_child, block_store.block_to_obj[b_obj_bone]) + # just child of the armature itself, so attach to armature root + else: + self.export_node(b_child, node) + else: + # export all children of this empty object as children of this node + for b_child in b_obj.children: + self.export_node(b_child, node) return node - def export_children(self, b_parent, n_parent): - """Export all children of blender object b_parent as children of n_parent.""" - for b_child in b_parent.children: - # special case: objects parented to armature bones - find the nif parent bone - if b_parent.type == 'ARMATURE' and b_child.parent_bone != "": - b_parent_bone = b_parent.data.bones[b_child.parent_bone] - n_parent = block_store.block_to_obj[b_parent_bone] - self.export_node(b_child, n_parent) - def export_collision(self, b_obj, n_parent): """Main function for adding collision object b_obj to a node. Returns True if this object is exported as a collision""" diff --git a/io_scene_niftools/modules/nif_export/types.py b/io_scene_niftools/modules/nif_export/types.py index 758b18a3a..c62a5a711 100644 --- a/io_scene_niftools/modules/nif_export/types.py +++ b/io_scene_niftools/modules/nif_export/types.py @@ -45,21 +45,22 @@ from io_scene_niftools.utils.singleton import NifOp -def create_ninode(b_obj=None): +def create_ninode(b_obj=None, n_node_type=None): """Essentially a wrapper around create_block() that creates nodes of the right type""" - # when no b_obj is passed, it means we create a root node + # when no b_obj is passed, use the passed n_node_type if not b_obj: - return block_store.create_block("NiNode") - + if n_node_type is None: + n_node_type = "NiNode" # get node type - some are stored as custom property of the b_obj - try: - n_node_type = b_obj["type"] - except KeyError: - n_node_type = "NiNode" - - # ...others by presence of constraints - if has_track(b_obj): - n_node_type = "NiBillboardNode" + else: + try: + n_node_type = b_obj["type"] + except KeyError: + n_node_type = "NiNode" + + # ...others by presence of constraints + if has_track(b_obj): + n_node_type = "NiBillboardNode" # now create the node n_node = block_store.create_block(n_node_type, b_obj) From 7e679b4902dc347f8e9f091732659f75f6586b8a Mon Sep 17 00:00:00 2001 From: HENDRIX-ZT2 Date: Sat, 2 Apr 2022 16:00:26 +0200 Subject: [PATCH 30/49] Move BS Inv marker properties and UI to scene --- .../nif_export/property/object/__init__.py | 12 +++--- .../nif_import/property/object/__init__.py | 12 +++--- io_scene_niftools/operators/geometry.py | 24 +++++------ io_scene_niftools/properties/object.py | 40 +----------------- io_scene_niftools/properties/scene.py | 41 ++++++++++++++++++- io_scene_niftools/ui/object.py | 32 --------------- io_scene_niftools/ui/scene.py | 34 +++++++++++++-- 7 files changed, 96 insertions(+), 99 deletions(-) diff --git a/io_scene_niftools/modules/nif_export/property/object/__init__.py b/io_scene_niftools/modules/nif_export/property/object/__init__.py index 844908b3f..5bc56c41a 100644 --- a/io_scene_niftools/modules/nif_export/property/object/__init__.py +++ b/io_scene_niftools/modules/nif_export/property/object/__init__.py @@ -223,17 +223,17 @@ def has_collision(): def export_inventory_marker(n_root, root_objects): if bpy.context.scene.niftools_scene.game in ('SKYRIM',): for root_object in root_objects: - if root_object.niftools_bs_invmarker: + if root_scene.bs_inv_marker: for extra_item in n_root.extra_data_list: if isinstance(extra_item, NifFormat.BSInvMarker): raise NifError("Multiple Items have Inventory marker data only one item may contain this data") else: n_extra_list = NifFormat.BSInvMarker() - n_extra_list.name = root_object.niftools_bs_invmarker[0].name.encode() - n_extra_list.rotation_x = (-root_object.niftools_bs_invmarker[0].bs_inv_x % (2 * pi)) * 1000 - n_extra_list.rotation_y = (-root_object.niftools_bs_invmarker[0].bs_inv_y % (2 * pi)) * 1000 - n_extra_list.rotation_z = (-root_object.niftools_bs_invmarker[0].bs_inv_z % (2 * pi)) * 1000 - n_extra_list.zoom = root_object.niftools_bs_invmarker[0].bs_inv_zoom + n_extra_list.name = root_scene.bs_inv_marker[0].name.encode() + n_extra_list.rotation_x = (-root_scene.bs_inv_marker[0].bs_inv_x % (2 * pi)) * 1000 + n_extra_list.rotation_y = (-root_scene.bs_inv_marker[0].bs_inv_y % (2 * pi)) * 1000 + n_extra_list.rotation_z = (-root_scene.bs_inv_marker[0].bs_inv_z % (2 * pi)) * 1000 + n_extra_list.zoom = root_scene.bs_inv_marker[0].bs_inv_zoom n_root.add_extra_data(n_extra_list) # TODO [object][property] Move to new object type diff --git a/io_scene_niftools/modules/nif_import/property/object/__init__.py b/io_scene_niftools/modules/nif_import/property/object/__init__.py index b76d23eee..6a9822585 100644 --- a/io_scene_niftools/modules/nif_import/property/object/__init__.py +++ b/io_scene_niftools/modules/nif_import/property/object/__init__.py @@ -81,9 +81,9 @@ def import_extra_datas(self, root_block, b_obj): elif isinstance(n_extra, NifFormat.BSXFlags): b_obj.niftools.bsxflags = n_extra.integer_data elif isinstance(n_extra, NifFormat.BSInvMarker): - b_obj.niftools_bs_invmarker.add() - b_obj.niftools_bs_invmarker[0].name = n_extra.name.decode() - b_obj.niftools_bs_invmarker[0].bs_inv_x = (-n_extra.rotation_x / 1000) % (2 * pi) - b_obj.niftools_bs_invmarker[0].bs_inv_y = (-n_extra.rotation_y / 1000) % (2 * pi) - b_obj.niftools_bs_invmarker[0].bs_inv_z = (-n_extra.rotation_z / 1000) % (2 * pi) - b_obj.niftools_bs_invmarker[0].bs_inv_zoom = n_extra.zoom + b_obj.bs_inv_marker.add() + b_obj.bs_inv_marker[0].name = n_extra.name.decode() + b_obj.bs_inv_marker[0].bs_inv_x = (-n_extra.rotation_x / 1000) % (2 * pi) + b_obj.bs_inv_marker[0].bs_inv_y = (-n_extra.rotation_y / 1000) % (2 * pi) + b_obj.bs_inv_marker[0].bs_inv_z = (-n_extra.rotation_z / 1000) % (2 * pi) + b_obj.bs_inv_marker[0].bs_inv_zoom = n_extra.zoom diff --git a/io_scene_niftools/operators/geometry.py b/io_scene_niftools/operators/geometry.py index d4f26c9b9..e808b4ba3 100644 --- a/io_scene_niftools/operators/geometry.py +++ b/io_scene_niftools/operators/geometry.py @@ -45,31 +45,31 @@ class BsInvMarkerAdd(Operator): """Adds BsInvMarker set""" - bl_idname = "object.niftools_bs_invmarker_add" + bl_idname = "scene.bs_inv_marker_add" bl_label = "Add Inventory Marker" bl_options = {'REGISTER', 'UNDO'} def execute(self, context): - obj = context.active_object - b_obj_invmarker = obj.niftools_bs_invmarker.add() - b_obj_invmarker.name = "INV" - b_obj_invmarker.bs_inv_x = 0 - b_obj_invmarker.bs_inv_y = 0 - b_obj_invmarker.bs_inv_z = 0 - b_obj_invmarker.bs_inv_zoom = 1 + bs_inv = context.scene.niftools_scene.bs_inv + bs_inv_item = bs_inv.add() + bs_inv_item.name = "INV" + bs_inv_item.bs_inv_x = 0 + bs_inv_item.bs_inv_y = 0 + bs_inv_item.bs_inv_z = 0 + bs_inv_item.bs_inv_zoom = 1 return {'FINISHED'} class BsInvMarkerRemove(bpy.types.Operator): """Removes BsInvMarker set""" - bl_idname = "object.niftools_bs_invmarker_remove" + bl_idname = "scene.bs_inv_marker_remove" bl_label = "Remove Inventory Marker" bl_options = {'REGISTER', 'UNDO'} def execute(self, context): - item = len(context.active_object.niftools_bs_invmarker) - 1 - obj = context.active_object - obj.niftools_bs_invmarker.remove(item) + bs_inv = context.scene.niftools_scene.bs_inv + item = len(bs_inv) - 1 + bs_inv.remove(item) return {'FINISHED'} diff --git a/io_scene_niftools/properties/object.py b/io_scene_niftools/properties/object.py index 78c165b1f..5a5049f32 100644 --- a/io_scene_niftools/properties/object.py +++ b/io_scene_niftools/properties/object.py @@ -147,46 +147,10 @@ class ObjectProperty(PropertyGroup): ) -class BsInventoryMarker(PropertyGroup): - - name: StringProperty( - name="", - default='INV' - ) - - bs_inv_x: FloatProperty( - name="Inv X value", - description="Rotation of object in inventory around the x axis", - default=0, - subtype = "ANGLE" - ) - - bs_inv_y: FloatProperty( - name="Inv Y value", - description="Rotation of object in inventory around the y axis", - default=0, - subtype = "ANGLE" - ) - - bs_inv_z: FloatProperty( - name="Inv Z value", - description="Rotation of object in inventory around the z axis", - default=0, - subtype = "ANGLE" - ) - - bs_inv_zoom: FloatProperty( - name="Inv Zoom Value", - description="Inventory object Zoom level", - default=1 - ) - - CLASSES = [ ExtraData, ExtraDataStore, ObjectProperty, - BsInventoryMarker ] @@ -194,12 +158,12 @@ def register(): register_classes(CLASSES, __name__) bpy.types.Object.niftools = bpy.props.PointerProperty(type=ObjectProperty) - bpy.types.Object.niftools_bs_invmarker = bpy.props.CollectionProperty(type=BsInventoryMarker) + # bpy.types.scene.bs_inv_marker = bpy.props.CollectionProperty(type=BsInventoryMarker) def unregister(): del bpy.types.Object.niftools - del bpy.types.Object.niftools_bs_invmarker + # del bpy.types.scene.bs_inv_marker unregister_classes(CLASSES, __name__) diff --git a/io_scene_niftools/properties/scene.py b/io_scene_niftools/properties/scene.py index a0ccfd095..a8c5298b0 100644 --- a/io_scene_niftools/properties/scene.py +++ b/io_scene_niftools/properties/scene.py @@ -39,7 +39,7 @@ import bpy -from bpy.props import PointerProperty, IntProperty, EnumProperty +from bpy.props import PointerProperty, IntProperty, EnumProperty, StringProperty, FloatProperty, CollectionProperty from bpy.types import PropertyGroup from pyffi.formats.nif import NifFormat @@ -62,8 +62,41 @@ def update_version_from_game(self, context): self.user_version_2 = self.USER_VERSION_2.get(self.game, 0) -class Scene(PropertyGroup): +class BsInventoryMarker(PropertyGroup): + name: StringProperty( + name="", + default='INV' + ) + + bs_inv_x: FloatProperty( + name="Inv X value", + description="Rotation of object in inventory around the x axis", + default=0, + subtype="ANGLE" + ) + bs_inv_y: FloatProperty( + name="Inv Y value", + description="Rotation of object in inventory around the y axis", + default=0, + subtype="ANGLE" + ) + + bs_inv_z: FloatProperty( + name="Inv Z value", + description="Rotation of object in inventory around the z axis", + default=0, + subtype="ANGLE" + ) + + bs_inv_zoom: FloatProperty( + name="Inv Zoom Value", + description="Inventory object Zoom level", + default=1 + ) + + +class Scene(PropertyGroup): nif_version: IntProperty( name='Version', description="The Gamebryo Engine version used", @@ -128,8 +161,11 @@ class Scene(PropertyGroup): default=0.1, min=0.001, max=100.0, precision=2) + bs_inv: bpy.props.CollectionProperty(type=BsInventoryMarker) + CLASSES = [ + BsInventoryMarker, Scene ] @@ -138,6 +174,7 @@ def register(): register_classes(CLASSES, __name__) bpy.types.Scene.niftools_scene = bpy.props.PointerProperty(type=Scene) + # bpy.types.Scene.niftools_bs_inv = bpy.props.CollectionProperty(type=BsInventoryMarker) def unregister(): diff --git a/io_scene_niftools/ui/object.py b/io_scene_niftools/ui/object.py index 72a6aab77..3a8c2748e 100644 --- a/io_scene_niftools/ui/object.py +++ b/io_scene_niftools/ui/object.py @@ -138,39 +138,7 @@ def draw_item(self, context, layout, data, item, icon, active_data, active_propn split.prop(item, "data", text="", emboss=False, translate=False, icon='BORDERMOVE') -class ObjectBSInvMarkerPanel(Panel): - bl_label = "Niftools BS Inv Marker" - bl_idname = "NIFTOOLS_PT_ObjectBSInvMarker" - - bl_space_type = 'PROPERTIES' - bl_region_type = 'WINDOW' - bl_context = "object" - - # noinspection PyUnusedLocal - @classmethod - def poll(cls, context): - return True - - def draw(self, context): - layout = self.layout - nif_bsinv_props = context.object.niftools_bs_invmarker - - row = layout.row() - if not context.object.niftools_bs_invmarker: - row.operator("object.niftools_bs_invmarker_add", icon='ZOOM_IN', text="") - if context.object.niftools_bs_invmarker: - row.operator("object.niftools_bs_invmarker_remove", icon='ZOOM_OUT', text="") - - col = row.column(align=True) - for i, x in enumerate(nif_bsinv_props): - col.prop(nif_bsinv_props[i], "bs_inv_x", index=i) - col.prop(nif_bsinv_props[i], "bs_inv_y", index=i) - col.prop(nif_bsinv_props[i], "bs_inv_z", index=i) - col.prop(nif_bsinv_props[i], "bs_inv_zoom", index=i) - - classes = [ - ObjectBSInvMarkerPanel, ObjectExtraDataList, ObjectExtraDataType, ObjectExtraData, diff --git a/io_scene_niftools/ui/scene.py b/io_scene_niftools/ui/scene.py index 094f02685..fdb05fcdc 100644 --- a/io_scene_niftools/ui/scene.py +++ b/io_scene_niftools/ui/scene.py @@ -44,13 +44,13 @@ from io_scene_niftools.utils.decorators import register_classes, unregister_classes -class SceneButtonsPanel: +class SceneButtonsPanel(Panel): bl_space_type = 'PROPERTIES' bl_region_type = 'WINDOW' bl_context = "scene" -class ScenePanel(SceneButtonsPanel, Panel): +class ScenePanel(SceneButtonsPanel): bl_label = "Niftools Scene Panel" bl_idname = "NIFTOOLS_PT_scene" @@ -68,7 +68,7 @@ def draw(self, context): row.prop(nif_scene_props, "rootnode") -class SceneVersionInfoPanel(SceneButtonsPanel, Panel): +class SceneVersionInfoPanel(SceneButtonsPanel): bl_label = "Nif Version Info" bl_idname = "NIFTOOLS_PT_scene_version_info" bl_parent_id = "NIFTOOLS_PT_scene" @@ -91,6 +91,33 @@ def draw(self, context): col = flow.column() col.prop(nif_scene_props, "user_version_2") + +class SceneBSInvMarkerPanel(SceneButtonsPanel): + bl_label = "Niftools BS Inv Marker" + bl_idname = "NIFTOOLS_PT_ObjectBSInvMarker" + bl_parent_id = "NIFTOOLS_PT_scene" + + # noinspection PyUnusedLocal + @classmethod + def poll(cls, context): + return True + + def draw(self, context): + layout = self.layout + row = layout.row() + bs_inv = context.scene.niftools_scene.bs_inv + if not bs_inv: + row.operator("scene.bs_inv_marker_add", icon='ZOOM_IN', text="") + else: + row.operator("scene.bs_inv_marker_remove", icon='ZOOM_OUT', text="") + col = row.column(align=True) + for i, x in enumerate(bs_inv): + col.prop(bs_inv[i], "bs_inv_x", index=i) + col.prop(bs_inv[i], "bs_inv_y", index=i) + col.prop(bs_inv[i], "bs_inv_z", index=i) + col.prop(bs_inv[i], "bs_inv_zoom", index=i) + + # class SceneAuthorInfoPanel(SceneButtonsPanel, Panel): # bl_label = "Nif Author Info" # bl_idname = "NIFTOOLS_PT_scene_author_info" @@ -115,6 +142,7 @@ def draw(self, context): classes = [ ScenePanel, SceneVersionInfoPanel, + SceneBSInvMarkerPanel, ] From bb9c5bf4fb60494567bfc03201038348be44667b Mon Sep 17 00:00:00 2001 From: HENDRIX-ZT2 Date: Sat, 2 Apr 2022 16:14:47 +0200 Subject: [PATCH 31/49] Refactor BS Inv marker --- .../modules/nif_export/object/__init__.py | 2 +- .../nif_export/property/object/__init__.py | 27 +++++++++---------- .../nif_import/property/object/__init__.py | 15 ++++++----- io_scene_niftools/properties/scene.py | 16 +++++------ io_scene_niftools/ui/scene.py | 8 +++--- 5 files changed, 33 insertions(+), 35 deletions(-) diff --git a/io_scene_niftools/modules/nif_export/object/__init__.py b/io_scene_niftools/modules/nif_export/object/__init__.py index ea8b4d3c6..9eefbf9de 100644 --- a/io_scene_niftools/modules/nif_export/object/__init__.py +++ b/io_scene_niftools/modules/nif_export/object/__init__.py @@ -117,7 +117,7 @@ def export_root_node(self, root_objects, filebase): # various extra datas object_property = ObjectDataProperty() object_property.export_bsxflags_upb(self.n_root, root_objects) - object_property.export_inventory_marker(self.n_root, root_objects) + object_property.export_inventory_marker(self.n_root) object_property.export_weapon_location(self.n_root, b_obj) types.export_furniture_marker(self.n_root, filebase) return self.n_root diff --git a/io_scene_niftools/modules/nif_export/property/object/__init__.py b/io_scene_niftools/modules/nif_export/property/object/__init__.py index 5bc56c41a..e6385eb9c 100644 --- a/io_scene_niftools/modules/nif_export/property/object/__init__.py +++ b/io_scene_niftools/modules/nif_export/property/object/__init__.py @@ -220,21 +220,18 @@ def has_collision(): # TODO [object][property] Move to object property @staticmethod - def export_inventory_marker(n_root, root_objects): - if bpy.context.scene.niftools_scene.game in ('SKYRIM',): - for root_object in root_objects: - if root_scene.bs_inv_marker: - for extra_item in n_root.extra_data_list: - if isinstance(extra_item, NifFormat.BSInvMarker): - raise NifError("Multiple Items have Inventory marker data only one item may contain this data") - else: - n_extra_list = NifFormat.BSInvMarker() - n_extra_list.name = root_scene.bs_inv_marker[0].name.encode() - n_extra_list.rotation_x = (-root_scene.bs_inv_marker[0].bs_inv_x % (2 * pi)) * 1000 - n_extra_list.rotation_y = (-root_scene.bs_inv_marker[0].bs_inv_y % (2 * pi)) * 1000 - n_extra_list.rotation_z = (-root_scene.bs_inv_marker[0].bs_inv_z % (2 * pi)) * 1000 - n_extra_list.zoom = root_scene.bs_inv_marker[0].bs_inv_zoom - n_root.add_extra_data(n_extra_list) + def export_inventory_marker(n_root): + """Attaches a BSInvMarker to n_root if desired and fill in its values""" + niftools_scene = bpy.context.scene.niftools_scene + if niftools_scene.game in ('SKYRIM',) and niftools_scene.bs_inv: + bs_inv = niftools_scene.bs_inv[0] + n_bs_inv_marker = NifFormat.BSInvMarker() + n_bs_inv_marker.name = bs_inv.name.encode() + n_bs_inv_marker.rotation_x = (-bs_inv.x % (2 * pi)) * 1000 + n_bs_inv_marker.rotation_y = (-bs_inv.y % (2 * pi)) * 1000 + n_bs_inv_marker.rotation_z = (-bs_inv.z % (2 * pi)) * 1000 + n_bs_inv_marker.zoom = bs_inv.zoom + n_root.add_extra_data(n_bs_inv_marker) # TODO [object][property] Move to new object type def export_weapon_location(self, n_root, root_obj): diff --git a/io_scene_niftools/modules/nif_import/property/object/__init__.py b/io_scene_niftools/modules/nif_import/property/object/__init__.py index 6a9822585..47312c871 100644 --- a/io_scene_niftools/modules/nif_import/property/object/__init__.py +++ b/io_scene_niftools/modules/nif_import/property/object/__init__.py @@ -48,9 +48,10 @@ class ObjectProperty: # TODO [property] Add delegate processing def import_extra_datas(self, root_block, b_obj): """ Only to be called on nif and blender root objects! """ + niftools_scene = bpy.context.scene.niftools_scene # store type of root node if isinstance(root_block, NifFormat.BSFadeNode): - bpy.context.scene.niftools_scene.rootnode = 'BSFadeNode' + niftools_scene.rootnode = 'BSFadeNode' # store its flags b_obj.niftools.flags = root_block.flags # store extra datas @@ -81,9 +82,9 @@ def import_extra_datas(self, root_block, b_obj): elif isinstance(n_extra, NifFormat.BSXFlags): b_obj.niftools.bsxflags = n_extra.integer_data elif isinstance(n_extra, NifFormat.BSInvMarker): - b_obj.bs_inv_marker.add() - b_obj.bs_inv_marker[0].name = n_extra.name.decode() - b_obj.bs_inv_marker[0].bs_inv_x = (-n_extra.rotation_x / 1000) % (2 * pi) - b_obj.bs_inv_marker[0].bs_inv_y = (-n_extra.rotation_y / 1000) % (2 * pi) - b_obj.bs_inv_marker[0].bs_inv_z = (-n_extra.rotation_z / 1000) % (2 * pi) - b_obj.bs_inv_marker[0].bs_inv_zoom = n_extra.zoom + bs_inv_item = niftools_scene.bs_inv.add() + bs_inv_item.name = n_extra.name.decode() + bs_inv_item.x = (-n_extra.rotation_x / 1000) % (2 * pi) + bs_inv_item.y = (-n_extra.rotation_y / 1000) % (2 * pi) + bs_inv_item.z = (-n_extra.rotation_z / 1000) % (2 * pi) + bs_inv_item.zoom = n_extra.zoom diff --git a/io_scene_niftools/properties/scene.py b/io_scene_niftools/properties/scene.py index a8c5298b0..051c1b7b4 100644 --- a/io_scene_niftools/properties/scene.py +++ b/io_scene_niftools/properties/scene.py @@ -68,29 +68,29 @@ class BsInventoryMarker(PropertyGroup): default='INV' ) - bs_inv_x: FloatProperty( - name="Inv X value", + x: FloatProperty( + name="X Rotation", description="Rotation of object in inventory around the x axis", default=0, subtype="ANGLE" ) - bs_inv_y: FloatProperty( - name="Inv Y value", + y: FloatProperty( + name="Y Rotation", description="Rotation of object in inventory around the y axis", default=0, subtype="ANGLE" ) - bs_inv_z: FloatProperty( - name="Inv Z value", + z: FloatProperty( + name="Z Rotation", description="Rotation of object in inventory around the z axis", default=0, subtype="ANGLE" ) - bs_inv_zoom: FloatProperty( - name="Inv Zoom Value", + zoom: FloatProperty( + name="Zoom", description="Inventory object Zoom level", default=1 ) diff --git a/io_scene_niftools/ui/scene.py b/io_scene_niftools/ui/scene.py index fdb05fcdc..68215402f 100644 --- a/io_scene_niftools/ui/scene.py +++ b/io_scene_niftools/ui/scene.py @@ -112,10 +112,10 @@ def draw(self, context): row.operator("scene.bs_inv_marker_remove", icon='ZOOM_OUT', text="") col = row.column(align=True) for i, x in enumerate(bs_inv): - col.prop(bs_inv[i], "bs_inv_x", index=i) - col.prop(bs_inv[i], "bs_inv_y", index=i) - col.prop(bs_inv[i], "bs_inv_z", index=i) - col.prop(bs_inv[i], "bs_inv_zoom", index=i) + col.prop(bs_inv[i], "x", index=i) + col.prop(bs_inv[i], "y", index=i) + col.prop(bs_inv[i], "z", index=i) + col.prop(bs_inv[i], "zoom", index=i) # class SceneAuthorInfoPanel(SceneButtonsPanel, Panel): From cff479695222ece07b694fcd7917874c22a8eb24 Mon Sep 17 00:00:00 2001 From: HENDRIX-ZT2 Date: Sat, 2 Apr 2022 18:21:24 +0200 Subject: [PATCH 32/49] Refactor import_root_collision, import_empty --- io_scene_niftools/modules/nif_import/object/types.py | 12 ++++-------- io_scene_niftools/nif_import.py | 3 +-- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/io_scene_niftools/modules/nif_import/object/types.py b/io_scene_niftools/modules/nif_import/object/types.py index 43ebd53e9..4eb645040 100644 --- a/io_scene_niftools/modules/nif_import/object/types.py +++ b/io_scene_niftools/modules/nif_import/object/types.py @@ -47,16 +47,14 @@ class NiTypes: @staticmethod def import_root_collision(n_node, b_obj): - """ Import a RootCollisionNode """ + """ Import a RootCollisionNode, which is usually attached to a root node and holds a NiTriShape""" if isinstance(n_node, NifFormat.RootCollisionNode): b_obj["type"] = "RootCollisionNode" b_obj.name = "RootCollisionNode" - b_obj.display_type = 'BOUNDS' - b_obj.show_wire = True - b_obj.display_bounds_type = 'BOX' - # b_obj.game.use_collision_bounds = True - # b_obj.game.collision_bounds_type = 'TRIANGLE_MESH' b_obj.niftools.flags = n_node.flags + for b_child in b_obj.children: + b_child.display_type = 'WIRE' + @staticmethod def import_range_lod_data(n_node, b_obj, b_children): @@ -97,6 +95,4 @@ def import_billboard(n_node, b_obj): def import_empty(n_block): """Creates and returns a grouping empty.""" b_empty = Object.create_b_obj(n_block, None) - # TODO [flags] Move out to generic processing - b_empty.niftools.flags = n_block.flags return b_empty diff --git a/io_scene_niftools/nif_import.py b/io_scene_niftools/nif_import.py index 1e0fca566..9a47dd0be 100644 --- a/io_scene_niftools/nif_import.py +++ b/io_scene_niftools/nif_import.py @@ -223,11 +223,10 @@ def import_branch(self, n_block, b_armature=None): else: # this is a fallback for a weird bug, when a node is child of a NiLodNode in a skeletal nif b_obj = self.objecthelper.create_b_obj(n_block, None, name=n_name) - b_obj.niftools.flags = n_block.flags - else: # import as an empty b_obj = NiTypes.import_empty(n_block) + b_obj.niftools.flags = n_block.flags # find children b_children = [] From 30d4e7bf88e862a1fdae004756a00028698b0696 Mon Sep 17 00:00:00 2001 From: Candoran2 <45334438+Candoran2@users.noreply.github.com> Date: Fri, 29 Apr 2022 22:15:28 +0200 Subject: [PATCH 33/49] Fixed error that occured when trying to qhull without any vertices. --- io_scene_niftools/modules/nif_import/collision/havok.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/io_scene_niftools/modules/nif_import/collision/havok.py b/io_scene_niftools/modules/nif_import/collision/havok.py index c3f1b3947..f0e11ced8 100644 --- a/io_scene_niftools/modules/nif_import/collision/havok.py +++ b/io_scene_niftools/modules/nif_import/collision/havok.py @@ -269,7 +269,11 @@ def import_bhkconvex_vertices_shape(self, bhk_shape): # find vertices (and fix scale) scaled_verts = [(self.HAVOK_SCALE * n_vert.x, self.HAVOK_SCALE * n_vert.y, self.HAVOK_SCALE * n_vert.z) for n_vert in bhk_shape.vertices] - verts, faces = qhull3d(scaled_verts) + if scaled_verts: + verts, faces = qhull3d(scaled_verts) + else: + verts = [] + faces = [] b_obj = Object.mesh_from_data("convexpoly", verts, faces) radius = bhk_shape.radius * self.HAVOK_SCALE From 085b06afb3d832eb39df0f161ba339d568f1d39e Mon Sep 17 00:00:00 2001 From: Candoran2 <45334438+Candoran2@users.noreply.github.com> Date: Fri, 29 Apr 2022 22:39:18 +0200 Subject: [PATCH 34/49] Temporarily moved penetration depth from collision modifier permeability to niftools collision property to fix error caused by changes in Blender 3.0 and later. --- io_scene_niftools/modules/nif_export/collision/havok.py | 2 +- io_scene_niftools/modules/nif_import/collision/havok.py | 2 +- io_scene_niftools/properties/collision.py | 6 ++++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/io_scene_niftools/modules/nif_export/collision/havok.py b/io_scene_niftools/modules/nif_export/collision/havok.py index aeaca69de..9f36dcdeb 100644 --- a/io_scene_niftools/modules/nif_export/collision/havok.py +++ b/io_scene_niftools/modules/nif_export/collision/havok.py @@ -173,7 +173,7 @@ def export_bhk_rigid_body(self, b_obj, n_col_obj): n_r_body.restitution = b_r_body.restitution n_r_body.max_linear_velocity = b_obj.nifcollision.max_linear_velocity n_r_body.max_angular_velocity = b_obj.nifcollision.max_angular_velocity - n_r_body.penetration_depth = b_obj.collision.permeability + n_r_body.penetration_depth = b_obj.nifcollision.penetration_depth n_r_body.motion_system = b_obj.nifcollision.motion_system n_r_body.deactivator_type = b_obj.nifcollision.deactivator_type n_r_body.solver_deactivation = b_obj.nifcollision.solver_deactivation diff --git a/io_scene_niftools/modules/nif_import/collision/havok.py b/io_scene_niftools/modules/nif_import/collision/havok.py index f0e11ced8..fa4e4be74 100644 --- a/io_scene_niftools/modules/nif_import/collision/havok.py +++ b/io_scene_niftools/modules/nif_import/collision/havok.py @@ -197,7 +197,7 @@ def _import_bhk_rigid_body(self, bhkshape, collision_objs): b_r_body.deactivate_angular_velocity = mathutils.Vector([ang_vel.w, ang_vel.x, ang_vel.y, ang_vel.z]).magnitude # Custom Niftools properties - b_col_obj.collision.permeability = bhkshape.penetration_depth + b_col_obj.nifcollision.penetration_depth = bhkshape.penetration_depth b_col_obj.nifcollision.deactivator_type = NifFormat.DeactivatorType._enumkeys[bhkshape.deactivator_type] b_col_obj.nifcollision.solver_deactivation = NifFormat.SolverDeactivation._enumkeys[bhkshape.solver_deactivation] b_col_obj.nifcollision.max_linear_velocity = bhkshape.max_linear_velocity diff --git a/io_scene_niftools/properties/collision.py b/io_scene_niftools/properties/collision.py index 877a9550a..dd938c22e 100644 --- a/io_scene_niftools/properties/collision.py +++ b/io_scene_niftools/properties/collision.py @@ -82,6 +82,12 @@ class CollisionProperty(PropertyGroup): items=game_specific_col_layer_items, ) + penetration_depth: FloatProperty( + name='Penetration Depth', + description='The maximum allowed penetration for this object.', + default=0.15 + ) + deactivator_type: EnumProperty( name='Deactivator Type', description='Motion deactivation setting', From fbdce870c9daa999e69584eb16d4bb70a242fa5c Mon Sep 17 00:00:00 2001 From: HENDRIX-ZT2 Date: Sat, 21 May 2022 09:28:29 +0200 Subject: [PATCH 35/49] Increase generated image name range for embedded textures - closes #510 --- .../modules/nif_import/property/texture/loader.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/io_scene_niftools/modules/nif_import/property/texture/loader.py b/io_scene_niftools/modules/nif_import/property/texture/loader.py index f395e8708..0e5d50863 100644 --- a/io_scene_niftools/modules/nif_import/property/texture/loader.py +++ b/io_scene_niftools/modules/nif_import/property/texture/loader.py @@ -221,8 +221,8 @@ def import_embedded_texture_source(self, source): def generate_image_name(): """Find a file name (but avoid overwriting)""" n = 0 - while n < 1000: - fn = "image{:0>3d}.dds".format(n) + while n < 10000: + fn = f"image{n:0>4d}.dds" tex = os.path.join(os.path.dirname(NifOp.props.filepath), fn) if not os.path.exists(tex): break From ee5ea9fe6d02e747bc1584be36175235eea999be Mon Sep 17 00:00:00 2001 From: HENDRIX-ZT2 Date: Sun, 22 May 2022 14:36:33 +0200 Subject: [PATCH 36/49] Refactor UV nodes names, add _ after TexCoordIndex --- .../property/nodes_wrapper/__init__.py | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/io_scene_niftools/modules/nif_import/property/nodes_wrapper/__init__.py b/io_scene_niftools/modules/nif_import/property/nodes_wrapper/__init__.py index 01b9efc07..094a1589f 100644 --- a/io_scene_niftools/modules/nif_import/property/nodes_wrapper/__init__.py +++ b/io_scene_niftools/modules/nif_import/property/nodes_wrapper/__init__.py @@ -69,6 +69,10 @@ def __init__(self): self.diffuse_texture = None self.vcol = None + @staticmethod + def uv_node_name(uv_index): + return f"TexCoordIndex_{uv_index}" + def set_uv_map(self, b_texture_node, uv_index=0, reflective=False): """Attaches a vector node describing the desired coordinate transforms to the texture node's UV input.""" if reflective: @@ -76,7 +80,7 @@ def set_uv_map(self, b_texture_node, uv_index=0, reflective=False): self.tree.links.new(uv.outputs[6], b_texture_node.inputs[0]) # use supplied UV maps for everything else, if present else: - uv_name = f"TexCoordIndex{uv_index}" + uv_name = self.uv_node_name(uv_index) existing_node = self.tree.nodes.get(uv_name) if not existing_node: uv = self.tree.nodes.new('ShaderNodeUVMap') @@ -90,29 +94,29 @@ def global_uv_offset_scale(self, x_scale, y_scale, x_offset, y_offset, clamp_x, # get all uv nodes (by name, since we are importing they have the predefined name # and then we don't have to loop through every node uv_nodes = {} - i = 0 + uv_index = 0 while True: - uv_name = "TexCoordIndex" + str(i) + uv_name = self.uv_node_name(uv_index) uv_node = self.tree.nodes.get(uv_name) if uv_node and isinstance(uv_node, bpy.types.ShaderNodeUVMap): - uv_nodes[uv_name] = uv_node - i += 1 + uv_nodes[uv_index] = uv_node + uv_index += 1 else: break clip_texture = clamp_x and clamp_y - for uv_name, uv_node in uv_nodes.items(): + for uv_index, uv_node in uv_nodes.items(): # for each of those, create a new uv output node and relink split_node = self.tree.nodes.new("ShaderNodeSeparateXYZ") - split_node.name = "Separate UV" + uv_name[-1] + split_node.name = f"Separate UV{uv_index}" split_node.label = split_node.name combine_node = self.tree.nodes.new("ShaderNodeCombineXYZ") - combine_node.name = "Combine UV" + uv_name[-1] + combine_node.name = f"Combine UV{uv_index}" combine_node.label = combine_node.name x_node = self.tree.nodes.new("ShaderNodeMath") - x_node.name = "X offset and scale UV" + uv_name[-1] + x_node.name = f"X offset and scale UV{uv_index}" x_node.label = x_node.name x_node.operation = 'MULTIPLY_ADD' # only clamp on the math node when we're not clamping on both directions @@ -124,7 +128,7 @@ def global_uv_offset_scale(self, x_scale, y_scale, x_offset, y_offset, clamp_x, self.tree.links.new(x_node.outputs[0], combine_node.inputs[0]) y_node = self.tree.nodes.new("ShaderNodeMath") - y_node.name = "Y offset and scale UV" + uv_name[-1] + y_node.name = f"Y offset and scale UV{uv_index}" y_node.label = y_node.name y_node.operation = 'MULTIPLY_ADD' y_node.use_clamp = clamp_y and not clip_texture From 58f618b081bce99f8a3d4b43ef78e8a17e55ac71 Mon Sep 17 00:00:00 2001 From: HENDRIX-ZT2 Date: Thu, 26 May 2022 12:04:19 +0200 Subject: [PATCH 37/49] External textures fixes - closes #510, closes #517 - use file name instead of generated name if possible - only extract once per run - fix too long value error for big images --- .../nif_import/property/texture/loader.py | 55 ++++++++++++------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/io_scene_niftools/modules/nif_import/property/texture/loader.py b/io_scene_niftools/modules/nif_import/property/texture/loader.py index 0e5d50863..4c2d05a17 100644 --- a/io_scene_niftools/modules/nif_import/property/texture/loader.py +++ b/io_scene_niftools/modules/nif_import/property/texture/loader.py @@ -36,16 +36,16 @@ # POSSIBILITY OF SUCH DAMAGE. # # ***** END LICENSE BLOCK ***** - from functools import reduce import operator +import traceback import os.path import bpy from pyffi.formats.dds import DdsFormat from pyffi.formats.nif import NifFormat -from io_scene_niftools.modules.nif_import.property import texture +from io_scene_niftools.utils.blocks import safe_decode from io_scene_niftools.utils.singleton import NifOp from io_scene_niftools.utils.logging import NifLog @@ -68,8 +68,9 @@ def get_pixeldata_stream_overide(self): # used in newer nif versions return ''.join(self.pixel_data_matrix) else: - raise ValueError( - "cannot retrieve pixel data when saving pixel format %i as DDS") + raise ValueError(f"cannot retrieve pixel data when saving pixel format {self.pixel_format} as DDS") + + def save_as_dds_override(self, stream): data = DdsFormat.Data() header = data.header @@ -112,7 +113,7 @@ def save_as_dds_override(self, stream): header.caps_1.complex = 1 header.caps_1.texture = 1 header.caps_1.mipmap = 1 - pixeldata.set_value(self.__get_pixeldata_stream()) + # pixeldata.set_value(self.__get_pixeldata_stream()) elif self.pixel_format == NifFormat.PixelFormat.PX_FMT_DXT1: # format used in Megami Tensei: Imagine and Bully SE header.flags.caps = 1 @@ -135,7 +136,7 @@ def save_as_dds_override(self, stream): header.caps_1.complex = 1 header.caps_1.texture = 1 header.caps_1.mipmap = 1 - pixeldata.set_value(self.__get_pixeldata_stream()) + # pixeldata.set_value(self.__get_pixeldata_stream()) elif self.pixel_format in (NifFormat.PixelFormat.PX_FMT_DXT5, NifFormat.PixelFormat.PX_FMT_DXT5_ALT): # format used in Megami Tensei: Imagine @@ -159,12 +160,14 @@ def save_as_dds_override(self, stream): header.caps_1.complex = 1 header.caps_1.texture = 1 header.caps_1.mipmap = 1 - pixeldata.set_value(self.__get_pixeldata_stream()) else: - raise ValueError( - "cannot save pixel format %i as DDS" % self.pixel_format) + raise ValueError(f"cannot save pixel format {self.pixel_format} as DDS") + # pyffi pixeldata can complain about a too long value for perfectly fine data + pixeldata.set_value(b"") data.write(stream) + # so just dump the bytes directly + stream.write(self.__get_pixeldata_stream()) NifFormat.ATextureRenderData.__get_pixeldata_stream = get_pixeldata_stream_overide @@ -174,6 +177,8 @@ def save_as_dds_override(self, stream): class TextureLoader: + external_textures = set() + @staticmethod def load_image(tex_path): """Returns an image or a generated image if none was found""" @@ -184,7 +189,7 @@ def load_image(tex_path): except: NifLog.warn(f"Texture '{name}' not found or not supported and no alternate available") b_image = bpy.data.images.new(name=name, width=1, height=1, alpha=True) - b_image.filepath=tex_path + b_image.filepath = tex_path else: b_image = bpy.data.images[name] return b_image @@ -204,18 +209,26 @@ def import_texture_source(self, source): return self.import_external_source(source) def import_embedded_texture_source(self, source): + # first try to use the actual file name of this NiSourceTexture + tex_name = safe_decode(source.file_name) + tex_path = os.path.join(os.path.dirname(NifOp.props.filepath), tex_name) + # not set, then use generated sequence name + if not tex_name: + tex_path = self.generate_image_name() - fn, tex = self.generate_image_name() + # only save them once per run, obviously only useful if file_name was set + if tex_path not in self.external_textures: + # save embedded texture as dds file + with open(tex_path, "wb") as stream: + try: + NifLog.info(f"Saving embedded texture as {tex_path}") + source.pixel_data.save_as_dds(stream) + except ValueError: + NifLog.warn(f"Pixel format not supported in embedded texture {tex_path}!") + traceback.print_exc() + self.external_textures.add(tex_path) - # save embedded texture as dds file - with open(tex, "wb") as stream: - try: - NifLog.info(f"Saving embedded texture as {tex}") - source.pixel_data.save_as_dds(stream) - except ValueError: - NifLog.warn(f"Pixel format not supported in embedded texture {tex}!") - - return self.load_image(tex) + return self.load_image(tex_path) @staticmethod def generate_image_name(): @@ -227,7 +240,7 @@ def generate_image_name(): if not os.path.exists(tex): break n += 1 - return fn, tex + return tex def import_external_source(self, source): # the texture uses an external image file From 6a74b8dff00e750ccd832b7df890054ab96ccbaa Mon Sep 17 00:00:00 2001 From: HENDRIX-ZT2 Date: Thu, 26 May 2022 12:32:46 +0200 Subject: [PATCH 38/49] get_controller_data more intelligently for anim import --- .../modules/nif_import/animation/__init__.py | 12 ++++++++++++ .../modules/nif_import/animation/material.py | 13 ++++++++----- .../modules/nif_import/animation/morph.py | 2 -- .../modules/nif_import/animation/object.py | 4 +++- .../modules/nif_import/animation/transform.py | 10 ++-------- 5 files changed, 25 insertions(+), 16 deletions(-) diff --git a/io_scene_niftools/modules/nif_import/animation/__init__.py b/io_scene_niftools/modules/nif_import/animation/__init__.py index 2b02c0a26..364d1db47 100644 --- a/io_scene_niftools/modules/nif_import/animation/__init__.py +++ b/io_scene_niftools/modules/nif_import/animation/__init__.py @@ -55,6 +55,18 @@ def __init__(self): # and still be able to access existing actions from this run self.actions = {} + @staticmethod + def get_controller_data(ctrl): + """Return data for ctrl, look in interpolator (for newer games) or directly on ctrl""" + if ctrl.interpolator: + data = ctrl.interpolator.data + else: + data = ctrl.data + # these have their data set as a KeyGroup on data + if isinstance(data, (NifFormat.NiBoolData, NifFormat.NiFloatData, NifFormat.NiPosData)): + return data.data + return data + @staticmethod def get_keys_values(items): """Returns list of times and keys for an array 'items' with key elements having 'time' and 'value' attributes""" diff --git a/io_scene_niftools/modules/nif_import/animation/material.py b/io_scene_niftools/modules/nif_import/animation/material.py index e61b044f1..d563c0b90 100644 --- a/io_scene_niftools/modules/nif_import/animation/material.py +++ b/io_scene_niftools/modules/nif_import/animation/material.py @@ -69,8 +69,9 @@ def import_material_alpha_controller(self, b_material, n_material): NifLog.info("Importing alpha controller") b_mat_action = self.create_action(b_material, "MaterialAction") - interp = self.get_b_interp_from_n_interp(n_ctrl.data.data.interpolation) - times, keys = self.get_keys_values(n_ctrl.data.data.keys) + n_ctrl_data = self.get_controller_data(n_ctrl) + interp = self.get_b_interp_from_n_interp(n_ctrl_data.interpolation) + times, keys = self.get_keys_values(n_ctrl_data.keys) # key needs to be RGB due to current representation in blender keys = [(v, v, v) for v in keys] self.add_keys(b_mat_action, "niftools.emissive_alpha", range(3), n_ctrl.flags, times, keys, interp) @@ -85,8 +86,9 @@ def import_material_color_controller(self, b_material, n_material, b_channel, n_ return NifLog.info(f"Importing material color controller for target color {n_target_color} into blender channel {b_channel}") b_mat_action = self.create_action(b_material, "MaterialAction") - interp = self.get_b_interp_from_n_interp(n_ctrl.data.data.interpolation) - times, keys = self.get_keys_values(n_ctrl.data.data.keys) + n_ctrl_data = self.get_controller_data(n_ctrl) + interp = self.get_b_interp_from_n_interp(n_ctrl_data.interpolation) + times, keys = self.get_keys_values(n_ctrl_data.keys) self.add_keys(b_mat_action, b_channel, range(3), n_ctrl.flags, times, keys, interp) def import_material_uv_controller(self, b_material, n_geom): @@ -97,7 +99,8 @@ def import_material_uv_controller(self, b_material, n_geom): return NifLog.info("Importing UV controller") - if not any(n_uvgroup.keys for n_uvgroup in n_ctrl.data.uv_groups): + n_ctrl_data = self.get_controller_data(n_ctrl) + if not any(n_uvgroup.keys for n_uvgroup in n_ctrl_data.uv_groups): return b_mat_action = self.create_action(b_material.node_tree, "MaterialAction") diff --git a/io_scene_niftools/modules/nif_import/animation/morph.py b/io_scene_niftools/modules/nif_import/animation/morph.py index f9f4f2add..4dbed0911 100644 --- a/io_scene_niftools/modules/nif_import/animation/morph.py +++ b/io_scene_niftools/modules/nif_import/animation/morph.py @@ -117,8 +117,6 @@ def import_egm_morphs(self, b_obj): sk_basis = b_obj.shape_key_add(name="Basis") b_mesh.shape_keys.use_relative = False - # TODO: I'm not entirely sure that changing the morphs to f-strings won't - # TODO: break anything. They _shouldn't_. morphs = ([(morph, f"EGM SYM {i}") for i, morph in enumerate(sym_morphs)] + [(morph, f"EGM ASYM {i}") for i, morph in enumerate(asym_morphs)]) diff --git a/io_scene_niftools/modules/nif_import/animation/object.py b/io_scene_niftools/modules/nif_import/animation/object.py index 8ce5d21be..9799180f8 100644 --- a/io_scene_niftools/modules/nif_import/animation/object.py +++ b/io_scene_niftools/modules/nif_import/animation/object.py @@ -54,5 +54,7 @@ def import_visibility(self, n_node, b_obj): return NifLog.info("Importing vis controller") b_obj_action = self.create_action(b_obj, f"{b_obj.name}-Anim") - times, keys = self.get_keys_values(n_vis_ctrl.data.keys) + + n_ctrl_data = self.get_controller_data(n_vis_ctrl) + times, keys = self.get_keys_values(n_ctrl_data.keys) self.add_keys(b_obj_action, "hide_viewport", (0,), n_vis_ctrl.flags, times, keys, "CONSTANT") diff --git a/io_scene_niftools/modules/nif_import/animation/transform.py b/io_scene_niftools/modules/nif_import/animation/transform.py index 8d8208fb2..2b6eb2b4f 100644 --- a/io_scene_niftools/modules/nif_import/animation/transform.py +++ b/io_scene_niftools/modules/nif_import/animation/transform.py @@ -246,12 +246,8 @@ def import_keyframe_controller(self, n_kfc, b_armature, b_target, b_action_name) b_action = self.create_action(b_target, f"{b_action_name}_{b_target.name}") bone_name = None - # transform controllers (dartgun.nif) - if isinstance(n_kfc, NifFormat.NiTransformController): - if n_kfc.interpolator: - n_kfd = n_kfc.interpolator.data # B-spline curve import - elif isinstance(n_kfc, NifFormat.NiBSplineInterpolator): + if isinstance(n_kfc, NifFormat.NiBSplineInterpolator): # Bsplines are Bezier curves interp = "BEZIER" if isinstance(n_kfc, NifFormat.NiBSplineCompFloatInterpolator): @@ -271,9 +267,7 @@ def import_keyframe_controller(self, n_kfc, b_armature, b_target, b_action_name) elif isinstance(n_kfc, NifFormat.NiMultiTargetTransformController): # not sure what this is used for return - else: - # ZT2 & Fallout - n_kfd = n_kfc.data + n_kfd = self.get_controller_data(n_kfc) # ZT2 - get extrapolation for every kfc if isinstance(n_kfc, NifFormat.NiKeyframeController): flags = n_kfc.flags From b93a623db44b50ba72187e6ffcf461ec352dcadd Mon Sep 17 00:00:00 2001 From: HENDRIX-ZT2 Date: Thu, 26 May 2022 13:30:10 +0200 Subject: [PATCH 39/49] Cleanup embedded tex --- .../nif_import/property/texture/loader.py | 73 ++++++------------- 1 file changed, 21 insertions(+), 52 deletions(-) diff --git a/io_scene_niftools/modules/nif_import/property/texture/loader.py b/io_scene_niftools/modules/nif_import/property/texture/loader.py index 4c2d05a17..5ef8d1302 100644 --- a/io_scene_niftools/modules/nif_import/property/texture/loader.py +++ b/io_scene_niftools/modules/nif_import/property/texture/loader.py @@ -61,9 +61,7 @@ def get_pixeldata_stream_overide(self): elif isinstance(self, NifFormat.NiPixelData): if self.pixel_data: # used in older nif versions - return bytearray().join( - bytearray().join([bytearray([x]) for x in tex]) - for tex in self.pixel_data) + return bytearray(x for tex in self.pixel_data for x in tex) else: # used in newer nif versions return ''.join(self.pixel_data_matrix) @@ -76,20 +74,23 @@ def save_as_dds_override(self, stream): header = data.header pixeldata = data.pixeldata + header.flags.caps = 1 + header.flags.height = 1 + header.flags.width = 1 + header.flags.pixel_format = 1 + header.flags.mipmap_count = 1 + header.mipmap_count = len(self.mipmaps) + header.height = self.mipmaps[0].height + header.width = self.mipmaps[0].width + header.caps_1.complex = 1 + header.caps_1.texture = 1 + header.caps_1.mipmap = 1 # create header, depending on the format if self.pixel_format in (NifFormat.PixelFormat.PX_FMT_RGB8, NifFormat.PixelFormat.PX_FMT_RGBA8): # uncompressed RGB(A) - header.flags.caps = 1 - header.flags.height = 1 - header.flags.width = 1 - header.flags.pixel_format = 1 - header.flags.mipmap_count = 1 header.flags.linear_size = 1 - header.height = self.mipmaps[0].height - header.width = self.mipmaps[0].width header.linear_size = len(self.pixel_data) - header.mipmap_count = len(self.mipmaps) header.pixel_format.flags.rgb = 1 header.pixel_format.bit_count = self.bits_per_pixel if not self.channels: @@ -110,56 +111,24 @@ def save_as_dds_override(self, stream): elif channel.type == NifFormat.ChannelType.CHNL_ALPHA: header.pixel_format.a_mask = mask bit_pos += channel.bits_per_channel - header.caps_1.complex = 1 - header.caps_1.texture = 1 - header.caps_1.mipmap = 1 - # pixeldata.set_value(self.__get_pixeldata_stream()) - elif self.pixel_format == NifFormat.PixelFormat.PX_FMT_DXT1: - # format used in Megami Tensei: Imagine and Bully SE - header.flags.caps = 1 - header.flags.height = 1 - header.flags.width = 1 - header.flags.pixel_format = 1 - header.flags.mipmap_count = 1 - header.flags.linear_size = 0 - header.height = self.mipmaps[0].height - header.width = self.mipmaps[0].width - header.linear_size = 0 - header.mipmap_count = len(self.mipmaps) - header.pixel_format.flags.four_c_c = 1 - header.pixel_format.four_c_c = DdsFormat.FourCC.DXT1 - header.pixel_format.bit_count = 0 - header.pixel_format.r_mask = 0 - header.pixel_format.g_mask = 0 - header.pixel_format.b_mask = 0 - header.pixel_format.a_mask = 0 - header.caps_1.complex = 1 - header.caps_1.texture = 1 - header.caps_1.mipmap = 1 - # pixeldata.set_value(self.__get_pixeldata_stream()) - elif self.pixel_format in (NifFormat.PixelFormat.PX_FMT_DXT5, + elif self.pixel_format in (NifFormat.PixelFormat.PX_FMT_DXT1, + NifFormat.PixelFormat.PX_FMT_DXT5, NifFormat.PixelFormat.PX_FMT_DXT5_ALT): - # format used in Megami Tensei: Imagine - header.flags.caps = 1 - header.flags.height = 1 - header.flags.width = 1 - header.flags.pixel_format = 1 - header.flags.mipmap_count = 1 + # format used in Megami Tensei: Imagine and Bully SE + header.flags.linear_size = 0 - header.height = self.mipmaps[0].height - header.width = self.mipmaps[0].width header.linear_size = 0 - header.mipmap_count = len(self.mipmaps) header.pixel_format.flags.four_c_c = 1 - header.pixel_format.four_c_c = DdsFormat.FourCC.DXT5 + if self.pixel_format in (NifFormat.PixelFormat.PX_FMT_DXT1,): + header.pixel_format.four_c_c = DdsFormat.FourCC.DXT1 + if self.pixel_format in (NifFormat.PixelFormat.PX_FMT_DXT5, + NifFormat.PixelFormat.PX_FMT_DXT5_ALT): + header.pixel_format.four_c_c = DdsFormat.FourCC.DXT5 header.pixel_format.bit_count = 0 header.pixel_format.r_mask = 0 header.pixel_format.g_mask = 0 header.pixel_format.b_mask = 0 header.pixel_format.a_mask = 0 - header.caps_1.complex = 1 - header.caps_1.texture = 1 - header.caps_1.mipmap = 1 else: raise ValueError(f"cannot save pixel format {self.pixel_format} as DDS") From e95efd9e268a33f8072b5979ded7a033edabc253 Mon Sep 17 00:00:00 2001 From: HENDRIX-ZT2 Date: Thu, 26 May 2022 13:52:56 +0200 Subject: [PATCH 40/49] Move BSInvMarker back to object --- .../modules/nif_export/object/__init__.py | 2 +- .../nif_export/property/object/__init__.py | 7 +-- .../nif_import/property/object/__init__.py | 6 ++- io_scene_niftools/operators/geometry.py | 8 ++-- io_scene_niftools/properties/object.py | 39 +++++++++++++++- io_scene_niftools/properties/scene.py | 38 ---------------- io_scene_niftools/ui/object.py | 45 ++++++++++++++----- io_scene_niftools/ui/scene.py | 27 ----------- 8 files changed, 85 insertions(+), 87 deletions(-) diff --git a/io_scene_niftools/modules/nif_export/object/__init__.py b/io_scene_niftools/modules/nif_export/object/__init__.py index 9eefbf9de..4f09a4255 100644 --- a/io_scene_niftools/modules/nif_export/object/__init__.py +++ b/io_scene_niftools/modules/nif_export/object/__init__.py @@ -117,7 +117,7 @@ def export_root_node(self, root_objects, filebase): # various extra datas object_property = ObjectDataProperty() object_property.export_bsxflags_upb(self.n_root, root_objects) - object_property.export_inventory_marker(self.n_root) + object_property.export_inventory_marker(self.n_root, b_obj) object_property.export_weapon_location(self.n_root, b_obj) types.export_furniture_marker(self.n_root, filebase) return self.n_root diff --git a/io_scene_niftools/modules/nif_export/property/object/__init__.py b/io_scene_niftools/modules/nif_export/property/object/__init__.py index e6385eb9c..2644a0725 100644 --- a/io_scene_niftools/modules/nif_export/property/object/__init__.py +++ b/io_scene_niftools/modules/nif_export/property/object/__init__.py @@ -220,11 +220,12 @@ def has_collision(): # TODO [object][property] Move to object property @staticmethod - def export_inventory_marker(n_root): + def export_inventory_marker(n_root, b_obj): """Attaches a BSInvMarker to n_root if desired and fill in its values""" niftools_scene = bpy.context.scene.niftools_scene - if niftools_scene.game in ('SKYRIM',) and niftools_scene.bs_inv: - bs_inv = niftools_scene.bs_inv[0] + bs_inv_store = b_obj.niftools.bs_inv + if niftools_scene.game in ('SKYRIM',) and bs_inv_store: + bs_inv = bs_inv_store[0] n_bs_inv_marker = NifFormat.BSInvMarker() n_bs_inv_marker.name = bs_inv.name.encode() n_bs_inv_marker.rotation_x = (-bs_inv.x % (2 * pi)) * 1000 diff --git a/io_scene_niftools/modules/nif_import/property/object/__init__.py b/io_scene_niftools/modules/nif_import/property/object/__init__.py index 47312c871..a8adfa59e 100644 --- a/io_scene_niftools/modules/nif_import/property/object/__init__.py +++ b/io_scene_niftools/modules/nif_import/property/object/__init__.py @@ -42,6 +42,8 @@ from io_scene_niftools.properties.object import PRN_DICT from math import pi +from io_scene_niftools.utils.blocks import safe_decode + class ObjectProperty: @@ -82,8 +84,8 @@ def import_extra_datas(self, root_block, b_obj): elif isinstance(n_extra, NifFormat.BSXFlags): b_obj.niftools.bsxflags = n_extra.integer_data elif isinstance(n_extra, NifFormat.BSInvMarker): - bs_inv_item = niftools_scene.bs_inv.add() - bs_inv_item.name = n_extra.name.decode() + bs_inv_item = b_obj.niftools.bs_inv.add() + bs_inv_item.name = safe_decode(n_extra.name) bs_inv_item.x = (-n_extra.rotation_x / 1000) % (2 * pi) bs_inv_item.y = (-n_extra.rotation_y / 1000) % (2 * pi) bs_inv_item.z = (-n_extra.rotation_z / 1000) % (2 * pi) diff --git a/io_scene_niftools/operators/geometry.py b/io_scene_niftools/operators/geometry.py index e808b4ba3..7ae69f19e 100644 --- a/io_scene_niftools/operators/geometry.py +++ b/io_scene_niftools/operators/geometry.py @@ -45,12 +45,12 @@ class BsInvMarkerAdd(Operator): """Adds BsInvMarker set""" - bl_idname = "scene.bs_inv_marker_add" + bl_idname = "object.bs_inv_marker_add" bl_label = "Add Inventory Marker" bl_options = {'REGISTER', 'UNDO'} def execute(self, context): - bs_inv = context.scene.niftools_scene.bs_inv + bs_inv = context.object.niftools.bs_inv bs_inv_item = bs_inv.add() bs_inv_item.name = "INV" bs_inv_item.bs_inv_x = 0 @@ -62,12 +62,12 @@ def execute(self, context): class BsInvMarkerRemove(bpy.types.Operator): """Removes BsInvMarker set""" - bl_idname = "scene.bs_inv_marker_remove" + bl_idname = "object.bs_inv_marker_remove" bl_label = "Remove Inventory Marker" bl_options = {'REGISTER', 'UNDO'} def execute(self, context): - bs_inv = context.scene.niftools_scene.bs_inv + bs_inv = context.object.niftools.bs_inv item = len(bs_inv) - 1 bs_inv.remove(item) return {'FINISHED'} diff --git a/io_scene_niftools/properties/object.py b/io_scene_niftools/properties/object.py index 5a5049f32..dd4a651a2 100644 --- a/io_scene_niftools/properties/object.py +++ b/io_scene_niftools/properties/object.py @@ -97,6 +97,40 @@ class ExtraDataStore(PropertyGroup): ) +class BsInventoryMarker(PropertyGroup): + name: StringProperty( + name="", + default='INV' + ) + + x: FloatProperty( + name="X Rotation", + description="Rotation of object in inventory around the x axis", + default=0, + subtype="ANGLE" + ) + + y: FloatProperty( + name="Y Rotation", + description="Rotation of object in inventory around the y axis", + default=0, + subtype="ANGLE" + ) + + z: FloatProperty( + name="Z Rotation", + description="Rotation of object in inventory around the z axis", + default=0, + subtype="ANGLE" + ) + + zoom: FloatProperty( + name="Zoom", + description="Inventory object Zoom level", + default=1 + ) + + class ObjectProperty(PropertyGroup): prn_location: EnumProperty( @@ -146,8 +180,11 @@ class ObjectProperty(PropertyGroup): description="The bone that acts as the root of the SkinInstance", ) + bs_inv: bpy.props.CollectionProperty(type=BsInventoryMarker) + CLASSES = [ + BsInventoryMarker, ExtraData, ExtraDataStore, ObjectProperty, @@ -158,12 +195,10 @@ def register(): register_classes(CLASSES, __name__) bpy.types.Object.niftools = bpy.props.PointerProperty(type=ObjectProperty) - # bpy.types.scene.bs_inv_marker = bpy.props.CollectionProperty(type=BsInventoryMarker) def unregister(): del bpy.types.Object.niftools - # del bpy.types.scene.bs_inv_marker unregister_classes(CLASSES, __name__) diff --git a/io_scene_niftools/properties/scene.py b/io_scene_niftools/properties/scene.py index 051c1b7b4..686946932 100644 --- a/io_scene_niftools/properties/scene.py +++ b/io_scene_niftools/properties/scene.py @@ -62,40 +62,6 @@ def update_version_from_game(self, context): self.user_version_2 = self.USER_VERSION_2.get(self.game, 0) -class BsInventoryMarker(PropertyGroup): - name: StringProperty( - name="", - default='INV' - ) - - x: FloatProperty( - name="X Rotation", - description="Rotation of object in inventory around the x axis", - default=0, - subtype="ANGLE" - ) - - y: FloatProperty( - name="Y Rotation", - description="Rotation of object in inventory around the y axis", - default=0, - subtype="ANGLE" - ) - - z: FloatProperty( - name="Z Rotation", - description="Rotation of object in inventory around the z axis", - default=0, - subtype="ANGLE" - ) - - zoom: FloatProperty( - name="Zoom", - description="Inventory object Zoom level", - default=1 - ) - - class Scene(PropertyGroup): nif_version: IntProperty( name='Version', @@ -161,11 +127,8 @@ class Scene(PropertyGroup): default=0.1, min=0.001, max=100.0, precision=2) - bs_inv: bpy.props.CollectionProperty(type=BsInventoryMarker) - CLASSES = [ - BsInventoryMarker, Scene ] @@ -174,7 +137,6 @@ def register(): register_classes(CLASSES, __name__) bpy.types.Scene.niftools_scene = bpy.props.PointerProperty(type=Scene) - # bpy.types.Scene.niftools_bs_inv = bpy.props.CollectionProperty(type=BsInventoryMarker) def unregister(): diff --git a/io_scene_niftools/ui/object.py b/io_scene_niftools/ui/object.py index 3a8c2748e..9cbf9e6d1 100644 --- a/io_scene_niftools/ui/object.py +++ b/io_scene_niftools/ui/object.py @@ -42,13 +42,15 @@ from io_scene_niftools.utils.decorators import register_classes, unregister_classes -class ObjectPanel(Panel): - bl_label = "Niftools Object Property" - bl_idname = "NIFTOOLS_PT_ObjectPanel" - +class ObjectButtonsPanel(Panel): bl_space_type = 'PROPERTIES' bl_region_type = 'WINDOW' bl_context = "object" + + +class ObjectPanel(ObjectButtonsPanel): + bl_label = "Niftools Object Property" + bl_idname = "NIFTOOLS_PT_ObjectPanel" # noinspection PyUnusedLocal @classmethod @@ -73,14 +75,10 @@ def draw(self, context): row.prop_search(nif_obj_props, "skeleton_root", parent.data, "bones") -class ObjectExtraData(Panel): +class ObjectExtraData(ObjectButtonsPanel): bl_label = "Niftools Object Extra Data" bl_idname = "NIFTOOLS_PT_ObjectExtraDataPanel" - bl_space_type = 'PROPERTIES' - bl_region_type = 'WINDOW' - bl_context = "object" - # noinspection PyUnusedLocal @classmethod def poll(cls, context): @@ -138,11 +136,38 @@ def draw_item(self, context, layout, data, item, icon, active_data, active_propn split.prop(item, "data", text="", emboss=False, translate=False, icon='BORDERMOVE') +class ObjectBSInvMarkerPanel(ObjectButtonsPanel): + bl_label = "Niftools BS Inv Marker" + bl_idname = "NIFTOOLS_PT_ObjectBSInvMarker" + bl_parent_id = "NIFTOOLS_PT_ObjectPanel" + + # noinspection PyUnusedLocal + @classmethod + def poll(cls, context): + return True + + def draw(self, context): + layout = self.layout + row = layout.row() + bs_inv = context.object.niftools.bs_inv + if not bs_inv: + row.operator("object.bs_inv_marker_add", icon='ZOOM_IN', text="") + else: + row.operator("object.bs_inv_marker_remove", icon='ZOOM_OUT', text="") + col = row.column(align=True) + for i, x in enumerate(bs_inv): + col.prop(bs_inv[i], "x", index=i) + col.prop(bs_inv[i], "y", index=i) + col.prop(bs_inv[i], "z", index=i) + col.prop(bs_inv[i], "zoom", index=i) + + classes = [ ObjectExtraDataList, ObjectExtraDataType, ObjectExtraData, - ObjectPanel + ObjectPanel, + ObjectBSInvMarkerPanel, ] diff --git a/io_scene_niftools/ui/scene.py b/io_scene_niftools/ui/scene.py index 68215402f..cfc603594 100644 --- a/io_scene_niftools/ui/scene.py +++ b/io_scene_niftools/ui/scene.py @@ -92,32 +92,6 @@ def draw(self, context): col.prop(nif_scene_props, "user_version_2") -class SceneBSInvMarkerPanel(SceneButtonsPanel): - bl_label = "Niftools BS Inv Marker" - bl_idname = "NIFTOOLS_PT_ObjectBSInvMarker" - bl_parent_id = "NIFTOOLS_PT_scene" - - # noinspection PyUnusedLocal - @classmethod - def poll(cls, context): - return True - - def draw(self, context): - layout = self.layout - row = layout.row() - bs_inv = context.scene.niftools_scene.bs_inv - if not bs_inv: - row.operator("scene.bs_inv_marker_add", icon='ZOOM_IN', text="") - else: - row.operator("scene.bs_inv_marker_remove", icon='ZOOM_OUT', text="") - col = row.column(align=True) - for i, x in enumerate(bs_inv): - col.prop(bs_inv[i], "x", index=i) - col.prop(bs_inv[i], "y", index=i) - col.prop(bs_inv[i], "z", index=i) - col.prop(bs_inv[i], "zoom", index=i) - - # class SceneAuthorInfoPanel(SceneButtonsPanel, Panel): # bl_label = "Nif Author Info" # bl_idname = "NIFTOOLS_PT_scene_author_info" @@ -142,7 +116,6 @@ def draw(self, context): classes = [ ScenePanel, SceneVersionInfoPanel, - SceneBSInvMarkerPanel, ] From 9d8b2cb73beae0a56e0b60733b89f7ad136465ed Mon Sep 17 00:00:00 2001 From: HENDRIX-ZT2 Date: Thu, 26 May 2022 19:37:31 +0200 Subject: [PATCH 41/49] Preliminary support for texture transform import - #514 just adds the node but doesn't link it --- .../modules/nif_import/animation/material.py | 92 +++++++++++++++---- io_scene_niftools/utils/math.py | 10 ++ 2 files changed, 84 insertions(+), 18 deletions(-) diff --git a/io_scene_niftools/modules/nif_import/animation/material.py b/io_scene_niftools/modules/nif_import/animation/material.py index d563c0b90..d58ed5fac 100644 --- a/io_scene_niftools/modules/nif_import/animation/material.py +++ b/io_scene_niftools/modules/nif_import/animation/material.py @@ -44,6 +44,11 @@ from io_scene_niftools.utils.singleton import NifOp from io_scene_niftools.utils.logging import NifLog +# indices for blender ShaderNodeMapping node +LOC_DP = 1 +SCALE_DP = 3 +MAPPING = "ShaderNodeMapping" + class MaterialAnimation(Animation): @@ -59,7 +64,8 @@ def import_material_controllers(self, n_geom, b_material): ("specular_color", NifFormat.TargetColor.TC_SPECULAR)): self.import_material_color_controller(b_material, n_material, b_channel, n_target_color) - self.import_material_uv_controller(b_material, n_geom) + self.import_uv_controller(b_material, n_geom) + self.import_tex_transform_controller(b_material, n_geom) def import_material_alpha_controller(self, b_material, n_material): # find alpha controller @@ -91,7 +97,7 @@ def import_material_color_controller(self, b_material, n_material, b_channel, n_ times, keys = self.get_keys_values(n_ctrl_data.keys) self.add_keys(b_mat_action, b_channel, range(3), n_ctrl.flags, times, keys, interp) - def import_material_uv_controller(self, b_material, n_geom): + def import_uv_controller(self, b_material, n_geom): """Import UV controller data as a mapping node with animated values.""" # search for the block n_ctrl = math.find_controller(n_geom, NifFormat.NiUVController) @@ -103,17 +109,79 @@ def import_material_uv_controller(self, b_material, n_geom): if not any(n_uvgroup.keys for n_uvgroup in n_ctrl_data.uv_groups): return - b_mat_action = self.create_action(b_material.node_tree, "MaterialAction") + b_mat_action, transform = self.insert_mapping_node(b_material) + + # loc U, loc V, scale U, scale V + dtypes = (LOC_DP, 0), (LOC_DP, 1), (SCALE_DP, 0), (SCALE_DP, 1) + for n_uvgroup, (data_path, array_ind) in zip(n_ctrl.data.uv_groups, dtypes): + if n_uvgroup.keys: + interp = self.get_b_interp_from_n_interp(n_uvgroup.interpolation) + times, keys = self.get_keys_values(n_uvgroup.keys) + # UV V coordinate is inverted in blender + if 1 == LOC_DP and array_ind == 1: + keys = [-key for key in keys] + self.add_keys(b_mat_action, f'nodes["{transform.name}"].inputs[{data_path}].default_value', (array_ind,), n_ctrl.flags, times, keys, interp) + def import_tex_transform_controller(self, b_material, n_geom): + """Import UV controller data as a mapping node with animated values.""" + # search for the block + n_tex_prop = math.find_property(n_geom, NifFormat.NiTexturingProperty) + if not n_tex_prop: + return + for n_ctrl in math.controllers_iter(n_tex_prop, NifFormat.NiTextureTransformController): + NifLog.info("Importing Texture Transform controller") + + n_ctrl_data = self.get_controller_data(n_ctrl) + if not n_ctrl_data.keys: + return + # todo [material] get the mapping from enum to node, and standardize texture slot names everywhere + # the whole node logic needs to be refactored to seamlessly integrate this + # get tex slot + tex_slot = n_ctrl.texture_slot + times, keys = self.get_keys_values(n_ctrl_data.keys) + # get operation + operation = n_ctrl.operation + if operation == NifFormat.TexTransform.TT_TRANSLATE_U: + data_path = LOC_DP + array_ind = 0 + elif operation == NifFormat.TexTransform.TT_TRANSLATE_V: + data_path = LOC_DP + array_ind = 1 + # UV V coordinate is inverted in blender + keys = [-key for key in keys] + elif operation == NifFormat.TexTransform.TT_ROTATE: + # not sure, need example nif + NifLog.warn("Rotation in Texture Transform is not supported") + return + elif operation == NifFormat.TexTransform.TT_SCALE_U: + data_path = SCALE_DP + array_ind = 0 + elif operation == NifFormat.TexTransform.TT_SCALE_V: + data_path = SCALE_DP + array_ind = 1 + + # in example nif, no node tree exists, so this doesn't link the transform node + b_mat_action, transform = self.insert_mapping_node(b_material) + + interp = self.get_b_interp_from_n_interp(n_ctrl_data.interpolation) + self.add_keys(b_mat_action, f'nodes["{transform.name}"].inputs[{data_path}].default_value', (array_ind,), n_ctrl.flags, times, keys, interp) + + def insert_mapping_node(self, b_material): + b_mat_action = self.create_action(b_material.node_tree, "MaterialAction") tree = b_material.node_tree - transform = tree.nodes.new('ShaderNodeMapping') + # reuse mapping node if one had been added before + for node in tree.nodes: + if node.type == "MAPPING": + transform = node + break + else: + transform = tree.nodes.new(MAPPING) # get previous links used_links = [] for link in tree.links: # get uv nodes if link.from_node.type == "UVMAP": used_links.append(link) - # link the node between previous uv node and texture node for link in used_links: from_socket = link.from_socket @@ -121,17 +189,5 @@ def import_material_uv_controller(self, b_material, n_geom): tree.links.remove(link) tree.links.new(from_socket, transform.inputs[0]) tree.links.new(transform.outputs[0], to_socket) - - # loc U, loc V, scale U, scale V - LOC_DP = 1 - SCALE_DP = 3 - dtypes = (LOC_DP, 0), (LOC_DP, 1), (SCALE_DP, 0), (SCALE_DP, 1) - for n_uvgroup, (data_path, array_ind) in zip(n_ctrl.data.uv_groups, dtypes): - if n_uvgroup.keys: - interp = self.get_b_interp_from_n_interp(n_uvgroup.interpolation) - times, keys = self.get_keys_values(n_uvgroup.keys) - # UV V coordinate is inverted in blender - if 1 == LOC_DP and array_ind == 1: - keys = [-key for key in keys] - self.add_keys(b_mat_action, f'nodes["{transform.name}"].inputs[{data_path}].default_value', (array_ind,), n_ctrl.flags, times, keys, interp) + return b_mat_action, transform diff --git a/io_scene_niftools/utils/math.py b/io_scene_niftools/utils/math.py index 74c10735a..70edad10b 100644 --- a/io_scene_niftools/utils/math.py +++ b/io_scene_niftools/utils/math.py @@ -198,6 +198,16 @@ def find_controller(n_block, controller_type): ctrl = ctrl.next_controller +def controllers_iter(n_block, controller_type): + """Find a controller.""" + ctrl = n_block.controller + while ctrl: + if isinstance(ctrl, controller_type): + if ctrl.data or ctrl.interpolator: + yield ctrl + ctrl = ctrl.next_controller + + def find_extra(n_block, extratype): # TODO: 3.0 - Optimise From 9c7eba0cbb608d2fdad2029c416b635da97f0728 Mon Sep 17 00:00:00 2001 From: HENDRIX-ZT2 Date: Fri, 27 May 2022 07:30:52 +0200 Subject: [PATCH 42/49] Naminc conventions for MorphAnimation as per review --- .../modules/nif_import/animation/morph.py | 58 +++++++++---------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/io_scene_niftools/modules/nif_import/animation/morph.py b/io_scene_niftools/modules/nif_import/animation/morph.py index 4dbed0911..59d83e26b 100644 --- a/io_scene_niftools/modules/nif_import/animation/morph.py +++ b/io_scene_niftools/modules/nif_import/animation/morph.py @@ -57,55 +57,55 @@ def __init__(self): def import_morph_controller(self, n_node, b_obj): """Import NiGeomMorpherController as shape keys for blender object.""" - n_morphCtrl = math.find_controller(n_node, NifFormat.NiGeomMorpherController) - if n_morphCtrl: + n_morph_ctrl = math.find_controller(n_node, NifFormat.NiGeomMorpherController) + if n_morph_ctrl: NifLog.debug("NiGeomMorpherController processed") b_mesh = b_obj.data - morphData = n_morphCtrl.data - if morphData.num_morphs: + morph_data = n_morph_ctrl.data + if morph_data.num_morphs: # get name for base key - morph_data = morphData.morphs[0] - keyname = morph_data.frame_name.decode() - if not keyname: - keyname = 'Base' + morph = morph_data.morphs[0] + key_name = morph.frame_name.decode() + if not key_name: + key_name = 'Base' # insert base key, using relative keys - sk_basis = b_obj.shape_key_add(name=keyname) + sk_basis = b_obj.shape_key_add(name=key_name) # get base vectors and import all morphs - baseverts = morph_data.vectors + base_verts = morph.vectors shape_action = self.create_action(b_obj.data.shape_keys, f"{b_obj.name}-Morphs") - for morph_i in range(1, morphData.num_morphs): - morph_data = morphData.morphs[morph_i] + for morph_i in range(1, morph_data.num_morphs): + morph = morph_data.morphs[morph_i] # get name for key - keyname = morph_data.frame_name.decode() - if not keyname: - keyname = f'Key {morph_i}' - NifLog.info(f"Inserting key '{keyname}'") + key_name = morph.frame_name.decode() + if not key_name: + key_name = f'Key {morph_i}' + NifLog.info(f"Inserting key '{key_name}'") # get vectors - morph_verts = morph_data.vectors - self.morph_mesh(b_mesh, baseverts, morph_verts) - shape_key = b_obj.shape_key_add(name=keyname, from_mix=False) + morph_verts = morph.vectors + self.morph_mesh(b_mesh, base_verts, morph_verts) + shape_key = b_obj.shape_key_add(name=key_name, from_mix=False) # find the keys - # older versions store keys in the morphData + # older versions store keys in the morph_data # newer versions store keys in the controller - if not morph_data.keys: + if not morph.keys: try: - if n_morphCtrl.interpolators: - morph_data = n_morphCtrl.interpolators[morph_i].data.data - elif n_morphCtrl.interpolator_weights: - morph_data = n_morphCtrl.interpolator_weights[morph_i].interpolator.data.data + if n_morph_ctrl.interpolators: + morph = n_morph_ctrl.interpolators[morph_i].data.data + elif n_morph_ctrl.interpolator_weights: + morph = n_morph_ctrl.interpolator_weights[morph_i].interpolator.data.data except KeyError: - NifLog.info(f"Unsupported interpolator '{type(n_morphCtrl.interpolator_weights[morph_i].interpolator)}'") + NifLog.info(f"Unsupported interpolator '{type(n_morph_ctrl.interpolator_weights[morph_i].interpolator)}'") continue # get the interpolation mode - interp = self.get_b_interp_from_n_interp(morph_data.interpolation) - times, keys = self.get_keys_values(morph_data.keys) - self.add_keys(shape_action, "value", (0,), n_morphCtrl.flags, times, keys, interp, key_name=shape_key.name) + interp = self.get_b_interp_from_n_interp(morph.interpolation) + times, keys = self.get_keys_values(morph.keys) + self.add_keys(shape_action, "value", (0,), n_morph_ctrl.flags, times, keys, interp, key_name=shape_key.name) def import_egm_morphs(self, b_obj): """Import all EGM morphs as shape keys for blender object.""" From e31eaa6f9cb9e53b6569e4b4ecefe2a7c49f6e18 Mon Sep 17 00:00:00 2001 From: HENDRIX-ZT2 Date: Fri, 27 May 2022 08:23:53 +0200 Subject: [PATCH 43/49] Fix frozen import with multiple tex transform controllers --- .../modules/nif_import/animation/__init__.py | 40 +++++++++---------- .../modules/nif_import/animation/material.py | 8 ++-- 2 files changed, 21 insertions(+), 27 deletions(-) diff --git a/io_scene_niftools/modules/nif_import/animation/__init__.py b/io_scene_niftools/modules/nif_import/animation/__init__.py index 364d1db47..e3fd22246 100644 --- a/io_scene_niftools/modules/nif_import/animation/__init__.py +++ b/io_scene_niftools/modules/nif_import/animation/__init__.py @@ -169,28 +169,24 @@ def add_keys(self, b_action, key_type, key_range, flags, times, keys, interp, bo ipo = bpy.types.Keyframe.bl_rna.properties['interpolation'].enum_items[interp].value interpolations = [ipo for _ in range(len(samples))] # import the keys - fcurves = self.create_fcurves(b_action, key_type, key_range, flags, bone_name, key_name) - if len(key_range) == 1: - # flat key - make it zippable - key_per_fcurve = [keys] - else: - key_per_fcurve = zip(*keys) - for fcurve, fcu_keys in zip(fcurves, key_per_fcurve): - # add new points - fcurve.keyframe_points.add(count=len(fcu_keys)) - # populate points with keys for this curve - fcurve.keyframe_points.foreach_set("co", [x for co in zip(samples, fcu_keys) for x in co]) - fcurve.keyframe_points.foreach_set("interpolation", interpolations) - # update - fcurve.update() - - def add_key(self, fcurves, t, key, interp): - """ - Add a key (len=n) to a set of fcurves (len=n) at the given frame. Set the key's interpolation to interp. - """ - frame = round(t * self.fps) - for fcurve, k in zip(fcurves, key): - fcurve.keyframe_points.insert(frame, k).interpolation = interp + try: + fcurves = self.create_fcurves(b_action, key_type, key_range, flags, bone_name, key_name) + if len(key_range) == 1: + # flat key - make it zippable + key_per_fcurve = [keys] + else: + key_per_fcurve = zip(*keys) + for fcurve, fcu_keys in zip(fcurves, key_per_fcurve): + # add new points + fcurve.keyframe_points.add(count=len(fcu_keys)) + # populate points with keys for this curve + fcurve.keyframe_points.foreach_set("co", [x for co in zip(samples, fcu_keys) for x in co]) + fcurve.keyframe_points.foreach_set("interpolation", interpolations) + # update + fcurve.update() + except RuntimeError: + # blender throws F-Curve ... already exists in action ... + NifLog.warn(f"Could not add fcurve '{key_type}' to '{b_action.name}', already added before?") # import animation groups def import_text_keys(self, n_block, b_action): diff --git a/io_scene_niftools/modules/nif_import/animation/material.py b/io_scene_niftools/modules/nif_import/animation/material.py index d58ed5fac..bb46dcb71 100644 --- a/io_scene_niftools/modules/nif_import/animation/material.py +++ b/io_scene_niftools/modules/nif_import/animation/material.py @@ -167,15 +167,13 @@ def import_tex_transform_controller(self, b_material, n_geom): self.add_keys(b_mat_action, f'nodes["{transform.name}"].inputs[{data_path}].default_value', (array_ind,), n_ctrl.flags, times, keys, interp) def insert_mapping_node(self, b_material): - b_mat_action = self.create_action(b_material.node_tree, "MaterialAction") + b_mat_action = self.create_action(b_material.node_tree, f"{b_material.name}-MaterialNodesAction") tree = b_material.node_tree # reuse mapping node if one had been added before for node in tree.nodes: if node.type == "MAPPING": - transform = node - break - else: - transform = tree.nodes.new(MAPPING) + return b_mat_action, node + transform = tree.nodes.new(MAPPING) # get previous links used_links = [] for link in tree.links: From a8d6e09a7870fd857dc7c63d2a4e65f261e300ac Mon Sep 17 00:00:00 2001 From: HENDRIX-ZT2 Date: Fri, 27 May 2022 12:11:50 +0200 Subject: [PATCH 44/49] Cleanup & pull out skin partition code --- .../nif_export/geometry/mesh/__init__.py | 149 +++++++++--------- .../modules/nif_export/object/__init__.py | 2 +- .../geometry/trishape/test_trishape.py | 2 +- 3 files changed, 80 insertions(+), 73 deletions(-) diff --git a/io_scene_niftools/modules/nif_export/geometry/mesh/__init__.py b/io_scene_niftools/modules/nif_export/geometry/mesh/__init__.py index 8d0d753cd..211412f16 100644 --- a/io_scene_niftools/modules/nif_export/geometry/mesh/__init__.py +++ b/io_scene_niftools/modules/nif_export/geometry/mesh/__init__.py @@ -69,7 +69,7 @@ def export_tri_shapes(self, b_obj, n_parent, n_root, trishape_name=None): n_parent, as NiTriShape and NiTriShapeData blocks, possibly along with some NiTexturingProperty, NiSourceTexture, NiMaterialProperty, and NiAlphaProperty blocks. We export one - trishape block per mesh material. We also export vertex weights. + n_geom block per mesh material. We also export vertex weights. The parameter trishape_name passes on the name for meshes that should be exported as a single mesh. @@ -111,7 +111,7 @@ def export_tri_shapes(self, b_obj, n_parent, n_root, trishape_name=None): # Non-textured materials, vertex colors are used to color the mesh # Textured materials, they represent lighting details - # let's now export one trishape for every mesh material + # let's now export one n_geom for every mesh material # TODO [material] needs refactoring - move material, texture, etc. to separate function for materialIndex, b_mat in enumerate(mesh_materials): @@ -121,53 +121,53 @@ def export_tri_shapes(self, b_obj, n_parent, n_root, trishape_name=None): if (game == 'SKYRIM') and b_mat.niftools_shader.slsf_1_model_space_normals: mesh_hasnormals = False # for proper lighting - # create a trishape block + # create a n_geom block if not NifOp.props.stripify: - trishape = block_store.create_block("NiTriShape", b_obj) + n_geom = block_store.create_block("NiTriShape", b_obj) else: - trishape = block_store.create_block("NiTriStrips", b_obj) + n_geom = block_store.create_block("NiTriStrips", b_obj) # fill in the NiTriShape's non-trivial values if isinstance(n_parent, NifFormat.RootCollisionNode): - trishape.name = "" + n_geom.name = "" else: if not trishape_name: if n_parent.name: - trishape.name = "Tri " + n_parent.name.decode() + n_geom.name = "Tri " + n_parent.name.decode() else: - trishape.name = "Tri " + b_obj.name.decode() + n_geom.name = "Tri " + b_obj.name.decode() else: - trishape.name = trishape_name + n_geom.name = trishape_name # multimaterial meshes: add material index (Morrowind's child naming convention) if len(mesh_materials) > 1: - trishape.name = f"{trishape.name.decode()}: {materialIndex}" + n_geom.name = f"{n_geom.name.decode()}: {materialIndex}" else: - trishape.name = block_store.get_full_name(trishape) + n_geom.name = block_store.get_full_name(n_geom) - self.set_mesh_flags(b_obj, trishape) + self.set_mesh_flags(b_obj, n_geom) # extra shader for Sid Meier's Railroads if game == 'SID_MEIER_S_RAILROADS': - trishape.has_shader = True - trishape.shader_name = "RRT_NormalMap_Spec_Env_CubeLight" - trishape.unknown_integer = -1 # default + n_geom.has_shader = True + n_geom.shader_name = "RRT_NormalMap_Spec_Env_CubeLight" + n_geom.unknown_integer = -1 # default # if we have an animation of a blender mesh # an intermediate NiNode has been created which holds this b_obj's transform - # the trishape itself then needs identity transform (default) + # the n_geom itself then needs identity transform (default) if trishape_name is not None: # only export the bind matrix on trishapes that were not animated - math.set_object_matrix(b_obj, trishape) + math.set_object_matrix(b_obj, n_geom) # check if there is a parent if n_parent: - # add texture effect block (must be added as parent of the trishape) + # add texture effect block (must be added as parent of the n_geom) n_parent = self.export_texture_effect(n_parent, b_mat) # refer to this mesh in the parent's children list - n_parent.add_child(trishape) + n_parent.add_child(n_geom) - self.object_property.export_properties(b_obj, b_mat, trishape) + self.object_property.export_properties(b_obj, b_mat, n_geom) # -> now comes the real export @@ -217,7 +217,7 @@ def export_tri_shapes(self, b_obj, n_parent, n_root, trishape_name=None): for poly in eval_mesh.polygons: - # does the face belong to this trishape? + # does the face belong to this n_geom? if b_mat is not None and poly.material_index != materialIndex: # we have a material but this face has another material, so skip continue @@ -321,11 +321,11 @@ def export_tri_shapes(self, b_obj, n_parent, n_root, trishape_name=None): continue # m_4444x: skip 'empty' material indices # add NiTriShape's data - if isinstance(trishape, NifFormat.NiTriShape): + if isinstance(n_geom, NifFormat.NiTriShape): tridata = block_store.create_block("NiTriShapeData", b_obj) else: tridata = block_store.create_block("NiTriStripsData", b_obj) - trishape.data = tridata + n_geom.data = tridata # data tridata.num_vertices = len(vertex_positions) @@ -376,7 +376,7 @@ def export_tri_shapes(self, b_obj, n_parent, n_root, trishape_name=None): bitangents = bitangent_signs * np.cross(normals, tangents) # B_tan: +d(B_u), B_bit: +d(B_v) and N_tan: +d(N_v), N_bit: +d(N_u) # moreover, N_v = 1 - B_v, so d(B_v) = - d(N_v), therefore N_tan = -B_bit and N_bit = B_tan - self.add_defined_tangents(trishape, + self.add_defined_tangents(n_geom, tangents=-bitangents, bitangents=tangents, as_extra_data=(game == 'OBLIVION')) @@ -392,7 +392,7 @@ def export_tri_shapes(self, b_obj, n_parent, n_root, trishape_name=None): if boneinfluences: # yes we have skinning! # create new skinning instance block and link it skininst, skindata = self.create_skin_inst_data(b_obj, b_obj_armature, polygon_parts) - trishape.skin_instance = skininst + n_geom.skin_instance = skininst # Vertex weights, find weights and normalization factors vert_list = {} @@ -449,53 +449,17 @@ def export_tri_shapes(self, b_obj, n_parent, n_root, trishape_name=None): vert_added[vert_index] = True # add bone as influence, but only if there were actually any vertices influenced by the bone if vert_weights: - trishape.add_bone(bone_block, vert_weights) + n_geom.add_bone(bone_block, vert_weights) # update bind position skinning data - # trishape.update_bind_position() - # override pyffi trishape.update_bind_position with custom one that is relative to the nif root - self.update_bind_position(trishape, n_root, b_obj_armature) + # n_geom.update_bind_position() + # override pyffi n_geom.update_bind_position with custom one that is relative to the nif root + self.update_bind_position(n_geom, n_root, b_obj_armature) # calculate center and radius for each skin bone data block - trishape.update_skin_center_radius() - - if NifData.data.version >= 0x04020100 and NifOp.props.skin_partition: - NifLog.info("Creating skin partition") - - # warn on bad config settings - if game == 'OBLIVION': - if NifOp.props.pad_bones: - NifLog.warn("Using padbones on Oblivion export. Disable the pad bones option to get higher quality skin partitions.") - if game in ('OBLIVION', 'FALLOUT_3'): - if NifOp.props.max_bones_per_partition < 18: - NifLog.warn("Using less than 18 bones per partition on Oblivion/Fallout 3 export." - "Set it to 18 to get higher quality skin partitions.") - elif NifOp.props.max_bones_per_partition > 18: - NifLog.warn("Using more than 18 bones per partition on Oblivion/Fallout 3 export." - "This may cause issues in-game.") - if game == 'SKYRIM': - if NifOp.props.max_bones_per_partition < 24: - NifLog.warn("Using less than 24 bones per partition on Skyrim export." - "Set it to 24 to get higher quality skin partitions.") - # Skyrim Special Edition has a limit of 80 bones per partition, but export is not yet supported - - part_order = [getattr(NifFormat.BSDismemberBodyPartType, face_map.name, None) for face_map in b_obj.face_maps] - part_order = [body_part for body_part in part_order if body_part is not None] - # override pyffi trishape.update_skin_partition with custom one (that allows ordering) - trishape.update_skin_partition = update_skin_partition.__get__(trishape) - lostweight = trishape.update_skin_partition( - maxbonesperpartition=NifOp.props.max_bones_per_partition, - maxbonespervertex=NifOp.props.max_bones_per_vertex, - stripify=NifOp.props.stripify, - stitchstrips=NifOp.props.stitch_strips, - padbones=NifOp.props.pad_bones, - triangles=triangles, - trianglepartmap=bodypartfacemap, - maximize_bone_sharing=(game in ('FALLOUT_3', 'SKYRIM')), - part_sort_order=part_order) - - if lostweight > NifOp.props.epsilon: - NifLog.warn(f"Lost {lostweight:f} in vertex weights while creating a skin partition for Blender object '{b_obj.name}' (nif block '{trishape.name}')") + n_geom.update_skin_center_radius() + + self.export_skin_partition(b_obj, bodypartfacemap, triangles, n_geom) # clean up del vert_weights @@ -506,8 +470,51 @@ def export_tri_shapes(self, b_obj, n_parent, n_root, trishape_name=None): # export EGM or NiGeomMorpherController animation # shape keys are only present on the raw, unevaluated mesh - self.morph_anim.export_morph(b_mesh, trishape, vertex_map) - return trishape + self.morph_anim.export_morph(b_mesh, n_geom, vertex_map) + return n_geom + + def export_skin_partition(self, b_obj, bodypartfacemap, triangles, n_geom): + """Attaches a skin partition to n_geom if needed""" + game = bpy.context.scene.niftools_scene.game + if NifData.data.version >= 0x04020100 and NifOp.props.skin_partition: + NifLog.info("Creating skin partition") + + # warn on bad config settings + if game == 'OBLIVION': + if NifOp.props.pad_bones: + NifLog.warn( + "Using padbones on Oblivion export. Disable the pad bones option to get higher quality skin partitions.") + + # Skyrim Special Edition has a limit of 80 bones per partition, but export is not yet supported + bones_per_partition_lut = {"OBLIVION": 18, "FALLOUT_3": 18, "SKYRIM": 24} + rec_bones = bones_per_partition_lut.get(game, None) + if rec_bones is not None: + if NifOp.props.max_bones_per_partition < rec_bones: + NifLog.warn(f"Using less than {rec_bones} bones per partition on {game} export." + f"Set it to {rec_bones} to get higher quality skin partitions.") + elif NifOp.props.max_bones_per_partition > rec_bones: + NifLog.warn(f"Using more than {rec_bones} bones per partition on {game} export." + f"This may cause issues in-game.") + + part_order = [getattr(NifFormat.BSDismemberBodyPartType, face_map.name, None) for face_map in + b_obj.face_maps] + part_order = [body_part for body_part in part_order if body_part is not None] + # override pyffi n_geom.update_skin_partition with custom one (that allows ordering) + n_geom.update_skin_partition = update_skin_partition.__get__(n_geom) + lostweight = n_geom.update_skin_partition( + maxbonesperpartition=NifOp.props.max_bones_per_partition, + maxbonespervertex=NifOp.props.max_bones_per_vertex, + stripify=NifOp.props.stripify, + stitchstrips=NifOp.props.stitch_strips, + padbones=NifOp.props.pad_bones, + triangles=triangles, + trianglepartmap=bodypartfacemap, + maximize_bone_sharing=(game in ('FALLOUT_3', 'SKYRIM')), + part_sort_order=part_order) + + if lostweight > NifOp.props.epsilon: + NifLog.warn( + f"Lost {lostweight:f} in vertex weights while creating a skin partition for Blender object '{b_obj.name}' (nif block '{n_geom.name}')") def update_bind_position(self, n_geom, n_root, b_obj_armature): """Transfer the Blender bind position to the nif bind position. @@ -730,7 +737,7 @@ def export_texture_effect(self, n_block, b_mat): extra_node.rotation.set_identity() extra_node.scale = 1.0 extra_node.flags = 0x000C # morrowind - # create texture effect block and parent the texture effect and trishape to it + # create texture effect block and parent the texture effect and n_geom to it texeff = self.texture_helper.export_texture_effect(ref_mtex) extra_node.add_child(texeff) extra_node.add_effect(texeff) diff --git a/io_scene_niftools/modules/nif_export/object/__init__.py b/io_scene_niftools/modules/nif_export/object/__init__.py index 4f09a4255..41a554a8f 100644 --- a/io_scene_niftools/modules/nif_export/object/__init__.py +++ b/io_scene_niftools/modules/nif_export/object/__init__.py @@ -199,7 +199,7 @@ def export_node(self, b_obj, n_parent, n_node_type=None): # export object animation self.transform_anim.export_transforms(node, b_obj, b_action) self.object_anim.export_visibility(node, b_action) - # if it is a mesh, export the mesh as trishape children of this ninode + # if it is a mesh, export the mesh as n_geom children of this ninode if b_obj.type == 'MESH': return self.mesh_helper.export_tri_shapes(b_obj, node, self.n_root) # if it is an armature, export the bones as ninode children of this ninode diff --git a/testframework/integration/modules/geometry/trishape/test_trishape.py b/testframework/integration/modules/geometry/trishape/test_trishape.py index 67faf83ca..047b12911 100644 --- a/testframework/integration/modules/geometry/trishape/test_trishape.py +++ b/testframework/integration/modules/geometry/trishape/test_trishape.py @@ -49,7 +49,7 @@ class TestTriShape(SingleNif): """Test base geometry, single blender object.""" - g_path = 'geometry/trishape' # (documented in base class) + g_path = 'geometry/n_geom' # (documented in base class) g_name = "test_trishape" b_name = 'Cube' From 519c413119175cebdf787a34070a43799828d998 Mon Sep 17 00:00:00 2001 From: HENDRIX-ZT2 Date: Fri, 27 May 2022 12:20:39 +0200 Subject: [PATCH 45/49] Cleanup trishape export --- .../modules/nif_export/geometry/mesh/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/io_scene_niftools/modules/nif_export/geometry/mesh/__init__.py b/io_scene_niftools/modules/nif_export/geometry/mesh/__init__.py index 211412f16..3bf5ec2db 100644 --- a/io_scene_niftools/modules/nif_export/geometry/mesh/__init__.py +++ b/io_scene_niftools/modules/nif_export/geometry/mesh/__init__.py @@ -113,7 +113,7 @@ def export_tri_shapes(self, b_obj, n_parent, n_root, trishape_name=None): # let's now export one n_geom for every mesh material # TODO [material] needs refactoring - move material, texture, etc. to separate function - for materialIndex, b_mat in enumerate(mesh_materials): + for b_mat_index, b_mat in enumerate(mesh_materials): mesh_hasnormals = False if b_mat is not None: @@ -141,7 +141,7 @@ def export_tri_shapes(self, b_obj, n_parent, n_root, trishape_name=None): # multimaterial meshes: add material index (Morrowind's child naming convention) if len(mesh_materials) > 1: - n_geom.name = f"{n_geom.name.decode()}: {materialIndex}" + n_geom.name = f"{n_geom.name.decode()}: {b_mat_index}" else: n_geom.name = block_store.get_full_name(n_geom) @@ -218,7 +218,7 @@ def export_tri_shapes(self, b_obj, n_parent, n_root, trishape_name=None): for poly in eval_mesh.polygons: # does the face belong to this n_geom? - if b_mat is not None and poly.material_index != materialIndex: + if b_mat is not None and poly.material_index != b_mat_index: # we have a material but this face has another material, so skip continue From e11ac76eb08a9ad677075165afbc924270270d5f Mon Sep 17 00:00:00 2001 From: HENDRIX-ZT2 Date: Fri, 27 May 2022 13:06:25 +0200 Subject: [PATCH 46/49] Cleanup trishape export --- .../nif_export/geometry/mesh/__init__.py | 121 +++++++++--------- .../modules/nif_export/object/__init__.py | 7 +- 2 files changed, 62 insertions(+), 66 deletions(-) diff --git a/io_scene_niftools/modules/nif_export/geometry/mesh/__init__.py b/io_scene_niftools/modules/nif_export/geometry/mesh/__init__.py index 3bf5ec2db..4e833a9be 100644 --- a/io_scene_niftools/modules/nif_export/geometry/mesh/__init__.py +++ b/io_scene_niftools/modules/nif_export/geometry/mesh/__init__.py @@ -98,7 +98,7 @@ def export_tri_shapes(self, b_obj, n_parent, n_root, trishape_name=None): # ignore materials on collision trishapes mesh_materials = [] - # if the mesh has no materials, all face material indices should be 0, so it's ok to fake one material in the material list + # if mesh has no materials, all face material indices should be 0, so fake one material in the material list if not mesh_materials: mesh_materials = [None] @@ -124,8 +124,10 @@ def export_tri_shapes(self, b_obj, n_parent, n_root, trishape_name=None): # create a n_geom block if not NifOp.props.stripify: n_geom = block_store.create_block("NiTriShape", b_obj) + n_geom.data = block_store.create_block("NiTriShapeData", b_obj) else: n_geom = block_store.create_block("NiTriStrips", b_obj) + n_geom.data = block_store.create_block("NiTriStripsData", b_obj) # fill in the NiTriShape's non-trivial values if isinstance(n_parent, NifFormat.RootCollisionNode): @@ -189,7 +191,7 @@ def export_tri_shapes(self, b_obj, n_parent, n_root, trishape_name=None): # The following algorithm extracts all unique quads(vert, uv-vert, normal, vcol), # produce lists of vertices, uv-vertices, normals, vertex colors, and face indices. - mesh_uv_layers = eval_mesh.uv_layers + b_uv_layers = eval_mesh.uv_layers vertquad_list = [] # (vertex, uv coordinate, normal, vertex color) list vertex_map = [None for _ in range(len(eval_mesh.vertices))] # blender vertex -> nif vertices vertex_positions = [] @@ -202,19 +204,23 @@ def export_tri_shapes(self, b_obj, n_parent, n_root, trishape_name=None): polygons_without_bodypart = [] if eval_mesh.polygons: - if mesh_uv_layers: + if b_uv_layers: # if we have uv coordinates double check that we have uv data if not eval_mesh.uv_layer_stencil: NifLog.warn(f"No UV map for texture associated with selected mesh '{eval_mesh.name}'.") use_tangents = False - if mesh_uv_layers and mesh_hasnormals: + if b_uv_layers and mesh_hasnormals: if game in ('OBLIVION', 'FALLOUT_3', 'SKYRIM') or (game in self.texture_helper.USED_EXTRA_SHADER_TEXTURES): use_tangents = True - eval_mesh.calc_tangents(uvmap=mesh_uv_layers[0].name) + eval_mesh.calc_tangents(uvmap=b_uv_layers[0].name) tangents = [] bitangent_signs = [] + if game in ('FALLOUT_3', 'SKYRIM'): + if len(b_uv_layers) > 1: + raise NifError(f"{game} does not support multiple UV layers.") + for poly in eval_mesh.polygons: # does the face belong to this n_geom? @@ -287,7 +293,7 @@ def export_tri_shapes(self, b_obj, n_parent, n_root, trishape_name=None): bitangent_signs.append([eval_mesh.loops[loop_index].bitangent_sign]) if mesh_hasvcol: vertex_colors.append(vertquad[3]) - if mesh_uv_layers: + if b_uv_layers: uv_coords.append(vertquad[1]) # now add the (hopefully, convex) face, in triangles @@ -320,58 +326,17 @@ def export_tri_shapes(self, b_obj, n_parent, n_root, trishape_name=None): if len(vertex_positions) == 0: continue # m_4444x: skip 'empty' material indices - # add NiTriShape's data - if isinstance(n_geom, NifFormat.NiTriShape): - tridata = block_store.create_block("NiTriShapeData", b_obj) - else: - tridata = block_store.create_block("NiTriStripsData", b_obj) - n_geom.data = tridata - - # data - tridata.num_vertices = len(vertex_positions) - tridata.has_vertices = True - tridata.vertices.update_size() - for i, v in enumerate(tridata.vertices): - v.x, v.y, v.z = vertex_positions[i] - tridata.update_center_radius() - - if mesh_hasnormals: - tridata.has_normals = True - tridata.normals.update_size() - for i, v in enumerate(tridata.normals): - v.x, v.y, v.z = normals[i] - - if mesh_hasvcol: - tridata.has_vertex_colors = True - tridata.vertex_colors.update_size() - for i, v in enumerate(tridata.vertex_colors): - v.r, v.g, v.b, v.a = vertex_colors[i] - - if mesh_uv_layers: - if game in ('FALLOUT_3', 'SKYRIM'): - if len(mesh_uv_layers) > 1: - raise NifError(f"{game} does not support multiple UV layers.") - tridata.num_uv_sets = len(mesh_uv_layers) - tridata.bs_num_uv_sets = len(mesh_uv_layers) - tridata.has_uv = True - tridata.uv_sets.update_size() - for j, uv_layer in enumerate(mesh_uv_layers): - for i, uv in enumerate(tridata.uv_sets[j]): - if len(uv_coords[i]) == 0: - continue # skip non-uv textures - uv.u = uv_coords[i][j][0] - # NIF flips the texture V-coordinate (OpenGL standard) - uv.v = 1.0 - uv_coords[i][j][1] # opengl standard + self.set_geom_data(n_geom, vertex_positions, normals, vertex_colors, uv_coords, b_uv_layers) # set triangles stitch strips for civ4 - tridata.set_triangles(triangles, stitchstrips=NifOp.props.stitch_strips) + n_geom.data.set_triangles(triangles, stitchstrips=NifOp.props.stitch_strips) - # update tangent space (as binary extra data only for Oblivion) + # update tangent space # for extra shader texture games, only export it if those textures are actually exported # (civ4 seems to be consistent with not using tangent space on non shadered nifs) if use_tangents: if game == 'SKYRIM': - tridata.bs_num_uv_sets = tridata.bs_num_uv_sets + 4096 + n_geom.data.bs_num_uv_sets = n_geom.data.bs_num_uv_sets + 4096 # calculate the bitangents using the normals, tangent list and bitangent sign bitangents = bitangent_signs * np.cross(normals, tangents) # B_tan: +d(B_u), B_bit: +d(B_v) and N_tan: +d(N_v), N_bit: +d(N_u) @@ -379,7 +344,7 @@ def export_tri_shapes(self, b_obj, n_parent, n_root, trishape_name=None): self.add_defined_tangents(n_geom, tangents=-bitangents, bitangents=tangents, - as_extra_data=(game == 'OBLIVION')) + as_extra_data=(game == 'OBLIVION')) # as binary extra data only for Oblivion # todo [mesh/object] use more sophisticated armature finding, also taking armature modifier into account # now export the vertex weights, if there are any @@ -425,13 +390,8 @@ def export_tri_shapes(self, b_obj, n_parent, n_root, trishape_name=None): self.select_unweighted_vertices(b_obj, unweighted_vertices) - # for each bone, first we get the bone block then we get the vertex weights and then we add it to the NiSkinData - # note: allocate memory for faster performance - vert_added = [False for _ in range(len(vertex_positions))] + # for each bone, get the vertex weights and add its n_node to the NiSkinData for b_bone_name in boneinfluences: - # find bone in exported blocks - bone_block = self.get_bone_block(b_obj_armature.data.bones[b_bone_name]) - # find vertex weights vert_weights = {} for v in vert_list[b_bone_name]: @@ -446,10 +406,14 @@ def export_tri_shapes(self, b_obj, n_parent, n_root, trishape_name=None): if vertex_map[v[0]] and vert_norm[v[0]]: for vert_index in vertex_map[v[0]]: vert_weights[vert_index] = v[1] / vert_norm[v[0]] - vert_added[vert_index] = True # add bone as influence, but only if there were actually any vertices influenced by the bone if vert_weights: - n_geom.add_bone(bone_block, vert_weights) + # find bone in exported blocks + n_node = self.get_bone_block(b_obj_armature.data.bones[b_bone_name]) + n_geom.add_bone(n_node, vert_weights) + + # clean up + del vert_weights # update bind position skinning data # n_geom.update_bind_position() @@ -461,18 +425,47 @@ def export_tri_shapes(self, b_obj, n_parent, n_root, trishape_name=None): self.export_skin_partition(b_obj, bodypartfacemap, triangles, n_geom) - # clean up - del vert_weights - del vert_added # fix data consistency type - tridata.consistency_flags = b_obj.niftools.consistency_flags + n_geom.data.consistency_flags = b_obj.niftools.consistency_flags # export EGM or NiGeomMorpherController animation # shape keys are only present on the raw, unevaluated mesh self.morph_anim.export_morph(b_mesh, n_geom, vertex_map) return n_geom + def set_geom_data(self, n_geom, vertex_positions, normals, vertex_colors, uv_coords, b_uv_layers): + """Sets flat lists of per-vertex data to n_geom""" + # coords + n_geom.data.num_vertices = len(vertex_positions) + n_geom.data.has_vertices = True + n_geom.data.vertices.update_size() + for n_v, b_v in zip(n_geom.data.vertices, vertex_positions): + n_v.x, n_v.y, n_v.z = b_v + n_geom.data.update_center_radius() + # normals + n_geom.data.has_normals = bool(normals) + n_geom.data.normals.update_size() + for n_v, b_v in zip(n_geom.data.normals, normals): + n_v.x, n_v.y, n_v.z = b_v + # vertex_colors + n_geom.data.has_vertex_colors = bool(vertex_colors) + n_geom.data.vertex_colors.update_size() + for n_v, b_v in zip(n_geom.data.vertex_colors, vertex_colors): + n_v.r, n_v.g, n_v.b, n_v.a = b_v + # uv_sets + n_geom.data.has_uv = bool(b_uv_layers) + n_geom.data.num_uv_sets = len(b_uv_layers) + n_geom.data.bs_num_uv_sets = len(b_uv_layers) + n_geom.data.uv_sets.update_size() + for j, n_uv_set in enumerate(n_geom.data.uv_sets): + for i, n_uv in enumerate(n_uv_set): + if len(uv_coords[i]) == 0: + continue # skip non-uv textures + n_uv.u = uv_coords[i][j][0] + # NIF flips the texture V-coordinate (OpenGL standard) + n_uv.v = 1.0 - uv_coords[i][j][1] # opengl standard + def export_skin_partition(self, b_obj, bodypartfacemap, triangles, n_geom): """Attaches a skin partition to n_geom if needed""" game = bpy.context.scene.niftools_scene.game diff --git a/io_scene_niftools/modules/nif_export/object/__init__.py b/io_scene_niftools/modules/nif_export/object/__init__.py index 41a554a8f..fb99e782b 100644 --- a/io_scene_niftools/modules/nif_export/object/__init__.py +++ b/io_scene_niftools/modules/nif_export/object/__init__.py @@ -207,10 +207,13 @@ def export_node(self, b_obj, n_parent, n_node_type=None): self.armaturehelper.export_bones(b_obj, node) # special case: objects parented to armature bones for b_child in b_obj.children: - # find and attach to the right bone + # find and attach to the right node if b_child.parent_bone: b_obj_bone = b_obj.data.bones[b_child.parent_bone] - self.export_node(b_child, block_store.block_to_obj[b_obj_bone]) + # find the correct n_node + # todo [object] this is essentially the same as Mesh.get_bone_block() + n_node = [k for k, v in block_store.block_to_obj.items() if v == b_obj_bone][0] + self.export_node(b_child, n_node) # just child of the armature itself, so attach to armature root else: self.export_node(b_child, node) From 1fd4486a16f87ad1936b1aab67b7ada9a0714094 Mon Sep 17 00:00:00 2001 From: HENDRIX-ZT2 Date: Fri, 27 May 2022 13:09:04 +0200 Subject: [PATCH 47/49] Cleanup trishape export --- io_scene_niftools/modules/nif_export/geometry/mesh/__init__.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/io_scene_niftools/modules/nif_export/geometry/mesh/__init__.py b/io_scene_niftools/modules/nif_export/geometry/mesh/__init__.py index 4e833a9be..a00878931 100644 --- a/io_scene_niftools/modules/nif_export/geometry/mesh/__init__.py +++ b/io_scene_niftools/modules/nif_export/geometry/mesh/__init__.py @@ -411,8 +411,6 @@ def export_tri_shapes(self, b_obj, n_parent, n_root, trishape_name=None): # find bone in exported blocks n_node = self.get_bone_block(b_obj_armature.data.bones[b_bone_name]) n_geom.add_bone(n_node, vert_weights) - - # clean up del vert_weights # update bind position skinning data @@ -425,7 +423,6 @@ def export_tri_shapes(self, b_obj, n_parent, n_root, trishape_name=None): self.export_skin_partition(b_obj, bodypartfacemap, triangles, n_geom) - # fix data consistency type n_geom.data.consistency_flags = b_obj.niftools.consistency_flags From 56af76a9611ff3ea808de76a57f827649629c4e1 Mon Sep 17 00:00:00 2001 From: HENDRIX-ZT2 Date: Fri, 27 May 2022 13:23:12 +0200 Subject: [PATCH 48/49] Cleanup add_defined_tangents --- .../nif_export/geometry/mesh/__init__.py | 38 ++++++++----------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/io_scene_niftools/modules/nif_export/geometry/mesh/__init__.py b/io_scene_niftools/modules/nif_export/geometry/mesh/__init__.py index a00878931..0db8591b8 100644 --- a/io_scene_niftools/modules/nif_export/geometry/mesh/__init__.py +++ b/io_scene_niftools/modules/nif_export/geometry/mesh/__init__.py @@ -760,40 +760,34 @@ def ensure_tri_modifier(self, b_obj): else: b_obj.modifiers.new('Triangulate', 'TRIANGULATE') - def add_defined_tangents(self, trishape, tangents, bitangents, as_extra_data): + def add_defined_tangents(self, n_geom, tangents, bitangents, as_extra_data): # check if size of tangents and bitangents is equal to num_vertices - if not (len(tangents) == len(bitangents) == trishape.data.num_vertices): - raise NifError(f'Number of tangents or bitangents does not agree with number of vertices in {trishape.name}') + if not (len(tangents) == len(bitangents) == n_geom.data.num_vertices): + raise NifError(f'Number of tangents or bitangents does not agree with number of vertices in {n_geom.name}') if as_extra_data: # if tangent space extra data already exists, use it # find possible extra data block - for extra in trishape.get_extra_datas(): + extra_name = b'Tangent space (binormal & tangent vectors)' + for extra in n_geom.get_extra_datas(): if isinstance(extra, NifFormat.NiBinaryExtraData): - if extra.name == b'Tangent space (binormal & tangent vectors)': + if extra.name == extra_name: break else: - extra = None - if not extra: - # otherwise, create a new block and link it + # create a new block and link it extra = NifFormat.NiBinaryExtraData() - extra.name = b'Tangent space (binormal & tangent vectors)' - trishape.add_extra_data(extra) - + extra.name = extra_name + n_geom.add_extra_data(extra) # write the data extra.binary_data = np.concatenate((tangents, bitangents), axis=0).astype(' Date: Thu, 2 Jun 2022 18:22:01 +0200 Subject: [PATCH 49/49] Revert TestTriShape --- .../integration/modules/geometry/trishape/test_trishape.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testframework/integration/modules/geometry/trishape/test_trishape.py b/testframework/integration/modules/geometry/trishape/test_trishape.py index 047b12911..67faf83ca 100644 --- a/testframework/integration/modules/geometry/trishape/test_trishape.py +++ b/testframework/integration/modules/geometry/trishape/test_trishape.py @@ -49,7 +49,7 @@ class TestTriShape(SingleNif): """Test base geometry, single blender object.""" - g_path = 'geometry/n_geom' # (documented in base class) + g_path = 'geometry/trishape' # (documented in base class) g_name = "test_trishape" b_name = 'Cube'