Ejemplo n.º 1
0
def update_bounds(self, context):
    if self.sollum_type == SollumType.BOUND_BOX:
        create_box(self.data, 1,
                   Matrix.Diagonal(Vector(self.bound_dimensions)))
    elif self.sollum_type == SollumType.BOUND_SPHERE or self.sollum_type == SollumType.BOUND_POLY_SPHERE:
        create_sphere(mesh=self.data, radius=self.bound_radius)

    elif self.sollum_type == SollumType.BOUND_CYLINDER:
        create_cylinder(mesh=self.data,
                        radius=self.bound_radius,
                        length=self.bound_length)
    elif self.sollum_type == SollumType.BOUND_POLY_CYLINDER:
        create_cylinder(mesh=self.data,
                        radius=self.bound_radius,
                        length=self.bound_length,
                        rot_mat=Matrix())

    elif self.sollum_type == SollumType.BOUND_DISC:
        create_disc(mesh=self.data,
                    radius=self.bound_radius,
                    length=self.margin * 2)

    elif self.sollum_type == SollumType.BOUND_CAPSULE:
        create_capsule(mesh=self.data,
                       diameter=self.margin,
                       length=self.bound_radius,
                       use_rot=True)
    elif self.sollum_type == SollumType.BOUND_POLY_CAPSULE:
        create_capsule(mesh=self.data,
                       diameter=self.bound_radius / 2,
                       length=self.bound_length)
Ejemplo n.º 2
0
def boundaries() -> Tuple[Vector, Vector]:
    x = []
    y = []
    z = []

    for perimeter in Perimeter.all():

        reference = Reference(perimeter)
        user = reference.matrix()

        for obj in perimeter.objects():

            scale = Matrix.Diagonal(perimeter.matrix().to_scale()).to_4x4()

            bb = obj.bound_box
            for p in range(8):
                v_local = perimeter.matrix_1() @ obj.matrix_world @ Vector(
                    [bb[p][0], bb[p][1], bb[p][2]])

                v = user @ scale @ v_local

                x.append(v[0])
                y.append(v[1])
                z.append(v[2])

    minimum = Vector([min(x), min(y), min(z)])
    maximum = Vector([max(x), max(y), max(z)])
    return minimum, maximum
Ejemplo n.º 3
0
def obj_to_bone(obj, rig, bone_name, bone_transform_name=None):
    """ Places an object at the location/rotation/scale of the given bone.
    """
    if bpy.context.mode == 'EDIT_ARMATURE':
        raise MetarigError("obj_to_bone(): does not work while in edit mode")

    bone = rig.pose.bones[bone_name]

    loc = bone.custom_shape_translation
    rot = bone.custom_shape_rotation_euler
    scale = Vector(bone.custom_shape_scale_xyz)

    if bone.use_custom_shape_bone_size:
        scale *= bone.length

    if bone_transform_name is not None:
        bone = rig.pose.bones[bone_transform_name]
    elif bone.custom_shape_transform:
        bone = bone.custom_shape_transform

    shape_mat = Matrix.Translation(loc) @ (
        Euler(rot).to_matrix() @ Matrix.Diagonal(scale)).to_4x4()

    obj.rotation_mode = 'XYZ'
    obj.matrix_basis = rig.matrix_world @ bone.bone.matrix_local @ shape_mat
    def compute(self, **kwargs):
        input_stage = self.get_input_link('Input', **kwargs)

        if not input_stage or not self.name:
            return None

        path = f'/{Tf.MakeValidIdentifier(self.name)}'
        stage = self.cached_stage.create()
        UsdGeom.SetStageMetersPerUnit(stage, 1)
        UsdGeom.SetStageUpAxis(stage, UsdGeom.Tokens.z)
        root_xform = UsdGeom.Xform.Define(stage, path)
        root_prim = root_xform.GetPrim()

        for prim in input_stage.GetPseudoRoot().GetAllChildren():
            override_prim = stage.OverridePrim(
                root_xform.GetPath().AppendChild(prim.GetName()))
            override_prim.GetReferences().AddReference(
                input_stage.GetRootLayer().realPath, prim.GetPath())

        translation = Matrix.Translation((self.translation[:3]))

        diagonal = Matrix.Diagonal((self.scale[:3])).to_4x4()

        rotation_x = Matrix.Rotation(self.rotation[0], 4, 'X')
        rotation_y = Matrix.Rotation(self.rotation[1], 4, 'Y')
        rotation_z = Matrix.Rotation(self.rotation[2], 4, 'Z')

        transform = translation @ rotation_x @ rotation_y @ rotation_z @ diagonal

        UsdGeom.Xform.Get(stage, root_xform.GetPath()).AddTransformOp()
        root_prim.GetAttribute('xformOp:transform').Set(
            Gf.Matrix4d(transform.transposed()))

        return stage
Ejemplo n.º 5
0
def transform_to_matrix(pos: Vector, rot: Quaternion, scale: Vector) -> Matrix:
    scale_matrix = Matrix.Diagonal(scale)
    scale_matrix.resize_4x4()
    rot_matrix = rot.to_matrix()
    rot_matrix.resize_4x4()
    pos_matrix = Matrix.Translation(pos.xyz)
    pos_matrix.resize_4x4()
    return pos_matrix @ rot_matrix @ scale_matrix
Ejemplo n.º 6
0
    def generate_ik_tweak_widget(self, i, ctrl, wgt_mch):
        set_bone_widget_transform(self.obj, ctrl, wgt_mch)

        obj = create_circle_widget(self.obj,
                                   ctrl,
                                   head_tail=0.0,
                                   with_line=False)

        adjust_widget_transform_mesh(obj,
                                     Matrix.Diagonal((1.1, 1, 0.9, 1)),
                                     local=True)
Ejemplo n.º 7
0
    def update_gizmo_matrix(self, context):
        light = context.object
        data = light.data

        # Gizmo scale is dependent on UI scale
        ui_scale = context.preferences.system.ui_scale
        r = data.size / ui_scale * 0.5
        s = (light.scale.y * data.size_y) / ui_scale * 0.5

        smatrix = Matrix.Diagonal([r, s, r]).to_4x4()
        self.matrix_offset = smatrix
