Piffpoof Macro Half-Hull Model

From FreeCAD Documentation
Jump to navigation Jump to search
#
#
#			Half Hull
#
#
################################

import FreeCAD
from FreeCAD import Base, Draft
import Part, PartGui, sys, math, collections
from collections import OrderedDict
from os.path import expanduser		# default input directory
from PySide import QtGui, QtCore

# UI Class definitions

class ConfigParams:
	"""carrier for the user selection parameters"""
	def __init__(self):
		self.result				= None
		self.cb1a				= None
		self.cb1b				= None
		self.cb1c				= None
		self.cb2a				= None
		self.cb2b				= None
		self.cb2c				= None
		self.cb3a				= None
		self.cb3b				= None
		self.cb3c				= None
		self.cb4a				= None
		self.rb4b				= None
		self.rb5b				= None
		self.skipAtBow			= None
		self.skipAtStern		= None
		self.deckWidth			= None
		self.deckThrow			= None
		self.coachhouseRise		= None
		self.coachhouseIncline	= None
		self.documentFileName	= None

class GetConfigParams(QtGui.QDialog):
	""""""
	def __init__(self):
		super(GetConfigParams, self).__init__()
		self.initUI()
	def initUI(self):
		self.configParams			= ConfigParams()
		# set default return value
		self.configParams.result 	= userCancelled
		# set default values
		skipAtBowDefault			= str(2)
		skipAtSternDefault			= str(2)
		deckWidthDefault			= str(50)
		deckThrowDefault			= str(2)
		coachhouseRiseDefault		= str(50)
		coachhouseInclineDefault	= str(8)
		# field descriptors
		self.promptLbl = QtGui.QLabel("Please Choose Options:", self)
		self.promptLbl.move(20, 20)
		# checkboxes - define first so  signals can be set up
		self.cb1a = QtGui.QCheckBox("Starboard half-hull", self)
		self.cb1b = QtGui.QCheckBox("Mounting plaque", self)
		self.cb1c = QtGui.QCheckBox("Allow space for keel", self)
		self.cb2a = QtGui.QCheckBox("Port half-hull", self)
		self.cb2b = QtGui.QCheckBox("Mounting plaque", self)
		self.cb2c = QtGui.QCheckBox("Allow space for keel", self)
		self.cb3a = QtGui.QCheckBox("Complete hull", self)
		self.cb3b = QtGui.QCheckBox("Bottle for complete hull", self)
		self.cb3c = QtGui.QCheckBox("Allow space for keel", self)
		self.rb4b = QtGui.QRadioButton("Bulkheads for flush deck",self)
		self.rb5b = QtGui.QRadioButton("Bulkheads for coachhouse",self)
		#
		self.cb1a.clicked.connect(self.onCb1a)
		self.cb1a.toggle() # set default value
		self.cb1c.setEnabled(False)
		#self.cb1a.stateChanged.connect(self.onCb1a)
		self.cb1a.move(20,50)
		#
		self.cb1b.clicked.connect(self.onCb1b)
		self.cb1b.move(250,50)
		#
		self.cb1c.clicked.connect(self.onCb1c)
		self.cb1c.move(450,50)
		#
		self.cb2a.clicked.connect(self.onCb2a)
		self.cb2b.setEnabled(False)
		self.cb2c.setEnabled(False)
		self.cb2a.move(20,80)
		#
		self.cb2b.clicked.connect(self.onCb2b)
		self.cb2b.move(250,80)
		#
		self.cb2c.clicked.connect(self.onCb2c)
		self.cb2c.move(450,80)
		#
		self.cb3a.clicked.connect(self.onCb3a)
		self.cb3b.setEnabled(False)
		self.cb3c.setEnabled(False)
		self.cb3a.move(20,110)
		#
		self.cb3b.clicked.connect(self.onCb3b)
		self.cb3b.move(250,110)
		#
		self.cb3c.clicked.connect(self.onCb3c)
		self.cb3c.move(450,110)
		#
		self.cb4a = QtGui.QCheckBox("Bulkheads for complete hull", self)
		self.cb4a.clicked.connect(self.onCb4a)
		self.rb4b.setEnabled(False)
		self.rb5b.setEnabled(False)
		#self.hideCoachhouseFields(True) # grey out coachhouse fields
		self.cb4a.move(20,140)
		# radio buttons
		self.rb4b.move(250,140)
		self.rb4b.clicked.connect(self.onRb4b)
		#
		self.rb5b.move(250,170)
		self.rb5b.clicked.connect(self.onRb5b)
		#
		self.skipAtBowLabel = QtGui.QLabel("Cross-sections to skip at bow:", self)
		self.skipAtBowLabel.move(270, 200)
		self.skipAtBow = 0
		#
		self.skipAtSternLabel = QtGui.QLabel("Cross-sections to skip at stern:", self)
		self.skipAtSternLabel.move(270, 230)
		self.skipAtStern = 0
		#
		self.deckWidthLabel = QtGui.QLabel("Deck Width:", self)
		self.deckWidthLabel.move(270, 260)
		self.deckWidth = QtGui.QLineEdit(self)
		self.deckWidth.setInputMask("999")
		self.deckWidth.setText(deckWidthDefault)
		self.deckWidth.setFixedWidth(35)
		self.deckWidth.move(493, 260)
		#
		self.deckThrowLabel = QtGui.QLabel("Deck throw:", self)
		self.deckThrowLabel.move(270, 290)
		self.deckThrow = QtGui.QLineEdit(self)
		self.deckThrow.setInputMask("999")
		self.deckThrow.setText(deckThrowDefault)
		self.deckThrow.setFixedWidth(35)
		self.deckThrow.move(493, 290)
		#
		self.coachhouseRiseLabel = QtGui.QLabel("Coachhouse Rise:", self)
		self.coachhouseRiseLabel.move(270, 320)
		self.coachhouseRise = QtGui.QLineEdit(self)
		self.coachhouseRise.setInputMask("999")
		self.coachhouseRise.setText(coachhouseRiseDefault)
		self.coachhouseRise.setFixedWidth(35)
		self.coachhouseRise.move(493, 320)
		#
		self.coachhouseInclineLabel = QtGui.QLabel("Coachhouse Incline:", self)
		self.coachhouseInclineLabel.move(270, 350)
		self.coachhouseIncline = QtGui.QLineEdit(self)
		self.coachhouseIncline.setInputMask("999")
		self.coachhouseIncline.setText(coachhouseInclineDefault)
		self.coachhouseIncline.setFixedWidth(35)
		self.coachhouseIncline.move(493, 350)
		# set up lists for pop-ups
		self.popupItemsB = OrderedDict([("2",""),("3",""),("4",""),("5",""),("6",""),("7",""),("8",""),("9",""),("10","")])
		self.popupItemsS = OrderedDict([("1",""),("2",""),("3",""),("4",""),("5",""),("6",""),("7",""),("8",""),("9",""),("10","")])
		# set up pop-up menu of bulkheads to skip bulkheads at bow
		self.skipAtBowPop = QtGui.QComboBox(self)
		self.skipAtBowPop.addItems(self.popupItemsB.keys())
		self.skipAtBowPop.setCurrentIndex(self.popupItemsB.keys().index(skipAtBowDefault))
		self.skipAtBow = skipAtBowDefault
		self.skipAtBowPop.move(490, 200)
		self.skipAtBowPop.activated[str].connect(self.onSkipBowActivated)
		# set up pop-up menu of bulkheads to skip bulkheads at stern
		self.skipAtSternPop = QtGui.QComboBox(self)
		self.skipAtSternPop.addItems(self.popupItemsS.keys())
		self.skipAtSternPop.setCurrentIndex(self.popupItemsS.keys().index(skipAtSternDefault))
		self.skipAtStern = skipAtSternDefault
		self.skipAtSternPop.move(490, 230)
		self.skipAtSternPop.activated[str].connect(self.onSkipSternActivated)
		# cancel button
		cancelButton = QtGui.QPushButton('Cancel', self)
		cancelButton.clicked.connect(self.onCancel)
		cancelButton.move(260, 390)
		# last used button
		lastFileButton = QtGui.QPushButton('Re-use last file', self)
		lastFileButton.clicked.connect(self.onLastFile)
		lastFileButton.move(345, 390)
		# OK button
		sfButton = QtGui.QPushButton('Select File', self)
		sfButton.clicked.connect(self.onSf)
		sfButton.move(480, 390)
		# define window		xLoc,yLoc,xDim,yDim
		self.setGeometry(	250, 250, 630, 445)
		self.setWindowTitle("Macro Configuration")
		self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
		self.disableCoachhouseFields(True) # grey out coachhouse fields
		self.show()
		#
	def onCb1a(self):
		if self.cb1a.isChecked():
			self.cb1b.setEnabled(True)
		else:
			self.cb1b.setEnabled(False)
			self.cb1b.setChecked(False)
			self.cb1c.setEnabled(False)
			self.cb1c.setChecked(False)
	def onCb1b(self):
		if self.cb1b.isChecked():
			self.cb1c.setEnabled(True)
		else:
			self.cb1c.setEnabled(False)
			self.cb1c.setChecked(False)
	def onCb1c(self):
		pass
	def onCb2a(self):
		if self.cb2a.isChecked():
			self.cb2b.setEnabled(True)
		else:
			self.cb2b.setEnabled(False)
			self.cb2b.setChecked(False)
			self.cb2c.setEnabled(False)
			self.cb2c.setChecked(False)
	def onCb2b(self):
		if self.cb2b.isChecked():
			self.cb2c.setEnabled(True)
		else:
			self.cb2c.setEnabled(False)
			self.cb2c.setChecked(False)
	def onCb2c(self):
		pass
	def onCb3a(self):
		if self.cb3a.isChecked():
			self.cb3b.setEnabled(True)
		else:
			self.cb3b.setEnabled(False)
			self.cb3b.setChecked(False)
			self.cb3c.setEnabled(False)
			self.cb3c.setChecked(False)
	def onCb3b(self):
		if self.cb3b.isChecked():
			self.cb3c.setEnabled(True)
		else:
			self.cb3c.setEnabled(False)
			self.cb3c.setChecked(False)
	def onCb3c(self):
		pass
	def onCb4a(self):
		if self.cb4a.isChecked():
			self.rb4b.setEnabled(True)
			self.rb4b.setChecked(True)
			self.rb5b.setEnabled(True)
			self.rb5b.setChecked(False)
		else:
			self.rb4b.setChecked(False)
			self.rb4b.setEnabled(False)
			self.rb5b.setChecked(False)
			self.rb5b.setEnabled(False)
			self.disableCoachhouseFields(True)
	def onRb4b(self):
		if self.rb4b.isChecked():
			self.disableCoachhouseFields(True)
		else:
			self.disableCoachhouseFields(False)
	def onRb5b(self):
		if self.rb5b.isChecked():
			self.disableCoachhouseFields(False)
		else:
			self.disableCoachhouseFields(True)
	def onSkipBowActivated(self, text):
		self.skipAtBow = text
	def onSkipSternActivated(self, text):
		self.skipAtStern = text
	def disableCoachhouseFields(self, aFlag):
		# enable or disable coachhouse parameter fields
		if aFlag:
			self.skipAtBowLabel.setEnabled(False)
			self.skipAtBowPop.setEnabled(False)
			self.skipAtSternLabel.setEnabled(False)
			self.skipAtSternPop.setEnabled(False)
			self.deckWidthLabel.setEnabled(False)
			self.deckWidth.setEnabled(False)
			self.deckThrowLabel.setEnabled(False)
			self.deckThrow.setEnabled(False)
			self.coachhouseRiseLabel.setEnabled(False)
			self.coachhouseRise.setEnabled(False)
			self.coachhouseInclineLabel.setEnabled(False)
			self.coachhouseIncline.setEnabled(False)
		else:
			self.skipAtBowLabel.setEnabled(True)
			self.skipAtBowPop.setEnabled(True)
			self.skipAtSternLabel.setEnabled(True)
			self.skipAtSternPop.setEnabled(True)
			self.deckWidthLabel.setEnabled(True)
			self.deckWidth.setEnabled(True)
			self.deckThrowLabel.setEnabled(True)
			self.deckThrow.setEnabled(True)
			self.coachhouseRiseLabel.setEnabled(True)
			self.coachhouseRise.setEnabled(True)
			self.coachhouseInclineLabel.setEnabled(True)
			self.coachhouseIncline.setEnabled(True)
	def onCancel(self):
		self.configParams.result			= userCancelled
		self.close()
	def transferConfigParams(self):
		self.configParams.cb1a				= self.cb1a.isChecked()
		self.configParams.cb1b				= self.cb1b.isChecked()
		self.configParams.cb1c				= self.cb1c.isChecked()
		self.configParams.cb2a				= self.cb2a.isChecked()
		self.configParams.cb2b				= self.cb2b.isChecked()
		self.configParams.cb2c				= self.cb2c.isChecked()
		self.configParams.cb3a				= self.cb3a.isChecked()
		self.configParams.cb3b				= self.cb3b.isChecked()
		self.configParams.cb3c				= self.cb3c.isChecked()
		self.configParams.cb4a				= self.cb4a.isChecked()
		self.configParams.rb5b				= self.rb5b.isChecked()
		self.configParams.skipAtBow			= self.skipAtBow
		self.configParams.skipAtStern		= self.skipAtStern
		self.configParams.deckWidth			= self.deckWidth
		self.configParams.deckThrow			= self.deckThrow
		self.configParams.coachhouseRise	= self.coachhouseRise
		self.configParams.coachhouseIncline	= self.coachhouseIncline
	def onLastFile(self):
		self.configParams.result			= userLastFile
		self.transferConfigParams()
		self.close()
	def onSf(self):
		self.configParams.result			= userOK
		self.transferConfigParams()
		self.close()

