def import_texture_extra_shader(self, b_mat, n_texture_prop, extra_datas):
        # extra texture shader slots
        for shader_tex_desc in n_texture_prop.shader_textures:

            if not shader_tex_desc.is_used:
                continue

            # it is used, figure out the slot it is used for
            for extra in extra_datas:
                if extra.integer_data == shader_tex_desc.map_index:
                    shader_name = extra.name
                    break
            else:
                NifLog.warn("No slot for shader texture {0}.".format(
                    shader_tex_desc.texture_data.source.file_name))
                continue
            try:
                extra_shader_index = (
                    self.nif_import.EXTRA_SHADER_TEXTURES.index(shader_name))
            except ValueError:
                # shader_name not in self.EXTRA_SHADER_TEXTURES
                NifLog.warn("No slot for shader texture {0}.".format(
                    shader_tex_desc.texture_data.source.file_name))
                continue

            self.import_shader_by_type(shader_tex_desc, extra_shader_index)
    def get_extend_from_flags(self, flags):
        if flags & 6 == 4:  # 0b100
            return "CONST"
        elif flags & 6 == 0:  # 0b000
            return "CYCLIC"

        NifLog.warn("Unsupported cycle mode in nif, using clamped.")
        return "CONST"
 def get_b_interp_from_n_interp(self, n_ipol):
     if n_ipol == NifFormat.KeyType.LINEAR_KEY:
         return "LINEAR"
     elif n_ipol == NifFormat.KeyType.QUADRATIC_KEY:
         return "BEZIER"
     elif n_ipol == 0:
         # guessing, not documented in nif.xml
         return "CONSTANT"
     NifLog.warn(
         "Unsupported interpolation mode ({0}) in nif, using quadratic/bezier."
         .format(n_ipol))
     return "BEZIER"
    def get_n_apply_mode_from_b_blend_type(self, b_blend_type):
        if b_blend_type == "LIGHTEN":
            return NifFormat.ApplyMode.APPLY_HILIGHT
        elif b_blend_type == "MULTIPLY":
            return NifFormat.ApplyMode.APPLY_HILIGHT2
        elif b_blend_type == "MIX":
            return NifFormat.ApplyMode.APPLY_MODULATE

        NifLog.warn(
            "Unsupported blend type ({0}) in material, using apply mode APPLY_MODULATE"
            .format(b_blend_type))
        return NifFormat.ApplyMode.APPLY_MODULATE
    def get_n_interp_from_b_interp(self, b_ipol):
        if b_ipol == "LINEAR":
            return NifFormat.KeyType.LINEAR_KEY
        elif b_ipol == "BEZIER":
            return NifFormat.KeyType.QUADRATIC_KEY
        elif b_ipol == "CONSTANT":
            return NifFormat.KeyType.CONST_KEY

        NifLog.warn(
            "Unsupported interpolation mode ({0}) in blend, using quadratic/bezier."
            .format(b_ipol))
        return NifFormat.KeyType.QUADRATIC_KEY
示例#6
0
    def export_tex_desc(self, texdesc=None, uvlayers=None, b_mat_texslot=None):
        """Helper function for export_texturing_property to export each texture
        slot."""
        try:
            texdesc.uv_set = uvlayers.index(
                b_mat_texslot.uv_layer) if b_mat_texslot.uv_layer else 0
        except ValueError:  # mtex.uv_layer not in uvlayers list
            NifLog.warn(
                "Bad uv layer name '{0}' in texture '{1}'. Using first uv layer"
                .format(b_mat_texslot.uv_layer, b_mat_texslot.texture.name))
            texdesc.uv_set = 0  # assume 0 is active layer

        texdesc.source = self.export_source_texture(b_mat_texslot.texture)
示例#7
0
    def import_texture_source(self, source):
        """Convert a NiSourceTexture block, or simply a path string,
        to a Blender Texture object, return the Texture object and
        stores it in the self.nif_import.dict_textures dictionary to avoid future
        duplicate imports.
        """
        # if the source block is not linked then return None
        if not source:
            return None

        # calculate the texture hash key
        texture_hash = self.get_texture_hash(source)

        try:
            # look up the texture in the dictionary of imported textures
            # and return it if found
            return self.nif_import.dict_textures[texture_hash]
        except KeyError:
            pass

        b_image = None

        if (isinstance(source, NifFormat.NiSourceTexture)
                and not source.use_external):
            fn, b_image = self.import_embedded_texture_source(source)
        else:
            fn, b_image = self.import_source(source)

        # create a stub image if the image could not be loaded

        b_text_name = os.path.basename(fn)
        if not b_image:
            NifLog.warn(
                "Texture '{0}' not found or not supported and no alternate available"
                .format(fn))
            b_image = bpy.data.images.new(name=b_text_name,
                                          width=1,
                                          height=1,
                                          alpha=False)
            b_image.filepath = fn

        # create a texture
        b_texture = bpy.data.textures.new(name=b_text_name, type='IMAGE')
        b_texture.image = b_image
        b_texture.use_interpolation = True
        b_texture.use_mipmap = True

        # save texture to avoid duplicate imports, and return it
        self.nif_import.dict_textures[texture_hash] = b_texture
        return b_texture
    def import_bhk_shape(self, bhkshape):
        """Imports any supported collision shape as list of blender meshes."""

        if self.nif_import.data._user_version_value_._value == 12:
            if self.nif_import.data._user_version_2_value_._value == 83:
                self.HAVOK_SCALE = self.nif_import.HAVOK_SCALE * 10
            else:
                self.HAVOK_SCALE = self.nif_import.HAVOK_SCALE

        if isinstance(bhkshape, NifFormat.bhkTransformShape):
            return self.import_bhktransform(bhkshape)

        elif isinstance(bhkshape, NifFormat.bhkRigidBody):
            return self.import_bhkridgidbody(bhkshape)

        elif isinstance(bhkshape, NifFormat.bhkBoxShape):
            return self.import_bhkbox_shape(bhkshape)

        elif isinstance(bhkshape, NifFormat.bhkSphereShape):
            return self.import_bhksphere_shape(bhkshape)

        elif isinstance(bhkshape, NifFormat.bhkCapsuleShape):
            return self.import_bhkcapsule_shape(bhkshape)

        elif isinstance(bhkshape, NifFormat.bhkConvexVerticesShape):
            return self.import_bhkconvex_vertices_shape(bhkshape)

        elif isinstance(bhkshape, NifFormat.bhkPackedNiTriStripsShape):
            return self.import_bhkpackednitristrips_shape(bhkshape)

        elif isinstance(bhkshape, NifFormat.bhkNiTriStripsShape):
            self.havok_mat = bhkshape.material
            return reduce(operator.add, (self.import_bhk_shape(strips)
                                         for strips in bhkshape.strips_data))

        elif isinstance(bhkshape, NifFormat.NiTriStripsData):
            return self.import_nitristrips(bhkshape)

        elif isinstance(bhkshape, NifFormat.bhkMoppBvTreeShape):
            return self.import_bhk_shape(bhkshape.shape)

        elif isinstance(bhkshape, NifFormat.bhkListShape):
            return reduce(operator.add, (self.import_bhk_shape(subshape)
                                         for subshape in bhkshape.sub_shapes))

        NifLog.warn("Unsupported bhk shape {0}".format(
            bhkshape.__class__.__name__))
        return []
 def get_b_blend_type_from_n_apply_mode(self, n_apply_mode):
     # TODO: - Check out n_apply_modes
     if n_apply_mode == NifFormat.ApplyMode.APPLY_MODULATE:
         return "MIX"
     elif n_apply_mode == NifFormat.ApplyMode.APPLY_REPLACE:
         return "COLOR"
     elif n_apply_mode == NifFormat.ApplyMode.APPLY_DECAL:
         return "OVERLAY"
     elif n_apply_mode == NifFormat.ApplyMode.APPLY_HILIGHT:
         return "LIGHTEN"
     elif n_apply_mode == NifFormat.ApplyMode.APPLY_HILIGHT2:  # used by Oblivion for parallax
         return "MULTIPLY"
     else:
         NifLog.warn(
             "Unknown apply mode (%i) in material, using blend type 'MIX'".
             format(n_apply_mode))
         return "MIX"
示例#10
0
    def export_source_texture(self, texture=None, filename=None):
        """Export a NiSourceTexture.

        :param texture: The texture object in blender to be exported.
        :param filename: The full or relative path to the texture file
            (this argument is used when exporting NiFlipControllers
            and when exporting default shader slots that have no use in
            being imported into Blender).
        :return: The exported NiSourceTexture block.
        """

        # create NiSourceTexture
        srctex = NifFormat.NiSourceTexture()
        srctex.use_external = True
        if not filename is None:
            # preset filename
            srctex.file_name = filename
        elif not texture is None:
            srctex.file_name = self.export_texture_filename(texture)
        else:
            # this probably should not happen
            NifLog.warn(
                "Exporting source texture without texture or filename (bug?).")

        # fill in default values (TODO: can we use 6 for everything?)
        if bpy.context.scene.niftools_scene.nif_version >= 0x0A000100:
            srctex.pixel_layout = 6
        else:
            srctex.pixel_layout = 5
        srctex.use_mipmaps = 1
        srctex.alpha_format = 3
        srctex.unknown_byte = 1

        # search for duplicate
        for block in self.nif_export.nif_export.dict_blocks:
            if isinstance(block, NifFormat.NiSourceTexture) and block.get_hash(
            ) == srctex.get_hash():
                return block

        # no identical source texture found, so use and register
        # the new one
        return self.nif_export.nif_export.objecthelper.register_block(
            srctex, texture)
示例#11
0
    def export_collision(self, b_obj, parent_block):
        """Main function for adding collision object b_obj to a node."""
        if NifOp.props.game == 'MORROWIND':
            if b_obj.game.collision_bounds_type != 'TRIANGLE_MESH':
                raise nif_utils.NifError(
                    "Morrowind only supports Triangle Mesh collisions.")
            node = self.objecthelper.create_block("RootCollisionNode", b_obj)
            parent_block.add_child(node)
            node.flags = 0x0003  # default
            self.objecthelper.set_object_matrix(b_obj, node)
            for child in b_obj.children:
                self.objecthelper.export_node(child, node, None)

        elif NifOp.props.game in ('OBLIVION', 'FALLOUT_3', 'SKYRIM'):

            nodes = [parent_block]
            nodes.extend([
                block for block in parent_block.children
                if block.name[:14] == 'collisiondummy'
            ])
            for node in nodes:
                try:
                    self.bhkshapehelper.export_collision_helper(b_obj, node)
                    break
                except ValueError:  # adding collision failed
                    continue
            else:  # all nodes failed so add new one
                node = self.objecthelper.create_ninode(b_obj)
                node.set_transform(self.IDENTITY44)
                node.name = 'collisiondummy%i' % parent_block.num_children
                if b_obj.niftools.objectflags != 0:
                    node_flag_hex = hex(b_obj.niftools.objectflags)
                else:
                    node_flag_hex = 0x000E  # default
                node.flags = node_flag_hex
                parent_block.add_child(node)
                self.bhkshapehelper.export_collision_helper(b_obj, node)

        else:
            NifLog.warn(
                "Only Morrowind, Oblivion, and Fallout 3 collisions are supported, skipped collision object '{0}'"
                .format(b_obj.name))
