Macro Rubik Cube

Description
Macro to Display a Rubik Cube and interactively do slice rotations.



Script
""" """ __title__  = "Rubik_cube" __author__ = "Aleph0" __version__ = "00.03" __date__   = "25/10/2017" __Comment__ = "Virtual Rubik Cube" __Wiki__ = "http://www.freecadweb.org/wiki/index.php?title=Macro_Rubik_Cube" __Help__ = "see first few lines of macro text" __Status__ = "stable" __Requires__ = "freecad 0.16"
 * 1) -*- coding: utf-8 -*-
 * This macro creates a virtual Rubik Cube and enable you to manipulate *
 * it.                                                                  *
 * You can chooose the size (number of small cubes along an edge).      *
 * It then makes the cube: large sizes can take a while.                *
 * It then displays several views of the cube.                          *
 * The central and largest view is an axonometric projection.           *
 * This has arrows around it which you can click on to rotate slices.   *
 * Another view is an axonometric projection from the other side.       *
 * Another view combines views towards each face so as to               *
 * look like a net of the cube's surface unfolded.                      *
 * The macro maintains a history of the slice rotations you have done.  *
 * It puts three buttons at the top of the window.                      *
 * One undoes the last slice rotation and removes it from the history.  *
 * One saves the history to the clipboard as a sequence of function     *
 * calls which can be pasted into a macro which can then be called      *
 * to replay the same set of rotations. Thus you can save an "operator" *
 * (a sequence of slice rotation which does something useful).          *
 * The third button resets the cube to its initial state                *
 * and clears the history.                                              *
 * Copyright © 2017 Richard P. Parkins, M. A.                           *
 * This file is a supplement to the FreeCAD CAx development system.     *
 * This program is free software; you can redistribute it and/or modify *
 * it under the terms of the GNU Lesser General Public License (LGPL)   *
 * as published by the Free Software Foundation; either version 2 of    *
 * the License, or (at your option) any later version.                  *
 * for detail see the LICENCE text file.                                *
 * This software is distributed in the hope that it will be useful,     *
 * but WITHOUT ANY WARRANTY; without even the implied warranty of       *
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the         *
 * GNU Library General Public License for more details.                 *
 * You should have received a copy of the GNU Library General Public    *
 * License along with this macro; if not, write to the Free Software    *
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  *
 * USA                                                                  *
 * This software is distributed in the hope that it will be useful,     *
 * but WITHOUT ANY WARRANTY; without even the implied warranty of       *
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the         *
 * GNU Library General Public License for more details.                 *
 * You should have received a copy of the GNU Library General Public    *
 * License along with this macro; if not, write to the Free Software    *
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  *
 * USA                                                                  *
 * USA                                                                  *


 * OS: Ubuntu 14.04.5 LTS
 * 1) Word size of OS: 64-bit
 * 2) Word size of FreeCAD: 64-bit
 * 3) Version: 0.16.6703 (Git)
 * 4) Build type: None
 * 5) Branch: releases/FreeCAD-0-16
 * 6) Hash: 2ce5c8d2e3020d05005ed71f710e09e9aa561f40
 * 7) Python version: 2.7.6
 * 8) Qt version: 4.8.6
 * 9) Coin version: 4.0.0a
 * 10) OCC version: 6.8.0.oce-0.17

slowness = 500
 * 1) This parameter determines the speed at which slice rotations are animated
 * 2) If you have a faster computer you can increase the value
 * 3) This will make the animation smoother
 * 4) If you have a slow computer you can decrease the value
 * 5) This will make the animation faster but more jerky for large cubes

import FreeCAD import Part import time from FreeCAD import Base from FreeCAD import Console from pivy import coin from pivy.coin import * import PySide

try: from PyQt4 import QtCore, QtGui except Exception: from PySide import QtCore, QtGui
 * 1) I copied this code from Macro_Mouse_Cross
 * 2) It seems a bit version dependent, but it works

if not hasattr(FreeCAD, "Rubik_Cube_executed"): FreeCAD.Rubik_Cube_executed = 1 Dictionary = {}
 * 1) If this is the first time this macro has been run in this invocation of FreeCAD,
 * 2) create our dictionary of document-specific data structures

