Macro Rubik Cube

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



Script
Macro_Rubik_Cube.FCMacro """ """ __title__  = "Rubik_cube" __author__ = "Aleph0" __version__ = "00.05" __date__   = "15/12/2018" __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.   *
 * 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. *
 * step_history replays the history, popping a dialog box before each *
 * rotation - this is useful if you have developed an operator using the *
 * model and you want to do the same rotations on a real cube.          *
 * Copyright © 2018 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                                                                  *
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  *
 * 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 Draft import time import random from FreeCAD import Base from FreeCAD import Console from pivy import coin from pivy.coin import * from PySide import *

try: import PyQt4.QtGui def busyCursor: PyQt4.QtGui.QApplication.setOverrideCursor(           PyQt4.QtGui.QCursor(PyQt4.QtCore.Qt.WaitCursor)) def restoreCursor: PyQt4.QtGui.QApplication.restoreOverrideCursor except Exception: def busyCursor: QtGui.QApplication.setOverrideCursor(           QtGui.QCursor(QtCore.Qt.WaitCursor)) def restoreCursor: QtGui.QApplication.restoreOverrideCursor
 * 1) This horrible hack is needed because some versions of FreeCAD seem to have
 * 2) been built with a mixture of Qt4 and Qt5. In those versions, the Qt5 version
 * 3) of setOverrideCursor doesn't work, but the Qt4 version of MainWindow
 * 4) doesn't seem to have a centralWidget, which we need.

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
 * 2) FreeCAD, 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

def showMessage(s): diag = QtGui.QMessageBox(QtGui.QMessageBox.NoIcon, '', s)   diag.setWindowModality(QtCore.Qt.NonModal) diag.exec_
 * 1) for debugging

defaultSize = 3 class AskSizeWindow(QtGui.QDialog): # automagically called when a class instance 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 width = 300 height = 200 margin = 10 xpos = geom.center.x - width / 2 ypos = geom.center.y - height / 2 self.setGeometry(xpos, ypos, width, height) 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.checkBox = QtGui.QCheckBox("Oriented inner faces", self) self.checkBox.setGeometry(QtCore.QRect(width/2 - 100, ypos, 200, 30)) Dictionary[str(App.ActiveDocument.Name)+"Oriented"] = QtCore.Qt.Unchecked self.checkBox.setCheckState(QtCore.Qt.Unchecked) self.checkBox.setObjectName("checkBox") self.checkBox.stateChanged.connect(self.on_checkBox_stateChanged) 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 on_checkBox_stateChanged(self, val): Dictionary[str(App.ActiveDocument.Name)+"Oriented"] = val def onOK(self): self.close self.destroy AskSizeWindow.exec_
 * 1) This bit of code pops up the dialog to ask for the size and whether to orient
 * 2) inner faces (useful if you are simulating a cube which has non-symmetrical
 * 3) patterns instead of plain colours).
 * 1) now create the class instance which shows the dialog

busyCursor
 * 1) Display a wait cursor while we are making the cube

class StepWindow(QtGui.QDialog): # automagically called when a class instance is created def __init__(self): super(StepWindow, self).__init__ # Lay out the interactive elements mw = Gui.getMainWindow cw = mw.centralWidget ww = 220 wh = 100 margin = 10 self.setWindowTitle("Next Step") geom1 = mw.geometry geom2 = cw.geometry xpos = geom1.left + geom2.center.x - ww / 2 buttons = Dictionary[str(App.ActiveDocument.Name)+"buttons"] buttonsheight = buttons.geometry.height # The 25 is a bit magic to get the Dialog box just below the buttons ypos = geom1.top + geom2.top + buttonsheight + 25 self.setGeometry(xpos, ypos, ww, wh) ypos = margin self.label_1 = QtGui.QLabel(self) self.label_1.setGeometry(QtCore.QRect(ww/2 - 100, ypos, 200, 25)) self.label_1.setObjectName("label_1") ypos = ypos + 40 self.OKbutton = QtGui.QPushButton(self) self.OKbutton.setGeometry(QtCore.QRect(margin, ypos, 80, 40)) self.OKbutton.setText("OK") self.OKbutton.clicked.connect(self.onOK) self.Quitbutton = QtGui.QPushButton(self) xpos = ww - 80 - margin self.Quitbutton.setGeometry(QtCore.QRect(xpos, ypos, 80, 40)) self.Quitbutton.setText("Stop") self.Quitbutton.clicked.connect(self.onQuit) def onOK(self): self.close self.destroy def onQuit(self): Dictionary[str(App.ActiveDocument.Name)+"Stepping"] = 0 self.close self.destroy
 * 1) Dialog box shown on each step by step_history

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
 * 1) This bit of code catches clicks on the rotation arrows