示例#12
0
def decompose_srt(matrix):
    """Decompose Blender transform matrix as a scale, rotation matrix, and
    translation vector."""

    # get matrix components
    trans_vec, rot_quat, scale_vec = matrix.decompose()

    #obtain a combined scale and rotation matrix to test determinate
    rotmat = rot_quat.to_matrix()
    scalemat = mathutils.Matrix(
        ((scale_vec[0], 0.0, 0.0), (0.0, scale_vec[1], 0.0), (0.0, 0.0,
                                                              scale_vec[2])))
    scale_rot = scalemat * rotmat

    # and fix their sign
    if (scale_rot.determinant() < 0): scale_vec.negate()
    # only uniform scaling
    # allow rather large error to accomodate some nifs
    if abs(scale_vec[0] - scale_vec[1]) + abs(scale_vec[1] -
                                              scale_vec[2]) > 0.02:
        NifLog.warn("Non-uniform scaling not supported." +
                    " Workaround: apply size and rotation (CTRL-A).")
    return [scale_vec[0], rotmat, trans_vec]
    def import_shader_by_type(self, b_mat, shader_tex_desc,
                              extra_shader_index):
        if extra_shader_index == 0:
            # EnvironmentMapIndex
            if shader_tex_desc.texture_data.source.file_name.lower(
            ).startswith("rrt_engine_env_map"):
                # sid meier's railroads: env map generated by engine
                # we can skip this
                NifLog.info(
                    "Skipping environment map texture. Env Map is generated by Engine"
                )
            envTexDesc = shader_tex_desc.texture_data
            self.has_envtex = True
            self.env_map = self.import_image_texture(b_mat, envTexDesc)

        elif extra_shader_index == 1:
            # NormalMapIndex
            bumpTexDesc = shader_tex_desc.texture_data
            self.has_bumptex = True
            self.bump_map = self.import_image_texture(b_mat, bumpTexDesc)

        elif extra_shader_index == 2:
            # SpecularIntensityIndex
            glossTexDesc = shader_tex_desc.texture_data
            self.has_glosstex = True
            self.gloss_map = self.import_image_texture(b_mat, glossTexDesc)

        elif extra_shader_index == 3:
            # EnvironmentIntensityIndex (this is reflection)
            refTexDesc = shader_tex_desc.texture_data
            self.has_reftex = True
            self.reflection_map = self.reflection_map = self.import_image_texture(
                b_mat, refTexDesc)

        elif extra_shader_index == 4:
            # LightCubeMapIndex
            if shader_tex_desc.texture_data.source.file_name.lower(
            ).startswith("rrt_cube_light_map"):
                # sid meier's railroads: light map generated by engine
                # we can skip this
                NifLog.info("Ignoring Env Map as generated by Engine")
            NifLog.warn("Skipping light cube texture.")

        elif extra_shader_index == 5:
            # ShadowTextureIndex
            NifLog.warn("Skipping shadow texture.")

        else:
            NifLog.warn("Unknown texture type found in extra_shader_index")
示例#14
0
    def export_nitextureprop_tex_descs(self, texprop):

        if self.base_mtex:
            texprop.has_base_texture = True
            self.texture_writer.export_tex_desc(texdesc=texprop.base_texture,
                                                uvlayers=self.nif_export.dict_mesh_uvlayers,
                                                b_mat_texslot=self.base_mtex)
            # check for texture flip definition
            try:
                fliptxt = Blender.Text.Get(basemtex.texture.name)
            except NameError:
                pass
            else:
                # texture slot 0 = base
                self.animationhelper.texture_animation.export_flip_controller(fliptxt, self.base_mtex.texture, texprop, 0)

        if self.glow_mtex:
            texprop.has_glow_texture = True
            self.texture_writer.export_tex_desc(texdesc=texprop.glow_texture,
                                                uvlayers=self.nif_export.dict_mesh_uvlayers,
                                                b_mat_texslot=self.glow_mtex)

        if self.bump_mtex:
            if NifOp.props.game not in self.USED_EXTRA_SHADER_TEXTURES:
                texprop.has_bump_map_texture = True
                self.texture_writer.export_tex_desc(texdesc=texprop.bump_map_texture,
                                                    uvlayers=self.nif_export.dict_mesh_uvlayers,
                                                    b_mat_texslot=self.bump_mtex)
                texprop.bump_map_luma_scale = 1.0
                texprop.bump_map_luma_offset = 0.0
                texprop.bump_map_matrix.m_11 = 1.0
                texprop.bump_map_matrix.m_12 = 0.0
                texprop.bump_map_matrix.m_21 = 0.0
                texprop.bump_map_matrix.m_22 = 1.0

        if self.normal_mtex:
            shadertexdesc = texprop.shader_textures[1]
            shadertexdesc.is_used = True
            shadertexdesc.texture_data.source = self.texture_writer.export_source_texture(texture=self.normal_mtex.texture)

        if self.gloss_mtex:
            if NifOp.props.game not in self.USED_EXTRA_SHADER_TEXTURES:
                texprop.has_gloss_texture = True
                self.texture_writer.export_tex_desc(texdesc=texprop.gloss_texture,
                                                    uvlayers=self.nif_export.dict_mesh_uvlayers,
                                                    b_mat_texslot=self.gloss_mtex)
            else:
                shadertexdesc = texprop.shader_textures[2]
                shadertexdesc.is_used = True
                shadertexdesc.texture_data.source = self.texture_writer.export_source_texture(texture=self.gloss_mtex.texture)

        if self.dark_mtex:
            texprop.has_dark_texture = True
            self.texture_writer.export_tex_desc(texdesc=texprop.dark_texture,
                                                uvlayers=self.nif_export.dict_mesh_uvlayers,
                                                b_mat_texslot=self.dark_mtex)

        if self.detail_mtex:
            texprop.has_detail_texture = True
            self.texture_writer.export_tex_desc(texdesc=texprop.detail_texture,
                                                uvlayers=self.nif_export.dict_mesh_uvlayers,
                                                b_mat_texslot=self.detail_mtex)

        if self.ref_mtex:
            if NifOp.props.game not in self.USED_EXTRA_SHADER_TEXTURES:
                NifLog.warn("Cannot export reflection texture for this game.")
                # texprop.hasRefTexture = True
                # self.export_tex_desc(texdesc=texprop.refTexture, uvlayers=uvlayers, mtex=refmtex)
            else:
                shadertexdesc = texprop.shader_textures[3]
                shadertexdesc.is_used = True
                shadertexdesc.texture_data.source = self.texture_writer.export_source_texture(texture=self.ref_mtex.texture)
示例#15
0
    def import_constraint(self, hkbody):
        """Imports a bone havok constraint as Blender object constraint."""
        assert (isinstance(hkbody, NifFormat.bhkRigidBody))

        # check for constraints
        if not hkbody.constraints:
            return

        # find objects
        if len(self.nif_import.dict_havok_objects[hkbody]) != 1:
            NifLog.warn(
                "Rigid body with no or multiple shapes, constraints skipped")
            return

        b_hkobj = self.nif_import.dict_havok_objects[hkbody][0]

        NifLog.info("Importing constraints for %s" % b_hkobj.name)

        # now import all constraints
        for hkconstraint in hkbody.constraints:

            # check constraint entities
            if not hkconstraint.num_entities == 2:
                NifLog.warn("Constraint with more than 2 entities, skipped")
                continue
            if not hkconstraint.entities[0] is hkbody:
                NifLog.warn("First constraint entity not self, skipped")
                continue
            if not hkconstraint.entities[
                    1] in self.nif_import.dict_havok_objects:
                NifLog.warn("Second constraint entity not imported, skipped")
                continue

            # get constraint descriptor
            if isinstance(hkconstraint, NifFormat.bhkRagdollConstraint):
                hkdescriptor = hkconstraint.ragdoll
                b_hkobj.rigid_body.enabled = True
            elif isinstance(hkconstraint, NifFormat.bhkLimitedHingeConstraint):
                hkdescriptor = hkconstraint.limited_hinge
                b_hkobj.rigid_body.enabled = True
            elif isinstance(hkconstraint, NifFormat.bhkHingeConstraint):
                hkdescriptor = hkconstraint.hinge
                b_hkobj.rigid_body.enabled = True
            elif isinstance(hkconstraint, NifFormat.bhkMalleableConstraint):
                if hkconstraint.type == 7:
                    hkdescriptor = hkconstraint.ragdoll
                    b_hkobj.rigid_body.enabled = False
                elif hkconstraint.type == 2:
                    hkdescriptor = hkconstraint.limited_hinge
                    b_hkobj.rigid_body.enabled = False
                else:
                    NifLog.warn("Unknown malleable type ({0}), skipped".format(
                        str(hkconstraint.type)))
                # extra malleable constraint settings
                ### damping parameters not yet in Blender Python API
                ### tau (force between bodies) not supported by Blender
            else:
                NifLog.warn("Unknown constraint type ({0}), skipped".format(
                    hkconstraint.__class__.__name__))
                continue

            # add the constraint as a rigid body joint
            b_constr = b_hkobj.constraints.new('RIGID_BODY_JOINT')
            b_constr.name = b_hkobj.name
            b_constr.show_pivot = True

            # note: rigidbodyjoint parameters (from Constraint.c)
            # CONSTR_RB_AXX 0.0
            # CONSTR_RB_AXY 0.0
            # CONSTR_RB_AXZ 0.0
            # CONSTR_RB_EXTRAFZ 0.0
            # CONSTR_RB_MAXLIMIT0 0.0
            # CONSTR_RB_MAXLIMIT1 0.0
            # CONSTR_RB_MAXLIMIT2 0.0
            # CONSTR_RB_MAXLIMIT3 0.0
            # CONSTR_RB_MAXLIMIT4 0.0
            # CONSTR_RB_MAXLIMIT5 0.0
            # CONSTR_RB_MINLIMIT0 0.0
            # CONSTR_RB_MINLIMIT1 0.0
            # CONSTR_RB_MINLIMIT2 0.0
            # CONSTR_RB_MINLIMIT3 0.0
            # CONSTR_RB_MINLIMIT4 0.0
            # CONSTR_RB_MINLIMIT5 0.0
            # CONSTR_RB_PIVX 0.0
            # CONSTR_RB_PIVY 0.0
            # CONSTR_RB_PIVZ 0.0
            # CONSTR_RB_TYPE 12
            # LIMIT 63
            # PARSIZEY 63
            # TARGET [Object "capsule.002"]

            # limit 3, 4, 5 correspond to angular limits along x, y and z
            # and are measured in degrees

            # pivx/y/z is the pivot point

            # set constraint target
            b_constr.target = \
                self.nif_import.dict_havok_objects[hkconstraint.entities[1]][0]
            # set rigid body type (generic)
            b_constr.pivot_type = 'GENERIC_6_DOF'
            # limiting parameters (limit everything)
            b_constr.use_angular_limit_x = True
            b_constr.use_angular_limit_y = True
            b_constr.use_angular_limit_z = True

            # get pivot point
            pivot = mathutils.Vector(
                (hkdescriptor.pivot_a.x * self.HAVOK_SCALE,
                 hkdescriptor.pivot_a.y * self.HAVOK_SCALE,
                 hkdescriptor.pivot_a.z * self.HAVOK_SCALE))

            # get z- and x-axes of the constraint
            # (also see export_nif.py NifImport.export_constraints)
            if isinstance(hkdescriptor, NifFormat.RagdollDescriptor):
                b_constr.pivot_type = 'CONE_TWIST'
                # for ragdoll, take z to be the twist axis (central axis of the
                # cone, that is)
                axis_z = mathutils.Vector(
                    (hkdescriptor.twist_a.x, hkdescriptor.twist_a.y,
                     hkdescriptor.twist_a.z))
                # for ragdoll, let x be the plane vector
                axis_x = mathutils.Vector(
                    (hkdescriptor.plane_a.x, hkdescriptor.plane_a.y,
                     hkdescriptor.plane_a.z))
                # set the angle limits
                # (see http://niftools.sourceforge.net/wiki/Oblivion/Bhk_Objects/Ragdoll_Constraint
                # for a nice picture explaining this)
                b_constr.limit_angle_min_x = \
                    hkdescriptor.plane_min_angle
                b_constr.limit_angle_max_x = \
                    hkdescriptor.plane_max_angle

                b_constr.limit_angle_min_y = \
                    -hkdescriptor.cone_max_angle
                b_constr.limit_angle_max_y = \
                    hkdescriptor.cone_max_angle

                b_constr.limit_angle_min_z = \
                    hkdescriptor.twist_min_angle
                b_constr.limit_angle_max_z = \
                    hkdescriptor.twist_max_angle

                b_hkobj.niftools_constraint.LHMaxFriction = hkdescriptor.max_friction

            elif isinstance(hkdescriptor, NifFormat.LimitedHingeDescriptor):
                # for hinge, y is the vector on the plane of rotation defining
                # the zero angle
                axis_y = mathutils.Vector((hkdescriptor.perp_2_axle_in_a_1.x,
                                           hkdescriptor.perp_2_axle_in_a_1.y,
                                           hkdescriptor.perp_2_axle_in_a_1.z))
                # for hinge, take x to be the the axis of rotation
                # (this corresponds with Blender's convention for hinges)
                axis_x = mathutils.Vector(
                    (hkdescriptor.axle_a.x, hkdescriptor.axle_a.y,
                     hkdescriptor.axle_a.z))
                # for hinge, z is the vector on the plane of rotation defining
                # the positive direction of rotation
                axis_z = mathutils.Vector((hkdescriptor.perp_2_axle_in_a_2.x,
                                           hkdescriptor.perp_2_axle_in_a_2.y,
                                           hkdescriptor.perp_2_axle_in_a_2.z))
                # they should form a orthogonal basis
                if (mathutils.Vector.cross(axis_x, axis_y) -
                        axis_z).length > 0.01:
                    # either not orthogonal, or negative orientation
                    if (mathutils.Vector.cross(-axis_x, axis_y) -
                            axis_z).length > 0.01:
                        NifLog.warn(
                            "Axes are not orthogonal in {0}; Arbitrary orientation has been chosen"
                            .format(hkdescriptor.__class__.__name__))
                        axis_z = mathutils.Vector.cross(axis_x, axis_y)
                    else:
                        # fix orientation
                        NifLog.warn(
                            "X axis flipped in {0} to fix orientation".format(
                                hkdescriptor.__class__.__name__))
                        axis_x = -axis_x
                # getting properties with no blender constraint
                # equivalent and setting as obj properties
                b_constr.limit_angle_max_x = hkdescriptor.max_angle
                b_constr.limit_angle_min_x = hkdescriptor.min_angle
                b_hkobj.niftools_constraint.LHMaxFriction = hkdescriptor.max_friction

                if hasattr(hkconstraint, "tau"):
                    b_hkobj.niftools_constraint.tau = hkconstraint.tau
                    b_hkobj.niftools_constraint.damping = hkconstraint.damping

            elif isinstance(hkdescriptor, NifFormat.HingeDescriptor):
                # for hinge, y is the vector on the plane of rotation defining
                # the zero angle
                axis_y = mathutils.Vector((hkdescriptor.perp_2_axle_in_a_1.x,
                                           hkdescriptor.perp_2_axle_in_a_1.y,
                                           hkdescriptor.perp_2_axle_in_a_1.z))
                # for hinge, z is the vector on the plane of rotation defining
                # the positive direction of rotation
                axis_z = mathutils.Vector((hkdescriptor.perp_2_axle_in_a_2.x,
                                           hkdescriptor.perp_2_axle_in_a_2.y,
                                           hkdescriptor.perp_2_axle_in_a_2.z))
                # take x to be the the axis of rotation
                # (this corresponds with Blender's convention for hinges)
                axis_x = mathutils.Vector.cross(axis_y, axis_z)
                b_hkobj.niftools_constraint.LHMaxFriction = hkdescriptor.max_friction
            else:
                raise ValueError("unknown descriptor %s" %
                                 hkdescriptor.__class__.__name__)

            # transform pivot point and constraint matrix into object
            # coordinates
            # (also see export_nif.py NifImport.export_constraints)

            # the pivot point v is in hkbody coordinates
            # however blender expects it in object coordinates, v'
            # v * R * B = v' * O * T * B'
            # with R = rigid body transform (usually unit tf)
            # B = nif bone matrix
            # O = blender object transform
            # T = bone tail matrix (translation in Y direction)
            # B' = blender bone matrix
            # so we need to cancel out the object transformation by
            # v' = v * R * B * B'^{-1} * T^{-1} * O^{-1}

            # the local rotation L at the pivot point must be such that
            # (axis_z + v) * R * B = ([0 0 1] * L + v') * O * T * B'
            # so (taking the rotation parts of all matrices!!!)
            # [0 0 1] * L = axis_z * R * B * B'^{-1} * T^{-1} * O^{-1}
            # and similarly
            # [1 0 0] * L = axis_x * R * B * B'^{-1} * T^{-1} * O^{-1}
            # hence these give us the first and last row of L
            # which is exactly enough to provide the euler angles

            # multiply with rigid body transform
            if isinstance(hkbody, NifFormat.bhkRigidBodyT):
                # set rotation
                transform = mathutils.Quaternion(
                    (hkbody.rotation.w, hkbody.rotation.x, hkbody.rotation.y,
                     hkbody.rotation.z)).to_matrix()
                transform.resize_4x4()
                # set translation
                transform[0][3] = hkbody.translation.x * self.HAVOK_SCALE
                transform[1][3] = hkbody.translation.y * self.HAVOK_SCALE
                transform[2][3] = hkbody.translation.z * self.HAVOK_SCALE
                # apply transform
                pivot = pivot * transform
                transform = transform.to_3x3()
                axis_z = axis_z * transform
                axis_x = axis_x * transform

            # TODO: update this to use the new bone system
            # # next, cancel out bone matrix correction
            # # note that B' = X * B with X = self.nif_import.dict_bones_extra_matrix[B]
            # # so multiply with the inverse of X
            # for niBone in self.nif_import.dict_bones_extra_matrix:
            # if niBone.collision_object \
            # and niBone.collision_object.body is hkbody:
            # transform = mathutils.Matrix(
            # self.nif_import.dict_bones_extra_matrix[niBone])
            # transform.invert()
            # pivot = pivot * transform
            # transform = transform.to_3x3()
            # axis_z = axis_z * transform
            # axis_x = axis_x * transform
            # break

            # cancel out bone tail translation
            if b_hkobj.parent_bone:
                pivot[1] -= b_hkobj.parent.data.bones[
                    b_hkobj.parent_bone].length

            # cancel out object transform
            transform = mathutils.Matrix(b_hkobj.matrix_local)
            transform.invert()
            pivot = pivot * transform
            transform = transform.to_3x3()
            axis_z = axis_z * transform
            axis_x = axis_x * transform

            # set pivot point
            b_constr.pivot_x = pivot[0]
            b_constr.pivot_y = pivot[1]
            b_constr.pivot_z = pivot[2]

            # set euler angles
            constr_matrix = mathutils.Matrix(
                (axis_x, mathutils.Vector.cross(axis_z, axis_x), axis_z))
            constr_euler = constr_matrix.to_euler()
            b_constr.axis_x = constr_euler.x
            b_constr.axis_y = constr_euler.y
            b_constr.axis_z = constr_euler.z
            # DEBUG
            assert ((axis_x - mathutils.Vector(
                (1, 0, 0)) * constr_matrix).length < 0.0001)
            assert ((axis_z - mathutils.Vector(
                (0, 0, 1)) * constr_matrix).length < 0.0001)

            # the generic rigid body type is very buggy... so for simulation
            # purposes let's transform it into ball and hinge
            if isinstance(hkdescriptor, NifFormat.RagdollDescriptor):
                # cone_twist
                b_constr.pivot_type = 'CONE_TWIST'
            elif isinstance(
                    hkdescriptor,
                (NifFormat.LimitedHingeDescriptor, NifFormat.HingeDescriptor)):
                # (limited) hinge
                b_constr.pivot_type = 'HINGE'
            else:
                raise ValueError("unknown descriptor %s" %
                                 hkdescriptor.__class__.__name__)