App.ActiveDocument = App.newDocument("Rubik_Cube") Gui.ActiveDocument = Gui.getDocument(str(App.ActiveDocument.Name))
 * 1) Create a new document and make it current

width = 300 height = 150 defaultSize = 3 class AskSizeWindow(QtGui.QDialog): # automagically called when the window is created def __init__(self): super(AskSizeWindow, self).__init__ self.initUI # Lay out the interactive elements def initUI(self): self.setWindowTitle("Rubik Cube Size") geom = Gui.getMainWindow.geometry xpos = geom.center.x - width / 2 ypos = geom.center.y - height / 2 self.setGeometry(xpos, ypos, width, height) margin = 10 ypos = margin self.label_1 = QtGui.QLabel(self) self.label_1.setGeometry(QtCore.QRect(width/2 - 100, ypos, 200, 25)) self.label_1.setObjectName("label_1") self.label_1.setText("Number of small cubes") self.label_1.setAlignment(QtCore.Qt.AlignCenter) ypos = ypos + 25 self.label_2 = QtGui.QLabel(self) self.label_2.setGeometry(QtCore.QRect(width/2 - 100, ypos, 200, 25)) self.label_2.setObjectName("label_1") self.label_2.setText("along an edge") self.label_2.setAlignment(QtCore.Qt.AlignCenter) ypos = ypos + 25 self.spinBox = QtGui.QSpinBox(self) self.spinBox.setGeometry(QtCore.QRect(width/2 - 50, ypos, 100, 30)) self.spinBox.setMinimum(2) self.spinBox.setMaximum(100) Dictionary[str(App.ActiveDocument.Name)+"Size"] = defaultSize self.spinBox.setValue(defaultSize) self.spinBox.setSingleStep(1) self.spinBox.setObjectName("spinBox") self.spinBox.valueChanged.connect(self.on_spinBox_valueChanged) ypos = ypos + 40 self.OKbutton = QtGui.QPushButton(self) self.OKbutton.setGeometry(QtCore.QRect(width/2 - 40, ypos, 80, 40)) self.OKbutton.setText("OK") self.OKbutton.clicked.connect(self.onOK) def on_spinBox_valueChanged(self, val): Dictionary[str(App.ActiveDocument.Name)+"Size"] = val def onOK(self): self.close self.destroy AskSizeWindow.exec_
 * 1) This bit of code pops up the dialog to ask for the size

QtGui.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.WaitCursor))
 * 1) Display a wait cursor while we are making the cube

class ViewObserver: def __init__(self): self.view = FreeCADGui.ActiveDocument.ActiveView self.callback = self.view.addEventCallbackPivy(SoMouseButtonEvent.getClassTypeId,self.getpoint) def getpoint(self,event_cb): event = event_cb.getEvent if event.getButton == 1: pos = event.getPosition.getValue obj = self.view.getObjectInfo((int(pos[0]),int(pos[1]))) if obj != None: obname = obj["Object"] if obname[:5] == "arrow": if event.getState == 1: wh = obname[5:7] i = int(obname[7:]) if wh == "mX": rotmX(i) elif wh == "pX": rotpX(i) elif wh == "mY": rotmY(i) elif wh == "pY": rotpY(i) elif wh == "mZ": rotmZ(i) elif wh == "pZ": rotpZ(i) event_cb.setHandled Dictionary[str(App.ActiveDocument.Name)+"ViewObserver"] = ViewObserver
 * 1) This bit of code catches clicks on the rotation arrows