Dictionary[str(App.ActiveDocument.Name)+"ViewObserver"] = ViewObserver
 * 1) Create a ViewObserver and save it in the dictionary so that it does not get
 * 2) garbage-collected

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 oriented = Dictionary[str(App.ActiveDocument.Name)+"Oriented"] 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):            if ix != 0 and ix != n-1 and (              iy != 0 and iy != n-1 and              iz != 0 and iz != n-1):                continue            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)            if oriented != QtCore.Qt.Unchecked and (              ix == 0 and iy != 0 and iy != n-1 and iz != 0 and iz != n-1):                x0y2z1 = Base.Vector(fx-0.5,fy,fz)                face = Part.Face(Part.makePolygon(                    [x0y0z0,x0y0z1,x0y2z1,x0y1z1,x0y1z0,x0y0z0]))            else:                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"))            if oriented != QtCore.Qt.Unchecked and (              ix == n - 1 and iy != 0 and iy != n-1 and iz != 0 and iz != n-1):                x1y2z1 = Base.Vector(fx+0.5,fy,fz)                face = Part.Face(Part.makePolygon(                    [x1y0z0,x1y0z1,x1y2z1,x1y1z1,x1y1z0,x1y0z0]))            else:                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"))            if oriented != QtCore.Qt.Unchecked and (              ix != 0 and ix != n-1 and iy == 0 and iz != 0 and iz != n-1):                x1y0z2 = Base.Vector(fx,fy-0.5,fz)                face = Part.Face(Part.makePolygon(                    [x0y0z0,x0y0z1,x1y0z1,x1y0z2,x1y0z0,x0y0z0]))            else:                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"))            if oriented != QtCore.Qt.Unchecked and (              ix != 0 and ix != n-1 and iy == n - 1 and iz != 0 and iz != n-1):                x1y1z2 = Base.Vector(fx,fy+0.5,fz)                face = Part.Face(Part.makePolygon(                    [x0y1z0,x0y1z1,x1y1z1,x1y1z2,x1y1z0,x0y1z0]))            else:                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"))            if oriented != QtCore.Qt.Unchecked and (              ix != 0 and ix != n-1 and iy != 0 and iy != n-1 and iz == 0):                x2y0z0 = Base.Vector(fx,fy,fz-0.5)                face = Part.Face(Part.makePolygon(                    [x0y0z0,x0y1z0,x1y1z0,x1y0z0,x2y0z0,x0y0z0]))            else:                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"))            if oriented != QtCore.Qt.Unchecked and (              ix != 0 and ix != n-1 and iy != 0 and iy != n-1 and iz == n - 1):                x2y0z1 = Base.Vector(fx,fy,fz+0.5)                face = Part.Face(Part.makePolygon(                    [x0y0z1,x0y1z1,x1y1z1,x1y0z1,x2y0z1,x0y0z1]))            else:                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]
 * 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
 * 3) making a cube with different coloured faces
 * 4) Each face has a name and a label
 * 5) The name is persistent regardless of how the face moves about the cube
 * 6) The label changes and tells where the face is now: this is needed for
 * 7) fix_reload
 * 8) I don't make the interior cubies because you can never see them
 * 1) Make array of 6 faces for each of n x n x n cubies

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.

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)
 * 1) Utility routine for doLabels to make a single label

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)
 * 1) This bit of code creates some labels for the arrows
 * 2) Clicking on one rotates the whole cube

doLabels
 * 1) now call it to make them

Gui.ActiveDocument.ActiveView.viewAxonometric Gui.SendMsgToActiveView("ViewFit")

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

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
 * 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, 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)
 * 1) This bit of code persuades FreeCAD'a renderer to put
 * 2) several views of the cube into the same window

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:                                    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):                    if fc[j][iy][iz][0] <> None:                        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):                            if fc[j][iy][iz][0] <> None:                                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):                    if fc[j][iy][iz][0] <> None:                        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): if fc[ix][j][iz][0] <> None: 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): if fc[ix][j][iz][0] <> None: 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): if fc[ix][j][iz][0] <> None: 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): if fc[ix][j][iz][0] <> None: 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): if fc[ix][j][iz][0] <> None: 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): if fc[ix][j][iz][0] <> None: 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): if fc[ix][iy][j][0] <> None: 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): if fc[ix][iy][j][0] <> None: 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): if fc[ix][iy][j][0] <> None: 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): if fc[ix][iy][j][0] <> None: 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): if fc[ix][iy][j][0] <> None: 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): if fc[ix][iy][j][0] <> None: 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
 * 1) This bit of code animates a slice rotation

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

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

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:    busyCursor    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    restoreCursor
 * 1) These functions manage 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 created by saving history
 * 4) or by hand from the python console window