Ejemplo n.º 8
0
def get_matrices_single_bone(ob, pose_bone, edit_bone, frames):
    use_quaternions = pose_bone.rotation_mode == 'QUATERNION'
    transforms = {
        'loc': {
            'count': 3,
            'path': 'location',
            'object': pose_bone.location
        },
        'rot': {
            'count':
            4 if use_quaternions else 3,
            'path':
            'rotation_quaternion' if use_quaternions else 'rotation_euler',
            'object':
            pose_bone.rotation_quaternion
            if use_quaternions else pose_bone.rotation_euler
        },
        'sca': {
            'count': 3,
            'path': 'scale',
            'object': pose_bone.scale
        },
    }

    if ob.animation_data == None:
        ob.animation_data_create()

    for key, value in transforms.items():
        result = dict(zip(frames, [value['object'].copy() for f in frames]))

        for i in range(value['count']):
            if ob.animation_data.action:
                crv = ob.animation_data.action.fcurves.find(
                    'pose.bones["' + pose_bone.name + '"].' + value['path'],
                    index=i)
            else:
                crv = None
            if crv != None:
                for f in frames:
                    v = crv.evaluate(f)
                    result[f][i] = crv.evaluate(f)
        transforms[key] = result

    if use_quaternions:
        for quat in transforms['rot'].values():
            quat.normalize()

    loc, rot, sca = transforms.values()
    matrices = [
        Matrix.Translation(loc[frame]) @ rot[frame].to_matrix().to_4x4()
        @ Matrix.Diagonal(sca[frame]).to_4x4() for frame in loc
    ]

    return matrices
Ejemplo n.º 9
0
 def get_perm_transform(self, perm):
     if not math.isnan(perm.scale):
         #in amf files geometry is stored as 100x the halo units with the exception of instanced geometry
         #this scales up to match the rest of the geometry and corrects the translation to match the unit settings
         offset_scale = self.get_scale_multiplier() / 0.01
         pos, rot, scale = perm.transform.decompose()
         return Matrix.Translation(pos * offset_scale) @ rot.to_matrix(
         ).to_4x4() @ Matrix.Diagonal(scale).to_4x4() @ Matrix.Scale(
             100 * perm.scale, 4)
     else:
         return Matrix()
Ejemplo n.º 10
0
    def _get_tmat(self, context, ob):
        if self.apply_to == 'LOC':
            tmat = Matrix.Translation(ob.matrix_local.translation)
        elif self.apply_to == 'ROT':
            tmat = ob.matrix_local.to_quaternion().to_matrix().to_4x4()
        elif self.apply_to == 'SCL':
            tmat = Matrix.Diagonal(ob.matrix_local.to_scale()).to_4x4()
        else:
            tmat = ob.matrix_local.copy()

        if self.to_cursor:
            return context.scene.cursor.matrix.inverted() @ tmat
        return tmat
Ejemplo n.º 11
0
    def execute(self, context):
        ob = context.object
        loc, rot, scl = ob.matrix_local.decompose()

        # If no additional objects are selected in addition to the
        # active one, consider all objects in the scene
        selobs = context.selected_objects
        if not selobs or selobs == [ob]:
            selobs = context.scene.objects

        if self.apply_to == 'LOC':
            tmat = Matrix.Translation(loc)
        elif self.apply_to == 'ROT':
            tmat = rot.to_matrix().to_4x4()
        elif self.apply_to == 'SCL':
            tmat = Matrix.Diagonal(scl).to_4x4()
        else:
            tmat = ob.matrix_local

        if self.to_cursor:
            tmat = context.scene.cursor.matrix.inverted() @ tmat

        ob.data.transform(tmat)
        tmat_inv = tmat.inverted()

        # Update ob's children to let them visually stay in place
        for c in ob.children:
            c.matrix_local = tmat @ c.matrix_local

        # Update objects sharing the same mesh as 'ob'
        for o in selobs:
            if o.data is not ob.data:
                continue

            # Check if an object is a child AND an instance of 'ob'.
            # matrix_local seems to be updated only once per frame after
            # execution of this script, so objects that are children and
            # an instance, whose matrix has already been set in the first
            # loop, still return their transformation from before the loop
            # here
            if ob is o.parent:
                o.matrix_local = tmat @ o.matrix_local @ tmat_inv
            else:
                o.matrix_local = o.matrix_local @ tmat_inv

        # What does o.matrix_local turn into?
        # * no child, no instance: o.matrix_local
        # * child, no instance: tmat @ o.matrix_local
        # * no child, instance: o.matrix_local @ tmat_inv
        # * child, instance: tmat @ o.matrix_local @ tmat_inv
        return {'FINISHED'}
Ejemplo n.º 12
0
def _fit_lattice_to_selection(object, vertices, lattice_object):
    ob_mat = object.matrix_world
    ob_loc, ob_rot, ob_scale = ob_mat.decompose()
    vert_locs = [Matrix.Diagonal(ob_scale) @ v.co for v in vertices]
    avg_vert_loc = sum(vert_locs, Vector()) / len(vert_locs)
    lat_origin = _calc_lattice_origin(vert_locs, avg_vert_loc)

    lattice_object.matrix_world = (Matrix.Translation(ob_loc) @ ob_rot.to_matrix().to_4x4() @
                                   Matrix.Translation(lat_origin))

    dims = _calc_lattice_dimensions(vert_locs, avg_vert_loc)
    # Avoid setting dimensions of a lattice to 0; it causes problems.
    ensured_dims = [d if d > 0 else 0.1 for d in dims]

    lattice_object.dimensions = ensured_dims

    _set_lattice_points(lattice_object, dims)
Ejemplo n.º 13
0
def getRotatedMatrix(sourceMat, pivotPos, axis, angle):
    # sourceMat = source.matrix_world
    loc, rot, sca = sourceMat.decompose()

    pivOffset = loc - pivotPos

    locMat = Matrix.Translation(pivOffset)
    rotMat = rot.to_matrix().to_4x4()
    scaMat = Matrix.Diagonal(sca).to_4x4()

    pivMat = Matrix.Translation(pivotPos)

    mat_rotBy = Matrix.Rotation(math.radians(angle), 4, axis)

    mat_out = pivMat @ mat_rotBy @ locMat @ rotMat @ scaMat

    return mat_out