fcd = App.ActiveDocument n = Dictionary[str(App.ActiveDocument.Name)+"Size"] fc = [[range(n) for iy in range(n)] for ix in range(n)] Dictionary[str(App.ActiveDocument.Name)+"cubies"] = fc for ix in range(n):   fx = ix - (n - 1) / 2.0    for iy in range(n):        fy = iy - (n - 1) / 2.0        for iz in range(n):            fz = iz - (n - 1) / 2.0            fs = "fs"+str(ix)+"q"+str(iy)+"q"+str(iz)            x0y0z0 = Base.Vector(fx-0.5,fy-0.5,fz-0.5)            x0y0z1 = Base.Vector(fx-0.5,fy-0.5,fz+0.5)            x0y1z0 = Base.Vector(fx-0.5,fy+0.5,fz-0.5)            x0y1z1 = Base.Vector(fx-0.5,fy+0.5,fz+0.5)            x1y0z0 = Base.Vector(fx+0.5,fy-0.5,fz-0.5)            x1y0z1 = Base.Vector(fx+0.5,fy-0.5,fz+0.5)            x1y1z0 = Base.Vector(fx+0.5,fy+0.5,fz-0.5)            x1y1z1 = Base.Vector(fx+0.5,fy+0.5,fz+0.5)            face = Part.Face(Part.makePolygon([x0y0z0,x0y0z1,x0y1z1,x0y1z0,x0y0z0]))            f1 = fcd.addObject("Part::Feature", fs+"x0")            f1.Shape = face            if ix == 0:                f1.ViewObject.DiffuseColor=[(1.0,1.0,1.0)]            else:                f1.ViewObject.DiffuseColor=[(0.0,0.0,0.0)]            f1.ViewObject.RootNode.setName(coin.SbName(fs+"x0"))            face = Part.Face(Part.makePolygon([x1y0z0,x1y0z1,x1y1z1,x1y1z0,x1y0z0]))            f2 = fcd.addObject("Part::Feature", fs+"x1")            f2.Shape = face            if ix == n - 1:                f2.ViewObject.DiffuseColor=[(1.0,0.0,0.0)]            else:                f2.ViewObject.DiffuseColor=[(0.0,0.0,0.0)]            f2.ViewObject.RootNode.setName(coin.SbName(fs+"x1"))            face = Part.Face(Part.makePolygon([x0y0z0,x0y0z1,x1y0z1,x1y0z0,x0y0z0]))            f3 = fcd.addObject("Part::Feature", fs+"y0")            f3.Shape = face            if iy == 0:                f3.ViewObject.DiffuseColor=[(0.0,1.0,0.0)]            else:                f3.ViewObject.DiffuseColor=[(0.0,0.0,0.0)]            f3.ViewObject.RootNode.setName(coin.SbName(fs+"y0"))            face = Part.Face(Part.makePolygon([x0y1z0,x0y1z1,x1y1z1,x1y1z0,x0y1z0]))            f4 = fcd.addObject("Part::Feature", fs+"y1")            f4.Shape = face            if iy == n - 1:                f4.ViewObject.DiffuseColor=[(1.0,0.0,1.0)]            else:                f4.ViewObject.DiffuseColor=[(0.0,0.0,0.0)]            f4.ViewObject.RootNode.setName(coin.SbName(fs+"y1"))            face = Part.Face(Part.makePolygon([x0y0z0,x0y1z0,x1y1z0,x1y0z0,x0y0z0]))            f5 = fcd.addObject("Part::Feature", fs+"z0")            f5.Shape = face            if iz == 0:                f5.ViewObject.DiffuseColor=[(1.0,1.0,0.0)]            else:                f5.ViewObject.DiffuseColor=[(0.0,0.0,0.0)]            f5.ViewObject.RootNode.setName(coin.SbName(fs+"z0"))            face = Part.Face(Part.makePolygon([x0y0z1,x0y1z1,x1y1z1,x1y0z1,x0y0z1]))            f6 = fcd.addObject("Part::Feature", fs+"z1")            f6.Shape = face            if iz == n - 1:                f6.ViewObject.DiffuseColor=[(0.0,0.0,1.0)]            else:                f6.ViewObject.DiffuseColor=[(0.0,0.0,0.0)]            f6.ViewObject.RootNode.setName(coin.SbName(fs+"z1"))            fc[ix][iy][iz]=[f1,f2,f3,f4,f5,f6]
 * 1) This bit of code creates the basic cube model
 * 2) It is composed of faces rather than cubes because I haven't found a way of making
 * 3) a cube with different coloured faces

