Macro Rubik Cube

From FreeCAD Documentation
Revision as of 21:08, 5 December 2017 by Aleph0 (talk | contribs)

Description

File:Text-x-python Macro_Rubik_Cube

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

Macro version: 00.04
Last modified: 2017-12-05
Author: Aleph0
Author
Aleph0
Download
None
Links
Macro Version
00.04
Date last modified
2017-12-05
FreeCAD Version(s)
None
Default shortcut
None
See also
None

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


# -*- 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.    *
*   There are some text direction labels near the arrows: clicking on one *
*   of those rotates the whole cube. You have to click on the actual      *
*   letter: FreeCAD doesn't see clicks on the text background             *
*   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.                                               *
*                                                                         *
*   There are also some functions defined which can be called from the    *
*   python console window:                                                *
*   fix_reload() modifies a restored FreeCAD save file to add the extra   *
*   views and interactive controls that this macro and its functions use; *
*   ramdomise() randomises the cube;                                      *
*   reverse_history(), reflectX_history(), reflectY_history(),            *
*   reflectZ_history(), rotpX_history(), rotmX_history(),                 *
*   rotpY_history(), rotmY_history(), rotpZ_history(), and                *
*   rotmZ_history() each replace the history by a version modified in the *
*   manner indicated - these are useful for creating modified operators.  *
*                                                                         *
***************************************************************************
*   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                                                                   *
*                                                                         *
***************************************************************************
"""
__title__   = "Rubik_cube"
__author__  = "Aleph0"
__version__ = "00.04"
__date__    = "05/12/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"

#OS: Ubuntu 14.04.5 LTS
#Word size of OS: 64-bit
#Word size of FreeCAD: 64-bit
#Version: 0.16.6703 (Git)
#Build type: None
#Branch: releases/FreeCAD-0-16
#Hash: 2ce5c8d2e3020d05005ed71f710e09e9aa561f40
#Python version: 2.7.6
#Qt version: 4.8.6
#Coin version: 4.0.0a
#OCC version: 6.8.0.oce-0.17

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

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

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

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

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

# This bit of code pops up the dialog to ask for the size
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_()

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

# This bit of code catches clicks on the rotation arrows
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()
            obInfo = self.view.getObjectInfo((int(pos[0]),int(pos[1])))
            if obInfo != None:
                obj = App.ActiveDocument.getObject(obInfo["Object"])
                obname = obj.Label
                if obname[:5] == "arrow":
                    if event.getState() == 1:
                        wh = obname[5:7]
                        i = int(obname[7:])
                        if wh == "mX":
                            mrotmX(i,i+1)
                        elif wh == "pX":
                            mrotpX(i,i+1)
                        elif wh == "mY":
                            mrotmY(i,i+1)
                        elif wh == "pY":
                            mrotpY(i,i+1)
                        elif wh == "mZ":
                            mrotmZ(i,i+1)
                        elif wh == "pZ":
                            mrotpZ(i,i+1)
                    event_cb.setHandled()
                elif obname[:5] == "label":
                    if event.getState() == 1:
                        wh = obname[5:7]
                        n = Dictionary[str(App.ActiveDocument.Name)+"Size"]
                        if wh == "mX":
                            mrotmX(0,n)
                        elif wh == "pX":
                            mrotpX(0,n)
                        elif wh == "mY":
                            mrotmY(0,n)
                        elif wh == "pY":
                            mrotpY(0,n)
                        elif wh == "mZ":
                            mrotmZ(0,n)
                        elif wh == "pZ":
                            mrotpZ(0,n)
                    event_cb.setHandled()

Dictionary[str(App.ActiveDocument.Name)+"ViewObserver"] = ViewObserver()

# This bit of code creates the basic cube model
# It is composed of faces rather than cubes because I haven't found a way of making
# a cube with different coloured faces
# Each face has a name and a label
# The name is persistent regardless of how the face moves about the cube
# The label changes and tells where the face is now: this is needed for fix_reload
fcd = App.ActiveDocument
n = Dictionary[str(App.ActiveDocument.Name)+"Size"]
fc = [[[[None for j in range(6)] for ix in 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 = 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", "ff"+fs+"x0")
            f1.Shape = face
            f1.Label = "fs"+fs+"x0"
            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("ff"+fs+"x0"))
            face = Part.Face(Part.makePolygon([x1y0z0,x1y0z1,x1y1z1,x1y1z0,x1y0z0]))
            f2 = fcd.addObject("Part::Feature", "ff"+fs+"x1")
            f2.Shape = face
            f2.Label = "fs"+fs+"x1"
            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("ff"+fs+"x1"))
            face = Part.Face(Part.makePolygon([x0y0z0,x0y0z1,x1y0z1,x1y0z0,x0y0z0]))
            f3 = fcd.addObject("Part::Feature", "ff"+fs+"y0")
            f3.Shape = face
            f3.Label = "fs"+fs+"y0"
            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("ff"+fs+"y0"))
            face = Part.Face(Part.makePolygon([x0y1z0,x0y1z1,x1y1z1,x1y1z0,x0y1z0]))
            f4 = fcd.addObject("Part::Feature", "ff"+fs+"y1")
            f4.Shape = face
            f4.Label = "fs"+fs+"y1"
            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("ff"+fs+"y1"))
            face = Part.Face(Part.makePolygon([x0y0z0,x0y1z0,x1y1z0,x1y0z0,x0y0z0]))
            f5 = fcd.addObject("Part::Feature", "ff"+fs+"z0")
            f5.Shape = face
            f5.Label = "fs"+fs+"z0"
            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("ff"+fs+"z0"))
            face = Part.Face(Part.makePolygon([x0y0z1,x0y1z1,x1y1z1,x1y0z1,x0y0z1]))
            f6 = fcd.addObject("Part::Feature", "ff"+fs+"z1")
            f6.Shape = face
            f6.Label = "fs"+fs+"z1"
            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("ff"+fs+"z1"))
            fc[ix][iy][iz]=[f1,f2,f3,f4,f5,f6]

# This bit of code creates the clickable arrows
# Note we make them not selectable because mouse clicking on them
# does a slice rotation instead of selecting the arrow.
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

# This bit of code creates some labels for the arrows
# Clicking on one rotates the whole cube
def makeLabel(text, mat):
    n = Dictionary[str(App.ActiveDocument.Name)+"Size"]
    tx = Draft.makeText(text)
    tx.Label = "label"+text
    tx.ViewObject.TextColor = (0.0,0.0,0.0)
    tx.ViewObject.FontSize = 0.2 * n
    tx.ViewObject.Justification = 'Center'
    node = tx.ViewObject.RootNode
    node.setName(coin.SbName("label"+text))
    if node.getNumChildren() > 0:
        child = node.getChild(0)
        if child.getTypeId().getName().__str__() == "Transform":
            child.setMatrix(mat)

def doLabels():
    n = Dictionary[str(App.ActiveDocument.Name)+"Size"]
    m = coin.SbMatrix()
    m.makeIdentity()
    m.setTransform(
        coin.SbVec3f(0.0, - n / 2.0, -(1.0 + n * 0.7)),
        coin.SbRotation(coin.SbVec3f(1.0, 0.0, 0.0), 3.14159 / 2.0),
        coin.SbVec3f(1.0, 1.0, 1.0))
    makeLabel("pX", m)
    m = coin.SbMatrix()
    m.makeIdentity()
    m.setTransform(
        coin.SbVec3f(0.0, 1.1 + n / 2.0, n / 2.0),
        coin.SbRotation(coin.SbVec3f(0.0, 1.0, 0.0), 0.0),
        coin.SbVec3f(1.0, 1.0, 1.0))
    makeLabel("mX", m)
    m = coin.SbMatrix()
    m.makeIdentity()
    m.setTransform(
        coin.SbVec3f(n / 2.0, 0.0, -(1.0 + n * 0.7)),
        coin.SbRotation(coin.SbVec3f(0.0, 0.0, 1.0), 3.14159 / 2.0),
        coin.SbVec3f(1.0, 1.0, 1.0))
    m1 = coin.SbMatrix()
    m1.makeIdentity()
    m1.setTransform(
        coin.SbVec3f(0.0, 0.0, 0.0),
        coin.SbRotation(coin.SbVec3f(1.0, 0.0, 0.0), 3.14159 / 2.0),
        coin.SbVec3f(1.0, 1.0, 1.0))
    m1.multRight(m)
    makeLabel("pY", m1)
    m = coin.SbMatrix()
    m.makeIdentity()
    m.setTransform(
        coin.SbVec3f(-1.1 - n / 2.0, 0.0, n / 2.0),
        coin.SbRotation(coin.SbVec3f(0.0, 0.0, 1.0), 3.14159 / 2.0),
        coin.SbVec3f(1.0, 1.0, 1.0))
    makeLabel("mY", m)
    m = coin.SbMatrix()
    m.makeIdentity()
    m.setTransform(
        coin.SbVec3f(n / 2.0, 0.7 + n * 0.6, 0.0),
        coin.SbRotation(coin.SbVec3f(0.0, 1.0, 0.0), 3.14159 / 2.0),
        coin.SbVec3f(1.0, 1.0, 1.0))
    makeLabel("pZ", m)
    m = coin.SbMatrix()
    m.makeIdentity()
    m.setTransform(
        coin.SbVec3f(-1.1 - n / 2.0, - n / 2.0, 0.0),
        coin.SbRotation(coin.SbVec3f(1.0, 0.0, 0.0), 3.14159 / 2.0),
        coin.SbVec3f(1.0, 1.0, 1.0))
    m1 = coin.SbMatrix()
    m1.makeIdentity()
    m1.setTransform(
        coin.SbVec3f(0.0, 0.0, 0.0),
        coin.SbRotation(coin.SbVec3f(0.0, 0.0, 1.0), 3.14159 / 2.0),
        coin.SbVec3f(1.0, 1.0, 1.0))
    m1.multRight(m)
    makeLabel("mZ", m1)
doLabels()

Gui.ActiveDocument.ActiveView.viewAxonometric()
Gui.SendMsgToActiveView("ViewFit")
# Viewfit doesn't seem to do the right thing with MultiViews
# so we adjust the camera height manually before creating them
def fixCamera(lift):
    # This gets FreeCAD's top level SceneGraph (including camera node),
    # not the document's SceneGraph which hangs off of it
    v = Gui.ActiveDocument.ActiveView.getViewer()
    sceneGraph = v.getSoEventManager().getSceneGraph()
    camera = sceneGraph.getChild(2)
    if camera.getTypeId().getName().__str__() == "OrthographicCamera":
        if lift:
            camera.height.setValue((2.0 + n / 20.0) * camera.height.getValue())
        return camera.orientation.getValue()
rotation = fixCamera(True)

# This bit of code finds the widget corresponding to the View3DInventor
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

# This bit of code creates the buttons at the top of the view window
# The buttons are in a frameless window to save screen space
height = 40
class ButtonRow(QtGui.QWidget):
    def __init__(self):
        super(ButtonRow, self).__init__()
        view3DWidget = findView(QtGui.qApp.activeWindow().centralWidget())
        if view3DWidget != None:
            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()
Dictionary[str(App.ActiveDocument.Name)+"buttons"] = ButtonRow()

# This bit of code disables the default Phong shading
# and avoids the face colours appearing to change during rotation
def fixLightModel():
    v = Gui.ActiveDocument.ActiveView.getViewer()
    sceneGraph = v.getSoEventManager().getSceneGraph()
    if str(sceneGraph.getChild(0).getName()) <> "LightModel":
        lm=coin.SoLightModel()
        lm.model.setValue(0)
        lm.setName("LightModel")
        sceneGraph.insertChild(lm,0)
fixLightModel()

# This bit of code persuades FreeCAD'a renderer to put
# several views of the cube into the same window
def MultiViews(parent, child, i, rotation):
    n = Dictionary[str(App.ActiveDocument.Name)+"Size"]
    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 * 0.9 + 0.4, n * 0.9 + 0.4, 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.15 - 0.7, - n * 1.15 - 0.7, 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(rotation):
    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':
                name = child.getName().__str__()[:5]
                if name != "arrow" and name != "label":
                    MultiViews(sg,child,i,rotation)
            if child.getTypeId().getName().__str__() == 'MultipleCopy':
                if child.getNumChildren() != 0:
                    name = child.getChild(0).getName().__str__()
                    if fcd.getObject(name) == None:
                        child.removeAllChildren()
createMultiViews(rotation)

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

# This bit of code animates a slice rotation
def rotate(dir, rs, re, steps):
    n = Dictionary[str(App.ActiveDocument.Name)+"Size"]
    fc = Dictionary[str(App.ActiveDocument.Name)+"cubies"]
    fd = [[[[None for j in range(6)] for ix in range(n)] for iy in range(n)] for ix in range(n)]
    fp = [[[[Base.Placement() for j in range(6)] for ix in range(n)] for iy in range(n)] for ix in range(n)]
    xyz = ["x0","x1","y0","y1","z0","z1"]
    if dir.x > 0:
        # We need an explicit matrix for the final rotation step
        # to prevent rounding errors accumulating over time
        ff = Base.Matrix(1,0,0,0,0,0,-1,0,0,1,0,0,0,0,0,1)
        kk =[0,1,5,4,2,3]
        for iy in range(n):
            for iz in range(n):
                for j in range(rs, re):
                    for k in range(6):
                        c = fc[j][iy][iz][k]
                        c.Label = c.Name
                        fd[j][iy][iz][k] = c
                        fp[j][iy][iz][k] = c.Placement
        if steps > 0:
            fm = Base.Matrix()
            fm.rotateX(1.570795 / steps)
            for i in range(steps):
                for iy in range(n):
                    for iz in range(n):
                        for j in range(rs, re):
                            for k in range(6):
                                c = fc[j][iy][iz][k]
                                p = c.Placement
                                c.Placement = Base.Placement(fm).multiply(p)
                Gui.updateGui()
        for iy in range(n):
            for iz in range(n):
                for j in range(rs, re):
                    for k in range(6):
                        c = fd[j][iz][n-1-iy][kk[k]]
                        p = fp[j][iz][n-1-iy][kk[k]]
                        c.Label = "fs"+str(j)+"q"+str(iy)+"q"+str(iz)+xyz[k]
                        c.Placement = Base.Placement(ff).multiply(p)
                        fc[j][iy][iz][k] = c
    elif dir.x < 0:
        ff = Base.Matrix(1,0,0,0,0,0,1,0,0,-1,0,0,0,0,0,1)
        kk =[0,1,4,5,3,2]
        for iy in range(n):
            for iz in range(n):
                for j in range(rs, re):
                    for k in range(6):
                        c = fc[j][iy][iz][k]
                        c.Label = c.Name
                        fd[j][iy][iz][k] = c
                        fp[j][iy][iz][k] = c.Placement
        if steps > 0:
            fm = Base.Matrix()
            fm.rotateX(-1.570795 / steps)
            for i in range(steps):
                for iy in range(n):
                    for iz in range(n):
                        for j in range(rs, re):
                            for k in range(6):
                                c = fc[j][iy][iz][k]
                                p = c.Placement
                                c.Placement = Base.Placement(fm).multiply(p)
                Gui.updateGui()
        for iy in range(n):
            for iz in range(n):
                for j in range(rs, re):
                    for k in range(6):
                        c = fd[j][n-1-iz][iy][kk[k]]
                        p = fp[j][n-1-iz][iy][kk[k]]
                        c.Label = "fs"+str(j)+"q"+str(iy)+"q"+str(iz)+xyz[k]
                        c.Placement = Base.Placement(ff).multiply(p)
                        fc[j][iy][iz][k] = c
    elif dir.y > 0:
        ff = Base.Matrix(0,0,1,0,0,1,0,0,-1,0,0,0,0,0,0,1)
        kk =[4,5,2,3,1,0]
        for ix in range(n):
            for iz in range(n):
                for j in range(rs, re):
                    for k in range(6):
                        c = fc[ix][j][iz][k]
                        c.Label = c.Name
                        fd[ix][j][iz][k] = c
                        fp[ix][j][iz][k] = c.Placement
        if steps > 0:
            fm = Base.Matrix()
            fm.rotateY(1.570795 / steps)
            for i in range(steps):
                for ix in range(n):
                    for iz in range(n):
                        for j in range(rs, re):
                            for k in range(6):
                                c = fc[ix][j][iz][k]
                                p = c.Placement
                                c.Placement = Base.Placement(fm).multiply(p)
                Gui.updateGui()
        for ix in range(n):
            for iz in range(n):
                for j in range(rs, re):
                    for k in range(6):
                        c = fd[n-1-iz][j][ix][kk[k]]
                        p = fp[n-1-iz][j][ix][kk[k]]
                        c.Label = "fs"+str(ix)+"q"+str(j)+"q"+str(iz)+xyz[k]
                        c.Placement = Base.Placement(ff).multiply(p)
                        fc[ix][j][iz][k] = c
    elif dir.y < 0:
        ff = Base.Matrix(0,0,-1,0,0,1,0,0,1,0,0,0,0,0,0,1)
        kk =[5,4,2,3,0,1]
        for ix in range(n):
            for iz in range(n):
                for j in range(rs, re):
                    for k in range(6):
                        c = fc[ix][j][iz][k]
                        c.Label = c.Name
                        fd[ix][j][iz][k] = c
                        fp[ix][j][iz][k] = c.Placement
        if steps > 0:
            fm = Base.Matrix()
            fm.rotateY(-1.570795 / steps)
            for i in range(steps):
                for ix in range(n):
                    for iz in range(n):
                        for j in range(rs, re):
                            for k in range(6):
                                c = fc[ix][j][iz][k]
                                p = c.Placement
                                c.Placement = Base.Placement(fm).multiply(p)
                Gui.updateGui()
        for ix in range(n):
            for iz in range(n):
                for j in range(rs, re):
                    for k in range(6):
                        c = fd[iz][j][n-1-ix][kk[k]]
                        p = fp[iz][j][n-1-ix][kk[k]]
                        c.Label = "fs"+str(ix)+"q"+str(j)+"q"+str(iz)+xyz[k]
                        c.Placement = Base.Placement(ff).multiply(p)
                        fc[ix][j][iz][k] = c
    elif dir.z > 0:
        ff = Base.Matrix(0,-1,0,0,1,0,0,0,0,0,1,0,0,0,0,1)
        kk = [3,2,0,1,4,5]
        for ix in range(n):
            for iy in range(n):
                for j in range(rs, re):
                    for k in range(6):
                        c = fc[ix][iy][j][k]
                        c.Label = c.Name
                        fd[ix][iy][j][k] = c
                        fp[ix][iy][j][k] = c.Placement
        if steps > 0:
            fm = Base.Matrix()
            fm.rotateZ(1.570795 / steps)
            for i in range(steps):
                for ix in range(n):
                    for iy in range(n):
                        for j in range(rs, re):
                            for k in range(6):
                                c = fc[ix][iy][j][k]
                                p = c.Placement
                                c.Placement = Base.Placement(fm).multiply(p)
                Gui.updateGui()
        for ix in range(n):
            for iy in range(n):
                for j in range(rs, re):
                    for k in range(6):
                        c = fd[iy][n-1-ix][j][kk[k]]
                        p = fp[iy][n-1-ix][j][kk[k]]
                        c.Label = "fs"+str(ix)+"q"+str(iy)+"q"+str(j)+xyz[k]
                        c.Placement = Base.Placement(ff).multiply(p)
                        fc[ix][iy][j][k] = c
    elif dir.z < 0:
        ff = Base.Matrix(0,1,0,0,-1,0,0,0,0,0,1,0,0,0,0,1)
        kk = [2,3,1,0,5,4]
        for ix in range(n):
            for iy in range(n):
                for j in range(rs, re):
                    for k in range(6):
                        c = fc[ix][iy][j][k]
                        c.Label = c.Name
                        fd[ix][iy][j][k] = c
                        fp[ix][iy][j][k] = c.Placement
        if steps > 0:
            fm = Base.Matrix()
            fm.rotateZ(-1.570795 / steps)
            for i in range(steps):
                for ix in range(n):
                    for iy in range(n):
                        for j in range(rs, re):
                            for k in range(6):
                                c = fc[ix][iy][j][k]
                                p = c.Placement
                                c.Placement = Base.Placement(fm).multiply(p)
                Gui.updateGui()
        for ix in range(n):
            for iy in range(n):
                for j in range(rs, re):
                    for k in range(6):
                        c = fd[n-1-iy][ix][j][kk[k]]
                        p = fp[n-1-iy][ix][j][kk[k]]
                        c.Label = "fs"+str(ix)+"q"+str(iy)+"q"+str(j)+xyz[k]
                        c.Placement = Base.Placement(ff).multiply(p)
                        fc[ix][iy][j][k] = c

def slowrotate(dir, rs, re):
    n = Dictionary[str(App.ActiveDocument.Name)+"Size"]
    steps = slowness / (n * n)
    if re > rs:
        rotate(dir, rs, re, steps / (re - rs))
        Gui.updateGui()

# Quick rotation for use when randomising or modifying history
def fastrotate(dir, rs, re):
    rotate(dir, rs, re, 0)

# These functions manage the history
# Once you have created a cube, these functions will be defined and in scope
# and you can call them from another macro created by saving history
# or by hand from the python console window
history = []
Dictionary[str(App.ActiveDocument.Name)+"history"] = history
def mrotpX(i,j):
    slowrotate(Base.Vector(1,0,0),i,j)
    Dictionary[str(App.ActiveDocument.Name)+"history"].append("mrotpX("+str(i)+","+str(j)+")")
def mrotpY(i,j):
    slowrotate(Base.Vector(0,1,0),i,j)
    Dictionary[str(App.ActiveDocument.Name)+"history"].append("mrotpY("+str(i)+","+str(j)+")")
def mrotpZ(i,j):
    slowrotate(Base.Vector(0,0,1),i,j)
    Dictionary[str(App.ActiveDocument.Name)+"history"].append("mrotpZ("+str(i)+","+str(j)+")")
def mrotmX(i,j):
    slowrotate(Base.Vector(-1,0,0),i,j)
    Dictionary[str(App.ActiveDocument.Name)+"history"].append("mrotmX("+str(i)+","+str(j)+")")
def mrotmY(i,j):
    slowrotate(Base.Vector(0,-1,0),i,j)
    Dictionary[str(App.ActiveDocument.Name)+"history"].append("mrotmY("+str(i)+","+str(j)+")")
def mrotmZ(i,j):
    slowrotate(Base.Vector(0,0,-1),i,j)
    Dictionary[str(App.ActiveDocument.Name)+"history"].append("mrotmZ("+str(i)+","+str(j)+")")
def undo():
    history = Dictionary[str(App.ActiveDocument.Name)+"history"]
    if len(history) > 0:
        fs = history.pop()
        wh = fs[:6]
        p = fs.index(",")
        i = int(fs[7:p])
        j = int(fs[(p+1):(len(fs)-1)])
        if wh == "mrotmX":
            slowrotate(Base.Vector(1,0,0),i,j)
        elif wh == "mrotpX":
            slowrotate(Base.Vector(-1,0,0),i,j)
        elif wh == "mrotmY":
            slowrotate(Base.Vector(0,1,0),i,j)
        elif wh == "mrotpY":
            slowrotate(Base.Vector(0,-1,0),i,j)
        elif wh == "mrotmZ":
            slowrotate(Base.Vector(0,0,1),i,j)
        elif wh == "mrotpZ":
            slowrotate(Base.Vector(0,0,-1),i,j)
def itostring(i, n):
    if 2 * i == n - 1:
    	return "n/2"
    elif i <= n / 2:
        return str(i)
    elif i == n - 1:
        return "n-1"
    else:
        return "n-" + str(n-i) 
def jtostring(j, n):
    if j <= n / 2:
       return str(j)
    elif 2 * j == n + 1:
    	return "n/2+1"
    elif j == n:
        return "n"
    elif j == n - 1:
        return "n-1"
    else:
        return "n-" + str(n-j) 
def saveHistory():
    fcd = App.ActiveDocument
    history = Dictionary[str(fcd.Name)+"history"]
    n = Dictionary[str(fcd.Name)+"Size"]
    if len(history) > 0:
        # This statement will be needed at the start of any macro
        # to get the size of the cube in the currently active document
        # in case we have several documents open at once
        fs = "n = Dictionary[str(App.ActiveDocument.Name)+'Size']\n"
        for s in history:
            p = s.index(",")
            i = int(s[7:p])
            j = int(s[(p+1):(len(s)-1)])
            fs = fs + s[:7] + itostring(i, n) + ","
            fs = fs + jtostring(j, n) + ")\n"
        clip = QtCore.QCoreApplication.instance().clipboard()
        clip.setText(fs)
def reset():
    QtGui.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.WaitCursor))
    fcd = App.ActiveDocument
    Dictionary[str(fcd.Name)+"history"] = []
    n = Dictionary[str(fcd.Name)+"Size"]
    fc = Dictionary[str(fcd.Name)+"cubies"]
    for obj in fcd.Objects:
        fs = obj.Name
        if fs[0:2] == "ff":
            obj.Label = fs
    for obj in fcd.Objects:
        fs = obj.Name
        if fs[0:2] == "ff":
            obj.Label = "fs"+fs[2:]
            obj.Placement = Base.Placement()
            l = len(fs)
            q1 = fs.find("q")
            q2 = fs.find("q",q1+1)
            ix = int(fs[2:q1])
            iy = int(fs[q1+1:q2])
            iz = int(fs[q2+1:l-2])
            k = "x0x1y0y1z0z1".find(fs[l-2:])/2
            fc[ix][iy][iz][k] = obj
    QtGui.QApplication.restoreOverrideCursor()

# Randomise the cube
def randomise():
    QtGui.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.WaitCursor))
    fcd = App.ActiveDocument
    history = Dictionary[str(fcd.Name)+"history"]
    n = Dictionary[str(fcd.Name)+"Size"]
    random.seed()
    i = random.randrange(0,6)
    for x in range(n*24):
        i = (i + 2 - i % 2 +random.randrange(0,4)) % 6
        j = random.randrange(0,n)
        if i == 0:
            fastrotate(Base.Vector(1,0,0),j,j+1)
            history.append("mrotpX("+str(j)+","+str(j+1)+")")
        elif i == 1:
            fastrotate(Base.Vector(-1,0,0),j,j+1)
            history.append("mrotmX("+str(j)+","+str(j+1)+")")
        elif i == 2:
            fastrotate(Base.Vector(0,1,0),j,j+1)
            history.append("mrotpY("+str(j)+","+str(j+1)+")")
        elif i == 3:
            fastrotate(Base.Vector(0,-1,0),j,j+1)
            history.append("mrotmY("+str(j)+","+str(j+1)+")")
        elif i == 4:
            fastrotate(Base.Vector(0,0,1),j,j+1)
            history.append("mrotpZ("+str(j)+","+str(j+1)+")")
        elif i == 5:
            fastrotate(Base.Vector(0,0,-1),j,j+1)
            history.append("mrotmZ("+str(j)+","+str(j+1)+")")
    QtGui.QApplication.restoreOverrideCursor()

# Various macros to modify the history
# They all read the history and undo it, and then perform some modified version
# of the history. The modifications can be time-reversal, reflection along an
# axis (as if the cube were reflected, the history replayed,
# and the cube reflected back again), or rotation (as if the cube were rotated,
# the history replayed, and the cube rotated back again).
def slow_history():
    QtGui.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.WaitCursor))
    fcd = App.ActiveDocument
    history = Dictionary[str(fcd.Name)+"history"]
    n = Dictionary[str(fcd.Name)+"Size"]
    nh = []
    while len(history) > 0:
        nh.append(history.pop())
    reset()
    Gui.updateGui()
    while len(nh) > 0:
        fs = nh.pop()
        wh = fs[:6]
        p = fs.index(",")
        i = int(fs[7:p])
        j = int(fs[p+1:len(fs)-1])
        if wh == "mrotpX":
            mrotpX(i,j)
        elif wh == "mrotmX":
            mrotmX(i,j)
        elif wh == "mrotpY":
            mrotpY(i,j)
        elif wh == "mrotmY":
            mrotmY(i,j)
        elif wh == "mrotpZ":
            mrotpZ(i,j)
        elif wh == "mrotmZ":
            mrotmZ(i,j)
    QtGui.QApplication.restoreOverrideCursor()
def reverse_history():
    QtGui.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.WaitCursor))
    fcd = App.ActiveDocument
    history = Dictionary[str(fcd.Name)+"history"]
    n = Dictionary[str(fcd.Name)+"Size"]
    nh = []
    while len(history) > 0:
        nh.append(history.pop())
    reset()
    history = Dictionary[str(fcd.Name)+"history"]
    for fs in nh:
        wh = fs[:6]
        p = fs.index(",")
        i = int(fs[7:p])
        j = int(fs[p+1:len(fs)-1])
        if wh == "mrotpX":
            fastrotate(Base.Vector(1,0,0),i,j)
            history.append("mrotpX("+str(i)+","+str(j)+")")
        elif wh == "mrotmX":
            fastrotate(Base.Vector(-1,0,0),i,j)
            history.append("mrotmX("+str(i)+","+str(j)+")")
        elif wh == "mrotpY":
            fastrotate(Base.Vector(0,1,0),i,j)
            history.append("mrotpY("+str(i)+","+str(j)+")")
        elif wh == "mrotmY":
            fastrotate(Base.Vector(0,-1,0),i,j)
            history.append("mrotmY("+str(i)+","+str(j)+")")
        elif wh == "mrotpZ":
            fastrotate(Base.Vector(0,0,1),i,j)
            history.append("mrotpZ("+str(i)+","+str(j)+")")
        elif wh == "mrotmZ":
            fastrotate(Base.Vector(0,0,-1),i,j)
            history.append("mrotmZ("+str(i)+","+str(j)+")")
    QtGui.QApplication.restoreOverrideCursor()
def undo_history():
    QtGui.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.WaitCursor))
    fcd = App.ActiveDocument
    history = Dictionary[str(fcd.Name)+"history"]
    n = Dictionary[str(fcd.Name)+"Size"]
    nh = []
    while len(history) > 0:
        nh.append(history.pop())
    reset()
    history = Dictionary[str(fcd.Name)+"history"]
    for fs in nh:
        wh = fs[:6]
        p = fs.index(",")
        i = int(fs[7:p])
        j = int(fs[p+1:len(fs)-1])
        if wh == "mrotpX":
            fastrotate(Base.Vector(-1,0,0),i,j)
            history.append("mrotmX("+str(i)+","+str(j)+")")
        elif wh == "mrotmX":
            fastrotate(Base.Vector(1,0,0),i,j)
            history.append("mrotpX("+str(i)+","+str(j)+")")
        elif wh == "mrotpY":
            fastrotate(Base.Vector(0,-1,0),i,j)
            history.append("mrotmY("+str(i)+","+str(j)+")")
        elif wh == "mrotmY":
            fastrotate(Base.Vector(0,1,0),i,j)
            history.append("mrotpY("+str(i)+","+str(j)+")")
        elif wh == "mrotpZ":
            fastrotate(Base.Vector(0,0,-1),i,j)
            history.append("mrotmZ("+str(i)+","+str(j)+")")
        elif wh == "mrotmZ":
            fastrotate(Base.Vector(0,0,1),i,j)
            history.append("mrotpZ("+str(i)+","+str(j)+")")
    QtGui.QApplication.restoreOverrideCursor()
def reflectX_history():
    QtGui.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.WaitCursor))
    fcd = App.ActiveDocument
    history = Dictionary[str(fcd.Name)+"history"]
    n = Dictionary[str(fcd.Name)+"Size"]
    nh = []
    while len(history) > 0:
        nh.append(history.pop())
    reset()
    history = Dictionary[str(fcd.Name)+"history"]
    while len(nh) > 0:
        fs = nh.pop()
        wh = fs[:6]
        p = fs.index(",")
        i = int(fs[7:p])
        j = int(fs[p+1:len(fs)-1])
        if wh == "mrotpX":
            fastrotate(Base.Vector(1,0,0),n-j,n-i)
            history.append("mrotpX("+str(n-j)+","+str(n-i)+")")
        elif wh == "mrotmX":
            fastrotate(Base.Vector(-1,0,0),n-j,n-i)
            history.append("mrotmX("+str(n-j)+","+str(n-i)+")")
        elif wh == "mrotpY":
            fastrotate(Base.Vector(0,-1,0),i,j)
            history.append("mrotmY("+str(i)+","+str(j)+")")
        elif wh == "mrotmY":
            fastrotate(Base.Vector(0,1,0),i,j)
            history.append("mrotpY("+str(i)+","+str(j)+")")
        elif wh == "mrotpZ":
            fastrotate(Base.Vector(0,0,-1),i,j)
            history.append("mrotmZ("+str(i)+","+str(j)+")")
        elif wh == "mrotmZ":
            fastrotate(Base.Vector(0,0,1),i,j)
            history.append("mrotpZ("+str(i)+","+str(j)+")")
    QtGui.QApplication.restoreOverrideCursor()
def reflectY_history():
    QtGui.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.WaitCursor))
    fcd = App.ActiveDocument
    history = Dictionary[str(fcd.Name)+"history"]
    n = Dictionary[str(fcd.Name)+"Size"]
    nh = []
    while len(history) > 0:
        nh.append(history.pop())
    reset()
    history = Dictionary[str(fcd.Name)+"history"]
    while len(nh) > 0:
        fs = nh.pop()
        wh = fs[:6]
        p = fs.index(",")
        i = int(fs[7:p])
        j = int(fs[p+1:len(fs)-1])
        if wh == "mrotpX":
            fastrotate(Base.Vector(-1,0,0),i,j)
            history.append("mrotmX("+str(i)+","+str(j)+")")
        elif wh == "mrotmX":
            fastrotate(Base.Vector(1,0,0),i,j)
            history.append("mrotpX("+str(i)+","+str(j)+")")
        elif wh == "mrotpY":
            fastrotate(Base.Vector(0,1,0),n-j,n-i)
            history.append("mrotpY("+str(n-j)+","+str(n-i)+")")
        elif wh == "mrotmY":
            fastrotate(Base.Vector(0,-1,0),n-j,n-i)
            history.append("mrotmY("+str(n-j)+","+str(n-i)+")")
        elif wh == "mrotpZ":
            fastrotate(Base.Vector(0,0,-1),i,j)
            history.append("mrotmZ("+str(i)+","+str(j)+")")
        elif wh == "mrotmZ":
            fastrotate(Base.Vector(0,0,1),i,j)
            history.append("mrotpZ("+str(i)+","+str(j)+")")
    QtGui.QApplication.restoreOverrideCursor()
def reflectZ_history():
    QtGui.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.WaitCursor))
    fcd = App.ActiveDocument
    history = Dictionary[str(fcd.Name)+"history"]
    n = Dictionary[str(fcd.Name)+"Size"]
    nh = []
    while len(history) > 0:
        nh.append(history.pop())
    reset()
    history = Dictionary[str(fcd.Name)+"history"]
    while len(nh) > 0:
        fs = nh.pop()
        wh = fs[:6]
        p = fs.index(",")
        i = int(fs[7:p])
        j = int(fs[p+1:len(fs)-1])
        if wh == "mrotpX":
            fastrotate(Base.Vector(-1,0,0),i,j)
            history.append("mrotmX("+str(i)+","+str(j)+")")
        elif wh == "mrotmX":
            fastrotate(Base.Vector(1,0,0),i,j)
            history.append("mrotpX("+str(i)+","+str(j)+")")
        elif wh == "mrotpY":
            fastrotate(Base.Vector(0,-1,0),i,j)
            history.append("mrotmY("+str(i)+","+str(j)+")")
        elif wh == "mrotmY":
            fastrotate(Base.Vector(0,1,0),i,j)
            history.append("mrotpY("+str(i)+","+str(j)+")")
        elif wh == "mrotpZ":
            fastrotate(Base.Vector(0,0,1),n-j,n-i)
            history.append("mrotpZ("+str(n-j)+","+str(n-i)+")")
        elif wh == "mrotmZ":
            fastrotate(Base.Vector(0,0,-1),n-j,n-i)
            history.append("mrotmZ("+str(n-j)+","+str(n-i)+")")
    QtGui.QApplication.restoreOverrideCursor()
def rotpX_history():
    QtGui.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.WaitCursor))
    fcd = App.ActiveDocument
    history = Dictionary[str(fcd.Name)+"history"]
    n = Dictionary[str(fcd.Name)+"Size"]
    nh = []
    while len(history) > 0:
        nh.append(history.pop())
    reset()
    history = Dictionary[str(fcd.Name)+"history"]
    while len(nh) > 0:
        fs = nh.pop()
        wh = fs[:6]
        p = fs.index(",")
        i = int(fs[7:p])
        j = int(fs[p+1:len(fs)-1])
        if wh == "mrotpX":
            fastrotate(Base.Vector(1,0,0),i,j)
            history.append("mrotpX("+str(i)+","+str(j)+")")
        elif wh == "mrotmX":
            fastrotate(Base.Vector(-1,0,0),i,j)
            history.append("mrotmX("+str(i)+","+str(j)+")")
        elif wh == "mrotpY":
            fastrotate(Base.Vector(0,0,1),i,j)
            history.append("mrotpZ("+str(i)+","+str(j)+")")
        elif wh == "mrotmY":
            fastrotate(Base.Vector(0,0,-1),i,j)
            history.append("mrotmZ("+str(i)+","+str(j)+")")
        elif wh == "mrotpZ":
            fastrotate(Base.Vector(0,-1,0),n-j,n-i)
            history.append("mrotmY("+str(n-j)+","+str(n-i)+")")
        elif wh == "mrotmZ":
            fastrotate(Base.Vector(0,1,0),n-j,n-i)
            history.append("mrotpY("+str(n-j)+","+str(n-i)+")")
    QtGui.QApplication.restoreOverrideCursor()
def rotmX_history():
    QtGui.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.WaitCursor))
    fcd = App.ActiveDocument
    history = Dictionary[str(fcd.Name)+"history"]
    n = Dictionary[str(fcd.Name)+"Size"]
    nh = []
    while len(history) > 0:
        nh.append(history.pop())
    reset()
    history = Dictionary[str(fcd.Name)+"history"]
    while len(nh) > 0:
        fs = nh.pop()
        wh = fs[:6]
        p = fs.index(",")
        i = int(fs[7:p])
        j = int(fs[p+1:len(fs)-1])
        if wh == "mrotpX":
            fastrotate(Base.Vector(1,0,0),i,j)
            history.append("mrotpX("+str(i)+","+str(j)+")")
        elif wh == "mrotmX":
            fastrotate(Base.Vector(-1,0,0),i,j)
            history.append("mrotmX("+str(i)+","+str(j)+")")
        elif wh == "mrotpY":
            fastrotate(Base.Vector(0,0,-1),n-j,n-i)
            history.append("mrotmZ("+str(n-j)+","+str(n-i)+")")
        elif wh == "mrotmY":
            fastrotate(Base.Vector(0,0,1),n-j,n-i)
            history.append("mrotpZ("+str(n-j)+","+str(n-i)+")")
        elif wh == "mrotpZ":
            fastrotate(Base.Vector(0,1,0),i,j)
            history.append("mrotpY("+str(i)+","+str(j)+")")
        elif wh == "mrotmZ":
            fastrotate(Base.Vector(0,-1,0),i,j)
            history.append("mrotmY("+str(i)+","+str(j)+")")
    QtGui.QApplication.restoreOverrideCursor()
def rotpY_history():
    QtGui.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.WaitCursor))
    fcd = App.ActiveDocument
    history = Dictionary[str(fcd.Name)+"history"]
    n = Dictionary[str(fcd.Name)+"Size"]
    nh = []
    while len(history) > 0:
        nh.append(history.pop())
    reset()
    history = Dictionary[str(fcd.Name)+"history"]
    while len(nh) > 0:
        fs = nh.pop()
        wh = fs[:6]
        p = fs.index(",")
        i = int(fs[7:p])
        j = int(fs[p+1:len(fs)-1])
        if wh == "mrotpX":
            fastrotate(Base.Vector(0,0,-1),n-j,n-i)
            history.append("mrotmZ("+str(n-j)+","+str(n-i)+")")
        elif wh == "mrotmX":
            fastrotate(Base.Vector(0,0,1),n-j,n-i)
            history.append("mrotpZ("+str(n-j)+","+str(n-i)+")")
        elif wh == "mrotpY":
            fastrotate(Base.Vector(0,1,0),i,j)
            history.append("mrotpY("+str(i)+","+str(j)+")")
        elif wh == "mrotmY":
            fastrotate(Base.Vector(0,-1,0),i,j)
            history.append("mrotmY("+str(i)+","+str(j)+")")
        elif wh == "mrotpZ":
            fastrotate(Base.Vector(1,0,0),i,j)
            history.append("mrotpX("+str(i)+","+str(j)+")")
        elif wh == "mrotmZ":
            fastrotate(Base.Vector(-1,0,0),i,j)
            history.append("mrotmX("+str(i)+","+str(j)+")")
    QtGui.QApplication.restoreOverrideCursor()
def rotmY_history():
    QtGui.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.WaitCursor))
    fcd = App.ActiveDocument
    history = Dictionary[str(fcd.Name)+"history"]
    n = Dictionary[str(fcd.Name)+"Size"]
    nh = []
    while len(history) > 0:
        nh.append(history.pop())
    reset()
    history = Dictionary[str(fcd.Name)+"history"]
    while len(nh) > 0:
        fs = nh.pop()
        wh = fs[:6]
        p = fs.index(",")
        i = int(fs[7:p])
        j = int(fs[p+1:len(fs)-1])
        if wh == "mrotpX":
            fastrotate(Base.Vector(0,0,1),i,j)
            history.append("mrotpZ("+str(i)+","+str(j)+")")
        elif wh == "mrotmX":
            fastrotate(Base.Vector(0,0,-1),i,j)
            history.append("mrotmZ("+str(i)+","+str(j)+")")
        elif wh == "mrotpY":
            fastrotate(Base.Vector(0,1,0),i,j)
            history.append("mrotpY("+str(i)+","+str(j)+")")
        elif wh == "mrotmY":
            fastrotate(Base.Vector(0,-1,0),i,j)
            history.append("mrotmY("+str(i)+","+str(j)+")")
        elif wh == "mrotpZ":
            fastrotate(Base.Vector(-1,0,0),n-j,n-i)
            history.append("mrotmX("+str(n-j)+","+str(n-i)+")")
        elif wh == "mrotmZ":
            fastrotate(Base.Vector(1,0,0),n-j,n-i)
            history.append("mrotpX("+str(n-j)+","+str(n-i)+")")
    QtGui.QApplication.restoreOverrideCursor()
def rotpZ_history():
    QtGui.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.WaitCursor))
    fcd = App.ActiveDocument
    history = Dictionary[str(fcd.Name)+"history"]
    n = Dictionary[str(fcd.Name)+"Size"]
    nh = []
    while len(history) > 0:
        nh.append(history.pop())
    reset()
    history = Dictionary[str(fcd.Name)+"history"]
    while len(nh) > 0:
        fs = nh.pop()
        wh = fs[:6]
        p = fs.index(",")
        i = int(fs[7:p])
        j = int(fs[p+1:len(fs)-1])
        if wh == "mrotpX":
            fastrotate(Base.Vector(0,1,0),i,j)
            history.append("mrotpY("+str(i)+","+str(j)+")")
        elif wh == "mrotmX":
            fastrotate(Base.Vector(0,-1,0),i,j)
            history.append("mrotmY("+str(i)+","+str(j)+")")
        elif wh == "mrotpY":
            fastrotate(Base.Vector(-1,0,0),n-j,n-i)
            history.append("mrotmX("+str(n-j)+","+str(n-i)+")")
        elif wh == "mrotmY":
            fastrotate(Base.Vector(1,0,0),n-j,n-i)
            history.append("mrotpX("+str(n-j)+","+str(n-i)+")")
        elif wh == "mrotpZ":
            fastrotate(Base.Vector(0,0,1),i,j)
            history.append("mrotpZ("+str(i)+","+str(j)+")")
        elif wh == "mrotmZ":
            fastrotate(Base.Vector(0,0,-1),i,j)
            history.append("mrotmZ("+str(i)+","+str(j)+")")
    QtGui.QApplication.restoreOverrideCursor()
def rotmZ_history():
    QtGui.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.WaitCursor))
    fcd = App.ActiveDocument
    history = Dictionary[str(fcd.Name)+"history"]
    n = Dictionary[str(fcd.Name)+"Size"]
    nh = []
    while len(history) > 0:
        nh.append(history.pop())
    reset()
    history = Dictionary[str(fcd.Name)+"history"]
    while len(nh) > 0:
        fs = nh.pop()
        wh = fs[:6]
        p = fs.index(",")
        i = int(fs[7:p])
        j = int(fs[p+1:len(fs)-1])
        if wh == "mrotpX":
            fastrotate(Base.Vector(0,-1,0),n-j,n-i)
            history.append("mrotmY("+str(n-j)+","+str(n-i)+")")
        elif wh == "mrotmX":
            fastrotate(Base.Vector(0,1,0),n-j,n-i)
            history.append("mrotpY("+str(n-j)+","+str(n-i)+")")
        elif wh == "mrotpY":
            fastrotate(Base.Vector(1,0,0),i,j)
            history.append("mrotpX("+str(i)+","+str(j)+")")
        elif wh == "mrotmY":
            fastrotate(Base.Vector(-1,0,0),i,j)
            history.append("mrotmX("+str(i)+","+str(j)+")")
        elif wh == "mrotpZ":
            fastrotate(Base.Vector(0,0,1),i,j)
            history.append("mrotpZ("+str(i)+","+str(j)+")")
        elif wh == "mrotmZ":
            fastrotate(Base.Vector(0,0,-1),i,j)
            history.append("mrotmZ("+str(i)+","+str(j)+")")
    QtGui.QApplication.restoreOverrideCursor()

# FreeCAD's file save only saves the state of the camera
# and the shapes, positions, orientations, names, and labels
# of the objects in the scene 
# This function recreates all the other stuff after reloading a saved file
# I haven't found a way to make FreeCAD save the history, so it gets reset
def fix_reload():
    QtGui.QApplication.setOverrideCursor(QtGui.QCursor(QtCore.Qt.WaitCursor))
    fcd = App.ActiveDocument
    # first we find how big a cube we have
    n = 0
    for obj in fcd.Objects:
        fs = obj.Name
        if fs[0:2] == "ff":
            i = int(fs[2:fs.index("q")])
            if i > n:
                n = i
        elif fs[0:4] == "Text":
            # get rid of this because it's incorrectly positioned
            # we'll create a new one later
            fcd.removeObject(fs)
        elif fs[0:5] == "arrow":
            obj.ViewObject.RootNode.setName(coin.SbName(fs))
    n = n + 1
    fc = [[[[None for j in range(6)] for ix in range(n)] for iy in range(n)] for ix in range(n)]
    Dictionary[str(fcd.Name)+"Size"] = n
    for obj in fcd.Objects:
        fs = obj.Label
        if fs[0:2] == "fs":
            l = len(fs)
            q1 = fs.find("q")
            q2 = fs.find("q",q1+1)
            ix = int(fs[2:q1])
            iy = int(fs[q1+1:q2])
            iz = int(fs[q2+1:l-2])
            k = "x0x1y0y1z0z1".find(fs[l-2:])/2
            fc[ix][iy][iz][k] = obj
    Dictionary[str(fcd.Name)+"cubies"] = fc
    Dictionary[str(fcd.Name)+"ViewObserver"] = ViewObserver()
    doLabels() # create new labels
    rotation = fixCamera(False)
    Dictionary[str(fcd.Name)+"buttons"] = ButtonRow()
    fixLightModel()
    createMultiViews(rotation)
    history = [] # according to Henry Ford
    Dictionary[str(fcd.Name)+"history"] = history
    QtGui.QApplication.restoreOverrideCursor()
    FreeCAD.Console.PrintLog("fix_reload() done\n")