def __gather_trans_rot_scale(vnode, export_settings):
    if vnode.parent_uuid is None:
        # No parent, so matrix is world matrix
        trans, rot, sca = vnode.matrix_world.decompose()
    else:
        # calculate local matrix
        trans, rot, sca = (
            export_settings['vtree'].nodes[vnode.parent_uuid].matrix_world.
            inverted_safe() @ vnode.matrix_world).decompose()

    # make sure the rotation is normalized
    rot.normalize()

    trans = __convert_swizzle_location(trans, export_settings)
    rot = __convert_swizzle_rotation(rot, export_settings)
    sca = __convert_swizzle_scale(sca, export_settings)

    if vnode.blender_object.instance_type == 'COLLECTION' and vnode.blender_object.instance_collection:
        offset = -__convert_swizzle_location(
            vnode.blender_object.instance_collection.instance_offset,
            export_settings)

        s = Matrix.Diagonal(sca).to_4x4()
        r = rot.to_matrix().to_4x4()
        t = Matrix.Translation(trans).to_4x4()
        o = Matrix.Translation(offset).to_4x4()
        m = t @ r @ s @ o

        trans = m.translation

    translation, rotation, scale = (None, None, None)
    trans[0], trans[1], trans[2] = gltf2_blender_math.round_if_near(trans[0], 0.0), gltf2_blender_math.round_if_near(trans[1], 0.0), \
                                   gltf2_blender_math.round_if_near(trans[2], 0.0)
    rot[0], rot[1], rot[2], rot[3] = gltf2_blender_math.round_if_near(rot[0], 1.0), gltf2_blender_math.round_if_near(rot[1], 0.0), \
                                     gltf2_blender_math.round_if_near(rot[2], 0.0), gltf2_blender_math.round_if_near(rot[3], 0.0)
    sca[0], sca[1], sca[2] = gltf2_blender_math.round_if_near(sca[0], 1.0), gltf2_blender_math.round_if_near(sca[1], 1.0), \
                             gltf2_blender_math.round_if_near(sca[2], 1.0)
    if trans[0] != 0.0 or trans[1] != 0.0 or trans[2] != 0.0:
        translation = [trans[0], trans[1], trans[2]]
    if rot[0] != 1.0 or rot[1] != 0.0 or rot[2] != 0.0 or rot[3] != 0.0:
        rotation = [rot[1], rot[2], rot[3], rot[0]]
    if sca[0] != 1.0 or sca[1] != 1.0 or sca[2] != 1.0:
        scale = [sca[0], sca[1], sca[2]]
    return translation, rotation, scale
Ejemplo n.º 15
0
def box(p: Point3d, vx: Vector3d, vy: Vector3d, dx: float, dy: float,
        dz: float, mat: MatId) -> Id:
    id, name = new_id()
    rot = quaternion_from_vx_vy(vx, vy)
    bm = bmesh.new()
    bmesh.ops.create_cube(
        bm,
        size=1,
        matrix=Matrix.Diagonal(Vector((dx, dy, dz, 1.0))),
        # AML Scale here? May influence UVs
        #matrix=Matrix.Translation(Vector((0, 0, -depth/2))),
        #calc_uvs=True
    )
    obj = mesh_from_bmesh(name, bm)
    obj.rotation_euler = rot.to_euler()
    obj.location = p
    current_collection.objects.link(obj)
    assign_material(obj, mat)
    return id
Ejemplo n.º 16
0
def reset_pose_bone_rotation(obj, bone_name) -> None:
    entered, active, mode = enter_mode_if(MODE_POSE, obj)

    bone = obj.pose.bones[bone_name]

    rest_matrix = get_pose_bone_rest_matrix_object(obj, bone_name)
    current_matrix = bone.matrix

    _, rr, _ = rest_matrix.decompose()
    cl, cr, cs = current_matrix.decompose()

    ml = Matrix.Translation(cl)
    mr = rr.to_matrix().to_4x4()
    msc = Matrix.Diagonal(cs).to_4x4()

    ms = ml @ mr @ msc

    bone.matrix = ms

    exit_mode_if(entered, active, mode)
Ejemplo n.º 17
0
def adjust_widget_axis(obj, axis='y', offset=0.0):
    mesh = obj.data

    if axis[0] == '-':
        s = -1.0
        axis = axis[1]
    else:
        s = 1.0

    trans_matrix = Matrix.Translation((0.0, offset, 0.0))
    rot_matrix = Matrix.Diagonal((1.0, s, 1.0, 1.0))

    if axis == "x":
        rot_matrix = Matrix.Rotation(-s * math.pi / 2, 4, 'Z')
        trans_matrix = Matrix.Translation((offset, 0.0, 0.0))

    elif axis == "z":
        rot_matrix = Matrix.Rotation(s * math.pi / 2, 4, 'X')
        trans_matrix = Matrix.Translation((0.0, 0.0, offset))

    matrix = trans_matrix @ rot_matrix

    for vert in mesh.vertices:
        vert.co = matrix @ vert.co
Ejemplo n.º 18
0
def execute(self, context):
    space_data = context.space_data
    use_local_view = bool(space_data.local_view)
    collection = context.collection
    start = self.start
    end = self.end
    sizes = context.window_manager.jewelcraft.sizes
    view_layer_upd = context.view_layer.update

    # Prepare objects
    # ---------------------------

    obs = []
    app = obs.append

    if self.is_distribute:

        if not sizes.values():
            return {"FINISHED"}

        curve = context.object
        curve.select_set(False)

        ob = context.selected_objects[0]
        context.view_layer.objects.active = ob

        mat_sca = Matrix.Diagonal(ob.scale).to_4x4()
        ob.matrix_world = mat_sca

        if self.rot_x:
            mat_rot = Matrix.Rotation(self.rot_x, 4, "X")
            ob.matrix_world @= mat_rot

        if self.rot_z:
            mat_rot = Matrix.Rotation(self.rot_z, 4, "Z")
            ob.matrix_world @= mat_rot

        if self.loc_z:
            mat_loc = Matrix.Translation((0.0, 0.0, self.loc_z))
            ob.matrix_world @= mat_loc

        first_cycle = True

        for item in sizes.values():
            for _ in range(item.qty):

                if first_cycle:
                    con = ob.constraints.new("FOLLOW_PATH")
                    con.target = curve
                    con.use_curve_follow = True
                    con.forward_axis = "FORWARD_X"

                    ob.scale *= item.size / ob.dimensions.y
                    view_layer_upd()

                    app((ob, con, None, item.size))

                    first_cycle = False
                    continue

                ob_copy = ob.copy()
                collection.objects.link(ob_copy)

                if use_local_view:
                    ob_copy.local_view_set(space_data, True)

                if ob.children:
                    for child in ob.children:
                        child_copy = child.copy()
                        collection.objects.link(child_copy)
                        child_copy.parent = ob_copy
                        child_copy.matrix_parent_inverse = child.matrix_parent_inverse

                for con in ob_copy.constraints:
                    if con.type == "FOLLOW_PATH":
                        break

                ob_copy.scale *= item.size / ob_copy.dimensions.y

                app((ob_copy, con, None, item.size))

    else:

        for ob in context.selected_objects:
            for con in ob.constraints:
                if con.type == "FOLLOW_PATH":

                    if self.rot_x:
                        ob_mat_rot = ob.matrix_basis.to_quaternion().to_matrix(
                        ).to_4x4()
                        mat_rot = Matrix.Rotation(self.rot_x, 4, "X")
                        ob.matrix_basis @= ob_mat_rot.inverted(
                        ) @ mat_rot @ ob_mat_rot

                    if self.rot_z:
                        mat_rot = Matrix.Rotation(self.rot_z, 4, "Z")
                        ob.matrix_basis @= mat_rot

                    if self.rot_x or self.loc_z:
                        dist = ob.matrix_basis.translation.length
                        mat_rot = ob.matrix_basis.to_quaternion().to_matrix()
                        ob.matrix_basis.translation = mat_rot @ Vector(
                            (0.0, 0.0, dist + self.loc_z))

                    app((ob, con, con.offset, ob.dimensions.y))
                    break

        obs.sort(key=operator.itemgetter(2), reverse=True)
        ob = context.object

        for con in ob.constraints:
            if con.type == "FOLLOW_PATH":
                break
        else:
            ob, con, _ = obs[0]

        curve = con.target

    curve.data.use_radius = False
    asset.apply_scale(curve)

    # Start offset
    # ---------------------------

    if self.use_absolute_offset:
        base_unit = 100.0 / self.curve_length
    else:
        ofst = 0.0
        num = len(obs)

        if num > 1:
            closed_distribution = round(end - start, 1) == 100.0

            if self.cyclic and closed_distribution:
                ofst = (end - start) / num
            else:
                if not self.cyclic:
                    start = max(start, 0.0)
                    end = min(end, 100.0)
                ofst = (end - start) / (num - 1)

    # (Re)Distribute
    # ---------------------------

    ofst_fac = start
    size_prev = 0.0
    consecutive_cycle = False

    for ob, con, _, size in obs:

        if self.use_absolute_offset:
            ofst = base_unit * ((size + size_prev) / 2 + self.spacing)
            size_prev = size

        if consecutive_cycle:
            ofst_fac += ofst
        else:
            consecutive_cycle = True

        con.offset = -ofst_fac

    return {"FINISHED"}