for i in range(n): fx = i - (n - 1) / 2.0 fy = -(n / 2.0) fz = -(0.2 + n / 2.0) fs = "arrowpX"+str(i) v0 = Base.Vector(fx-0.1,fy,fz) v1 = Base.Vector(fx-0.1,fy,fz-0.5) v2 = Base.Vector(fx-0.2,fy,fz-0.5) v3 = Base.Vector(fx,fy,fz-0.7) v4 = Base.Vector(fx+0.2,fy,fz-0.5) v5 = Base.Vector(fx+0.1,fy,fz-0.5) v6 = Base.Vector(fx+0.1,fy,fz) arrow = fcd.addObject("Part::Feature", fs) arrow.Shape = Part.Face(Part.makePolygon([v0,v1,v2,v3,v4,v5,v6,v0])) arrow.ViewObject.DiffuseColor=[(0.0,0.0,0.0)] arrow.ViewObject.RootNode.setName(coin.SbName(fs)) arrow.ViewObject.Selectable = False fy = 0.2 + n / 2.0 fz = n / 2.0 fs = "arrowmX"+str(i) v0 = Base.Vector(fx-0.1,fy,fz) v1 = Base.Vector(fx-0.1,fy+0.5,fz) v2 = Base.Vector(fx-0.2,fy+0.5,fz) v3 = Base.Vector(fx,fy+0.7,fz) v4 = Base.Vector(fx+0.2,fy+0.5,fz) v5 = Base.Vector(fx+0.1,fy+0.5,fz) v6 = Base.Vector(fx+0.1,fy,fz) arrow = fcd.addObject("Part::Feature", fs) arrow.Shape = Part.Face(Part.makePolygon([v0,v1,v2,v3,v4,v5,v6,v0])) arrow.ViewObject.DiffuseColor=[(0.0,0.0,0.0)] arrow.ViewObject.RootNode.setName(coin.SbName(fs)) arrow.ViewObject.Selectable = False fx = n / 2.0 fy = i - (n - 1) / 2.0 fz = -(0.2 + n / 2.0) fs = "arrowpY"+str(i) v0 = Base.Vector(fx,fy-0.1,fz) v1 = Base.Vector(fx,fy-0.1,fz-0.5) v2 = Base.Vector(fx,fy-0.2,fz-0.5) v3 = Base.Vector(fx,fy,fz-0.7) v4 = Base.Vector(fx,fy+0.2,fz-0.5) v5 = Base.Vector(fx,fy+0.1,fz-0.5) v6 = Base.Vector(fx,fy+0.1,fz) arrow = fcd.addObject("Part::Feature", fs) arrow.Shape = Part.Face(Part.makePolygon([v0,v1,v2,v3,v4,v5,v6,v0])) arrow.ViewObject.DiffuseColor=[(0.0,0.0,0.0)] arrow.ViewObject.RootNode.setName(coin.SbName(fs)) arrow.ViewObject.Selectable = False fx = -(0.2 + n / 2.0) fy = i - (n - 1) / 2.0 fz = n / 2.0 fs = "arrowmY"+str(i) v0 = Base.Vector(fx,fy-0.1,fz) v1 = Base.Vector(fx-0.5,fy-0.1,fz) v2 = Base.Vector(fx-0.5,fy-0.2,fz) v3 = Base.Vector(fx-0.7,fy,fz) v4 = Base.Vector(fx-0.5,fy+0.2,fz) v5 = Base.Vector(fx-0.5,fy+0.1,fz) v6 = Base.Vector(fx,fy+0.1,fz) arrow = fcd.addObject("Part::Feature", fs) arrow.Shape = Part.Face(Part.makePolygon([v0,v1,v2,v3,v4,v5,v6,v0])) arrow.ViewObject.DiffuseColor=[(0.0,0.0,0.0)] arrow.ViewObject.RootNode.setName(coin.SbName(fs)) arrow.ViewObject.Selectable = False fx = n / 2.0 fy = 0.2 + n / 2.0 fz = i - (n - 1) / 2.0 fs = "arrowpZ"+str(i) v0 = Base.Vector(fx,fy,fz-0.1) v1 = Base.Vector(fx,fy+0.5,fz-0.1) v2 = Base.Vector(fx,fy+0.5,fz-0.2) v3 = Base.Vector(fx,fy+0.7,fz) v4 = Base.Vector(fx,fy+0.5,fz+0.2) v5 = Base.Vector(fx,fy+0.5,fz+0.1) v6 = Base.Vector(fx,fy,fz+0.1) arrow = fcd.addObject("Part::Feature", fs) arrow.Shape = Part.Face(Part.makePolygon([v0,v1,v2,v3,v4,v5,v6,v0])) arrow.ViewObject.DiffuseColor=[(0.0,0.0,0.0)] arrow.ViewObject.RootNode.setName(coin.SbName(fs)) arrow.ViewObject.Selectable = False fx = -(0.2 + n / 2.0) fy = -(n / 2.0) fz = i - (n - 1) / 2.0 fs = "arrowmZ"+str(i) v0 = Base.Vector(fx,fy,fz-0.1) v1 = Base.Vector(fx-0.5,fy,fz-0.1) v2 = Base.Vector(fx-0.5,fy,fz-0.2) v3 = Base.Vector(fx-0.7,fy,fz) v4 = Base.Vector(fx-0.5,fy,fz+0.2) v5 = Base.Vector(fx-0.5,fy,fz+0.1) v6 = Base.Vector(fx,fy,fz+0.1) arrow = fcd.addObject("Part::Feature", fs) arrow.Shape = Part.Face(Part.makePolygon([v0,v1,v2,v3,v4,v5,v6,v0])) arrow.ViewObject.DiffuseColor=[(0.0,0.0,0.0)] arrow.ViewObject.RootNode.setName(coin.SbName(fs)) arrow.ViewObject.Selectable = False
 * 1) This bit of code creates the clickable arrows
 * 2) Note we make them not selectable because mouse clicking on them
 * 3) does a slice rotation instead of selecting the arrow.