# Class definitions

class HullCrossSection:
	"Holder of information pertaining to a cross section profile"
	#persistentInstance = ""
	#import copy
	def __init__(self,aSketch):
		self.sketch =		aSketch
		self.geometryCount = aSketch.GeometryCount
		self.geometryS =	None # geometry for starboard side
		self.geometryP =	None # geometry for port side
		self.geometryC =	None # geometry for complete hull (i.e. both halves as one)
		self.label =		aSketch.Label
		# next 2 lines due to mysterious label morphing routine of FreeCAD
		self.altLabel =		self.label.replace(" ", "_")
		self.altLabel =		self.altLabel.replace("-", "_")
		#
		self.xPos =			0.0 # normalise sketch to axis
		self.yPos =			aSketch.Placement.Base.y
		self.zPos =			aSketch.Placement.Base.z
		self.xMin =			infinity # will hold min X value in Sketch
		self.yMin =			infinity # will hold min Y value in Sketch
		self.xMax =			infinityNegative # will hold max X value in Sketch
		self.yMax =			infinityNegative # will hold max Y value in Sketch
		self.endPoint =		None # will be the 'top' point on the polyline
		self.key =			int(self.yPos)
		self.rotation =		aSketch.Placement.Rotation
		self.rotAngle =		aSketch.Placement.Rotation.Angle
		self.rotAxis =		aSketch.Placement.Rotation.Axis
		self.rotQ =			aSketch.Placement.Rotation.Q
		# following 4 statements seem necessary to pass the Rotation quad-value
		self.rotQ1 =		aSketch.Placement.Rotation.Q[0]
		self.rotQ2 =		aSketch.Placement.Rotation.Q[1]
		self.rotQ3 =		aSketch.Placement.Rotation.Q[2]
		self.rotQ4 =		aSketch.Placement.Rotation.Q[3]
		# set flags for either stemline or transom or suspected transom cross-section
		if eqRotation(self.rotation,yzPlane):
			# if we have the stemline then wait to give it the foremost placement
			FreeCAD.Console.PrintMessage("Stemline identified '" + self.label + "'\n")
			self.stemlineFlag = True
		else:
			self.stemlineFlag = False
		if eqRotation(self.rotation,xyPlane):
			# if we have the transom then wait to give it the aftmost placement
			FreeCAD.Console.PrintMessage("Transom identified '" + self.label + "'\n")
			self.transomFlag = True
		else:
			self.transomFlag = False
		if eqRotation(self.rotation,xzPlane):
			# the most numerous sketches will be the cross-sections, so don't flag it
			self.possibleTransomCS = False
		else:
			# it's not lying in any of the 3 planes so it's either an error
			# or it could be an inclined cross-section for the transom
			# (although there should only be one or none of these)
			# flag it as such and sort it out later once all the other
			# cross-sections are placed
			if not (self.stemlineFlag or self.transomFlag):
				FreeCAD.Console.PrintMessage("Possible Transom cross-section identified '" + self.label + "'\n")
				self.possibleTransomCS = True
		self.defineGeometries() # make S & P & C geometries from the geometry of the supplied Sketch
				
	def defineGeometries(self):
		# the supplied geometry is for the starboard side and is part of the user supplied Sketch
		# - make a direct copy for the starboard half-hull
		# - negate the X coordinates for the Port side
		# - append a negated reversed copy to each starboard piece for the complete hull
		self.geometryS =	list()
		self.geometryP =	list()
		self.geometryC =	list()
		#grab the endPoint which will be used for covering the half-hull model
		epX = max(self.sketch.Geometry[-1].StartPoint.x, self.sketch.Geometry[-1].EndPoint.x)
		epY = self.yPos
		epZ = max(self.sketch.Geometry[-1].StartPoint.y, self.sketch.Geometry[-1].EndPoint.y)
		self.endPoint = Base.Vector(epX,epY,epZ)
		# first pass through segment of sketch is to determine the min and max X & Y values
		for seg in self.sketch.Geometry:
			# determine the minimum X & Y values
			self.xMin = min(self.xMin, seg.StartPoint.x, seg.EndPoint.x)	
			self.yMin = min(self.yMin, seg.StartPoint.y, seg.EndPoint.y)
			self.xMax = max(self.xMax, seg.StartPoint.x, seg.EndPoint.x)	
			self.yMax = max(self.yMax, seg.StartPoint.y, seg.EndPoint.y)
		# second pass is to create the S, P and starboard side of the C geometries
		for seg in self.sketch.Geometry:
			# extract the X,Y,Z for start and end
			segStartX = seg.StartPoint.x
			segStartY = seg.StartPoint.y
			segStartZ = seg.StartPoint.z
			segEndX   = seg.EndPoint.x
			segEndY   = seg.EndPoint.y
			segEndZ   = seg.EndPoint.z
			absMinX   = abs(self.xMin)
			# normalise segments within drawing to X axis if not stemline
			if not self.stemlineFlag:
				if abs(segStartX) == absMinX:
					if 0<absMinX<1:
						segStartX = 0
					elif absMinX >= 1:
						FreeCAD.Console.PrintMessage("Move to Y axis of '" + self.label + "'\n")
						if segStartX<0:
							segStartX = segStartX + absMinX
						elif segEndX>0:
							segStartX = segStartX - absMinX
				if abs(segEndX) == absMinX:
					if 0<absMinX<1:
						segEndX = 0
					elif absMinX >= 1:
						FreeCAD.Console.PrintMessage("Move to Y axis of '" + self.label + "'\n")
						if segEndX<0:
							segEndX = segEndX + absMinX
						elif segEndX>0:
							segEndX = segEndX - absMinX
			# now create starboard, port, complete geometries
			self.geometryS.append(Part.Line(
				Base.Vector(segStartX, segStartY, segStartZ),
				Base.Vector(segEndX, segEndY, segEndZ)))
			if self.stemlineFlag:
				# stemline is on the YZ axis and is common to both half-hulls
				# so don't flip it's X coordinates
				multiplicand = 1
			else:
				multiplicand = -1
			self.geometryP.append(Part.Line(
				Base.Vector(segStartX*multiplicand, segStartY, segStartZ),
				Base.Vector(segEndX*multiplicand, segEndY, segEndZ)))
			# starboard geometry is first half of complete hull geometry
			self.geometryC.append(self.geometryS[-1])
		# third pass is to create the Complete geometry so the segments have
		# constant direction from starboard to port:
		# 1) reverse segment order of starboard side
		# 2 copy port side geometry
		segCnt = len(self.sketch.Geometry)
		completeGeometry = list()
		for i in range(segCnt):
			completeGeometry.append(self.reverseLineDirection(self.geometryS[segCnt-1-i]))	
		for i in range(segCnt):
			completeGeometry.append(self.reverseLineDirection(self.geometryP[i]))	
		self.geometryC = completeGeometry
		
	def reverseLineDirection(self,aLine):
		return Part.Line(aLine.EndPoint, aLine.StartPoint)