def __gather_trans_rot_scale(blender_object, export_settings):
    if blender_object.matrix_parent_inverse == Matrix.Identity(4):
        trans = blender_object.location

        if blender_object.rotation_mode in ['QUATERNION', 'AXIS_ANGLE']:
            rot = blender_object.rotation_quaternion
        else:
            rot = blender_object.rotation_euler.to_quaternion()

        sca = blender_object.scale
    else:
        # matrix_local = matrix_parent_inverse*location*rotation*scale
        # Decomposing matrix_local gives less accuracy, but is needed if matrix_parent_inverse is not the identity.

        if blender_object.matrix_local[3][3] != 0.0:
            trans, rot, sca = blender_object.matrix_local.decompose()
        else:
            # Some really weird cases, scale is null (if parent is null when evaluation is done)
            print_console(
                'WARNING',
                'Some nodes are 0 scaled during evaluation. Result can be wrong'
            )
            trans = blender_object.location
            if blender_object.rotation_mode in ['QUATERNION', 'AXIS_ANGLE']:
                rot = blender_object.rotation_quaternion
            else:
                rot = blender_object.rotation_euler.to_quaternion()
            sca = blender_object.scale

    # make sure the rotation is normalized
    rot.normalize()

    trans = __convert_swizzle_location(trans, export_settings)
    rot = __convert_swizzle_rotation(rot, export_settings)
    sca = __convert_swizzle_scale(sca, export_settings)

    if blender_object.instance_type == 'COLLECTION' and blender_object.instance_collection:
        offset = -__convert_swizzle_location(
            blender_object.instance_collection.instance_offset,
            export_settings)

        s = Matrix.Diagonal(sca).to_4x4()
        r = rot.to_matrix().to_4x4()
        t = Matrix.Translation(trans).to_4x4()
        o = Matrix.Translation(offset).to_4x4()
        m = t @ r @ s @ o

        trans = m.translation

    translation, rotation, scale = (None, None, None)
    trans[0], trans[1], trans[2] = gltf2_blender_math.round_if_near(trans[0], 0.0), gltf2_blender_math.round_if_near(trans[1], 0.0), \
                                   gltf2_blender_math.round_if_near(trans[2], 0.0)
    rot[0], rot[1], rot[2], rot[3] = gltf2_blender_math.round_if_near(rot[0], 1.0), gltf2_blender_math.round_if_near(rot[1], 0.0), \
                                     gltf2_blender_math.round_if_near(rot[2], 0.0), gltf2_blender_math.round_if_near(rot[3], 0.0)
    sca[0], sca[1], sca[2] = gltf2_blender_math.round_if_near(sca[0], 1.0), gltf2_blender_math.round_if_near(sca[1], 1.0), \
                             gltf2_blender_math.round_if_near(sca[2], 1.0)
    if trans[0] != 0.0 or trans[1] != 0.0 or trans[2] != 0.0:
        translation = [trans[0], trans[1], trans[2]]
    if rot[0] != 1.0 or rot[1] != 0.0 or rot[2] != 0.0 or rot[3] != 0.0:
        rotation = [rot[1], rot[2], rot[3], rot[0]]
    if sca[0] != 1.0 or sca[1] != 1.0 or sca[2] != 1.0:
        scale = [sca[0], sca[1], sca[2]]
    return translation, rotation, scale
Ejemplo n.º 20
0
def execute(self, context):

    # Set objects
    # ---------------------------

    sizes_list = context.window_manager.jewelcraft.sizes.values()

    if self.is_distribute:

        if not sizes_list:
            return {"FINISHED"}

        curve, ob = _get_obs()

        curve.select_set(False)
        context.view_layer.objects.active = ob

        mat_sca = Matrix.Diagonal(ob.scale).to_4x4()
        ob.matrix_world = mat_sca

        if self.rot_x:
            mat_rot = Matrix.Rotation(self.rot_x, 4, "X")
            ob.matrix_world @= mat_rot

        if self.rot_z:
            mat_rot = Matrix.Rotation(self.rot_z, 4, "Z")
            ob.matrix_world @= mat_rot

        if self.loc_z:
            mat_loc = Matrix.Translation((0.0, 0.0, self.loc_z))
            ob.matrix_world @= mat_loc

        obs = _create_dstr(ob, curve, sizes_list)

    elif self.hash_sizes != _hash(sizes_list):

        for is_last, con in iterutils.spot_last(list(_get_cons())):
            ob = con.id_data

            if not is_last:

                for child in ob.children:
                    bpy.data.objects.remove(child)

                bpy.data.objects.remove(ob)

        context.view_layer.objects.active = ob
        curve = con.target

        _deform_redstr(ob, self.rot_x, self.rot_z, self.loc_z)
        obs = _create_dstr(ob, curve, sizes_list, con_add=False)

    else:

        obs = []
        app = obs.append

        for con in _get_cons():
            ob = con.id_data
            _deform_redstr(ob, self.rot_x, self.rot_z, self.loc_z)
            app((con, con.offset, ob.dimensions.y))

        obs.sort(key=operator.itemgetter(1), reverse=True)

        con = obs[0][0]
        curve = con.target

    curve.data.use_radius = False
    asset.apply_scale(curve)

    # Offset values
    # ---------------------------

    start = self.start
    end = self.end

    if not self.use_absolute_offset:
        ofst = 0.0
        num = len(obs)

        if num > 1:
            closed_distribution = round(end - start, 1) == 100.0

            if self.cyclic and closed_distribution:
                ofst = (end - start) / num
            else:
                if not self.cyclic:
                    start = max(start, 0.0)
                    end = min(end, 100.0)
                ofst = (end - start) / (num - 1)

    # Distribute
    # ---------------------------

    ofst_fac = start
    size_prev = 0.0
    consecutive_cycle = False

    for con, _, size in obs:

        if self.use_absolute_offset:
            ofst = self.base_unit * ((size + size_prev) / 2 + self.spacing)
            size_prev = size

        if consecutive_cycle:
            ofst_fac += ofst
        else:
            consecutive_cycle = True

        con.offset = -ofst_fac

    return {"FINISHED"}