sceneGraph = Gui.ActiveDocument.ActiveView.getViewer.getSoEventManager.getSceneGraph
 * 1) This gets FreeCAD's top level SceneGraph (including camera node),
 * 2) not the document's SceneGraph which hangs off of it

Gui.ActiveDocument.ActiveView.viewAxonometric Gui.SendMsgToActiveView("ViewFit") camera = sceneGraph.getChild(2) if camera.getTypeId.getName.__str__ == "OrthographicCamera": camera.height.setValue((2.2 + n / 12.0) * camera.height.getValue) rotation = camera.orientation.getValue # will be needed later
 * 1) Viewfit doesn't seem to do the right thing with MultiViews
 * 2) so we adjust the camera height manually before creating them

def findView(widget): if widget.metaObject.className.__str__ == "Gui::View3DInventor": return widget else: result = None for child in widget.children: v = findView(child) if v != None: result = v       return result view3DWidget = findView(QtGui.qApp.activeWindow.centralWidget)
 * 1) This bit of code finds the widget corresponding to the View3DInventor

height = 40 class ButtonRow(QtGui.QWidget): def __init__(self): super(ButtonRow, self).__init__ self.setParent(view3DWidget) self.setAutoFillBackground(True) xpos = 0 geom = view3DWidget.geometry self.setGeometry(xpos, 0, geom.width, height) buttonWidth = 80 gap = geom.width / 4 - buttonWidth if gap < 0: gap = 0 xpos = gap self.undoButton = QtGui.QPushButton(self) self.undoButton.setGeometry(xpos, 0, buttonWidth, 30) self.undoButton.setText("Undo") self.undoButton.clicked.connect(self.onUndo) xpos = xpos + buttonWidth + gap self.saveButton = QtGui.QPushButton(self) self.saveButton.setGeometry(xpos, 0, 3 * buttonWidth, 30) self.saveButton.setText("Copy history to clipboard") self.saveButton.clicked.connect(self.onSave) xpos = xpos + 3 * buttonWidth + gap self.resetButton = QtGui.QPushButton(self) self.resetButton.setGeometry(xpos, 0, buttonWidth, 30) self.resetButton.setText("Reset") self.resetButton.clicked.connect(self.onReset) self.show def onUndo(self): undo def onReset(self): reset def onSave(self): saveHistory if view3DWidget != None: Dictionary[str(App.ActiveDocument.Name)+"buttons"] = ButtonRow
 * 1) This bit of code creates the buttons at the top of the view window
 * 2) The buttons are in a frameless window to save screen space