# Function definitions

def createBottle(aKeelFlag):
	# create a bottle around the hull
	bottleRadius = 250
	neckRadius = 75
	bottleBottom = -1000
	bottleTop = 400
	neckBottom = 800
	neckTop = 1150
	corkHeight = 100
	# get some dimensions for the plaque based on the size of the hull or half-hull
	minusX = crossSections[-2].yPos * 1.1
	plusX = (crossSections[0].yPos + crossSections[0].yMax) * 1.1
	minusY = 0; plusY = 0
	for cs in crossSections:
		#minusY = max(minusY, abs(cs.yMin))
		plusY = max(plusY, cs.yMax)
	minusY = plusY * -0.75
	plusY = plusY * 1.1
	#print minusX, " ", plusX, " ", minusY, " ", plusY
	
	bs0 = FreeCAD.ActiveDocument.addObject("Part::Vertex","Ring0")
	bs0.Label='Ring0'
	bs0.X=0.00
	bs0.Y=bottleBottom*0.97
	bs0.Z=0.00
	bs0.Placement = Base.Placement(	Base.Vector(0.00,0.00,0.00),
									Base.Rotation(0.00,0.00,0.00,1.00))
	bs0.ViewObject.Visibility=False
	#
	bs1 = FreeCAD.activeDocument().addObject('Sketcher::SketchObject','Ring1')
	bs1.addGeometry(Part.Circle(Base.Vector(0,0,0), Base.Vector(0,0,1), bottleRadius))
	bs1.Placement = FreeCAD.Placement(FreeCAD.Vector(0.0,bottleBottom,0.0),
									FreeCAD.Rotation(-0.707107,0.0,0.0,-0.707107))
	#
	bs2 = FreeCAD.activeDocument().addObject('Sketcher::SketchObject','Ring2')
	bs2.addGeometry(Part.Circle(Base.Vector(0,0,0), Base.Vector(0,0,1), bottleRadius))
	bs2.Placement = FreeCAD.Placement(FreeCAD.Vector(0.0,bottleTop,0.0),
									FreeCAD.Rotation(-0.707107,0.0,0.0,-0.707107))
	#
	bs3 = FreeCAD.activeDocument().addObject('Sketcher::SketchObject','Ring3')
	bs3.addGeometry(Part.Circle(Base.Vector(0,0,0), Base.Vector(0,0,1), neckRadius))
	bs3.Placement = FreeCAD.Placement(FreeCAD.Vector(0.0,neckBottom,0.0),
									FreeCAD.Rotation(-0.707107,0.0,0.0,-0.707107))
	#
	bs4 = FreeCAD.activeDocument().addObject('Sketcher::SketchObject','Ring4')
	bs4.addGeometry(Part.Circle(Base.Vector(0,0,0), Base.Vector(0,0,1), neckRadius))
	bs4.Placement = FreeCAD.Placement(FreeCAD.Vector(0.0,neckTop,0.0),
									FreeCAD.Rotation(-0.707107,0.0,0.0,-0.707107))
	#
	bot0 = FreeCAD.getDocument('hull_complete').addObject('Part::Loft','Loft0')
	bot0.Sections=[bs0, bs1,]
	bot0.Solid=False; bot0.Ruled=False; bot0.Closed=False
	#
	bot1 = FreeCAD.getDocument('hull_complete').addObject('Part::Loft','Loft1')
	bot1.Sections=[bs1, bs2,]
	bot1.Solid=False; bot1.Ruled=False; bot1.Closed=False
	#
	bot2 = FreeCAD.getDocument('hull_complete').addObject('Part::Loft','Loft2')
	bot2.Sections=[bs2, bs3,]
	bot2.Solid=False; bot2.Ruled=False; bot2.Closed=False
	#
	bot3 = FreeCAD.getDocument('hull_complete').addObject('Part::Loft','Loft3')
	bot3.Sections=[bs3,bs4,]
	bot3.Solid=False; bot3.Ruled=False; bot3.Closed=False
	#
	bottle = FreeCAD.activeDocument().addObject("Part::MultiFuse","Bottle")
	bottle.Shapes = [bot0,bot1,bot2,bot3,]
	bot1.ViewObject.Visibility=False
	bot2.ViewObject.Visibility=False
	bot3.ViewObject.Visibility=False
	#bottle.ViewObject.ShapeColor=Gui.ActiveDocument.Loft1.ShapeColor
	bottle.ViewObject.DisplayMode="Shaded"
	bottle.ViewObject.Transparency=80
	bottle.ViewObject.ShapeColor=(0.4, 0.8, 0.5, 0.0)
	FreeCAD.ActiveDocument.recompute()
	#
	cork = FreeCAD.ActiveDocument.addObject("Part::Cylinder","Cylinder")
	cork.Label = "Cork"
	cork.Radius = neckRadius-1
	cork.Height = corkHeight
	cork.Placement = FreeCAD.Placement(	FreeCAD.Vector(0.0,neckTop-(corkHeight/2),0.0),
										FreeCAD.Rotation(FreeCAD.Vector(1,0,0),-90))
	cork.ViewObject.ShapeColor=(0.78, 0.65, 0.35, 0.0)
	cork.ViewObject.DisplayMode = "Shaded"
	#
	FreeCADGui.activeDocument().activeView().viewAxometric()
	FreeCADGui.SendMsgToActiveView("ViewFit")
	FreeCAD.ActiveDocument.recompute()