示例#16
0
    def determine_texture_types(self, b_obj, b_mat):

        used_slots = self.get_used_textslots(b_mat)
        self.base_mtex = None
        self.bump_mtex = None
        self.dark_mtex = None
        self.detail_mtex = None
        self.gloss_mtex = None
        self.glow_mtex = None
        self.normal_mtex = None
        self.ref_mtex = None

        for b_mat_texslot in used_slots:
            # check REFL-mapped textures
            # (used for "NiTextureEffect" materials)
            if b_mat_texslot.texture_coords == 'REFLECTION':
                if not b_mat_texslot.use_map_color_diffuse:
                    # it should map to colour
                    raise nif_utils.NifError("Non-COL-mapped reflection texture in mesh '%s', material '%s', these cannot be exported to NIF.\n"
                                             "Either delete all non-COL-mapped reflection textures, or in the Shading Panel, under Material Buttons, set texture 'Map To' to 'COL'." % (b_obj.name, b_mat.name))
                if b_mat_texslot.blend_type != 'ADD':
                    # it should have "ADD" blending mode
                    NifLog.warn("Reflection texture should have blending mode 'Add' on texture in mesh '{0}', material '{1}').".format(b_obj.name, b_mat.name))
                # an envmap image should have an empty... don't care
                self.ref_mtex = b_mat_texslot

            # check UV-mapped textures
            elif b_mat_texslot.texture_coords == 'UV':

                # update set of uv layers that must be exported
                if not b_mat_texslot.uv_layer in self.nif_export.dict_mesh_uvlayers:
                    self.nif_export.dict_mesh_uvlayers.append(b_mat_texslot.uv_layer)

                # glow tex
                if b_mat_texslot.use_map_emit:
                    # multi-check
                    if self.glow_mtex:
                        raise nif_utils.NifError("Multiple emissive textures in mesh '%s', material '%s'.\n"
                                                 " Make sure there is only one texture set as Influence > emit" % (b_obj.name, b_mat.name))

                    # check if alpha channel is enabled for this texture
                    if b_mat_texslot.use_map_alpha:
                        mesh_hasalpha = True

                    self.glow_mtex = b_mat_texslot

                # specular
                elif b_mat_texslot.use_map_specular or b_mat_texslot.use_map_color_spec:
                    # multi-check
                    if self.gloss_mtex:
                        raise nif_utils.NifError("Multiple specular gloss textures in mesh '%s', material '%s'.\n"
                                                 "Make sure there is only one texture set as Influence > specular" % (b_obj.name, b_mat.name))

                    # check if alpha channel is enabled for this texture
                    if b_mat_texslot.use_map_alpha:
                        mesh_hasalpha = True

                    # got the gloss map
                    self.gloss_mtex = b_mat_texslot

                # bump map
                elif b_mat_texslot.use_map_normal and \
                        b_mat_texslot.texture.use_normal_map == False:
                    # multi-check
                    if self.bump_mtex:
                        raise nif_utils.NifError("Multiple bump/normal texture in mesh '%s', material '%s'.\n"
                                                 "Make sure there is only one texture set as Influence > normal" % (b_obj.name, b_mat.name))

                    # check if alpha channel is enabled for this texture
                    if b_mat_texslot.use_map_alpha:
                        mesh_hasalpha = True

                    self.bump_mtex = b_mat_texslot

                # normal map
                elif b_mat_texslot.use_map_normal and b_mat_texslot.texture.use_normal_map:
                    # multi-check
                    if self.normal_mtex:
                        raise nif_utils.NifError("Multiple bump/normal textures in mesh '%s', material '%s'."
                                                 " Make sure there is only one texture set as Influence > normal" % (b_obj.name, b_mat.name))
                    # check if alpha channel is enabled for this texture
                    if b_mat_texslot.use_map_alpha:
                        mesh_hasalpha = True
                    self.normal_mtex = b_mat_texslot

                # darken
                elif b_mat_texslot.use_map_color_diffuse and b_mat_texslot.blend_type == 'DARKEN':

                    if self.dark_mtex:
                        raise nif_utils.NifError("Multiple Darken textures in mesh '%s', material '%s'."
                                                 " Make sure there is only one texture with Influence > Blend Type > Dark" % (b_obj.name, b_mat.name))

                    # check if alpha channel is enabled for this texture
                    if b_mat_texslot.use_map_alpha:
                        mesh_hasalpha = True
                    # got the dark map
                    self.dark_mtex = b_mat_texslot

                # diffuse
                elif b_mat_texslot.use_map_color_diffuse:
                    if self.base_mtex:
                        raise nif_utils.NifError("Multiple Diffuse textures in mesh '%s', material '%s'.\n"
                                                 "Make sure there is only one texture with Influence > Diffuse > color" % (b_obj.name, b_mat.name))

                    self.base_mtex = b_mat_texslot

                    # check if alpha channel is enabled for this texture
                    if b_mat_texslot.use_map_alpha:
                        mesh_hasalpha = True

                        '''
                        # in this case, Blender replaces the texture transparant parts with the underlying material color...
                        # in NIF, material alpha is multiplied with texture alpha channel...
                        # how can we emulate the NIF alpha system (simply multiplying material alpha with texture alpha) when MapTo.ALPHA is turned on?
                        # require the Blender material alpha to be 0.0 (no material color can show up), and use the "Var" slider in the texture blending mode tab!
                        # but...
    
                        if mesh_mat_transparency > NifOp.props.epsilon:
                            raise nif_utils.NifError(
                                "Cannot export this type of"
                                " transparency in material '%s': "
                                " instead, try to set alpha to 0.0"
                                " and to use the 'Var' slider"
                                " in the 'Map To' tab under the"
                                " material buttons."
                                %b_mat.name)
                        if (b_mat.animation_data and b_mat.animation_data.action.fcurves['Alpha']):
                            raise nif_utils.NifError(
                                "Cannot export animation for"
                                " this type of transparency"
                                " in material '%s':"
                                " remove alpha animation,"
                                " or turn off MapTo.ALPHA,"
                                " and try again."
                                %b_mat.name)
    
                        mesh_mat_transparency = b_mat_texslot.varfac # we must use the "Var" value
                        '''

                # detail
                elif b_mat_texslot.use_map_color_diffuse:
                    if self.detail_mtex:
                        raise nif_utils.NifError("Multiple detail textures in mesh '%s', material '%s'.\n" 
                                                 " Make sure there is only one texture with Influence Diffuse > color" % (b_obj.name, b_mat.name))
                    # extra diffuse consider as detail texture

                    # check if alpha channel is enabled for this texture
                    if b_mat_texslot.use_map_alpha:
                        mesh_hasalpha = True
                    self.detail_mtex = b_mat_texslot

                # reflection
                elif b_mat_texslot.use_map_mirror or b_mat_texslot.use_map_raymir:
                    # multi-check
                    if self.glow_mtex:
                        raise nif_utils.NifError("Multiple reflection textures in mesh '%s', material '%s'.\n"
                                                 "Make sure there is only one texture set as Influence > Mirror/Ray Mirror" % (b_obj.name, b_mat.name))
                    # got the reflection map
                    # check if alpha channel is enabled for this texture
                    if b_mat_texslot.use_map_alpha:
                        mesh_hasalpha = True
                    self.ref_mtex = b_mat_texslot

                # unsupported map
                else:
                    raise nif_utils.NifError("Do not know how to export texture '%s', in mesh '%s', material '%s'.\n"
                                             "Either delete it, or if this texture is to be your base texture.\n"
                                             "Go to the Shading Panel Material Buttons, and set texture 'Map To' to 'COL'."
                        % (b_mat_texslot.texture.name, b_obj.name, b_mat.name))

            # nif only support UV-mapped textures
            else:
                NifLog.warn("Non-UV texture in mesh '{0}', material '{1}'.\nEither delete all non-UV textures or "
                            "create a UV map for every texture associated with selected object and run the script again.".format(b_obj.name, b_mat.name))