if str(sceneGraph.getChild(0).getName) <> "LightModel": lm=coin.SoLightModel lm.model.setValue(0) lm.setName("LightModel") sceneGraph.insertChild(lm,0)
 * 1) This bit of code disables the default Phong shading
 * 2) and avoids the face colours appearing to change during rotation

def MultiViews(parent, child, i): newchild=coin.SoMultipleCopy newchild.addChild(child) views=coin.SoMFMatrix views.setNum(8) m1=coin.SbMatrix m1.makeIdentity views.set1Value(0,m1) m2=coin.SbMatrix m2.setTransform(       coin.SbVec3f(n,n,0.0),        coin.SbRotation(coin.SbVec3f(-0.5,0.5,1),3.14159),        coin.SbVec3f(0.5,0.5,0.5)) views.set1Value(1,m2) m3=coin.SbMatrix m3.setTransform(       coin.SbVec3f(-(n*1.3),-(n*1.3),0.0),        rotation,        coin.SbVec3f(0.5,0.5,0.5)) views.set1Value(2,m3) m4=coin.SbMatrix m4.setTransform(       coin.SbVec3f(0,n,0),        coin.SbRotation(coin.SbVec3f(1,0,0),3.14159*90/180.0),        coin.SbVec3f(1,1,1)) m4.multRight(m3) views.set1Value(3,m4) m5=coin.SbMatrix m5.setTransform(       coin.SbVec3f(n,0,0),        coin.SbRotation(coin.SbVec3f(0,1,0),-3.14159*90/180.0),        coin.SbVec3f(1,1,1)) m5.multRight(m3) views.set1Value(4,m5) m6=coin.SbMatrix m6.makeIdentity m6.setTransform(       coin.SbVec3f(-n,0,0),        coin.SbRotation(coin.SbVec3f(0,1,0),3.14159*90/180.0),        coin.SbVec3f(1,1,1)) m6.multRight(m3) views.set1Value(5,m6) m7=coin.SbMatrix m7.setTransform(       coin.SbVec3f(0,-n,0),        coin.SbRotation(coin.SbVec3f(-1,0,0),3.14159*90/180.0),        coin.SbVec3f(1,1,1)) m7.multRight(m3) views.set1Value(6,m7) m8=coin.SbMatrix m8.setTransform(       coin.SbVec3f(0,-n*2,0),        coin.SbRotation(coin.SbVec3f(-1,0,0),3.14159),        coin.SbVec3f(1,1,1)) m8.multRight(m3) views.set1Value(7,m8) newchild.matrix=views parent.replaceChild(i,newchild) def createMultiViews: sg = FreeCADGui.ActiveDocument.ActiveView.getSceneGraph if sg.getNumChildren != 0: for i in range(sg.getNumChildren): child = sg.getChild(i) type = child.getTypeId.getName.__str__ if child.getTypeId.getName.__str__ == 'Separator': if child.getName.__str__[:5] != "arrow": MultiViews(sg,child,i) if child.getTypeId.getName.__str__ == 'MultipleCopy': if child.getNumChildren != 0: name = child.getChild(0).getName.__str__ if fcd.getObject(name) == None: child.removeAllChildren createMultiViews
 * 1) This bit of code persuades FreeCAD'a renderer to put
 * 2) several views of the cube into the same window

QtGui.QApplication.restoreOverrideCursor
 * 1) Restore the normal cursor now that we have done all the slow stuff