Ejemplo n.º 21
0
    def execute(self, context):
        space_data = context.space_data
        use_local_view = bool(space_data.local_view)
        collection = context.collection
        start = self.start
        end = self.end

        # Init
        # ---------------------------

        if self.is_scatter:
            num = self.number - 1

            curve = context.object
            curve.select_set(False)

            ob = context.selected_objects[0]
            context.view_layer.objects.active = ob

        else:
            obs = {}

            for ob in context.selected_objects:
                con = ob.constraints.get("Follow Path")
                if con:
                    obs[ob] = con.offset

            obs_sorted = sorted(obs, key=obs.get, reverse=True)
            num = len(obs_sorted) - 1
            ob = context.object

            if ob not in obs:
                ob = obs_sorted[0]

            curve = ob.constraints["Follow Path"].target

        curve.data.use_radius = False
        asset.apply_scale(curve)

        # Offset
        # ---------------------------

        ofst = 0.0

        if num:

            if self.use_absolute_offset:
                ob_size = ob.dimensions[1]
                base_unit = 100.0 / self.curve_length

                ofst = base_unit * (ob_size + self.spacing)

            else:
                closed_scattering = True if round(end -
                                                  start, 1) == 100.0 else False

                if self.cyclic and closed_scattering:
                    ofst = (end - start) / (num + 1)
                else:
                    if not self.cyclic:
                        start = start if start >= 0.0 else 0.0
                        end = end if end <= 100.0 else 100.0

                    ofst = (end - start) / num

        # Scatter/Redistribute
        # ---------------------------

        if self.is_scatter:

            mat_sca = Matrix.Diagonal(ob.scale).to_4x4()
            ob.matrix_world = mat_sca

            if self.rot_y:
                mat_rot = Matrix.Rotation(self.rot_y, 4, "Y")
                ob.matrix_world @= mat_rot

            if self.rot_z:
                mat_rot = Matrix.Rotation(self.rot_z, 4, "Z")
                ob.matrix_world @= mat_rot

            if self.loc_z:
                mat_loc = Matrix.Translation((0.0, 0.0, self.loc_z))
                ob.matrix_world @= mat_loc

            ofst_fac = start + ofst

            for _ in range(num):
                ob_copy = ob.copy()
                collection.objects.link(ob_copy)

                if use_local_view:
                    ob_copy.local_view_set(space_data, True)

                con = ob_copy.constraints.new("FOLLOW_PATH")
                con.target = curve
                con.offset = -ofst_fac
                con.use_curve_follow = True
                con.forward_axis = "FORWARD_X"

                ofst_fac += ofst

                if ob.children:
                    for child in ob.children:
                        child_copy = child.copy()
                        collection.objects.link(child_copy)
                        child_copy.parent = ob_copy
                        child_copy.matrix_parent_inverse = child.matrix_parent_inverse

            con = ob.constraints.new("FOLLOW_PATH")
            con.target = curve
            con.offset = -start
            con.use_curve_follow = True
            con.forward_axis = "FORWARD_X"

        else:

            ofst_fac = start

            for ob in obs_sorted:

                if self.rot_y:
                    mat_rot = Matrix.Rotation(self.rot_y, 4, "Y")
                    ob.matrix_basis @= mat_rot

                if self.rot_z:
                    mat_rot = Matrix.Rotation(self.rot_z, 4, "Z")
                    ob.matrix_basis @= mat_rot

                if self.loc_z:
                    mat_loc = Matrix.Translation((0.0, 0.0, self.loc_z))
                    ob.matrix_basis @= mat_loc

                ob.constraints["Follow Path"].offset = -ofst_fac
                ofst_fac += ofst

        return {"FINISHED"}
Ejemplo n.º 22
0
def handle_draw_callback():
    if not bpy.data.actions:
        return
    action = bpy.data.actions[bpy.context.scene.rw4_list_index]
    if not action.rw4.is_morph_handle or not is_anim_panel_showing():
        return

    direction = Vector(action.rw4.final_pos) - Vector(action.rw4.initial_pos)
    length = direction.length
    if length == 0.0:
        return

    bbox = calc_global_bbox()
    width = (bbox[1] - bbox[0]).length * 0.02

    scale_matrix = Matrix.Scale(direction.length, 3, Vector((0, 0, 1)))
    scale_matrix = scale_matrix @ Matrix.Scale(width, 3, Vector((0, 1, 0)))
    scale_matrix = scale_matrix @ Matrix.Scale(width, 3, Vector((1, 0, 0)))

    matrix = Vector((0, 0, 1)).rotation_difference(direction).to_matrix()
    matrix = Matrix.Translation(
        action.rw4.initial_pos) @ (matrix @ scale_matrix).to_4x4()

    bgl.glEnable(bgl.GL_BLEND)
    bgl.glEnable(bgl.GL_LINE_SMOOTH)
    bgl.glEnable(bgl.GL_POLYGON_SMOOTH)
    bgl.glBlendFunc(bgl.GL_SRC_ALPHA, bgl.GL_ONE_MINUS_SRC_ALPHA)

    shader.bind()
    shader.uniform_float("color", (109 / 255.0, 141 / 255.0, 143 / 255.0, 0.4))
    batch = batch_for_shader(shader,
                             'TRIS',
                             {"pos": [matrix @ Vector(c) for c in BOX_COORDS]},
                             indices=BOX_INDICES)
    batch.draw(shader)

    # Draw initial handle pos
    handle_width = width * 1.25
    matrix = Matrix.Translation(
        Vector(action.rw4.initial_pos) - Vector(
            (0, 0, 0.5 * handle_width)))  # center it
    matrix = matrix @ Matrix.Diagonal(
        (handle_width, handle_width, handle_width, 1))

    shader.uniform_float("color", (165 / 255.0, 195 / 255.0, 196 / 255.0, 0.4))
    batch = batch_for_shader(shader,
                             'TRIS',
                             {"pos": [matrix @ Vector(c) for c in BOX_COORDS]},
                             indices=BOX_INDICES)
    batch.draw(shader)

    # Draw initial handle pos
    handle_width = width * 1.25
    matrix = Matrix.Translation(
        Vector(action.rw4.final_pos) - Vector(
            (0, 0, 0.5 * handle_width)))  # center it
    matrix = matrix @ Matrix.Diagonal(
        (handle_width, handle_width, handle_width, 1))

    shader.uniform_float("color", (165 / 255.0, 195 / 255.0, 196 / 255.0, 0.4))
    batch = batch_for_shader(shader,
                             'TRIS',
                             {"pos": [matrix @ Vector(c) for c in BOX_COORDS]},
                             indices=BOX_INDICES)
    batch.draw(shader)
