Code: Select all
"""Create rectangular to circular metal transitions
Name: squaretocircle.py
Author: Ed Williams (@edwilliams16)
Copyright: 2022
Licence: CC BY-NC-ND 4.0 IT
"""
import numpy as np
import Part, Mesh, MeshPart
import math
import flatmesh
from PySide import QtGui, QtCore
import Spreadsheet
# UI Class definitions
class TransitionGuiClass(QtGui.QDialog):
""""""
def __init__(self):
super(TransitionGuiClass, self).__init__()
self.initUI()
def initUI(self):
self.inches = True
self.result = userCancelled
# create our window
# define window xLoc,yLoc,xDim,yDim
self.setGeometry( 250, 250, 400, 350)
self.setWindowTitle("Rectangle to circle transition")
self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
# create some Labels
self.labelRectangle = QtGui.QLabel(" Rectangle", self)
self.labelRectangle.setFont(QtGui.QFont("Times", 16, QtGui.QFont.Bold))
self.labelRectangle.move(20, 20)
self.labelLengthX = QtGui.QLabel("LengthX", self)
self.labelLengthX.move(20, 55)
self.labelLengthX.setFont('Courier') # set to a non-proportional font
self.labelLengthY = QtGui.QLabel("LengthY", self)
self.labelLengthY.move(20, 80)
self.labelLengthY.setFont('Courier')
self.labelCircle = QtGui.QLabel(" Circle", self)
self.labelCircle.setFont(QtGui.QFont("Times", 16, QtGui.QFont.Bold))
self.labelCircle.move(20, 110)
self.labelRadius = QtGui.QLabel("Radius", self)
self.labelRadius.move(20, 140)
self.labelRadius.setFont('Courier')
self.labelCenterXYZ = QtGui.QLabel("CenterXYZ", self)
self.labelCenterXYZ.move(20, 170)
self.labelCenterXYZ.setFont('Courier')
self.labelRotAxis = QtGui.QLabel("Rot Axis", self)
self.labelRotAxis.move(20, 200)
self.labelRotAxis.setFont('Courier')
self.labelRotAngle = QtGui.QLabel("Rot Angle", self)
self.labelRotAngle.move(20, 230)
self.labelRotAngle.setFont('Courier')
self.noSegments = QtGui.QLabel("Number of Segments", self)
self.noSegments.move(20, 260)
self.noSegments.setFont('Courier')
# set up lists for scale pop-up
self.popupItems1 = ("inches","mm")
# set up pop-up menu
self.popup1 = QtGui.QComboBox(self)
self.popup1.addItems(self.popupItems1)
self.popup1.setCurrentIndex(self.popupItems1.index("inches"))
self.popup1.activated[str].connect(self.onPopup1)
self.popup1.move(210, 20)
# numeric input fields
self.lengthX = QtGui.QLineEdit(self)
self.lengthX.setValidator(QtGui.QDoubleValidator())
self.lengthX.setText("40.00")
self.lengthX.setFixedWidth(70)
self.lengthX.move(100, 50)
self.lengthY = QtGui.QLineEdit(self)
self.lengthY.setValidator(QtGui.QDoubleValidator())
self.lengthY.setText("45.00")
self.lengthY.setFixedWidth(70)
self.lengthY.move(100, 75)
self.radius = QtGui.QLineEdit(self)
self.radius.setValidator(QtGui.QDoubleValidator())
self.radius.setText("15.00")
self.radius.setFixedWidth(70)
self.radius.move(100, 135)
self.centerX = QtGui.QLineEdit(self)
self.centerX.setValidator(QtGui.QDoubleValidator())
self.centerX.setText("0.0")
self.centerX.setFixedWidth(70)
self.centerX.move(100, 165)
self.centerY = QtGui.QLineEdit(self)
self.centerY.setValidator(QtGui.QDoubleValidator())
self.centerY.setText("0.0")
self.centerY.setFixedWidth(70)
self.centerY.move(180, 165)
self.centerZ = QtGui.QLineEdit(self)
self.centerZ.setValidator(QtGui.QDoubleValidator())
self.centerZ.setText("15.00")
self.centerZ.setFixedWidth(70)
self.centerZ.move(260, 165)
self.axisX = QtGui.QLineEdit(self)
self.axisX.setValidator(QtGui.QDoubleValidator())
self.axisX.setText("1.0")
self.axisX.setFixedWidth(70)
self.axisX.move(100, 195)
self.axisY = QtGui.QLineEdit(self)
self.axisY.setValidator(QtGui.QDoubleValidator())
self.axisY.setText("0.0")
self.axisY.setFixedWidth(70)
self.axisY.move(180, 195)
self.axisZ = QtGui.QLineEdit(self)
self.axisZ.setValidator(QtGui.QDoubleValidator())
self.axisZ.setText("0.0")
self.axisZ.setFixedWidth(70)
self.axisZ.move(260, 195)
self.angle = QtGui.QLineEdit(self)
validator = QtGui.QDoubleValidator()
#validator.setRange(-90, 90, 1)
self.angle.setValidator(validator)
self.angle.setText("0.0")
self.angle.setFixedWidth(70)
self.angle.move(100, 225)
self.segments = QtGui.QLineEdit(self)
self.segments.setText('8')
self.segments.setValidator(QtGui.QIntValidator())
self.segments.setFixedWidth(30)
self.segments.move(180, 255)
# cancel button
cancelButton = QtGui.QPushButton('Cancel', self)
cancelButton.clicked.connect(self.onCancel)
cancelButton.setAutoDefault(True)
cancelButton.move(150, 280)
# OK button
okButton = QtGui.QPushButton('OK', self)
okButton.clicked.connect(self.onOk)
okButton.move(260, 280)
# now make the window visible
self.show()
#
def onPopup1(self, selectedText):
if selectedText == 'mm':
self.inches = False
else:
self.inches = True
def onCancel(self):
self.result = userCancelled
self.close()
def onOk(self):
self.result = userOK
self.close()
# Constant definitions
userCancelled = "Cancelled"
userOK = "OK"
# code ***********************************************************************************
def flattenMesh(obj):
#https://github.com/FreeCAD/FreeCAD/blob/0d0bd1168bb96ded8dfe5ab3ca60588dec5d2949/src/Mod/MeshPart/Gui/MeshFlatteningCommand.py
points = np.array([[i.x, i.y, i.z] for i in obj.Mesh.Points])
faces = np.array([list(i) for i in obj.Mesh.Topology[1]])
flattener = flatmesh.FaceUnwrapper(points, faces)
flattener.findFlatNodes(5, 0.95)
boundaries = flattener.getFlatBoundaryNodes()
wires = []
for edge in boundaries:
pi = Part.makePolygon([App.Vector(*node) for node in edge])
wires.append(Part.Wire(pi))
return wires
def makeTemplate(form):
def printVertices(vs):
App.Console.PrintMessage('Template Vertices\n')
if useInches:
App.Console.PrintMessage('Inch units\n')
else:
App.Console.PrintMessage('mm units\n')
for i, v in enumerate(vs):
App.Console.PrintMessage(f'{i}: ({v.x:.2f},{v.y:.2f})\n')
def spreadsheetVertices(vs):
if useInches:
ustr = ' in'
scale = 25.4
else:
ustr = ' mm'
scale = 1.0
sheet = App.ActiveDocument.addObject("Spreadsheet::Sheet","TransitionSheet")
sheet.set('A1','X'+ustr)
sheet.set('B1','Y'+ustr)
sheet.set('C1','LengthX'+ustr)
sheet.set('D1', f'{rectX/scale}')
sheet.set('C2','LengthY'+ustr)
sheet.set('D2', f'{rectY/scale}')
sheet.set('C3','Radius nom/adj'+ustr)
sheet.set('D3', f'{radius/scale/adjust}')
sheet.set('E3', f'{radius/scale}')
sheet.set('C4', 'CenterXYZ' + ustr)
sheet.set('D4', f'{xoffset/scale}')
sheet.set('E4', f'{yoffset/scale}')
sheet.set('F4', f'{height/scale}')
sheet.set('C5', 'Rot AxisXYZ')
sheet.set('D5', f'{circleRotationAxis.x}')
sheet.set('E5', f'{circleRotationAxis.y}')
sheet.set('F5', f'{circleRotationAxis.z}')
sheet.set('C6', 'Rot Angle (deg)')
sheet.set('D6', f'{circleRotationAngle}')
sheet.set('C7', 'Number of Segments')
sheet.set('D7', f'{nSides}')
for i, v in enumerate(vs):
sheet.set(f'A{i+2}', f'{v.x:.2f}')
sheet.set(f'B{i+2}', f'{v.y:.2f}')
#end spreadsheetVertices
if form.result==userOK:
useInches = form.inches
if useInches:
scale = 25.4
else:
scale = 1.
rectX = float(form.lengthX.text()) * scale
rectY = float(form.lengthY.text()) * scale
height = float(form.centerZ.text()) * scale
xoffset = float(form.centerX.text()) * scale
yoffset = float(form.centerY.text()) * scale
radius = float(form.radius.text()) * scale
circleRotationAngle = float(form.angle.text())
circleRotationAxis = V3(float(form.axisX.text()), float(form.axisY.text()), float(form.axisZ.text()))
nSides = int(form.segments.text())
adjust = (math.pi/(4 * nSides * math.sin(math.pi/(4 * nSides)))) #correct perimeter discretization
radius *= adjust
####
eps =1e-6
sV = [0.5*V3(*v) for v in [(rectX,0,0), (rectX, rectY, 0), (0, rectY, 0),(-rectX,rectY,0),
(-rectX, 0, 0), (-rectX, -rectY, 0), (0, -rectY, 0), (rectX,-rectY, 0), (rectX*(1-eps),0,0)]]
# sv[3] sV[2] sV[1]
#
# sv[4] O sV[0], sv[8] seam
#
# sv[5] sV[6] sV[7]
# 4N-sided quarter circle
thetas = [2 * math.pi * j/(4 * nSides) for j in range(4*nSides + 1)]
thetas[-1] -= eps #seam
#create and locate circle
cV2 = [V3(radius * math.cos(t), radius * math.sin(t), 0) for t in thetas]
pl = App.Placement(V3(xoffset, yoffset, height), App.Rotation(circleRotationAxis, circleRotationAngle))
cV = [pl.multVec(v) for v in cV2]
faceList = []
for k in range(4):
face = Part.Face(Part.makePolygon([sV[2*k], cV[nSides*k], sV[2*k + 1], sV[2*k]]))
#Part.show(face)
faceList.append(face)
face = Part.Face(Part.makePolygon([sV[2*k + 1], cV[nSides*(k + 1)], sV[2*k + 2], sV[2*k + 1]]))
#Part.show(face)
faceList.append(face)
for k in range(4):
for j in range(nSides):
f = Part.Face(Part.makePolygon([sV[2*k +1], cV[nSides*k +j], cV[nSides*k + j+1], sV[2*k +1]]))
#Part.show(f)
faceList.append(f)
shell = Part.Shell(faceList)
qshell = Part.show(shell, "QuarterShell")
qshell.Visibility = False
mesh = doc.addObject('Mesh::Feature','Mesh')
mesh.Mesh=MeshPart.meshFromShape(Shape=shell, LinearDeflection=0.1, AngularDeflection=0.523599, Relative=False)
mesh.Label="QuarterShell (Meshed)"
wires = flattenMesh(mesh)
doc.removeObject(mesh.Name)
template = Part.show(wires[0], "Template") #only one outline here
#change origin and orientation centering bottom edge along x-axis
newOrigin = template.Shape.Vertexes[4].Point
edgedir = template.Shape.Edges[3].Curve.Direction
rot = App.Rotation(-edgedir, V3(),V3(0, 0, 1), 'XZY')
template.Placement = App.Placement(newOrigin, rot).inverse()
doc.recompute()
templateVertices = [v.Point/scale for v in template.Shape.Vertexes]
print(useInches)
print(scale)
printVertices(templateVertices)
spreadsheetVertices(templateVertices)
if form.result==userCancelled:
pass
doc = App.ActiveDocument
V3 = App.Vector
#get inputs from Gui
form = TransitionGuiClass()
form.exec_()
makeTemplate(form)