def slowrotate(dir, num): n = Dictionary[str(App.ActiveDocument.Name)+"Size"] fc = Dictionary[str(App.ActiveDocument.Name)+"cubies"] fp = [[[[Base.Placement for j in range(6)] for ix in range(n)] for iy in range(n)] for ix in range(n)]   fq = [[[[Part.Shape for j in range(6)] for ix in range(n)] for iy in range(n)] for ix in range(n)]    steps = slowness / (n * n)    if dir.x > 0:        for iy in range(n):            for iz in range(n):                for j in range(6):                    c = fc[num][iy][iz][j]                    fp[num][iy][iz][j] = c.Placement        for i in range(steps + 1):            fm = Base.Matrix            fm.rotateX((1.570795 * i) / steps)            for iy in range(n):                for iz in range(n):                    for j in range(6):                        c = fc[num][iy][iz][j]                        p = fp[num][iy][iz][j]                        c.Placement = Base.Placement(fm).multiply(p)            Gui.updateGui        for iy in range(n):            for iz in range(n):                for j in range(6):                    fq[num][iy][iz][j]=fc[num][iz][n-1-iy][j]        for iy in range(n):            for iz in range(n):                for j in range(6):                    fc[num][iy][iz][j]=fq[num][iy][iz][j]    elif dir.x < 0:        for iy in range(n):            for iz in range(n):                for j in range(6):                    c = fc[num][iy][iz][j]                    fp[num][iy][iz][j]=c.Placement        for i in range(steps + 1):            fm = Base.Matrix            fm.rotateX((-1.570795 * i) / steps)            for iy in range(n):                for iz in range(n):                    for j in range(6):                        c = fc[num][iy][iz][j]                        p = fp[num][iy][iz][j]                        c.Placement = Base.Placement(fm).multiply(p)            Gui.updateGui        for iy in range(n):            for iz in range(n):                for j in range(6):                    fq[num][iy][iz][j]=fc[num][n-1-iz][iy][j]        for iy in range(n):            for iz in range(n):                for j in range(6):                    fc[num][iy][iz][j]=fq[num][iy][iz][j]    elif dir.y > 0:        for ix in range(n):            for iz in range(n):                for j in range(6):                    c = fc[ix][num][iz][j]                    fp[ix][num][iz][j]=c.Placement        for i in range(steps + 1):            fm = Base.Matrix            fm.rotateY((1.570795 * i) / steps)            for ix in range(n):                for iz in range(n):                    for j in range(6):                        c = fc[ix][num][iz][j]                        p = fp[ix][num][iz][j]                        c.Placement = Base.Placement(fm).multiply(p)            Gui.updateGui        for ix in range(n):            for iz in range(n):                for j in range(6):                    fq[ix][num][iz][j]=fc[n-1-iz][num][ix][j]        for ix in range(n):            for iz in range(n):                for j in range(6):                    fc[ix][num][iz][j]=fq[ix][num][iz][j]    elif dir.y < 0:        for ix in range(n):            for iz in range(n):                for j in range(6):                    c = fc[ix][num][iz][j]                    fp[ix][num][iz][j]=c.Placement        for i in range(steps + 1):            fm = Base.Matrix            fm.rotateY((-1.570795 * i) / steps)            for ix in range(n):                for iz in range(n):                    for j in range(6):                        c = fc[ix][num][iz][j]                        p = fp[ix][num][iz][j]                        c.Placement = Base.Placement(fm).multiply(p)            Gui.updateGui        for ix in range(n):            for iz in range(n):                for j in range(6):                    fq[ix][num][iz][j]=fc[iz][num][n-1-ix][j]        for ix in range(n):            for iz in range(n):                for j in range(6):                    fc[ix][num][iz][j]=fq[ix][num][iz][j]    elif dir.z > 0:        for ix in range(n):            for iy in range(n):                for j in range(6):                    c = fc[ix][iy][num][j]                    fp[ix][iy][num][j]=c.Placement        for i in range(steps + 1):            fm = Base.Matrix            fm.rotateZ((1.570795 * i) / steps)            for ix in range(n):                for iy in range(n):                    for j in range(6):                        c = fc[ix][iy][num][j]                        p = fp[ix][iy][num][j]                        c.Placement = Base.Placement(fm).multiply(p)            Gui.updateGui        for ix in range(n):            for iy in range(n):                for j in range(6):                    fq[ix][iy][num][j]=fc[iy][n-1-ix][num][j]        for ix in range(n):            for iy in range(n):                for j in range(6):                    fc[ix][iy][num][j]=fq[ix][iy][num][j]    elif dir.z < 0:        for ix in range(n):            for iy in range(n):                for j in range(6):                    c = fc[ix][iy][num][j]                    fp[ix][iy][num][j]=c.Placement        for i in range(steps + 1):            fm = Base.Matrix            fm.rotateZ((-1.570795 * i) / steps)            for ix in range(n):                for iy in range(n):                    for j in range(6):                        c = fc[ix][iy][num][j]                        p = fp[ix][iy][num][j]                        c.Placement = Base.Placement(fm).multiply(p)            Gui.updateGui        for ix in range(n):            for iy in range(n):                for j in range(6):                    fq[ix][iy][num][j]=fc[n-1-iy][ix][num][j]        for ix in range(n):            for iy in range(n):                for j in range(6):                    fc[ix][iy][num][j]=fq[ix][iy][num][j]
 * 1) This bit of code animates a slice rotation