示例#17
0
    def execute(self):
        """Main export function."""
        if bpy.context.mode != 'OBJECT':
            bpy.ops.object.mode_set(mode='OBJECT', toggle=False)

        NifLog.info("Exporting {0}".format(NifOp.props.filepath))

        # TODO:
        '''
        if NifOp.props.animation == 'ALL_NIF_XNIF_XKF' and NifOp.props.game == 'MORROWIND':
            # if exporting in nif+xnif+kf mode, then first export
            # the nif with geometry + animation, which is done by:
            NifOp.props.animation = 'ALL_NIF'
        '''

        # extract directory, base name, extension
        directory = os.path.dirname(NifOp.props.filepath)
        filebase, fileext = os.path.splitext(
            os.path.basename(NifOp.props.filepath))

        self.dict_armatures = {}
        self.dict_bone_priorities = {}
        self.dict_havok_objects = {}
        self.dict_names = {}
        self.dict_blocks = {}
        self.dict_block_names = []
        self.dict_materials = {}
        self.dict_textures = {}
        self.dict_mesh_uvlayers = []

        # if an egm is exported, this will contain the data
        self.egm_data = None

        try:  # catch export errors

            for b_obj in bpy.data.objects:
                # armatures should not be in rest position
                if b_obj.type == 'ARMATURE':
                    # ensure we get the mesh vertices in animation mode,
                    # and not in rest position!
                    b_obj.data.pose_position = 'POSE'

                if b_obj.type == 'MESH':
                    # TODO - Need to implement correct armature checking
                    # b_armature_modifier = None
                    b_obj_name = b_obj.name
                    if b_obj.parent:
                        for b_mod in bpy.data.objects[b_obj_name].modifiers:
                            if b_mod.type == 'ARMATURE':
                                # b_armature_modifier = b_mod.name
                                if b_mod.use_bone_envelopes:
                                    raise nif_utils.NifError(
                                        "'%s': Cannot export envelope skinning. If you have vertex groups, turn off envelopes.\n"
                                        "If you don't have vertex groups, select the bones one by one press W to "
                                        "convert their envelopes to vertex weights, and turn off envelopes."
                                        % b_obj.name)
                            # if not b_armature_modifier:
                            #     raise nif_utils.NifError("'%s': is parented but does not have"
                            #                              " the armature modifier set. This will"
                            #                              " cause animations to fail."
                            #                              % b_obj.name)

                # check for non-uniform transforms
                # (lattices are not exported so ignore them as they often tend
                # to have non-uniform scaling)
                if b_obj.type != 'LATTICE':
                    scale = b_obj.matrix_local.to_scale()
                    if abs(scale.x - scale.y) > NifOp.props.epsilon or abs(
                            scale.y - scale.z) > NifOp.props.epsilon:
                        raise nif_utils.NifError(
                            "Non-uniform scaling not supported.\n "
                            "Workaround: apply size and rotation (CTRL-A) on '%s'."
                            % b_obj.name)

            root_name = filebase
            # get the root object from selected object
            # only export empties, meshes, and armatures
            if not bpy.context.selected_objects:
                raise nif_utils.NifError(
                    "Please select the object(s) to export, and run this script again."
                )

            root_objects = set()
            export_types = ('EMPTY', 'MESH', 'ARMATURE')
            exportable_objects = [
                b_obj for b_obj in bpy.context.selected_objects
                if b_obj.type in export_types
            ]
            for root_object in exportable_objects:
                while root_object.parent:
                    root_object = root_object.parent
                if NifOp.props.game in ('CIVILIZATION_IV', 'OBLIVION',
                                        'FALLOUT_3', 'ZOO_TYCOON_2'):
                    if (root_object.type
                            == 'ARMATURE') or (root_object.name.lower()
                                               == "bip01"):
                        root_name = 'Scene Root'
                # TODO remove as already filtered
                if root_object.type not in export_types:
                    raise nif_utils.NifError(
                        "Root object (%s) must be an 'EMPTY', 'MESH', or 'ARMATURE' object."
                        % root_object.name)
                root_objects.add(root_object)

            # smooth seams of objects
            if NifOp.props.smooth_object_seams:
                self.objecthelper.mesh_helper.smooth_mesh_seams(
                    bpy.context.scene.objects)

            # TODO: use Blender actions for animation groups
            # check for animation groups definition in a text buffer 'Anim'
            try:
                animtxt = None  # Changed for testing needs fix bpy.data.texts["Anim"]
            except NameError:
                animtxt = None

            # rebuild the full name dictionary from the 'FullNames' text buffer
            self.objecthelper.rebuild_full_names()

            # export nif:
            # -----------
            NifLog.info("Exporting")

            # find nif version to write
            # TODO Move fully to scene level
            self.version = NifOp.op.version[NifOp.props.game]
            self.user_version, self.user_version_2 = scene_export.get_version_info(
                NifOp.props)
            #the axes used for bone correction depend on the nif version
            armature.set_bone_correction_from_version(self.version)

            # create a nif object

            # export the root node (the name is fixed later to avoid confusing the
            # exporter with duplicate names)
            root_block = self.objecthelper.export_node(None, None, '')

            # TODO Move to object system and redo
            # export objects
            NifLog.info("Exporting objects")
            for root_object in root_objects:
                if NifOp.props.game in 'SKYRIM':
                    if root_object.niftools_bs_invmarker:
                        for extra_item in root_block.extra_data_list:
                            if isinstance(extra_item, NifFormat.BSInvMarker):
                                raise nif_utils.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
                            n_extra_list.rotation_y = root_object.niftools_bs_invmarker[
                                0].bs_inv_y
                            n_extra_list.rotation_z = root_object.niftools_bs_invmarker[
                                0].bs_inv_z
                            n_extra_list.zoom = root_object.niftools_bs_invmarker[
                                0].bs_inv_zoom

                            root_block.add_extra_data(n_extra_list)

                # export the root objects as a NiNodes; their children are
                # exported as well
                self.objecthelper.export_node(root_object, root_block,
                                              root_object.name)

            # post-processing:
            # ----------------

            # if we exported animations, but no animation groups are defined,
            # define a default animation group
            NifLog.info("Checking animation groups")
            if not animtxt:
                has_controllers = False
                for block in self.dict_blocks:
                    # has it a controller field?
                    if isinstance(block, NifFormat.NiObjectNET):
                        if block.controller:
                            has_controllers = True
                            break
                if has_controllers:
                    NifLog.info("Defining default animation group.")
                    # write the animation group text buffer
                    animtxt = bpy.data.texts.new("Anim")
                    animtxt.write(
                        "%i/Idle: Start/Idle: Loop Start\n%i/Idle: Loop Stop/Idle: Stop"
                        % (bpy.context.scene.frame_start,
                           bpy.context.scene.frame_end))

            # animations without keyframe animations crash the TESCS
            # if we are in that situation, add a trivial keyframe animation
            NifLog.info("Checking controllers")
            if animtxt and NifOp.props.game == 'MORROWIND':
                has_keyframecontrollers = False
                for block in self.dict_blocks:
                    if isinstance(block, NifFormat.NiKeyframeController):
                        has_keyframecontrollers = True
                        break
                if ((not has_keyframecontrollers)
                        and (not NifOp.props.bs_animation_node)):
                    NifLog.info("Defining dummy keyframe controller")
                    # add a trivial keyframe controller on the scene root
                    self.animationhelper.export_keyframes(root_block)

            if NifOp.props.bs_animation_node and NifOp.props.game == 'MORROWIND':
                for block in self.dict_blocks:
                    if isinstance(block, NifFormat.NiNode):
                        # if any of the shape children has a controller
                        # or if the ninode has a controller
                        # convert its type
                        if block.controller or any(
                                child.controller for child in block.children
                                if isinstance(child, NifFormat.NiGeometry)):
                            new_block = NifFormat.NiBSAnimationNode().deepcopy(
                                block)
                            # have to change flags to 42 to make it work
                            new_block.flags = 42
                            root_block.replace_global_node(block, new_block)
                            if root_block is block:
                                root_block = new_block

            # oblivion skeleton export: check that all bones have a
            # transform controller and transform interpolator
            if NifOp.props.game in ('OBLIVION', 'FALLOUT_3',
                                    'SKYRIM') and filebase.lower() in (
                                        'skeleton', 'skeletonbeast'):
                # here comes everything that is Oblivion skeleton export specific
                NifLog.info(
                    "Adding controllers and interpolators for skeleton")
                for block in list(self.dict_blocks.keys()):
                    if isinstance(block, NifFormat.NiNode
                                  ) and block.name.decode() == "Bip01":
                        for bone in block.tree(block_type=NifFormat.NiNode):
                            ctrl = self.objecthelper.create_block(
                                "NiTransformController")
                            interp = self.objecthelper.create_block(
                                "NiTransformInterpolator")

                            ctrl.interpolator = interp
                            bone.add_controller(ctrl)

                            ctrl.flags = 12
                            ctrl.frequency = 1.0
                            ctrl.phase = 0.0
                            ctrl.start_time = self.FLOAT_MAX
                            ctrl.stop_time = self.FLOAT_MIN
                            interp.translation.x = bone.translation.x
                            interp.translation.y = bone.translation.y
                            interp.translation.z = bone.translation.z
                            scale, quat = bone.rotation.get_scale_quat()
                            interp.rotation.x = quat.x
                            interp.rotation.y = quat.y
                            interp.rotation.z = quat.z
                            interp.rotation.w = quat.w
                            interp.scale = bone.scale
            else:
                # here comes everything that should be exported EXCEPT
                # for Oblivion skeleton exports
                # export animation groups (not for skeleton.nif export!)
                if animtxt:
                    # TODO: removed temorarily to process bseffectshader export
                    anim_textextra = None  # self.animationhelper.export_text_keys(root_block)
                else:
                    anim_textextra = None

            # oblivion and Fallout 3 furniture markers
            if NifOp.props.game in (
                    'OBLIVION', 'FALLOUT_3',
                    'SKYRIM') and filebase[:15].lower() == 'furnituremarker':
                # exporting a furniture marker for Oblivion/FO3
                try:
                    furniturenumber = int(filebase[15:])
                except ValueError:
                    raise nif_utils.NifError(
                        "Furniture marker has invalid number (%s).\n"
                        "Name your file 'furnituremarkerxx.nif' where xx is a number between 00 and 19."
                        % filebase[15:])
                # name scene root name the file base name
                root_name = filebase

                # create furniture marker block
                furnmark = self.objecthelper.create_block("BSFurnitureMarker")
                furnmark.name = "FRN"
                furnmark.num_positions = 1
                furnmark.positions.update_size()
                furnmark.positions[0].position_ref_1 = furniturenumber
                furnmark.positions[0].position_ref_2 = furniturenumber

                # create extra string data sgoKeep
                sgokeep = self.objecthelper.create_block("NiStringExtraData")
                sgokeep.name = "UPB"  # user property buffer
                sgokeep.string_data = "sgoKeep=1 ExportSel = Yes"  # Unyielding = 0, sgoKeep=1ExportSel = Yes

                # add extra blocks
                root_block.add_extra_data(furnmark)
                root_block.add_extra_data(sgokeep)

            # FIXME:
            NifLog.info("Checking collision")
            # activate oblivion/Fallout 3 collision and physics
            if NifOp.props.game in ('OBLIVION', 'FALLOUT_3', 'SKYRIM'):
                hascollision = False
                for b_obj in bpy.data.objects:
                    if b_obj.game.use_collision_bounds:
                        hascollision = True
                        break
                if hascollision:
                    # enable collision
                    bsx = self.objecthelper.create_block("BSXFlags")
                    bsx.name = 'BSX'
                    bsx.integer_data = b_obj.niftools.bsxflags
                    root_block.add_extra_data(bsx)

                    # many Oblivion nifs have a UPB, but export is disabled as
                    # they do not seem to affect anything in the game
                    if b_obj.niftools.upb:
                        upb = self.objecthelper.create_block(
                            "NiStringExtraData")
                        upb.name = 'UPB'
                        if b_obj.niftools.upb == '':
                            upb.string_data = 'Mass = 0.000000\r\nEllasticity = 0.300000\r\nFriction = 0.300000\r\nUnyielding = 0\r\nSimulation_Geometry = 2\r\nProxy_Geometry = <None>\r\nUse_Display_Proxy = 0\r\nDisplay_Children = 1\r\nDisable_Collisions = 0\r\nInactive = 0\r\nDisplay_Proxy = <None>\r\n'
                        else:
                            upb.string_data = b_obj.niftools.upb.encode()
                        root_block.add_extra_data(upb)

                # update rigid body center of gravity and mass
                if self.EXPORT_OB_COLLISION_DO_NOT_USE_BLENDER_PROPERTIES:
                    # we are not using blender properties to set the mass
                    # so calculate mass automatically first calculate distribution of mass
                    total_mass = 0
                    for block in self.dict_blocks:
                        if isinstance(block, NifFormat.bhkRigidBody):
                            block.update_mass_center_inertia(
                                solid=self.EXPORT_OB_SOLID)
                            total_mass += block.mass

                    if total_mass == 0:
                        # to avoid zero division error later (if mass is zero then this does not matter anyway)
                        total_mass = 1

                    # now update the mass ensuring that total mass is self.EXPORT_OB_MASS
                    for block in self.dict_blocks:
                        if isinstance(block, NifFormat.bhkRigidBody):
                            mass = self.EXPORT_OB_MASS * block.mass / total_mass
                            # lower bound on mass
                            if mass < 0.0001:
                                mass = 0.05
                            block.update_mass_center_inertia(
                                mass=mass, solid=self.EXPORT_OB_SOLID)
                else:
                    # using blender properties, so block.mass *should* have
                    # been set properly
                    for block in self.dict_blocks:
                        if isinstance(block, NifFormat.bhkRigidBody):
                            # lower bound on mass
                            if block.mass < 0.0001:
                                block.mass = 0.05
                            block.update_mass_center_inertia(
                                mass=block.mass, solid=self.EXPORT_OB_SOLID)

            # bhkConvexVerticesShape of children of bhkListShapes need an extra bhkConvexTransformShape (see issue #3308638, reported by Koniption)
            # note: self.dict_blocks changes during iteration, so need list copy
            for block in list(self.dict_blocks):
                if isinstance(block, NifFormat.bhkListShape):
                    for i, sub_shape in enumerate(block.sub_shapes):
                        if isinstance(sub_shape,
                                      NifFormat.bhkConvexVerticesShape):
                            coltf = self.objecthelper.create_block(
                                "bhkConvexTransformShape")
                            coltf.material = sub_shape.material
                            coltf.unknown_float_1 = 0.1
                            coltf.unknown_8_bytes[0] = 96
                            coltf.unknown_8_bytes[1] = 120
                            coltf.unknown_8_bytes[2] = 53
                            coltf.unknown_8_bytes[3] = 19
                            coltf.unknown_8_bytes[4] = 24
                            coltf.unknown_8_bytes[5] = 9
                            coltf.unknown_8_bytes[6] = 253
                            coltf.unknown_8_bytes[7] = 4
                            coltf.transform.set_identity()
                            coltf.shape = sub_shape
                            block.sub_shapes[i] = coltf

            # export constraints
            for b_obj in self.objecthelper.get_exported_objects():
                if isinstance(b_obj, bpy.types.Object) and b_obj.constraints:
                    self.constrainthelper.export_constraints(b_obj, root_block)

            # export weapon location
            if NifOp.props.game in ('OBLIVION', 'FALLOUT_3', 'SKYRIM'):
                if self.EXPORT_OB_PRN != "NONE":
                    # add string extra data
                    prn = self.objecthelper.create_block("NiStringExtraData")
                    prn.name = 'Prn'
                    prn.string_data = {
                        "BACK": "BackWeapon",
                        "SIDE": "SideWeapon",
                        "QUIVER": "Quiver",
                        "SHIELD": "Bip01 L ForearmTwist",
                        "HELM": "Bip01 Head",
                        "RING": "Bip01 R Finger1"
                    }[self.EXPORT_OB_PRN]
                    root_block.add_extra_data(prn)

            # add vertex color and zbuffer properties for civ4 and railroads
            if NifOp.props.game in ('CIVILIZATION_IV',
                                    'SID_MEIER_S_RAILROADS'):
                self.propertyhelper.object_property.export_vertex_color_property(
                    root_block)
                self.propertyhelper.object_property.export_z_buffer_property(
                    root_block)
            elif NifOp.props.game in ('EMPIRE_EARTH_II', ):
                self.propertyhelper.object_property.export_vertex_color_property(
                    root_block)
                self.propertyhelper.object_property.export_z_buffer_property(
                    root_block, flags=15, function=1)

            # FIXME:
            """
            if self.EXPORT_FLATTENSKIN:
                # (warning: trouble if armatures parent other armatures or
                # if bones parent geometries, or if object is animated)
                # flatten skins
                skelroots = set()
                affectedbones = []
                for block in self.dict_blocks:
                    if isinstance(block, NifFormat.NiGeometry) and block.is_skin():
                        NifLog.info("Flattening skin on geometry {0}".format(block.name))
                        affectedbones.extend(block.flatten_skin())
                        skelroots.add(block.skin_instance.skeleton_root)
                # remove NiNodes that do not affect skin
                for skelroot in skelroots:
                    NifLog.info("Removing unused NiNodes in '{0}'".format(skelroot.name))
                    skelrootchildren = [child for child in skelroot.children
                                        if ((not isinstance(child,
                                                            NifFormat.NiNode))
                                            or (child in affectedbones))]
                    skelroot.num_children = len(skelrootchildren)
                    skelroot.children.update_size()
                    for i, child in enumerate(skelrootchildren):
                        skelroot.children[i] = child
            """

            # apply scale
            if abs(NifOp.props.scale_correction_export) > NifOp.props.epsilon:
                NifLog.info("Applying scale correction {0}".format(
                    str(NifOp.props.scale_correction_export)))
                data = NifFormat.Data()
                data.roots = [root_block]
                toaster = pyffi.spells.nif.NifToaster()
                toaster.scale = NifOp.props.scale_correction_export
                pyffi.spells.nif.fix.SpellScale(data=data,
                                                toaster=toaster).recurse()
                # also scale egm
                if self.egm_data:
                    self.egm_data.apply_scale(
                        NifOp.props.scale_correction_export)

            # generate mopps (must be done after applying scale!)
            if NifOp.props.game in ('OBLIVION', 'FALLOUT_3', 'SKYRIM'):
                for block in self.dict_blocks:
                    if isinstance(block, NifFormat.bhkMoppBvTreeShape):
                        NifLog.info("Generating mopp...")
                        block.update_mopp()
                        # print "=== DEBUG: MOPP TREE ==="
                        # block.parse_mopp(verbose = True)
                        # print "=== END OF MOPP TREE ==="
                        # warn about mopps on non-static objects
                        if any(sub_shape.layer != 1
                               for sub_shape in block.shape.sub_shapes):
                            NifLog.warn(
                                "Mopps for non-static objects may not function correctly in-game. You may wish to use simple primitives for collision."
                            )

            # delete original scene root if a scene root object was already defined
            if root_block.num_children == 1 and (
                    root_block.children[0].name in ['Scene Root', 'Bip01']
                    or root_block.children[0].name[-3:] == 'nif'):
                if root_block.children[0].name[-3:] == 'nif':
                    root_block.children[0].name = filebase
                NifLog.info("Making '{0}' the root block".format(
                    root_block.children[0].name))
                # remove root_block from self.dict_blocks
                self.dict_blocks.pop(root_block)
                # set new root block
                old_root_block = root_block
                root_block = old_root_block.children[0]
                # copy extra data and properties
                for extra in old_root_block.get_extra_datas():
                    # delete links in extras to avoid parentship problems
                    extra.next_extra_data = None
                    # now add it
                    root_block.add_extra_data(extra)
                for b in old_root_block.get_controllers():
                    root_block.add_controller(b)
                for b in old_root_block.properties:
                    root_block.add_property(b)
                for b in old_root_block.effects:
                    root_block.add_effect(b)
            else:
                root_block.name = root_name

            self.root_ninode = None
            for root_obj in root_objects:
                if root_obj.niftools.rootnode == 'BSFadeNode':
                    self.root_ninode = 'BSFadeNode'
                elif self.root_ninode is None:
                    self.root_ninode = 'NiNode'

            # making root block a fade node
            if NifOp.props.game in ('FALLOUT_3', 'SKYRIM'
                                    ) and self.root_ninode == 'BSFadeNode':
                NifLog.info("Making root block a BSFadeNode")
                fade_root_block = NifFormat.BSFadeNode().deepcopy(root_block)
                fade_root_block.replace_global_node(root_block,
                                                    fade_root_block)
                root_block = fade_root_block

            export_animation = NifOp.props.animation
            if export_animation == 'ALL_NIF':
                NifLog.info("Exporting geometry and animation")
            elif export_animation == 'GEOM_NIF':
                # for morrowind: everything except keyframe controllers
                NifLog.info("Exporting geometry only")
            elif export_animation == 'ANIM_KF':
                # for morrowind: only keyframe controllers
                NifLog.info("Exporting animation only (as .kf file)")

            # export nif file:
            # ----------------

            NifLog.info("Writing NIF version 0x%08X" % self.version)

            if export_animation != 'ANIM_KF':
                if NifOp.props.game == 'EMPIRE_EARTH_II':
                    ext = ".nifcache"
                else:
                    ext = ".nif"
                NifLog.info("Writing {0} file".format(ext))

                # make sure we have the right file extension
                if fileext.lower() != ext:
                    NifLog.warn(
                        "Changing extension from {0} to {1} on output file".
                        format(fileext, ext))
                niffile = os.path.join(directory, filebase + ext)

                data = NifFormat.Data(version=self.version,
                                      user_version=self.user_version,
                                      user_version_2=self.user_version_2)
                data.roots = [root_block]
                if NifOp.props.game == 'NEOSTEAM':
                    data.modification = "neosteam"
                elif NifOp.props.game == 'ATLANTICA':
                    data.modification = "ndoors"
                elif NifOp.props.game == 'HOWLING_SWORD':
                    data.modification = "jmihs1"
                with open(niffile, "wb") as stream:
                    data.write(stream)

            # create and export keyframe file and xnif file:
            # ----------------------------------------------

            # convert root_block tree into a keyframe tree
            if export_animation == 'ANIM_KF' or export_animation == 'ALL_NIF_XNIF_XKF':
                NifLog.info("Creating keyframe tree")
                # find all nodes and relevant controllers
                node_kfctrls = {}
                for node in root_block.tree():
                    if not isinstance(node, NifFormat.NiAVObject):
                        continue
                    # get list of all controllers for this node
                    ctrls = node.get_controllers()
                    for ctrl in ctrls:
                        if NifOp.props.game == 'MORROWIND':
                            # morrowind: only keyframe controllers
                            if not isinstance(ctrl,
                                              NifFormat.NiKeyframeController):
                                continue
                        if node not in node_kfctrls:
                            node_kfctrls[node] = []
                        node_kfctrls[node].append(ctrl)
                # morrowind
                if NifOp.props.game in ('MORROWIND', 'FREEDOM_FORCE'):
                    # create kf root header
                    kf_root = self.objecthelper.create_block(
                        "NiSequenceStreamHelper")
                    kf_root.add_extra_data(anim_textextra)
                    # reparent controller tree
                    for node, ctrls in node_kfctrls.items():
                        for ctrl in ctrls:
                            # create node reference by name
                            nodename_extra = self.objecthelper.create_block(
                                "NiStringExtraData")
                            nodename_extra.bytes_remaining = len(node.name) + 4
                            nodename_extra.string_data = node.name

                            # break the controller chain
                            ctrl.next_controller = None

                            # add node reference and controller
                            kf_root.add_extra_data(nodename_extra)
                            kf_root.add_controller(ctrl)
                            # wipe controller target
                            ctrl.target = None
                # oblivion
                elif NifOp.props.game in ('OBLIVION', 'FALLOUT_3',
                                          'CIVILIZATION_IV', 'ZOO_TYCOON_2',
                                          'FREEDOM_FORCE_VS_THE_3RD_REICH'):
                    # TODO [animation] allow for object kf only
                    # create kf root header
                    kf_root = self.objecthelper.create_block(
                        "NiControllerSequence")
                    # if self.EXPORT_ANIMSEQUENCENAME:
                    # kf_root.name = self.EXPORT_ANIMSEQUENCENAME
                    # else:
                    kf_root.name = filebase
                    kf_root.unknown_int_1 = 1
                    kf_root.weight = 1.0
                    kf_root.text_keys = anim_textextra
                    kf_root.cycle_type = NifFormat.CycleType.CYCLE_CLAMP
                    kf_root.frequency = 1.0
                    kf_root.start_time = bpy.context.scene.frame_start * bpy.context.scene.render.fps
                    kf_root.stop_time = (bpy.context.scene.frame_end -
                                         bpy.context.scene.frame_start
                                         ) * bpy.context.scene.render.fps
                    # quick hack to set correct target name
                    if "Bip01" in b_armature.data.bones:
                        targetname = "Bip01"
                    elif "Bip02" in b_armature.data.bones:
                        targetname = "Bip02"
                    else:
                        targetname = root_block.name
                    kf_root.target_name = targetname
                    kf_root.string_palette = NifFormat.NiStringPalette()
                    b_armature = armature.get_armature()
                    # per-node animation
                    if b_armature:
                        for b_bone in b_armature.data.bones:
                            self.animationhelper.export_keyframes(
                                kf_root, b_armature, b_bone)
                    # per-object animation
                    else:
                        for b_obj in bpy.data.objects:
                            self.animationhelper.export_keyframes(
                                kf_root, b_obj)

                    # for node, ctrls in zip(iter(node_kfctrls.keys()), iter(node_kfctrls.values())):
                    # # export a block for every interpolator in every controller
                    # for ctrl in ctrls:
                    # # XXX add get_interpolators to pyffi interface
                    # if isinstance(ctrl, NifFormat.NiSingleInterpController):
                    # interpolators = [ctrl.interpolator]
                    # elif isinstance( ctrl, (NifFormat.NiGeomMorpherController, NifFormat.NiMorphWeightsController)):
                    # interpolators = ctrl.interpolators

                    # if isinstance(ctrl, NifFormat.NiGeomMorpherController):
                    # variable_2s = [morph.frame_name for morph in ctrl.data.morphs]
                    # else:
                    # variable_2s = [None for interpolator in interpolators]
                    # for interpolator, variable_2 in zip(interpolators, variable_2s):
                    # # create ControlledLink for each interpolator
                    # controlledblock = kf_root.add_controlled_block()
                    # if self.version < 0x0A020000:
                    # # older versions need the actual controller blocks
                    # controlledblock.target_name = node.name
                    # controlledblock.controller = ctrl
                    # # erase reference to target node
                    # ctrl.target = None
                    # else:
                    # # newer versions need the interpolator blocks
                    # controlledblock.interpolator = interpolator
                    # # get bone animation priority (previously fetched from the constraints during export_bones)
                    # if not node.name in self.dict_bone_priorities or self.EXPORT_ANIM_DO_NOT_USE_BLENDER_PROPERTIES:
                    # if self.EXPORT_ANIMPRIORITY != 0:
                    # priority = self.EXPORT_ANIMPRIORITY
                    # else:
                    # priority = 26
                    # NifLog.warn("No priority set for bone {0}, falling back on default value ({1})".format(node.name, str(priority)))
                    # else:
                    # priority = self.dict_bone_priorities[node.name]
                    # controlledblock.priority = priority
                    # # set palette, and node and controller type names, and variables
                    # controlledblock.string_palette = kf_root.string_palette
                    # controlledblock.set_node_name(node.name)
                    # controlledblock.set_controller_type(ctrl.__class__.__name__)
                    # if variable_2:
                    # controlledblock.set_variable_2(variable_2)
                else:
                    raise nif_utils.NifError(
                        "Keyframe export for '%s' is not supported.\nOnly Morrowind, Oblivion, Fallout 3, Civilization IV,"
                        " Zoo Tycoon 2, Freedom Force, and Freedom Force vs. the 3rd Reich keyframes are supported."
                        % NifOp.props.game)

                # write kf (and xnif if asked)
                prefix = "" if (
                    export_animation != 'ALL_NIF_XNIF_XKF') else "x"

                ext = ".kf"
                NifLog.info("Writing {0} file".format(prefix + ext))

                kffile = os.path.join(directory, prefix + filebase + ext)
                data = NifFormat.Data(version=self.version,
                                      user_version=self.user_version,
                                      user_version_2=self.user_version_2)
                data.roots = [kf_root]
                data.neosteam = (NifOp.props.game == 'NEOSTEAM')
                stream = open(kffile, "wb")
                try:
                    data.write(stream)
                finally:
                    stream.close()

            if export_animation == 'ALL_NIF_XNIF_XKF':
                NifLog.info("Detaching keyframe controllers from nif")
                # detach the keyframe controllers from the nif (for xnif)
                for node in root_block.tree():
                    if not isinstance(node, NifFormat.NiNode):
                        continue
                    # remove references to keyframe controllers from node
                    # (for xnif)
                    while isinstance(node.controller,
                                     NifFormat.NiKeyframeController):
                        node.controller = node.controller.next_controller
                    ctrl = node.controller
                    while ctrl:
                        if isinstance(ctrl.next_controller,
                                      NifFormat.NiKeyframeController):
                            ctrl.next_controller = ctrl.next_controller.next_controller
                        else:
                            ctrl = ctrl.next_controller

                NifLog.info("Detaching animation text keys from nif")
                # detach animation text keys
                if root_block.extra_data is not anim_textextra:
                    raise RuntimeError(
                        "Oops, you found a bug! Animation extra data"
                        " wasn't where expected...")
                root_block.extra_data = None

                prefix = "x"  # we are in morrowind 'nifxnifkf mode'
                ext = ".nif"
                NifLog.info("Writing {0} file".format(prefix + ext))

                xniffile = os.path.join(directory, prefix + filebase + ext)
                data = NifFormat.Data(version=self.version,
                                      user_version=self.user_version,
                                      user_version_2=self.user_version_2)
                data.roots = [root_block]
                data.neosteam = (NifOp.props.game == 'NEOSTEAM')
                stream = open(xniffile, "wb")
                try:
                    data.write(stream)
                finally:
                    stream.close()

            # export egm file:
            # -----------------
            if self.egm_data:
                ext = ".egm"
                NifLog.info("Writing {0} file".format(ext))

                egmfile = os.path.join(directory, filebase + ext)
                stream = open(egmfile, "wb")
                try:
                    self.egm_data.write(stream)
                finally:
                    stream.close()
        finally:
            # clear progress bar
            NifLog.info("Finished")

        # save exported file (this is used by the test suite)
        self.root_blocks = [root_block]

        return {'FINISHED'}
