You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PyOpenGLtk框架下魔方分步复原动画实现问题求助

魔方分步复原动画问题解决方案

问题背景

我使用PyOpenGL、Tkinter和PyOpenGLtk绘制魔方,已实现魔方展示及切片旋转功能,但无法完成分步复原动画。当重复执行动画指令(如两次调用self.animate, self.action = True, rot_slice_map['K_1'])时,仅最后一条指令生效,动画始终重复同一动作。相关代码如下:

import tkinter as tk
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *
from pyopengltk import OpenGLFrame

vertices = (
    (1, -1, -1), (1, 1, -1), (-1, 1, -1), (-1, -1, -1),
    (1, -1, 1), (1, 1, 1), (-1, -1, 1), (-1, 1, 1)
)
edges = ((0, 1), (0, 3), (0, 4), (2, 1), (2, 3), (2, 7), (6, 3), (6, 4), (6, 7), (5, 1), (5, 4), (5, 7))
surfaces = ((0, 1, 2, 3), (3, 2, 7, 6), (6, 7, 5, 4), (4, 5, 1, 0), (1, 5, 7, 2), (4, 0, 3, 6))
colors = ((1, 0, 0), (0, 1, 0), (1, 0.5, 0), (1, 1, 0), (1, 1, 1), (0, 0, 1))

rot_cube_map = {'K_UP': (-1, 0), 'K_DOWN': (1, 0), 'K_LEFT': (0, -1), 'K_RIGHT': (0, 1)}
rot_slice_map = {
    'K_1': (0, 0, 1), 'K_2': (0, 1, 1), 'K_3': (0, 2, 1), 'K_4': (1, 0, 1), 'K_5': (1, 1, 1),
    'K_6': (1, 2, 1), 'K_7': (2, 0, 1), 'K_8': (2, 1, 1), 'K_9': (2, 2, 1),
    'K_F1': (0, 0, -1), 'K_F2': (0, 1, -1), 'K_F3': (0, 2, -1), 'K_F4': (1, 0, -1), 'K_F5': (1, 1, -1),
    'K_F6': (1, 2, -1), 'K_F7': (2, 0, -1), 'K_F8': (2, 1, -1), 'K_F9': (2, 2, -1),
}


class Cube():
    def __init__(self, id, N, scale):
        self.N = 3
        self.scale = scale
        self.init_i = [*id]
        self.current_i = [*id]
        self.rot = [[1 if i == j else 0 for i in range(3)] for j in range(3)]

    def isAffected(self, axis, slice, dir):
        return self.current_i[axis] == slice

    def update(self, axis, slice, dir):
        if not self.isAffected(axis, slice, dir):
            return
        i, j = (axis + 1) % 3, (axis + 2) % 3
        for k in range(3):
            self.rot[k][i], self.rot[k][j] = -self.rot[k][j] * dir, self.rot[k][i] * dir
        self.current_i[i], self.current_i[j] = (
            self.current_i[j] if dir < 0 else self.N - 1 - self.current_i[j],
            self.current_i[i] if dir > 0 else self.N - 1 - self.current_i[i])

    def transformMat(self):
        scaleA = [[s * self.scale for s in a] for a in self.rot]
        scaleT = [(p - (self.N - 1) / 2) * 2.1 * self.scale for p in self.current_i]
        return [*scaleA[0], 0, *scaleA[1], 0, *scaleA[2], 0, *scaleT, 1]

    def draw(self, col, surf, vert, animate, angle, axis, slice, dir):
        glPushMatrix()
        if animate and self.isAffected(axis, slice, dir):
            glRotatef(angle * dir, *[1 if i == axis else 0 for i in range(3)])
        glMultMatrixf(self.transformMat())
        glBegin(GL_QUADS)
        for i in range(len(surf)):
            glColor3fv(colors[i])
            for j in surf[i]:
                glVertex3fv(vertices[j])
        glEnd()
        glPopMatrix()


class mycube():
    def __init__(self, N, scale):
        self.N = N
        cr = range(self.N)
        self.cubes = [Cube((x, y, z), self.N, scale) for x in cr for y in cr for z in cr]

    def maindd(self):
        for cube in self.cubes:
            cube.draw(colors, surfaces, vertices, False, 0, 0, 0, 0)