def randomise: busyCursor 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)+")") restoreCursor
 * 1) Randomise the cube

def slow_history: busyCursor 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) restoreCursor def step_history: 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 Dictionary[str(fcd.Name)+"Stepping"] = 1 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]) w = StepWindow w.label_1.setText("Doing "+wh+"("+str(i)+","+str(j)+")") w.label_1.setAlignment(QtCore.Qt.AlignCenter) w.exec_ if Dictionary[str(fcd.Name)+"Stepping"] == 0: break 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) def reverse_history: busyCursor 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)+")") restoreCursor def undo_history: busyCursor 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)+")") restoreCursor def reflectX_history: busyCursor 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)+")") restoreCursor def reflectY_history: busyCursor 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)+")") restoreCursor def reflectZ_history: busyCursor 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)+")") restoreCursor def rotpX_history: busyCursor 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)+")") restoreCursor def rotmX_history: busyCursor 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)+")") restoreCursor def rotpY_history: busyCursor 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)+")") restoreCursor def rotmY_history: busyCursor 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)+")") restoreCursor def rotpZ_history: busyCursor 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)+")") restoreCursor def rotmZ_history: busyCursor 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)+")") restoreCursor
 * 1) Various macros to modify the history
 * 2) They all read the history and undo it, and then perform some modified version
 * 3) of the history. The modifications can be time-reversal, reflection along an
 * 4) axis (as if the cube were reflected, the history replayed,
 * 5) and the cube reflected back again), or rotation (as if the cube were rotated,
 * 6) the history replayed, and the cube rotated back again).

class ButtonRow(QtGui.QWidget): def __init__(self): super(ButtonRow, self).__init__ mw = Gui.getMainWindow view3DWidget = findView(mw.centralWidget) if view3DWidget != None: self.setParent(view3DWidget) self.setAutoFillBackground(True) self.undoButton = QtGui.QPushButton(self) self.undoButton.setText("Undo") self.undoButton.clicked.connect(self.onUndo) self.saveButton = QtGui.QPushButton(self) self.saveButton.setText("Copy history to clipboard") self.saveButton.clicked.connect(self.onSave) self.resetButton = QtGui.QPushButton(self) self.resetButton.setText("Reset") self.resetButton.clicked.connect(self.onReset) self.show def onUndo(self): undo def onReset(self): reset def onSave(self): saveHistory def paintEvent(self, event): # we position the buttons every paint # in case the window size has changed bh = 30 bw = 80 mw = Gui.getMainWindow view3DWidget = findView(mw.centralWidget) if view3DWidget != None: geom = view3DWidget.geometry xpos = 0 self.setGeometry(xpos, 0, geom.width, bh + 10) gap = (geom.width - 5 * bw) / 4 if gap < 0: gap = 0 xpos = gap self.undoButton.setGeometry(xpos, 0, bw, bh) xpos = xpos + gap + bw           self.saveButton.setGeometry(xpos, 0, 3 * bw, bh) xpos = xpos + 3 * bw + gap self.resetButton.setGeometry(xpos, 0, bw, bh) # now do the paint, which will paint the buttons as well since they # are this widget's children super(ButtonRow, self).paintEvent(event) 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

def fix_reload: busyCursor 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)    # create the buttons at the top of the window    Dictionary[str(App.ActiveDocument.Name)+"buttons"] = ButtonRow    fixLightModel    createMultiViews(rotation)    history = [] # according to Henry Ford    Dictionary[str(fcd.Name)+"history"] = history    restoreCursor    FreeCAD.Console.PrintLog("fix_reload done\n")
 * 1) FreeCAD's file save only saves the state of the camera
 * 2) and the shapes, positions, orientations, names, and labels
 * 3) of the objects in the scene
 * 4) This function recreates all the other stuff after reloading a saved file
 * 5) I haven't found a way to make FreeCAD save the history, so it gets reset

restoreCursor