Instant Insanity - An exercise in learning FreeCAD scripting.
Forum rules
Be nice to others! Respect the FreeCAD code of conduct!
Be nice to others! Respect the FreeCAD code of conduct!
-
- Posts: 53
- Joined: Sun Feb 02, 2020 4:02 am
- Location: Santa Cruz, California
- Contact:
Instant Insanity - An exercise in learning FreeCAD scripting.
This FreeCAD Macro is offered to the FC Forum for review and comment.
Being a user of FC for some time now and having no experience with Python and object oriented languages, just C and LiveCode, I decided to challenge myself to learn Python and some of the innards of FC by coding an interesting problem.
‘Instant Insanity’, a popular puzzle, consists of four cubes with faces colored either blue, red, green or white. The object of the puzzle is to find the alignment of the four cubes such that each of the four rows of exposed sides contain the four colors, ie. no duplicate colors in a row.
When executing the Macro ‘Instant Insanity’ a new document named “Instant Insanity’ is created and four cubes are generated. The colors of the faces of the cubes are initially white. The Macro also generates a control window with buttons as follows:
‘Run Loop’ will initiate a loop to find the correct orientation of the four cubes that solves the problem. The solution, with the given initial conditions, is found in about 34 seconds.
‘Random Orientations’ orients the four cubes in a random alignment. The solution time varies with the initial arrangement of the cubes. The four cubes can take on 331,776 possible alignments.
‘Results’ will print the alignment that solves the puzzle. All output is sent to the Report view.
‘Set Colors’ will assign the puzzle’s colors to the four cubes. The particular ordering and alignment of the cubes used as initial conditions was chosen arbitrarily.
The four buttons ‘Red’, ‘Blue’, ‘Green’ and ‘White’ allows one to set the colors of the faces of the four cubes as one wishes. A face is first selected with the mouse and then the color button is chosen.
I want to assure FC users that I did not become insane while learning Python and FC scripting, although I admit to some frustrating hours searching the Forum and the broader web for how to accomplish certain tasks.
I hope that students of FC coding will find some value in exploring the details of this project.
Being a user of FC for some time now and having no experience with Python and object oriented languages, just C and LiveCode, I decided to challenge myself to learn Python and some of the innards of FC by coding an interesting problem.
‘Instant Insanity’, a popular puzzle, consists of four cubes with faces colored either blue, red, green or white. The object of the puzzle is to find the alignment of the four cubes such that each of the four rows of exposed sides contain the four colors, ie. no duplicate colors in a row.
When executing the Macro ‘Instant Insanity’ a new document named “Instant Insanity’ is created and four cubes are generated. The colors of the faces of the cubes are initially white. The Macro also generates a control window with buttons as follows:
‘Run Loop’ will initiate a loop to find the correct orientation of the four cubes that solves the problem. The solution, with the given initial conditions, is found in about 34 seconds.
‘Random Orientations’ orients the four cubes in a random alignment. The solution time varies with the initial arrangement of the cubes. The four cubes can take on 331,776 possible alignments.
‘Results’ will print the alignment that solves the puzzle. All output is sent to the Report view.
‘Set Colors’ will assign the puzzle’s colors to the four cubes. The particular ordering and alignment of the cubes used as initial conditions was chosen arbitrarily.
The four buttons ‘Red’, ‘Blue’, ‘Green’ and ‘White’ allows one to set the colors of the faces of the four cubes as one wishes. A face is first selected with the mouse and then the color button is chosen.
I want to assure FC users that I did not become insane while learning Python and FC scripting, although I admit to some frustrating hours searching the Forum and the broader web for how to accomplish certain tasks.
I hope that students of FC coding will find some value in exploring the details of this project.
- Attachments
-
- InstantInsanity.FCMacro
- (20.87 KiB) Downloaded 44 times
Last edited by DanielLeeWenger on Thu Oct 28, 2021 12:06 am, edited 3 times in total.
-
- Veteran
- Posts: 5513
- Joined: Thu Apr 05, 2018 1:53 am
Re: Instant Insanity - An exercise in learning FreeCAD scripting.
I think replace:
with:
Will fix the task panel bug.
Code: Select all
Gui.Control.showDialog(panel)
Code: Select all
panel.show()
-
- Posts: 53
- Joined: Sun Feb 02, 2020 4:02 am
- Location: Santa Cruz, California
- Contact:
Re: Instant Insanity - An exercise in learning FreeCAD scripting.
To TheMarkster
Thanks for the suggestion. The bug is still there.
Correction: That did fix the bug. Thank you.
Thanks for the suggestion. The bug is still there.
Correction: That did fix the bug. Thank you.
Last edited by DanielLeeWenger on Thu Oct 28, 2021 12:14 am, edited 1 time in total.
Re: Instant Insanity - An exercise in learning FreeCAD scripting.
Quote button is top-right of each post (double quote symbol).DanielLeeWenger wrote: ↑Wed Oct 27, 2021 9:37 pm To TheMarkster
Thanks for the suggestion. The bug is still there.
By the way, how do I gracefully refer to your post in my reply?
-
- Veteran
- Posts: 5513
- Joined: Thu Apr 05, 2018 1:53 am
Re: Instant Insanity - An exercise in learning FreeCAD scripting.
Here are some observations. You may, of course, feel free to disregard them.
If I were to risk my (dubious) sanity by playing this I think I would want to be able to select 2 faces of a cube and swap their colors. This way I am not left with a cube that has a missing color and 2 faces with the same color. I presume this can never lead to a valid solution or else the solution would be trivial -- set each cube to all faces the same color. It should not be too hard to implement.
When viewing from the top do the colors on the left and right side of each cube play a role at all? It seems to me only the top, back, bottom, and front faces play a role in the puzzle. If not, then it might be possible to remove those faces and enable 2-sided rendering so that the color of the back face can be discerned more easily. An added benefit is to remove the confusion that these extraneous faces provide, if they are, indeed, extraneous.
Something like this I have done with the cube on the left using the Gui:
These would not be cubes anymore, but shells. Or maybe you could make 4 faces and a compound of those 4 faces.
In this image I made the cube on the right using a WireFilter object. Select the 4 faces to use, run the WireFilter macro. Right click -> set colors -> set a color for each face. In the view tab, 2-sided Lighting option. Edit: Forgot to mention use FaceMakerExtrusion to make the faces and set Follow Source property to False.
Such 4-sided cubes should not be too hard to make. Perhaps add a Part::Feature object and make its shape from the faces of a hidden cube object.
A better alternative: use Part.makeBox() to make the box in memory instead of having the hidden cube object.
If I were to risk my (dubious) sanity by playing this I think I would want to be able to select 2 faces of a cube and swap their colors. This way I am not left with a cube that has a missing color and 2 faces with the same color. I presume this can never lead to a valid solution or else the solution would be trivial -- set each cube to all faces the same color. It should not be too hard to implement.
When viewing from the top do the colors on the left and right side of each cube play a role at all? It seems to me only the top, back, bottom, and front faces play a role in the puzzle. If not, then it might be possible to remove those faces and enable 2-sided rendering so that the color of the back face can be discerned more easily. An added benefit is to remove the confusion that these extraneous faces provide, if they are, indeed, extraneous.
Something like this I have done with the cube on the left using the Gui:
These would not be cubes anymore, but shells. Or maybe you could make 4 faces and a compound of those 4 faces.
In this image I made the cube on the right using a WireFilter object. Select the 4 faces to use, run the WireFilter macro. Right click -> set colors -> set a color for each face. In the view tab, 2-sided Lighting option. Edit: Forgot to mention use FaceMakerExtrusion to make the faces and set Follow Source property to False.
Such 4-sided cubes should not be too hard to make. Perhaps add a Part::Feature object and make its shape from the faces of a hidden cube object.
Code: Select all
FreeCAD.newDocument()
cube1 = FreeCAD.ActiveDocument.addObject("Part::Box","Cube1")
shell1 = FreeCAD.ActiveDocument.addObject("Part::Feature","Shell1")
FreeCAD.ActiveDocument.recompute()
cube1.ViewObject.ShowInTree = False
cube1.ViewObject.Visibility = False
shell1.ViewObject.Lighting = "Two side"
shell1.Shape = Part.makeCompound([cube1.Shape.Faces[2], cube1.Shape.Faces[3],cube1.Shape.Faces[4], cube1.Shape.Faces[5]])
Last edited by TheMarkster on Wed Oct 27, 2021 10:19 pm, edited 1 time in total.
-
- Veteran
- Posts: 5513
- Joined: Thu Apr 05, 2018 1:53 am
Re: Instant Insanity - An exercise in learning FreeCAD scripting.
My suggestion works for me here:
OS: Windows 10 (10.0)
Word size of FreeCAD: 64-bit
Version: 0.20.25997 (Git)
Build type: Release
Branch: master
Hash: 77b198048a63f1e9ca15eef64c8042d599a14cf3
Python version: 3.8.12
Qt version: 5.12.9
Coin version: 4.0.0
OCC version: 7.5.2
Locale: English/United States (en_US)
and here:
OS: Windows 10 (10.0)
Word size of OS: 64-bit
Word size of FreeCAD: 64-bit
Version: 0.19.24267 (Git)
Build type: Release
Branch: master
Hash: b2ca86d8d72b636011a73394bf9bcdedb3b109b7
Python version: 3.8.8
Qt version: 5.12.5
Coin version: 4.0.0
OCC version: 7.4.0
Locale: English/United States (en_US)
OS: Windows 10 (10.0)
Word size of FreeCAD: 64-bit
Version: 0.20.25997 (Git)
Build type: Release
Branch: master
Hash: 77b198048a63f1e9ca15eef64c8042d599a14cf3
Python version: 3.8.12
Qt version: 5.12.9
Coin version: 4.0.0
OCC version: 7.5.2
Locale: English/United States (en_US)
and here:
OS: Windows 10 (10.0)
Word size of OS: 64-bit
Word size of FreeCAD: 64-bit
Version: 0.19.24267 (Git)
Build type: Release
Branch: master
Hash: b2ca86d8d72b636011a73394bf9bcdedb3b109b7
Python version: 3.8.8
Qt version: 5.12.5
Coin version: 4.0.0
OCC version: 7.4.0
Locale: English/United States (en_US)
Code: Select all
import PySide
from PySide import QtGui ,QtCore
from PySide.QtGui import *
from PySide.QtCore import *
import Draft, Part, PartGui, FreeCADGui, FreeCAD
from FreeCAD import Base
import DraftVecUtils
import time
import random
import copy
# ususal assignments---------------------------
Gui = FreeCADGui
App = FreeCAD
doc=App.ActiveDocument
v=App.Vector
# defined terms---------------------------------
# these tuples have 4 elements, the last is the transparency
red=1.0,0.0,0.0,0.0
green=0.0,1.0,0.0,0.0
blue=0.0,0.0,1.0,0.0
white=1.0,1.0,1.0,0.0
Red="Red"
Green="Green"
Blue="Blue"
White="White"
colorDictionary={red:Red,green:Green,blue:Blue,white:White}
Cube1Position=v(15,0,0)
Cube2Position=v(30,0,0)
Cube3Position=v(45,0,0)
Cube4Position=v(60,0,0)
Cube1CofM=v(20,5,5)
Cube2CofM=v(35,5,5)
Cube3CofM=v(50,5,5)
Cube4CofM=v(65,5,5)
# these are the initial 6 colors of each cube corresponding to the cubes of the actual puzzle (arbitarly numbered and oriented)
Cube1Colors=[green,red,blue,white,red,red]
Cube2Colors=[white,green,blue,red,white,red]
Cube3Colors=[green,green,blue,white,blue,red]
Cube4Colors=[green,white,white,blue,red,green]
# global variables-------------------------------------
# orignail placements of the cubes, in a list
originalPlacements=[]
# each entry for each cube 1,2,3,4 has the Placement and the col (four colors associated with plusZ, minusY, minusZ and plusY) for each of 24 possible orientations
orient1=[]
orient2=[]
orient3=[]
orient4=[]
# the Rows constructed from the set of cols for a configuration of cubes
plusZRow=[]
minusYRow=[]
minusZRow=[]
plusYRow=[]
# the collection of cols for a set of cubes, inially just one cube, then two cubes, then three cubes, then four cubes
curConfig=[]
# indices used in doTheLoop
index1=0
index2=0
index3=0
index4=0
resultFlag=0
# functions defined---------------------------------
#------------------------------------makeCubes
def makeCubes():# called by the main program
# the four cubes are not indexed into an list in this program in order to make the program more readable
global originalPlacements
makeCube("Cube1",Cube1Position)
makeCube("Cube2",Cube2Position)
makeCube("Cube3",Cube3Position)
makeCube("Cube4",Cube4Position)
originalPlacements=[]
originalPlacements.append(doc.Cube1.Placement)
originalPlacements.append(doc.Cube2.Placement)
originalPlacements.append(doc.Cube3.Placement)
originalPlacements.append(doc.Cube4.Placement)
#Gui.updateGui()
doc.recompute(None,True,True)
#------------------------------------makeCube
def makeCube(name,location): # called by makeCubes()
obj=doc.addObject("Part::Box",name)
Draft.move(obj,location,False)
# the above generates just one entry for the color of the faces
# thecode below expands the one color explicitly to each face
# this is done to generate a list with 6 entries, not just 1 entry
faceColors=obj.ViewObject.DiffuseColor # faceColors[0] is the only one returned
faceColor = white
faceColors=[faceColor,faceColor,faceColor,faceColor,faceColor,faceColor]
obj.ViewObject.DiffuseColor=faceColors # assigns each face an explicit color
#------------------------------------getPlacementsCols
def getPlacementsCols(): # called by makeCubes()
# generate the four lists orient1, orient2, orient3 and orient4
# the lists hold the Placement and the col for each orientation of each cube
global orient1;global orient2;global orient3;global orient4
orient1=generatePlacementsCols(doc.Cube1,Cube1CofM)
orient2=generatePlacementsCols(doc.Cube2,Cube2CofM)
orient3=generatePlacementsCols(doc.Cube3,Cube3CofM)
orient4=generatePlacementsCols(doc.Cube4,Cube4CofM)
#------------------------------------generatePlacementsCols
def generatePlacementsCols(Cube,cm): # called by getPlacementsCols()
# for each of the 24 possible orientations of the Cube
# generate the associated Placements and cols
# the col holds the four colors of the cube Top, Front, Bottom and Back for the corresponding Placement
# the Placement and the col are stored together as entries in the orient list
theAngle = 90
x=(theAngle,0.0,0.0) # an angle about the x axis
y=(0.0,theAngle,0.0) # an angle about the y axis
z=(0.0,0.0,theAngle) # an angle about the z axis
orient=[]
originalPlacement=Cube.Placement # save for final restoration of the cube
orient.append(getPair(Cube)) # getPair() returns the Placement and the col of the orientation of the cube
rotate(Cube,x,cm); orient.append(getPair(Cube)) # rotate the cube about the x axis by 90 deg
rotate(Cube,x,cm);orient.append(getPair(Cube))
rotate(Cube,x,cm);orient.append(getPair(Cube))
rotate(Cube,x,cm) # rotated about the x axis by 360 deg
rotate(Cube,z,cm);orient.append(getPair(Cube)) # rotated about the z axis by 90 deg
rotate(Cube,x,cm);orient.append(getPair(Cube))
rotate(Cube,x,cm);orient.append(getPair(Cube))
rotate(Cube,x,cm);orient.append(getPair(Cube))
rotate(Cube,x,cm)
rotate(Cube,z,cm);orient.append(getPair(Cube)) # rotated about the z axis by 180 deg
rotate(Cube,x,cm);orient.append(getPair(Cube))
rotate(Cube,x,cm);orient.append(getPair(Cube))
rotate(Cube,x,cm);orient.append(getPair(Cube))
rotate(Cube,x,cm)
rotate(Cube,z,cm);orient.append(getPair(Cube)) # rotated about the z axis by 270 deg
rotate(Cube,x,cm);orient.append(getPair(Cube))
rotate(Cube,x,cm);orient.append(getPair(Cube))
rotate(Cube,x,cm); orient.append(getPair(Cube))
rotate(Cube,x,cm)
rotate(Cube,z,cm) # rotated about the z axis by 360 deg
rotate(Cube,y,cm);orient.append(getPair(Cube)) # rotated about the y axis by 90 deg
rotate(Cube,x,cm); orient.append(getPair(Cube))
rotate(Cube,x,cm); orient.append(getPair(Cube))
rotate(Cube,x,cm); orient.append(getPair(Cube))
rotate(Cube,x,cm)
rotate(Cube,y,cm)
rotate(Cube,y,cm);orient.append(getPair(Cube)) # rotated about the y axis by 180 deg
rotate(Cube,x,cm); orient.append(getPair(Cube))
rotate(Cube,x,cm); orient.append(getPair(Cube))
rotate(Cube,x,cm); orient.append(getPair(Cube))
Cube.Placement=originalPlacement
return(orient)
#------------------------------------getPair
def getPair(Cube): # called by generatePlacementsCols()
thePlacement=Cube.Placement # get the Placement of Cube
col=getCol(Cube) # get the four significant colors of the Cube in its current Placement
return((thePlacement,list(col)))
#------------------------------------doRotation
def rotate(theObj,theAngles,theCScenter): # called by generatePlacementsCols()
# this is a very useful function for use in FreeCAD
# Rotate an object around the center of a coordinate system CS located at theCScenter
# by angleX about the x axis of the CS, by angleY about the y axis and then angleZ about the z axis of the CS,in that order.
# this code is patterned after Rotate_To_Point.FCMacro by "Mario52"
#
# To achieve a rotation about z axis of the CS and then about the x axis of the CS, for example
# perform two calls to rotate(), one for the z rotation and then one for the x rotation.
angleX=theAngles[0]
angleY=theAngles[1]
angleZ=theAngles[2]
CSx=theCScenter[0]
CSy=theCScenter[1]
CSz=theCScenter[2]
theObj.Placement = App.Placement(App.Vector(0.0,0.0,0.0), App.Rotation(angleZ, angleY, angleX), App.Vector(CSx, CSy, CSz)).multiply(App.ActiveDocument.getObject(theObj.Name).Placement)
App.ActiveDocument.recompute()
#------------------------------------getCol
def getCol(Cube): # called by getPair()
# check each face of cube, collect colors of those facing in the four directions +-y or +-z
global colorDictionary
col=[0,0,0,0] # initialize list
CubeColors=Cube.ViewObject.DiffuseColor
for faceIndex in range(6):
v=Cube.Shape.Faces[faceIndex].normalAt(0,0) # get normal vector for the face
vY=v[1] # only interested in those faces with normals in the +-y and +-z directions
vZ=v[2]
# normal should have a value of magnitude 1, but allow for a value very close to 1
# due to possible accumulated error in Placement
theColor=CubeColors[faceIndex] # theColor is a tuple with 4 elemets, 3 for the color and the last for the transparency
theColor2=colorDictionary[theColor] # theColor2 is a string
if vY > 0.9:
col[0]= theColor2
elif vZ < -0.9:
col[1] = theColor2
elif vY < -0.9:
col[2]= theColor2
elif vZ > 0.9:
col[3]= theColor2
return(col)
#------------------------------------getSelection
def getMySelection(): # called by processFace()
# handles the selection of a face to allow assigning a color to that face.
# only used for custom coloring of the cubes
try:
selectedName = Gui.Selection.getSelection()[0].Name
selectedObject=doc.getObject(selectedName)
selectedFace = Gui.Selection.getSelectionEx()[0].SubElementNames[0]
theFace = Gui.Selection.getSelectionEx()[0].SubObjects[0]
if selectedFace.count("Face")!=1:
print("Select a Face and run script again.")
return([])
return([selectedObject,selectedFace])
except:
print("Select a Face and run script again.")
return([])
#------------------------------------MyPanel
class MyPanel: # called by the main program
# window and buttons are actually displayed
# not my code, borrowed from a macro
def __init__(self):
self.form = QtGui.QLineEdit()
def accept(self):
# add your code her
QtGui.QMessageBox.information(None,"Task panel",self.form.text())
return True
#------------------------------------MyGui
class MyGui(QtGui.QWidget): # called by the main program
# where the window and buttons and their functions are defined
# not my code, borrowed from a macro
def __init__(self):
super(MyGui, self).__init__()
#Window
self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
self.setWindowTitle("Selection Panel")
self.resize(600,200)
self.show()
#Buttons
self.bt_start = QtGui.QPushButton("Run Loop",self)
self.bt_stop = QtGui.QPushButton("Random Orientions",self)
self.bt_red = QtGui.QPushButton("Red",self)
self.bt_blue = QtGui.QPushButton("Blue",self)
self.bt_green = QtGui.QPushButton("Green",self)
self.bt_white = QtGui.QPushButton("White",self)
self.bt_save = QtGui.QPushButton("Results",self)
self.bt_restore = QtGui.QPushButton("Set Colors",self)
QtCore.QObject.connect(self.bt_start, QtCore.SIGNAL("pressed()"), self.bt_start_click)
QtCore.QObject.connect(self.bt_stop, QtCore.SIGNAL("pressed()"), self.bt_stop_click)
QtCore.QObject.connect(self.bt_red, QtCore.SIGNAL("pressed()"), self.bt_red_click)
QtCore.QObject.connect(self.bt_blue, QtCore.SIGNAL("pressed()"), self.bt_blue_click)
QtCore.QObject.connect(self.bt_green, QtCore.SIGNAL("pressed()"), self.bt_green_click)
QtCore.QObject.connect(self.bt_white, QtCore.SIGNAL("pressed()"), self.bt_white_click)
QtCore.QObject.connect(self.bt_save, QtCore.SIGNAL("pressed()"), self.bt_save_click)
QtCore.QObject.connect(self.bt_restore, QtCore.SIGNAL("pressed()"), self.bt_restore_click)
#Layout
layout = QtGui.QGridLayout()
layout.addWidget(self.bt_start, 0,0)
layout.addWidget(self.bt_stop, 0,1)
layout.addWidget(self.bt_save, 0,2)
layout.addWidget(self.bt_restore, 0,3)
layout.addWidget(self.bt_red, 1,0)
layout.addWidget(self.bt_blue, 1,1)
layout.addWidget(self.bt_green, 1,2)
layout.addWidget(self.bt_white, 1,3)
self.setLayout(layout)
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.timer_tick)
def timer_tick(self):
#App.ActiveDocument.getObject("Box").Placement.Rotation.Angle += 0.1
#App.Console.PrintMessage("Timer ticked ")
pass
def bt_start_click(self):
#App.Console.PrintMessage("button start click\n")
print("Beginning the loop to find the solution.")
#self.timer.start(1)
doTheLoop()
def bt_stop_click(self):
#App.Console.PrintMessage("button stop click\n")
#self.timer.stop()
stepRandom()
def bt_red_click(self):
global red
#App.Console.PrintMessage("button red click\n")
processFace(red)
def bt_blue_click(self):
global blue
#App.Console.PrintMessage("button blue click\n")
processFace(blue)
def bt_green_click(self):
#App.Console.PrintMessage("button green click\n")
processFace(green)
def bt_white_click(self):
#App.Console.PrintMessage("button white click\n")
processFace(white)
def bt_save_click(self):
#App.Console.PrintMessage("button save click\n")
printResults()
def bt_restore_click(self):
#App.Console.PrintMessage("button restored click\n")
restoreColors()
def closeEvent(self, event):
#self.timer.stop()
App.Console.PrintMessage("closing")
#------------------------------------restore
def restoreColors(): #called by button click
# set colors and cube orientation lists
global originalPlacements
doc.Cube1.ViewObject.DiffuseColor=Cube1Colors
doc.Cube2.ViewObject.DiffuseColor=Cube2Colors
doc.Cube3.ViewObject.DiffuseColor=Cube3Colors
doc.Cube4.ViewObject.DiffuseColor=Cube4Colors
doc.Cube1.Placement=originalPlacements[0]
doc.Cube2.Placement=originalPlacements[1]
doc.Cube3.Placement=originalPlacements[2]
doc.Cube4.Placement=originalPlacements[3]
getPlacementsCols()
Gui.updateGui()
doc.recompute()
#------------------------------------doTheLoop
def doTheLoop(): # initiated by a "Sart" button click. Run the loop to find a solution to "Instant Insanity"
# Outline of the approach:
# the possible orientations of a cube are given by orient[index], index has range 24
# orient[index] has two components, orient[index][0] and orient[index][1]
# orient[index][0] is the Placement of the cube for that index
# orient[index][1] is the associated col (list of relavent colors) for that index
# col is the Top, Front, Bottom, Back colors of the faces of the cube for that particular Placement
# curConfig is the collection of cols, one for each cube
# initially curConfig is empty
# starting with Cube1, any orientation is acceptable, curConfig is just the col of Cube1 in its starting orientation
# when adding Cube2, ie., when adding the col for Cube2 to curConfig, a test is done to see if the two cols meet the requirment of the puzzle
# the plusZRow, ie., the top colors of Cube1 and Cube2 need to be distinct, ie. no row can have duplicate colors
# the same restriction applies to the minusYRow, minusZRow and plusYRow
# if the col of Cube2 meets the test then curConfig is updated to the col of Cube1 and the col of Cube2
# the sme process is applied to Cube3, but now the col of Cube3 must meet the test, the Rows, now containing the colors of three cubes, do not contain duplicate colors
# ditto for Cube4
# if any test shows that the newly added col is does not meet the test of the puzzle, a continue is performed and the next
# orientation of the cube under consideration is examined
# should all orientats of a particular cube fail the test then the last cube that was acceped is rejected and anothe aceepable orientation for that cube is sought
# should that search fail to find an acceptable oriention, the process of moving back one cube is repeated until a cube orientation is found
# if all possible cube orientations fail to meet thetest, then no solution exits
# if after working on Cube4 an oriention is found acceptable, then the solution to the puzzle has been found
global orient1;global orient2;global orient3;global orient4
global curConfig
global index1;global index2;global index3;global index4
global orientationCount
global resultFlag
# run getPlacementsCols() again should be colors of any cube face been changed
getPlacementsCols()
count=24 # count is 24 for final run, a lesser value for test purposes
orientationCount=0
resultFlag=0
for index1 in range(count):
#1111111111111111111111
orientationCount +=1
curConfig=[]; curConfig.append(orient1[index1][1]) # set configuration to orientation of Cube1
upDate() # update display
for index2 in range(count):
#2222222222222222222222
orientationCount +=1
if newColDoesNotDanceWithCurConfig(orient2[index2][1]): # test Cube2 col aganst curConfig, curConfig contains just Cube1's col
continue # try next orientation
upDate() # success so far
for index3 in range(count):
#3333333333333333333333
orientationCount +=1
if newColDoesNotDanceWithCurConfig(orient3[index3][1]): # test Cube3 aganst curConfig, curdConfig contains the cols for Cube1 and Cube2
continue
upDate() # success so far
for index4 in range(count):
#4444444444444444444444
orientationCount +=1
if newColDoesNotDanceWithCurConfig(orient4[index4][1]): # test Cube4 aganst curConfig
continue
upDate()
print("Success!")
resultFlag=1
print("Number of adjustments: ",orientationCount)
printResults()
return
#4444444444444444444444
del curConfig[-1]
#3333333333333333333333
del curConfig[-1]
#2222222222222222222222
del curConfig[-1]
#1111111111111111111111
print("No solution!")
return
#------------------------------------testConfig
def newColDoesNotDanceWithCurConfig(newCol): # called by doTheLoop()
# checks to see if the newCol is compatible with the curConfig
# calculate the Rows including the new Cube
# and see if they meet the conditions for success
global curConfig
global plusZRow;global minusYRow;global minusZRow;global plusYRow
plusZRow=[]
minusYRow=[]
minusZRow=[]
plusYRow=[]
for index in range(len(curConfig)): # build Rows with current cols in curConfig
buildRows(curConfig[index])
buildRows(newCol) # add the col of the next cube
if isAcceptable(): # test if the proposed configuration is accepable, ie. meets the conditions of the puzzle
curConfig.append(newCol) # update curConfig with the added Cube
return(False) # this little trickery in order to make doTheLoop more readable
return(True)
#------------------------------------printResults
def printResults():
global plusZRow;global minusYRow;global minusZRow;global plusYRow
global resultFlag
if resultFlag==0:
print("No results yet.")
else:
myTable=[
["-","plusY","minuZ","minusY","plusZ"],
["Cube1",curConfig[0][0],curConfig[0][1],curConfig[0][2],curConfig[0][3]],
["Cube2",curConfig[1][0],curConfig[1][1],curConfig[1][2],curConfig[1][3]],
["Cube3",curConfig[2][0],curConfig[2][1],curConfig[2][2],curConfig[2][3]],
["Cube4",curConfig[3][0],curConfig[3][1],curConfig[3][2],curConfig[3][3]]]
for row in myTable:
print("{: <20} {: <20} {: <20} {: <20} {: <20}".format(*row)) # < left justified, > right justified, ^ cenetered
#------------------------------------upDate
def upDate(): # called by isAcceptable()
global orient1;global orient2;global orient3;global orient4
global index1;global index2;global index3;global index4
doc.Cube1.Placement=orient1[index1][0]
doc.Cube2.Placement=orient2[index2][0]
doc.Cube3.Placement=orient3[index3][0]
doc.Cube4.Placement=orient4[index4][0]
Gui.updateGui()
#time.sleep(0.2)
#------------------------------------buildRows
def buildRows(col): # called by newColDoesNotDanceWithCurConfig()
global plusZRow;global minusYRow;global minusZRow;global plusYRow
plusZRow.append(col[0])
minusYRow.append(col[1])
minusZRow.append(col[2])
plusYRow.append(col[3])
#------------------------------------isAcceptable
def isAcceptable(): # called by testConfig()
# test to see if there are duplicat colors in any Row
global plusZRow;global minusYRow;global minusZRow;global plusYRow
num=len(plusZRow) # all Rows the same length
if len(set(plusZRow)) != num or len(set(minusYRow)) != num or len(set(minusZRow)) != num or len(set(plusYRow)) != num:
upDate()
return(False)
else:
upDate()
return(True)
#------------------------------------stepRandom
def stepRandom(): # called when a random placement of the cubes is desired, via the "Random" button
global orient1
global orient2
global orient3
global orient4
global resultFlag
resultFlag=0
getPlacementsCols() # run each time to allow for any change in face coloring
doc.Cube1.Placement=orient1[random.randint(0,23)][0]
doc.Cube2.Placement=orient2[random.randint(0,23)][0]
doc.Cube3.Placement=orient3[random.randint(0,23)][0]
doc.Cube4.Placement=orient4[random.randint(0,23)][0]
doc.recompute()
#------------------------------------processFace
def processFace(clickedColor): # called when applying colors to faces of cubes, called by MyGui() via selection of color button
theSelection=getMySelection()
if theSelection==[]:
return
colorFace(theSelection,clickedColor)
doc.recompute()
#------------------------------------colorFace
def colorFace(theSelection,colorSelected): # called by processFace() used when applying a color to a face of a cube
theCube=theSelection[0]
faceColors=theCube.ViewObject.DiffuseColor
theFace=theSelection[1]
theFaceNum=theFace[4] # Face
pos=int(theFaceNum)-1
faceColors[pos]=colorSelected
theCube.ViewObject.DiffuseColor=faceColors
#------------------------------------main program
doc=App.newDocument("Instant-Insanity")
makeCubes()
Gui.activeDocument().activeView().viewIsometric()
Gui.SendMsgToActiveView("ViewFit")
# this coding is just copied and tweeked from a macro
gui = MyGui()
panel=MyPanel()
try:
#Gui.Control.showDialog(panel)
panel.show()
except:
pass
# other actions are initiated via the control buttons in panel
doc.recompute()
-
- Posts: 53
- Joined: Sun Feb 02, 2020 4:02 am
- Location: Santa Cruz, California
- Contact:
-
- Posts: 53
- Joined: Sun Feb 02, 2020 4:02 am
- Location: Santa Cruz, California
- Contact:
Re: Instant Insanity - An exercise in learning FreeCAD scripting.
All six sides of the cubes are relevant. With the coloring provided by "Set Colors" the coloring is fixed to correspond to the actual color pattern of the puzzle. Each cube can appear in 24 different orientations. Only the faces along the four rows are relevant in the test for a solution. The side/end faces do not play a role in that test.TheMarkster wrote: ↑Wed Oct 27, 2021 10:08 pm Here are some observations. You may, of course, feel free to disregard them
The capability of setting colors of choice is just for fun to see what happens if the game is played with different color patterns.
It is true that one has to move the view around to see what the back side colors are. A color map of each cube might be done.
I appreciate your attention to the code. \
With regard to the substitution panel.show() to fix the bug, I now see that it does solve the problem. Thank you!.