class GLFrame(OpenGLFrame):
    def initgl(self):
        self.rota = 0
        self.count = 0
        self.ang_x, self.ang_y, self.rot_cube = 0, 0, (0, 0)
        self.animate1, self.animate_ang, self.animate_speed = False, 0, 0.5
        self.action = (0, 0, 0)
        glClearColor(0.0, 0.0, 0.0, 0.0)
        glEnable(GL_DEPTH_TEST)
        glDepthFunc(GL_LEQUAL)
        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()
        gluPerspective(30, self.width / self.height, 0.1, 50.0)

    def redraw(self):
        self.N = 3
        cr = range(self.N)
        self.cubes = [Cube((x, y, z), self.N, 1.5) for x in cr for y in cr for z in cr]
        self.animate, self.action = True, rot_slice_map['K_1']
        self.ang_x += self.rot_cube[0] * 2
        self.ang_y += self.rot_cube[1] * 2
        glMatrixMode(GL_MODELVIEW)
        glLoadIdentity()
        glTranslatef(0, 0, -40)
        glRotatef(self.ang_y, 0, 1, 0)
        glRotatef(self.ang_x, 1, 0, 0)
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
        if self.animate1:
            if self.animate_ang >= 90:
                for cube in self.cubes:
                    cube.update(*self.action)
                self.animate1, self.animate_ang = False, 0
        for cube in self.cubes:
            cube.draw(colors, surfaces, vertices, self.animate, self.animate_ang, *self.action)
        if self.animate:
            self.animate_ang += self.animate_speed