def createBulkheads(aDictionary):
	userAction = None

	docKey = findOpenDocumentName(outputWorkspaceB)
	print docKey
	if docKey!=None:
		reply = QtGui.QMessageBox.question(None, "",
			"The previous 'Bulkheads' output file is still open, close it",
			QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, QtGui.QMessageBox.No)
		if reply == QtGui.QMessageBox.Yes:
			FreeCAD.closeDocument(outputWorkspaceB)
		else:
			userAction = userCancelled

	if userAction!=userCancelled:
		# bring in values from user dialogue
		coachhouseFlag			= aDictionary["coachhouseDeckBulkheadsFlag"]
		forwardBulkheadsToSkip	= aDictionary["forwardBulkheadsToSkip"]
		aftBulkheadsToSkip		= aDictionary["aftBulkheadsToSkip"]
		deckWidth				= aDictionary["deckWidth"]
		deckThrow				= aDictionary["deckThrow"]
		coachhouseRise			= aDictionary["coachhouseRise"]
		coachhouseIncline		= aDictionary["coachhouseIncline"]
		# set up output workspaces
		doc = FreeCAD.newDocument(outputWorkspaceB)
		FreeCAD.setActiveDocument(outputWorkspaceB)
		FreeCAD.ActiveDocument = FreeCAD.getDocument(outputWorkspaceB)
		FreeCADGui.ActiveDocument = FreeCADGui.getDocument(outputWorkspaceB)
	
		for i in range(forwardBulkheadsToSkip,len(crossSections)-aftBulkheadsToSkip):
			# add new bulkhead
			cs = crossSections[i]
			newSketch = FreeCAD.activeDocument().addObject('Sketcher::SketchObject',cs.label)
			#place bulkhead along keel
			newSketch.Placement = FreeCAD.Placement(
				FreeCAD.Vector(cs.xPos,cs.yPos,cs.zPos),
				FreeCAD.Rotation(cs.rotQ1,cs.rotQ2,cs.rotQ3,cs.rotQ4))
			# insert geometry segments from both half-hulls plus bulkhead into new Sketch
			for seg in cs.geometryC:
				newSketch.addGeometry(
					Part.Line(FreeCAD.Vector(seg.StartPoint.x,
						seg.StartPoint.y,	0),
					FreeCAD.Vector(seg.EndPoint.x,
						seg.EndPoint.y,0)))
				FreeCAD.ActiveDocument.recompute()
			xMin = cs.xMax * -1
			if coachhouseFlag:
				# user wants a coachhouse bulkhead
				newSketch.addGeometry(
					Part.Line(	FreeCAD.Vector(xMin, cs.yMax, 0),
								FreeCAD.Vector(xMin+deckWidth, cs.yMax+deckThrow, 0)))
				FreeCAD.ActiveDocument.recompute()
				newSketch.addGeometry(
					Part.Line(	FreeCAD.Vector(xMin+deckWidth, cs.yMax+deckThrow, 0),
								FreeCAD.Vector(xMin+deckWidth+coachhouseIncline, cs.yMax+deckThrow+coachhouseRise, 0)))
				FreeCAD.ActiveDocument.recompute()
				#
				newSketch.addGeometry(
					Part.Line(	FreeCAD.Vector(xMin+deckWidth+coachhouseIncline, cs.yMax+deckThrow+coachhouseRise, 0),
								FreeCAD.Vector(cs.xMax-deckWidth-coachhouseIncline, cs.yMax+deckThrow+coachhouseRise, 0)))
				FreeCAD.ActiveDocument.recompute()
				# focus at about -800
				#newSketch.addGeometry(
				#	Part.ArcOfCircle(Part.Circle(App.Vector(0.0,-190,0),App.Vector(0,0,1),240.0),1.078868,2.064096))
				newSketch.addGeometry(
					Part.Line(	FreeCAD.Vector(cs.xMax-deckWidth-coachhouseIncline, cs.yMax+deckThrow+coachhouseRise, 0),
								FreeCAD.Vector(cs.xMax-deckWidth, cs.yMax+deckThrow, 0)))
				FreeCAD.ActiveDocument.recompute()
				newSketch.addGeometry(
					Part.Line(	FreeCAD.Vector(cs.xMax-deckWidth, cs.yMax+deckThrow, 0),
								FreeCAD.Vector(cs.xMax, cs.yMax, 0)))
			else:
				# generate bulkheads for flush deck
				newSketch.addGeometry(
					Part.Line(	FreeCAD.Vector(xMin, cs.yMax, 0),
								FreeCAD.Vector(cs.xMax, cs.yMax, 0)))
				FreeCAD.ActiveDocument.recompute()
	
			newPad = App.activeDocument().addObject("PartDesign::Pad","Bulkhead")
			newPad.Sketch = newSketch
			newPad.Length = 1.0
			newPad.Sketch.ViewObject.hide()
			FreeCAD.ActiveDocument.recompute()
			
		FreeCADGui.SendMsgToActiveView("ViewFit")
		FreeCADGui.activeDocument().activeView().viewAxometric()