Ejemplo n.º 23
0
    def execute(self, context):
        import operator

        space_data = context.space_data
        use_local_view = bool(space_data.local_view)
        collection = context.collection
        start = self.start
        end = self.end

        # Init
        # ---------------------------

        if self.is_scatter:
            num = self.number - 1

            curve = context.object
            curve.select_set(False)

            ob = context.selected_objects[0]
            context.view_layer.objects.active = ob

        else:
            obs = []
            app = obs.append

            for ob in context.selected_objects:
                for con in ob.constraints:
                    if con.type == "FOLLOW_PATH":
                        app((ob, con, con.offset))
                        break

            obs.sort(key=operator.itemgetter(2), reverse=True)
            num = len(obs) - 1
            ob = context.object

            for con in ob.constraints:
                if con.type == "FOLLOW_PATH":
                    break
            else:
                ob, con, _ = obs[0]

            curve = con.target

        curve.data.use_radius = False
        asset.apply_scale(curve)

        # Offset
        # ---------------------------

        ofst = 0.0

        if num:

            if self.use_absolute_offset:
                ob_size = ob.dimensions[1]
                base_unit = 100.0 / self.curve_length

                ofst = base_unit * (ob_size + self.spacing)

            else:
                closed_scattering = True if round(end -
                                                  start, 1) == 100.0 else False

                if self.cyclic and closed_scattering:
                    ofst = (end - start) / (num + 1)
                else:
                    if not self.cyclic:
                        start = start if start >= 0.0 else 0.0
                        end = end if end <= 100.0 else 100.0

                    ofst = (end - start) / num

        # Scatter/Redistribute
        # ---------------------------

        if self.is_scatter:

            mat_sca = Matrix.Diagonal(ob.scale).to_4x4()
            ob.matrix_world = mat_sca

            if self.rot_x:
                mat_rot = Matrix.Rotation(self.rot_x, 4, "X")
                ob.matrix_world @= mat_rot

            if self.rot_z:
                mat_rot = Matrix.Rotation(self.rot_z, 4, "Z")
                ob.matrix_world @= mat_rot

            if self.loc_z:
                mat_loc = Matrix.Translation((0.0, 0.0, self.loc_z))
                ob.matrix_world @= mat_loc

            ofst_fac = start + ofst

            for _ in range(num):
                ob_copy = ob.copy()
                collection.objects.link(ob_copy)

                if use_local_view:
                    ob_copy.local_view_set(space_data, True)

                con = ob_copy.constraints.new("FOLLOW_PATH")
                con.target = curve
                con.offset = -ofst_fac
                con.use_curve_follow = True
                con.forward_axis = "FORWARD_X"

                ofst_fac += ofst

                if ob.children:
                    for child in ob.children:
                        child_copy = child.copy()
                        collection.objects.link(child_copy)
                        child_copy.parent = ob_copy
                        child_copy.matrix_parent_inverse = child.matrix_parent_inverse

            con = ob.constraints.new("FOLLOW_PATH")
            con.target = curve
            con.offset = -start
            con.use_curve_follow = True
            con.forward_axis = "FORWARD_X"

        else:

            ofst_fac = start

            for ob, con, _ in obs:

                if self.rot_x:
                    ob_mat_rot = ob.matrix_basis.to_quaternion().to_matrix(
                    ).to_4x4()
                    mat_rot = Matrix.Rotation(self.rot_x, 4, "X")
                    ob.matrix_basis @= ob_mat_rot.inverted(
                    ) @ mat_rot @ ob_mat_rot

                if self.rot_z:
                    mat_rot = Matrix.Rotation(self.rot_z, 4, "Z")
                    ob.matrix_basis @= mat_rot

                if self.rot_x or self.loc_z:
                    dist = ob.matrix_basis.translation.length
                    mat_rot = ob.matrix_basis.to_quaternion().to_matrix()
                    ob.matrix_basis.translation = mat_rot @ Vector(
                        (0.0, 0.0, dist + self.loc_z))

                con.offset = -ofst_fac
                ofst_fac += ofst

        return {"FINISHED"}
