Instant Insanity - An exercise in learning FreeCAD scripting.

Need help, or want to share a macro? Post here!
Forum rules
Be nice to others! Respect the FreeCAD code of conduct!
Post Reply
DanielLeeWenger
Posts: 53
Joined: Sun Feb 02, 2020 4:02 am
Location: Santa Cruz, California
Contact:

Instant Insanity - An exercise in learning FreeCAD scripting.

Post by DanielLeeWenger »

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.
Attachments
InstantInsanity.FCMacro
(20.87 KiB) Downloaded 42 times
Last edited by DanielLeeWenger on Thu Oct 28, 2021 12:06 am, edited 3 times in total.
TheMarkster
Veteran
Posts: 5505
Joined: Thu Apr 05, 2018 1:53 am

Re: Instant Insanity - An exercise in learning FreeCAD scripting.

Post by TheMarkster »

I think replace:

Code: Select all

Gui.Control.showDialog(panel)
with:

Code: Select all

panel.show()
Will fix the task panel bug.
DanielLeeWenger
Posts: 53
Joined: Sun Feb 02, 2020 4:02 am
Location: Santa Cruz, California
Contact:

Re: Instant Insanity - An exercise in learning FreeCAD scripting.

Post by DanielLeeWenger »

To TheMarkster

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.
openBrain
Veteran
Posts: 9034
Joined: Fri Nov 09, 2018 5:38 pm
Contact:

Re: Instant Insanity - An exercise in learning FreeCAD scripting.

Post by openBrain »

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?
Quote button is top-right of each post (double quote symbol). ;)
TheMarkster
Veteran
Posts: 5505
Joined: Thu Apr 05, 2018 1:53 am

Re: Instant Insanity - An exercise in learning FreeCAD scripting.

Post by TheMarkster »

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:
Snip macro screenshot-d96d8c.png
Snip macro screenshot-d96d8c.png (36.25 KiB) Viewed 1648 times
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.
Snip macro screenshot-8785d2.png
Snip macro screenshot-8785d2.png (40.27 KiB) Viewed 1648 times
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]])
A better alternative: use Part.makeBox() to make the box in memory instead of having the hidden cube object.
Last edited by TheMarkster on Wed Oct 27, 2021 10:19 pm, edited 1 time in total.
TheMarkster
Veteran
Posts: 5505
Joined: Thu Apr 05, 2018 1:53 am

Re: Instant Insanity - An exercise in learning FreeCAD scripting.

Post by TheMarkster »

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)


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()
DanielLeeWenger
Posts: 53
Joined: Sun Feb 02, 2020 4:02 am
Location: Santa Cruz, California
Contact:

Re: Instant Insanity - An exercise in learning FreeCAD scripting.

Post by DanielLeeWenger »

openBrain wrote: Wed Oct 27, 2021 9:59 pm
By the way, how do I gracefully refer to your post in my reply?
Quote button is top-right of each post (double quote symbol). ;)
[/quote]

Ahhh. Thank you.
DanielLeeWenger
Posts: 53
Joined: Sun Feb 02, 2020 4:02 am
Location: Santa Cruz, California
Contact:

Re: Instant Insanity - An exercise in learning FreeCAD scripting.

Post by DanielLeeWenger »

TheMarkster wrote: Wed Oct 27, 2021 10:08 pm Here are some observations. You may, of course, feel free to disregard them
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.

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!.
Post Reply