def createPlaque(aSideFlag, aKeelFlag):
	# create plaque to mount half-hull on
	# get some dimensions for the plaque based on the size of the hull or half-hull
	# note: the X & Y in this routine are to do with the XY of the plaque, not the Sketches
	woodColour = (0.53, 0.42, 0.23, 0.0)
	# find the overall max & min for X & Y
	minusY = crossSections[1].yMin; plusY = crossSections[1].yMin
	# get the plaque's Y min & max for the cross-sections (not the stemline or transom)
	for i in range(1,len(crossSections)-1):
		minusY = min(minusY, crossSections[i].yMin)
		plusY = max(plusY, crossSections[i].yMax)
	# now allow for the extent of the stemline
	minusY = min(minusY, crossSections[0].yMin)
	plusY = max(plusY, crossSections[0].yMax)
	# get extent of aftmost cross-section and add what the transom sticks out
	minusX = crossSections[-2].yPos + crossSections[-1].yMin - crossSections[-1].yMax
	# get the placement of the stemline and add what the stemline extends forward
	plusX = crossSections[0].yPos + crossSections[0].xMax
	# some scaling factors to provide margin space around the half-hull
	minusX = minusX * 1.1
	plusX = plusX * 1.1
	minusY = minusY * 1.5
	plusY = plusY * 1.25

	ps = FreeCAD.activeDocument().addObject('Sketcher::SketchObject','PlainPlaqueSketch')
	ps.Placement = FreeCAD.Placement(FreeCAD.Vector(0.0,0.0,0.0),
									FreeCAD.Rotation(0.5,0.5,0.5,0.5))		
	if aSideFlag==starboardSideFlag: ps.Placement.Base.x = -10
	ps.addGeometry(Part.Line(FreeCAD.Vector(minusX,plusY,0),FreeCAD.Vector(plusX,plusY,0)))
	ps.addGeometry(Part.Line(FreeCAD.Vector(plusX,plusY,0),FreeCAD.Vector(plusX,minusY,0)))
	ps.addGeometry(Part.Line(FreeCAD.Vector(plusX,minusY,0),FreeCAD.Vector(minusX,minusY,0)))
	ps.addGeometry(Part.Line(FreeCAD.Vector(minusX,minusY,0),FreeCAD.Vector(minusX,plusY,0)))
	#
	ps.addConstraint(Sketcher.Constraint('Coincident',0,2,1,1)) 
	ps.addConstraint(Sketcher.Constraint('Coincident',1,2,2,1)) 
	ps.addConstraint(Sketcher.Constraint('Coincident',2,2,3,1)) 
	ps.addConstraint(Sketcher.Constraint('Coincident',3,2,0,1)) 
	ps.addConstraint(Sketcher.Constraint('Horizontal',0)) 
	ps.addConstraint(Sketcher.Constraint('Horizontal',2)) 
	ps.addConstraint(Sketcher.Constraint('Vertical',1)) 
	ps.addConstraint(Sketcher.Constraint('Vertical',3)) 
	FreeCAD.ActiveDocument.recompute()
	#
	pad = FreeCAD.activeDocument().addObject("PartDesign::Pad","PlainPlaquePad")
	pad.Sketch = ps
	pad.Length = 10.0
	pad.Sketch.ViewObject.hide()
	pad.ViewObject.ShapeColor = woodColour
	FreeCAD.ActiveDocument.recompute()
	#
	cyl1 = FreeCAD.ActiveDocument.addObject("Part::Cylinder","Cylinder1")
	cyl1.Label = "Cylinder1"
	cyl1.Radius = plusY/5
	cyl1.Placement =	FreeCAD.Placement(FreeCAD.Vector(0,plusX,plusY),
						FreeCAD.Rotation(FreeCAD.Vector(0,1,0),90))
	if aSideFlag==starboardSideFlag: cyl1.Placement.Base.x = -10
	cut1 = FreeCAD.activeDocument().addObject("Part::Cut","Cut1")
	cut1.Base = App.activeDocument().PlainPlaquePad
	cut1.Tool = App.activeDocument().Cylinder1
	cut1.ViewObject.ShapeColor = woodColour
	FreeCAD.ActiveDocument.recompute()
	#
	cyl2 = FreeCAD.ActiveDocument.addObject("Part::Cylinder","Cylinder2")
	cyl2.Label = "Cylinder2"
	cyl2.Radius = plusY/5
	cyl2.Placement =	FreeCAD.Placement(FreeCAD.Vector(0,plusX,minusY),
						FreeCAD.Rotation(FreeCAD.Vector(0,1,0),90))
	if aSideFlag==starboardSideFlag: cyl2.Placement.Base.x = -10
	cut2 = FreeCAD.activeDocument().addObject("Part::Cut","Cut2")
	cut2.Base = App.activeDocument().Cut1
	cut2.Tool = App.activeDocument().Cylinder2
	cut2.ViewObject.ShapeColor = woodColour
	FreeCAD.ActiveDocument.recompute()
	#
	cyl3 = FreeCAD.ActiveDocument.addObject("Part::Cylinder","Cylinder3")
	cyl3.Label = "Cylinder3"
	cyl3.Radius = plusY/5
	cyl3.Placement =	FreeCAD.Placement(FreeCAD.Vector(0,minusX,minusY),
						FreeCAD.Rotation(FreeCAD.Vector(0,1,0),90))
	if aSideFlag==starboardSideFlag: cyl3.Placement.Base.x = -10
	cut3 = FreeCAD.activeDocument().addObject("Part::Cut","Cut3")
	cut3.Base = App.activeDocument().Cut2
	cut3.Tool = App.activeDocument().Cylinder3
	cut3.ViewObject.ShapeColor = woodColour
	FreeCAD.ActiveDocument.recompute()
	#
	cyl4 = FreeCAD.ActiveDocument.addObject("Part::Cylinder","Cylinder4")
	cyl4.Label = "Cylinder4"
	cyl4.Radius = plusY/5
	cyl4.Placement =	FreeCAD.Placement(FreeCAD.Vector(0,minusX,plusY),
						FreeCAD.Rotation(FreeCAD.Vector(0,1,0),90))
	if aSideFlag==starboardSideFlag: cyl4.Placement.Base.x = -10
	cut4 = FreeCAD.activeDocument().addObject("Part::Cut","Cut4")
	cut4.Base = App.activeDocument().Cut3
	cut4.Tool = App.activeDocument().Cylinder4
	cut4.ViewObject.ShapeColor = woodColour
	FreeCAD.ActiveDocument.recompute()
	#
	cham = FreeCAD.ActiveDocument.addObject("Part::Chamfer","Plaque")
	cham.Base = FreeCAD.ActiveDocument.Cut4
	edges = []
	if aSideFlag == starboardSideFlag:
		edges.append((3,3.00,3.00)); edges.append((13,3.00,3.00)); edges.append((14,3.00,3.00)); edges.append((15,3.00,3.00))
		edges.append((16,3.00,3.00)); edges.append((17,3.00,3.00)); edges.append((18,3.00,3.00)); edges.append((19,3.00,3.00))
	else:
		edges.append((1,3.00,3.00)); edges.append((5,3.00,3.00)); edges.append((6,3.00,3.00)); edges.append((7,3.00,3.00))
		edges.append((8,3.00,3.00)); edges.append((9,3.00,3.00)); edges.append((10,3.00,3.00)); edges.append((11,3.00,3.00))
	cham.Edges = edges
	cham.Base.ViewObject.Visibility = False
	cham.ViewObject.ShapeColor = woodColour

	createPlaqueCover()
	
	if aSideFlag == starboardSideFlag:
		FreeCADGui.activeDocument().activeView().viewRight()
	else:
		FreeCADGui.activeDocument().activeView().viewLeft()
	FreeCADGui.SendMsgToActiveView("ViewFit")
	FreeCAD.ActiveDocument.recompute()