示例#18
0
    def export_collision_object(self, b_obj, layer, n_havok_mat):
        """Export object obj as box, sphere, capsule, or convex hull.
        Note: polyheder is handled by export_collision_packed."""

        # find bounding box data
        if not b_obj.data.vertices:
            NifLog.warn("Skipping collision object {0} without vertices.".format(b_obj))
            return None
        b_vertlist = [vert.co for vert in b_obj.data.vertices]

        minx = min([b_vert[0] for b_vert in b_vertlist])
        miny = min([b_vert[1] for b_vert in b_vertlist])
        minz = min([b_vert[2] for b_vert in b_vertlist])
        maxx = max([b_vert[0] for b_vert in b_vertlist])
        maxy = max([b_vert[1] for b_vert in b_vertlist])
        maxz = max([b_vert[2] for b_vert in b_vertlist])
        
        calc_bhkshape_radius = (maxx - minx + maxy - miny + maxz - minz) / (6.0 * self.HAVOK_SCALE)
        if(b_obj.game.radius - calc_bhkshape_radius > NifOp.props.epsilon):
            radius = calc_bhkshape_radius
        else:
            radius = b_obj.game.radius
        
        if b_obj.game.collision_bounds_type in {'BOX', 'SPHERE'}:
            # note: collision settings are taken from lowerclasschair01.nif
            coltf = self.nif_export.objecthelper.create_block("bhkConvexTransformShape", b_obj)
            coltf.material = n_havok_mat
            coltf.unknown_float_1 = 0.1
            coltf.unknown_8_bytes[0] = 96
            coltf.unknown_8_bytes[1] = 120
            coltf.unknown_8_bytes[2] = 53
            coltf.unknown_8_bytes[3] = 19
            coltf.unknown_8_bytes[4] = 24
            coltf.unknown_8_bytes[5] = 9
            coltf.unknown_8_bytes[6] = 253
            coltf.unknown_8_bytes[7] = 4
            hktf = mathutils.Matrix(
                self.nif_export.objecthelper.get_object_matrix(b_obj).as_list())
            # the translation part must point to the center of the data
            # so calculate the center in local coordinates
            center = mathutils.Vector(((minx + maxx) / 2.0, (miny + maxy) / 2.0, (minz + maxz) / 2.0))
            # and transform it to global coordinates
            center = center * hktf
            hktf[0][3] = center[0]
            hktf[1][3] = center[1]
            hktf[2][3] = center[2]
            # we need to store the transpose of the matrix
            hktf.transpose()
            coltf.transform.set_rows(*hktf)
            # fix matrix for havok coordinate system
            coltf.transform.m_41 /= self.HAVOK_SCALE
            coltf.transform.m_42 /= self.HAVOK_SCALE
            coltf.transform.m_43 /= self.HAVOK_SCALE

            if b_obj.game.collision_bounds_type == 'BOX':
                colbox = self.nif_export.objecthelper.create_block("bhkBoxShape", b_obj)
                coltf.shape = colbox
                colbox.material = n_havok_mat
                colbox.radius = radius
                colbox.unknown_8_bytes[0] = 0x6b
                colbox.unknown_8_bytes[1] = 0xee
                colbox.unknown_8_bytes[2] = 0x43
                colbox.unknown_8_bytes[3] = 0x40
                colbox.unknown_8_bytes[4] = 0x3a
                colbox.unknown_8_bytes[5] = 0xef
                colbox.unknown_8_bytes[6] = 0x8e
                colbox.unknown_8_bytes[7] = 0x3e
                # fix dimensions for havok coordinate system
                colbox.dimensions.x = (maxx - minx) / (2.0 * self.HAVOK_SCALE)
                colbox.dimensions.y = (maxy - miny) / (2.0 * self.HAVOK_SCALE)
                colbox.dimensions.z = (maxz - minz) / (2.0 * self.HAVOK_SCALE)
                colbox.minimum_size = min(colbox.dimensions.x, colbox.dimensions.y, colbox.dimensions.z)

            elif b_obj.game.collision_bounds_type == 'SPHERE':
                colsphere = self.nif_export.objecthelper.create_block("bhkSphereShape", b_obj)
                coltf.shape = colsphere
                colsphere.material = n_havok_mat
                # take average radius and
                # Todo find out what this is: fix for havok coordinate system (6 * 7 = 42)
                colsphere.radius = radius

            return coltf

        elif b_obj.game.collision_bounds_type in {'CYLINDER', 'CAPSULE'}:
            # take average radius and calculate end points
            localradius = (maxx + maxy - minx - miny) / 4.0
            transform = b_obj.matrix_local.transposed()
            vert1 = mathutils.Vector( [ (maxx + minx)/2.0,
                                       (maxy + miny)/2.0,
                                       maxz - localradius ] )
            vert2 = mathutils.Vector( [ (maxx + minx) / 2.0,
                                       (maxy + miny) / 2.0,
                                       minz + localradius ] )
            vert1 = vert1 * transform
            vert2 = vert2 * transform

            # check if end points are far enough from each other
            if (vert1 - vert2).length < NifOp.props.epsilon:
                NifLog.warn("End points of cylinder {0} too close, converting to sphere.".format(b_obj))
                # change type
                b_obj.game.collision_bounds_type = 'SPHERE'
                # instead of duplicating code, just run the function again
                return self.export_collision_object(b_obj, layer, n_havok_mat)

            # end points are ok, so export as capsule
            colcaps = self.nif_export.objecthelper.create_block("bhkCapsuleShape", b_obj)
            colcaps.material = n_havok_mat
            colcaps.first_point.x = vert1[0] / self.HAVOK_SCALE
            colcaps.first_point.y = vert1[1] / self.HAVOK_SCALE
            colcaps.first_point.z = vert1[2] / self.HAVOK_SCALE
            colcaps.second_point.x = vert2[0] / self.HAVOK_SCALE
            colcaps.second_point.y = vert2[1] / self.HAVOK_SCALE
            colcaps.second_point.z = vert2[2] / self.HAVOK_SCALE

            # set radius, with correct scale
            size_x = b_obj.scale.x
            size_y = b_obj.scale.y
            size_z = b_obj.scale.z

            colcaps.radius = localradius * (size_x + size_y) * 0.5
            colcaps.radius_1 = colcaps.radius
            colcaps.radius_2 = colcaps.radius

            # fix havok coordinate system for radii
            colcaps.radius /= self.HAVOK_SCALE
            colcaps.radius_1 /= self.HAVOK_SCALE
            colcaps.radius_2 /= self.HAVOK_SCALE
            return colcaps

        elif b_obj.game.collision_bounds_type == 'CONVEX_HULL':
            b_mesh = b_obj.data
            b_transform_mat = mathutils.Matrix(self.nif_export.objecthelper.get_object_matrix(b_obj).as_list())

            b_rot_quat = b_transform_mat.decompose()[1]
            b_scale_vec = b_transform_mat.decompose()[0]
            '''
            scale = math.avg(b_scale_vec.to_tuple())
            if scale < 0:
                scale = - (-scale) ** (1.0 / 3)
            else:
                scale = scale ** (1.0 / 3)
            rotation /= scale
            '''

            # calculate vertices, normals, and distances
            vertlist = [b_transform_mat * vert.co for vert in b_mesh.vertices]
            fnormlist = [b_rot_quat * b_face.normal for b_face in b_mesh.polygons]
            fdistlist = [(b_transform_mat * (-1 * b_mesh.vertices[b_mesh.polygons[b_face.index].vertices[0]].co)).dot(
                            b_rot_quat.to_matrix() * b_face.normal)
                         for b_face in b_mesh.polygons ]

            # remove duplicates through dictionary
            vertdict = {}
            for i, vert in enumerate(vertlist):
                vertdict[(int(vert[0]*self.nif_export.VERTEX_RESOLUTION),
                          int(vert[1]*self.nif_export.VERTEX_RESOLUTION),
                          int(vert[2]*self.nif_export.VERTEX_RESOLUTION))] = i
            fdict = {}
            for i, (norm, dist) in enumerate(zip(fnormlist, fdistlist)):
                fdict[(int(norm[0]*self.nif_export.NORMAL_RESOLUTION),
                       int(norm[1]*self.nif_export.NORMAL_RESOLUTION),
                       int(norm[2]*self.nif_export.NORMAL_RESOLUTION),
                       int(dist*self.nif_export.VERTEX_RESOLUTION))] = i
            # sort vertices and normals
            vertkeys = sorted(vertdict.keys())
            fkeys = sorted(fdict.keys())
            vertlist = [ vertlist[vertdict[hsh]] for hsh in vertkeys ]
            fnormlist = [ fnormlist[fdict[hsh]] for hsh in fkeys ]
            fdistlist = [ fdistlist[fdict[hsh]] for hsh in fkeys ]

            if len(fnormlist) > 65535 or len(vertlist) > 65535:
                raise nif_utils.NifError(
                    "ERROR%t|Too many polygons/vertices."
                    " Decimate/split your b_mesh and try again.")

            colhull = self.nif_export.objecthelper.create_block("bhkConvexVerticesShape", b_obj)
            colhull.material = n_havok_mat
            colhull.radius = radius
            colhull.unknown_6_floats[2] = -0.0 # enables arrow detection
            colhull.unknown_6_floats[5] = -0.0 # enables arrow detection
            # note: unknown 6 floats are usually all 0
            colhull.num_vertices = len(vertlist)
            colhull.vertices.update_size()
            for vhull, vert in zip(colhull.vertices, vertlist):
                vhull.x = vert[0] / self.HAVOK_SCALE
                vhull.y = vert[1] / self.HAVOK_SCALE
                vhull.z = vert[2] / self.HAVOK_SCALE
                # w component is 0
            colhull.num_normals = len(fnormlist)
            colhull.normals.update_size()
            for nhull, norm, dist in zip(colhull.normals, fnormlist, fdistlist):
                nhull.x = norm[0]
                nhull.y = norm[1]
                nhull.z = norm[2]
                nhull.w = dist / self.HAVOK_SCALE

            return colhull

        else:
            raise nif_utils.NifError(
                'cannot export collision type %s to collision shape list'
                % b_obj.game.collision_bounds_type)
    def export_text_keys(
        self,
        block_parent,
    ):
        """Parse the animation groups buffer and write an extra string
        data block, and attach it to an existing block (typically, the root
        of the nif tree)."""
        if NifOp.props.animation == 'GEOM_NIF':
            # animation group extra data is not present in geometry only files
            return
        if "Anim" not in bpy.data.texts:
            return
        animtxt = bpy.data.texts["Anim"]
        NifLog.info("Exporting animation groups")
        # -> get animation groups information

        # parse the anim text descriptor

        # the format is:
        # frame/string1[/string2[.../stringN]]

        # example:
        # 001/Idle: Start/Idle: Stop/Idle2: Start/Idle2: Loop Start
        # 051/Idle2: Stop/Idle3: Start
        # 101/Idle3: Loop Start/Idle3: Stop

        slist = animtxt.asLines()
        flist = []
        dlist = []
        for s in slist:
            # ignore empty lines
            if not s:
                continue
            # parse line
            t = s.split('/')
            if (len(t) < 2):
                raise nif_utils.NifError("Syntax error in Anim buffer ('%s')" %
                                         s)
            f = int(t[0])
            if ((f < bpy.context.scene.frame_start)
                    or (f > bpy.context.scene.frame_end)):
                NifLog.warn(
                    "Frame in animation buffer out of range ({0} not between [{1}, {2}])"
                    .format(str(f), str(bpy.context.scene.frame_start),
                            str(bpy.context.scene.frame_end)))
            d = t[1].strip()
            for i in range(2, len(t)):
                d = d + '\r\n' + t[i].strip()
            #print 'frame %d'%f + ' -> \'%s\''%d # debug
            flist.append(f)
            dlist.append(d)

        # -> now comes the real export

        # add a NiTextKeyExtraData block, and refer to this block in the
        # parent node (we choose the root block)
        textextra = self.nif_export.objecthelper.create_block(
            "NiTextKeyExtraData", animtxt)
        block_parent.add_extra_data(textextra)

        # create a text key for each frame descriptor
        textextra.num_text_keys = len(flist)
        textextra.text_keys.update_size()
        for i, key in enumerate(textextra.text_keys):
            key.time = flist[i] / self.fps
            key.value = dlist[i]

        return textextra
    def import_bhkcapsule_shape(self, bhkshape):
        """Import a BhkCapsule block as a simple cylinder collision object"""
        b_radius = bhkshape.radius
        # create capsule mesh
        length = (bhkshape.first_point - bhkshape.second_point).norm()
        minx = miny = -b_radius * self.HAVOK_SCALE
        maxx = maxy = +b_radius * self.HAVOK_SCALE
        minz = -(length + 2 * b_radius) * (self.HAVOK_SCALE / 2)
        maxz = +(length + 2 * b_radius) * (self.HAVOK_SCALE / 2)

        b_mesh = bpy.data.meshes.new('capsule')
        vert_list = {}
        vert_index = 0

        for x in [minx, maxx]:
            for y in [miny, maxy]:
                for z in [minz, maxz]:
                    b_mesh.vertices.add(1)
                    b_mesh.vertices[-1].co = (x, y, z)
                    vert_list[vert_index] = [x, y, z]
                    vert_index += 1

        poly_gens = [[0, 1, 3, 2], [6, 7, 5, 4], [0, 2, 6, 4], [3, 1, 5, 7],
                     [4, 5, 1, 0], [7, 6, 2, 3]]
        b_mesh = poly_gen.col_poly_gen(b_mesh, poly_gens)

        # Recalculate mesh to render correctly
        b_mesh.validate()
        b_mesh.update()

        # link box to scene and set transform
        b_obj = bpy.data.objects.new('Capsule', b_mesh)
        bpy.context.scene.objects.link(b_obj)
        scn = bpy.context.scene
        scn.objects.active = b_obj

        # set bounds type
        b_obj.draw_type = 'BOUNDS'
        b_obj.draw_bounds_type = 'CAPSULE'
        b_obj.game.use_collision_bounds = True
        b_obj.game.collision_bounds_type = 'CAPSULE'
        b_obj.game.radius = bhkshape.radius * self.HAVOK_SCALE
        b_obj.nifcollision.havok_material = NifFormat.HavokMaterial._enumkeys[
            bhkshape.material]

        # find transform
        if length > NifOp.props.epsilon:
            normal = (bhkshape.first_point - bhkshape.second_point) / length
            normal = mathutils.Vector((normal.x, normal.y, normal.z))
        else:
            NifLog.warn(
                "BhkCapsuleShape with identical points: using arbitrary axis")
            normal = mathutils.Vector((0, 0, 1))

        minindex = min((abs(x), i) for i, x in enumerate(normal))[1]
        orthvec = mathutils.Vector([(1 if i == minindex else 0)
                                    for i in (0, 1, 2)])

        vec1 = mathutils.Vector.cross(normal, orthvec)
        vec1.normalize()
        vec2 = mathutils.Vector.cross(normal, vec1)

        # the rotation matrix should be such that (0,0,1) maps to normal
        transform = mathutils.Matrix([vec1, vec2, normal]).transposed()
        transform.resize_4x4()
        transform[0][3] = (self.HAVOK_SCALE / 2) * (bhkshape.first_point.x +
                                                    bhkshape.second_point.x)
        transform[1][3] = (self.HAVOK_SCALE / 2) * (bhkshape.first_point.y +
                                                    bhkshape.second_point.y)
        transform[2][3] = (self.HAVOK_SCALE / 2) * (bhkshape.first_point.z +
                                                    bhkshape.second_point.z)
        b_obj.matrix_local = transform

        # Recalculate mesh to render correctly
        b_mesh = b_obj.data
        b_mesh.validate()
        b_mesh.update()
        b_obj.select = True

        # return object
        return [b_obj]
    def import_mesh_controllers(self, n_node, b_obj):
        """Import mesh controller for blender object."""

        morphCtrl = nif_utils.find_controller(
            n_node, NifFormat.NiGeomMorpherController)
        if morphCtrl:
            b_mesh = b_obj.data
            morphData = morphCtrl.data
            if morphData.num_morphs:
                fps = bpy.context.scene.render.fps
                # insert base key at frame 1, using relative keys
                b_mesh.insertKey(1, 'relative')
                # get name for base key
                keyname = morphData.morphs[0].frame_name
                if not keyname:
                    keyname = 'Base'
                # set name for base key
                b_mesh.key.blocks[0].name = keyname
                # get base vectors and import all morphs
                baseverts = morphData.morphs[0].vectors
                b_ipo = Blender.Ipo.New('Key', 'KeyIpo')
                b_mesh.key.ipo = b_ipo
                for idxMorph in range(1, morphData.num_morphs):
                    # get name for key
                    keyname = morphData.morphs[idxMorph].frame_name
                    if not keyname:
                        keyname = 'Key %i' % idxMorph
                    NifLog.info("Inserting key '{0}'".format(keyname))
                    # get vectors
                    morphverts = morphData.morphs[idxMorph].vectors
                    # for each vertex calculate the key position from base
                    # pos + delta offset
                    assert (len(baseverts) == len(morphverts) == len(v_map))
                    for bv, mv, b_v_index in zip(baseverts, morphverts, v_map):
                        base = mathutils.Vector(bv.x, bv.y, bv.z)
                        delta = mathutils.Vector(mv.x, mv.y, mv.z)
                        v = base + delta
                        if applytransform:
                            v *= transform
                        b_mesh.vertices[b_v_index].co[0] = v.x
                        b_mesh.vertices[b_v_index].co[1] = v.y
                        b_mesh.vertices[b_v_index].co[2] = v.z
                    # update the mesh and insert key
                    b_mesh.insertKey(idxMorph, 'relative')
                    # set name for key
                    b_mesh.key.blocks[idxMorph].name = keyname
                    # set up the ipo key curve
                    try:
                        b_curve = b_ipo.addCurve(keyname)
                    except ValueError:
                        # this happens when two keys have the same name
                        # an instance of this is in fallout 3
                        # meshes/characters/_male/skeleton.nif HeadAnims:0
                        NifLog.warn(
                            "Skipped duplicate of key '{0}'".format(keyname))
                    # no idea how to set up the bezier triples -> switching
                    # to linear instead
                    b_curve.interpolation = Blender.IpoCurve.InterpTypes.LINEAR
                    # select extrapolation
                    b_curve.extend = self.get_extend_from_flags(
                        morphCtrl.flags)
                    # set up the curve's control points
                    # first find the keys
                    # older versions store keys in the morphData
                    morphkeys = morphData.morphs[idxMorph].keys
                    # newer versions store keys in the controller
                    if (not morphkeys) and morphCtrl.interpolators:
                        morphkeys = morphCtrl.interpolators[
                            idxMorph].data.data.keys
                    for key in morphkeys:
                        x = key.value
                        frame = 1 + int(key.time * fps + 0.5)
                        b_curve.addBezier((frame, x))
                    # finally: return to base position
                    for bv, b_v_index in zip(baseverts, v_map):
                        base = mathutils.Vector(bv.x, bv.y, bv.z)
                        if applytransform:
                            base *= transform
                        b_mesh.vertices[b_v_index].co[0] = base.x
                        b_mesh.vertices[b_v_index].co[1] = base.y
                        b_mesh.vertices[b_v_index].co[2] = base.z
