#
#
# 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