Ejemplo n.º 24
0
def sync_light(ainode, light):
    data = light.data
    _type = types.AI_LIGHT_TYPE[data.type]

    # Common properties
    AiNodeSetStr(ainode, "name", light.name)
    if not hasattr(data, "shape") or light.data.shape != 'RECTANGLE':
        # Set matrix for everything except cylinder lights
        AiNodeSetMatrix(ainode, "matrix",
                        generate_aimatrix(light.matrix_world))

    AiNodeSetRGB(ainode, "color", *data.color)
    AiNodeSetFlt(ainode, "intensity", data.arnold.intensity)
    AiNodeSetFlt(ainode, "exposure", data.arnold.exposure)
    AiNodeSetInt(ainode, "samples", data.arnold.samples)
    AiNodeSetBool(ainode, "normalize", data.arnold.normalize)

    AiNodeSetBool(ainode, "cast_shadows", data.arnold.cast_shadows)
    AiNodeSetBool(ainode, "cast_volumetric_shadows",
                  data.arnold.cast_volumetric_shadows)
    AiNodeSetFlt(ainode, "shadow_density", data.arnold.shadow_density)

    AiNodeSetFlt(ainode, "camera", data.arnold.camera)
    AiNodeSetFlt(ainode, "diffuse", data.arnold.diffuse)
    AiNodeSetFlt(ainode, "specular", data.arnold.specular)
    AiNodeSetFlt(ainode, "transmission", data.arnold.transmission)
    AiNodeSetFlt(ainode, "sss", data.arnold.sss)
    AiNodeSetFlt(ainode, "indirect", data.arnold.indirect)
    AiNodeSetFlt(ainode, "volume", data.arnold.volume)
    AiNodeSetInt(ainode, "max_bounces", data.arnold.max_bounces)
    # shadow_color

    # Light data
    if _type in ('point_light', 'spot_light'):
        AiNodeSetFlt(ainode, "radius", data.shadow_soft_size)

    if _type == 'distant_light':
        AiNodeSetFlt(ainode, "angle", data.arnold.angle)

    if _type == 'spot_light':
        AiNodeSetFlt(ainode, "cone_angle", math.degrees(data.spot_size))
        AiNodeSetFlt(ainode, "penumbra_angle",
                     math.degrees(data.arnold.penumbra_angle))
        AiNodeSetFlt(ainode, "roundness", data.arnold.spot_roundness)
        AiNodeSetFlt(ainode, "aspect_ratio", data.arnold.aspect_ratio)
        AiNodeSetFlt(ainode, "lens_radius", data.arnold.lens_radius)

    if _type == 'area_light':
        if data.shape == 'SQUARE':
            smatrix = Matrix.Diagonal(
                (data.size / 2, data.size / 2, data.size / 2)).to_4x4()

            tmatrix = light.matrix_world @ smatrix

            AiNodeSetMatrix(ainode, "matrix", generate_aimatrix(tmatrix))
        elif data.shape == 'DISK':
            # Get largest scale value (disk_light can't be an ellipse)
            s = light.scale.x if light.scale.x > light.scale.y else light.scale.y
            AiNodeSetFlt(ainode, "radius", 0.5 * data.size * s)
        elif data.shape == 'RECTANGLE':
            # Cylinder light
            d = 0.5 * data.size_y * light.scale.y
            top = get_position_along_local_vector(light, d, 'Y')
            bottom = get_position_along_local_vector(light, -d, 'Y')

            AiNodeSetVec(ainode, "top", *top)
            AiNodeSetVec(ainode, "bottom", *bottom)

            s = light.scale.x if light.scale.x > light.scale.z else light.scale.z
            AiNodeSetFlt(ainode, "radius", 0.5 * data.size * s)

        AiNodeSetFlt(ainode, "roundness", data.arnold.area_roundness)
        AiNodeSetFlt(ainode, "spread", data.arnold.spread)
        AiNodeSetInt(ainode, "resolution", data.arnold.resolution)
        AiNodeSetFlt(ainode, "soft_edge", data.arnold.soft_edge)
    def import_animation_channel(
            b_pose_bone, b_action, b_action_group, channel, index, channel_keyframes):

        import_locrot = channel.keyframe_class in (rw4_base.LocRotScaleKeyframe, rw4_base.LocRotKeyframe)
        import_scale = channel.keyframe_class == rw4_base.LocRotScaleKeyframe

        fcurves_qr = []
        fcurves_vt = []
        fcurves_vs = []

        if import_locrot:
            data_path = b_pose_bone.path_from_id('rotation_quaternion')
            for i in range(4):
                fcurve = b_action.fcurves.new(data_path, index=i)
                fcurve.group = b_action_group
                fcurves_qr.append(fcurve)

            data_path = b_pose_bone.path_from_id('location')
            for i in range(3):
                fcurve = b_action.fcurves.new(data_path, index=i)
                fcurve.group = b_action_group
                fcurves_vt.append(fcurve)

        if import_scale:
            data_path = b_pose_bone.path_from_id('scale')
            for i in range(3):
                fcurve = b_action.fcurves.new(data_path, index=i)
                fcurve.group = b_action_group
                fcurves_vs.append(fcurve)

        for k, kf in enumerate(channel.keyframes):
            time = kf.time * rw4_base.KeyframeAnim.FPS
            bpy.context.scene.frame_set(time)  # So that parent.matrix works

            transform = channel_keyframes[index][k]

            if import_locrot:
                # Rotation is in model space
                qr = transform.to_quaternion()
                if b_pose_bone.parent is not None:
                    qr = b_pose_bone.parent.matrix.inverted().to_quaternion() @ qr

                for i in range(4):
                    fcurves_qr[i].keyframe_points.insert(time, qr[i])

                # The position, in world coordinates relative to origin
                vt = transform @ b_pose_bone.bone.head_local

                # Convert from WORLD position into LOCAL position
                # This only works because we import our bones with no rotation; for exporting this will require more
                if b_pose_bone.parent is not None:
                    parent_matrix = b_pose_bone.parent.matrix
                    parent_transform = (parent_matrix @ b_pose_bone.parent.bone.matrix_local.inverted())
                    # The position, in world coordinates relative to posed position
                    world_pos_relative = vt - (parent_transform @ b_pose_bone.bone.head_local)
                    vt = parent_matrix.to_3x3().inverted() @ world_pos_relative

                for i in range(3):
                    fcurves_vt[i].keyframe_points.insert(time, vt[i])

                if import_scale:
                    # It's in the transform coordinate system; in Blender it's in the bone.matrix
                    # Since we use an Identity rotation for the bone matrix, it's the same
                    vs = transform.to_scale()

                    # BUT we have to compensate for the parent scaling.
                    if b_pose_bone.parent is not None:
                        _, parent_rotation, parent_scale = b_pose_bone.parent.matrix.decompose()
                        world_parent_scale = (parent_rotation.to_matrix() @ Matrix.Diagonal(parent_scale)).to_scale()

                        # This assumes matrix_local is Identity
                        _, rotation, _ = transform.decompose()
                        # Parent scale in bone coordinate system
                        bone_parent_scale = \
                            (rotation.inverted().to_matrix() @ Matrix.Diagonal(world_parent_scale)).to_scale()

                        vs = Vector([vs[i] / bone_parent_scale[i] for i in range(3)])

                    for i in range(3):
                        fcurves_vs[i].keyframe_points.insert(time, vs[i])
Ejemplo n.º 26
0
 def compute_world_matrix(self):
     mat_loc = Matrix.Translation(self.cur_location).to_4x4()
     mat_rot = Euler(self.cur_rotation, 'XYZ').to_matrix().to_4x4()
     mat_sca = Matrix.Diagonal(self.cur_scale).to_4x4()
     mat = mat_loc @ mat_rot @ mat_sca
     return self.cur_link_matrix @ mat
Ejemplo n.º 27
0
def apply_scale(ob: Object) -> None:
    mat = Matrix.Diagonal(ob.scale).to_4x4()
    ob.data.transform(mat)
    ob.scale = (1.0, 1.0, 1.0)