history = [] Dictionary[str(App.ActiveDocument.Name)+"history"] = history def rotpX(i): slowrotate(Base.Vector(1,0,0),i) Dictionary[str(App.ActiveDocument.Name)+"history"].append("rotpX("+str(i)+")") def rotpY(i): slowrotate(Base.Vector(0,1,0),i) Dictionary[str(App.ActiveDocument.Name)+"history"].append("rotpY("+str(i)+")") def rotpZ(i): slowrotate(Base.Vector(0,0,1),i) Dictionary[str(App.ActiveDocument.Name)+"history"].append("rotpZ("+str(i)+")") def rotmX(i): slowrotate(Base.Vector(-1,0,0),i) Dictionary[str(App.ActiveDocument.Name)+"history"].append("rotmX("+str(i)+")") def rotmY(i): slowrotate(Base.Vector(0,-1,0),i) Dictionary[str(App.ActiveDocument.Name)+"history"].append("rotmY("+str(i)+")") def rotmZ(i): slowrotate(Base.Vector(0,0,-1),i) Dictionary[str(App.ActiveDocument.Name)+"history"].append("rotmZ("+str(i)+")") def undo: history = Dictionary[str(App.ActiveDocument.Name)+"history"] if len(history) > 0: fs = history.pop wh = fs[:5] i = int(fs[6:len(fs)-1]) if wh == "rotmX": slowrotate(Base.Vector(1,0,0),i) elif wh == "rotpX": slowrotate(Base.Vector(-1,0,0),i) elif wh == "rotmY": slowrotate(Base.Vector(0,1,0),i) elif wh == "rotpY": slowrotate(Base.Vector(0,-1,0),i) elif wh == "rotmZ": slowrotate(Base.Vector(0,0,1),i) elif wh == "rotpZ": slowrotate(Base.Vector(0,0,-1),i) def saveHistory: history = Dictionary[str(App.ActiveDocument.Name)+"history"] n = Dictionary[str(App.ActiveDocument.Name)+"Size"] if len(history) > 0: fs = "" while len(history) > 0: s = history.pop(0) i = int(s[6:len(s)-1]) if 2 * i == n - 1: fs = fs + s[:6] + "n/2)\n"           elif i <= n / 2:                fs = fs + s[:6] + str(i) + ")\n" else: fs = fs + s[:6] + "n-1-" + str(n-1-i) + ")\n"        clip = QtCore.QCoreApplication.instance.clipboard        clip.setText(fs) def reset:    fcd = App.ActiveDocument    Dictionary[str(fcd.Name)+"history"] = []    n = Dictionary[str(fcd.Name)+"Size"]    fc = Dictionary[str(fcd.Name)+"cubies"]    for ix in range(n):        for iy in range(n):            for iz in range(n):                fs = "fs"+str(ix)+"q"+str(iy)+"q"+str(iz)                for j in range(6):                    c = fcd.getObject(fs+"x0")                    c.Placement = Base.Placement                    fc[ix][iy][iz][0] = c                    c = fcd.getObject(fs+"x1")                    c.Placement = Base.Placement                    fc[ix][iy][iz][1] = c                    c = fcd.getObject(fs+"y0")                    c.Placement = Base.Placement                    fc[ix][iy][iz][2] = c                    c = fcd.getObject(fs+"y1") c.Placement = Base.Placement fc[ix][iy][iz][3] = c                   c = fcd.getObject(fs+"z0") c.Placement = Base.Placement fc[ix][iy][iz][4] = c                   c = fcd.getObject(fs+"z1") c.Placement = Base.Placement fc[ix][iy][iz][5] = c
 * 1) This bit of code manages the history
 * 2) Once you have created a cube, these functions will be defined and in scope
 * 3) and you can call them from another macro craeted by saving history