Square to round transition

Need help, or want to share a macro? Post here!
Forum rules
Be nice to others! Respect the FreeCAD code of conduct!
edwilliams16
Veteran
Posts: 3108
Joined: Thu Sep 24, 2020 10:31 pm
Location: Hawaii
Contact:

Re: Square to round transition

Post by edwilliams16 »

OK. It looks like I have to test with a fresh FreeCAD instance to be sure it's clean. I added a spreadsheet output to see how that worked.

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)



User avatar
johnwang
Veteran
Posts: 1345
Joined: Sun Jan 27, 2019 12:41 am

Re: Square to round transition

Post by johnwang »

Here is the first quarter, but the length of the bottom line of the FIRST BACK TRIANGLE is not right yet.
I changed the Height to 250 to make the drawing smaller.
t.png
t.png (7.02 KiB) Viewed 1166 times
Last edited by johnwang on Thu Aug 11, 2022 12:15 pm, edited 1 time in total.
hfc series CAE workbenches for FreeCAD (hfcNastran95, hfcMystran, hfcFrame3DD, hfcSU2 and more)
jfc4120
Posts: 448
Joined: Sat Jul 02, 2022 11:16 pm

Re: Square to round transition

Post by jfc4120 »

@johnwang thanks, I definitely want to program fittings the way I am used to, because I still do not understand all of the other Mesh programming and things like that.

Right now being new I am happy working with wire and faces.

But @edwilliams16 did a wonderful job on his program. I have studied over but it will be a long time before I understand those things. I definitely appreciate both of you for assistance.
jfc4120
Posts: 448
Joined: Sat Jul 02, 2022 11:16 pm

Re: Square to round transition

Post by jfc4120 »

@edwilliams16 your final looked good, probably mine was .09 off, due to I use polygon and you used true arc lengths. If for example I increase number to say 36 it would probably only be .01 or .02 off which is close.

In the previous version I only got the error <class 'NameError'>: name 'useInches' is not defined, but it still worked anyway.
User avatar
johnwang
Veteran
Posts: 1345
Joined: Sun Jan 27, 2019 12:41 am

Re: Square to round transition

Post by johnwang »

jfc4120 wrote: Sat Jul 23, 2022 2:20 am @Right now being new I am happy working with wire and faces.
If you open my macro, you can see only line in used.
hfc series CAE workbenches for FreeCAD (hfcNastran95, hfcMystran, hfcFrame3DD, hfcSU2 and more)
jfc4120
Posts: 448
Joined: Sat Jul 02, 2022 11:16 pm

Re: Square to round transition

Post by jfc4120 »

@johnwang I have looked at it. I also have been working on my first python program:

https://forum.freecadweb.org/viewtopic.php?f=22&t=70474

I got to the point where I can figure the angle, but I haven't figured out how to draw them yet.

I had to go back and do radian to degree conversions.

What it does in the other basicad is:
Attachments
Screenshot 2022-07-23 115149.png
Screenshot 2022-07-23 115149.png (7.33 KiB) Viewed 1025 times
edwilliams16
Veteran
Posts: 3108
Joined: Thu Sep 24, 2020 10:31 pm
Location: Hawaii
Contact:

Re: Square to round transition

Post by edwilliams16 »

jfc4120 wrote: Sat Jul 23, 2022 5:24 am @edwilliams16 your final looked good, probably mine was .09 off, due to I use polygon and you used true arc lengths. If for example I increase number to say 36 it would probably only be .01 or .02 off which is close.

In the previous version I only got the error <class 'NameError'>: name 'useInches' is not defined, but it still worked anyway.
Just comment out the line

Code: Select all

radius *= adjust
if you don't want the true arc length correction.
User avatar
johnwang
Veteran
Posts: 1345
Joined: Sun Jan 27, 2019 12:41 am

Re: Square to round transition

Post by johnwang »

jfc4120 wrote: Sat Jul 23, 2022 4:52 pm What it does in the other basicad is:
It looks like a hand rail I drawed the other day.
hfc series CAE workbenches for FreeCAD (hfcNastran95, hfcMystran, hfcFrame3DD, hfcSU2 and more)
User avatar
johnwang
Veteran
Posts: 1345
Joined: Sun Jan 27, 2019 12:41 am

Re: Square to round transition

Post by johnwang »

I tried to print out all the points. Could you also output from your program, so I can compare which line is wrong. Thanks.
Last edited by johnwang on Thu Aug 11, 2022 12:15 pm, edited 1 time in total.
hfc series CAE workbenches for FreeCAD (hfcNastran95, hfcMystran, hfcFrame3DD, hfcSU2 and more)
jfc4120
Posts: 448
Joined: Sat Jul 02, 2022 11:16 pm

Re: Square to round transition

Post by jfc4120 »

Yes, I have been busy doing something but I will get to it as soon as possible, on mobile now.

Edit, here are dimensions, but inches used.
Attachments
t1_compare.FCStd
(10.54 KiB) Downloaded 11 times
sq7.png
sq7.png (14.71 KiB) Viewed 743 times
sq5.png
sq5.png (14.31 KiB) Viewed 747 times
Post Reply