Ejemplo n.º 28
0
    def get_active_geo_rotation(self, context) -> str:
        '''Get and set the normal direction of the 3D Cursor based on the active geo'''
        return_msg = None
        ob = context.object
        me = ob.data

        self.bm = bmesh.from_edit_mesh(me)
        self.bm.faces.ensure_lookup_table()
        self.bm.edges.ensure_lookup_table()

        if 'VERT' in self.bm.select_mode:
            return_msg = 'This operator does not work on vertices'
            return return_msg

        active_geo = self.bm.select_history.active
        if active_geo is None:
            return_msg = "Could not find the active geo to copy rotation"
            return return_msg

        if isinstance(active_geo, bmesh.types.BMFace):
            direction_vec = active_geo.normal
        else:  # Edge
            if self.edge_rot_type == 'face_1_angle':
                if len(active_geo.link_faces):
                    direction_vec = active_geo.link_faces[0].normal
                else:
                    return_msg = "There is no face to sample from, using edge angle as fallback"
            elif self.edge_rot_type == 'face_2_angle':
                if len(active_geo.link_faces) > 1:
                    direction_vec = active_geo.link_faces[1].normal
                else:
                    return_msg = "There is no second face to sample from, using edge angle as fallback"
            elif self.edge_rot_type == 'average_face_angle':
                if len(active_geo.link_faces) > 1:
                    direction_vec = (active_geo.link_faces[0].normal +
                                     active_geo.link_faces[1].normal) / 2
                else:
                    return_msg = "There isn't 2 faces to average from, using edge angle as fallback"

            if self.edge_rot_type == 'edge_angle' or return_msg is not None:  # Also use as fallback
                direction_vec = active_geo.verts[0].co - active_geo.verts[1].co

        _loc, rot, scl = ob.matrix_world.decompose()
        rot_mat = rot.to_matrix().to_4x4()
        scl_mat = Matrix.Diagonal(scl.to_4d())
        direction_vec = rot_mat @ scl_mat @ direction_vec

        normal_quat = direction_vec.to_track_quat('Z', 'Y')
        normal_euler = normal_quat.to_euler('XYZ')
        normal_axis = normal_quat.to_axis_angle()

        if self.internal_align_type == 'cursor':
            cursor = context.scene.cursor

            if cursor.rotation_mode == 'QUATERNION':
                cursor.rotation_quaternion = normal_quat
            elif cursor.rotation_mode == 'AXIS_ANGLE':
                cursor.rotation_axis_angle[0] = normal_axis[1]
                cursor.rotation_axis_angle[1] = normal_axis[0][0]
                cursor.rotation_axis_angle[2] = normal_axis[0][1]
                cursor.rotation_axis_angle[3] = normal_axis[0][2]
            else:
                cursor.rotation_euler = normal_euler
        else:  # origin
            ob_mat = ob.matrix_world

            bpy.ops.object.mode_set(mode='OBJECT')

            # Make local space = world space
            me.transform(ob_mat)

            # Faster way to calculate new matrix - without refreshing the View Layer
            loc, _rot, scl = ob_mat.decompose()
            ob.matrix_world = (
                Matrix.Translation(loc) @ normal_euler.to_matrix().to_4x4()
                @ Matrix.Diagonal(scl.to_4d()))

            # Apply the inverted matrix on the mesh
            me.transform(ob.matrix_world.inverted())

            bpy.ops.object.mode_set(mode='EDIT')
            self.bm = bmesh.from_edit_mesh(ob.data)

            # Wierd rounding errors without this, less accurate but looks nicer
            ob.rotation_euler[0] = round(ob.rotation_euler[0], 5)
            ob.rotation_euler[1] = round(ob.rotation_euler[1], 5)
            ob.rotation_euler[2] = round(ob.rotation_euler[2], 5)

        if return_msg is not None:
            self.report({'WARNING'}, return_msg)
Ejemplo n.º 29
0
 def make_control_widget(self, ctrl, is_hip):
     obj = create_circle_widget(self.obj, ctrl, radius=1.0, head_tail=0.5)
     if is_hip:
         adjust_widget_transform_mesh(obj,
                                      Matrix.Diagonal((1, -1, 1, 1)),
                                      local=True)
    def process_animation(self, animation):
        """
        Process all the keyframes of the animation, computing the final transformation matrices used in the shader.
        The matrices are the model space transformation from the base pose to the animated pose.
        Returns a list of channels, where every channel is a list of Matrix4 keyframes with the transformation.
        :param animation:
        :return: [[channel0_keyframe0, channel0_keyframe1,...], [channel1_keyframe0,...],...]
        """
        # 1. Clssify all keyframes by their time
        # [(time1, pose_bones), (time2, pose_bones), etc], one keyframe per channel per time
        keyframe_poses = {}
        for c, channel in enumerate(animation.channels):
            for kf in channel.keyframes:
                if kf.time not in keyframe_poses:
                    keyframe_poses[kf.time] = [None] * len(animation.channels)

                if channel.keyframe_class == rw4_base.LocRotScaleKeyframe or \
                        channel.keyframe_class == rw4_base.LocRotKeyframe:
                    r = kf.rot
                    t = kf.loc
                else:
                    r = Quaternion()
                    t = Vector((0, 0, 0))

                if channel.keyframe_class == rw4_base.LocRotScaleKeyframe:
                    s = kf.scale
                else:
                    s = Vector((1.0, 1.0, 1.0))

                keyframe_poses[kf.time][c] = PoseBone(r=r, t=t, s=s)

        # 2. Process the transformation matrix
        # This is the same algorithm used by Spore; the result is what is sent to the DirectX shader
        # These are the transforms in model space from the rest pose to the animated pose
        # This assumes that parents will always be processed before their children
        # List of channels, which are list of PoseBone keyframes containing the transformation
        channel_keyframes = [[] for _ in animation.channels]

        # Process for every channel for every time
        # We must do it even if the channel didn't have a keyframe there, because it might be used by other channels
        for time, pose_bones in sorted(keyframe_poses.items()):
            branches = []  # Used as an stack
            parent_rot = Matrix.Identity(3)
            parent_loc = Vector((0, 0, 0))
            parent_scale = Vector((1.0, 1.0, 1.0))  # inverse scale

            for c, (pose_bone, bone, skin) in enumerate(zip(pose_bones, self.bones, self.skin_data)):
                skip_bone = pose_bone is None
                if skip_bone:
                    pose_bone = interpolate_pose(animation, time, c, keyframe_poses)

                # Apply the scale
                scale_matrix = Matrix.Diagonal(pose_bone.s)
                parent_inv_scale = \
                    Matrix.Diagonal((1.0 / parent_scale.x, 1.0 / parent_scale.y, 1.0 / parent_scale.z))
                scaled_m = parent_inv_scale @ (pose_bone.r.to_matrix() @ scale_matrix)
                m = parent_rot @ scaled_m

                t = parent_rot @ pose_bone.t + parent_loc

                if not skip_bone:
                    dst_r = m @ skin.matrix.inverted()
                    dst_t = t + (m @ skin.translation)

                    #for i in range(3):
                    #    print(f"skin_bones_data += struct.pack('ffff', {dst_r[i][0]}, {dst_r[i][1]}, {dst_r[i][2]}, {dst_t[i]})")
                    channel_keyframes[c].append(Matrix.Translation(dst_t) @ dst_r.to_4x4())

                if bone.flags == rw4_base.SkeletonBone.TYPE_ROOT:
                    parent_rot = m
                    parent_loc = t
                    parent_scale = pose_bone.s

                elif bone.flags == rw4_base.SkeletonBone.TYPE_LEAF:
                    if branches:
                        parent_rot, parent_loc, parent_scale = branches.pop()

                elif bone.flags == rw4_base.SkeletonBone.TYPE_BRANCH:
                    branches.append((parent_rot, parent_loc, parent_scale))
                    parent_rot = m
                    parent_loc = t
                    parent_scale = pose_bone.s

        return channel_keyframes