示例#22
0
    def export_texture_filename(self, texture):
        """Returns file name from texture.

        @param texture: The texture object in blender.
        @return: The file name of the image used in the texture.
        """
        if texture.type == 'ENVIRONMENT_MAP':
            # this works for morrowind only
            if NifOp.props.game != 'MORROWIND':
                raise nif_utils.NifError(
                    "cannot export environment maps for nif version '%s'" %
                    NifOp.props.game)
            return "enviro 01.TGA"

        elif texture.type == 'IMAGE':
            # get filename from image

            # XXX still needed? can texture.image be None in current blender?
            # check that image is loaded
            if texture.image is None:
                raise nif_utils.NifError(
                    "image type texture has no file loaded ('%s')" %
                    texture.name)

            filename = texture.image.filepath

            # warn if packed flag is enabled
            if texture.image.packed_file:
                NifLog.warn(
                    "Packed image in texture '{0}' ignored, exporting as '{1}' instead."
                    .format(texture.name, filename))

            # try and find a DDS alternative, force it if required
            ddsfilename = "%s%s" % (filename[:-4], '.dds')
            if os.path.exists(ddsfilename) or NifOp.props.force_dds:
                filename = ddsfilename

            # sanitize file path
            if not NifOp.props.game in ('MORROWIND', 'OBLIVION', 'FALLOUT_3',
                                        'SKYRIM'):
                # strip texture file path
                filename = os.path.basename(filename)

            else:
                # strip the data files prefix from the texture's file name
                filename = filename.lower()
                idx = filename.find("textures")
                if (idx >= 0):
                    filename = filename[idx:]
                else:
                    NifLog.warn(
                        "{0} does not reside in a 'Textures' folder; texture path will be stripped  and textures may not display in-game"
                        .format(filename))
                    filename = os.path.basename(filename)
            # for linux export: fix path seperators
            return filename.replace('/', '\\')
        else:
            # texture must be of type IMAGE or ENVMAP
            raise nif_utils.NifError(
                "Error: Texture '%s' must be of type IMAGE or ENVMAP" %
                texture.name)