def createPlaqueCover():
	# get upper bow point UBP
	# get upper stern point USP
	# get number of sections NS
	# divide the line UBP-USP into NS pieces
	#---
	# for each sketch, get the top point TP
	# make segments between each consecutive points
	#---
	# make RuledSurface between corresponding segments
	FreeCAD.zot = crossSections
	if len(crossSections)<5:
		FreeCAD.Console.PrintMessage("Insufficient cross-sections for plaque cover")
	else:
		# the number of cross-section and therefore chines determines how many
		# segments will be in the cover for the half-hull model
		segmentCount = len(crossSections)-2
		# determine endpoints for the line segments along the plaque
		bowPoint = Base.Vector(0,
			max(crossSections[0].geometryS[-1].StartPoint.x, crossSections[0].geometryS[-1].EndPoint.x),
			max(crossSections[0].geometryS[-1].StartPoint.y, crossSections[0].geometryS[-1].EndPoint.y))
		sternPoint = Base.Vector(0,
			crossSections[-2].yPos,
			max(crossSections[-2].geometryS[-1].StartPoint.y, crossSections[-2].geometryS[-1].EndPoint.y))
		plaquePoints = [bowPoint, sternPoint]
		lineAlongPlaqueToSplit = Part.Line(bowPoint,sternPoint)
		lapSection = lineAlongPlaqueToSplit.length()/segmentCount

		# build a list of the points that start/end the segments
		pointList = []
		pointList.append(lineAlongPlaqueToSplit.StartPoint)
		print lineAlongPlaqueToSplit.StartPoint
		for i in range(1, segmentCount):
			pointList.append(lineAlongPlaqueToSplit.value((i)*lapSection))
			print lineAlongPlaqueToSplit.value((i)*lapSection)
		pointList.append(lineAlongPlaqueToSplit.EndPoint)
		print lineAlongPlaqueToSplit.EndPoint
		print
		# iterate the list of points from the first segment to the last
		# do stemline first as it is in the YZ plane (cross-sections are in the XZ plane)
		a=FreeCAD.ActiveDocument.addObject('Part::Line', 'cs1k')
		a.X1=0; a.Y1=pointList[0].y; a.Z1=pointList[0].z
		a.X2=0; a.Y2=pointList[1].y; a.Z2=pointList[1].z
		b=FreeCAD.ActiveDocument.addObject('Part::Line', 'cs1h')
		# B1 wrong
		b.X1=0; b.Y1=crossSections[0].endPoint.x; b.Z1=crossSections[0].endPoint.z
		b.X2=crossSections[1].endPoint.x; b.Y2=crossSections[1].endPoint.y; b.Z2=crossSections[1].endPoint.z
		FreeCAD.ActiveDocument.addObject('Part::RuledSurface', 'coverSeg1')
		FreeCAD.ActiveDocument.ActiveObject.Curve1=(a,[''])
		FreeCAD.ActiveDocument.ActiveObject.Curve2=(b,[''])
		FreeCAD.ActiveDocument.recompute()
		# now do cross=sections
		for i in range(1, segmentCount):
			a=FreeCAD.ActiveDocument.addObject('Part::Line', 'cs'+str(i+1)+'k')
			a.X1=0; a.Y1=pointList[i].y; a.Z1=pointList[i].z
			a.X2=0; a.Y2=pointList[i+1].y;   a.Z2=pointList[i+1].z
			b=FreeCAD.ActiveDocument.addObject('Part::Line', 'cs'+str(i+1)+'h')
			b.X1=crossSections[i].endPoint.x;   b.Y1=crossSections[i].endPoint.y;   b.Z1=crossSections[i-1].endPoint.z
			b.X2=crossSections[i+1].endPoint.x; b.Y2=crossSections[i+1].endPoint.y; b.Z2=crossSections[i].endPoint.z
			FreeCAD.ActiveDocument.addObject('Part::RuledSurface', 'coverSeg'+str(i+1))
			FreeCAD.ActiveDocument.ActiveObject.Curve1=(a,[''])
			FreeCAD.ActiveDocument.ActiveObject.Curve2=(b,[''])
			FreeCAD.ActiveDocument.recompute()
				
		FreeCAD.ActiveDocument.recompute()

def displayChinePanels(crossSectionLabelA, crossSectionLabelB, aSideFlag):
	# accept 2 sketch labels and generate a ruled surface between them
	#print sketchLabelA
	placeholder = "_"
	csA = FreeCAD.ActiveDocument.getObjectsByLabel(crossSectionLabelA)[0]
	csB = FreeCAD.ActiveDocument.getObjectsByLabel(crossSectionLabelB)[0]
	labelA = csA.Label
	labelB = csB.Label
	lblA = labelA.split('_', 1)[0]
	lblB = labelB.split('_', 1)[0]
	if aSideFlag == portSideFlag:
		sideText = " Port"
	elif aSideFlag == starboardSideFlag:
		sideText = " Starboard"
	else:
		sideText = ""
	#print str(sketchLabelA) + " " + str(sketchLabelB)
	FreeCAD.ActiveDocument.addObject('Part::RuledSurface', placeholder+lblA+placeholder+lblB+sideText)
	csObjectA = FreeCAD.ActiveDocument.getObjectsByLabel(crossSectionLabelA)[0]
	csObjectB = FreeCAD.ActiveDocument.getObjectsByLabel(crossSectionLabelB)[0]
	#print "> " + str(sketchObjectA) + " " + str(sketchObjectB)
	FreeCAD.ActiveDocument.ActiveObject.Curve1=(csObjectA,[''])
	FreeCAD.ActiveDocument.ActiveObject.Curve2=(csObjectB,[''])