class App(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title('Pineapple')
        self.glframe = GLFrame(self, width=800, height=600)
        self.glframe.pack(expand=True, fill=tk.BOTH)


App().mainloop()

问题分析与解决方案

核心问题点

  1. 魔方状态被频繁重置:redraw方法每次都会重新创建self.cubes,导致之前的旋转操作全部丢失,相当于每次都从零开始画魔方。
  2. 动画指令被直接覆盖:重复赋值self.action会把前一个动作覆盖,程序只会执行最后一次设置的动作。
  3. 动画状态逻辑混乱:animate和animate1两个变量职责重叠,没有清晰的动画完成判定与后续动作衔接逻辑。

具体修改方案

1. 把魔方初始化移到initgl

将创建魔方的代码从redraw移到初始化方法,确保魔方状态可以持续保存:

def initgl(self):
    self.rota = 0
    self.count = 0
    self.ang_x, self.ang_y, self.rot_cube = 0, 0, (0, 0)
    self.animate1, self.animate_ang, self.animate_speed = False, 0, 0.5
    # 初始化魔方实例
    self.N = 3
    cr = range(self.N)
    self.cubes = [Cube((x, y, z), self.N, 1.5) for x in cr for y in cr for z in cr]
    # 新增动作队列,用来存储分步动画指令
    self.action_queue = []
    self.current_action = None
    # OpenGL初始化配置
    glClearColor(0.0, 0.0, 0.0, 0.0)
    glEnable(GL_DEPTH_TEST)
    glDepthFunc(GL_LEQUAL)
    glMatrixMode(GL_PROJECTION)
    glLoadIdentity()
    gluPerspective(30, self.width / self.height, 0.1, 50.0)

2. 添加动作队列管理函数

用队列来存储需要执行的动画,完成一个动作后自动启动下一个:

def add_action(self, action):
    self.action_queue.append(action)
    # 如果当前没有正在执行的动画,直接启动第一个动作
    if not self.animate1 and not self.current_action:
        self.start_next_action()

def start_next_action(self):
    # 从队列取出下一个动作
    if self.action_queue:
        self.current_action = self.action_queue.pop(0)
        self.animate1 = True
        self.animate_ang = 0
    else:
        # 队列为空,结束动画
        self.current_action = None
        self.animate1 = False

3. 重构redraw方法的动画逻辑

移除重复创建魔方的代码,修正动画完成后的处理逻辑:

def redraw(self):
    self.ang_x += self.rot_cube[0] * 2
    self.ang_y += self.rot_cube[1] * 2
    glMatrixMode(GL_MODELVIEW)
    glLoadIdentity()
    glTranslatef(0, 0, -40)
    glRotatef(self.ang_y, 0, 1, 0)
    glRotatef(self.ang_x, 1, 0, 0)
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

    # 处理动画进度
    if self.animate1:
        if self.animate_ang >= 90:
            # 动画完成,更新魔方实际状态
            for cube in self.cubes:
                cube.update(*self.current_action)
            # 启动下一个动作
            self.start_next_action()
        else:
            # 累加动画角度
            self.animate_ang += self.animate_speed

    # 绘制魔方
    for cube in self.cubes:
        if self.animate1 and self.current_action:
            # 正在执行动画,绘制带旋转的切片
            cube.draw(colors, surfaces, vertices, True, self.animate_ang, *self.current_action)
        else:
            # 无动画,绘制当前状态的魔方
            cube.draw(colors, surfaces, vertices, False, 0, 0, 0, 0)

4. 测试分步动画

在App类中添加多个动作,测试分步执行效果:

class App(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title('Pineapple')
        self.glframe = GLFrame(self, width=800, height=600)
        self.glframe.pack(expand=True, fill=tk.BOTH)
        # 添加多个分步动作,会依次执行
        self.glframe.add_action(rot_slice_map['K_1'])
        self.glframe.add_action(rot_slice_map['K_2'])
        self.glframe.add_action(rot_slice_map['K_3'])

修改后的完整代码

import tkinter as tk
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *
from pyopengltk import OpenGLFrame

vertices = (
    (1, -1, -1), (1, 1, -1), (-1, 1, -1), (-1, -1, -1),
    (1, -1, 1), (1, 1, 1), (-1, -1, 1), (-1, 1, 1)
)
edges = ((0, 1), (0, 3), (0, 4), (2, 1), (2, 3), (2, 7), (6, 3), (6, 4), (6, 7), (5, 1), (5, 4), (5, 7))
surfaces = ((0, 1, 2, 3), (3, 2, 7, 6), (6, 7, 5, 4), (4, 5, 1, 0), (1, 5, 7, 2), (4, 0, 3, 6))
colors = ((1, 0, 0), (0, 1, 0), (1, 0.5, 0), (1, 1, 0), (1, 1, 1), (0, 0, 1))

rot_cube_map = {'K_UP': (-1, 0), 'K_DOWN': (1, 0), 'K_LEFT': (0, -1), 'K_RIGHT': (0, 1)}
rot_slice_map = {
    'K_1': (0, 0, 1), 'K_2': (0, 1, 1), 'K_3': (0, 2, 1), 'K_4': (1, 0, 1), 'K_5': (1, 1, 1),
    'K_6': (1, 2, 1), 'K_7': (2, 0, 1), 'K_8': (2, 1, 1), 'K_9': (2, 2, 1),
    'K_F1': (0, 0, -1), 'K_F2': (0, 1, -1), 'K_F3': (0, 2, -1), 'K_F4': (1, 0, -1), 'K_F5': (1, 1, -1),
    'K_F6': (1, 2, -1), 'K_F7': (2, 0, -1), 'K_F8': (2, 1, -1), 'K_F9': (2, 2, -1),
}


class Cube():
    def __init__(self, id, N, scale):
        self.N = 3
        self.scale = scale
        self.init_i = [*id]
        self.current_i = [*id]
        self.rot = [[1 if i == j else 0 for i in range(3)] for j in range(3)]

    def isAffected(self, axis, slice, dir):
        return self.current_i[axis] == slice

    def update(self, axis, slice, dir):
        if not self.isAffected(axis, slice, dir):
            return
        i, j = (axis + 1) % 3, (axis + 2) % 3
        for k in range(3):
            self.rot[k][i], self.rot[k][j] = -self.rot[k][j] * dir, self.rot[k][i] * dir
        self.current_i[i], self.current_i[j] = (
            self.current_i[j] if dir < 0 else self.N - 1 - self.current_i[j],
            self.current_i[i] if dir > 0 else self.N - 1 - self.current_i[i])

    def transformMat(self):
        scaleA = [[s * self.scale for s in a] for a in self.rot]
        scaleT = [(p - (self.N - 1) / 2) * 2.1 * self.scale for p in self.current_i]
        return [*scaleA[0], 0, *scaleA[1], 0, *scaleA[2], 0, *scaleT, 1]

    def draw(self, col, surf, vert, animate, angle, axis, slice, dir):
        glPushMatrix()
        if animate and self.isAffected(axis, slice, dir
相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 00:25:30