示例#23
0
    def export_constraints(self, b_obj, root_block):
        """Export the constraints of an object.

        @param b_obj: The object whose constraints to export.
        @param root_block: The root of the nif tree (required for update_a_b)."""
        if isinstance(b_obj, bpy.types.Bone):
            # bone object has its constraints stored in the posebone
            # so now we should get the posebone, but no constraints for
            # bones are exported anyway for now
            # so skip this object
            return

        if not hasattr(b_obj, "constraints"):
            # skip text buffers etc
            return

        for b_constr in b_obj.constraints:
            # rigid body joints
            if b_constr.type == 'RIGID_BODY_JOINT':
                if NifOp.props.game not in ('OBLIVION', 'FALLOUT_3', 'SKYRIM'):
                    NifLog.warn("Only Oblivion/Fallout/Skyrim rigid body constraints currently supported: Skipping {0}.".format(b_constr))
                    continue
                # check that the object is a rigid body
                for otherbody, otherobj in self.nif_export.dict_blocks.items():
                    if isinstance(otherbody, NifFormat.bhkRigidBody) \
                        and otherobj is b_obj:
                        hkbody = otherbody
                        break
                else:
                    # no collision body for this object
                    raise nif_utils.NifError(
                        "Object %s has a rigid body constraint,"
                        " but is not exported as collision object"
                        % b_obj.name)
                # yes there is a rigid body constraint
                # is it of a type that is supported?
                if b_constr.pivot_type == 'CONE_TWIST':
                    # ball
                    if b_obj.rigid_body.enabled == True:
                        hkconstraint = self.nif_export.objecthelper.create_block(
                            "bhkRagdollConstraint", b_constr)
                    else:
                        hkconstraint = self.nif_export.objecthelper.create_block(
                            "bhkMalleableConstraint", b_constr)
                        hkconstraint.type = 7
                    hkdescriptor = hkconstraint.ragdoll
                elif b_constr.pivot_type == 'HINGE':
                    # hinge
                    if b_obj.rigid_body.enabled == True:
                        hkconstraint = self.nif_export.objecthelper.create_block(
                            "bhkLimitedHingeConstraint", b_constr)
                    else:
                        hkconstraint = self.nif_export.objecthelper.create_block(
                            "bhkMalleableConstraint", b_constr)
                        hkconstraint.type = 2
                    hkdescriptor = hkconstraint.limited_hinge
                else:
                    raise nif_utils.NifError(
                        "Unsupported rigid body joint type (%i),"
                        " only ball and hinge are supported."
                        % b_constr.type)

                # defaults and getting object properties for user
                # settings (should use constraint properties, but
                # blender does not have those...)
                if b_constr.limit_angle_max_x != 0:
                    max_angle = b_constr.limit_angle_max_x
                else:
                    max_angle = 1.5
                if b_constr.limit_angle_min_x != 0:
                    min_angle = b_constr.limit_angle_min_x
                else:
                    min_angle = 0.0
                # friction: again, just picking a reasonable value if
                # no real value given
                if b_obj.niftools_constraint.LHMaxFriction != 0:
                    max_friction = b_obj.niftools_constraint.LHMaxFriction
                    
                
                else:
                    if isinstance(hkconstraint,
                                  NifFormat.bhkMalleableConstraint):
                        # malleable typically have 0
                        # (perhaps because they have a damping parameter)
                        max_friction = 0
                    else:
                        # non-malleable typically have 10
                        if NifOp.props.game == 'FALLOUT_3':
                            max_friction = 100
                        else: # oblivion
                            max_friction = 10

                # parent constraint to hkbody
                hkbody.num_constraints += 1
                hkbody.constraints.update_size()
                hkbody.constraints[-1] = hkconstraint

                # export hkconstraint settings
                hkconstraint.num_entities = 2
                hkconstraint.entities.update_size()
                hkconstraint.entities[0] = hkbody
                # is there a target?
                targetobj = b_constr.target
                if not targetobj:
                    NifLog.warn("Constraint {0} has no target, skipped".format(b_constr))
                    continue
                # find target's bhkRigidBody
                for otherbody, otherobj in self.nif_export.dict_blocks.items():
                    if isinstance(otherbody, NifFormat.bhkRigidBody) \
                        and otherobj == targetobj:
                        hkconstraint.entities[1] = otherbody
                        break
                else:
                    # not found
                    raise nif_utils.NifError(
                        "Rigid body target not exported in nif tree"
                        " check that %s is selected during export." % targetobj)
                # priority
                hkconstraint.priority = 1
                # extra malleable constraint settings
                if isinstance(hkconstraint, NifFormat.bhkMalleableConstraint):
                    # unknowns
                    hkconstraint.unknown_int_2 = 2
                    hkconstraint.unknown_int_3 = 1
                    # force required to keep bodies together
                    hkconstraint.tau = b_obj.niftools_constraint.tau
                    hkconstraint.damping = b_obj.niftools_constraint.damping

                # calculate pivot point and constraint matrix
                pivot = mathutils.Vector([
                    b_constr.pivot_x,
                    b_constr.pivot_y,
                    b_constr.pivot_z,
                    ])
                constr_matrix = mathutils.Euler((
                    b_constr.axis_x,
                    b_constr.axis_y,
                    b_constr.axis_z))
                constr_matrix = constr_matrix.to_matrix()

                # transform pivot point and constraint matrix into bhkRigidBody
                # coordinates (also see import_nif.py, the
                # NifImport.import_bhk_constraints method)

                # the pivot point v' is in object coordinates
                # however nif expects it in hkbody coordinates, v
                # v * R * B = v' * O * T * B'
                # with R = rigid body transform (usually unit tf)
                # B = nif bone matrix
                # O = blender object transform
                # T = bone tail matrix (translation in Y direction)
                # B' = blender bone matrix
                # so we need to cancel out the object transformation by
                # v = v' * O * T * B' * B^{-1} * R^{-1}

                # for the rotation matrix, we transform in the same way
                # but ignore all translation parts

                # assume R is unit transform...

                # apply object transform relative to the bone head
                # (this is O * T * B' * B^{-1} at once)
                transform = mathutils.Matrix(
                    b_obj.matrix_local)
                pivot = pivot * transform
                constr_matrix = constr_matrix * transform.to_3x3()

                # export hkdescriptor pivot point
                hkdescriptor.pivot_a.x = pivot[0] / self.HAVOK_SCALE
                hkdescriptor.pivot_a.y = pivot[1] / self.HAVOK_SCALE
                hkdescriptor.pivot_a.z = pivot[2] / self.HAVOK_SCALE
                # export hkdescriptor axes and other parameters
                # (also see import_nif.py NifImport.import_bhk_constraints)
                axis_x = mathutils.Vector([1,0,0]) * constr_matrix
                axis_y = mathutils.Vector([0,1,0]) * constr_matrix
                axis_z = mathutils.Vector([0,0,1]) * constr_matrix
                    
                if isinstance(hkdescriptor, NifFormat.RagdollDescriptor):
                    # z axis is the twist vector
                    hkdescriptor.twist_a.x = axis_z[0]
                    hkdescriptor.twist_a.y = axis_z[1]
                    hkdescriptor.twist_a.z = axis_z[2]
                    # x axis is the plane vector
                    hkdescriptor.plane_a.x = axis_x[0]
                    hkdescriptor.plane_a.y = axis_x[1]
                    hkdescriptor.plane_a.z = axis_x[2]
                    # angle limits
                    # take them twist and plane to be 45 deg (3.14 / 4 = 0.8)

                    hkdescriptor.plane_min_angle = b_constr.limit_angle_min_x
                    hkdescriptor.plane_max_angle = b_constr.limit_angle_max_x

                    hkdescriptor.cone_max_angle = b_constr.limit_angle_max_y

                    hkdescriptor.twist_min_angle = b_constr.limit_angle_min_z
                    hkdescriptor.twist_max_angle = b_constr.limit_angle_max_z
                    
                    # same for maximum cone angle
                    hkdescriptor.max_friction = max_friction
                elif isinstance(hkdescriptor, NifFormat.LimitedHingeDescriptor):
                    # y axis is the zero angle vector on the plane of rotation
                    hkdescriptor.perp_2_axle_in_a_1.x = axis_y[0]
                    hkdescriptor.perp_2_axle_in_a_1.y = axis_y[1]
                    hkdescriptor.perp_2_axle_in_a_1.z = axis_y[2]
                    # x axis is the axis of rotation
                    hkdescriptor.axle_a.x = axis_x[0]
                    hkdescriptor.axle_a.y = axis_x[1]
                    hkdescriptor.axle_a.z = axis_x[2]
                    # z is the remaining axis determining the positive
                    # direction of rotation
                    hkdescriptor.perp_2_axle_in_a_2.x = axis_z[0]
                    hkdescriptor.perp_2_axle_in_a_2.y = axis_z[1]
                    hkdescriptor.perp_2_axle_in_a_2.z = axis_z[2]
                    # angle limits
                    # typically, the constraint on one side is defined
                    # by the z axis
                    hkdescriptor.min_angle = min_angle
                    # the maximum axis is typically about 90 degrees
                    # 3.14 / 2 = 1.5
                    hkdescriptor.max_angle = max_angle
                    # friction
                    hkdescriptor.max_friction = max_friction
                else:
                    raise ValueError("unknown descriptor %s"
                                     % hkdescriptor.__class__.__name__)

                # do AB
                hkconstraint.update_a_b(root_block)