def displayCompleteHull():
	userAction = None

	docKey = findOpenDocumentName(outputWorkspaceC)
	if docKey!=None:
		reply = QtGui.QMessageBox.question(None, "",
			"The previous 'Complete Hull' output file is still open, close it",
			QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, QtGui.QMessageBox.No)
		if reply == QtGui.QMessageBox.Yes:
			FreeCAD.closeDocument(outputWorkspaceC)
		else:
			userAction = userCancelled

	if userAction!=userCancelled:
		doc = App.newDocument(outputWorkspaceC)
		App.setActiveDocument(outputWorkspaceC)
		App.ActiveDocument=App.getDocument(outputWorkspaceC)
		docComplete = App.ActiveDocument
		Gui.ActiveDocument=Gui.getDocument(outputWorkspaceC)
	
		for cs in crossSections:
			# add new Sketch object
			newSketch = FreeCAD.activeDocument().addObject('Sketcher::SketchObject',cs.label)
			newSketch.Placement = FreeCAD.Placement(
				FreeCAD.Vector(cs.xPos,cs.yPos,cs.zPos),
				FreeCAD.Rotation(cs.rotQ1,cs.rotQ2,cs.rotQ3,cs.rotQ4))
			Gui.activeDocument().setEdit(cs.altLabel)
			# insert geometry segments from both half-hulls into new Sketch
			for seg in cs.geometryC:
				newSketch.addGeometry(
					Part.Line(FreeCAD.Vector(seg.StartPoint.x,
						seg.StartPoint.y,	0),
					FreeCAD.Vector(seg.EndPoint.x,
						seg.EndPoint.y,0)))
			Gui.getDocument(doc.Label).resetEdit()
			#print obj.Name + " " + obj.Label
	
		FreeCAD.cs = crossSections # debug statement
		for i in range(0, len(crossSections)-1):
			displayChinePanels(crossSections[i].altLabel,crossSections[i+1].altLabel, "C")
		# now draw the bow sections going to the stemline
		#displayChinePanels(crossSections[].altLabel,crossSections[i+1].altLabel, "C")
		FreeCAD.activeDocument().recompute()
		FreeCADGui.SendMsgToActiveView("ViewFit")
		FreeCADGui.activeDocument().activeView().viewAxometric()

def displayHalfHull(aSideFlag):
	userAction = None
	# create output workspace for one side
	if aSideFlag == starboardSideFlag:
		query = "The previous 'Port Half-Hull' output file is still open, close it"
		selectedOutputWorkspace = outputWorkspaceS
		outputWorkspace = outputWorkspaceS
		sideFlag = starboardSideFlag
	else:
		query = "The previous 'Starboard Half-Hull' output file is still open, close it"
		selectedOutputWorkspace = outputWorkspaceP
		outputWorkspace = outputWorkspaceP
		sideFlag = portSideFlag

	docKey = findOpenDocumentName(selectedOutputWorkspace)
	if docKey==None:
		doc = FreeCAD.newDocument(selectedOutputWorkspace)
	else:
		reply = QtGui.QMessageBox.question(None, "",
			query,
			QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, QtGui.QMessageBox.No)
		if reply == QtGui.QMessageBox.Yes:
			FreeCAD.closeDocument(selectedOutputWorkspace)
			doc = FreeCAD.newDocument(selectedOutputWorkspace)
		else:
			userAction = userCancelled

	if userAction!=userCancelled:
		FreeCAD.setActiveDocument(outputWorkspace)
		FreeCAD.ActiveDocument=FreeCAD.getDocument(outputWorkspace)
		FreeCADGui.ActiveDocument=FreeCADGui.getDocument(outputWorkspace)
		
		# place the segments in the document
		for cs in crossSections:
			# add new Sketch object
			FreeCAD.activeDocument().addObject('Sketcher::SketchObject',cs.label)
			newSketch = FreeCAD.ActiveDocument.getObject(cs.altLabel)
			newSketch.Placement = FreeCAD.Placement(
				FreeCAD.Vector(cs.xPos,cs.yPos,cs.zPos),
				FreeCAD.Rotation(cs.rotQ1,cs.rotQ2,cs.rotQ3,cs.rotQ4))
			Gui.activeDocument().setEdit(cs.altLabel)
			# insert geometry segments into new Sketch
			if sideFlag == "S":
				geom = cs.geometryS
			else:
				geom = cs.geometryP
			for seg in geom:
				newSketch.addGeometry(
					Part.Line(FreeCAD.Vector(seg.StartPoint.x,
						seg.StartPoint.y,	0),
					FreeCAD.Vector(seg.EndPoint.x,
						seg.EndPoint.y,0)))
			Gui.getDocument(doc.Label).resetEdit()
		
		for i in range(0, len(crossSections)-1):
			displayChinePanels(crossSections[i].altLabel,crossSections[i+1].altLabel, sideFlag)
	
		FreeCAD.activeDocument().recompute()
		FreeCADGui.SendMsgToActiveView("ViewFit")
		FreeCADGui.activeDocument().activeView().viewAxometric()

def eqRotation(rotationA, rotationB, eps=0.0001):
	"takes 2 Rotations and compares for equality"
	eqFlag = True
	for i in range(0, 4):
		#print str(rotationA.Q[i]) + "#" + str(rotationB.Q[i])
		if rotationA.Q[i] == 0:
			if rotationB.Q[i] <> 0:
				eqFlag = False
		elif abs(abs(rotationA.Q[i])-abs(rotationB.Q[i]))/abs(rotationA.Q[i]) > eps:
			eqFlag = False
	return eqFlag

def resetSketchesVisibility():
	# set Visibility on all Sketches to False
	openWindows = list()
	if starboardHHFlag:
		openWindows.append(outputWorkspaceS)
	if portHHFlag:
		openWindows.append(outputWorkspaceP)
	if completeHullFlag:
		openWindows.append(outputWorkspaceC)
	#if fullHullBulkheadsFlag:
		#openWindows.append(outputWorkspaceB)

	for ws in openWindows:
		FreeCAD.setActiveDocument(ws)
		FreeCAD.ActiveDocument=FreeCAD.getDocument(ws)
		FreeCADGui.ActiveDocument=FreeCADGui.getDocument(ws)
		for obj in FreeCAD.ActiveDocument.Objects:
			if obj.TypeId == 'Sketcher::SketchObject':
				vo = FreeCADGui.ActiveDocument.getObject(obj.Name)
				vo.Visibility=False

def sortOutFilesAndDocuments():
	global keepSourceOpenFlag
	# this routine uses the following variables from the main handler:
	# docSrc (returns)
	# fileName (reads)
	# keepSourceOpenFlag (global, writes)
	# it determines if the 'user selected input document' is already open,
	# if it is then is is made the ActiveDocument, otherwise it is
	# Opened which also sets it to the ActiveDocument
	if len(FreeCAD.listDocuments())==0:
		# no documents open
		return FreeCAD.open(fileName)
	else:
		# some document(s) open so check if 'user selected input document' is already open
		"""docKey = None
		for key in FreeCAD.listDocuments():
			if FreeCAD.listDocuments()[key].FileName==fileName:
				docKey = key"""
		docKey = findOpenDocumentFileSpec(fileName)
		if docKey==None:
			# 'user selected input document' is not open
			return FreeCAD.open(fileName)
		else:
			# 'user selected input document' is among open documents
			# set the 'user selected input document' to the active document (in case it isn't)
			FreeCAD.setActiveDocument(docKey)
			# user started with 'user selected input document' open, so this flag will allow
			# us to leave it open when we finish
			keepSourceOpenFlag = True
			return FreeCAD.ActiveDocument

def findOpenDocumentFileSpec(aDocumentFileSpec):
	# check if supplied document is already open
	# return document name or None
	docKey = None
	for key in FreeCAD.listDocuments():
		if FreeCAD.listDocuments()[key].FileName==aDocumentFileSpec:
			docKey = key
	return docKey
	
def findOpenDocumentName(aDocumentName):
	# check if supplied document is already open
	# return document name or None
	docKey = None
	for key in FreeCAD.listDocuments():
		if FreeCAD.listDocuments()[key].Name==aDocumentName:
			docKey = key
	return docKey
	
# Constant definitions
outputWorkspaceS	= "hull_starboard"
outputWorkspaceP	= "hull_port"
outputWorkspaceC	= "hull_complete"
outputWorkspaceB	= "bulkheads"
xyPlane				= Base.Rotation(0.0, 0.0, 0.0, 1.0) # transom
xzPlane				= Base.Rotation(0.7071067811865475, 0.0, 0.0, 0.7071067811865476) # cross section profile
yzPlane				= Base.Rotation(0.5,0.5,0.5,0.5) # stemline
infinity			= +99999999999999.9 # will hold min Y value in Sketch
infinityNegative	= -99999999999999.9 # will hold max X value in Sketch
global starboardSideFlag, portSideFlag, userCancelled, userLastFile, userOK
starboardSideFlag	= "S"
portSideFlag		= "F"
completeSidesFlag	= "C"
userCancelled		= "Cancelled"
userLastFile		= "Last File"
userOK				= "OK"
defaultDir = FreeCAD.ConfigGet("UserHomePath")

# code ***********************************************************************************
allCrossSections	= {}
crossSections		= list()
docSrc				= None
global keepSourceOpenFlag
keepSourceOpenFlag	= False
starboardHHFlag		= False; starboardHHPlaqueFlag	= False; starboardHHPKeelFlag		= False
portHHFlag			= False; portHHPlaqueFlag		= False; portHHPKeelFlag			= False
completeHullFlag	= False; completeHullBottleFlag	= False; completeHullKeelFlag		= False
bulkheadsFlag		= False; flushDeckBulkheadsFlag	= True; coachhouseDeckBulkheadsFlag	= True

form = GetConfigParams()
form.exec_()
configParams = form.configParams

if configParams.result==userLastFile:
	if hasattr(FreeCAD,"MacroHalfHullConfigParams"):
		# global in FreeCAD exists so use the parameters stored there
		#if type(FreeCAD.MacroHalfHullConfigParams)=='GetConfigParams':
		configParams = FreeCAD.MacroHalfHullConfigParams
		configParams.result = userLastFile
	else:
		# user requested to re-use last file but there isn't one
		# so reset the choice to pick a file
		configParams.result = userOK
			
if configParams.result != userCancelled:
	# transfer results to control flags
	starboardHHFlag				= configParams.cb1a
	starboardHHPlaqueFlag		= configParams.cb1b
	starboardHHPKeelFlag		= configParams.cb1c
	portHHFlag					= configParams.cb2a
	portHHPlaqueFlag			= configParams.cb2b
	portHHPKeelFlag				= configParams.cb2c
	completeHullFlag			= configParams.cb3a
	completeHullBottleFlag		= configParams.cb3b
	completeHullKeelFlag		= configParams.cb3c
	bulkheadsFlag				= configParams.cb4a
	# transfer bulkhead parameters
	bulkheadParams = {}
	bulkheadParams["coachhouseDeckBulkheadsFlag"]	= configParams.rb5b
	bulkheadParams["forwardBulkheadsToSkip"]		= int(configParams.skipAtBow)
	bulkheadParams["aftBulkheadsToSkip"]			= int(configParams.skipAtStern)
	bulkheadParams["deckWidth"]						= float(configParams.deckWidth.text())
	bulkheadParams["deckThrow"]						= float(configParams.deckThrow.text())
	bulkheadParams["coachhouseRise"]				= float(configParams.coachhouseRise.text())
	bulkheadParams["coachhouseIncline"]				= float(configParams.coachhouseIncline.text())
		
	defaultDir = expanduser("~")
	defaultDir = "/Data Pool/Coding/FreeCAD/work items/hull mirroring/half-hull/"
	if configParams.result==userOK:
		# user wants to select file
		fileName = QtGui.QFileDialog.getOpenFileName(dir=defaultDir, caption = "Select a 'Hull Profile' to Load", filter="*.FCStd")[0]
		configParams.documentFileName = fileName
	else:
		# file is coming out of FreeCAD global
		fileName = FreeCAD.MacroHalfHullConfigParams.documentFileName

	if len(fileName) != 0:
		docSrc = sortOutFilesAndDocuments()
		# read all the objects, saving the Sketcher objects
		for obj in FreeCAD.ActiveDocument.Objects:
			if obj.TypeId == 'Sketcher::SketchObject':
				# ignore anything except Sketches
				newObj = HullCrossSection(obj)
				if newObj.transomFlag:
					# if we have the transom sketch then wait to give
					# it a placement further aft than anything else
					transomObject = newObj
				elif newObj.stemlineFlag:
					# if we have the stemline sketch then wait
					# to give it the first placement
					stemlineObject = newObj
					stemlineSegmentCount = stemlineObject.sketch.GeometryCount
				else:
					# must be a regular cross section profile
					# so add object to our collection
					allCrossSections[newObj.key] = newObj
					#maxY = max(maxY, newObj.yPos)

		# discard sketches with wrong number of points
		# i.e. cross-sections that have a different number
		# of points than the stemline
		for key, cs in allCrossSections.items():
			if cs.geometryCount <> stemlineSegmentCount:
				FreeCAD.Console.PrintMessage("Discard for wrong number of segments for '" + str(allCrossSections[key].label) + "'\n")
				del allCrossSections[key]
		
		# construct a list with cross sections in order from stemline to transom
		crossSections.append(stemlineObject)
		for sk in sorted(allCrossSections.keys(), reverse=True):
			crossSections.append(allCrossSections[sk])
		transomObject.zPos = crossSections[-1].zPos+crossSections[-1].yMax
		crossSections.append(transomObject)

		FreeCAD.cs = crossSections # debug statement
		
		# now set the transom elevation at the top of the last cross-section
		crossSections[-1].zPos = crossSections[-2].zPos + crossSections[-2].yMax
		
		FreeCAD.cs = crossSections # debug statement

		# depending on user flags, invoke appropriate modules
		if starboardHHFlag:			displayHalfHull(starboardSideFlag)
		if starboardHHPlaqueFlag:	createPlaque(starboardSideFlag, starboardHHPKeelFlag)
		if portHHFlag:				displayHalfHull(portSideFlag)
		if portHHPlaqueFlag:		createPlaque(portSideFlag, portHHPKeelFlag)
		if completeHullFlag:		displayCompleteHull()
		if completeHullBottleFlag:	createBottle(completeHullKeelFlag)
		if bulkheadsFlag:			createBulkheads(bulkheadParams)

		# save Config Params to FreeCAD global in case user wants to use it next run
		FreeCAD.MacroHalfHullConfigParams = configParams
		
		if not keepSourceOpenFlag:
			FreeCAD.closeDocument(docSrc.Name)
		resetSketchesVisibility()